test(plugins): install official bundles from isolated packed artifacts
This commit is contained in:
1 parent
f59c3196bf
commit
dfa0b337a5
4 files changed
+463
-159
No files matched your search
@@ -0,0 +1,60 @@
|
||||
/** Observe ordinary manager operations in an installed Web profile without starting native agents. */
|
||||
import { readFile, readdir, stat } from 'node:fs/promises'
|
||||
import { basename, join } from 'node:path'
|
||||
|
||||
export const name = 'official-installer-observer'
|
||||
export const inject = ['pluginManager', 'agentPresets', 'subagents', 'sessions', 'appReady']
|
||||
|
||||
export function apply(ctx, config) {
|
||||
let active = true
|
||||
const stop = ctx.appReady.onReady(() => {
|
||||
void exercise(ctx, config).then(
|
||||
value => { if (active) process.stdout.write(`PACKED_OFFICIAL_RESULT=${JSON.stringify({ value })}\n`) },
|
||||
error => { if (active) process.stdout.write(`PACKED_OFFICIAL_RESULT=${JSON.stringify({ error: String(error) })}\n`) },
|
||||
)
|
||||
})
|
||||
ctx.effect(() => () => { active = false; stop() }, 'packed-official-observer')
|
||||
}
|
||||
|
||||
function applied(stage, result) {
|
||||
if (result.application !== 'applied') throw new Error(`${stage}: ${JSON.stringify(result)}`)
|
||||
}
|
||||
|
||||
async function exercise(ctx, config) {
|
||||
const initial = await ctx.pluginManager.listBundles()
|
||||
await fetch(`${config.registry}catalog-observed`)
|
||||
const steps = []
|
||||
for (const name of config.packages) {
|
||||
const spec = initial.find(bundle => bundle.name === name)?.installTarget?.spec
|
||||
if (spec === undefined) throw new Error(`No catalog installation target for ${name}`)
|
||||
const options = { enabled: true, saveExact: true, registry: config.registry }
|
||||
let install = await ctx.pluginManager.installBundle(spec, options)
|
||||
const approval = install.pendingBuilds
|
||||
if (approval?.length) install = await ctx.pluginManager.installBundle(spec, { ...options, approvedBuilds: approval })
|
||||
applied(`install ${name}`, install)
|
||||
const manifest = JSON.parse(await readFile(join(config.profile, 'package.json'), 'utf8'))
|
||||
const installed = (await ctx.pluginManager.listBundles()).find(bundle => bundle.name === name)
|
||||
const provider = name.endsWith('-codex') ? 'codex' : 'claude-code'
|
||||
const providerEnabled = ctx.subagents.list().includes(provider)
|
||||
const nativeDir = join(config.profile, 'node_modules', config.nativePackages[name])
|
||||
const binaryName = `${provider === 'codex' ? 'codex' : 'claude'}${process.platform === 'win32' ? '.exe' : ''}`
|
||||
const nativeBinary = (await readdir(nativeDir, { recursive: true })).find(path => basename(path) === binaryName)
|
||||
const nativeArtifact = nativeBinary !== undefined && (await stat(join(nativeDir, nativeBinary))).size > 0
|
||||
const presets = await ctx.agentPresets.list()
|
||||
const beforeUnavailable = await readFile(join(config.profile, 'package.json'), 'utf8')
|
||||
const unavailable = await ctx.pluginManager.installBundle(`${name}@9999.0.0`, options)
|
||||
const unchangedAfterUnavailable = beforeUnavailable === await readFile(join(config.profile, 'package.json'), 'utf8')
|
||||
const off = await ctx.pluginManager.setBundleEnabled(name, false)
|
||||
applied(`disable ${name}`, off)
|
||||
const disabled = (await ctx.pluginManager.listBundles()).find(bundle => bundle.name === name)
|
||||
const on = await ctx.pluginManager.setBundleEnabled(name, true)
|
||||
applied(`enable ${name}`, on)
|
||||
const enabled = (await ctx.pluginManager.listBundles()).find(bundle => bundle.name === name)
|
||||
const removed = await ctx.pluginManager.removeBundle(name)
|
||||
applied(`remove ${name}`, removed)
|
||||
const absent = (await ctx.pluginManager.listBundles()).find(bundle => bundle.name === name)
|
||||
steps.push({ name, install, savedVersion: manifest.dependencies?.[name], installed, providerEnabled, nativeArtifact, presets,
|
||||
unavailable, unchangedAfterUnavailable, off, disabled, on, enabled, removed, absent })
|
||||
}
|
||||
return { initial, steps, sessionCount: ctx.sessions.list().length }
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
/** Install actual packed Official providers through the ordinary manager in a source-independent Web process. */
|
||||
import { createHash, randomUUID } from 'node:crypto'
|
||||
import { createReadStream, existsSync, globSync } from 'node:fs'
|
||||
import { readFile, writeFile } from 'node:fs/promises'
|
||||
import { createServer } from 'node:http'
|
||||
import { join } from 'node:path'
|
||||
import { createInterface } from 'node:readline'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { execa } from 'execa'
|
||||
import yaml from 'js-yaml'
|
||||
import { expect, it, onTestFinished } from 'vitest'
|
||||
import type { BundleInfo, ChangeResult } from '@deepseek-ai/dsh-plugin-manager'
|
||||
import { packedManifest } from '../../../../scripts/release/tarball.ts'
|
||||
import { assertInstalledTree, createPackedInstallation } from './packed-installation.ts'
|
||||
|
||||
const root = fileURLToPath(new URL('../../../..', import.meta.url))
|
||||
const packages = ['@deepseek-ai/dsh-subagent-claude-code', '@deepseek-ai/dsh-subagent-codex']
|
||||
const built = existsSync(join(root, 'apps/cli/lib/bin.js'))
|
||||
&& packages.every(name => existsSync(join(root, 'packages/subagent', name.slice('@deepseek-ai/dsh-'.length), 'lib/index.js')))
|
||||
|
||||
interface Observation {
|
||||
initial: BundleInfo[]
|
||||
sessionCount: number
|
||||
steps: Array<{
|
||||
name: string
|
||||
install: ChangeResult
|
||||
savedVersion: string
|
||||
installed: BundleInfo
|
||||
providerEnabled: boolean
|
||||
nativeArtifact: boolean
|
||||
presets: Array<{ id: string; broken?: string }>
|
||||
unavailable: ChangeResult
|
||||
unchangedAfterUnavailable: boolean
|
||||
off: ChangeResult
|
||||
disabled: BundleInfo
|
||||
on: ChangeResult
|
||||
enabled: BundleInfo
|
||||
removed: ChangeResult
|
||||
absent: BundleInfo
|
||||
}>
|
||||
}
|
||||
|
||||
it.skipIf(!built)('installs exact Official versions and switches/removes them through the generic package manager', {
|
||||
// Real tarball packing and isolated dependency installation own this budget; no native model request runs.
|
||||
timeout: 360_000,
|
||||
retry: 0,
|
||||
}, async () => {
|
||||
const fixture = await createPackedInstallation('dsh-official-packed-', packages)
|
||||
const { temporary, installation, home, profile, environment, tarballs, members, externalVersions } = fixture
|
||||
const manifests = new Map(packages.map((name) => {
|
||||
const archive = tarballs.get(name)
|
||||
if (archive === undefined) throw new Error(`missing provider archive ${name}`)
|
||||
return [name, { archive, manifest: packedManifest(archive) }]
|
||||
}))
|
||||
const version = manifests.get(packages[0]!)?.manifest.version
|
||||
if (typeof version !== 'string') throw new Error('packed provider has no version')
|
||||
const integrity = new Map(await Promise.all([...manifests].map(async ([name, entry]) =>
|
||||
[name, `sha512-${createHash('sha512').update(await readFile(entry.archive)).digest('base64')}`] as const)))
|
||||
const requests: string[] = []
|
||||
let requestsBeforeCatalog: string[] | undefined
|
||||
let registry = ''
|
||||
const namespace = `/${randomUUID()}/`
|
||||
const server = createServer((request, response) => {
|
||||
const pathname = new URL(request.url ?? '/', 'http://registry.invalid').pathname
|
||||
if (!pathname.startsWith(namespace)) { response.writeHead(404).end(); return }
|
||||
const path = decodeURIComponent(pathname.slice(namespace.length))
|
||||
if (path === 'catalog-observed') {
|
||||
requestsBeforeCatalog = [...requests]
|
||||
response.end('ok')
|
||||
return
|
||||
}
|
||||
requests.push(path)
|
||||
const archive = [...manifests].find(([name]) => path === `tarballs/${name}`)?.[1].archive
|
||||
if (archive !== undefined) {
|
||||
response.setHeader('content-type', 'application/octet-stream')
|
||||
createReadStream(archive).on('error', error => response.destroy(error)).pipe(response)
|
||||
return
|
||||
}
|
||||
const entry = manifests.get(path)
|
||||
if (entry === undefined) {
|
||||
// Only package publication is local: third-party metadata and payloads retain npm's real bytes.
|
||||
response.writeHead(302, { location: `https://registry.npmjs.org/${request.url?.slice(namespace.length) ?? ''}` }).end()
|
||||
return
|
||||
}
|
||||
response.setHeader('content-type', 'application/json')
|
||||
response.end(JSON.stringify({ name: path, 'dist-tags': { latest: version }, time: { [version]: '2000-01-01T00:00:00.000Z' },
|
||||
versions: { [version]: { ...entry.manifest, dist: { tarball: `${registry}tarballs/${encodeURIComponent(path)}`, integrity: integrity.get(path) } } },
|
||||
}))
|
||||
})
|
||||
onTestFinished(async () => {
|
||||
server.closeAllConnections()
|
||||
if (!server.listening) return
|
||||
await new Promise<void>((resolveClose, reject) => {
|
||||
server.close((error) => { if (error) reject(error); else resolveClose() })
|
||||
})
|
||||
})
|
||||
await new Promise<void>((resolveListen, reject) => {
|
||||
server.once('error', reject)
|
||||
server.listen(0, '127.0.0.1', () => { server.off('error', reject); resolveListen() })
|
||||
})
|
||||
const address = server.address()
|
||||
if (address === null || typeof address === 'string') throw new Error('test registry has no TCP address')
|
||||
registry = `http://127.0.0.1:${String(address.port)}${namespace}`
|
||||
|
||||
const dependencies = new Set(packages)
|
||||
for (const name of dependencies) {
|
||||
const member = members.get(name)
|
||||
if (member === undefined) throw new Error(`missing packed dependency ${name}`)
|
||||
for (const [dependency, range] of Object.entries(member.manifest.dependencies as Record<string, string> | undefined ?? {})) {
|
||||
if (range.startsWith('workspace:')) dependencies.add(dependency)
|
||||
}
|
||||
}
|
||||
for (const name of packages) {
|
||||
dependencies.delete(name)
|
||||
expect(existsSync(join(installation, 'node_modules', name))).toBe(false)
|
||||
}
|
||||
await writeFile(join(profile, 'package.json'), JSON.stringify({ name: 'packed-official-profile', private: true, dependencies: {},
|
||||
dsh: { profile: { bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'] } },
|
||||
}))
|
||||
const lock = yaml.load(await readFile(join(root, 'pnpm-lock.yaml'), 'utf8')) as {
|
||||
packages: Record<string, { os?: string[]; cpu?: string[]; libc?: string[] }>
|
||||
snapshots: Record<string, { optionalDependencies?: Record<string, string> }>
|
||||
}
|
||||
const report = process.report.getReport() as { header: { glibcVersionRuntime?: string } }
|
||||
const libc = process.platform === 'linux' ? report.header.glibcVersionRuntime === undefined ? 'musl' : 'glibc' : undefined
|
||||
// The fixture owns one host installation; foreign native artifacts are never executed or required here.
|
||||
const foreignPackages = new Set(Object.entries(lock.packages).filter(([, constraints]) =>
|
||||
([['os', process.platform], ['cpu', process.arch], ['libc', libc]] as const).some(([field, current]) => {
|
||||
const allowed = constraints[field]
|
||||
return current !== undefined && allowed !== undefined && !allowed.includes(current)
|
||||
&& !allowed.includes('any') && allowed.every(value => !value.startsWith('!'))
|
||||
})).map(([name]) => name))
|
||||
const ignoredOptionalDependencies = [...new Set(Object.values(lock.snapshots).flatMap(snapshot =>
|
||||
Object.entries(snapshot.optionalDependencies ?? {})
|
||||
.filter(([name, version]) => foreignPackages.has(`${name}@${version}`) || foreignPackages.has(version)).map(([name]) => name)))]
|
||||
const nativePackages = {
|
||||
'@deepseek-ai/dsh-subagent-claude-code': `@anthropic-ai/claude-agent-sdk-${process.platform}-${process.arch}${libc === 'musl' ? '-musl' : ''}`,
|
||||
'@deepseek-ai/dsh-subagent-codex': `@openai/codex-${process.platform}-${process.arch}`,
|
||||
}
|
||||
for (const name of Object.values(nativePackages)) expect(ignoredOptionalDependencies).not.toContain(name)
|
||||
await writeFile(join(profile, 'pnpm-workspace.yaml'), yaml.dump({ packages: ['.'], nodeLinker: 'hoisted', autoInstallPeers: false,
|
||||
ignoredOptionalDependencies,
|
||||
overrides: { ...externalVersions, ...Object.fromEntries([...dependencies].map(name => [name, `file:${tarballs.get(name)!}`])) },
|
||||
}))
|
||||
const externalScopes = new Set(Object.keys(externalVersions)
|
||||
.filter(name => name.startsWith('@') && !name.startsWith('@deepseek-ai/')).map(name => name.slice(0, name.indexOf('/'))))
|
||||
await writeFile(join(profile, '.npmrc'), [...externalScopes].map(scope => `${scope}:registry=https://registry.npmjs.org/`).join('\n') + '\n')
|
||||
const userConfig = join(temporary, 'empty.npmrc')
|
||||
await writeFile(userConfig, '')
|
||||
environment.npm_config_userconfig = userConfig
|
||||
const observer = join(temporary, 'official-installer-observer.mjs')
|
||||
await writeFile(observer, await readFile(new URL('./fixtures/official-installer-observer.mjs', import.meta.url), 'utf8'))
|
||||
const patch = join(temporary, 'official-installer.patch.yml')
|
||||
await writeFile(patch, yaml.dump([{ insert: [{ id: 'official-installer-observer', name: observer,
|
||||
config: { registry, profile, packages, nativePackages },
|
||||
}] }]))
|
||||
const child = execa(process.execPath, [join(installation, 'node_modules/@deepseek-ai/dsh/lib/bin.js'),
|
||||
'web', '--patch', patch, '--no-open', '--port', '0'], {
|
||||
cwd: temporary, env: environment, extendEnv: false, reject: false, timeout: 240_000,
|
||||
})
|
||||
if (child.stdout === null) throw new Error('installed Web process has no stdout')
|
||||
const lines = createInterface({ input: child.stdout })
|
||||
try {
|
||||
const observed = await new Promise<Observation>((resolveObservation, reject) => {
|
||||
lines.on('line', (line) => {
|
||||
if (!line.startsWith('PACKED_OFFICIAL_RESULT=')) return
|
||||
const report = JSON.parse(line.slice('PACKED_OFFICIAL_RESULT='.length)) as { value?: Observation; error?: string }
|
||||
if (report.error !== undefined) reject(new Error(report.error))
|
||||
else if (report.value !== undefined) resolveObservation(report.value)
|
||||
})
|
||||
void child.then((result) => { reject(new Error(`Web process exited before observation:\n${result.stdout}\n${result.stderr}`)) })
|
||||
})
|
||||
expect(requestsBeforeCatalog).toEqual([])
|
||||
for (const name of packages) expect(observed.initial.find(entry => entry.name === name)).toMatchObject({
|
||||
official: true, availability: 'missing', installed: false, enabled: false, installTarget: { spec: `${name}@${version}`, version },
|
||||
})
|
||||
for (const step of observed.steps) {
|
||||
expect(step.install, JSON.stringify(step.install)).toMatchObject({ application: 'applied', enabled: true, version })
|
||||
expect(step.savedVersion).toBe(version)
|
||||
expect(step.installed).toMatchObject({ official: true, availability: 'profile', enabled: true, installed: true, version })
|
||||
expect(step.installed.rows.filter(row => row.preset !== undefined).map(row => row.preset).sort())
|
||||
.toEqual(['preset-cordis', 'preset-ptc', 'preset-standard'])
|
||||
expect(step.providerEnabled).toBe(true)
|
||||
expect(step.nativeArtifact).toBe(true)
|
||||
expect(step.presets.every(preset => preset.broken === undefined)).toBe(true)
|
||||
expect(step.unavailable).toMatchObject({ application: 'failed', stage: 'install' })
|
||||
expect(step.unchangedAfterUnavailable).toBe(true)
|
||||
expect(step.off).toMatchObject({ application: 'applied' })
|
||||
expect(step.disabled).toMatchObject({ official: true, installed: true, enabled: false })
|
||||
expect(step.on).toMatchObject({ application: 'applied' })
|
||||
expect(step.enabled).toMatchObject({ official: true, installed: true, enabled: true })
|
||||
expect(step.removed, JSON.stringify(step.removed)).toMatchObject({ application: 'applied' })
|
||||
expect(step.absent).toMatchObject({ official: true, availability: 'missing', enabled: false, installed: false })
|
||||
}
|
||||
expect(observed.steps).toHaveLength(packages.length)
|
||||
expect(observed.sessionCount).toBe(0)
|
||||
expect(globSync('sessions/**/*.jsonl*', { cwd: home })).toEqual([])
|
||||
await assertInstalledTree(profile)
|
||||
} finally {
|
||||
lines.close()
|
||||
child.kill('SIGTERM')
|
||||
const result = await child
|
||||
expect(result.timedOut, `${result.stdout}\n${result.stderr}`).toBe(false)
|
||||
expect(result.exitCode === 0 || result.signal === 'SIGTERM', result.stderr).toBe(true)
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,190 @@
|
||||
/** Pack and install a source-independent product for idle Web-profile integration tests. */
|
||||
import { createRequire } from 'node:module'
|
||||
import { existsSync, globSync } from 'node:fs'
|
||||
import { lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join, relative, resolve, sep } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { execa } from 'execa'
|
||||
import yaml from 'js-yaml'
|
||||
import { expect, onTestFinished } from 'vitest'
|
||||
import { pnpmCommand } from '../../../../scripts/release/process.ts'
|
||||
import { packedWorkspaceClosure, type WorkspacePackage } from '../../../../packages/sandbox/sandbox-local/tests/packed-workspace-closure.ts'
|
||||
|
||||
const root = fileURLToPath(new URL('../../../..', import.meta.url))
|
||||
|
||||
/** Capture workspace dependency links without following their targets. */
|
||||
async function workspaceLinks(directories: string[]): Promise<Record<string, string>> {
|
||||
const links: Record<string, string> = {}
|
||||
for (const directory of directories) {
|
||||
const modules = join(directory, 'node_modules')
|
||||
links[modules] = existsSync(modules) ? 'directory' : 'absent'
|
||||
if (!existsSync(modules)) continue
|
||||
for (const entry of await readdir(modules, { withFileTypes: true })) {
|
||||
const path = join(modules, entry.name)
|
||||
const paths = entry.name.startsWith('@') && entry.isDirectory()
|
||||
? (await readdir(path)).map(name => join(path, name)) : [path]
|
||||
for (const dependency of paths) {
|
||||
const stat = await lstat(dependency)
|
||||
links[dependency] = stat.isSymbolicLink() ? await readlink(dependency) : stat.isDirectory() ? 'directory' : 'file'
|
||||
}
|
||||
}
|
||||
}
|
||||
return links
|
||||
}
|
||||
|
||||
/** Reject workspace links throughout an installed dependency tree.
|
||||
* @param directory - Installation or profile whose links must remain inside its own tree.
|
||||
*/
|
||||
export async function assertInstalledTree(directory: string): Promise<void> {
|
||||
const installedRoot = await realpath(directory)
|
||||
const visited = new Set<string>()
|
||||
const visit = async (path: string): Promise<void> => {
|
||||
const resolved = await realpath(path)
|
||||
expect(resolved === installedRoot || resolved.startsWith(installedRoot + sep), `${path} escapes its installation`).toBe(true)
|
||||
if (visited.has(resolved)) return
|
||||
visited.add(resolved)
|
||||
for (const entry of await readdir(path, { withFileTypes: true })) {
|
||||
const child = join(path, entry.name)
|
||||
if (entry.isSymbolicLink()) {
|
||||
// Removed packages can leave dangling .bin shims; those links must also stay inside the profile.
|
||||
const target = existsSync(child) ? await realpath(child) : resolve(dirname(child), await readlink(child))
|
||||
expect(target.startsWith(installedRoot + sep), `${child} escapes its installation`).toBe(true)
|
||||
} else if (entry.isDirectory()) await visit(child)
|
||||
}
|
||||
}
|
||||
await visit(directory)
|
||||
}
|
||||
|
||||
/** Actual tarballs and an isolated installed CLI, with an initially empty Web profile. */
|
||||
export interface PackedInstallation {
|
||||
temporary: string
|
||||
installation: string
|
||||
home: string
|
||||
profile: string
|
||||
archives: string
|
||||
environment: NodeJS.ProcessEnv
|
||||
tarballs: Map<string, string>
|
||||
members: Map<string, WorkspacePackage>
|
||||
externalVersions: Record<string, string>
|
||||
packageCommand: (args: string[], cwd: string) => Promise<{ stdout: string; stderr: string }>
|
||||
}
|
||||
|
||||
/** Build the packed product closure and additional packages without changing workspace dependency links.
|
||||
* Native confinement and Session-writing operations are outside this idle-profile fixture.
|
||||
* @param temporaryPrefix - Unique temporary-directory prefix identifying the owning test.
|
||||
* @param extraPackages - Additional workspace packages to pack without installing in the product.
|
||||
* @returns Isolated installation paths, tarballs, environment, and checked pnpm invocation.
|
||||
*/
|
||||
export async function createPackedInstallation(temporaryPrefix: string, extraPackages: readonly string[]): Promise<PackedInstallation> {
|
||||
const temporary = await realpath(await mkdtemp(join(tmpdir(), temporaryPrefix)))
|
||||
onTestFinished(() => rm(temporary, { recursive: true, force: true }))
|
||||
const installation = join(temporary, 'installation')
|
||||
const home = join(temporary, 'home')
|
||||
const profile = join(home, 'profiles', 'web')
|
||||
const archives = join(temporary, 'archives')
|
||||
await mkdir(profile, { recursive: true })
|
||||
await mkdir(archives)
|
||||
const environment: NodeJS.ProcessEnv = Object.fromEntries(Object.entries(process.env).filter(([key]) => (
|
||||
!/KEY|SECRET|TOKEN|PASSWORD/.test(key) && key !== 'NODE_OPTIONS' && key !== 'NODE_PATH'
|
||||
)))
|
||||
Object.assign(environment, {
|
||||
DSH_HOME: home,
|
||||
DSH_AGENTS_HOME: join(temporary, 'agents'),
|
||||
DSH_TELEMETRY_DISABLED: '1',
|
||||
CI: 'true',
|
||||
})
|
||||
const [pnpm, ...prefix] = pnpmCommand()
|
||||
const packageCommand = async (args: string[], cwd: string) => {
|
||||
const result = await execa(pnpm, [...prefix, ...args], {
|
||||
cwd, env: environment, extendEnv: false, timeout: 180_000, reject: false,
|
||||
})
|
||||
expect(result.timedOut, `${result.stdout}\n${result.stderr}`).toBe(false)
|
||||
expect(result.signal, result.stderr).toBeUndefined()
|
||||
expect(result.exitCode, `${result.stdout}\n${result.stderr}`).toBe(0)
|
||||
return result
|
||||
}
|
||||
const packages = new Map<string, WorkspacePackage>()
|
||||
for (const path of globSync([
|
||||
'apps/*/package.json', 'packages/*/*/package.json', 'vendor/*/package.json', 'native/system/packages/*/package.json',
|
||||
], { cwd: root })) {
|
||||
const manifest = JSON.parse(await readFile(join(root, path), 'utf8')) as Record<string, unknown>
|
||||
if (typeof manifest.name !== 'string') throw new Error(`${path} has no package name`)
|
||||
packages.set(manifest.name, { name: manifest.name, manifest, directory: dirname(join(root, path)) })
|
||||
}
|
||||
const sourceDirectories = [root, ...[...packages.values()].map(member => member.directory)]
|
||||
const initialLinks = await workspaceLinks(sourceDirectories)
|
||||
onTestFinished(async () => { expect(await workspaceLinks(sourceDirectories)).toEqual(initialLinks) })
|
||||
const closure = packedWorkspaceClosure('@deepseek-ai/dsh', packages)
|
||||
// These idle-profile tests create no Session or confined process. The native
|
||||
// platform packages load lazily for those operations and have separate smokes.
|
||||
const supportsIdleProfile = (member: WorkspacePackage) => (
|
||||
!member.name.startsWith('@deepseek-ai/node-addon-system-')
|
||||
&& (!Array.isArray(member.manifest.os) || member.manifest.os.includes(process.platform))
|
||||
&& (!Array.isArray(member.manifest.cpu) || member.manifest.cpu.includes(process.arch))
|
||||
)
|
||||
const runtimeMembers = closure.filter(supportsIdleProfile)
|
||||
const members = new Map(runtimeMembers.map(member => [member.name, member]))
|
||||
for (const name of extraPackages) {
|
||||
for (const member of packedWorkspaceClosure(name, packages).filter(supportsIdleProfile)) members.set(member.name, member)
|
||||
}
|
||||
// Packing reads package-owned files; installation never runs in the workspace.
|
||||
await packageCommand([
|
||||
'pack', '--recursive', '--workspace-concurrency=4',
|
||||
...[...members.keys()].map(name => `--filter=${name}`), '--pack-destination', archives,
|
||||
], root)
|
||||
const tarballs = new Map([...members.values()].map((member) => {
|
||||
if (typeof member.manifest.version !== 'string') throw new Error(`${member.name} has no version`)
|
||||
const name = member.name.replaceAll('/', '-').replace('@', '')
|
||||
const archive = join(archives, `${name}-${member.manifest.version}.tgz`)
|
||||
expect(existsSync(archive), `missing packed payload for ${member.name}`).toBe(true)
|
||||
return [member.name, archive]
|
||||
}))
|
||||
const workspaceSettings = yaml.load(await readFile(join(root, 'pnpm-workspace.yaml'), 'utf8')) as {
|
||||
allowBuilds: Record<string, boolean>
|
||||
patchedDependencies: Record<string, string>
|
||||
minimumReleaseAgeExclude: string[]
|
||||
overrides: Record<string, string>
|
||||
}
|
||||
const externalVersions: Record<string, string> = {}
|
||||
for (const member of members.values()) {
|
||||
const require = createRequire(join(member.directory, 'package.json'))
|
||||
for (const section of ['dependencies', 'optionalDependencies', 'peerDependencies']) {
|
||||
const dependencies = member.manifest[section] as Record<string, string> | undefined
|
||||
for (const [name, range] of Object.entries(dependencies ?? {})) {
|
||||
if (range.startsWith('workspace:')) continue
|
||||
const manifest = require.resolve.paths(name)?.map(path => join(path, name, 'package.json')).find(path => existsSync(path))
|
||||
if (manifest === undefined) continue
|
||||
const installed = JSON.parse(await readFile(manifest, 'utf8')) as { version: string }
|
||||
externalVersions[`${name}@${range}`] = installed.version
|
||||
}
|
||||
}
|
||||
}
|
||||
await mkdir(installation)
|
||||
for (const path of Object.values(workspaceSettings.patchedDependencies)) {
|
||||
await mkdir(dirname(join(installation, path)), { recursive: true })
|
||||
await writeFile(join(installation, path), await readFile(join(root, path)))
|
||||
}
|
||||
await writeFile(join(installation, 'package.json'), JSON.stringify({
|
||||
name: 'packed-product-installation', private: true,
|
||||
dependencies: Object.fromEntries(runtimeMembers.map(member => [member.name, `file:${tarballs.get(member.name)!}`])),
|
||||
}))
|
||||
await writeFile(join(installation, 'pnpm-workspace.yaml'), yaml.dump({
|
||||
packages: ['.'], nodeLinker: 'hoisted', autoInstallPeers: false, allowUnusedPatches: true,
|
||||
ignoredOptionalDependencies: closure.filter(member => !members.has(member.name)).map(member => member.name),
|
||||
allowBuilds: {
|
||||
...workspaceSettings.allowBuilds,
|
||||
[`@deepseek-ai/dsh-subprocess-local@file:${relative(installation, tarballs.get('@deepseek-ai/dsh-subprocess-local')!).split(sep).join('/')}`]: true,
|
||||
},
|
||||
patchedDependencies: workspaceSettings.patchedDependencies,
|
||||
minimumReleaseAgeExclude: workspaceSettings.minimumReleaseAgeExclude,
|
||||
overrides: {
|
||||
...workspaceSettings.overrides,
|
||||
...externalVersions,
|
||||
...Object.fromEntries(runtimeMembers.map(member => [member.name, `file:${tarballs.get(member.name)!}`])),
|
||||
},
|
||||
}))
|
||||
await packageCommand(['install', '--prefer-offline', '--no-frozen-lockfile'], installation)
|
||||
await assertInstalledTree(installation)
|
||||
return { temporary, installation, home, profile, archives, environment, tarballs, members, externalVersions, packageCommand }
|
||||
}
|
||||
@@ -1,16 +1,13 @@
|
||||
/** Exercise the published webhook example from an isolated installed Web profile. */
|
||||
import { createHmac } from 'node:crypto'
|
||||
import { createRequire } from 'node:module'
|
||||
import { existsSync, globSync } from 'node:fs'
|
||||
import { lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join, relative, sep } from 'node:path'
|
||||
import { readFile, writeFile } from 'node:fs/promises'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { execa } from 'execa'
|
||||
import yaml from 'js-yaml'
|
||||
import { expect, it, onTestFinished } from 'vitest'
|
||||
import { pnpmCommand } from '../../../../scripts/release/process.ts'
|
||||
import { packedWorkspaceClosure, type WorkspacePackage } from '../../../../packages/sandbox/sandbox-local/tests/packed-workspace-closure.ts'
|
||||
import { expect, it } from 'vitest'
|
||||
import { assertInstalledTree, createPackedInstallation } from './packed-installation.ts'
|
||||
|
||||
const root = fileURLToPath(new URL('../../../..', import.meta.url))
|
||||
const adapter = '@deepseek-ai/dsh-webhook-github'
|
||||
@@ -19,163 +16,14 @@ const built = existsSync(join(root, 'apps/cli/lib/bin.js'))
|
||||
&& existsSync(join(root, 'packages/experimental/webhook-github/lib/index.js'))
|
||||
const secret = 'packed-webhook-test-secret'
|
||||
|
||||
/** Capture workspace dependency links without following their targets. */
|
||||
async function workspaceLinks(directories: string[]): Promise<Record<string, string>> {
|
||||
const links: Record<string, string> = {}
|
||||
for (const directory of directories) {
|
||||
const modules = join(directory, 'node_modules')
|
||||
links[modules] = existsSync(modules) ? 'directory' : 'absent'
|
||||
if (!existsSync(modules)) continue
|
||||
for (const entry of await readdir(modules, { withFileTypes: true })) {
|
||||
const path = join(modules, entry.name)
|
||||
const paths = entry.name.startsWith('@') && entry.isDirectory()
|
||||
? (await readdir(path)).map(name => join(path, name)) : [path]
|
||||
for (const dependency of paths) {
|
||||
const stat = await lstat(dependency)
|
||||
links[dependency] = stat.isSymbolicLink() ? await readlink(dependency) : stat.isDirectory() ? 'directory' : 'file'
|
||||
}
|
||||
}
|
||||
}
|
||||
return links
|
||||
}
|
||||
|
||||
/** Reject workspace links throughout the installed dependency tree. */
|
||||
async function assertInstalledTree(directory: string): Promise<void> {
|
||||
const installedRoot = await realpath(directory)
|
||||
const visited = new Set<string>()
|
||||
const visit = async (path: string): Promise<void> => {
|
||||
const resolved = await realpath(path)
|
||||
expect(resolved === installedRoot || resolved.startsWith(installedRoot + sep), `${path} escapes its installation`).toBe(true)
|
||||
if (visited.has(resolved)) return
|
||||
visited.add(resolved)
|
||||
for (const entry of await readdir(path, { withFileTypes: true })) {
|
||||
const child = join(path, entry.name)
|
||||
if (entry.isSymbolicLink()) {
|
||||
const target = await realpath(child)
|
||||
expect(target.startsWith(installedRoot + sep), `${child} escapes its installation`).toBe(true)
|
||||
} else if (entry.isDirectory()) await visit(child)
|
||||
}
|
||||
}
|
||||
await visit(directory)
|
||||
}
|
||||
|
||||
it.skipIf(!built)('loads the packed GitHub example with its explicitly installed runtime peer', {
|
||||
// Packing and installing the complete product closure owns this budget; no model request runs.
|
||||
timeout: 360_000,
|
||||
retry: 0,
|
||||
}, async () => {
|
||||
const temporary = await realpath(await mkdtemp(join(tmpdir(), 'dsh-webhook-packed-')))
|
||||
onTestFinished(() => rm(temporary, { recursive: true, force: true }))
|
||||
const installation = join(temporary, 'installation')
|
||||
const home = join(temporary, 'home')
|
||||
const profile = join(home, 'profiles', 'web')
|
||||
const archives = join(temporary, 'archives')
|
||||
await mkdir(profile, { recursive: true })
|
||||
await mkdir(archives)
|
||||
const environment: NodeJS.ProcessEnv = Object.fromEntries(Object.entries(process.env).filter(([key]) => (
|
||||
!/KEY|SECRET|TOKEN|PASSWORD/.test(key) && key !== 'NODE_OPTIONS' && key !== 'NODE_PATH'
|
||||
)))
|
||||
Object.assign(environment, {
|
||||
DSH_HOME: home,
|
||||
DSH_AGENTS_HOME: join(temporary, 'agents'),
|
||||
DSH_TELEMETRY_DISABLED: '1',
|
||||
DSH_GITHUB_WEBHOOK_SECRET: secret,
|
||||
DSH_GITHUB_WEBHOOK_PORT: '0',
|
||||
CI: 'true',
|
||||
})
|
||||
const [pnpm, ...prefix] = pnpmCommand()
|
||||
const packageCommand = async (args: string[], cwd: string) => {
|
||||
const result = await execa(pnpm, [...prefix, ...args], {
|
||||
cwd, env: environment, extendEnv: false, timeout: 180_000, reject: false,
|
||||
})
|
||||
expect(result.timedOut, `${result.stdout}\n${result.stderr}`).toBe(false)
|
||||
expect(result.signal, result.stderr).toBeUndefined()
|
||||
expect(result.exitCode, `${result.stdout}\n${result.stderr}`).toBe(0)
|
||||
return result
|
||||
}
|
||||
const packages = new Map<string, WorkspacePackage>()
|
||||
for (const path of globSync([
|
||||
'apps/*/package.json', 'packages/*/*/package.json', 'vendor/*/package.json', 'native/system/packages/*/package.json',
|
||||
], { cwd: root })) {
|
||||
const manifest = JSON.parse(await readFile(join(root, path), 'utf8')) as Record<string, unknown>
|
||||
if (typeof manifest.name !== 'string') throw new Error(`${path} has no package name`)
|
||||
packages.set(manifest.name, { name: manifest.name, manifest, directory: dirname(join(root, path)) })
|
||||
}
|
||||
const sourceDirectories = [root, ...[...packages.values()].map(member => member.directory)]
|
||||
const initialLinks = await workspaceLinks(sourceDirectories)
|
||||
onTestFinished(async () => { expect(await workspaceLinks(sourceDirectories)).toEqual(initialLinks) })
|
||||
const closure = packedWorkspaceClosure('@deepseek-ai/dsh', packages)
|
||||
// A signed ping creates no Session and runs no confined process. The native
|
||||
// platform packages load lazily for those operations and have separate smokes.
|
||||
const runtimeMembers = closure.filter(member => (
|
||||
!member.name.startsWith('@deepseek-ai/node-addon-system-')
|
||||
&& (!Array.isArray(member.manifest.os) || member.manifest.os.includes(process.platform))
|
||||
&& (!Array.isArray(member.manifest.cpu) || member.manifest.cpu.includes(process.arch))
|
||||
))
|
||||
const members = new Map(runtimeMembers.map(member => [member.name, member]))
|
||||
for (const name of [runtime, adapter]) {
|
||||
const member = packages.get(name)
|
||||
if (member === undefined) throw new Error(`missing workspace package ${name}`)
|
||||
members.set(name, member)
|
||||
}
|
||||
// Packing reads package-owned files; installation never runs in the workspace.
|
||||
await packageCommand([
|
||||
'pack', '--recursive', '--workspace-concurrency=4',
|
||||
...[...members.keys()].map(name => `--filter=${name}`), '--pack-destination', archives,
|
||||
], root)
|
||||
const tarballs = new Map([...members.values()].map((member) => {
|
||||
if (typeof member.manifest.version !== 'string') throw new Error(`${member.name} has no version`)
|
||||
const name = member.name.replaceAll('/', '-').replace('@', '')
|
||||
const archive = join(archives, `${name}-${member.manifest.version}.tgz`)
|
||||
expect(existsSync(archive), `missing packed payload for ${member.name}`).toBe(true)
|
||||
return [member.name, archive]
|
||||
}))
|
||||
const workspaceSettings = yaml.load(await readFile(join(root, 'pnpm-workspace.yaml'), 'utf8')) as {
|
||||
allowBuilds: Record<string, boolean>
|
||||
patchedDependencies: Record<string, string>
|
||||
minimumReleaseAgeExclude: string[]
|
||||
overrides: Record<string, string>
|
||||
}
|
||||
const externalVersions: Record<string, string> = {}
|
||||
for (const member of members.values()) {
|
||||
const require = createRequire(join(member.directory, 'package.json'))
|
||||
for (const section of ['dependencies', 'optionalDependencies', 'peerDependencies']) {
|
||||
const dependencies = member.manifest[section] as Record<string, string> | undefined
|
||||
for (const [name, range] of Object.entries(dependencies ?? {})) {
|
||||
if (range.startsWith('workspace:')) continue
|
||||
const manifest = require.resolve.paths(name)?.map(path => join(path, name, 'package.json')).find(path => existsSync(path))
|
||||
if (manifest === undefined) continue
|
||||
const installed = JSON.parse(await readFile(manifest, 'utf8')) as { version: string }
|
||||
externalVersions[`${name}@${range}`] = installed.version
|
||||
}
|
||||
}
|
||||
}
|
||||
await mkdir(installation)
|
||||
for (const path of Object.values(workspaceSettings.patchedDependencies)) {
|
||||
await mkdir(dirname(join(installation, path)), { recursive: true })
|
||||
await writeFile(join(installation, path), await readFile(join(root, path)))
|
||||
}
|
||||
await writeFile(join(installation, 'package.json'), JSON.stringify({
|
||||
name: 'packed-webhook-installation', private: true,
|
||||
dependencies: Object.fromEntries(runtimeMembers.map(member => [member.name, `file:${tarballs.get(member.name)!}`])),
|
||||
}))
|
||||
await writeFile(join(installation, 'pnpm-workspace.yaml'), yaml.dump({
|
||||
packages: ['.'], nodeLinker: 'hoisted', autoInstallPeers: false, allowUnusedPatches: true,
|
||||
ignoredOptionalDependencies: closure.filter(member => !members.has(member.name)).map(member => member.name),
|
||||
allowBuilds: {
|
||||
...workspaceSettings.allowBuilds,
|
||||
[`@deepseek-ai/dsh-subprocess-local@file:${relative(installation, tarballs.get('@deepseek-ai/dsh-subprocess-local')!).split(sep).join('/')}`]: true,
|
||||
},
|
||||
patchedDependencies: workspaceSettings.patchedDependencies,
|
||||
minimumReleaseAgeExclude: workspaceSettings.minimumReleaseAgeExclude,
|
||||
overrides: {
|
||||
...workspaceSettings.overrides,
|
||||
...externalVersions,
|
||||
...Object.fromEntries(runtimeMembers.map(member => [member.name, `file:${tarballs.get(member.name)!}`])),
|
||||
},
|
||||
}))
|
||||
await packageCommand(['install', '--prefer-offline', '--no-frozen-lockfile'], installation)
|
||||
await assertInstalledTree(installation)
|
||||
const { temporary, installation, home, profile, environment, tarballs, members, externalVersions, packageCommand }
|
||||
= await createPackedInstallation('dsh-webhook-packed-', [runtime, adapter])
|
||||
Object.assign(environment, { DSH_GITHUB_WEBHOOK_SECRET: secret, DSH_GITHUB_WEBHOOK_PORT: '0' })
|
||||
expect(existsSync(join(installation, 'node_modules', runtime))).toBe(false)
|
||||
expect(existsSync(join(installation, 'node_modules', adapter))).toBe(false)
|
||||
await writeFile(join(profile, 'package.json'), JSON.stringify({
|
||||
|
||||
Reference in new issue
Block a user