verify-packed-install.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. /**
  2. * Install packed tarballs into a throwaway consumer outside the repository and
  3. * drive the installed executable with plain Node.
  4. *
  5. * Every tarball the installed tree needs comes from `--from`, so the only
  6. * registry traffic is for external dependencies. That matters beyond hermetic
  7. * verification: the harness packages declare the vendored framework as a peer,
  8. * and those packages live in another release sequence that this credential-free
  9. * job cannot fetch from a private registry — so a dsh verification passes the
  10. * vendored family's pack output too, while publishing only its own
  11. * ([rationale](../../.agents/notes/proposed/process/2026-08-10-npm-release-sequences.md)).
  12. *
  13. * What this proves is that `files` selected a complete payload and that the
  14. * published dependency ranges resolve. A workspace link or a stale `lib/` in the
  15. * checkout cannot stand in for a missing file here.
  16. */
  17. import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
  18. import { tmpdir } from 'node:os'
  19. import { join, resolve } from 'node:path'
  20. import { pathToFileURL } from 'node:url'
  21. import { parseArgs } from 'node:util'
  22. import { releaseFamily } from './families.ts'
  23. import { capture } from './process.ts'
  24. import { packedIdentity, readPublishOrder } from './tarball.ts'
  25. /**
  26. * Environment for the installed artifact: no host Node hooks, no host DeepSeek
  27. * Harness home, and no ambient npm user agent that would confuse npm.
  28. * @param consumerRoot - the throwaway consumer directory.
  29. * @returns The child environment.
  30. */
  31. function consumerEnvironment(consumerRoot: string): NodeJS.ProcessEnv {
  32. const environment = { ...process.env }
  33. delete environment.npm_config_user_agent
  34. delete environment.NPM_CONFIG_USER_AGENT
  35. delete environment.NODE_OPTIONS
  36. delete environment.NODE_PATH
  37. environment.DSH_HOME = resolve(consumerRoot, '.dsh')
  38. environment.DSH_AGENTS_HOME = resolve(consumerRoot, '.agents')
  39. environment.DSH_TELEMETRY_DISABLED = '1'
  40. return environment
  41. }
  42. /**
  43. * Every packed tarball in the given directories, as `file:` dependency entries.
  44. * @param directories - absolute pack output directories.
  45. * @returns Package name to tarball file URL, and the version each carries.
  46. */
  47. function packedDependencies(directories: readonly string[]): Map<string, { url: string; version: string }> {
  48. const dependencies = new Map<string, { url: string; version: string }>()
  49. for (const directory of directories) {
  50. for (const filename of readPublishOrder(directory)) {
  51. const tarball = join(directory, filename)
  52. const { name, version } = packedIdentity(tarball)
  53. dependencies.set(name, { url: pathToFileURL(tarball).href, version })
  54. }
  55. }
  56. return dependencies
  57. }
  58. /** Install every tarball under `--from` and drive the `--family` entry. */
  59. function main(): void {
  60. const { values } = parseArgs({
  61. options: { family: { type: 'string' }, from: { type: 'string', multiple: true } },
  62. allowPositionals: false,
  63. })
  64. if (values.family === undefined || values.from === undefined || values.from.length === 0) {
  65. throw new Error('usage: verify-packed-install.ts --family <dsh|vendor> --from <packed directory> [--from ...]')
  66. }
  67. const family = releaseFamily(values.family)
  68. const entry = family.installedEntry
  69. if (entry === undefined) {
  70. console.log(`release verify-packed-install: family ${family.id} publishes no executable, nothing to drive`)
  71. return
  72. }
  73. const root = process.cwd()
  74. const packed = packedDependencies(values.from.map(directory => resolve(root, directory)))
  75. const expected = packed.get(entry.packageName)
  76. if (expected === undefined) throw new Error(`${entry.packageName} is not among the packed tarballs`)
  77. const consumerRoot = mkdtempSync(join(tmpdir(), `dsh-packed-${family.id}-`))
  78. try {
  79. writeFileSync(join(consumerRoot, 'package.json'), `${JSON.stringify({
  80. name: `dsh-packed-install-${family.id}`,
  81. version: '0.0.0',
  82. private: true,
  83. dependencies: Object.fromEntries([...packed].map(([name, entryPacked]) => [name, entryPacked.url])),
  84. }, null, 2)}\n`)
  85. const environment = consumerEnvironment(consumerRoot)
  86. console.log(`release verify-packed-install: installing ${String(packed.size)} tarball(s) into ${consumerRoot}`)
  87. capture('npm', ['install', '--no-audit', '--no-fund', '--package-lock=false'], { cwd: consumerRoot, env: environment })
  88. const bin = join(consumerRoot, 'node_modules', ...entry.packageName.split('/'), entry.binPath)
  89. const version = capture(process.execPath, [bin, '--version'], { cwd: consumerRoot, env: environment })
  90. if (version !== expected.version) {
  91. throw new Error(`installed ${entry.packageName} --version reported ${JSON.stringify(version)}, expected ${expected.version}`)
  92. }
  93. console.log(`release verify-packed-install: installed ${entry.packageName} reports ${version}`)
  94. } finally {
  95. rmSync(consumerRoot, { recursive: true, force: true })
  96. }
  97. }
  98. main()