Merge worktree/ci-native-windows-20260808 into worktree/ci-native-windows-coverage-20260808

This commit is contained in:
Tianyi Cui
2026-08-09 13:01:04 +08:00
88 changed files with 2293 additions and 326 deletions

View File

@@ -3,4 +3,4 @@
# 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: 88d1c4ad0ced2f5a6440a1b64e34e738a842f938
README.zh.md: 454d504be72c2120ec4aeaa1b22545e7d6ba2fee

View File

@@ -31,6 +31,8 @@ Loader 并发挂载各个条目,因此当其他环节失败时,某个界面
此包不包含 loader 钩子,也不提供开发模式接口。[`dsh` 应用](../../../apps/cli/README.md)持有自己的 Node 源码启动钩子,并在启动序列中使用这些 helper构建后的消费方仍使用普通 Node 包解析。
<a id="profiles"></a>
## 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` 路径)。

View File

@@ -39,14 +39,14 @@ describe('RepositoryCache', () => {
calls.push(directory)
await fakePackage(directory)
}
const cache = new RepositoryCache(root, install)
const cache = new RepositoryCache(root, { install })
const specifier = 'github:owner/repository#0123456789abcdef'
const [first, concurrent] = await Promise.all([cache.resolve(specifier), cache.resolve(specifier)])
expect(concurrent).toBe(first)
expect(calls).toHaveLength(1)
const reopened = new RepositoryCache(root, async () => { throw new Error('cache miss') })
const reopened = new RepositoryCache(root, { install: async () => { throw new Error('cache miss') } })
expect(await reopened.resolve(specifier)).toBe(first)
expect(JSON.parse(await readFile(join(first, '..', '..', 'package.json'), 'utf8'))).toMatchObject({
packageManager: `pnpm@${BUNDLED_PNPM_VERSION}`,
@@ -71,8 +71,8 @@ describe('RepositoryCache', () => {
const specifier = 'github:owner/repository#race'
const [first, second] = await Promise.all([
new RepositoryCache(root, install).resolve(specifier),
new RepositoryCache(root, install).resolve(specifier),
new RepositoryCache(root, { install }).resolve(specifier),
new RepositoryCache(root, { install }).resolve(specifier),
])
expect(second).toBe(first)
@@ -83,11 +83,11 @@ describe('RepositoryCache', () => {
it('removes a failed staging tree and permits an exact retry', async () => {
const root = await temporaryRoot('repository-retry')
let attempts = 0
const cache = new RepositoryCache(root, async (directory) => {
const cache = new RepositoryCache(root, { install: async (directory) => {
attempts += 1
if (attempts === 1) throw new Error('install failed')
await fakePackage(directory)
})
} })
await expect(cache.resolve('github:owner/repository#ref')).rejects.toThrow('failed to prepare repository')
expect(await readdir(root)).toEqual([])
@@ -97,7 +97,7 @@ describe('RepositoryCache', () => {
it('rejects empty or padded specifiers before touching the cache', async () => {
const root = await temporaryRoot('repository-input')
const cache = new RepositoryCache(root, fakePackage)
const cache = new RepositoryCache(root, { install: fakePackage })
expect(() => cache.resolve('')).toThrow('non-empty unpadded string')
expect(() => cache.resolve(' github:owner/repository#ref')).toThrow('non-empty unpadded string')
await expect(readdir(root)).resolves.toEqual([])
@@ -110,35 +110,69 @@ describe('RepositoryCache', () => {
const entry = join(root, key)
await mkdir(join(entry, 'node_modules', 'repository'), { recursive: true })
await writeFile(join(entry, '.repository-cache.json'), '{}\n')
const cache = new RepositoryCache(root, async () => { throw new Error('must not reinstall') })
const cache = new RepositoryCache(root, { install: async () => { throw new Error('must not reinstall') } })
await expect(cache.resolve(specifier)).rejects.toThrow('repository cache marker is invalid')
})
it('selects and prepares a root .dsh-plugin Git subpath through the bundled pnpm', { timeout: 60_000 }, async () => {
it('isolates and prepares a .dsh-plugin Git subpath from an enclosing pnpm workspace', { timeout: 60_000 }, async () => {
const root = await temporaryRoot('repository-pnpm')
const repository = join(root, 'source')
await mkdir(join(repository, '.dsh-plugin'), { recursive: true })
await mkdir(join(repository, 'build-helper'), { recursive: true })
await mkdir(join(repository, 'prepare-helper'), { recursive: true })
await mkdir(join(repository, 'skills', 'fixture'), { recursive: true })
await writeFile(join(repository, 'package.json'), `${JSON.stringify({
name: 'repository-fixture',
private: true,
version: '1.0.0',
packageManager: `pnpm@${BUNDLED_PNPM_VERSION}`,
})}\n`)
await writeFile(join(repository, 'pnpm-workspace.yaml'), 'packages: []\n')
await writeFile(join(repository, 'pnpm-lock.yaml'), [
"lockfileVersion: '9.0'",
'settings:',
' autoInstallPeers: true',
' excludeLinksFromLockfile: false',
'importers:',
' .: {}',
'',
].join('\n'))
await writeFile(join(repository, 'build-helper', 'package.json'), `${JSON.stringify({
name: 'repository-build-helper',
version: '1.0.0',
bin: 'index.js',
})}\n`)
await writeFile(join(repository, 'build-helper', 'index.js'), [
'#!/usr/bin/env node',
"require('node:fs').writeFileSync('dependency-built.txt', 'dependency available\\n')",
'',
].join('\n'), { mode: 0o700 })
await writeFile(join(repository, 'prepare-helper', 'package.json'), `${JSON.stringify({
name: 'repository-prepare-helper',
version: '1.0.0',
bin: { 'dsh-plugin-prepare': 'index.js' },
})}\n`)
await writeFile(join(repository, 'prepare-helper', 'index.js'), [
'#!/usr/bin/env node',
"const { cpSync, mkdirSync, writeFileSync } = require('node:fs')",
"mkdirSync('dsh-plugin-assets/skills', { recursive: true })",
"cpSync('../skills', 'dsh-plugin-assets/skills/0', { recursive: true })",
"writeFileSync('dsh-plugin.mjs', 'export function apply() {}\\n')",
"writeFileSync('prepared.txt', `${process.env.REPOSITORY_TEST_VISIBLE ?? 'absent'}|${process.env.REPOSITORY_TEST_TOKEN ?? 'absent'}\\n`)",
'',
].join('\n'), { mode: 0o700 })
await writeFile(join(repository, 'skills', 'fixture', 'SKILL.md'), 'repository skill source\n')
await writeFile(join(repository, '.dsh-plugin', 'package.json'), `${JSON.stringify({
name: 'repository-plugin-fixture',
version: '1.0.0',
scripts: { prepare: 'node prepare.mjs' },
scripts: { prepack: 'repository-build-helper && dsh-plugin-prepare' },
devDependencies: {
'repository-build-helper': 'file:../build-helper',
'repository-prepare-helper': 'file:../prepare-helper',
},
dsh: { skills: ['../skills'] },
})}\n`)
await writeFile(join(repository, '.dsh-plugin', 'prepare.mjs'), [
"import { cp, mkdir, writeFile } from 'node:fs/promises'",
"await mkdir('dsh-plugin-assets/skills', { recursive: true })",
"await cp('../skills', 'dsh-plugin-assets/skills/0', { recursive: true })",
"await writeFile('dsh-plugin.mjs', 'export function apply() {}\\n')",
"await writeFile('prepared.txt', `${process.env.REPOSITORY_TEST_VISIBLE ?? 'absent'}|${process.env.REPOSITORY_TEST_TOKEN ?? 'absent'}\\n`)",
'',
].join('\n'))
await execFileAsync('git', ['init', '--quiet'], { cwd: repository })
await execFileAsync('git', ['add', '.'], { cwd: repository })
await execFileAsync('git', [
@@ -152,6 +186,7 @@ describe('RepositoryCache', () => {
vi.stubEnv('REPOSITORY_TEST_TOKEN', 'hidden')
const installed = await new RepositoryCache(join(root, 'cache')).resolve(specifier)
await expect(readFile(join(installed, 'dependency-built.txt'), 'utf8')).resolves.toBe('dependency available\n')
await expect(readFile(join(installed, 'prepared.txt'), 'utf8')).resolves.toBe('visible|absent\n')
await expect(readFile(join(installed, 'dsh-plugin.mjs'), 'utf8')).resolves.toContain('export function apply')
expect(lf(await readFile(join(installed, 'dsh-plugin-assets/skills/0/fixture/SKILL.md'), 'utf8')))