vouchington-tooling 0.0.14 → 0.0.16

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 (79) hide show
  1. package/README.md +13 -0
  2. package/dist/ci-local/index.d.mts +5 -0
  3. package/dist/ci-local/index.mjs +79 -0
  4. package/dist/ci-local/parse.d.mts +3 -0
  5. package/dist/ci-local/parse.mjs +43 -0
  6. package/dist/ci-local/types.d.mts +41 -0
  7. package/dist/ci-local/types.mjs +1 -0
  8. package/dist/cli/index.mjs +5 -0
  9. package/dist/cli/parse.d.mts +1 -1
  10. package/dist/cli/parse.mjs +5 -0
  11. package/dist/cli/usage.d.mts +1 -1
  12. package/dist/cli/usage.mjs +10 -0
  13. package/dist/coverage-transport/constants.d.mts +3 -0
  14. package/dist/coverage-transport/constants.mjs +11 -0
  15. package/dist/coverage-transport/control.d.mts +47 -0
  16. package/dist/coverage-transport/control.mjs +112 -0
  17. package/dist/coverage-transport/http.d.mts +6 -0
  18. package/dist/coverage-transport/http.mjs +99 -0
  19. package/dist/coverage-transport/index.d.mts +6 -0
  20. package/dist/coverage-transport/index.mjs +6 -0
  21. package/dist/coverage-transport/lib.d.mts +17 -0
  22. package/dist/coverage-transport/lib.mjs +84 -0
  23. package/dist/coverage-transport/outcome.d.mts +29 -0
  24. package/dist/coverage-transport/outcome.mjs +70 -0
  25. package/dist/coverage-transport/presign.d.mts +22 -0
  26. package/dist/coverage-transport/presign.mjs +51 -0
  27. package/dist/coverage-transport/vitest-blob-transport.d.mts +5 -0
  28. package/dist/coverage-transport/vitest-blob-transport.mjs +129 -0
  29. package/dist/dockerfile-parse/index.d.mts +4 -0
  30. package/dist/dockerfile-parse/index.mjs +2 -0
  31. package/dist/dockerfile-parse/prewarm-ports.d.mts +12 -0
  32. package/dist/dockerfile-parse/prewarm-ports.mjs +59 -0
  33. package/dist/dockerfile-parse/runtime-cmd.d.mts +11 -0
  34. package/dist/dockerfile-parse/runtime-cmd.mjs +142 -0
  35. package/dist/dockerfile-parse/stages.d.mts +7 -0
  36. package/dist/dockerfile-parse/stages.mjs +18 -0
  37. package/dist/gha-pr-checkpoint/index.d.mts +58 -0
  38. package/dist/gha-pr-checkpoint/index.mjs +100 -0
  39. package/dist/gha-rate-limit/index.d.mts +15 -0
  40. package/dist/gha-rate-limit/index.mjs +69 -0
  41. package/dist/index.d.mts +15 -0
  42. package/dist/index.mjs +8 -0
  43. package/dist/pnpm-deploy/index.d.mts +6 -0
  44. package/dist/pnpm-deploy/index.mjs +3 -0
  45. package/dist/pnpm-deploy/normalize.d.mts +15 -0
  46. package/dist/pnpm-deploy/normalize.mjs +64 -0
  47. package/dist/pnpm-deploy/prune.d.mts +14 -0
  48. package/dist/pnpm-deploy/prune.mjs +48 -0
  49. package/dist/pnpm-deploy/restore.d.mts +31 -0
  50. package/dist/pnpm-deploy/restore.mjs +109 -0
  51. package/dist/scc-complexity/index.d.mts +19 -0
  52. package/dist/scc-complexity/index.mjs +91 -0
  53. package/dist/transient-retry/decision-evaluator.d.mts +2 -0
  54. package/dist/transient-retry/decision-evaluator.mjs +6 -0
  55. package/dist/transient-retry/types.d.mts +3 -3
  56. package/dist/workspace-gates/dependabot.d.mts +3 -0
  57. package/dist/workspace-gates/dependabot.mjs +26 -0
  58. package/dist/workspace-gates/exclude-registry.d.mts +3 -0
  59. package/dist/workspace-gates/exclude-registry.mjs +30 -0
  60. package/dist/workspace-gates/first-party-graph.d.mts +4 -0
  61. package/dist/workspace-gates/first-party-graph.mjs +87 -0
  62. package/dist/workspace-gates/index.d.mts +2 -0
  63. package/dist/workspace-gates/index.mjs +1 -0
  64. package/dist/workspace-gates/options.d.mts +27 -0
  65. package/dist/workspace-gates/options.mjs +15 -0
  66. package/dist/workspace-gates/policy.d.mts +5 -0
  67. package/dist/workspace-gates/policy.mjs +27 -0
  68. package/dist/workspace-gates/test-helpers.d.mts +21 -0
  69. package/dist/workspace-gates/test-helpers.mjs +69 -0
  70. package/dist/workspace-gates/yaml-loader.d.mts +2 -0
  71. package/dist/workspace-gates/yaml-loader.mjs +28 -0
  72. package/dist/workspace-glob.d.mts +1 -0
  73. package/dist/workspace-glob.mjs +46 -0
  74. package/package.json +44 -2
  75. package/scripts/gha/check-cache-size.sh +38 -0
  76. package/scripts/gha/clean-workspace.sh +227 -0
  77. package/scripts/gha/install-github-release.sh +136 -0
  78. package/scripts/gha/load-runner-env.sh +75 -0
  79. package/scripts/gha/make-shard-matrix.sh +30 -0
