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:
imccyu
2026-08-11 00:51:02 +08:00
parent bb489d1325
commit 787443b13f
7 changed files with 182 additions and 124 deletions

View File

@@ -13,11 +13,11 @@
* the tag after the commit merges. CI never writes to the repository.
*/
import { spawnSync } from 'node:child_process'
import { readFileSync, writeFileSync } from 'node:fs'
import { join, matchesGlob } from 'node:path'
import { parseArgs } from 'node:util'
import { releaseFamily, type ReleaseFamily, type ReleaseMember } from './families.ts'
import { capture } from './process.ts'
/** Files npm publishes whether or not `files` lists them. */
const ALWAYS_PUBLISHED = ['package.json', 'README*', 'LICENSE*', 'LICENCE*'] as const
@@ -25,21 +25,6 @@ const ALWAYS_PUBLISHED = ['package.json', 'README*', 'LICENSE*', 'LICENCE*'] as
/** Release types the dsh family accepts besides an explicit version. */
const RELEASE_TYPES = ['major', 'minor', 'patch'] as const
/**
* Run a command and fail the process on a non-zero exit.
* @param command - executable name.
* @param args - command arguments.
* @returns The captured stdout, trimmed.
*/
function run(command: string, args: readonly string[]): string {
const result = spawnSync(command, [...args], { 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}`)
}
return result.stdout.trim()
}
/**
* Split a version into its release numbers, discarding any prerelease segment.
* @param version - the current version.
@@ -106,7 +91,7 @@ function reachesPayload(member: ReleaseMember, path: string): boolean {
*/
function lastPublishedTag(family: ReleaseFamily, member: ReleaseMember): string | undefined {
const prefix = family.tagFor(member).replace(/-v[^-]*$/, '-v')
const tags = run('git', ['tag', '--list', `${prefix}*`, '--sort=-v:refname']).split('\n').filter(line => line !== '')
const tags = capture('git', ['tag', '--list', `${prefix}*`, '--sort=-v:refname']).split('\n').filter(line => line !== '')
return tags[0]
}
@@ -119,7 +104,7 @@ function lastPublishedTag(family: ReleaseFamily, member: ReleaseMember): string
function changedSincePublication(family: ReleaseFamily, member: ReleaseMember): boolean {
const tag = lastPublishedTag(family, member)
if (tag === undefined) return true
const changed = run('git', ['diff', '--name-only', `${tag}..HEAD`, '--', member.directory])
const changed = capture('git', ['diff', '--name-only', `${tag}..HEAD`, '--', member.directory])
.split('\n').filter(line => line !== '')
return changed.some(path => reachesPayload(member, path))
}
@@ -176,7 +161,7 @@ function main(): void {
const dryRun = values['dry-run']
if (!dryRun) {
for (const { member, version } of planned) writeVersion(root, member, version)
run('pnpm', ['install', '--lockfile-only'])
capture('pnpm', ['install', '--lockfile-only'])
}
const summary = sharedVersion
@@ -188,8 +173,8 @@ function main(): void {
console.log('release bump: dry run, nothing written')
return
}
run('git', ['add', 'pnpm-lock.yaml', ...planned.map(entry => join(entry.member.directory, 'package.json'))])
run('git', ['commit', '-m', `release(${family.id}): ${summary}`])
capture('git', ['add', 'pnpm-lock.yaml', ...planned.map(entry => join(entry.member.directory, 'package.json'))])
capture('git', ['commit', '-m', `release(${family.id}): ${summary}`])
// The dsh family tags once for its shared version; vendor tags each package.
const tags = [...new Set(planned.map(entry => family.tagFor({ ...entry.member, version: entry.version })))]
console.log('release bump: committed. After this merges to master, tag it:')