mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-14 04:01:35 +00:00
411 lines
19 KiB
TypeScript
411 lines
19 KiB
TypeScript
import { createHash } from 'node:crypto'
|
|
import { existsSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { dirname, join, relative, sep } from 'node:path'
|
|
import { pathToFileURL } from 'node:url'
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import { resolveDesktopPaths } from '../src/paths.ts'
|
|
import {
|
|
createSeedMetadata,
|
|
DesktopProjectManager,
|
|
packageNameFromSpec,
|
|
verifySeedIntegrity,
|
|
type DesktopProjectHooks,
|
|
} from '../src/project-manager.ts'
|
|
import { DESKTOP_HOST_PROTOCOL_VERSION } from '../src/host-protocol.ts'
|
|
import { DESKTOP_PACKAGES_DIR, DESKTOP_PACKAGE_SET_FILE } from '../src/core-package-set.ts'
|
|
import type { DesktopRelease } from '../src/release.ts'
|
|
import { archivePnpmStore } from '../src/seed-store.ts'
|
|
|
|
const roots: string[] = []
|
|
const releaseWorkers: Array<() => Promise<void>> = []
|
|
|
|
function temporaryRoot(): string {
|
|
const root = mkdtempSync(join(tmpdir(), 'dsh-desktop-test-'))
|
|
roots.push(root)
|
|
return root
|
|
}
|
|
|
|
function writeIntegrity(seed: string): void {
|
|
const paths: string[] = []
|
|
const visit = (directory: string): void => {
|
|
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
const path = join(directory, entry.name)
|
|
if (entry.isDirectory()) visit(path)
|
|
else if (entry.name !== 'integrity.json') paths.push(path)
|
|
}
|
|
}
|
|
visit(seed)
|
|
const files = paths.sort().map((path) => {
|
|
const body = readFileSync(path)
|
|
return {
|
|
path: relative(seed, path).split(sep).join('/'),
|
|
bytes: statSync(path).size,
|
|
sha256: createHash('sha256').update(body).digest('hex'),
|
|
}
|
|
})
|
|
writeFileSync(join(seed, 'integrity.json'), `${JSON.stringify({ schemaVersion: 2, files })}\n`)
|
|
}
|
|
|
|
function archiveStore(seed: string): void {
|
|
const store = join(seed, 'store')
|
|
mkdirSync(store, { recursive: true })
|
|
if (readdirSync(store).length === 0) writeFileSync(join(store, 'test-entry'), 'content')
|
|
archivePnpmStore(seed, store)
|
|
}
|
|
|
|
function writeCorePackageSet(seed: string, version: string): void {
|
|
const packages = [
|
|
{ name: '@deepseek-ai/dsh', file: `deepseek-ai-dsh-${version}.tgz`, body: Buffer.from(`dsh-${version}`) },
|
|
{
|
|
name: '@deepseek-ai/dsh-desktop-host',
|
|
file: `deepseek-ai-dsh-desktop-host-${version}.tgz`,
|
|
body: Buffer.from(`desktop-host-${version}`),
|
|
},
|
|
]
|
|
mkdirSync(join(seed, DESKTOP_PACKAGES_DIR), { recursive: true })
|
|
for (const entry of packages) writeFileSync(join(seed, DESKTOP_PACKAGES_DIR, entry.file), entry.body)
|
|
writeFileSync(join(seed, DESKTOP_PACKAGE_SET_FILE), `${JSON.stringify({
|
|
schemaVersion: 1,
|
|
packages: packages.map(({ name, file, body }) => ({
|
|
name,
|
|
version,
|
|
file,
|
|
bytes: body.byteLength,
|
|
integrity: `sha512-${createHash('sha512').update(body).digest('base64')}`,
|
|
})),
|
|
})}\n`)
|
|
}
|
|
|
|
function createTestSeedMetadata(seed: string, desktopRelease: DesktopRelease): void {
|
|
writeCorePackageSet(seed, desktopRelease.version)
|
|
createSeedMetadata(seed, desktopRelease)
|
|
}
|
|
|
|
function writeFakePnpm(root: string): string {
|
|
const path = join(root, 'pnpm.mjs')
|
|
writeFileSync(path, String.raw`
|
|
import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
import { dirname, join } from 'node:path'
|
|
const args = process.argv.slice(2)
|
|
const project = process.cwd()
|
|
const command = args.find(value => value === 'install' || value === 'add' || value === 'remove')
|
|
const manifestPath = join(project, 'package.json')
|
|
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
|
const packageName = spec => spec.startsWith('@')
|
|
? spec.slice(0, spec.indexOf('@', spec.indexOf('/') + 1) === -1 ? undefined : spec.indexOf('@', spec.indexOf('/') + 1))
|
|
: spec.split('@')[0]
|
|
const packageVersion = spec => {
|
|
const index = spec.startsWith('@') ? spec.indexOf('@', spec.indexOf('/') + 1) : spec.indexOf('@')
|
|
return index === -1 ? '1.0.0' : spec.slice(index + 1)
|
|
}
|
|
|
|
if (command === 'add') {
|
|
const spec = args[args.indexOf('add') + 1]
|
|
manifest.dependencies[packageName(spec)] = packageVersion(spec)
|
|
}
|
|
if (command === 'remove') delete manifest.dependencies[args[args.indexOf('remove') + 1]]
|
|
writeFileSync(manifestPath, JSON.stringify(manifest))
|
|
rmSync(join(project, 'node_modules'), { recursive: true, force: true })
|
|
for (const [name, version] of Object.entries(manifest.dependencies)) {
|
|
const packageRoot = join(project, 'node_modules', ...name.split('/'))
|
|
mkdirSync(packageRoot, { recursive: true })
|
|
const core = name === '@deepseek-ai/dsh' || name === '@deepseek-ai/dsh-desktop-host'
|
|
const plugin = !core
|
|
const installedVersion = plugin
|
|
? version
|
|
: JSON.parse(readFileSync(join(project, 'desktop-release.json'), 'utf8')).version
|
|
writeFileSync(join(packageRoot, 'package.json'), JSON.stringify({
|
|
name, version: installedVersion,
|
|
...(plugin ? { dsh: { bundle: { patch: './bundle.yml' } } } : {}),
|
|
}))
|
|
if (plugin) writeFileSync(join(packageRoot, 'bundle.yml'), '[]\n')
|
|
else if (name === '@deepseek-ai/dsh-desktop-host') {
|
|
mkdirSync(join(packageRoot, 'lib'), { recursive: true })
|
|
writeFileSync(join(packageRoot, 'lib', 'index.js'), '')
|
|
}
|
|
}
|
|
writeFileSync(join(project, 'pnpm-lock.yaml'), 'lockfileVersion: 9\n')
|
|
if (process.env.TEST_PNPM_LOG) writeFileSync(process.env.TEST_PNPM_LOG, JSON.stringify({ args, env: process.env }))
|
|
`)
|
|
return path
|
|
}
|
|
|
|
function writeBlockingFakePnpm(root: string, ready: string, release: string): string {
|
|
const path = join(root, 'blocking-pnpm.mjs')
|
|
const delegate = writeFakePnpm(root)
|
|
writeFileSync(path, `
|
|
import { existsSync, writeFileSync } from 'node:fs'
|
|
import { setTimeout as sleep } from 'node:timers/promises'
|
|
writeFileSync(${JSON.stringify(ready)}, String(process.pid))
|
|
while (!existsSync(${JSON.stringify(release)})) await sleep(10)
|
|
await import(${JSON.stringify(pathToFileURL(delegate).href)})
|
|
`)
|
|
return path
|
|
}
|
|
|
|
function hooks(overrides: Partial<DesktopProjectHooks> = {}): DesktopProjectHooks {
|
|
return {
|
|
healthCheck: async () => {},
|
|
beforeActivate: async () => {},
|
|
afterActivate: async () => {},
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
function release(version = '1.0.0'): DesktopRelease {
|
|
return {
|
|
schemaVersion: 1,
|
|
version,
|
|
hostProtocolVersion: DESKTOP_HOST_PROTOCOL_VERSION,
|
|
nodeVersion: '24.17.0',
|
|
pnpmVersion: '11.7.0',
|
|
}
|
|
}
|
|
|
|
afterEach(async () => {
|
|
const cleanups = releaseWorkers.splice(0)
|
|
const directories = roots.splice(0)
|
|
const results = await Promise.allSettled(cleanups.map(cleanup => cleanup()))
|
|
for (const root of directories) rmSync(root, { recursive: true, force: true })
|
|
const failures: unknown[] = results.flatMap((result): unknown[] => result.status === 'rejected' ? [result.reason] : [])
|
|
if (failures.length > 0) throw new AggregateError(failures, 'desktop worker cleanup failed')
|
|
})
|
|
|
|
describe('desktop package policy', () => {
|
|
it('accepts registry package specs but rejects alternate sources and flags', () => {
|
|
expect(packageNameFromSpec('@scope/plugin@1.2.3')).toBe('@scope/plugin')
|
|
expect(packageNameFromSpec('plugin@next')).toBe('plugin')
|
|
expect(() => packageNameFromSpec('file:../plugin')).toThrow(/unsupported npm package spec/u)
|
|
expect(() => packageNameFromSpec('--registry=evil')).toThrow(/unsupported npm package spec/u)
|
|
expect(() => packageNameFromSpec('https://example.test/plugin.tgz')).toThrow(/unsupported npm package spec/u)
|
|
})
|
|
|
|
it('rejects any seed content changed after release inventory generation', () => {
|
|
const seed = join(temporaryRoot(), 'seed')
|
|
createTestSeedMetadata(seed, release())
|
|
writeFileSync(join(seed, 'pnpm-lock.yaml'), 'lockfileVersion: 9\n')
|
|
writeIntegrity(seed)
|
|
expect(() => { verifySeedIntegrity(seed) }).not.toThrow()
|
|
writeFileSync(join(seed, 'package.json'), '{}\n')
|
|
expect(() => { verifySeedIntegrity(seed) }).toThrow(/integrity verification failed/u)
|
|
})
|
|
})
|
|
|
|
describe('desktop project transactions', () => {
|
|
it('installs the offline seed and reconciles a mismatched private Host', async () => {
|
|
const root = temporaryRoot()
|
|
const seed = join(root, 'seed')
|
|
const log = join(root, 'pnpm-log.json')
|
|
createTestSeedMetadata(seed, release())
|
|
writeFileSync(join(seed, 'pnpm-lock.yaml'), 'lockfileVersion: 9\n')
|
|
mkdirSync(join(seed, 'store'), { recursive: true })
|
|
writeFileSync(join(seed, 'store', 'seed-entry'), 'content')
|
|
archiveStore(seed)
|
|
writeIntegrity(seed)
|
|
const paths = resolveDesktopPaths(join(root, '.dsh'))
|
|
const manager = new DesktopProjectManager(paths, { node: process.execPath, pnpm: writeFakePnpm(root) })
|
|
const previousLog = process.env.TEST_PNPM_LOG
|
|
const previousRegistry = process.env.npm_config_registry
|
|
process.env.TEST_PNPM_LOG = log
|
|
process.env.npm_config_registry = 'https://user-registry.invalid'
|
|
try {
|
|
await expect(manager.applyRelease(seed, '2.0.0', hooks())).rejects.toThrow(/does not match Electron/u)
|
|
await manager.applyRelease(seed, '1.0.0', hooks())
|
|
writeFileSync(
|
|
join(paths.profile, 'node_modules', '@deepseek-ai', 'dsh-desktop-host', 'package.json'),
|
|
'{"name":"@deepseek-ai/dsh-desktop-host","version":"0.9.0"}\n',
|
|
)
|
|
await expect(manager.applyRelease(seed, '1.0.0', hooks())).resolves.toBe(true)
|
|
} finally {
|
|
if (previousLog === undefined) delete process.env.TEST_PNPM_LOG
|
|
else process.env.TEST_PNPM_LOG = previousLog
|
|
if (previousRegistry === undefined) delete process.env.npm_config_registry
|
|
else process.env.npm_config_registry = previousRegistry
|
|
}
|
|
expect(manager.dshVersion()).toBe('1.0.0')
|
|
expect(manager.releaseVersion()).toBe('1.0.0')
|
|
expect(paths.profile).toBe(join(root, '.dsh', 'profiles', 'desktop'))
|
|
expect(existsSync(join(paths.profile, 'node_modules', '@deepseek-ai', 'dsh'))).toBe(true)
|
|
const installedHost = JSON.parse(readFileSync(
|
|
join(paths.profile, 'node_modules', '@deepseek-ai', 'dsh-desktop-host', 'package.json'),
|
|
'utf8',
|
|
)) as { version: string }
|
|
expect(installedHost.version).toBe('1.0.0')
|
|
expect(existsSync(join(paths.profile, 'desktop-plugins.json'))).toBe(false)
|
|
expect(readFileSync(join(paths.pnpm.store, 'seed-entry'), 'utf8')).toBe('content')
|
|
const invocation = JSON.parse(readFileSync(log, 'utf8')) as { args: string[]; env: Record<string, string> }
|
|
expect(invocation.args).toContain('--offline')
|
|
expect(invocation.args).toContain('--trust-lockfile')
|
|
expect(invocation.args).toContain(`--config.store-dir=${paths.pnpm.store}`)
|
|
expect(invocation.args).toContain('--config.enable-global-virtual-store=false')
|
|
expect(invocation.args).toContain('--config.registry=https://registry.npmjs.org/')
|
|
expect(invocation.env.NPM_CONFIG_REGISTRY).toBe('https://registry.npmjs.org/')
|
|
expect(invocation.env.NPM_CONFIG_STORE_DIR).toBe(paths.pnpm.store)
|
|
expect(invocation.env.NPM_CONFIG_USERCONFIG).toBe(join(paths.pnpm.config, 'npmrc'))
|
|
expect(invocation.env.npm_config_registry).toBeUndefined()
|
|
})
|
|
|
|
it('restores the active project when the replacement backend cannot start', async () => {
|
|
const root = temporaryRoot()
|
|
const seed = join(root, 'seed')
|
|
createTestSeedMetadata(seed, release())
|
|
writeFileSync(join(seed, 'pnpm-lock.yaml'), 'lockfileVersion: 9\n')
|
|
archiveStore(seed)
|
|
writeIntegrity(seed)
|
|
const paths = resolveDesktopPaths(join(root, '.dsh'))
|
|
const manager = new DesktopProjectManager(paths, { node: process.execPath, pnpm: writeFakePnpm(root) })
|
|
await manager.applyRelease(seed, '1.0.0', hooks())
|
|
let starts = 0
|
|
await expect(manager.mutate({ type: 'plugin-add', spec: '@scope/plugin@2.0.0' }, hooks({
|
|
afterActivate: async () => {
|
|
starts += 1
|
|
if (starts === 1) throw new Error('backend rejected staged graph')
|
|
},
|
|
}))).rejects.toThrow(/backend rejected staged graph/u)
|
|
expect(manager.listPlugins()).toEqual([])
|
|
expect(manager.dshVersion()).toBe('1.0.0')
|
|
expect(starts).toBe(2)
|
|
})
|
|
|
|
it('restores rollback when the active move completed before its journal update', async () => {
|
|
const root = temporaryRoot()
|
|
const seed = join(root, 'seed')
|
|
createTestSeedMetadata(seed, release())
|
|
writeFileSync(join(seed, 'pnpm-lock.yaml'), 'lockfileVersion: 9\n')
|
|
archiveStore(seed)
|
|
writeIntegrity(seed)
|
|
const paths = resolveDesktopPaths(join(root, '.dsh'))
|
|
const manager = new DesktopProjectManager(paths, { node: process.execPath, pnpm: writeFakePnpm(root) })
|
|
await manager.applyRelease(seed, '1.0.0', hooks())
|
|
await manager.mutate({ type: 'plugin-add', spec: '@scope/plugin@2.0.0' }, hooks())
|
|
const stagingProfile = join(paths.staging, 'interrupted', 'profile')
|
|
mkdirSync(stagingProfile, { recursive: true })
|
|
writeFileSync(join(stagingProfile, 'marker'), 'staging')
|
|
rmSync(paths.rollback, { recursive: true, force: true })
|
|
mkdirSync(dirname(paths.rollback), { recursive: true })
|
|
renameSync(paths.profile, paths.rollback)
|
|
writeFileSync(paths.pending, `${JSON.stringify({
|
|
schemaVersion: 1,
|
|
id: 'interrupted',
|
|
stagingProfile,
|
|
step: 'prepared',
|
|
})}\n`)
|
|
|
|
manager.recover()
|
|
|
|
expect(manager.listPlugins()).toEqual([{ name: '@scope/plugin', version: '2.0.0' }])
|
|
expect(existsSync(stagingProfile)).toBe(false)
|
|
expect(existsSync(paths.pending)).toBe(false)
|
|
})
|
|
|
|
it('records the live pnpm worker as transaction owner until it exits', async ({ task, signal }) => {
|
|
const root = temporaryRoot()
|
|
const seed = join(root, 'seed')
|
|
const ready = join(root, 'pnpm-ready')
|
|
const releaseWorker = join(root, 'pnpm-release')
|
|
createTestSeedMetadata(seed, release())
|
|
writeFileSync(join(seed, 'pnpm-lock.yaml'), 'lockfileVersion: 9\n')
|
|
archiveStore(seed)
|
|
writeIntegrity(seed)
|
|
const paths = resolveDesktopPaths(join(root, '.dsh'))
|
|
const runtime = { node: process.execPath, pnpm: writeBlockingFakePnpm(root, ready, releaseWorker) }
|
|
const manager = new DesktopProjectManager(paths, runtime)
|
|
const installing = manager.applyRelease(seed, '1.0.0', hooks())
|
|
// Teardown observes failures even if the runner has abandoned the test body.
|
|
const completed = installing.then(value => ({ value }), (error: unknown) => ({ error }))
|
|
releaseWorkers.push(async () => {
|
|
writeFileSync(releaseWorker, 'continue')
|
|
const outcome = await completed
|
|
if ('error' in outcome) throw outcome.error
|
|
})
|
|
// Child startup shares the test budget; an aborted poll must not resume ownership assertions.
|
|
await expect.poll(() => {
|
|
signal.throwIfAborted()
|
|
return existsSync(ready)
|
|
}, { timeout: task.timeout }).toBe(true)
|
|
signal.throwIfAborted()
|
|
const workerPid = Number.parseInt(readFileSync(ready, 'utf8'), 10)
|
|
expect(readFileSync(paths.lock, 'utf8')).toBe(`${String(workerPid)}\n`)
|
|
const competing = new DesktopProjectManager(paths, runtime)
|
|
await expect(competing.applyRelease(seed, '1.0.0', hooks())).rejects.toThrow(/another package transaction is active/u)
|
|
writeFileSync(releaseWorker, 'continue')
|
|
await expect(installing).resolves.toBe(true)
|
|
expect(existsSync(paths.lock)).toBe(false)
|
|
})
|
|
|
|
it('keeps core packages local while installing plugins from the desktop registry', async () => {
|
|
const root = temporaryRoot()
|
|
const seed = join(root, 'seed')
|
|
const log = join(root, 'pnpm-log.json')
|
|
createTestSeedMetadata(seed, release())
|
|
writeFileSync(join(seed, 'pnpm-lock.yaml'), 'lockfileVersion: 9\n')
|
|
archiveStore(seed)
|
|
writeIntegrity(seed)
|
|
const paths = resolveDesktopPaths(join(root, '.dsh'))
|
|
const manager = new DesktopProjectManager(paths, { node: process.execPath, pnpm: writeFakePnpm(root) })
|
|
await manager.applyRelease(seed, '1.0.0', hooks())
|
|
const previousLog = process.env.TEST_PNPM_LOG
|
|
process.env.TEST_PNPM_LOG = log
|
|
try {
|
|
await manager.mutate({ type: 'plugin-add', spec: '@scope/plugin@2.0.0' }, hooks())
|
|
} finally {
|
|
if (previousLog === undefined) delete process.env.TEST_PNPM_LOG
|
|
else process.env.TEST_PNPM_LOG = previousLog
|
|
}
|
|
|
|
const manifest = JSON.parse(readFileSync(join(paths.profile, 'package.json'), 'utf8')) as {
|
|
dependencies: Record<string, string>
|
|
}
|
|
const coreSpec = manifest.dependencies['@deepseek-ai/dsh']
|
|
expect(coreSpec).toMatch(/^file:\.\/desktop-packages\//u)
|
|
expect(readFileSync(join(paths.profile, 'pnpm-workspace.yaml'), 'utf8'))
|
|
.toContain(`${JSON.stringify('@deepseek-ai/dsh')}: ${JSON.stringify(coreSpec)}`)
|
|
expect(manifest.dependencies['@scope/plugin']).toBe('2.0.0')
|
|
const invocation = JSON.parse(readFileSync(log, 'utf8')) as { args: string[]; env: Record<string, string> }
|
|
expect(invocation.args).toContain('add')
|
|
expect(invocation.args).toContain('@scope/plugin@2.0.0')
|
|
expect(invocation.args).toContain('--config.registry=https://registry.npmjs.org/')
|
|
expect(invocation.env.NPM_CONFIG_REGISTRY).toBe('https://registry.npmjs.org/')
|
|
})
|
|
|
|
it('reconciles dsh to the packaged release without removing desktop plugins', async () => {
|
|
const root = temporaryRoot()
|
|
const paths = resolveDesktopPaths(join(root, '.dsh'))
|
|
const manager = new DesktopProjectManager(paths, { node: process.execPath, pnpm: writeFakePnpm(root) })
|
|
const firstSeed = join(root, 'seed-1')
|
|
createTestSeedMetadata(firstSeed, release('1.0.0'))
|
|
writeFileSync(join(firstSeed, 'pnpm-lock.yaml'), 'lockfileVersion: 9\n')
|
|
mkdirSync(join(firstSeed, 'store'), { recursive: true })
|
|
writeFileSync(join(firstSeed, 'store', 'release-1'), 'one')
|
|
archiveStore(firstSeed)
|
|
writeIntegrity(firstSeed)
|
|
await manager.applyRelease(firstSeed, '1.0.0', hooks())
|
|
await manager.mutate({ type: 'plugin-add', spec: '@scope/plugin@2.0.0' }, hooks())
|
|
|
|
const nextSeed = join(root, 'seed-2')
|
|
createTestSeedMetadata(nextSeed, release('1.1.0'))
|
|
writeFileSync(join(nextSeed, 'pnpm-lock.yaml'), 'lockfileVersion: 9\n')
|
|
mkdirSync(join(nextSeed, 'store'), { recursive: true })
|
|
writeFileSync(join(nextSeed, 'store', 'release-2'), 'two')
|
|
archiveStore(nextSeed)
|
|
writeIntegrity(nextSeed)
|
|
|
|
await expect(manager.applyRelease(nextSeed, '1.1.0', hooks())).resolves.toBe(true)
|
|
expect(manager.releaseVersion()).toBe('1.1.0')
|
|
expect(manager.dshVersion()).toBe('1.1.0')
|
|
expect(manager.listPlugins()).toEqual([{ name: '@scope/plugin', version: '2.0.0' }])
|
|
const profile = JSON.parse(readFileSync(join(paths.profile, 'package.json'), 'utf8')) as {
|
|
dsh: { profile: { bundles: string[] } }
|
|
}
|
|
expect(profile.dsh.profile.bundles).toEqual([
|
|
'@deepseek-ai/dsh-base',
|
|
'@deepseek-ai/dsh-web-app',
|
|
'@scope/plugin',
|
|
])
|
|
expect(readFileSync(join(paths.pnpm.store, 'release-1'), 'utf8')).toBe('one')
|
|
expect(readFileSync(join(paths.pnpm.store, 'release-2'), 'utf8')).toBe('two')
|
|
await expect(manager.applyRelease(nextSeed, '1.1.0', hooks())).resolves.toBe(false)
|
|
})
|
|
})
|