Merge remote-tracking branch 'origin/master' into feat/loader-entry-disabled-interpolation
# Conflicts: # vendor/README.md # vendor/loader/src/config/entry.ts
This commit is contained in:
Vendored
+3
-4
@@ -44,11 +44,10 @@ Keep this log exhaustive — every divergence from upstream must be listed.
|
||||
12. **`include/src/index.ts` serialized child-tree mutation and `hmr/src/index.ts` main-watcher initial-scan suppression**: every Include child-tree mutation (initial apply, refresh, `internal/update` patch re-application) runs through one per-Include queue, because the group's transactional `update` is not reentrant — two concurrent applies interleave create and rollback on the same entries and strand the Include fiber without ever settling. The HMR main watcher passes `ignoreInitial: true`: the initial scan re-announced files boot had just consumed, and its `add` for a config file refreshed an Include mid-initial-apply; once serialized, a failing initial apply's rollback disposed HMR, whose teardown drain waited on the queued refresh sitting behind that same apply — a deadlock that exited 13 with no diagnostic. `registerConfig()` keeps its own `ignoreInitial: false` watcher because a user patch layer present at registration must apply once. Covered by the patch-overlay boot-failure built-bin case in `apps/cli/tests/built-bin.e2e.ts`.
|
||||
13. **`include/src/index.ts` `writeTask` type**: widened the optional `writeTask?: NodeJS.Timeout` property to `NodeJS.Timeout | undefined` — the debounced writer assigns `undefined` on flush, which `exactOptionalPropertyTypes` rejects on a plain optional. Type-only; no behavior change.
|
||||
14. **`include/src/index.ts` durable debounced writes**: serialized and tracked config-file writes, retried transient `EACCES`/`EBUSY`/`EPERM` rename failures with a bounded backoff, observed asynchronous timer rejections, and drained the latest write during Include teardown. Windows can briefly retain a destination handle after a Loader child disposes; the upstream fire-and-forget rename escaped as an unhandled rejection and could lose the persisted `disabled` state. A terminal failure is logged by the asynchronous writer and remains on the queue so `Include.stop()` rethrows it instead of silently declaring persistence complete; Cordis's ordinary fiber teardown retains its separate error-containment contract. Covered by `packages/host/directory-picker-auto/tests/loader-composition.spec.ts` with injected transient and terminal rename failures.
|
||||
15. **Lazy Loader config resolution across `cordis/src/{events,fiber}.ts`, `loader/src/{index,config/entry}.ts`, `include/src/index.ts`, and `hmr/src/index.ts`**: ports [cordiverse/cordis#41](https://github.com/cordiverse/cordis/pull/41), retaining raw fiber config and resolving it through `internal/config` only after declared injections are active. Provider replacement re-resolves the raw expression, pending updates retain it, and HMR transfers it. Resolution applies only to the entry root, so child plugins mounted by a row keep caller-owned config identity. Include adds a static entry-config resolver so its own options interpolate while nested row `!!js` nodes remain deferred. Deferred failures retain the owning row diagnostic, and tree teardown does not persist failure-driven self-disposal. Covered by `packages/boot/app-boot/tests/{app-boot,user-patches}.spec.ts`, `packages/boot/cmdline/tests/cmdline.spec.ts`, `apps/cli/tests/web-agent-presets.e2e.ts`, and the built custom-profile cases in `apps/cli/tests/built-bin.e2e.ts`.
|
||||
16. **In-memory Loader entry activation in `loader/src/config/entry.ts`**: an invocation can activate a row shipped with `disabled: true` without mutating its serialized options. The override belongs to the mounted entry object, survives Include config reapplication, respects disabled ancestors, and disappears with the entry. Covered by `packages/boot/cmdline/tests/cmdline.spec.ts` and `apps/web/tests/hmr-live.e2e.ts`.
|
||||
15. **Lazy Loader config resolution across `cordis/src/{events,fiber}.ts`, `loader/src/{index,config/entry}.ts`, `include/src/index.ts`, and `hmr/src/index.ts`**: ports [cordiverse/cordis#41](https://github.com/cordiverse/cordis/pull/41), retaining raw fiber config and resolving it through `internal/config` only after declared injections are active. Provider replacement re-resolves the raw expression, pending updates retain it, and HMR transfers it. Resolution applies only to the entry root, so child plugins mounted by a row keep caller-owned config identity. Include declares the `EntryGroup.key` tree-carrier marker (as Group does): its config is entry and patch lists, so interpolation keeps it literal and a `!!js` expression inside a nested row's config resolves lazily in that row's own fiber (Include's own `path` therefore stays literal too). Deferred failures retain the owning row diagnostic, and tree teardown does not persist failure-driven self-disposal. Covered by `packages/boot/app-boot/tests/{app-boot,user-patches}.spec.ts`, `packages/boot/cmdline/tests/cmdline.spec.ts`, `apps/cli/tests/web-agent-presets.e2e.ts`, and the built custom-profile cases in `apps/cli/tests/built-bin.e2e.ts`.
|
||||
16. **`cordis/package.json` publishes `src`**: added `src` to the `files` list, joining the other eight vendored packages. Cordis declares `"./src/*": "./src/*"` in its exports, so a tarball without `src` publishes an export map pointing at absent files; the release change judgement also reads `files` to decide whether a diff reaches the payload, and a package whose only published paths are build output has no tracked path to match.
|
||||
17. **`@deepseek-ai` rescope**: every vendored manifest `name`, every internal dependency entry among the vendored set, and every module specifier that reaches them use the scoped names in the manifest table's `npm name` column. Directory names, version numbers, and dependency ranges are unchanged, and no upstream runtime identifier is renamed — `Symbol.for('schemastery')` and Schemastery's `vendor:` metadata field keep their upstream values. Re-apply with `pnpm run rescope-vendor --apply` after a sync; the table's two name columns are the mapping, restated for consumers in [docs/rescope.md](../docs/rescope.md).
|
||||
18. **`cordis/package.json` publishes `src`**: added `src` to the `files` list, joining the other eight vendored packages. Cordis declares `"./src/*": "./src/*"` in its exports, so a tarball without `src` publishes an export map pointing at absent files; the release change judgement also reads `files` to decide whether a diff reaches the payload, and a package whose only published paths are build output has no tracked path to match.
|
||||
19. **Entry `disabled` interpolation in `loader/src/config/entry.ts`**: a `disabled: !!js` expression evaluates against the loader context at every mount decision; the raw node stays in the options, so write-back keeps the `!!js` form. `disabled` is the only interpolated metadata field. Covered by `packages/boot/app-boot/tests/user-patches.spec.ts` and `apps/cli/tests/windows-shell.spec.ts`.
|
||||
18. **Entry `disabled` interpolation in `loader/src/config/entry.ts`**: a `disabled: !!js` expression evaluates against the loader context at every mount decision; the raw node stays in the options, so write-back keeps the `!!js` form. `disabled` is the only interpolated metadata field. Covered by `packages/boot/app-boot/tests/user-patches.spec.ts` and `apps/cli/tests/windows-shell.spec.ts`.
|
||||
|
||||
## Sync procedure
|
||||
|
||||
|
||||
Vendored
+7
-15
@@ -1,4 +1,4 @@
|
||||
import { EntryConfigResolver, EntryTree, interpolate, isJsExpr, type EntryOptions } from '@deepseek-ai/cordis-plugin-loader'
|
||||
import { EntryGroup, EntryTree, isJsExpr, type EntryOptions } from '@deepseek-ai/cordis-plugin-loader'
|
||||
import { Context, Service } from '@deepseek-ai/cordis'
|
||||
import { extname } from 'node:path'
|
||||
import { access, constants, readFile, rename, writeFile } from 'node:fs/promises'
|
||||
@@ -174,20 +174,12 @@ export namespace Include {
|
||||
export class Include extends EntryTree {
|
||||
static inject = ['loader']
|
||||
|
||||
/**
|
||||
* Resolve Include's own options while preserving nested entry expressions.
|
||||
* @param ctx - the Include plugin context.
|
||||
* @param config - the raw Include config.
|
||||
* @returns resolved Include options with `initial` and `patches` untouched.
|
||||
*/
|
||||
static [EntryConfigResolver](ctx: Context, config: Include.Config): Include.Config {
|
||||
const { initial, patches, ...own } = config
|
||||
return {
|
||||
...interpolate(ctx, own),
|
||||
...(initial === undefined ? {} : { initial }),
|
||||
...(patches === undefined ? {} : { patches }),
|
||||
}
|
||||
}
|
||||
// Tree-carrier marker (the Group plugin declares the same): this config is
|
||||
// entry and patch lists, so the Loader's `internal/config` interpolation
|
||||
// keeps it literal — a `!!js` expression inside a nested row's config
|
||||
// belongs to that row's fiber, resolving lazily in the row's own context.
|
||||
// Include's own fields (`path`, `enableLogs`) therefore stay literal too.
|
||||
static readonly [EntryGroup.key] = true
|
||||
|
||||
public filename: string
|
||||
private type?: string
|
||||
|
||||
Vendored
+2
-31
@@ -5,17 +5,6 @@ import { EntryGroup } from './group.ts'
|
||||
import { EntryTree } from './tree.ts'
|
||||
import { evaluate, isJsExpr } from './utils.ts'
|
||||
|
||||
/** Static plugin hook for resolving a container config while preserving nested entry configs. */
|
||||
export const EntryConfigResolver = Symbol.for('cordis.loader.entry-config-resolver')
|
||||
|
||||
/**
|
||||
* Resolve a container's own config while preserving any nested entry configs.
|
||||
* @param ctx - the container plugin context.
|
||||
* @param config - the container's raw config.
|
||||
* @returns the config to validate for this activation.
|
||||
*/
|
||||
export type EntryConfigResolver = (ctx: Context, config: any) => any
|
||||
|
||||
/** Serialized plugin entry options stored in loader config files. */
|
||||
export interface EntryOptions {
|
||||
/** Stable id inside the containing entry tree. */
|
||||
@@ -73,8 +62,6 @@ export class Entry {
|
||||
|
||||
_initTask?: Promise<void>
|
||||
_disposing = 0
|
||||
private runtimeEnabled = false
|
||||
private runtimeEnableTask?: Promise<void>
|
||||
|
||||
constructor(public loader: Loader) {
|
||||
this.ctx = loader.ctx.extend({ [Entry.key]: this })
|
||||
@@ -101,10 +88,10 @@ export class Entry {
|
||||
private _disabled(options: EntryOptions) {
|
||||
// group is always enabled
|
||||
if (options.group) return false
|
||||
if (this.disabledOf(options) && !this.runtimeEnabled) return true
|
||||
if (this.disabledOf(options)) return true
|
||||
let entry = this.parent.ctx.fiber.entry
|
||||
while (entry) {
|
||||
if (this.disabledOf(entry.options) && !entry.runtimeEnabled) return true
|
||||
if (this.disabledOf(entry.options)) return true
|
||||
entry = entry.parent.ctx.fiber.entry
|
||||
}
|
||||
return false
|
||||
@@ -120,22 +107,6 @@ export class Entry {
|
||||
: Boolean(options.disabled)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable this in-memory entry without rewriting its configured `disabled`
|
||||
* value; the override survives config reapplication for this entry object.
|
||||
* @returns a promise settling after its initial activation attempt.
|
||||
*/
|
||||
enableRuntime(): Promise<void> {
|
||||
if (this.runtimeEnableTask !== undefined) return this.runtimeEnableTask
|
||||
this.runtimeEnabled = true
|
||||
this.runtimeEnableTask = this.refresh().catch((error: unknown) => {
|
||||
this.runtimeEnabled = false
|
||||
this.runtimeEnableTask = undefined
|
||||
throw error
|
||||
})
|
||||
return this.runtimeEnableTask
|
||||
}
|
||||
|
||||
evaluate(expr: string) {
|
||||
return evaluate(this.ctx, expr)
|
||||
}
|
||||
|
||||
Vendored
+5
-8
@@ -1,12 +1,7 @@
|
||||
import { Context, FiberState, Inject, Service, type Fiber } from '@deepseek-ai/cordis'
|
||||
import { defineProperty, isNullable, type Dict } from '@deepseek-ai/cosmokit'
|
||||
import { ModuleLoader } from './internal.ts'
|
||||
import {
|
||||
Entry,
|
||||
EntryConfigResolver,
|
||||
type EntryConfigResolver as ConfigResolver,
|
||||
type EntryOptions,
|
||||
} from './config/entry.ts'
|
||||
import { Entry, type EntryOptions } from './config/entry.ts'
|
||||
import { EntryGroup } from './config/group.ts'
|
||||
import isolate from './config/isolate.ts'
|
||||
import { EntryTree } from './config/tree.ts'
|
||||
@@ -97,10 +92,12 @@ export class Loader extends EntryTree {
|
||||
ctx.on('internal/config', function (this: Fiber, _config, next) {
|
||||
const config = next()
|
||||
if (!this.entry || this.parent.fiber?.entry === this.entry) return config
|
||||
// Tree carriers (Group, Include) keep their configs literal: their
|
||||
// entry and patch lists hold other rows' configs, whose `!!js`
|
||||
// expressions belong to those rows' own fibers.
|
||||
const plugin = this.runtime?.callback as Record<PropertyKey, unknown> | undefined
|
||||
if (plugin?.[EntryGroup.key]) return config
|
||||
const resolve = plugin?.[EntryConfigResolver] as ConfigResolver | undefined
|
||||
return resolve ? resolve(this.ctx, config) : interpolate(this.ctx, config)
|
||||
return interpolate(this.ctx, config)
|
||||
}, { global: true })
|
||||
|
||||
ctx.on('internal/update', async function (config, noSave, next) {
|
||||
|
||||
Reference in New Issue
Block a user