vouchington-tooling 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +10 -0
  2. package/dist/cli/commands/allocate-browser-safe-ports.slot-fixtures.test-helpers.d.mts +6 -0
  3. package/dist/cli/commands/allocate-browser-safe-ports.slot-fixtures.test-helpers.mjs +103 -0
  4. package/dist/cli/commands/github-output.test-helpers.d.mts +1 -0
  5. package/dist/cli/commands/github-output.test-helpers.mjs +32 -0
  6. package/dist/cli/commands/pnpm-install.d.mts +1 -0
  7. package/dist/cli/commands/pnpm-install.mjs +27 -0
  8. package/dist/cli/commands/spawn-script.d.mts +5 -0
  9. package/dist/cli/commands/spawn-script.mjs +7 -0
  10. package/dist/cli/commands/vitest-blob-manifest.d.mts +1 -0
  11. package/dist/cli/commands/vitest-blob-manifest.mjs +11 -0
  12. package/dist/cli/index.mjs +28 -0
  13. package/dist/cli/parse.d.mts +11 -0
  14. package/dist/cli/parse.mjs +14 -0
  15. package/dist/cli/script-path.d.mts +1 -0
  16. package/dist/cli/script-path.mjs +4 -0
  17. package/dist/cli/usage.d.mts +1 -1
  18. package/dist/cli/usage.mjs +16 -1
  19. package/dist/index.d.mts +6 -0
  20. package/dist/index.mjs +3 -0
  21. package/dist/pnpm-install/exec.d.mts +8 -0
  22. package/dist/pnpm-install/exec.mjs +88 -0
  23. package/dist/pnpm-install/index.d.mts +7 -0
  24. package/dist/pnpm-install/index.mjs +6 -0
  25. package/dist/pnpm-install/metadata.d.mts +5 -0
  26. package/dist/pnpm-install/metadata.mjs +91 -0
  27. package/dist/pnpm-install/pnpm-install-fixture.test-helpers.d.mts +22 -0
  28. package/dist/pnpm-install/pnpm-install-fixture.test-helpers.mjs +174 -0
  29. package/dist/pnpm-install/process.d.mts +11 -0
  30. package/dist/pnpm-install/process.mjs +81 -0
  31. package/dist/pnpm-install/release-age.d.mts +9 -0
  32. package/dist/pnpm-install/release-age.mjs +59 -0
  33. package/dist/pnpm-install/runner.d.mts +2 -0
  34. package/dist/pnpm-install/runner.mjs +106 -0
  35. package/dist/pnpm-install/support.d.mts +30 -0
  36. package/dist/pnpm-install/support.mjs +143 -0
  37. package/dist/shared-context/fake-git.test-helpers.d.mts +11 -0
  38. package/dist/shared-context/fake-git.test-helpers.mjs +59 -0
  39. package/dist/shared-context/index.d.mts +26 -0
  40. package/dist/shared-context/index.mjs +89 -0
  41. package/dist/vitest-blob-manifest/cli.d.mts +2 -0
  42. package/dist/vitest-blob-manifest/cli.mjs +33 -0
  43. package/dist/vitest-blob-manifest/index.d.mts +37 -0
  44. package/dist/vitest-blob-manifest/index.mjs +134 -0
  45. package/package.json +16 -1
  46. package/scripts/allocate-browser-safe-ports.py +661 -0
  47. package/scripts/fetch-forbidden-ports.json +7 -0
  48. package/scripts/gha/check-needs-results.sh +19 -0
  49. package/scripts/gha/download-with-diagnostics.sh +52 -0
  50. package/scripts/gha/host-pressure-diagnostics.sh +179 -0
  51. package/scripts/gha/write-github-multiline-output.sh +60 -0
  52. package/scripts/runner-port-policy.json +7 -0
package/README.md CHANGED
@@ -17,6 +17,13 @@ vouchington runner-port-policy --file ./policy.json
17
17
  vouchington runner-port-policy --reserved 2200
18
18
  vouchington with-host-lock --name expensive-build --timeout-seconds 60 -- make build
19
19
  vouchington gha-runtime-audit --pr-workflow CI --push-workflow '/^Main CI \\(.+\\)$/'
20
+ vouchington gha-output name
21
+ vouchington gha-needs-results
22
+ vouchington download-with-diagnostics <url> <destination>
23
+ vouchington host-pressure-diagnostics
24
+ vouchington allocate-browser-safe-ports 2 --policy ./policy.json --forbidden-ports ./ports.json
25
+ vouchington vitest-blob-manifest <suite> [reports-directory]
26
+ vouchington pnpm-install --runner-lifecycle persistent --install-scripts true
20
27
  ```
21
28
 
22
29
  Host-lock environment:
@@ -39,4 +46,7 @@ import {
39
46
  import { initSqlAst, extractCreateTableMetadata } from 'vouchington-tooling/sql-ast'
40
47
  import { splitSqlStatements, stripSqlComments } from 'vouchington-tooling/sql-scanner'
41
48
  import { auditCiJobRuntime } from 'vouchington-tooling/gha-runtime-audit'
49
+ import { writeVitestBlobManifest } from 'vouchington-tooling/vitest-blob-manifest'
50
+ import { runInstallLifecycle } from 'vouchington-tooling/pnpm-install'
51
+ import { buildSharedContext, runNamedChecks } from 'vouchington-tooling/shared-context'
42
52
  ```
