fix(release): verify the packed install against the framework it peers on
The harness packages declare the vendored framework as a peer, so installing only the dsh tarballs left npm resolving @deepseek-ai/cordis from a private registry the credential-free pack job cannot reach. The verification now takes several pack directories and installs every tarball in them, and the dsh workflow packs the vendored family for that purpose while still publishing only its own set. The verification also reads what each tarball declares instead of what the checkout says, which is what let the process and tarball helpers become one home each - the three copies of a spawn wrapper were a duplication finding.
This commit is contained in:
@@ -1,23 +1,28 @@
|
||||
/**
|
||||
* Install a packed release family into a throwaway consumer outside the
|
||||
* repository and drive its installed executable with plain Node.
|
||||
* Install packed tarballs into a throwaway consumer outside the repository and
|
||||
* drive the installed executable with plain Node.
|
||||
*
|
||||
* Everything the packed tarballs need comes from the tarballs themselves: the
|
||||
* consumer declares every member as a `file:` dependency, so the only registry
|
||||
* traffic is for external dependencies. What this proves is that `files`
|
||||
* selected a complete payload and that the published dependency ranges resolve
|
||||
* — a workspace link or a stale `lib/` in the checkout cannot stand in for a
|
||||
* missing file here
|
||||
* Every tarball the installed tree needs comes from `--from`, so the only
|
||||
* registry traffic is for external dependencies. That matters beyond hermetic
|
||||
* verification: the harness packages declare the vendored framework as a peer,
|
||||
* and those packages live in another release sequence that this credential-free
|
||||
* job cannot fetch from a private registry — so a dsh verification passes the
|
||||
* vendored family's pack output too, while publishing only its own
|
||||
* ([rationale](../../.agents/notes/proposed/process/2026-08-10-npm-release-sequences.md)).
|
||||
*
|
||||
* What this proves is that `files` selected a complete payload and that the
|
||||
* published dependency ranges resolve. A workspace link or a stale `lib/` in the
|
||||
* checkout cannot stand in for a missing file here.
|
||||
*/
|
||||
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import { parseArgs } from 'node:util'
|
||||
import { releaseFamily, tarballName, type ReleaseMember } from './families.ts'
|
||||
import { releaseFamily } from './families.ts'
|
||||
import { capture } from './process.ts'
|
||||
import { packedIdentity, readPublishOrder } from './tarball.ts'
|
||||
|
||||
/**
|
||||
* Environment for the installed artifact: no host Node hooks, no host DeepSeek
|
||||
@@ -38,63 +43,61 @@ function consumerEnvironment(consumerRoot: string): NodeJS.ProcessEnv {
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a command in the consumer and fail the process on a non-zero exit.
|
||||
* @param command - executable name.
|
||||
* @param args - command arguments.
|
||||
* @param cwd - working directory.
|
||||
* @param env - child environment.
|
||||
* @returns The captured stdout, trimmed.
|
||||
* Every packed tarball in the given directories, as `file:` dependency entries.
|
||||
* @param directories - absolute pack output directories.
|
||||
* @returns Package name to tarball file URL, and the version each carries.
|
||||
*/
|
||||
function run(command: string, args: readonly string[], cwd: string, env: NodeJS.ProcessEnv): string {
|
||||
const result = spawnSync(command, [...args], { cwd, env, encoding: 'utf8' })
|
||||
if (result.error !== undefined) throw result.error
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`${command} ${args.join(' ')} exited with ${String(result.status)}:\n${result.stdout}\n${result.stderr}`)
|
||||
function packedDependencies(directories: readonly string[]): Map<string, { url: string; version: string }> {
|
||||
const dependencies = new Map<string, { url: string; version: string }>()
|
||||
for (const directory of directories) {
|
||||
for (const filename of readPublishOrder(directory)) {
|
||||
const tarball = join(directory, filename)
|
||||
const { name, version } = packedIdentity(tarball)
|
||||
dependencies.set(name, { url: pathToFileURL(tarball).href, version })
|
||||
}
|
||||
}
|
||||
return result.stdout.trim()
|
||||
return dependencies
|
||||
}
|
||||
|
||||
/** Install the family named by `--family` from `--from` and drive its entry. */
|
||||
/** Install every tarball under `--from` and drive the `--family` entry. */
|
||||
function main(): void {
|
||||
const { values } = parseArgs({
|
||||
options: { family: { type: 'string' }, from: { type: 'string' } },
|
||||
options: { family: { type: 'string' }, from: { type: 'string', multiple: true } },
|
||||
allowPositionals: false,
|
||||
})
|
||||
if (values.family === undefined || values.from === undefined) {
|
||||
throw new Error('usage: verify-packed-install.ts --family <dsh|vendor> --from <packed directory>')
|
||||
if (values.family === undefined || values.from === undefined || values.from.length === 0) {
|
||||
throw new Error('usage: verify-packed-install.ts --family <dsh|vendor> --from <packed directory> [--from ...]')
|
||||
}
|
||||
|
||||
const family = releaseFamily(values.family)
|
||||
const entry = family.installedEntry
|
||||
const root = process.cwd()
|
||||
const packed = resolve(root, values.from)
|
||||
const members: ReleaseMember[] = family.members(root)
|
||||
|
||||
if (entry === undefined) {
|
||||
console.log(`release verify-packed-install: family ${family.id} publishes no executable, nothing to drive`)
|
||||
return
|
||||
}
|
||||
|
||||
const root = process.cwd()
|
||||
const packed = packedDependencies(values.from.map(directory => resolve(root, directory)))
|
||||
const expected = packed.get(entry.packageName)
|
||||
if (expected === undefined) throw new Error(`${entry.packageName} is not among the packed tarballs`)
|
||||
|
||||
const consumerRoot = mkdtempSync(join(tmpdir(), `dsh-packed-${family.id}-`))
|
||||
try {
|
||||
const dependencies = Object.fromEntries(members.map(member =>
|
||||
[member.name, pathToFileURL(join(packed, tarballName(member))).href]))
|
||||
writeFileSync(join(consumerRoot, 'package.json'), `${JSON.stringify({
|
||||
name: `dsh-packed-install-${family.id}`,
|
||||
version: '0.0.0',
|
||||
private: true,
|
||||
dependencies,
|
||||
dependencies: Object.fromEntries([...packed].map(([name, entryPacked]) => [name, entryPacked.url])),
|
||||
}, null, 2)}\n`)
|
||||
|
||||
const environment = consumerEnvironment(consumerRoot)
|
||||
console.log(`release verify-packed-install: installing ${String(members.length)} tarball(s) into ${consumerRoot}`)
|
||||
run('npm', ['install', '--no-audit', '--no-fund', '--package-lock=false'], consumerRoot, environment)
|
||||
console.log(`release verify-packed-install: installing ${String(packed.size)} tarball(s) into ${consumerRoot}`)
|
||||
capture('npm', ['install', '--no-audit', '--no-fund', '--package-lock=false'], { cwd: consumerRoot, env: environment })
|
||||
|
||||
const bin = join(consumerRoot, 'node_modules', ...entry.packageName.split('/'), entry.binPath)
|
||||
const version = run(process.execPath, [bin, '--version'], consumerRoot, environment)
|
||||
const expected = members.find(member => member.name === entry.packageName)?.version
|
||||
if (version !== expected) {
|
||||
throw new Error(`installed ${entry.packageName} --version reported ${JSON.stringify(version)}, expected ${String(expected)}`)
|
||||
const version = capture(process.execPath, [bin, '--version'], { cwd: consumerRoot, env: environment })
|
||||
if (version !== expected.version) {
|
||||
throw new Error(`installed ${entry.packageName} --version reported ${JSON.stringify(version)}, expected ${expected.version}`)
|
||||
}
|
||||
console.log(`release verify-packed-install: installed ${entry.packageName} reports ${version}`)
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user