The root manifest carries the dsh family version. bump writes it with the members, because the workspace constraint requires them to match, and that constraint now accepts a prerelease segment: without both, release:dsh 0.0.2 left the root behind and 0.0.1-rc.1 could satisfy neither check. The Landlock workflow no longer passes --access public, which overrode the restricted publishConfig this repository just adopted for those packages. Vendored change detection reads build inputs when a package publishes build output, and vendor/cordis publishes the src its export map already pointed at: its lib/ is untracked, so a real source edit read as 'nothing changed' and the next publish would fail on a version whose bytes moved. The next version also takes the last published version as its baseline, so a re-sync that restores a lower upstream version cannot recompute a version already on the registry, and bump confirms the registry carries what the newest tag names. Tag prefixes are constructed rather than recovered from a full tag, which a hyphenated version defeated. Pack runs group per ref so concurrent pull requests stop displacing each other, the publish job carries the global group, and the unused id-token permission is gone. Every release script sits behind an entry guard, which is what lets the pure judgements carry tests: tag naming, publish order and cycle reporting, version arithmetic, payload policy, and the change judgement. The Agent Note moves to implemented and states what shipped: one probe command, the registry confirmation that now exists, and byte reproducibility recorded as assumed rather than measured.
83 lines
3.2 KiB
TypeScript
83 lines
3.2 KiB
TypeScript
/**
|
|
* Process helpers shared by the release scripts: the release steps drive `git`,
|
|
* `pnpm`, `npm`, and `tar`, and each needs one of three failure behaviours.
|
|
*/
|
|
|
|
import { spawnSync } from 'node:child_process'
|
|
import { realpathSync } from 'node:fs'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
/** Where and with what environment a release step runs a command. */
|
|
export interface RunOptions {
|
|
/** Working directory; defaults to the current one. */
|
|
readonly cwd?: string
|
|
/** Child environment; defaults to this process's. */
|
|
readonly env?: NodeJS.ProcessEnv
|
|
}
|
|
|
|
/** What a command produced, for a caller that decides what a failure means. */
|
|
export interface CommandResult {
|
|
/** Exit status, or null when a signal ended the process. */
|
|
readonly status: number | null
|
|
/** Captured standard output. */
|
|
readonly stdout: string
|
|
/** Captured standard error. */
|
|
readonly stderr: string
|
|
}
|
|
|
|
/**
|
|
* Run a command and capture its output without judging the exit status.
|
|
* @param command - executable name.
|
|
* @param args - command arguments.
|
|
* @param options - working directory and environment.
|
|
* @returns The exit status and captured streams.
|
|
*/
|
|
export function attempt(command: string, args: readonly string[], options: RunOptions = {}): CommandResult {
|
|
const result = spawnSync(command, [...args], { cwd: options.cwd, env: options.env, encoding: 'utf8' })
|
|
if (result.error !== undefined) throw result.error
|
|
return { status: result.status, stdout: result.stdout, stderr: result.stderr }
|
|
}
|
|
|
|
/**
|
|
* Run a command, capture its standard output, and fail on a non-zero exit.
|
|
* @param command - executable name.
|
|
* @param args - command arguments.
|
|
* @param options - working directory and environment.
|
|
* @returns The trimmed standard output.
|
|
*/
|
|
export function capture(command: string, args: readonly string[], options: RunOptions = {}): string {
|
|
const result = attempt(command, args, options)
|
|
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()
|
|
}
|
|
|
|
/**
|
|
* Run a command with inherited streams, so its progress reaches the log, and
|
|
* fail on a non-zero exit.
|
|
* @param command - executable name.
|
|
* @param args - command arguments.
|
|
* @param options - working directory and environment.
|
|
*/
|
|
export function run(command: string, args: readonly string[], options: RunOptions = {}): void {
|
|
const result = spawnSync(command, [...args], { cwd: options.cwd, env: options.env, stdio: 'inherit' })
|
|
if (result.error !== undefined) throw result.error
|
|
if (result.status !== 0) throw new Error(`${command} ${args.join(' ')} exited with ${String(result.status)}`)
|
|
}
|
|
|
|
/**
|
|
* Whether this module is the process entry point.
|
|
*
|
|
* The release scripts are both commands and modules: a test imports their pure
|
|
* logic, and importing a module runs its body, so an unguarded `main()` would
|
|
* run the wrong command with the wrong arguments.
|
|
* @param moduleUrl - the caller's `import.meta.url`.
|
|
* @returns True when Node started this module.
|
|
*/
|
|
export function isEntry(moduleUrl: string): boolean {
|
|
const invoked = process.argv[1]
|
|
if (invoked === undefined) return false
|
|
return realpathSync(invoked) === realpathSync(fileURLToPath(moduleUrl))
|
|
}
|