@@ -0,0 +1,6 @@
1
+ export declare const synthesizedRunnerSlot: number;
2
+ export declare const synthesizedPortRangeStart: number;
3
+ export declare const synthesizedPortRangeEnd: number;
4
+ export declare function requiredPort(port: number | undefined): number;
5
+ export declare const synthesizedSlotHostLockTestTimeoutMs: number;
6
+ export declare function withSynthesizedSlotHostLock<T>(body: () => Promise<T>): Promise<T>;
@@ -0,0 +1,103 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { join, resolve } from 'node:path';
6
+ import { runnerPortPolicy } from '../../runner-port-policy/index.mjs';
7
+ const hostLockScriptPath = resolve('packages/vouchington-tooling/scripts/host-lock/with-host-lock.sh');
8
+ // Slot `maximumRunnerSlot` is the fleet's established synthetic ceiling for boundary tests (see
9
+ // allocate-browser-safe-ports.test.mts's "uses the slot-50 boundary" case) — no host runs that
10
+ // many runner slots, so this range never collides with a real runner's live port allocation.
11
+ export const synthesizedRunnerSlot = runnerPortPolicy.maximumRunnerSlot;
12
+ export const synthesizedPortRangeStart = runnerPortPolicy.reservedPortStart +
13
+ (synthesizedRunnerSlot - runnerPortPolicy.minimumRunnerSlot) * runnerPortPolicy.portsPerRunner;
14
+ export const synthesizedPortRangeEnd = synthesizedPortRangeStart + runnerPortPolicy.portsPerRunner - 1;
15
+ export function requiredPort(port) {
16
+ if (port === undefined)
17
+ throw new Error('expected an allocated port');
18
+ return port;
19
+ }
20
+ const hostLockName = 'allocate-browser-safe-ports-synthesized-slot';
21
+ const hostLockTimeoutSeconds = 60;
22
+ const hostLockCommandTimeoutSeconds = 60;
23
+ // Grace this wrapper waits past with-host-lock.sh's own --timeout-seconds before giving up on the
24
+ // acquired marker (mirrors the script's internal polling/teardown overhead around acquisition).
25
+ const hostLockAcquireGraceSeconds = 15;
26
+ // Fixed overhead for this wrapper's own teardown once body() returns: writing the release marker,
27
+ // waiting for the placeholder to exit, and removing the marker dir.
28
+ const hostLockWrapperTeardownMarginSeconds = 15;
29
+ // Single source of truth for how long a vitest `it()` calling withSynthesizedSlotHostLock must be
30
+ // allowed to run: the full lock-acquisition window, then a critical section that may legitimately
31
+ // run right up to with-host-lock's own --command-timeout-seconds watchdog, plus fixed wrapper
32
+ // teardown. Deriving this -- instead of a second, independent literal on the `it()` call -- is what
33
+ // guarantees with-host-lock's watchdog can never force-release the lock while the calling test
34
+ // still believes it's within its own timeout budget: with-host-lock.sh's
35
+ // start_command_timeout_watchdog force-kills the placeholder and unconditionally releases the lock
36
+ // once command_timeout_seconds elapses, regardless of whether body() has finished, so the vitest
37
+ // timeout must always exceed that watchdog's own worst case, never fall short of it.
38
+ export const synthesizedSlotHostLockTestTimeoutMs = (hostLockTimeoutSeconds +
39
+ hostLockAcquireGraceSeconds +
40
+ hostLockCommandTimeoutSeconds +
41
+ hostLockWrapperTeardownMarginSeconds) *
42
+ 1000;
43
+ async function waitForMarker(path, timeoutMs) {
44
+ const deadline = Date.now() + timeoutMs;
45
+ while (!existsSync(path)) {
46
+ if (Date.now() >= deadline)
47
+ throw new Error(`timed out waiting for marker: ${path}`);
48
+ await new Promise((resolveWait) => setTimeout(resolveWait, 50));
49
+ }
50
+ }
51
+ // ci/with-host-lock.sh only wraps a subprocess command, but the racy resource here (binding real
52
+ // host ports) is in-process Node.js code. The wrapped command is therefore a placeholder that
53
+ // writes an "acquired" marker the instant it starts running — which is exactly when the lock is
54
+ // held, per with-host-lock.sh's start-release handshake — and blocks on a "release" marker until
55
+ // told to stop. That gates the Node-side critical section on lock acquisition/release instead of
56
+ // trying to run that section as the locked subprocess itself, so two concurrent copies of a test
57
+ // using the synthesized slot take turns instead of colliding on the same synthetic port range.
58
+ export async function withSynthesizedSlotHostLock(body) {
59
+ const markerDir = await mkdtemp(join(tmpdir(), 'host-lock-marker-'));
60
+ const acquiredMarker = join(markerDir, 'acquired');
61
+ const releaseMarker = join(markerDir, 'release');
62
+ const stderrChunks = [];
63
+ const holder = spawn('bash', [
64
+ hostLockScriptPath,
65
+ '--name',
66
+ hostLockName,
67
+ '--timeout-seconds',
68
+ String(hostLockTimeoutSeconds),
69
+ '--command-timeout-seconds',
70
+ String(hostLockCommandTimeoutSeconds),
71
+ '--',
72
+ 'bash',
73
+ '-c',
74
+ 'touch "$1"; while [ ! -e "$2" ]; do sleep 0.05; done',
75
+ '_',
76
+ acquiredMarker,
77
+ releaseMarker,
78
+ ], { stdio: ['ignore', 'ignore', 'pipe'] });
79
+ holder.stderr?.on('data', (chunk) => stderrChunks.push(chunk));
80
+ const exited = new Promise((resolveExit) => {
81
+ holder.once('close', (code) => resolveExit(code));
82
+ });
83
+ const acquireTimeoutMs = (hostLockTimeoutSeconds + hostLockAcquireGraceSeconds) * 1000;
84
+ const acquired = await Promise.race([
85
+ waitForMarker(acquiredMarker, acquireTimeoutMs).then(() => true),
86
+ exited.then(() => false),
87
+ ]);
88
+ if (!acquired) {
89
+ const code = await exited;
90
+ throw new Error(`with-host-lock exited before acquiring "${hostLockName}" (code ${code}): ${Buffer.concat(stderrChunks).toString('utf8')}`);
91
+ }
92
+ try {
93
+ return await body();
94
+ }
95
+ finally {
96
+ await releasePlaceholderAndAwaitExit(releaseMarker, exited);
97
+ await rm(markerDir, { force: true, recursive: true });
98
+ }
99
+ }
100
+ async function releasePlaceholderAndAwaitExit(releaseMarker, exited) {
101
+ await writeFile(releaseMarker, '');
102
+ await exited;
103
+ }
@@ -0,0 +1 @@
1
+ export declare function parseGithubOutput(text: string): Record<string, string>;
@@ -0,0 +1,32 @@
1
+ export function parseGithubOutput(text) {
2
+ const outputs = {};
3
+ const lines = text.split(/\r?\n/);
4
+ for (let index = 0; index < lines.length; index += 1) {
5
+ const line = lines[index];
6
+ if (!line)
7
+ continue;
8
+ const heredocSeparator = line.indexOf('<<');
9
+ if (heredocSeparator !== -1) {
10
+ const name = line.slice(0, heredocSeparator);
11
+ const delimiter = line.slice(heredocSeparator + 2);
12
+ const valueLines = [];
13
+ index += 1;
14
+ while (index < lines.length) {
15
+ const next = lines[index];
16
+ if (next === undefined || next === delimiter)
17
+ break;
18
+ valueLines.push(next);
19
+ index += 1;
20
+ }
21
+ if (index === lines.length)
22
+ throw new Error(`Missing output delimiter: ${delimiter}`);
23
+ outputs[name] = valueLines.join('\n');
24
+ continue;
25
+ }
26
+ const assignmentSeparator = line.indexOf('=');
27
+ if (assignmentSeparator === -1)
28
+ throw new Error(`Invalid output record: ${line}`);
29
+ outputs[line.slice(0, assignmentSeparator)] = line.slice(assignmentSeparator + 1);
30
+ }
31
+ return outputs;
32
+ }
@@ -0,0 +1 @@
1
+ export declare function runPnpmInstallCli(args: readonly string[]): Promise<number>;
@@ -0,0 +1,27 @@
1
+ import { appendFile } from 'node:fs/promises';
2
+ import { parseInstallOptions, runInstallLifecycle } from '../../pnpm-install/index.mjs';
3
+ export async function runPnpmInstallCli(args) {
4
+ const started = performance.now();
5
+ let mode = 'unknown';
6
+ let outcome = 'failed';
7
+ try {
8
+ mode = await runInstallLifecycle(parseInstallOptions([...args]));
9
+ outcome = 'completed';
10
+ return 0;
11
+ }
12
+ catch (error) {
13
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
14
+ return 1;
15
+ }
16
+ finally {
17
+ const summary = process.env.GITHUB_STEP_SUMMARY;
18
+ if (summary) {
19
+ try {
20
+ await appendFile(summary, `pnpm install: ${mode} ${outcome} in ${Math.round(performance.now() - started)}ms\n`);
21
+ }
22
+ catch (error) {
23
+ console.warn(`unable to append pnpm install summary: ${error instanceof Error ? error.message : String(error)}`);
24
+ }
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,5 @@
1
+ import { type SpawnSyncReturns } from 'node:child_process';
2
+ export type ScriptSpawn = (command: string, args: readonly string[], options: {
3
+ stdio: 'inherit';
4
+ }) => Pick<SpawnSyncReturns<Buffer>, 'error' | 'status'>;
5
+ export declare function runScript(command: string, scriptPath: string, args: readonly string[], spawn?: ScriptSpawn): number;
@@ -0,0 +1,7 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ export function runScript(command, scriptPath, args, spawn = spawnSync) {
3
+ const result = spawn(command, [scriptPath, ...args], { stdio: 'inherit' });
4
+ if (result.error)
5
+ throw result.error;
6
+ return result.status ?? 1;
7
+ }
@@ -0,0 +1 @@
1
+ export declare function runVitestBlobManifestCommand(args: readonly string[]): number;
@@ -0,0 +1,11 @@
1
+ import { runVitestBlobManifestCli } from '../../vitest-blob-manifest/cli.mjs';
2
+ export function runVitestBlobManifestCommand(args) {
3
+ try {
4
+ runVitestBlobManifestCli(args);
5
+ return 0;
6
+ }
7
+ catch (error) {
8
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
9
+ return 1;
10
+ }
11
+ }
@@ -4,10 +4,30 @@ import { resolve } from 'node:path';
4
4
  import { pathToFileURL } from 'node:url';
5
5
  import { readPackageVersion } from '../package-version.mjs';
6
6
  import { runGhaRuntimeAudit } from './commands/gha-runtime-audit.mjs';
7
+ import { runPnpmInstallCli } from './commands/pnpm-install.mjs';
7
8
  import { runRunnerPortPolicy } from './commands/runner-port-policy.mjs';
9
+ import { runScript } from './commands/spawn-script.mjs';
10
+ import { runVitestBlobManifestCommand } from './commands/vitest-blob-manifest.mjs';
8
11
  import { runWithHostLock } from './commands/with-host-lock.mjs';
9
12
  import { parseCli } from './parse.mjs';
13
+ import { packageScriptPath } from './script-path.mjs';
10
14
  import { printUsage } from './usage.mjs';
15
+ const SCRIPT_PATHS = {
16
+ 'gha-output': { command: 'bash', path: 'scripts/gha/write-github-multiline-output.sh' },
17
+ 'gha-needs-results': { command: 'bash', path: 'scripts/gha/check-needs-results.sh' },
18
+ 'download-with-diagnostics': {
19
+ command: 'bash',
20
+ path: 'scripts/gha/download-with-diagnostics.sh',
21
+ },
22
+ 'host-pressure-diagnostics': {
23
+ command: 'bash',
24
+ path: 'scripts/gha/host-pressure-diagnostics.sh',
25
+ },
26
+ 'allocate-browser-safe-ports': {
27
+ command: 'python3',
28
+ path: 'scripts/allocate-browser-safe-ports.py',
29
+ },
30
+ };
11
31
  export function runCli(argv = process.argv) {
12
32
  const parsed = parseCli(argv);
13
33
  switch (parsed.kind) {
@@ -27,6 +47,14 @@ export function runCli(argv = process.argv) {
27
47
  return runWithHostLock(parsed.args);
28
48
  case 'gha-runtime-audit':
29
49
  return runGhaRuntimeAudit(parsed);
50
+ case 'script': {
51
+ const spec = SCRIPT_PATHS[parsed.command];
52
+ return runScript(spec.command, packageScriptPath(spec.path), parsed.args);
53
+ }
54
+ case 'pnpm-install':
55
+ return runPnpmInstallCli(parsed.args);
56
+ case 'vitest-blob-manifest':
57
+ return runVitestBlobManifestCommand(parsed.args);
30
58
  }
31
59
  }
32
60
  function readInstalledVersion() {
@@ -13,5 +13,16 @@ export type ParsedCli = {
13
13
  } | {
14
14
  kind: 'with-host-lock';
15
15
  args: string[];
16
+ } | {
17
+ kind: 'script';
18
+ command: ScriptCommand;
19
+ args: string[];
20
+ } | {
21
+ kind: 'pnpm-install';
22
+ args: string[];
23
+ } | {
24
+ kind: 'vitest-blob-manifest';
25
+ args: string[];
16
26
  } | ParsedGhaRuntimeAudit;
27
+ export type ScriptCommand = 'gha-output' | 'gha-needs-results' | 'download-with-diagnostics' | 'host-pressure-diagnostics' | 'allocate-browser-safe-ports';
17
28
  export declare function parseCli(argv: readonly string[]): ParsedCli;
@@ -1,4 +1,11 @@
1
1
  import { parseGhaRuntimeAudit } from './parse-gha-runtime-audit.mjs';
2
+ const SCRIPT_COMMANDS = new Set([
3
+ 'gha-output',
4
+ 'gha-needs-results',
5
+ 'download-with-diagnostics',
6
+ 'host-pressure-diagnostics',
7
+ 'allocate-browser-safe-ports',
8
+ ]);
2
9
  export function parseCli(argv) {
3
10
  const args = argv.slice(2);
4
11
  if (args.length === 0 || args[0] === '--help' || args[0] === '-h')
@@ -12,6 +19,13 @@ export function parseCli(argv) {
12
19
  return { kind: 'with-host-lock', args: rest };
13
20
  if (command === 'gha-runtime-audit')
14
21
  return parseGhaRuntimeAudit(rest);
22
+ if (command === 'pnpm-install')
23
+ return { kind: 'pnpm-install', args: rest };
24
+ if (command === 'vitest-blob-manifest')
25
+ return { kind: 'vitest-blob-manifest', args: rest };
26
+ if (command !== undefined && SCRIPT_COMMANDS.has(command)) {
27
+ return { kind: 'script', command: command, args: rest };
28
+ }
15
29
  return { kind: 'error', message: `unknown command: ${command}` };
16
30
  }
17
31
  function parseRunnerPortPolicy(args) {
@@ -0,0 +1 @@
1
+ export declare function packageScriptPath(relativeFromPackageRoot: string): string;
@@ -0,0 +1,4 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ export function packageScriptPath(relativeFromPackageRoot) {
3
+ return fileURLToPath(new URL(`../../${relativeFromPackageRoot}`, import.meta.url));
4
+ }
@@ -1,2 +1,2 @@
1
- export declare const USAGE = "Usage: vouchington <command> [options]\n\nCommands:\n runner-port-policy Print or validate a runner port policy\n with-host-lock Run a command under a host-wide lock\n gha-runtime-audit Audit successful GitHub Actions job runtimes\n\nOptions:\n -h, --help Show this help\n -v, --version Print the package version\n\nrunner-port-policy\n (no args) Print the shipped policy as JSON\n --file <path> Validate and print a policy file\n --reserved <port> Print true if the port is reserved\n\nwith-host-lock\n --name <family>\n [--slots <n>]\n --timeout-seconds <n>\n [--command-timeout-seconds <n>]\n [--failure-diagnostics <absolute-script>]\n [--on-acquire-timeout fail|run-unlocked]\n -- <command> [args...]\n\ngha-runtime-audit\n [--repository owner/name] Default GITHUB_REPOSITORY\n [--branch main]\n --pr-workflow <name|/regex/> Repeatable\n --push-workflow <name|/regex/> Repeatable\n";
1
+ export declare const USAGE = "Usage: vouchington <command> [options]\n\nCommands:\n runner-port-policy Print or validate a runner port policy\n with-host-lock Run a command under a host-wide lock\n gha-runtime-audit Audit successful GitHub Actions job runtimes\n gha-output Write a collision-safe multiline GITHUB_OUTPUT record\n gha-needs-results Fail if required GitHub Actions job results failed\n download-with-diagnostics Download a URL and report HTTP status on failure\n host-pressure-diagnostics Print a bounded host memory/OOM/PSI snapshot\n allocate-browser-safe-ports Allocate Fetch-safe localhost ports\n vitest-blob-manifest Stamp a vitest-blob-manifest:v1 identity file\n pnpm-install Install a pnpm workspace with retry and release-age fail-fast\n\nOptions:\n -h, --help Show this help\n -v, --version Print the package version\n\nrunner-port-policy\n (no args) Print the shipped policy as JSON\n --file <path> Validate and print a policy file\n --reserved <port> Print true if the port is reserved\n\nwith-host-lock\n --name <family>\n [--slots <n>]\n --timeout-seconds <n>\n [--command-timeout-seconds <n>]\n [--failure-diagnostics <absolute-script>]\n [--on-acquire-timeout fail|run-unlocked]\n -- <command> [args...]\n\ngha-runtime-audit\n [--repository owner/name] Default GITHUB_REPOSITORY\n [--branch main]\n --pr-workflow <name|/regex/> Repeatable\n --push-workflow <name|/regex/> Repeatable\n\ngha-output <name>\ngha-needs-results [label]\ndownload-with-diagnostics <url> <destination> [-- curl-args...]\nhost-pressure-diagnostics\nallocate-browser-safe-ports [count] [--policy path] [--forbidden-ports path]\nvitest-blob-manifest <suite> [reports-directory]\npnpm-install --runner-lifecycle persistent|ephemeral|ephemeral-full --install-scripts true|false\n";
2
2
  export declare function printUsage(stream?: NodeJS.WritableStream): void;
@@ -3,7 +3,14 @@ export const USAGE = `Usage: vouchington <command> [options]
3
3
  Commands:
4
4
  runner-port-policy Print or validate a runner port policy
5
5
  with-host-lock Run a command under a host-wide lock
6
- gha-runtime-audit Audit successful GitHub Actions job runtimes
6
+ gha-runtime-audit Audit successful GitHub Actions job runtimes
7
+ gha-output Write a collision-safe multiline GITHUB_OUTPUT record
8
+ gha-needs-results Fail if required GitHub Actions job results failed
9
+ download-with-diagnostics Download a URL and report HTTP status on failure
10
+ host-pressure-diagnostics Print a bounded host memory/OOM/PSI snapshot
11
+ allocate-browser-safe-ports Allocate Fetch-safe localhost ports
12
+ vitest-blob-manifest Stamp a vitest-blob-manifest:v1 identity file
13
+ pnpm-install Install a pnpm workspace with retry and release-age fail-fast
7
14
 
8
15
  Options:
9
16
  -h, --help Show this help
@@ -28,6 +35,14 @@ gha-runtime-audit
28
35
  [--branch main]
29
36
  --pr-workflow <name|/regex/> Repeatable
30
37
  --push-workflow <name|/regex/> Repeatable
38
+
39
+ gha-output <name>
40
+ gha-needs-results [label]
41
+ download-with-diagnostics <url> <destination> [-- curl-args...]
42
+ host-pressure-diagnostics
43
+ allocate-browser-safe-ports [count] [--policy path] [--forbidden-ports path]
44
+ vitest-blob-manifest <suite> [reports-directory]
45
+ pnpm-install --runner-lifecycle persistent|ephemeral|ephemeral-full --install-scripts true|false
31
46
  `;
32
47
  export function printUsage(stream = process.stdout) {
33
48
  stream.write(USAGE);
package/dist/index.d.mts CHANGED
@@ -5,3 +5,9 @@ export type { ForeignKey, SqlCreateIndexMetadata, SqlCreateTableColumn, SqlCreat
5
5
  export { dollarQuoteEnd, lineOf, maskSqlQuotedText, readDollarQuoteDelimiter, readStringLiteral, splitSqlStatements, sqlFragments, stripSqlComments, } from './sql-scanner/index.mts';
6
6
  export { auditCiJobRuntime, parseWorkflowNameMatch } from './gha-runtime-audit/index.mts';
7
7
  export type { GhApiExecutor, RuntimeAuditOptions, RuntimeAuditResult, RuntimeAuditWorkflowFilter, RuntimeJobResult, RuntimeSample, } from './gha-runtime-audit/index.mts';
8
+ export { createVitestBlobManifest, inspectVitestBlobBundle, parseVitestBlobManifest, serializeVitestBlobManifest, VITEST_BLOB_MANIFEST_FILENAME, VITEST_BLOB_MANIFEST_VERSION, vitestBlobBundlePaths, writeVitestBlobManifest, } from './vitest-blob-manifest/index.mts';
9
+ export type { InspectedVitestBlobBundle, VitestBlobIdentity, VitestBlobManifest, } from './vitest-blob-manifest/index.mts';
10
+ export { findWorkspaceLinkMismatches, formatReleaseAgeFailure, INSTALL_TERMINATION_FAILED, isReleaseAgeViolation, parseInstallOptions, parseReleaseAgeViolations, runInstallLifecycle, } from './pnpm-install/index.mts';
11
+ export type { InstallOptions, Lifecycle } from './pnpm-install/index.mts';
12
+ export { buildContextFromTrackedFiles, buildSharedContext, gitEnv, runNamedChecks, } from './shared-context/index.mts';
13
+ export type { NamedCheck, SharedContext } from './shared-context/index.mts';
package/dist/index.mjs CHANGED
@@ -2,3 +2,6 @@ export { EphemeralListenerAttemptsExhaustedError, isRunnerReservedPort, listenOn
2
2
  export { extractAlterTableAddColumnLocations, extractCreateIndexMetadata, extractCreateTableMetadata, extractDefaultFunction, extractDropIndexMetadata, extractFuncCallArgColumnNames, extractMigrationConstraintMetadata, initSqlAst, lineOfUtf8ByteOffset, MissingSqlAstParserError, parseSql, } from './sql-ast/index.mjs';
3
3
  export { dollarQuoteEnd, lineOf, maskSqlQuotedText, readDollarQuoteDelimiter, readStringLiteral, splitSqlStatements, sqlFragments, stripSqlComments, } from './sql-scanner/index.mjs';
4
4
  export { auditCiJobRuntime, parseWorkflowNameMatch } from './gha-runtime-audit/index.mjs';
5
+ export { createVitestBlobManifest, inspectVitestBlobBundle, parseVitestBlobManifest, serializeVitestBlobManifest, VITEST_BLOB_MANIFEST_FILENAME, VITEST_BLOB_MANIFEST_VERSION, vitestBlobBundlePaths, writeVitestBlobManifest, } from './vitest-blob-manifest/index.mjs';
6
+ export { findWorkspaceLinkMismatches, formatReleaseAgeFailure, INSTALL_TERMINATION_FAILED, isReleaseAgeViolation, parseInstallOptions, parseReleaseAgeViolations, runInstallLifecycle, } from './pnpm-install/index.mjs';
7
+ export { buildContextFromTrackedFiles, buildSharedContext, gitEnv, runNamedChecks, } from './shared-context/index.mjs';
@@ -0,0 +1,8 @@
1
+ import type { CommandResult, InstallOptions } from './support.mts';
2
+ /**
3
+ * Spawn one `pnpm` attempt. Both stdout and stderr are always captured (so a terminal failure
4
+ * like a release-age violation can be classified from `errorOutput`) and, unless `capture` is
5
+ * set, forwarded live to the parent process's streams — `capture` is for structured calls (e.g.
6
+ * `pnpm m ls --json`) whose stdout the caller parses rather than displays.
7
+ */
8
+ export declare function runPnpm(args: string[], options: InstallOptions, capture?: boolean): Promise<CommandResult>;
@@ -0,0 +1,88 @@
1
+ import { spawn } from 'node:child_process';
2
+ // oxfmt-ignore
3
+ import { INSTALL_TERMINATION_FAILED, installExitCode, startInstallHeartbeat, terminateSafeProcessGroup } from './process.mjs';
4
+ /**
5
+ * Spawn one `pnpm` attempt. Both stdout and stderr are always captured (so a terminal failure
6
+ * like a release-age violation can be classified from `errorOutput`) and, unless `capture` is
7
+ * set, forwarded live to the parent process's streams — `capture` is for structured calls (e.g.
8
+ * `pnpm m ls --json`) whose stdout the caller parses rather than displays.
9
+ */
10
+ export function runPnpm(args, options, capture = false) {
11
+ return new Promise((resolve) => {
12
+ const errorOutput = [];
13
+ const output = [];
14
+ // oxfmt-ignore
15
+ const result = (code) => ({ code, errorOutput: Buffer.concat(errorOutput).toString(), output: Buffer.concat(output).toString() });
16
+ let completed = false;
17
+ const complete = (result) => {
18
+ if (completed)
19
+ return;
20
+ completed = true;
21
+ resolve(result);
22
+ };
23
+ let child;
24
+ try {
25
+ child = spawn('pnpm', args, {
26
+ detached: true,
27
+ stdio: ['ignore', 'pipe', 'pipe'],
28
+ });
29
+ }
30
+ catch (error) {
31
+ const message = error instanceof Error ? error.message : String(error);
32
+ console.error(`pnpm failed to spawn: ${message}`);
33
+ complete({ code: 1, errorOutput: message, output: '' });
34
+ return;
35
+ }
36
+ child.stdout?.on('data', (data) => {
37
+ output.push(data);
38
+ if (!capture)
39
+ process.stdout.write(data);
40
+ });
41
+ child.stderr?.on('data', (data) => {
42
+ errorOutput.push(data);
43
+ if (!capture)
44
+ process.stderr.write(data);
45
+ });
46
+ child.once('error', (error) => {
47
+ console.error(`pnpm failed to start: ${error.message}`);
48
+ complete({ code: 1, errorOutput: error.message, output: '' });
49
+ });
50
+ const heartbeat = startInstallHeartbeat();
51
+ let termination;
52
+ // Benign race: if the timeout callback and the 'close' handler both fire, `complete`'s dedup
53
+ // guard lets only the first resolve. `timedOut` is only read by the winner, so whichever wins
54
+ // sees the correct value for its own case (true when the timeout won, false when close won).
55
+ let timedOut = false;
56
+ const terminate = () => (termination ??= terminateSafeProcessGroup(child.pid));
57
+ const timeout = options.commandTimeoutSeconds === 0
58
+ ? undefined
59
+ : setTimeout(() => {
60
+ /* v8 ignore next -- close can win the race and already settle */
61
+ if (completed)
62
+ return;
63
+ timedOut = true;
64
+ void terminate()
65
+ .then((stopped) => complete(result(stopped ? 1 : INSTALL_TERMINATION_FAILED)))
66
+ .catch((error) => {
67
+ console.error(`pnpm install termination failed: ${String(error)}`);
68
+ complete(result(INSTALL_TERMINATION_FAILED));
69
+ })
70
+ .finally(() => {
71
+ clearInterval(heartbeat);
72
+ });
73
+ }, options.commandTimeoutSeconds * 1000);
74
+ timeout?.unref();
75
+ child.once('close', async (code) => {
76
+ try {
77
+ await termination;
78
+ complete(result(installExitCode(code, timedOut)));
79
+ }
80
+ finally {
81
+ clearInterval(heartbeat);
82
+ // oxlint-disable-next-line promise/no-multiple-resolved -- oxlint reports the multi-path settlement at this finally line; the sole `resolve` call site is first-winner-guarded by `completed` inside `complete`
83
+ if (timeout)
84
+ clearTimeout(timeout);
85
+ }
86
+ });
87
+ });
88
+ }
@@ -0,0 +1,7 @@
1
+ export { runInstallLifecycle } from './runner.mts';
2
+ export { runPnpm } from './exec.mts';
3
+ export { baseInstallArgs, findWorkspaceLinkMismatches, listWorkspaces, logWorkspaceLinkMismatches, parseInstallOptions, reportGlibcVersionRuntime, } from './support.mts';
4
+ export type { CaptureCommand, CommandResult, InstallOptions, Lifecycle, Workspace, WorkspaceLinkMismatch, } from './support.mts';
5
+ export { formatReleaseAgeFailure, isReleaseAgeViolation, parseReleaseAgeViolations, } from './release-age.mts';
6
+ export { INSTALL_TERMINATION_FAILED, installExitCode, safeProcessGroup, startInstallHeartbeat, terminateProcessGroup, terminateSafeProcessGroup, } from './process.mts';
7
+ export { persistentDependencyTreeIsCold, persistentMetadataFingerprint, persistentMetadataMatches, writePersistentMetadataStamp, } from './metadata.mts';
@@ -0,0 +1,6 @@
1
+ export { runInstallLifecycle } from './runner.mjs';
2
+ export { runPnpm } from './exec.mjs';
3
+ export { baseInstallArgs, findWorkspaceLinkMismatches, listWorkspaces, logWorkspaceLinkMismatches, parseInstallOptions, reportGlibcVersionRuntime, } from './support.mjs';
4
+ export { formatReleaseAgeFailure, isReleaseAgeViolation, parseReleaseAgeViolations, } from './release-age.mjs';
5
+ export { INSTALL_TERMINATION_FAILED, installExitCode, safeProcessGroup, startInstallHeartbeat, terminateProcessGroup, terminateSafeProcessGroup, } from './process.mjs';
6
+ export { persistentDependencyTreeIsCold, persistentMetadataFingerprint, persistentMetadataMatches, writePersistentMetadataStamp, } from './metadata.mjs';
@@ -0,0 +1,5 @@
1
+ import { type CaptureCommand } from './support.mts';
2
+ export declare function persistentMetadataFingerprint(runCapture: CaptureCommand, installScripts: boolean): Promise<string>;
3
+ export declare function persistentMetadataMatches(fingerprint: string): Promise<boolean>;
4
+ export declare function persistentDependencyTreeIsCold(): Promise<boolean>;
5
+ export declare function writePersistentMetadataStamp(fingerprint: string): Promise<void>;
@@ -0,0 +1,91 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { listWorkspaces, reportGlibcVersionRuntime } from './support.mjs';
5
+ function persistentMetadataStampPath() {
6
+ return path.join(process.cwd(), 'node_modules', '.pnpm-install-metadata-health.json');
7
+ }
8
+ function fail(message) {
9
+ throw new Error(message);
10
+ }
11
+ function addFingerprintInput(hash, label, value) {
12
+ hash.update(`${label.length}:${label}${value.length}:${value}`);
13
+ }
14
+ async function optionalFile(pathname) {
15
+ try {
16
+ return await readFile(pathname, 'utf8');
17
+ }
18
+ catch (error) {
19
+ if (error.code === 'ENOENT')
20
+ return '';
21
+ throw error;
22
+ }
23
+ }
24
+ function runtimePlatformIdentity() {
25
+ const report = process.report?.getReport();
26
+ return JSON.stringify({
27
+ arch: process.arch,
28
+ glibc: reportGlibcVersionRuntime(report),
29
+ modules: process.versions.modules,
30
+ node: process.version,
31
+ npmConfigArch: process.env.npm_config_arch ?? '',
32
+ npmConfigLibc: process.env.npm_config_libc ?? '',
33
+ npmConfigPlatform: process.env.npm_config_platform ?? '',
34
+ platform: process.platform,
35
+ });
36
+ }
37
+ export async function persistentMetadataFingerprint(runCapture, installScripts) {
38
+ const workspaces = (await listWorkspaces(runCapture)).toSorted((left, right) => left.path.localeCompare(right.path));
39
+ const pnpmVersion = await runCapture(['--version']);
40
+ if (pnpmVersion.code !== 0)
41
+ fail(`pnpm --version failed: ${pnpmVersion.errorOutput?.trim() || pnpmVersion.output.trim() || 'unknown error'}`);
42
+ const hash = createHash('sha256');
43
+ addFingerprintInput(hash, 'runtime', runtimePlatformIdentity());
44
+ addFingerprintInput(hash, 'pnpm', pnpmVersion.output.trim());
45
+ addFingerprintInput(hash, 'installScripts', String(installScripts));
46
+ for (const filename of [
47
+ 'pnpm-lock.yaml',
48
+ 'pnpm-workspace.yaml',
49
+ '.npmrc',
50
+ '.pnpmfile.cjs',
51
+ '.pnpmfile.mjs',
52
+ ])
53
+ addFingerprintInput(hash, filename, await optionalFile(path.join(process.cwd(), filename)));
54
+ for (const workspace of workspaces)
55
+ addFingerprintInput(hash, path.relative(process.cwd(), path.join(workspace.path, 'package.json')), await readFile(path.join(workspace.path, 'package.json'), 'utf8'));
56
+ return hash.digest('hex');
57
+ }
58
+ export async function persistentMetadataMatches(fingerprint) {
59
+ try {
60
+ const stamp = JSON.parse(await readFile(persistentMetadataStampPath(), 'utf8'));
61
+ return stamp.version === 1 && stamp.fingerprint === fingerprint;
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ }
67
+ export async function persistentDependencyTreeIsCold() {
68
+ try {
69
+ await stat(path.dirname(persistentMetadataStampPath()));
70
+ return false;
71
+ }
72
+ catch (error) {
73
+ if (error.code === 'ENOENT')
74
+ return true;
75
+ /* v8 ignore next -- non-ENOENT stat failures are host-specific */
76
+ throw error;
77
+ }
78
+ }
79
+ export async function writePersistentMetadataStamp(fingerprint) {
80
+ const stampPath = persistentMetadataStampPath();
81
+ const directory = path.dirname(stampPath);
82
+ const temporary = `${stampPath}.${process.pid}.tmp`;
83
+ await mkdir(directory, { recursive: true });
84
+ try {
85
+ await writeFile(temporary, `${JSON.stringify({ fingerprint, version: 1 })}\n`);
86
+ await rename(temporary, stampPath);
87
+ }
88
+ finally {
89
+ await rm(temporary, { force: true });
90
+ }
91
+ }
@@ -0,0 +1,22 @@
1
+ export type FixtureOptions = {
2
+ commandTimeoutSeconds?: number;
3
+ installScripts?: boolean;
4
+ lifecycle?: 'ephemeral' | 'ephemeral-full' | 'persistent';
5
+ maxAttempts?: number;
6
+ selectors?: string;
7
+ };
8
+ export declare function makeFixture(): Promise<{
9
+ consumer: string;
10
+ dependency: string;
11
+ dependencyLink: string;
12
+ env: NodeJS.ProcessEnv;
13
+ pnpmLog: string;
14
+ root: string;
15
+ summary: string;
16
+ }>;
17
+ export declare function runInstaller(fixture: Awaited<ReturnType<typeof makeFixture>>, options?: FixtureOptions): Promise<{
18
+ stdout: string;
19
+ stderr: string;
20
+ }>;
21
+ export declare function installCalls(fixture: Awaited<ReturnType<typeof makeFixture>>): Promise<string[]>;
22
+ export declare function resetInstallCalls(fixture: Awaited<ReturnType<typeof makeFixture>>): Promise<void>;