package/README.md CHANGED
@@ -28,6 +28,11 @@ vouchington gha-artifacts-cleanup run --run-id 123 --keep-pattern 'plan-*' --del
28
28
  vouchington http-origin --field cdn_origin https://images.example.com
29
29
  vouchington vitest-blob-manifest <suite> [reports-directory]
30
30
  vouchington pnpm-install --runner-lifecycle persistent --install-scripts true
31
+ vouchington check-cache-size /tmp/cache 1048576 node-modules
32
+ vouchington make-shard-matrix 4
33
+ vouchington load-runner-env
34
+ vouchington clean-workspace
35
+ vouchington install-github-release --repo lycheeverse/lychee --version 0.24.2 --asset 'lychee-{platform}.tar.gz' --bin lychee
31
36
  ```
32
37
 
33
38
  Host-lock environment:
@@ -79,6 +84,14 @@ import { readResponseBody } from 'vouchington-tooling/http-body'
79
84
  import { runAstGrepRule } from 'vouchington-tooling/ast-grep-rule'
80
85
  import { parseReviewPayload, remapReviewComments } from 'vouchington-tooling/gha-review-payload'
81
86
  import { nextPageUrlFromLinkHeader } from 'vouchington-tooling/http-link-pagination'
87
+ import { cmdUpload, mintPresignedControl } from 'vouchington-tooling/coverage-transport'
88
+ import { pruneDeployedRuntimeDeps } from 'vouchington-tooling/pnpm-deploy'
89
+ import { parseDockerfileRuntimeImages } from 'vouchington-tooling/dockerfile-parse'
90
+ import { checkSccComplexity } from 'vouchington-tooling/scc-complexity'
91
+ import { runCiLocal } from 'vouchington-tooling/ci-local'
92
+ import { rateLimitDelay } from 'vouchington-tooling/gha-rate-limit'
93
+ import { parseCheckpoint } from 'vouchington-tooling/gha-pr-checkpoint'
94
+ import { checkWorkspaceGatesPolicy } from 'vouchington-tooling/workspace-gates'
82
95
  ```
83
96
 
84
97
  The artifact, review-payload, HTTP body, and pagination APIs validate untrusted inputs at their
