Merge branch 'worktree/ci-native-windows-20260808' into worktree/ci-native-windows-coverage-20260808
This commit is contained in:
@@ -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 packages/boot/app-boot/README.md
|
||||
README.md: c256b89288e3e384c1dd3e64629a06d7cfef31f6
|
||||
README.zh.md: 454d504be72c2120ec4aeaa1b22545e7d6ba2fee
|
||||
README.md: c060e99acb8cc40b8de92806727eea9e3723b7d2
|
||||
README.zh.md: f28b89e63e4bd6f07f2f7a55dfba5c2f78862feb
|
||||
|
||||
@@ -33,7 +33,7 @@ This package carries no loader hooks and no dev-mode surface. The [`dsh` app](..
|
||||
|
||||
## Profiles
|
||||
|
||||
A profile is a directory under `$DSH_HOME/profiles/<name>` (the Harness home resolves through [`resolveDshHome`](../../util/paths/README.md): `$DSH_HOME`, else `~/.dsh`) holding a `package.json` — out-of-tree plugin `dependencies` plus the profile manifest `dsh.profile` with its ordered `bundles` layer list — and the user's own `cordis.patch.yml`. A bundle is an npm package whose manifest declares `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }`; `loadProfile` resolves each `dsh.profile.bundles` name two-anchored (the dsh installation first, then the profile directory) and fails loud on a listed package without a bundle declaration. `composeEntries` applies patch layers over an empty entry list through the include's own `applyEntryPatches`, so composition, flag derivation, and config dumps can never drift from what boots. `healProfilesModuleFallback` maintains the flat `$DSH_HOME/profiles/node_modules` directory — one symlink per package the installation's app and bundles depend on — so bare plugin names in any profile resolve through Node's ordinary parent-walk without pnpm ever managing in-box packages. `PROFILE_TEMPLATES` (`web`, `headless`) auto-initialize on first use; other names fail loud until `initProfile` creates them (the `dsh plugin` path).
|
||||
A profile is a directory under `$DSH_HOME/profiles/<name>` (the Harness home resolves through [`resolveDshHome`](../../util/paths/README.md): `$DSH_HOME`, else `~/.dsh`) holding a `package.json` — out-of-tree plugin `dependencies` plus the profile manifest `dsh.profile` with its ordered `bundles` layer list — and the user's own `cordis.patch.yml`. A bundle is an npm package whose manifest declares `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }`; `loadProfile` resolves each `dsh.profile.bundles` name two-anchored (the dsh installation first, then the profile directory) and fails loud on a listed package without a bundle declaration. `composeEntries` applies patch layers over an empty entry list through the include's own `applyEntryPatches`, so composition, flag derivation, and config dumps cannot drift from what boots. `healProfilesModuleFallback` maintains the flat `$DSH_HOME/profiles/node_modules` directory — one symlink per package the installation's app and bundles depend on — so bare plugin names in any profile resolve through Node's ordinary parent-walk without pnpm managing in-box packages. `PROFILE_TEMPLATES` (`web`, `headless`) auto-initialize on first use; other names fail loud until `initProfile` creates them (the `dsh plugin` path). `loadProfile` normalizes an exact installation-owned bundle tuple to its shipped template while preserving every other manifest field; any extra, missing, or reordered entry makes the list user-owned and leaves it unchanged.
|
||||
|
||||
User-level machine-local preferences also live in the Harness home:
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ Loader 并发挂载各个条目,因此当其他环节失败时,某个界面
|
||||
|
||||
## Profile
|
||||
|
||||
profile 是位于 `$DSH_HOME/profiles/<name>` 下的目录(Harness home 由 [`resolveDshHome`](../../util/paths/README.md) 解析:先取 `$DSH_HOME`,否则取 `~/.dsh`),其中包含一个 `package.json`(树外插件 `dependencies`,加上 profile manifest `dsh.profile` 及其有序的 `bundles` 层列表)和用户自己的 `cordis.patch.yml`。组合包是在 manifest 中声明 `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }` 的 npm 包;`loadProfile` 以双锚点解析每个 `dsh.profile.bundles` 名称(先从 dsh 安装目录,再从 profile 目录),列出的包若没有组合包声明则大声失败。`composeEntries` 通过 include 自己的 `applyEntryPatches` 在空条目列表之上应用各 patch 层,因此组合、标志推导和配置 dump 绝不会与实际启动内容发生偏离。`healProfilesModuleFallback` 维护扁平的 `$DSH_HOME/profiles/node_modules` 目录(安装目录的应用与各组合包依赖的每个包对应一个符号链接),使任意 profile 中的裸插件名都能经 Node 常规的逐级向上查找解析,而 pnpm 从不管理随安装内置的包。`PROFILE_TEMPLATES`(`web`、`headless`)在首次使用时自动初始化;其他名称在 `initProfile` 创建之前都会大声失败(即 `dsh plugin` 路径)。
|
||||
profile 是位于 `$DSH_HOME/profiles/<name>` 下的目录(Harness home 由 [`resolveDshHome`](../../util/paths/README.md) 解析:先取 `$DSH_HOME`,否则取 `~/.dsh`),其中包含一个 `package.json`(树外插件 `dependencies`,加上 profile manifest `dsh.profile` 及其有序的 `bundles` 层列表)和用户自己的 `cordis.patch.yml`。组合包是在 manifest 中声明 `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }` 的 npm 包;`loadProfile` 以双锚点解析每个 `dsh.profile.bundles` 名称(先从 dsh 安装目录,再从 profile 目录),列出的包若没有组合包声明则大声失败。`composeEntries` 通过 include 自己的 `applyEntryPatches` 在空条目列表之上应用各 patch 层,因此组合、标志推导和配置 dump 不会与实际启动内容发生偏离。`healProfilesModuleFallback` 维护扁平的 `$DSH_HOME/profiles/node_modules` 目录(安装目录的应用与各组合包依赖的每个包对应一个符号链接),使任意 profile 中的裸插件名都能经 Node 常规的逐级向上查找解析,且无需 pnpm 管理随安装内置的包。`PROFILE_TEMPLATES`(`web`、`headless`)在首次使用时自动初始化;其他名称在 `initProfile` 创建之前都会大声失败(即 `dsh plugin` 路径)。`loadProfile` 会将与安装自有组合包元组完全一致的列表规范化为随发行版交付的模板,同时保留 manifest 中其他所有字段;一旦条目有任何额外、缺失或重排,该列表就归用户所有并保持不变。
|
||||
|
||||
用户级的机器本地偏好同样位于 Harness home 中:
|
||||
|
||||
|
||||
@@ -114,6 +114,11 @@ export function resolveProfileDir(name: string, home: string = resolveDshHome())
|
||||
/** The shipped profile templates auto-initialized on first use, by name. */
|
||||
export const PROFILE_TEMPLATES: Record<string, readonly string[]> = {
|
||||
web: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'],
|
||||
headless: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-headless'],
|
||||
}
|
||||
|
||||
/** Installation-owned bundle tuples normalized to the shipped template. */
|
||||
const INSTALLATION_OWNED_PROFILE_TUPLES: Record<string, readonly string[]> = {
|
||||
headless: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app', '@deepseek-ai/dsh-headless'],
|
||||
}
|
||||
|
||||
@@ -279,6 +284,32 @@ export function writeProfileManifest(dir: string, manifest: ProfileManifest): vo
|
||||
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest, undefined, 2) + '\n')
|
||||
}
|
||||
|
||||
/** Return whether two bundle lists have the same values in the same order. */
|
||||
function sameBundles(left: readonly string[], right: readonly string[]): boolean {
|
||||
return left.length === right.length && left.every((value, index) => value === right[index])
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize an exact installation-owned bundle tuple to its shipped template
|
||||
* while preserving every other manifest field. Any other list is user-owned.
|
||||
*/
|
||||
function normalizeShippedProfile(name: string, dir: string, manifest: ProfileManifest): ProfileManifest {
|
||||
const installationOwned = INSTALLATION_OWNED_PROFILE_TUPLES[name]
|
||||
const current = PROFILE_TEMPLATES[name]
|
||||
const bundles = manifest.dsh?.profile?.bundles
|
||||
if (installationOwned === undefined || current === undefined || bundles === undefined
|
||||
|| !sameBundles(bundles, installationOwned)) return manifest
|
||||
const normalized: ProfileManifest = {
|
||||
...manifest,
|
||||
dsh: {
|
||||
...manifest.dsh,
|
||||
profile: { ...manifest.dsh?.profile, bundles: [...current] },
|
||||
},
|
||||
}
|
||||
writeProfileManifest(dir, normalized)
|
||||
return normalized
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a package's root directory from one anchor without depending on the
|
||||
* package exporting `./package.json` (`require.resolve` would need that):
|
||||
@@ -350,7 +381,7 @@ export function loadProfile(
|
||||
}
|
||||
initProfile(dir, template)
|
||||
}
|
||||
const manifest = readProfileManifest(binName, dir)
|
||||
const manifest = normalizeShippedProfile(name, dir, readProfileManifest(binName, dir))
|
||||
// A hand-written profile manifest may omit the dsh section entirely.
|
||||
const bundles = manifest.dsh?.profile?.bundles ?? []
|
||||
const layers = bundles.map((packageName): ProfileLayer => {
|
||||
|
||||
@@ -161,6 +161,33 @@ describe('loadProfile', () => {
|
||||
.toEqual([...PROFILE_TEMPLATES.web ?? []])
|
||||
})
|
||||
|
||||
it('normalizes only the exact installation-owned headless bundle tuple', () => {
|
||||
const anchor = stageInstallation({
|
||||
'@deepseek-ai/dsh-base': { patch: '[]\n' },
|
||||
'@deepseek-ai/dsh-web-app': { patch: '[]\n' },
|
||||
'@deepseek-ai/dsh-headless': { patch: '[]\n' },
|
||||
'custom-bundle': { patch: '[]\n' },
|
||||
})
|
||||
const home = tmp()
|
||||
const stock = resolveProfileDir('headless', home)
|
||||
initProfile(stock, [
|
||||
'@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app', '@deepseek-ai/dsh-headless',
|
||||
])
|
||||
loadProfile('t', 'headless', anchor, home)
|
||||
expect(readProfileManifest('t', stock).dsh?.profile?.bundles)
|
||||
.toEqual(['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-headless'])
|
||||
|
||||
const customHome = tmp()
|
||||
const custom = resolveProfileDir('headless', customHome)
|
||||
initProfile(custom, [
|
||||
'@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app', '@deepseek-ai/dsh-headless', 'custom-bundle',
|
||||
])
|
||||
loadProfile('t', 'headless', anchor, customHome)
|
||||
expect(readProfileManifest('t', custom).dsh?.profile?.bundles).toEqual([
|
||||
'@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app', '@deepseek-ai/dsh-headless', 'custom-bundle',
|
||||
])
|
||||
})
|
||||
|
||||
it('fails loud when a listed bundle declares no dsh.bundle', () => {
|
||||
const anchor = stageInstallation({ 'not-a-bundle': {} })
|
||||
const home = tmp()
|
||||
|
||||
Reference in New Issue
Block a user