fix(desktop): preserve and verify production dependency resolutions

This commit is contained in:
kingwl committed 2026-09-30 22:15:39 +08:00
1 parent 0dac34c338
commit 8d4503f3f0
11 files changed
+350 -119

No files matched your search

+2 -2
View File
@@ -57,8 +57,8 @@
en: f176a8b1c7de3064
zh: 6ea2dbe6bb808c34
/deepseek-harness-desktop/package/windows-ev-signing:
en: 7a049a4cd52604b3
zh: cd73f9c96c6a9984
en: 8408737b507cc3c1
zh: 0b450629d3d5ae14
/deepseek-harness-desktop/updates:
en: d0df867b69cbffa4
zh: eeb47b79b86fe5ee
+3 -1
View File
@@ -396,7 +396,9 @@ Every package command builds the repository, packs the first-party production cl
macOS packaging writes `Contents/Resources/app-update.yml` while assembling the App and before code signing, including the directory build that feeds the parallel ZIP and DMG lanes. The signing hook verifies the exact feed and updater cache directory. Both lane copies and the promoted App are checked again before the release completion record is written; a missing or mismatched configuration prevents artifact promotion and therefore prevents upload.
Koffi consumers, including the private Desktop Host, pin `3.1.1`; `pnpm run constraints` checks every workspace declaration. `prepare:dsh` generates a fresh production lockfile from the packed manifests, independently of the repository lockfile, and rejects missing Koffi or any other Koffi JavaScript/native package version before installation. Upgrade all consumers and the shared [version check](../../scripts/koffi-policy.ts) together after qualifying source builds without optional prebuilts and the packed production install.
Koffi consumers, including the private Desktop Host, pin `3.1.1`; `pnpm run constraints` checks every workspace declaration. Upgrade all consumers together after qualifying source builds without optional prebuilts and the packed production install.
`prepare:dsh` seeds production resolution from the repository lockfile and carries its overrides and patch files into the disposable build project. The [dependency check](scripts/dependency-lock.ts) requires every registry package version, integrity and applied patch to match that baseline before installation; dependency snapshots must resolve completely. First-party packages must use the verified local tarballs. Optional workspace platform packages omitted from those tarballs use their exact workspace version from the registry. Existing locked versions and peer contexts are permitted, so unrelated major versions need not be forced together. Update and qualify repository dependencies before changing the production graph.
An unpacked artifact contains Electron, the materialized dsh production tree, pnpm, and the shell application. Installer size and filesystem size differ; release qualification measures both, plus the profile’s plugin storage and first-launch latency. The runtime trades more application files for eliminating core package installation on the user’s machine.
+3 -1
View File
@@ -398,7 +398,9 @@ pnpm run prepare:desktop
macOS 打包在组装 App 时、代码签名前写入 `Contents/Resources/app-update.yml`,供并行 ZIP 与 DMG 路线使用的目录构建也执行此操作。签名钩子验证准确的更新源和 updater 缓存目录。写入发布完成记录前,流程会再次检查两条路线的副本和最终移入的 App;配置缺失或不匹配会阻止移入产物,因而也会阻止上传。
所有 Koffi 使用方(包括私有 Desktop Host)固定使用 `3.1.1`;`pnpm run constraints` 检查每个工作区的声明。`prepare:dsh` 根据打包后的 manifest 生成独立于仓库锁文件的新生产锁文件,并在安装前拒绝缺失 Koffi 或包含其他版本 Koffi JavaScript/原生包的结果。升级前须验证不含可选预编译包的源码构建和打包后的生产安装,再同步更新所有使用方及共享的[版本检查](../../scripts/koffi-policy.ts)。
所有 Koffi 使用方(包括私有 Desktop Host)固定使用 `3.1.1`;`pnpm run constraints` 检查每个工作区的声明。升级前须验证不含可选预编译包的源码构建和打包后的生产安装,再同步更新所有使用方。
`prepare:dsh` 以仓库锁文件作为生产依赖解析的起点,并将其中的 overrides 和补丁文件带入临时构建项目。[依赖检查](scripts/dependency-lock.ts)在安装前要求每个 registry 包的版本、完整性信息和实际应用的补丁与此基准一致,依赖快照中的引用必须完整。第一方包必须使用已验证的本地 tarball。未包含在这些 tarball 中的可选工作区平台包从 registry 获取,但版本固定为其工作区版本。允许仓库已锁定的多版本及 peer 上下文,无需强行合并不同主版本。修改生产依赖图前须先更新并验证仓库依赖。
未压缩产物包含 Electron、物化后的 dsh 生产依赖树、pnpm,以及壳应用。安装包大小与文件系统占用不同;发布验收需要测量两者,以及 profile 插件存储和首次启动耗时。此布局用更多应用内文件换取消除用户机器上的核心包安装过程。
+165
View File
@@ -0,0 +1,165 @@
/** Preserve repository dependency resolutions and patches in the packed Desktop runtime. */
import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { isDeepStrictEqual } from 'node:util'
import { dump, load } from 'js-yaml'
import { readWorkspaceManifests } from '../../../scripts/check-workspace-constraints.ts'
import { desktopCorePackageSpec, type DesktopCorePackageSet } from '../src/core-package-set.ts'
function mapping(value: unknown, subject: string): Record<string, unknown> {
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
throw new Error(`desktop dependencies: ${subject} must be a mapping`)
}
return value as Record<string, unknown>
}
function strings(value: unknown, subject: string): Record<string, string> {
return Object.fromEntries(Object.entries(mapping(value, subject)).map(([key, entry]) => {
if (typeof entry !== 'string') throw new Error(`desktop dependencies: ${subject}.${key} must be a string`)
return [key, entry]
}))
}
function parseLockfile(text: string, subject: string) {
const data = mapping(load(text), subject)
if (data.lockfileVersion !== '9.0') throw new Error(`desktop dependencies: ${subject} must use lockfile version 9.0`)
const packages = mapping(data.packages, `${subject}.packages`)
if (Object.keys(packages).length === 0) throw new Error(`desktop dependencies: ${subject}.packages must not be empty`)
const snapshots = mapping(data.snapshots, `${subject}.snapshots`)
const patchedDependencies = strings(data.patchedDependencies ?? {}, `${subject}.patchedDependencies`)
return { data, packages, snapshots, patchedDependencies }
}
function dependencyPolicy(repositoryRoot: string, packageSet: DesktopCorePackageSet) {
const lock = parseLockfile(readFileSync(join(repositoryRoot, 'pnpm-lock.yaml'), 'utf8'), 'repository lockfile')
const workspace = mapping(load(readFileSync(join(repositoryRoot, 'pnpm-workspace.yaml'), 'utf8')), 'repository workspace')
const patches = strings(workspace.patchedDependencies ?? {}, 'repository workspace.patchedDependencies')
const overrides = strings(workspace.overrides ?? {}, 'repository workspace.overrides')
const members = new Map(readWorkspaceManifests(repositoryRoot).map(entry => [entry.manifest.name, entry.manifest]))
const packed = new Set(packageSet.packages.map(entry => entry.name))
const optionalWorkspaceVersions: Record<string, string> = {}
for (const entry of packageSet.packages) {
const manifest = members.get(entry.name)
if (manifest === undefined) throw new Error(`desktop dependencies: ${entry.name} is not a workspace member`)
for (const name of Object.keys(manifest.optionalDependencies ?? {})) {
const optional = members.get(name)
if (packed.has(name) || optional === undefined) continue
if (optional.version === undefined) throw new Error(`desktop dependencies: ${name} has no workspace version`)
optionalWorkspaceVersions[name] = optional.version
}
}
return { lock, patches, overrides, optionalWorkspaceVersions }
}
/**
* Seed the disposable production project with repository resolutions and patch files.
* @param projectDir - Staged project containing local tarballs and its generated workspace configuration.
* @param repositoryRoot - Checkout whose lockfile and patches qualified those tarballs.
* @param packageSet - Verified local first-party tarballs.
*/
export function prepareDesktopDependencyLock(projectDir: string, repositoryRoot: string, packageSet: DesktopCorePackageSet): void {
const policy = dependencyPolicy(repositoryRoot, packageSet)
const path = join(projectDir, 'pnpm-workspace.yaml')
const workspace = mapping(load(readFileSync(path, 'utf8')), 'production workspace')
workspace.overrides = {
...policy.overrides,
...policy.optionalWorkspaceVersions,
...strings(workspace.overrides ?? {}, 'production workspace.overrides'),
}
const patchedDependencies: Record<string, string> = {}
const patchDir = 'dependency-patches'
mkdirSync(join(projectDir, patchDir), { recursive: true })
for (const [index, [name, source]] of Object.entries(policy.patches).sort(([left], [right]) => left.localeCompare(right)).entries()) {
const destination = `${patchDir}/${String(index)}.patch`
copyFileSync(resolve(repositoryRoot, source), join(projectDir, destination))
patchedDependencies[name] = destination
}
workspace.patchedDependencies = patchedDependencies
// The repository also patches build-only dependencies absent from the production graph.
workspace.allowUnusedPatches = true
writeFileSync(path, dump(workspace))
writeFileSync(join(projectDir, 'pnpm-lock.yaml'), dump({
...policy.lock.data,
settings: { ...mapping(policy.lock.data.settings, 'repository lockfile.settings'), autoInstallPeers: false },
overrides: workspace.overrides,
importers: { '.': {} },
}))
}
/**
* Reject production resolutions outside the repository lock and verified local package set.
* Existing repository versions, including distinct compatible dependency trees, remain permitted.
* @param text - Production lockfile generated from the seeded project.
* @param repositoryRoot - Checkout supplying the dependency and patch baseline.
* @param packageSet - Verified local first-party tarballs.
* @throws When a package version, integrity, local tarball, or applied patch differs from its baseline.
*/
export function verifyDesktopDependencyLock(text: string, repositoryRoot: string, packageSet: DesktopCorePackageSet): void {
const policy = dependencyPolicy(repositoryRoot, packageSet)
const actual = parseLockfile(text, 'production lockfile')
const packed = new Map(packageSet.packages.map(entry => [entry.name, entry]))
const errors: string[] = []
for (const [key, value] of Object.entries(actual.packages)) {
const match = /^((?:@[^/]+\/)?[^@]+)@(.+)$/u.exec(key)
const name = match?.[1]
const version = match?.[2]
if (name === undefined || version === undefined) {
errors.push(`invalid package identity ${key}`)
continue
}
const resolution = mapping(mapping(value, key).resolution, `${key}.resolution`)
const local = packed.get(name)
if (local !== undefined) {
const spec = desktopCorePackageSpec(local).replace('file:./', 'file:')
if (version !== spec || resolution.integrity !== local.integrity || resolution.tarball !== spec) {
errors.push(`${key} must resolve to the verified local tarball ${spec}`)
}
continue
}
const expected = policy.lock.packages[key]
if (expected === undefined) {
if (policy.optionalWorkspaceVersions[name] !== version || typeof resolution.integrity !== 'string') {
errors.push(`${key} is absent from the repository lockfile; update and qualify the repository dependency first`)
}
continue
}
if (!isDeepStrictEqual(resolution, mapping(expected, `repository ${key}`).resolution)) {
errors.push(`${key} resolution differs from the repository lockfile`)
}
}
const snapshots = Object.keys(actual.snapshots)
for (const key of Object.keys(actual.packages)) {
const patch = policy.lock.patchedDependencies[key]
if (actual.patchedDependencies[key] !== patch) errors.push(`${key} patch differs from the repository lockfile`)
const instances = snapshots.filter(snapshot => snapshot.split('(')[0] === key)
if (instances.length === 0) errors.push(`${key} has no dependency snapshot`)
for (const snapshot of instances) {
if (/\(patch_hash=([^)]+)\)/u.exec(snapshot)?.[1] !== patch) {
errors.push(`${snapshot} does not apply the repository patch`)
}
}
}
const importers = mapping(actual.data.importers, 'production lockfile.importers')
if (importers['.'] === undefined) errors.push('missing production root importer')
for (const [owner, value] of [...Object.entries(importers), ...Object.entries(actual.snapshots)]) {
const entry = mapping(value, owner)
for (const section of ['dependencies', 'optionalDependencies']) {
for (const [name, dependency] of Object.entries(mapping(entry[section] ?? {}, `${owner}.${section}`))) {
const reference = typeof dependency === 'string' ? dependency : mapping(dependency, `${owner}.${name}`).version
if (typeof reference !== 'string'
|| (actual.snapshots[`${name}@${reference}`] === undefined && actual.snapshots[reference] === undefined)) {
errors.push(`${owner}.${section}.${name} has no resolved dependency snapshot`)
}
}
}
}
for (const snapshot of snapshots) {
if (actual.packages[snapshot.split('(')[0] ?? ''] === undefined) errors.push(`${snapshot} has no package resolution`)
}
for (const entry of packageSet.packages) {
const key = `${entry.name}@${desktopCorePackageSpec(entry).replace('file:./', 'file:')}`
if (actual.packages[key] === undefined) errors.push(`missing local package ${entry.name}`)
}
if (errors.length > 0) throw new Error(`desktop dependencies:\n${errors.join('\n')}`)
}
+3 -4
View File
@@ -15,7 +15,6 @@ import {
DESKTOP_PACKAGES_DIR,
DESKTOP_PACKAGE_SET_FILE,
readDesktopCorePackageSet,
verifyDesktopCoreLockfile,
} from '../src/core-package-set.ts'
import { smokePrimaryRuntime } from './prepare-primary-runtime.ts'
import { smokePreparedRuntime } from './smoke-prepared-runtime.ts'
@@ -32,7 +31,7 @@ import {
import { desktopTargetPlatform, resolveDesktopBuildTarget, resolveDesktopTargetBuildPaths } from './desktop-build-paths.mjs'
import { desktopRuntimeFileExclusion } from './runtime-file-policy.ts'
import { selectOfficeEngine } from '../../../scripts/libreoffice-packages.mjs'
import { verifyKoffiLockfile } from '../../../scripts/koffi-policy.ts'
import { prepareDesktopDependencyLock, verifyDesktopDependencyLock } from './dependency-lock.ts'
const APP_ROOT = resolve(import.meta.dirname, '..')
const BUILD_PATHS = resolveDesktopTargetBuildPaths()
@@ -122,11 +121,11 @@ async function main(): Promise<void> {
copyFileSync(join(PACKAGE_SET_ROOT, DESKTOP_PACKAGE_SET_FILE), join(BUILD_ROOT, DESKTOP_PACKAGE_SET_FILE))
cpSync(join(PACKAGE_SET_ROOT, DESKTOP_PACKAGES_DIR), join(BUILD_ROOT, DESKTOP_PACKAGES_DIR), { recursive: true })
createRuntimeProjectMetadata(BUILD_ROOT, release)
prepareDesktopDependencyLock(BUILD_ROOT, resolve(APP_ROOT, '..', '..'), readDesktopCorePackageSet(BUILD_ROOT, release.version))
})
await packagingStep(process.env.DSH_DESKTOP_PACKAGING_RUN_DIR, 'runtime:lockfile', () => runPnpm(['install', '--lockfile-only']))
const lockfile = readFileSync(join(BUILD_ROOT, 'pnpm-lock.yaml'), 'utf8')
verifyDesktopCoreLockfile(lockfile, readDesktopCorePackageSet(BUILD_ROOT, release.version))
verifyKoffiLockfile(lockfile)
verifyDesktopDependencyLock(lockfile, resolve(APP_ROOT, '..', '..'), readDesktopCorePackageSet(BUILD_ROOT, release.version))
await packagingStep(process.env.DSH_DESKTOP_PACKAGING_RUN_DIR, 'runtime:install', () => runPnpm(['install', '--prod', '--frozen-lockfile', '--trust-lockfile']))
const packageSet = readDesktopCorePackageSet(BUILD_ROOT, release.version)
const targetName = resolveDesktopBuildTarget()
-24
View File
@@ -157,27 +157,3 @@ export function verifyDesktopCorePackageSet(
}
return packageSet
}
function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&')
}
/**
* Reject a lockfile that resolved any packaged core name through a registry version.
* @param lockfile - Generated pnpm lockfile text.
* @param packageSet - Verified local core package set.
*/
export function verifyDesktopCoreLockfile(
lockfile: string,
packageSet: DesktopCorePackageSet,
): void {
for (const record of packageSet.packages) {
const registryResolution = new RegExp(
`^ ['"]?${escapeRegExp(record.name)}@${escapeRegExp(record.version)}(?:\\([^\\r\\n]*\\))?['"]?:`,
'mu',
)
if (registryResolution.test(lockfile)) {
throw new Error(`desktop package set: lockfile resolved ${record.name}@${record.version} outside the local package set`)
}
}
}
@@ -9,7 +9,6 @@ import {
desktopCorePackageOverrides,
desktopDshPackageSpec,
parseDesktopCorePackageSet,
verifyDesktopCoreLockfile,
verifyDesktopCorePackageSet,
type DesktopCorePackageRecord,
} from '../src/core-package-set.ts'
@@ -83,21 +82,4 @@ describe('desktop core package set', () => {
expect(() => verifyDesktopCorePackageSet(root, '1.2.3')).toThrow(/does not match its descriptor/u)
})
it('rejects registry resolutions for names supplied by the local package set', () => {
const dsh = record('@deepseek-ai/dsh', 'dsh.tgz', Buffer.from('dsh'))
const host = record('@deepseek-ai/dsh-desktop-host', 'host.tgz', Buffer.from('host'))
const packageSet = parseDesktopCorePackageSet({ schemaVersion: 1, packages: [dsh, host] })
expect(() => {
verifyDesktopCoreLockfile(
"packages:\n '@deepseek-ai/dsh@file:desktop-packages/dsh.tgz':\n resolution: {}\n",
packageSet,
)
}).not.toThrow()
expect(() => {
verifyDesktopCoreLockfile(
"packages:\n '@deepseek-ai/[email protected]':\n resolution: {integrity: sha512-registry}\n",
packageSet,
)
}).toThrow(/outside the local package set/u)
})
})
+172
View File
@@ -0,0 +1,172 @@
/** Repository dependency and patch preservation in a disposable Desktop install. */
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { dump, load } from 'js-yaml'
import { describe, expect, it, onTestFinished } from 'vitest'
import { prepareDesktopDependencyLock, verifyDesktopDependencyLock } from '../scripts/dependency-lock.ts'
import { desktopCorePackageOverrides, desktopCorePackageSpec, type DesktopCorePackageSet } from '../src/core-package-set.ts'
interface Lockfile {
lockfileVersion: string
settings: { autoInstallPeers: boolean }
importers: Record<string, { dependencies: Record<string, { specifier: string; version: string }> }>
packages: Record<string, { resolution: { integrity: string; tarball?: string }; version?: string }>
snapshots: Record<string, { dependencies?: Record<string, string> }>
patchedDependencies: Record<string, string>
}
function fixture() {
const root = mkdtempSync(join(tmpdir(), 'dsh-dependency-lock-'))
onTestFinished(() => { rmSync(root, { recursive: true, force: true }) })
const project = join(root, 'project')
mkdirSync(project)
mkdirSync(join(root, 'patches'))
writeFileSync(join(root, 'package.json'), '{}')
const members = [
['apps/cli', { name: '@deepseek-ai/dsh', version: '1.0.0' }],
['apps/host', { name: '@deepseek-ai/dsh-desktop-host', version: '1.0.0', optionalDependencies: { '@test/native': '^1.0.0' } }],
['native/linux', { name: '@test/native', version: '1.0.0' }],
] as const
for (const [dir, manifest] of members) {
mkdirSync(join(root, dir), { recursive: true })
writeFileSync(join(root, dir, 'package.json'), JSON.stringify(manifest))
}
writeFileSync(join(root, 'patches/runtime.patch'), 'runtime patch\n')
writeFileSync(join(root, 'patches/build.patch'), 'build patch\n')
writeFileSync(join(root, 'pnpm-workspace.yaml'), dump({
packages: ['apps/*', 'native/*'],
overrides: { '@deepseek-ai/dsh': 'link:apps/cli', 'parent>child': '1.0.0' },
patchedDependencies: { '[email protected]': 'patches/runtime.patch', '[email protected]': 'patches/build.patch' },
}))
const source: Lockfile = {
lockfileVersion: '9.0', settings: { autoInstallPeers: true }, importers: { '.': { dependencies: {} } },
packages: {
'[email protected]': { resolution: { integrity: 'sha512-one' } },
'[email protected]': { resolution: { integrity: 'sha512-two' } },
'[email protected]': { resolution: { integrity: 'sha512-build' } },
},
snapshots: { '[email protected](patch_hash=runtime)': {}, '[email protected]': {}, '[email protected](patch_hash=build)': {} },
patchedDependencies: { '[email protected]': 'runtime', '[email protected]': 'build' },
}
writeFileSync(join(root, 'pnpm-lock.yaml'), dump(source))
const set: DesktopCorePackageSet = {
schemaVersion: 1,
packages: members.slice(0, 2).map(([, manifest], index) => ({
name: manifest.name, version: manifest.version, file: `${String(index)}.tgz`, bytes: 1, integrity: `sha512-local${String(index)}`,
})),
}
const local = Object.fromEntries(set.packages.map((entry) => {
const spec = desktopCorePackageSpec(entry).replace('file:./', 'file:')
return [`${entry.name}@${spec}`, { version: entry.version, resolution: { tarball: spec, integrity: entry.integrity } }]
}))
const actual: Lockfile = {
...source,
importers: { '.': { dependencies: Object.fromEntries(set.packages.map((entry) => {
const spec = desktopCorePackageSpec(entry).replace('file:./', 'file:')
return [entry.name, { specifier: spec, version: spec }]
})) } },
packages: { ...local, '[email protected]': source.packages['[email protected]']!, '[email protected]': source.packages['[email protected]']! },
snapshots: {
...Object.fromEntries(Object.keys(local).map(key => [key, { dependencies: { library: '1.0.0(patch_hash=runtime)', alias: '[email protected]' } }])),
'[email protected](patch_hash=runtime)': {}, '[email protected]': {},
},
patchedDependencies: { ...source.patchedDependencies },
}
writeFileSync(join(project, 'pnpm-workspace.yaml'), dump({
packages: ['.'], nodeLinker: 'hoisted', autoInstallPeers: false,
overrides: desktopCorePackageOverrides(set), allowBuilds: { library: true },
}))
return { root, project, source, actual, set, localKey: Object.keys(local)[0]! }
}
describe('Desktop dependency lock', () => {
it('seeds repository resolutions, overrides and patches while preserving the source checkout', () => {
const { root, project, source, set } = fixture()
prepareDesktopDependencyLock(project, root, set)
expect(load(readFileSync(join(project, 'pnpm-workspace.yaml'), 'utf8'))).toMatchObject({
nodeLinker: 'hoisted', autoInstallPeers: false, allowBuilds: { library: true }, allowUnusedPatches: true,
overrides: { ...desktopCorePackageOverrides(set), '@test/native': '1.0.0', 'parent>child': '1.0.0' },
patchedDependencies: { '[email protected]': 'dependency-patches/0.patch', '[email protected]': 'dependency-patches/1.patch' },
})
expect(readFileSync(join(project, 'dependency-patches/0.patch'), 'utf8')).toBe('build patch\n')
expect(readFileSync(join(project, 'dependency-patches/1.patch'), 'utf8')).toBe('runtime patch\n')
expect(load(readFileSync(join(project, 'pnpm-lock.yaml'), 'utf8'))).toMatchObject({
packages: source.packages, snapshots: source.snapshots, patchedDependencies: source.patchedDependencies,
importers: { '.': {} }, settings: { autoInstallPeers: false },
})
expect(load(readFileSync(join(root, 'pnpm-lock.yaml'), 'utf8'))).toEqual(source)
})
it('accepts existing major versions, npm aliases and peer contexts without installing build-only dependencies', () => {
const { root, actual, set } = fixture()
actual.snapshots['[email protected]([email protected])'] = {}
expect(() => { verifyDesktopDependencyLock(dump(actual), root, set) }).not.toThrow()
})
it.each(['[email protected]', '[email protected]', '@scope/[email protected]'])(
'rejects a new dependency or version: %s', (key) => {
const { root, actual, set } = fixture()
actual.packages[key] = { resolution: { integrity: 'sha512-unqualified' } }
actual.snapshots[key] = {}
expect(() => { verifyDesktopDependencyLock(dump(actual), root, set) }).toThrow(`${key} is absent from the repository lockfile`)
},
)
it('rejects changed registry integrity', () => {
const { root, actual, set } = fixture()
actual.packages['[email protected]']!.resolution.integrity = 'sha512-changed'
expect(() => { verifyDesktopDependencyLock(dump(actual), root, set) }).toThrow('resolution differs from the repository lockfile')
})
it('requires both patch metadata and the patched dependency snapshot', () => {
const { root, actual, set } = fixture()
delete actual.patchedDependencies['[email protected]']
expect(() => { verifyDesktopDependencyLock(dump(actual), root, set) }).toThrow('patch differs from the repository lockfile')
actual.patchedDependencies['[email protected]'] = 'runtime'
delete actual.snapshots['[email protected](patch_hash=runtime)']
actual.snapshots['[email protected]'] = {}
expect(() => { verifyDesktopDependencyLock(dump(actual), root, set) }).toThrow('does not apply the repository patch')
})
it('rejects missing package metadata, dependency snapshots and local tarballs', () => {
const { root, actual, set, localKey } = fixture()
delete actual.packages['[email protected]']
expect(() => { verifyDesktopDependencyLock(dump(actual), root, set) }).toThrow('has no package resolution')
delete actual.snapshots['[email protected]']
expect(() => { verifyDesktopDependencyLock(dump(actual), root, set) }).toThrow('has no resolved dependency snapshot')
actual.packages = Object.fromEntries(Object.entries(actual.packages).filter(([key]) => key !== localKey))
expect(() => { verifyDesktopDependencyLock(dump(actual), root, set) }).toThrow('missing local package')
})
it.each(['1.0.0', '9.0.0'])(
'rejects a registry copy of a locally packed package at version %s', (version) => {
const { root, actual, set } = fixture()
actual.packages[`@deepseek-ai/dsh@${version}`] = { resolution: { integrity: 'sha512-registry' } }
expect(() => { verifyDesktopDependencyLock(dump(actual), root, set) }).toThrow('must resolve to the verified local tarball')
},
)
it('rejects corrupted local tarball integrity', () => {
const { root, actual, set, localKey } = fixture()
actual.packages[localKey]!.resolution.integrity = 'sha512-corrupt'
expect(() => { verifyDesktopDependencyLock(dump(actual), root, set) }).toThrow('must resolve to the verified local tarball')
})
it('allows unpacked optional workspace platforms only at their exact source version', () => {
const { root, actual, set } = fixture()
actual.packages['@test/[email protected]'] = { resolution: { integrity: 'sha512-platform' } }
actual.snapshots['@test/[email protected]'] = {}
expect(() => { verifyDesktopDependencyLock(dump(actual), root, set) }).not.toThrow()
actual.packages['@test/[email protected]'] = { resolution: { integrity: 'sha512-other-platform' } }
expect(() => { verifyDesktopDependencyLock(dump(actual), root, set) }).toThrow('@test/[email protected] is absent')
})
it.each(['{}', 'lockfileVersion: "8.0"', 'lockfileVersion: "9.0"\npackages: {}', 'lockfileVersion: "9.0"\npackages: []'])(
'rejects an invalid or empty production lockfile: %s', (text) => {
const { root, set } = fixture()
expect(() => { verifyDesktopDependencyLock(text, root, set) }).toThrow('desktop dependencies:')
},
)
})
+2 -1
View File
@@ -17,7 +17,6 @@ import { hasTypertRemoteNavigation, isForbiddenPublicationFile } from './publica
import type { DshBundleManifest } from '../packages/util/package-manifest/src/types.ts'
import { OPTIONAL_BUNDLES, bundlePatchFiles } from '../packages/boot/app-boot/src/profile.ts'
import { collectProjectReferenceFaceViolations } from './project-reference-faces.ts'
import { KOFFI_VERSION } from './koffi-policy.ts'
const root = resolve(import.meta.dirname, '..')
// Publication rules cover these package trees; dependency rules read all pnpm members.
@@ -552,6 +551,8 @@ function checkRepositoryVersion(): string[] {
const dependencySections = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'] as const
/** Dependency sections present in an installed runtime. */
const runtimeDependencySections = ['dependencies', 'optionalDependencies', 'peerDependencies'] as const
/** Koffi version qualified for source builds without optional native prebuilts. */
const KOFFI_VERSION = '3.1.1'
/**
* Prevent an official runtime from requiring an experimental package. The dsh installation's `dependencies`
-39
View File
@@ -1,39 +0,0 @@
/** Koffi resolution checks for freshly generated Desktop lockfiles. */
import { dump } from 'js-yaml'
import { describe, expect, it } from 'vitest'
import { verifyKoffiLockfile } from './koffi-policy.ts'
function lockfile(...packages: string[]): string {
return dump({ lockfileVersion: '9.0', packages: Object.fromEntries(packages.map(name => [name, {}])) })
}
describe('Desktop Koffi lockfile', () => {
it('accepts the qualified JavaScript and platform packages alongside unrelated dependencies', () => {
expect(() => {
verifyKoffiLockfile(lockfile(
'[email protected]', '@koromix/[email protected]', '@koromix/[email protected]', '[email protected]',
))
}).not.toThrow()
expect(() => { verifyKoffiLockfile(lockfile('[email protected]')) }).not.toThrow()
})
it.each(['[email protected]', '@koromix/[email protected]', '@koromix/[email protected]'])(
'rejects an additional divergent resolution: %s', (entry) => {
expect(() => { verifyKoffiLockfile(lockfile('[email protected]', entry)) })
.toThrow(`lockfile resolved ${entry}; all Koffi packages must use 3.1.1`)
},
)
it.each([lockfile(), lockfile('[email protected]'), lockfile('@koromix/[email protected]')])(
'requires the qualified JavaScript package: %s', (text) => {
expect(() => { verifyKoffiLockfile(text) }).toThrow('lockfile must resolve [email protected]')
},
)
it.each(['', '{}', 'packages: []', 'packages: null', 'packages: invalid'])(
'rejects a missing or invalid package mapping: %s', (text) => {
expect(() => { verifyKoffiLockfile(text) }).toThrow('lockfile must contain a packages mapping')
},
)
})
-29
View File
@@ -1,29 +0,0 @@
/** Shared Koffi pin for workspace declarations and fresh Desktop production installs. */
import { load as loadYaml } from 'js-yaml'
/** Version qualified for source builds when optional native prebuilts are omitted. */
export const KOFFI_VERSION = '3.1.1'
/**
* Reject missing or divergent Koffi resolutions before installing the Desktop runtime.
* @param lockfile - Freshly generated pnpm lockfile text.
* @throws When Koffi is absent or its JavaScript or native packages use another version.
*/
export function verifyKoffiLockfile(lockfile: string): void {
const data: unknown = loadYaml(lockfile)
if (typeof data !== 'object' || data === null || !('packages' in data)
|| typeof data.packages !== 'object' || data.packages === null || Array.isArray(data.packages)) {
throw new Error('Koffi: lockfile must contain a packages mapping')
}
const packages = Object.keys(data.packages)
if (!packages.includes(`koffi@${KOFFI_VERSION}`)) {
throw new Error(`Koffi: lockfile must resolve koffi@${KOFFI_VERSION}`)
}
for (const entry of packages) {
const match = /^(koffi|@koromix\/koffi-[^@]+)@(.+)$/u.exec(entry)
if (match !== null && match[2] !== KOFFI_VERSION) {
throw new Error(`Koffi: lockfile resolved ${entry}; all Koffi packages must use ${KOFFI_VERSION}`)
}
}
}