diff --git a/apps/cli/reference/README.i18n.yaml b/apps/cli/reference/README.i18n.yaml index 96e7b4c7b0..8af7762497 100644 --- a/apps/cli/reference/README.i18n.yaml +++ b/apps/cli/reference/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write apps/cli/reference/README.md -README.md: 33de399dc4b2b8e67ed24ed046fcc0da2ff0a7ac -README.zh.md: 0f20245f318e31159780d29cca955fc85a5b5481 +README.md: eb33816b3ac859e8173e62635f74abbe13fb8462 +README.zh.md: ed84927aa4f211926fd32750385268dc5153b3a9 diff --git a/apps/cli/reference/README.md b/apps/cli/reference/README.md index 33de399dc4..eb33816b3a 100644 --- a/apps/cli/reference/README.md +++ b/apps/cli/reference/README.md @@ -39,7 +39,7 @@ dsh --profile web --dump-default-config dsh --profile web --patch ./extra.yml --dump-config ``` -`--dump-default-config` prints only the bundle layers; `--dump-config` adds the profile's `cordis.patch.yml`, the home-level `$DSH_HOME/cordis.patch.yml`, and `--patch` overlays. Both print comments naming the file that supplied each row and every overlay that changed it; `!!js` expressions remain unevaluated, relative plugin names in inserted rows resolve beside their patch file, and unmatched patch targets are reported on stderr. A dump never runs app command-line providers, so it shows the composed tree before any app argument is resolved and rejects an invocation that carries app arguments. +`--dump-default-config` prints only the bundle layers; `--dump-config` adds the profile's `cordis.patch.yml`, the home-level `$DSH_HOME/cordis.patch.yml`, and `--patch` overlays. Both print comments naming the file that supplied each row and every overlay that changed it; `!!js` expressions remain unevaluated, relative plugin names in inserted rows resolve beside their patch file, and unmatched patch targets are reported on stderr. A dump initializes missing profile files but does not prepare the runtime module fallback under `$DSH_HOME/profiles/node_modules`. It never runs app command-line providers, so it shows the composed tree before any app argument is resolved and rejects an invocation that carries app arguments. ## Plugin management diff --git a/apps/cli/reference/README.zh.md b/apps/cli/reference/README.zh.md index 0f20245f31..ed84927aa4 100644 --- a/apps/cli/reference/README.zh.md +++ b/apps/cli/reference/README.zh.md @@ -39,7 +39,7 @@ dsh --profile web --dump-default-config dsh --profile web --patch ./extra.yml --dump-config ``` -`--dump-default-config` 只打印组合包各层;`--dump-config` 额外加上 profile 的 `cordis.patch.yml`、home 级的 `$DSH_HOME/cordis.patch.yml` 和 `--patch` overlay。两者都会打印注释,标明每行由哪个文件提供,以及哪些 overlay 修改过它;`!!js` 表达式保持未求值,插入行中的相对插件名以各自 patch 文件所在目录解析,找不到目标的 patch 会报告到 stderr。dump 操作不会运行应用的命令行参数提供方,因此展示的是解析任何应用参数之前的组合配置树;如果调用中包含应用参数,dump 会拒绝该调用。 +`--dump-default-config` 只打印组合包各层;`--dump-config` 额外加上 profile 的 `cordis.patch.yml`、home 级的 `$DSH_HOME/cordis.patch.yml` 和 `--patch` overlay。两者都会打印注释,标明每行由哪个文件提供,以及哪些 overlay 修改过它;`!!js` 表达式保持未求值,插入行中的相对插件名以各自 patch 文件所在目录解析,找不到目标的 patch 会报告到 stderr。dump 操作会初始化缺失的 profile 文件,但不会准备 `$DSH_HOME/profiles/node_modules` 下的运行时模块 fallback。它不会运行应用的命令行参数提供方,因此展示的是解析任何应用参数之前的组合配置树;如果调用中包含应用参数,dump 会拒绝该调用。 ## 插件管理 diff --git a/apps/cli/src/bin.ts b/apps/cli/src/bin.ts index 9e7c600c8d..321849f2d9 100644 --- a/apps/cli/src/bin.ts +++ b/apps/cli/src/bin.ts @@ -41,7 +41,7 @@ switch (invocation.mode) { } case 'dump-config': { const { runDumpConfig } = await import('./dump-config.ts') - await runDumpConfig(invocation.profile, invocation.defaultOnly, invocation.patches) + runDumpConfig(invocation.profile, invocation.defaultOnly, invocation.patches) break } default: diff --git a/apps/cli/src/dump-config.ts b/apps/cli/src/dump-config.ts index dc2089e7c8..1754eb4efd 100644 --- a/apps/cli/src/dump-config.ts +++ b/apps/cli/src/dump-config.ts @@ -26,10 +26,9 @@ const NAME = 'dsh' * (the recovery diagnostic for a broken `cordis.patch.yml`, which is then * never parsed). * @param patches - `--patch` overlay paths, in argv order. - * @returns settlement after the profile is healed and the dump is written. */ -export async function runDumpConfig(profile: string, defaultOnly: boolean, patches: readonly string[]): Promise { - const loaded = await prepareProfile(profile, !defaultOnly) +export function runDumpConfig(profile: string, defaultOnly: boolean, patches: readonly string[]): void { + const loaded = prepareProfile(profile, !defaultOnly) const layers: ConfigDumpLayer[] = loaded.layers.map(layer => ({ label: layer.packageName, patches: layer.patches, diff --git a/apps/cli/src/profile-boot.ts b/apps/cli/src/profile-boot.ts index 30058a380f..674095bca7 100644 --- a/apps/cli/src/profile-boot.ts +++ b/apps/cli/src/profile-boot.ts @@ -103,20 +103,19 @@ export function resolveTelemetryPatch(disabledEnv: string | undefined, hasRow: b } /** - * Load a resolved profile for `name`: heal the shared module fallback, then - * (re)write the empty root config. The root is always rewritten: the whole - * composition is patch layers, and the vendored Loader's tree write-back (a - * plugin self-disposing persists the current tree) can bake composed rows - * into this file — which would duplicate every bundle insert on the next - * boot. The file exists on disk only because the Loader needs a real include - * root to anchor `baseUrl` at the profile directory (the config dump anchors - * on the same file, so both compose over the identical base). + * Load a resolved profile for `name` and (re)write the empty root config. The + * root is always rewritten: the whole composition is patch layers, and the + * vendored Loader's tree write-back (a plugin self-disposing persists the + * current tree) can bake composed rows into this file — which would duplicate + * every bundle insert on the next boot. The file exists on disk only because + * the Loader needs a real include root to anchor `baseUrl` at the profile + * directory (the config dump anchors on the same file, so both compose over + * the identical base). * @param name - the profile name. * @param userLayer - `false` skips parsing `cordis.patch.yml` (the default dump). * @returns the loaded profile. */ -export async function prepareProfile(name: string, userLayer = true): Promise { - await healProfilesModuleFallback(INSTALL_ANCHOR) +export function prepareProfile(name: string, userLayer = true): Profile { const profile = loadProfile(NAME, name, INSTALL_ANCHOR, undefined, { userLayer }) writeFileSync(join(profile.dir, PROFILE_ROOT_FILENAME), PROFILE_ROOT_CONFIG) return profile @@ -158,7 +157,8 @@ async function composeProfile( name: string, patchFiles: readonly string[], ): Promise { - const profile = await prepareProfile(name) + await healProfilesModuleFallback(INSTALL_ANCHOR) + const profile = prepareProfile(name) const homePatches = loadOptionalPatches(NAME, homePatchPath()) ?? [] const overlays = patchFiles.flatMap(file => loadOverlayPatches(NAME, resolve(file))) const bundlePatches = profile.layers.flatMap(layer => layer.patches) diff --git a/apps/cli/tests/built-bin.e2e.ts b/apps/cli/tests/built-bin.e2e.ts index eb22fed232..5f9791a050 100644 --- a/apps/cli/tests/built-bin.e2e.ts +++ b/apps/cli/tests/built-bin.e2e.ts @@ -911,6 +911,7 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)', expect(stdout).toContain('agents: []') expect(stdout).toContain('# == @deepseek-ai/dsh-base') expect(stdout).toContain("name: '@deepseek-ai/dsh-host-webserver'") + expect(existsSync(join(home, 'profiles', 'node_modules'))).toBe(false) }, 30_000) it('prints the headless profile without Host or browser layers', async () => {