| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788 |
- /**
- * Pack one release family's whole publish set into a single directory, in
- * publish order, and record that order for the publish step.
- *
- * The pack step is the release boundary: it runs without credentials, produces
- * every tarball from one commit, and hands the publish step exactly those bytes
- * ([rationale](../../.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md)).
- */
- import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
- import { join, resolve } from 'node:path'
- import { parseArgs } from 'node:util'
- import { releaseFamily, tarballName, type ReleaseFamily, type ReleaseMember } from './families.ts'
- import { isEntry, runConcurrent } from './process.ts'
- import { PUBLISH_ORDER_FILE, tarballFiles } from './tarball.ts'
- /** Where pack output lands when `--out` is omitted. */
- const DEFAULT_OUTPUT = 'dist/npm'
- /**
- * Pack one member and check what its tarball carries.
- * @param family - the release family being packed.
- * @param member - the member to pack.
- * @param destination - absolute output directory.
- * @returns The tarball filename.
- */
- async function packMember(family: ReleaseFamily, member: ReleaseMember, destination: string): Promise<string> {
- await runConcurrent('pnpm', ['--dir', member.directory, 'pack', '--pack-destination', destination])
- const filename = tarballName(member)
- const tarball = join(destination, filename)
- if (!existsSync(tarball)) throw new Error(`${member.name} produced no tarball at ${tarball}`)
- family.validatePayload(member, tarballFiles(tarball))
- return filename
- }
- /**
- * @returns The validated `--concurrency` value; 1 (the default) packs the
- * members one at a time, exactly as the credentialed publish workflows run it.
- */
- function parseConcurrency(raw: string | undefined): number {
- if (raw === undefined) return 1
- const parsed = Number.parseInt(raw, 10)
- if (!Number.isSafeInteger(parsed) || parsed < 1 || String(parsed) !== raw) {
- throw new Error(`--concurrency must be a positive integer, got ${JSON.stringify(raw)}`)
- }
- return parsed
- }
- /** Pack the family named by `--family` into `--out`. */
- async function main(): Promise<void> {
- const { values } = parseArgs({
- options: { family: { type: 'string' }, out: { type: 'string' }, concurrency: { type: 'string' } },
- allowPositionals: false,
- })
- if (values.family === undefined) throw new Error('usage: pack.ts --family <dsh|vendor> [--out dist/npm] [--concurrency 1]')
- const concurrency = parseConcurrency(values.concurrency)
- const family = releaseFamily(values.family)
- const root = process.cwd()
- const destination = resolve(root, values.out ?? DEFAULT_OUTPUT)
- const members = family.publishOrder(family.members(root)).order
- family.verifyBuildArtifacts(root)
- family.verifyVersions(members)
- rmSync(destination, { recursive: true, force: true })
- mkdirSync(destination, { recursive: true })
- // Members pack in a bounded pool; the recorded publish order stays the
- // members' order regardless of completion order, because each worker writes
- // its result at the member's own position.
- const order = new Array<string>(members.length)
- let cursor = 0
- await Promise.all(Array.from({ length: Math.min(concurrency, members.length) }, async () => {
- while (cursor < members.length) {
- const index = cursor
- cursor += 1
- const member = members[index]
- if (member === undefined) break
- order[index] = await packMember(family, member, destination)
- }
- }))
- writeFileSync(join(destination, PUBLISH_ORDER_FILE), `${order.join('\n')}\n`)
- console.log(`release pack: family ${family.id}, ${String(order.length)} tarball(s) in ${values.out ?? DEFAULT_OUTPUT}`)
- }
- if (isEntry(import.meta.url)) await main()
|