@@ -0,0 +1,5 @@
1
+ import type { CiLocalTarget, RunCiLocalOptions } from './types.mts';
2
+ export type { CiLocalCommand, CiLocalSpawn, CiLocalSpawnOptions, CiLocalSpawnResult, CiLocalTarget, LineWriter, ParsedCiLocalArgs, RunCiLocalOptions, } from './types.mts';
3
+ export { parseCiLocalArgs } from './parse.mts';
4
+ export declare function assertWorkflowCommandDrift(targets: Record<string, CiLocalTarget>, rootDir?: string): void;
5
+ export declare function runCiLocal({ args, targets, cwd, spawn, stdout, stderr, usage, }: RunCiLocalOptions): number;
@@ -0,0 +1,79 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { readFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { parseCiLocalArgs } from './parse.mjs';
5
+ export { parseCiLocalArgs } from './parse.mjs';
6
+ const DEFAULT_USAGE = 'Usage: ci-local [--list] | <target> [--dry-run]';
7
+ export function assertWorkflowCommandDrift(targets, rootDir = process.cwd()) {
8
+ for (const target of Object.values(targets)) {
9
+ for (const command of target.commands) {
10
+ if (!command.source)
11
+ continue;
12
+ const { workflow: sourceWorkflow, contains } = command.source;
13
+ const workflow = readFileSync(path.join(rootDir, sourceWorkflow), 'utf8');
14
+ const expectedCommands = Array.isArray(contains) ? contains : [contains];
15
+ if (expectedCommands.every((expectedCommand) => workflow.includes(expectedCommand)))
16
+ continue;
17
+ throw new Error(`ci-local drift: ${command.description} command was not found in ${command.source.workflow}`);
18
+ }
19
+ }
20
+ }
21
+ function defaultSpawn(command, args, options) {
22
+ const result = spawnSync(command, [...args], options);
23
+ return { error: result.error, status: result.status };
24
+ }
25
+ function writeLine(stream, line) {
26
+ stream.write(`${line}\n`);
27
+ }
28
+ function runCommand(command, dryRun, cwd, spawn, stdout) {
29
+ const envPreview = Object.keys(command.env ?? {})
30
+ .filter((key) => command.env?.[key] !== undefined)
31
+ .join(',');
32
+ const renderedCommand = envPreview
33
+ ? `env ${envPreview} bash -c ${JSON.stringify(command.command)}`
34
+ : command.command;
35
+ writeLine(stdout, `\n$ ${renderedCommand}`);
36
+ if (dryRun)
37
+ return 0;
38
+ const result = spawn('/bin/bash', ['-c', command.command], {
39
+ cwd,
40
+ env: {
41
+ ...process.env,
42
+ ...command.env,
43
+ },
44
+ stdio: 'inherit',
45
+ });
46
+ if (result.error)
47
+ throw result.error;
48
+ return result.status ?? 1;
49
+ }
50
+ export function runCiLocal({ args, targets, cwd = process.cwd(), spawn = defaultSpawn, stdout = process.stdout, stderr = process.stderr, usage = DEFAULT_USAGE, }) {
51
+ try {
52
+ const targetNames = Object.keys(targets);
53
+ const parsed = parseCiLocalArgs(args, targetNames);
54
+ if (parsed.help) {
55
+ writeLine(stdout, usage);
56
+ return 0;
57
+ }
58
+ if (parsed.list || !parsed.target) {
59
+ for (const [name, target] of Object.entries(targets)) {
60
+ writeLine(stdout, `${name} - ${target.description}`);
61
+ }
62
+ return 0;
63
+ }
64
+ const target = targets[parsed.target];
65
+ assertWorkflowCommandDrift({ [parsed.target]: target }, cwd);
66
+ writeLine(stdout, `${parsed.target}: ${target.description}`);
67
+ for (const command of target.commands) {
68
+ const status = runCommand(command, parsed.dryRun, cwd, spawn, stdout);
69
+ if (status !== 0)
70
+ return status;
71
+ }
72
+ return 0;
73
+ }
74
+ catch (error) {
75
+ const message = error instanceof Error ? error.message : String(error);
76
+ stderr.write(`${message}\n`);
77
+ return 1;
78
+ }
79
+ }
@@ -0,0 +1,3 @@
1
+ import * as nodeUtil from 'node:util';
2
+ import type { ParsedCiLocalArgs } from './types.mts';
3
+ export declare function parseCiLocalArgs(args: readonly string[], targetNames: readonly string[], parseArgs?: typeof nodeUtil.parseArgs): ParsedCiLocalArgs;
@@ -0,0 +1,43 @@
1
+ import * as nodeUtil from 'node:util';
2
+ export function parseCiLocalArgs(args, targetNames, parseArgs = nodeUtil.parseArgs) {
3
+ const helpArgs = args.filter((arg) => arg === '-h' || arg === '--help');
4
+ const help = helpArgs.length === 1 && args.length === 1;
5
+ if (helpArgs.length > 0 && !help)
6
+ throw new Error('help must be used by itself');
7
+ if (args.filter((arg) => arg === '--dry-run').length > 1 ||
8
+ args.filter((arg) => arg === '--list').length > 1) {
9
+ throw new Error('Options may only be specified once.');
10
+ }
11
+ let dryRun;
12
+ let list;
13
+ let positionals;
14
+ try {
15
+ const parsed = parseArgs({
16
+ args: [...args],
17
+ options: {
18
+ 'dry-run': { type: 'boolean' },
19
+ help: { short: 'h', type: 'boolean' },
20
+ list: { type: 'boolean' },
21
+ },
22
+ strict: true,
23
+ allowPositionals: true,
24
+ });
25
+ dryRun = parsed.values['dry-run'] ?? false;
26
+ list = parsed.values.list ?? false;
27
+ positionals = parsed.positionals;
28
+ }
29
+ catch (error) {
30
+ throw new Error(error instanceof Error ? error.message : String(error), { cause: error });
31
+ }
32
+ if (positionals.length > 1)
33
+ throw new Error('Only one target may be specified.');
34
+ if (list && (dryRun || positionals.length > 0))
35
+ throw new Error('--list must be used by itself.');
36
+ const target = positionals[0];
37
+ if (dryRun && !target)
38
+ throw new Error('--dry-run requires a target.');
39
+ if (target && !targetNames.includes(target)) {
40
+ throw new Error(`Unknown target "${target}". Valid targets: ${targetNames.join(', ')}`);
41
+ }
42
+ return target ? { dryRun, help, list, target } : { dryRun, help, list };
43
+ }
@@ -0,0 +1,41 @@
1
+ export interface CiLocalCommand {
2
+ command: string;
3
+ description: string;
4
+ env?: Record<string, string | undefined>;
5
+ source?: {
6
+ workflow: string;
7
+ contains: string | string[];
8
+ };
9
+ }
10
+ export interface CiLocalTarget {
11
+ description: string;
12
+ commands: CiLocalCommand[];
13
+ }
14
+ export interface ParsedCiLocalArgs {
15
+ dryRun: boolean;
16
+ help: boolean;
17
+ list: boolean;
18
+ target?: string;
19
+ }
20
+ export interface CiLocalSpawnResult {
21
+ error?: Error | undefined;
22
+ status: number | null;
23
+ }
24
+ export interface CiLocalSpawnOptions {
25
+ cwd: string;
26
+ env: NodeJS.ProcessEnv;
27
+ stdio: 'inherit';
28
+ }
29
+ export type CiLocalSpawn = (command: string, args: readonly string[], options: CiLocalSpawnOptions) => CiLocalSpawnResult;
30
+ export interface LineWriter {
31
+ write(chunk: string): unknown;
32
+ }
33
+ export interface RunCiLocalOptions {
34
+ args: readonly string[];
35
+ targets: Record<string, CiLocalTarget>;
36
+ cwd?: string;
37
+ spawn?: CiLocalSpawn;
38
+ stdout?: LineWriter;
39
+ stderr?: LineWriter;
40
+ usage?: string;
41
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -34,6 +34,11 @@ const SCRIPT_PATHS = {
34
34
  path: 'scripts/gha/diagnose-port-collision.sh',
35
35
  },
36
36
  'prepare-trivy-db': { command: 'bash', path: 'scripts/gha/prepare-trivy-db.sh' },
37
+ 'check-cache-size': { command: 'bash', path: 'scripts/gha/check-cache-size.sh' },
38
+ 'make-shard-matrix': { command: 'bash', path: 'scripts/gha/make-shard-matrix.sh' },
39
+ 'load-runner-env': { command: 'bash', path: 'scripts/gha/load-runner-env.sh' },
40
+ 'clean-workspace': { command: 'bash', path: 'scripts/gha/clean-workspace.sh' },
41
+ 'install-github-release': { command: 'bash', path: 'scripts/gha/install-github-release.sh' },
37
42
  };
38
43
  export function runCli(argv = process.argv) {
39
44
  const parsed = parseCli(argv);
@@ -29,5 +29,5 @@ export type ParsedCli = {
29
29
  field: string;
30
30
  value: string;
31
31
  } | ParsedGhaRuntimeAudit | ParsedGhaArtifactsCleanup;
32
- export type ScriptCommand = 'gha-output' | 'gha-needs-results' | 'download-with-diagnostics' | 'host-pressure-diagnostics' | 'allocate-browser-safe-ports' | 'diagnose-port-collision' | 'prepare-trivy-db';
32
+ export type ScriptCommand = 'gha-output' | 'gha-needs-results' | 'download-with-diagnostics' | 'host-pressure-diagnostics' | 'allocate-browser-safe-ports' | 'diagnose-port-collision' | 'prepare-trivy-db' | 'check-cache-size' | 'make-shard-matrix' | 'load-runner-env' | 'clean-workspace' | 'install-github-release';
33
33
  export declare function parseCli(argv: readonly string[]): ParsedCli;
@@ -8,6 +8,11 @@ const SCRIPT_COMMANDS = new Set([
8
8
  'allocate-browser-safe-ports',
9
9
  'diagnose-port-collision',
10
10
  'prepare-trivy-db',
11
+ 'check-cache-size',
12
+ 'make-shard-matrix',
13
+ 'load-runner-env',
14
+ 'clean-workspace',
15
+ 'install-github-release',
11
16
  ]);
12
17
  export function parseCli(argv) {
13
18
  const args = argv.slice(2);
@@ -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 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 diagnose-port-collision Capture bounded localhost port diagnostics\n prepare-trivy-db Download the Trivy vulnerability database\n gha-artifacts-cleanup Delete classified GitHub Actions artifacts\n http-origin Validate an optional HTTP(S) origin\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]\ndiagnose-port-collision [--ports \"2200 2216\"] [--output-dir PATH]\nprepare-trivy-db\ngha-artifacts-cleanup run --run-id <id> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]\ngha-artifacts-cleanup sweep --older-than-hours <n> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]\nhttp-origin [--field NAME] [value]\nvitest-blob-manifest <suite> [reports-directory]\npnpm-install --runner-lifecycle persistent|ephemeral|ephemeral-full --install-scripts true|false\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 diagnose-port-collision Capture bounded localhost port diagnostics\n prepare-trivy-db Download the Trivy vulnerability database\n gha-artifacts-cleanup Delete classified GitHub Actions artifacts\n http-origin Validate an optional HTTP(S) origin\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 check-cache-size Measure a path and decide whether to save a GHA cache\n make-shard-matrix Emit a [1..N] GitHub Actions shard matrix\n load-runner-env Overlay a runner env file onto GITHUB_ENV with injection guards\n clean-workspace Reset a persistent-runner workspace with a fork-PR trust gate\n install-github-release Download a checksum-verified GitHub Release binary\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]\ndiagnose-port-collision [--ports \"2200 2216\"] [--output-dir PATH]\nprepare-trivy-db\ngha-artifacts-cleanup run --run-id <id> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]\ngha-artifacts-cleanup sweep --older-than-hours <n> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]\nhttp-origin [--field NAME] [value]\nvitest-blob-manifest <suite> [reports-directory]\npnpm-install --runner-lifecycle persistent|ephemeral|ephemeral-full --install-scripts true|false\ncheck-cache-size <path> <max-bytes> <label>\nmake-shard-matrix <total>\nload-runner-env\nclean-workspace\ninstall-github-release --repo owner/name --version X --asset 'name-{platform}.tar.gz' --bin name\n";
2
2
  export declare function printUsage(stream?: NodeJS.WritableStream): void;
@@ -15,6 +15,11 @@ Commands:
15
15
  http-origin Validate an optional HTTP(S) origin
16
16
  vitest-blob-manifest Stamp a vitest-blob-manifest:v1 identity file
17
17
  pnpm-install Install a pnpm workspace with retry and release-age fail-fast
18
+ check-cache-size Measure a path and decide whether to save a GHA cache
19
+ make-shard-matrix Emit a [1..N] GitHub Actions shard matrix
20
+ load-runner-env Overlay a runner env file onto GITHUB_ENV with injection guards
21
+ clean-workspace Reset a persistent-runner workspace with a fork-PR trust gate
22
+ install-github-release Download a checksum-verified GitHub Release binary
18
23
 
19
24
  Options:
20
25
  -h, --help Show this help
@@ -52,6 +57,11 @@ gha-artifacts-cleanup sweep --older-than-hours <n> [--keep-pattern glob] [--dele
52
57
  http-origin [--field NAME] [value]
53
58
  vitest-blob-manifest <suite> [reports-directory]
54
59
  pnpm-install --runner-lifecycle persistent|ephemeral|ephemeral-full --install-scripts true|false
60
+ check-cache-size <path> <max-bytes> <label>
61
+ make-shard-matrix <total>
62
+ load-runner-env
63
+ clean-workspace
64
+ install-github-release --repo owner/name --version X --asset 'name-{platform}.tar.gz' --bin name
55
65
  `;
56
66
  export function printUsage(stream = process.stdout) {
57
67
  stream.write(USAGE);
@@ -0,0 +1,3 @@
1
+ export declare const DEFAULT_COVERAGE_MANIFEST_FILENAME = "coverage-manifest.json";
2
+ export declare const DEFAULT_MAX_BODY_BYTES: number;
3
+ export declare function assertCoverageManifestFilename(filename: string): void;
@@ -0,0 +1,11 @@
1
+ export const DEFAULT_COVERAGE_MANIFEST_FILENAME = 'coverage-manifest.json';
2
+ export const DEFAULT_MAX_BODY_BYTES = 32 * 1024 * 1024;
3
+ const MANIFEST_FILENAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.json$/;
4
+ export function assertCoverageManifestFilename(filename) {
5
+ if (filename.includes('/') ||
6
+ filename.includes('\\') ||
7
+ filename.includes('..') ||
8
+ !MANIFEST_FILENAME_PATTERN.test(filename)) {
9
+ throw new Error('Coverage manifest filename is invalid');
10
+ }
11
+ }
@@ -0,0 +1,47 @@
1
+ export interface PresignedCoverageUrls {
2
+ lcovPut: string;
3
+ lcovGet: string;
4
+ manifestPut: string;
5
+ manifestGet: string;
6
+ }
7
+ export interface PresignedBlobUrls {
8
+ put: string;
9
+ get: string;
10
+ }
11
+ interface TransportControlBase {
12
+ readonly version: 1;
13
+ readonly repository: string;
14
+ readonly revision: string;
15
+ readonly run: {
16
+ readonly id: string;
17
+ readonly controlAttempt: number;
18
+ };
19
+ }
20
+ export interface PresignedTransportControl extends TransportControlBase {
21
+ readonly mode: 'presigned';
22
+ readonly expiresAt: string;
23
+ readonly coverage: Record<string, PresignedCoverageUrls>;
24
+ readonly blobs: Record<string, PresignedBlobUrls>;
25
+ }
26
+ export interface FallbackOnlyTransportControl extends TransportControlBase {
27
+ readonly mode: 'fallback-only';
28
+ readonly reason: string;
29
+ }
30
+ export type TransportControl = PresignedTransportControl | FallbackOnlyTransportControl;
31
+ export interface ExpectedTransportIdentity {
32
+ readonly repository: string;
33
+ readonly revision: string;
34
+ readonly runId: string;
35
+ readonly currentAttempt: number;
36
+ }
37
+ export interface RequestOptions {
38
+ readonly retryDelayMs?: number;
39
+ readonly timeoutMs?: number;
40
+ readonly maxBodyBytes?: number;
41
+ readonly maxMemberBytes?: number;
42
+ readonly log?: (line: string) => void;
43
+ }
44
+ export declare function parseTransportControl(raw: unknown): TransportControl;
45
+ export declare function writeTransportControl(path: string, control: TransportControl): void;
46
+ export declare function readTransportControl(path: string, expected?: ExpectedTransportIdentity): TransportControl;
47
+ export {};
@@ -0,0 +1,112 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { chmodSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
3
+ import { VITEST_SUITE_PATTERN } from '../vitest-blob-manifest/index.mjs';
4
+ function isRecord(value) {
5
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
6
+ }
7
+ function positiveInteger(value) {
8
+ return Number.isSafeInteger(value) && Number(value) > 0;
9
+ }
10
+ function assertUrlMap(raw, fields, label) {
11
+ if (!isRecord(raw))
12
+ throw new Error(`${label} URL map must be an object`);
13
+ for (const [suite, urls] of Object.entries(raw)) {
14
+ if (!VITEST_SUITE_PATTERN.test(suite) ||
15
+ !isRecord(urls) ||
16
+ Object.keys(urls).toSorted().join('\0') !== fields.toSorted().join('\0')) {
17
+ throw new Error(`${label} URL map has an invalid entry`);
18
+ }
19
+ for (const field of fields) {
20
+ const value = urls[field];
21
+ if (typeof value !== 'string' || !URL.canParse(value) || !/^https?:/i.test(value)) {
22
+ throw new Error(`${label} URL map has an invalid URL`);
23
+ }
24
+ }
25
+ }
26
+ }
27
+ export function parseTransportControl(raw) {
28
+ if (!isRecord(raw) || raw.version !== 1 || !isRecord(raw.run)) {
29
+ throw new Error('Coverage transport control has an unsupported schema');
30
+ }
31
+ if (typeof raw.repository !== 'string' ||
32
+ !raw.repository ||
33
+ typeof raw.revision !== 'string' ||
34
+ !/^[0-9a-f]{40}$/.test(raw.revision) ||
35
+ typeof raw.run.id !== 'string' ||
36
+ !/^[1-9][0-9]*$/.test(raw.run.id) ||
37
+ !positiveInteger(raw.run.controlAttempt)) {
38
+ throw new Error('Coverage transport control has invalid identity fields');
39
+ }
40
+ if (Object.keys(raw.run).toSorted().join('\0') !== ['controlAttempt', 'id'].join('\0')) {
41
+ throw new Error('Coverage transport control run schema is invalid');
42
+ }
43
+ if (raw.mode === 'fallback-only') {
44
+ const fallbackKeys = ['mode', 'reason', 'repository', 'revision', 'run', 'version'];
45
+ if (Object.keys(raw).toSorted().join('\0') !== fallbackKeys.toSorted().join('\0') ||
46
+ typeof raw.reason !== 'string' ||
47
+ !raw.reason) {
48
+ throw new Error('Fallback-only coverage transport control is invalid');
49
+ }
50
+ return raw;
51
+ }
52
+ const presignedKeys = [
53
+ 'blobs',
54
+ 'coverage',
55
+ 'expiresAt',
56
+ 'mode',
57
+ 'repository',
58
+ 'revision',
59
+ 'run',
60
+ 'version',
61
+ ];
62
+ if (raw.mode !== 'presigned' ||
63
+ Object.keys(raw).toSorted().join('\0') !== presignedKeys.toSorted().join('\0') ||
64
+ typeof raw.expiresAt !== 'string' ||
65
+ !Number.isFinite(Date.parse(raw.expiresAt))) {
66
+ throw new Error('Presigned coverage transport control is invalid');
67
+ }
68
+ assertUrlMap(raw.coverage, ['lcovGet', 'lcovPut', 'manifestGet', 'manifestPut'], 'Coverage');
69
+ assertUrlMap(raw.blobs, ['get', 'put'], 'Blob');
70
+ return raw;
71
+ }
72
+ export function writeTransportControl(path, control) {
73
+ const validated = parseTransportControl(control);
74
+ const temporary = `${path}.${randomUUID()}.tmp`;
75
+ try {
76
+ writeFileSync(temporary, `${JSON.stringify(validated, null, 2)}\n`, {
77
+ flag: 'wx',
78
+ mode: 0o600,
79
+ });
80
+ chmodSync(temporary, 0o600);
81
+ renameSync(temporary, path);
82
+ }
83
+ catch (error) {
84
+ try {
85
+ unlinkSync(temporary);
86
+ }
87
+ catch {
88
+ // The write/rename failure is the actionable error.
89
+ }
90
+ throw error;
91
+ }
92
+ }
93
+ export function readTransportControl(path, expected) {
94
+ if ((statSync(path).mode & 0o777) !== 0o600) {
95
+ throw new Error('Coverage transport control file must have mode 0600');
96
+ }
97
+ const control = parseTransportControl(JSON.parse(readFileSync(path, 'utf8')));
98
+ if (expected && !positiveInteger(expected.currentAttempt)) {
99
+ throw new Error('Coverage transport control identity does not match this run');
100
+ }
101
+ if (expected &&
102
+ (control.repository !== expected.repository ||
103
+ control.revision !== expected.revision ||
104
+ control.run.id !== expected.runId ||
105
+ control.run.controlAttempt > expected.currentAttempt)) {
106
+ throw new Error('Coverage transport control identity does not match this run');
107
+ }
108
+ if (control.mode === 'presigned' && Date.parse(control.expiresAt) <= Date.now()) {
109
+ throw new Error('Coverage transport control has expired');
110
+ }
111
+ return control;
112
+ }
@@ -0,0 +1,6 @@
1
+ import type { RequestOptions } from './control.mts';
2
+ export declare function redactTransportLog(value: unknown): string;
3
+ export declare function coveragePresignFailureLog(error: unknown): string;
4
+ export declare function logTransport(options: RequestOptions, line: string): void;
5
+ export declare function fetchPut(url: string, body: Buffer, options?: RequestOptions): Promise<boolean>;
6
+ export declare function fetchGet(url: string, options?: RequestOptions): Promise<Buffer | null>;
@@ -0,0 +1,99 @@
1
+ import { DEFAULT_MAX_BODY_BYTES } from './constants.mjs';
2
+ const FETCH_TIMEOUT_MS = 30_000;
3
+ const FETCH_ATTEMPTS = 2;
4
+ const DEFAULT_RETRY_DELAY_MS = 1000;
5
+ function formatTransportError(value, seen) {
6
+ if (typeof value === 'object' && value !== null) {
7
+ if (seen.has(value))
8
+ return '[circular]';
9
+ seen.add(value);
10
+ }
11
+ if (value instanceof Error) {
12
+ const cause = value.cause !== undefined ? `; cause: ${formatTransportError(value.cause, seen)}` : '';
13
+ return `${value.name}: ${value.message}${cause}`;
14
+ }
15
+ return String(value);
16
+ }
17
+ export function redactTransportLog(value) {
18
+ return formatTransportError(value, new WeakSet()).replaceAll(/https?:\/\/[^\s]+/gi, '[redacted-url]');
19
+ }
20
+ export function coveragePresignFailureLog(error) {
21
+ return `[coverage-transport] presign failed: ${redactTransportLog(error)}; artifact fallback required`;
22
+ }
23
+ export function logTransport(options, line) {
24
+ ;
25
+ (options.log ?? ((message) => process.stderr.write(`${message}\n`)))(redactTransportLog(line));
26
+ }
27
+ async function delay(milliseconds) {
28
+ if (milliseconds <= 0)
29
+ return;
30
+ await new Promise((resolve) => setTimeout(resolve, milliseconds));
31
+ }
32
+ async function cancelBody(response) {
33
+ await response.body?.cancel();
34
+ }
35
+ async function readLimitedBody(response, maxBytes) {
36
+ if (response.body === null)
37
+ return Buffer.alloc(0);
38
+ const reader = response.body.getReader();
39
+ const chunks = [];
40
+ let total = 0;
41
+ for (;;) {
42
+ const { done, value } = await reader.read();
43
+ if (done)
44
+ return Buffer.concat(chunks);
45
+ total += value.byteLength;
46
+ if (total > maxBytes) {
47
+ await reader.cancel();
48
+ throw new Error('[coverage-transport] GET body exceeds size limit');
49
+ }
50
+ chunks.push(value);
51
+ }
52
+ }
53
+ async function request(method, url, body, options) {
54
+ for (let attempt = 1; attempt <= FETCH_ATTEMPTS; attempt += 1) {
55
+ const controller = new AbortController();
56
+ const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? FETCH_TIMEOUT_MS);
57
+ try {
58
+ const response = await fetch(url, {
59
+ method,
60
+ ...(body ? { body: new Uint8Array(body) } : {}),
61
+ signal: controller.signal,
62
+ });
63
+ // A missing presigned object is terminal. Let fetchGet yield null or fetchPut yield false
64
+ // without retrying the same URL or emitting a misleading transport-error diagnostic.
65
+ if (response.ok || response.status === 404) {
66
+ const payload = method === 'GET' && response.ok
67
+ ? await readLimitedBody(response, options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES)
68
+ : undefined;
69
+ if (payload === undefined)
70
+ await cancelBody(response);
71
+ return { ok: response.ok, status: response.status, body: payload };
72
+ }
73
+ await cancelBody(response);
74
+ logTransport(options, `[coverage-transport] ${method} failed: HTTP ${response.status}`);
75
+ }
76
+ catch (error) {
77
+ logTransport(options, `[coverage-transport] ${method} error: ${redactTransportLog(error)}`);
78
+ }
79
+ finally {
80
+ clearTimeout(timer);
81
+ }
82
+ if (attempt < FETCH_ATTEMPTS) {
83
+ await delay(options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS);
84
+ }
85
+ }
86
+ return null;
87
+ }
88
+ export async function fetchPut(url, body, options = {}) {
89
+ const response = await request('PUT', url, body, options);
90
+ return response?.ok === true;
91
+ }
92
+ export async function fetchGet(url, options = {}) {
93
+ const response = await request('GET', url, undefined, options);
94
+ if (response?.status === 404)
95
+ return null;
96
+ if (!response?.ok)
97
+ throw new Error('[coverage-transport] GET exhausted');
98
+ return response.body;
99
+ }
@@ -0,0 +1,6 @@
1
+ export { DEFAULT_COVERAGE_MANIFEST_FILENAME, cmdDownloadCoverage, cmdDownloadVitestBlobs, cmdUpload, } from './lib.mts';
2
+ export { parseTransportControl, readTransportControl, writeTransportControl, type ExpectedTransportIdentity, type FallbackOnlyTransportControl, type PresignedBlobUrls, type PresignedCoverageUrls, type PresignedTransportControl, type RequestOptions, type TransportControl, } from './control.mts';
3
+ export { coveragePresignFailureLog, fetchGet, fetchPut, logTransport, redactTransportLog, } from './http.mts';
4
+ export { assertCoverageTransportBlobOutcome, assertCoverageTransportOutcome, isBlobPrimaryState, isStepOutcome, writeUploadOutcomeOutput, type AppendOutput, type BlobPrimaryState, type StepOutcome, } from './outcome.mts';
5
+ export { downloadVitestBlobBundles, packVitestBlobBundle } from './vitest-blob-transport.mts';
6
+ export { mintPresignedControl, transportObjectKeys, type MintPresignedControlOptions, type ObjectSigner, type PresignIdentity, } from './presign.mts';
@@ -0,0 +1,6 @@
1
+ export { DEFAULT_COVERAGE_MANIFEST_FILENAME, cmdDownloadCoverage, cmdDownloadVitestBlobs, cmdUpload, } from './lib.mjs';
2
+ export { parseTransportControl, readTransportControl, writeTransportControl, } from './control.mjs';
3
+ export { coveragePresignFailureLog, fetchGet, fetchPut, logTransport, redactTransportLog, } from './http.mjs';
4
+ export { assertCoverageTransportBlobOutcome, assertCoverageTransportOutcome, isBlobPrimaryState, isStepOutcome, writeUploadOutcomeOutput, } from './outcome.mjs';
5
+ export { downloadVitestBlobBundles, packVitestBlobBundle } from './vitest-blob-transport.mjs';
6
+ export { mintPresignedControl, transportObjectKeys, } from './presign.mjs';
@@ -0,0 +1,17 @@
1
+ import { type ExpectedTransportIdentity, type RequestOptions } from './control.mts';
2
+ export { DEFAULT_COVERAGE_MANIFEST_FILENAME } from './constants.mts';
3
+ interface UploadOptions extends RequestOptions {
4
+ readonly cwd?: string;
5
+ readonly expectedIdentity: ExpectedTransportIdentity;
6
+ readonly coverageManifestFilename?: string;
7
+ }
8
+ interface DownloadOptions extends RequestOptions {
9
+ readonly expectedIdentity: ExpectedTransportIdentity;
10
+ readonly coverageManifestFilename?: string;
11
+ }
12
+ export declare function cmdUpload(controlPath: string, suite: string, options: UploadOptions): Promise<{
13
+ coverage: boolean;
14
+ blob: boolean;
15
+ }>;
16
+ export declare function cmdDownloadCoverage(controlPath: string, destinationRoot: string, options: DownloadOptions): Promise<void>;
17
+ export declare function cmdDownloadVitestBlobs(controlPath: string, destinationRoot: string, options: DownloadOptions): Promise<void>;