From 27c9ca12a2d3079e51fc85dff048ccec1828186f Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Tue, 11 Aug 2026 00:26:26 +0800 Subject: [PATCH] feat(release): drive the installed entry from the packed tarballs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A throwaway consumer outside the repository declares every member as a file: dependency, installs, and runs the installed executable with plain Node, asserting the version it reports. That is the check a workspace link or a stale lib/ in the checkout cannot pass for: it reads only what files selected. The family declares its executable, so the vendored family — libraries a consumer imports, with no executable — states that it has none instead of carrying a probe that would prove nothing. Both pack workflows run it after packing, still without credentials. --- .github/workflows/release-vendor.yml | 3 + .github/workflows/release.yml | 3 + package.json | 1 + scripts/release/families.ts | 19 ++++ scripts/release/verify-packed-install.ts | 105 +++++++++++++++++++++++ 5 files changed, 131 insertions(+) create mode 100644 scripts/release/verify-packed-install.ts diff --git a/.github/workflows/release-vendor.yml b/.github/workflows/release-vendor.yml index b83547b560..dc778f3d21 100644 --- a/.github/workflows/release-vendor.yml +++ b/.github/workflows/release-vendor.yml @@ -80,6 +80,9 @@ jobs: - name: Pack release tarballs run: pnpm run release:pack --family vendor --out dist/npm-vendor + - name: Verify packed install + run: pnpm run release:verify-packed-install --family vendor --from dist/npm-vendor + - uses: actions/upload-artifact@v4 with: name: vendor-npm-tarballs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8b4005f42f..affdd3c900 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -79,6 +79,9 @@ jobs: - name: Pack release tarballs run: pnpm run release:pack --family dsh --out dist/npm + - name: Verify packed install + run: pnpm run release:verify-packed-install --family dsh --from dist/npm + - uses: actions/upload-artifact@v4 with: name: dsh-npm-tarballs diff --git a/package.json b/package.json index 32138d03e8..0d17549909 100644 --- a/package.json +++ b/package.json @@ -123,6 +123,7 @@ "publish:npm-baseline": "tsx scripts/publish-npm-baseline.ts", "release:verify": "tsx scripts/release/verify.ts", "release:pack": "tsx scripts/release/pack.ts", + "release:verify-packed-install": "tsx scripts/release/verify-packed-install.ts", "release:publish": "tsx scripts/release/publish.ts", "dsh": "node --import tsx/esm apps/cli/src/bin.ts", "demo:headless": "node --import tsx/esm apps/cli/src/bin.ts --profile headless", diff --git a/scripts/release/families.ts b/scripts/release/families.ts index b02c21cb1e..78f5de1951 100644 --- a/scripts/release/families.ts +++ b/scripts/release/families.ts @@ -57,6 +57,14 @@ function requireString(manifest: Record, field: string, context return value } +/** The executable a family's installed artifacts are driven through. */ +export interface InstalledEntry { + /** Package that carries the executable. */ + readonly packageName: string + /** Path to the executable inside that package. */ + readonly binPath: string +} + /** A release sequence: its members, its version baseline, and its tag naming. */ export abstract class ReleaseFamily { /** Workflow-facing identifier, also the `--family` argument. */ @@ -167,6 +175,12 @@ export abstract class ReleaseFamily { * @param files - every path inside its tarball. */ abstract validatePayload(member: ReleaseMember, files: readonly string[]): void + + /** + * The executable that proves this family's artifacts install and run, or + * `undefined` for a family that publishes no executable. + */ + abstract readonly installedEntry: InstalledEntry | undefined } /** `packages/*` and `apps/*`: one shared version across the whole family. */ @@ -206,6 +220,8 @@ class DshFamily extends ReleaseFamily { typeRTRemoteNavigation: hasTypeRTRemoteNavigation(member.manifest), }) } + + readonly installedEntry = { packageName: '@deepseek-ai/dsh', binPath: 'lib/bin.js' } } /** `vendor/*`: every package keeps its own version line, so every package has its own tag. */ @@ -250,6 +266,9 @@ class VendorFamily extends ReleaseFamily { validatePayload(member: ReleaseMember, files: readonly string[]): void { if (files.length === 0) throw new Error(`${member.name} packed an empty tarball`) } + + /** No installed-entry probe: these are libraries a consumer imports, with no executable. */ + readonly installedEntry = undefined } /** Every release family this module owns, in workflow order. */ diff --git a/scripts/release/verify-packed-install.ts b/scripts/release/verify-packed-install.ts new file mode 100644 index 0000000000..d50e876f06 --- /dev/null +++ b/scripts/release/verify-packed-install.ts @@ -0,0 +1,105 @@ +/** + * Install a packed release family into a throwaway consumer outside the + * repository and drive its 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 + * ([rationale](../../.agents/notes/proposed/process/2026-08-10-npm-release-sequences.md)). + */ + +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' + +/** + * Environment for the installed artifact: no host Node hooks, no host DeepSeek + * Harness home, and no ambient npm user agent that would confuse npm. + * @param consumerRoot - the throwaway consumer directory. + * @returns The child environment. + */ +function consumerEnvironment(consumerRoot: string): NodeJS.ProcessEnv { + const environment = { ...process.env } + delete environment.npm_config_user_agent + delete environment.NPM_CONFIG_USER_AGENT + delete environment.NODE_OPTIONS + delete environment.NODE_PATH + environment.DSH_HOME = resolve(consumerRoot, '.dsh') + environment.DSH_AGENTS_HOME = resolve(consumerRoot, '.agents') + environment.DSH_TELEMETRY_DISABLED = '1' + return environment +} + +/** + * 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. + */ +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}`) + } + return result.stdout.trim() +} + +/** Install the family named by `--family` from `--from` and drive its entry. */ +function main(): void { + const { values } = parseArgs({ + options: { family: { type: 'string' }, from: { type: 'string' } }, + allowPositionals: false, + }) + if (values.family === undefined || values.from === undefined) { + throw new Error('usage: verify-packed-install.ts --family --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 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, + }, 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) + + 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)}`) + } + console.log(`release verify-packed-install: installed ${entry.packageName} reports ${version}`) + } finally { + rmSync(consumerRoot, { recursive: true, force: true }) + } +} + +main()