vouchington-tooling 0.3.5 → 0.4.0
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.
- package/README.md +19 -5
- package/dist/cli/commands/prepare-vitest-reports.d.mts +2 -0
- package/dist/cli/commands/prepare-vitest-reports.mjs +11 -0
- package/dist/cli/commands/vitest-report-attempt.d.mts +2 -0
- package/dist/cli/commands/vitest-report-attempt.mjs +11 -0
- package/dist/cli/index.mjs +6 -0
- package/dist/cli/parse.d.mts +6 -0
- package/dist/cli/parse.mjs +4 -0
- package/dist/cli/usage.d.mts +1 -1
- package/dist/cli/usage.mjs +4 -0
- package/dist/gha-claude-post-review/index.d.mts +3 -0
- package/dist/gha-claude-post-review/index.mjs +2 -0
- package/dist/gha-post-review/github.d.mts +7 -0
- package/dist/gha-post-review/github.mjs +20 -5
- package/dist/gha-post-review/index.d.mts +5 -1
- package/dist/gha-post-review/index.mjs +4 -1
- package/dist/gha-post-review/token.d.mts +1 -0
- package/dist/gha-post-review/token.mjs +1 -0
- package/dist/gha-workspace-policy/docker-workspace-user.d.mts +2 -0
- package/dist/gha-workspace-policy/docker-workspace-user.mjs +208 -0
- package/dist/gha-workspace-policy/index.d.mts +8 -0
- package/dist/gha-workspace-policy/index.mjs +17 -0
- package/dist/gha-workspace-policy/shared.d.mts +6 -0
- package/dist/gha-workspace-policy/shared.mjs +54 -0
- package/dist/gha-workspace-policy/sparse-checkout.d.mts +2 -0
- package/dist/gha-workspace-policy/sparse-checkout.mjs +28 -0
- package/dist/index.d.mts +8 -2
- package/dist/index.mjs +4 -1
- package/dist/pnpm-install/install-operations.d.mts +1 -0
- package/dist/pnpm-install/install-operations.mjs +26 -6
- package/dist/pnpm-install/native-health.d.mts +2 -0
- package/dist/pnpm-install/native-health.mjs +22 -6
- package/dist/pnpm-install/pending-builds.d.mts +1 -0
- package/dist/pnpm-install/pending-builds.mjs +17 -0
- package/dist/pnpm-install/pnpm-install-fake-pnpm.test-helpers.mjs +6 -0
- package/dist/pnpm-install/pnpm-install-fixture.test-helpers.d.mts +1 -0
- package/dist/pnpm-install/pnpm-install-fixture.test-helpers.mjs +17 -0
- package/dist/pnpm-install/runner.mjs +21 -5
- package/dist/pnpm-install/support.d.mts +1 -0
- package/dist/pnpm-install/support.mjs +3 -3
- package/dist/vitest-blob-manifest/cli.mjs +2 -4
- package/dist/vitest-blob-manifest/report-attempt-cli.d.mts +2 -0
- package/dist/vitest-blob-manifest/report-attempt-cli.mjs +32 -0
- package/dist/vitest-blob-manifest/reports-cli.d.mts +2 -0
- package/dist/vitest-blob-manifest/reports-cli.mjs +55 -0
- package/dist/vitest-blob-manifest/run-attempt.d.mts +2 -0
- package/dist/vitest-blob-manifest/run-attempt.mjs +11 -0
- package/dist/workspace-gates/manifest-version-assertions.d.mts +3 -0
- package/dist/workspace-gates/manifest-version-assertions.mjs +100 -0
- package/dist/workspace-gates/manifest-version-parser.d.mts +6 -0
- package/dist/workspace-gates/manifest-version-parser.mjs +104 -0
- package/dist/workspace-gates/manifest-version-patterns.d.mts +9 -0
- package/dist/workspace-gates/manifest-version-patterns.mjs +21 -0
- package/dist/workspace-gates/policy.mjs +2 -0
- package/package.json +13 -3
- package/scripts/gha/clean-workspace.sh +48 -2
- package/skills/dependabot/SKILL.md +111 -0
- package/skills/github-actions-authoring/SKILL.md +10 -0
- package/skills/github-actions-checklist/SKILL.md +29 -2
- package/skills/manifest.json +24 -17
- package/skills/review-ci-logs/SKILL.md +8 -2
- package/skills/static-analysis-checklist/SKILL.md +8 -5
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ npm install vouchington-tooling
|
|
|
7
7
|
# optional, only if you import vouchington-tooling/sql-ast
|
|
8
8
|
npm install @libpg-query/parser
|
|
9
9
|
# optional, only for vouchington-tooling/agent-blackboard and agent-blackboard CLI commands
|
|
10
|
-
npm install agent-blackboard@^0.
|
|
10
|
+
npm install agent-blackboard@^0.5.0
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## CLI
|
|
@@ -30,6 +30,9 @@ vouchington prepare-trivy-db
|
|
|
30
30
|
vouchington gha-artifacts-cleanup run --run-id 123 --keep-pattern 'plan-*' --delete-pattern 'coverage-*'
|
|
31
31
|
vouchington http-origin --field cdn_origin https://images.example.com
|
|
32
32
|
vouchington vitest-blob-manifest <suite> [reports-directory]
|
|
33
|
+
vouchington vitest-report-attempt write <directory> <suite>
|
|
34
|
+
vouchington vitest-report-attempt read <root>
|
|
35
|
+
vouchington prepare-vitest-reports [primary-directory] [fallback-directory] [output-directory]
|
|
33
36
|
vouchington pnpm-install --runner-lifecycle persistent --install-scripts true
|
|
34
37
|
vouchington check-cache-size /tmp/cache 1048576 node-modules
|
|
35
38
|
vouchington make-shard-matrix 4
|
|
@@ -63,6 +66,10 @@ For persistent `pnpm-install`, v4 metadata tracks structural inputs separately f
|
|
|
63
66
|
uses one script-suppressed verification install followed by `pnpm rebuild --pending --recursive`.
|
|
64
67
|
When only newly pending dependency package IDs remain, it instead rebuilds those exact IDs without
|
|
65
68
|
rerunning first-party workspace hooks.
|
|
69
|
+
An isolated native-binary mismatch uses one strict forced install only when structural provenance
|
|
70
|
+
matches, workspace links are valid, and pnpm records empty `ignoredBuilds` and `pendingBuilds` ledgers;
|
|
71
|
+
otherwise it retains the script-free then strict reconciliation. Native and workspace-link health
|
|
72
|
+
are verified before its metadata stamp is refreshed.
|
|
66
73
|
The command emits a structured non-secret provenance diagnostic identifying changed structural
|
|
67
74
|
categories, the last script policy, script capability, and native-binary health.
|
|
68
75
|
|
|
@@ -88,7 +95,7 @@ GitHub reports a merged PR whose `baseRefName` is `main`; a merged PR into anoth
|
|
|
88
95
|
as not merged to main, and a missing base is unavailable.
|
|
89
96
|
|
|
90
97
|
Agent Blackboard support is optional: only the `agent-blackboard` subpath and its CLI commands
|
|
91
|
-
need `agent-blackboard@^0.
|
|
98
|
+
need `agent-blackboard@^0.5.0`. Snapshot cleanup accepts only package-generated temporary paths.
|
|
92
99
|
Programmatic callers launched from a different workspace directory pass their own module URL as
|
|
93
100
|
`dependencies: { resolveFrom: import.meta.url }`; the CLI defaults to the current package context.
|
|
94
101
|
It captures a target under a private tombstone, validates partition names, permissions, JSONL,
|
|
@@ -154,7 +161,8 @@ import { parseCsvRows, streamCsvRows } from 'vouchington-tooling/csv'
|
|
|
154
161
|
import { readResponseBody } from 'vouchington-tooling/http-body'
|
|
155
162
|
import { runAstGrepRule } from 'vouchington-tooling/ast-grep-rule'
|
|
156
163
|
import { parseReviewPayload, remapReviewComments } from 'vouchington-tooling/gha-review-payload'
|
|
157
|
-
import { runPostReview } from 'vouchington-tooling/gha-post-review'
|
|
164
|
+
import { postReviewWithTokenFromEnv, runPostReview } from 'vouchington-tooling/gha-post-review'
|
|
165
|
+
import { postClaudeReviewFromEnv } from 'vouchington-tooling/gha-claude-post-review'
|
|
158
166
|
import { nextPageUrlFromLinkHeader } from 'vouchington-tooling/http-link-pagination'
|
|
159
167
|
import {
|
|
160
168
|
cmdUpload,
|
|
@@ -168,6 +176,7 @@ import { runCiLocal } from 'vouchington-tooling/ci-local'
|
|
|
168
176
|
import { rateLimitDelay } from 'vouchington-tooling/gha-rate-limit'
|
|
169
177
|
import { parseCheckpoint } from 'vouchington-tooling/gha-pr-checkpoint'
|
|
170
178
|
import { checkWorkspaceGatesPolicy } from 'vouchington-tooling/workspace-gates'
|
|
179
|
+
import { checkGhaWorkspacePolicy } from 'vouchington-tooling/gha-workspace-policy'
|
|
171
180
|
import { validateNugetUpdate } from 'vouchington-tooling/nuget-central-version'
|
|
172
181
|
import { normalizeSwiftSource } from 'vouchington-tooling/swift-semantic-equal'
|
|
173
182
|
import { parseUniqueSwiftBinaryTargetChecksum } from 'vouchington-tooling/swift-source-offset'
|
|
@@ -181,6 +190,10 @@ import { appendJournal, probeBlackboard } from 'vouchington-tooling/agent-blackb
|
|
|
181
190
|
import { buildSessionFrictionReport, recordFriction } from 'vouchington-tooling/session-friction'
|
|
182
191
|
```
|
|
183
192
|
|
|
193
|
+
`checkWorkspaceGatesPolicy` rejects tracked test assertions that hard-code the exact version of a
|
|
194
|
+
dependency declared by a non-fixture package manifest. Assert dependency membership or placement,
|
|
195
|
+
or derive a configuration or documentation package spec from that manifest instead.
|
|
196
|
+
|
|
184
197
|
`session-friction` is an opt-in capture and reporting library. Callers supply the session id,
|
|
185
198
|
absolute log directory, host-independent observation, and journal loader; it does not inspect host
|
|
186
199
|
environment variables, install hooks, or connect to a journal service by itself. Invoking
|
|
@@ -211,8 +224,9 @@ touch. Recording and report log reads are synchronous: on contention they block
|
|
|
211
224
|
event loop for up to one second before failing explicitly. Avoid these APIs on hot request paths.
|
|
212
225
|
|
|
213
226
|
The artifact, review-payload, HTTP body, and pagination APIs validate untrusted inputs at their
|
|
214
|
-
boundaries.
|
|
215
|
-
|
|
227
|
+
boundaries. Neutral review posting lives in `gha-post-review` and requires a caller-supplied GitHub
|
|
228
|
+
token. Claude App OIDC is an explicitly selected `gha-claude-post-review` adapter; the legacy
|
|
229
|
+
token-source router remains deprecated for one release line.
|
|
216
230
|
|
|
217
231
|
`vitest-diagnostics` reads Node diagnostic report JSON from a caller-selected directory. It sorts
|
|
218
232
|
filenames, tolerates partial files, returns only a bounded field allowlist, and never emits raw
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { runPrepareVitestReportsCli } from '../../vitest-blob-manifest/reports-cli.mjs';
|
|
2
|
+
export function runPrepareVitestReportsCommand(args, run = runPrepareVitestReportsCli) {
|
|
3
|
+
try {
|
|
4
|
+
run(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
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { runVitestReportAttemptCli } from '../../vitest-blob-manifest/report-attempt-cli.mjs';
|
|
2
|
+
export function runVitestReportAttemptCommand(args, run = runVitestReportAttemptCli) {
|
|
3
|
+
try {
|
|
4
|
+
run(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
|
+
}
|
package/dist/cli/index.mjs
CHANGED
|
@@ -14,6 +14,8 @@ import { runPostReviewCommand } from './commands/post-review.mjs';
|
|
|
14
14
|
import { runStageReviewPayloadCommand } from './commands/stage-review-payload.mjs';
|
|
15
15
|
import { runSwiftSemanticEqualCommand } from './commands/swift-semantic-equal.mjs';
|
|
16
16
|
import { runVitestBlobManifestCommand } from './commands/vitest-blob-manifest.mjs';
|
|
17
|
+
import { runVitestReportAttemptCommand } from './commands/vitest-report-attempt.mjs';
|
|
18
|
+
import { runPrepareVitestReportsCommand } from './commands/prepare-vitest-reports.mjs';
|
|
17
19
|
import { runRetrospectiveTranscriptCommand } from './commands/retrospective-transcript.mjs';
|
|
18
20
|
import { runLinkSkill } from './commands/link-skill.mjs';
|
|
19
21
|
import { runRetrospectiveFactsCommand } from './commands/retrospective-facts.mjs';
|
|
@@ -93,6 +95,10 @@ export function runCli(argv = process.argv) {
|
|
|
93
95
|
return runPnpmInstallCli(parsed.args);
|
|
94
96
|
case 'vitest-blob-manifest':
|
|
95
97
|
return runVitestBlobManifestCommand(parsed.args);
|
|
98
|
+
case 'vitest-report-attempt':
|
|
99
|
+
return runVitestReportAttemptCommand(parsed.args);
|
|
100
|
+
case 'prepare-vitest-reports':
|
|
101
|
+
return runPrepareVitestReportsCommand(parsed.args);
|
|
96
102
|
case 'nuget-central-version':
|
|
97
103
|
return runNugetCentralVersionCommand(parsed.args);
|
|
98
104
|
case 'swift-semantic-equal':
|
package/dist/cli/parse.d.mts
CHANGED
|
@@ -24,6 +24,12 @@ export type ParsedCli = {
|
|
|
24
24
|
} | {
|
|
25
25
|
kind: 'vitest-blob-manifest';
|
|
26
26
|
args: string[];
|
|
27
|
+
} | {
|
|
28
|
+
kind: 'vitest-report-attempt';
|
|
29
|
+
args: string[];
|
|
30
|
+
} | {
|
|
31
|
+
kind: 'prepare-vitest-reports';
|
|
32
|
+
args: string[];
|
|
27
33
|
} | {
|
|
28
34
|
kind: 'nuget-central-version';
|
|
29
35
|
args: string[];
|
package/dist/cli/parse.mjs
CHANGED
|
@@ -40,6 +40,10 @@ export function parseCli(argv) {
|
|
|
40
40
|
return { kind: 'pnpm-install', args: rest };
|
|
41
41
|
if (command === 'vitest-blob-manifest')
|
|
42
42
|
return { kind: 'vitest-blob-manifest', args: rest };
|
|
43
|
+
if (command === 'vitest-report-attempt')
|
|
44
|
+
return { kind: 'vitest-report-attempt', args: rest };
|
|
45
|
+
if (command === 'prepare-vitest-reports')
|
|
46
|
+
return { kind: 'prepare-vitest-reports', args: rest };
|
|
43
47
|
if (command === 'nuget-central-version')
|
|
44
48
|
return { kind: 'nuget-central-version', args: rest };
|
|
45
49
|
if (command === 'swift-semantic-equal')
|
package/dist/cli/usage.d.mts
CHANGED
|
@@ -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 download-optional-run-artifacts Download optional artifacts from the current run\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 run-with-timeout Run a command with GNU timeout or a Perl fallback\n lint-links Two-pass lychee: internal links fail, external warn\n materialize-pr-context Dump PR title/body/files/diff/comments and #N crawl\n wait-for-apt-locks Wait until apt/dpkg lock files are free\n install-playwright-chromium-arm64 Install Playwright Chromium from browsers.json\n ghcr-package-retention Delete old GHCR package versions past KEEP_MIN\n harness-admission-lane Compute a GITHUB_RUN_ID admission lane for fleet fan-out\n harness-assert-gates Fail if any named HARNESS_*_ENABLED gate is enabled\n nuget-central-version Validate a Directory.Packages.props PackageVersion delta\n swift-semantic-equal Compare Swift sources ignoring comments and whitespace\n post-review Post one COMMENT review from a staged payload file\n stage-review-payload Validate a review payload file into a staging directory\n retrospective-transcript Format facts from Claude-compatible, Codex, or Grok transcripts\n link-skill Link one packaged skill into an explicit consumer directory\n retrospective-facts Gather immutable facts for a retrospective\n agent-blackboard Probe and journal an Agent Blackboard deployment\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...]\ndownload-optional-run-artifacts (--name <name> | --pattern <pattern>) --dir <directory>\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 [--tag-prefix PREFIX] [--expected-sha256 SHA256] [--no-checksum] [--checksums-asset NAME] [--version-flag FLAG] [--bin-dir DIR]\nrun-with-timeout <timeout-seconds> <kill-after-seconds> <command...>\nlint-links [--offline] [--config PATH] [--glob PATTERN] [files...]\nmaterialize-pr-context\nwait-for-apt-locks\ninstall-playwright-chromium-arm64 [name:archive...]\nghcr-package-retention <url-encoded-package>...\nharness-admission-lane <lanes>\nharness-assert-gates <gate>...\nnuget-central-version <trusted-props> <candidate-props> <metadata-json> <output-props>\nswift-semantic-equal <base> <head> <file.swift>\npost-review\nstage-review-payload optional|required <source> <destination>\nretrospective-transcript [--session-id ID] [--jsonl PATH] [--projects-dir PATH] [--codex-sessions-dir PATH] [--grok-sessions-dir PATH]\nlink-skill <name> --source-root <skills-dir> --target-root <consumer-skills-dir> Link a packaged or repository-local skill\nretrospective-facts (--pr NUMBER | --branch NAME | --no-pr) [--repo OWNER/NAME] [--raw]\nagent-blackboard probe\nagent-blackboard journal append --session-id UUID --agent NAME --version VERSION --file PATH [--parent-session-id UUID] [--timestamp ISO8601]\nagent-blackboard journal entries --session-id UUID\nagent-blackboard snapshot partition --snapshot PATH --checksum SHA256 --counts '{\"sessions\":N,\"entries\":N,\"records\":N,\"bytes\":N}'\nagent-blackboard snapshot cleanup [--snapshot PATH] [--partition-directory PATH --receipt JSON]\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 download-optional-run-artifacts Download optional artifacts from the current run\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 vitest-report-attempt Write or read a Vitest report-attempt marker\n prepare-vitest-reports Validate and select Vitest report JSON files\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 run-with-timeout Run a command with GNU timeout or a Perl fallback\n lint-links Two-pass lychee: internal links fail, external warn\n materialize-pr-context Dump PR title/body/files/diff/comments and #N crawl\n wait-for-apt-locks Wait until apt/dpkg lock files are free\n install-playwright-chromium-arm64 Install Playwright Chromium from browsers.json\n ghcr-package-retention Delete old GHCR package versions past KEEP_MIN\n harness-admission-lane Compute a GITHUB_RUN_ID admission lane for fleet fan-out\n harness-assert-gates Fail if any named HARNESS_*_ENABLED gate is enabled\n nuget-central-version Validate a Directory.Packages.props PackageVersion delta\n swift-semantic-equal Compare Swift sources ignoring comments and whitespace\n post-review Post one COMMENT review from a staged payload file\n stage-review-payload Validate a review payload file into a staging directory\n retrospective-transcript Format facts from Claude-compatible, Codex, or Grok transcripts\n link-skill Link one packaged skill into an explicit consumer directory\n retrospective-facts Gather immutable facts for a retrospective\n agent-blackboard Probe and journal an Agent Blackboard deployment\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...]\ndownload-optional-run-artifacts (--name <name> | --pattern <pattern>) --dir <directory>\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]\nvitest-report-attempt <write DIRECTORY SUITE|read ROOT>\nprepare-vitest-reports [primary-directory] [fallback-directory] [output-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 [--tag-prefix PREFIX] [--expected-sha256 SHA256] [--no-checksum] [--checksums-asset NAME] [--version-flag FLAG] [--bin-dir DIR]\nrun-with-timeout <timeout-seconds> <kill-after-seconds> <command...>\nlint-links [--offline] [--config PATH] [--glob PATTERN] [files...]\nmaterialize-pr-context\nwait-for-apt-locks\ninstall-playwright-chromium-arm64 [name:archive...]\nghcr-package-retention <url-encoded-package>...\nharness-admission-lane <lanes>\nharness-assert-gates <gate>...\nnuget-central-version <trusted-props> <candidate-props> <metadata-json> <output-props>\nswift-semantic-equal <base> <head> <file.swift>\npost-review\nstage-review-payload optional|required <source> <destination>\nretrospective-transcript [--session-id ID] [--jsonl PATH] [--projects-dir PATH] [--codex-sessions-dir PATH] [--grok-sessions-dir PATH]\nlink-skill <name> --source-root <skills-dir> --target-root <consumer-skills-dir> Link a packaged or repository-local skill\nretrospective-facts (--pr NUMBER | --branch NAME | --no-pr) [--repo OWNER/NAME] [--raw]\nagent-blackboard probe\nagent-blackboard journal append --session-id UUID --agent NAME --version VERSION --file PATH [--parent-session-id UUID] [--timestamp ISO8601]\nagent-blackboard journal entries --session-id UUID\nagent-blackboard snapshot partition --snapshot PATH --checksum SHA256 --counts '{\"sessions\":N,\"entries\":N,\"records\":N,\"bytes\":N}'\nagent-blackboard snapshot cleanup [--snapshot PATH] [--partition-directory PATH --receipt JSON]\n";
|
|
2
2
|
export declare function printUsage(stream?: NodeJS.WritableStream): void;
|
package/dist/cli/usage.mjs
CHANGED
|
@@ -15,6 +15,8 @@ Commands:
|
|
|
15
15
|
gha-artifacts-cleanup Delete classified GitHub Actions artifacts
|
|
16
16
|
http-origin Validate an optional HTTP(S) origin
|
|
17
17
|
vitest-blob-manifest Stamp a vitest-blob-manifest:v1 identity file
|
|
18
|
+
vitest-report-attempt Write or read a Vitest report-attempt marker
|
|
19
|
+
prepare-vitest-reports Validate and select Vitest report JSON files
|
|
18
20
|
pnpm-install Install a pnpm workspace with retry and release-age fail-fast
|
|
19
21
|
check-cache-size Measure a path and decide whether to save a GHA cache
|
|
20
22
|
make-shard-matrix Emit a [1..N] GitHub Actions shard matrix
|
|
@@ -74,6 +76,8 @@ gha-artifacts-cleanup run --run-id <id> [--keep-pattern glob] [--delete-pattern
|
|
|
74
76
|
gha-artifacts-cleanup sweep --older-than-hours <n> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]
|
|
75
77
|
http-origin [--field NAME] [value]
|
|
76
78
|
vitest-blob-manifest <suite> [reports-directory]
|
|
79
|
+
vitest-report-attempt <write DIRECTORY SUITE|read ROOT>
|
|
80
|
+
prepare-vitest-reports [primary-directory] [fallback-directory] [output-directory]
|
|
77
81
|
pnpm-install --runner-lifecycle persistent|ephemeral|ephemeral-full --install-scripts true|false
|
|
78
82
|
check-cache-size <path> <max-bytes> <label>
|
|
79
83
|
make-shard-matrix <total>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { CLAUDE_APP_TOKEN_EXCHANGE_URL, CLAUDE_OIDC_AUDIENCE, CLAUDE_POSTER_PERMISSIONS, GITHUB_INSTALLATION_TOKEN_URL, createActionsClaudeTokenIo, mintClaudeAppToken, oidcTokenRequest, revokeClaudeAppToken, withClaudeAppToken, } from '../gha-post-review/claude-token.mts';
|
|
2
|
+
export type { ClaudeTokenIo, FetchLike } from '../gha-post-review/claude-token.mts';
|
|
3
|
+
export { postClaudeReviewFromEnv } from '../gha-post-review/github.mts';
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { CLAUDE_APP_TOKEN_EXCHANGE_URL, CLAUDE_OIDC_AUDIENCE, CLAUDE_POSTER_PERMISSIONS, GITHUB_INSTALLATION_TOKEN_URL, createActionsClaudeTokenIo, mintClaudeAppToken, oidcTokenRequest, revokeClaudeAppToken, withClaudeAppToken, } from '../gha-post-review/claude-token.mjs';
|
|
2
|
+
export { postClaudeReviewFromEnv } from '../gha-post-review/github.mjs';
|
|
@@ -17,6 +17,13 @@ export declare function createGhPostReviewIo(options: {
|
|
|
17
17
|
expectedHeadSha?: string;
|
|
18
18
|
expectedBaseSha?: string;
|
|
19
19
|
}): PostReviewIo;
|
|
20
|
+
/** @deprecated Use postReviewWithTokenFromEnv or postClaudeReviewFromEnv explicitly. */
|
|
20
21
|
export declare function postReviewFromEnv(env?: NodeJS.ProcessEnv, exec?: GhExec, claudeIo?: import("./claude-token.mts").ClaudeTokenIo): Promise<{
|
|
21
22
|
posted: boolean;
|
|
22
23
|
}>;
|
|
24
|
+
export declare function postReviewWithTokenFromEnv(env?: NodeJS.ProcessEnv, exec?: GhExec, token?: string | undefined): {
|
|
25
|
+
posted: boolean;
|
|
26
|
+
};
|
|
27
|
+
export declare function postClaudeReviewFromEnv(env?: NodeJS.ProcessEnv, exec?: GhExec, claudeIo?: import("./claude-token.mts").ClaudeTokenIo): Promise<{
|
|
28
|
+
posted: boolean;
|
|
29
|
+
}>;
|
|
@@ -103,12 +103,19 @@ export function createGhPostReviewIo(options) {
|
|
|
103
103
|
},
|
|
104
104
|
};
|
|
105
105
|
}
|
|
106
|
+
/** @deprecated Use postReviewWithTokenFromEnv or postClaudeReviewFromEnv explicitly. */
|
|
106
107
|
export async function postReviewFromEnv(env = process.env, exec = createGhExec(), claudeIo = createActionsClaudeTokenIo(env)) {
|
|
108
|
+
const token = resolveReviewPostToken(env);
|
|
109
|
+
if (token.source === 'github-token')
|
|
110
|
+
return postReviewWithTokenFromEnv(env, exec, token.token);
|
|
111
|
+
return await postClaudeReviewFromEnv(env, exec, claudeIo);
|
|
112
|
+
}
|
|
113
|
+
function createPostWithToken(env, exec) {
|
|
107
114
|
const repository = requireEnv('GITHUB_REPOSITORY', env);
|
|
108
115
|
const prNumber = requireEnv('PR_NUMBER', env);
|
|
109
116
|
const payloadPath = requireEnv('CODE_REVIEW_PAYLOAD_PATH', env);
|
|
110
117
|
const payloadBytes = readRegularReviewPayload(payloadPath, 'required');
|
|
111
|
-
|
|
118
|
+
return (token) => runPostReview(payloadPath, createGhPostReviewIo({
|
|
112
119
|
repository,
|
|
113
120
|
prNumber,
|
|
114
121
|
payloadPath,
|
|
@@ -118,8 +125,16 @@ export async function postReviewFromEnv(env = process.env, exec = createGhExec()
|
|
|
118
125
|
expectedHeadSha: env.EXPECTED_HEAD_SHA ?? '',
|
|
119
126
|
expectedBaseSha: env.EXPECTED_BASE_SHA ?? '',
|
|
120
127
|
}));
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return
|
|
128
|
+
}
|
|
129
|
+
function withTokenEnv(exec, env, token) {
|
|
130
|
+
const tokenEnv = { ...env, GH_TOKEN: token, GITHUB_TOKEN: token };
|
|
131
|
+
return (args, options) => exec(args, { ...options, env: { ...options?.env, ...tokenEnv } });
|
|
132
|
+
}
|
|
133
|
+
export function postReviewWithTokenFromEnv(env = process.env, exec = createGhExec(), token = env.GH_TOKEN || env.GITHUB_TOKEN) {
|
|
134
|
+
if (!token)
|
|
135
|
+
throw new ReviewPayloadError('GH_TOKEN or GITHUB_TOKEN is required.');
|
|
136
|
+
return createPostWithToken(env, withTokenEnv(exec, env, token))(token);
|
|
137
|
+
}
|
|
138
|
+
export async function postClaudeReviewFromEnv(env = process.env, exec = createGhExec(), claudeIo = createActionsClaudeTokenIo(env)) {
|
|
139
|
+
return await withClaudeAppToken(claudeIo, (token) => createPostWithToken(env, withTokenEnv(exec, env, token))(token));
|
|
125
140
|
}
|
|
@@ -2,8 +2,12 @@ export { MAX_COMMENTS, MAX_PAYLOAD_BYTES, PostReviewError, runPostReview } from
|
|
|
2
2
|
export type { PostResult, PostReviewIo, PullFile, ReviewComment, SanitizedReview } from './post.mts';
|
|
3
3
|
export { requireEnv, resolveReviewPostToken } from './token.mts';
|
|
4
4
|
export type { ReviewPostToken } from './token.mts';
|
|
5
|
+
/** @deprecated Import Claude helpers from vouchington-tooling/gha-claude-post-review. */
|
|
5
6
|
export { CLAUDE_APP_TOKEN_EXCHANGE_URL, CLAUDE_OIDC_AUDIENCE, CLAUDE_POSTER_PERMISSIONS, GITHUB_INSTALLATION_TOKEN_URL, createActionsClaudeTokenIo, mintClaudeAppToken, oidcTokenRequest, revokeClaudeAppToken, withClaudeAppToken, } from './claude-token.mts';
|
|
7
|
+
/** @deprecated Import Claude helpers from vouchington-tooling/gha-claude-post-review. */
|
|
6
8
|
export type { ClaudeTokenIo, FetchLike } from './claude-token.mts';
|
|
7
|
-
export { createGhExec, createGhPostReviewIo,
|
|
9
|
+
export { createGhExec, createGhPostReviewIo, postReviewWithTokenFromEnv, postWithGh, writePostedOutput, } from './github.mts';
|
|
10
|
+
/** @deprecated Select an explicit posting adapter. */
|
|
11
|
+
export { postClaudeReviewFromEnv, postReviewFromEnv } from './github.mts';
|
|
8
12
|
export type { GhExec } from './github.mts';
|
|
9
13
|
export { runPostReviewCli } from './cli.mts';
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export { MAX_COMMENTS, MAX_PAYLOAD_BYTES, PostReviewError, runPostReview } from './post.mjs';
|
|
2
2
|
export { requireEnv, resolveReviewPostToken } from './token.mjs';
|
|
3
|
+
/** @deprecated Import Claude helpers from vouchington-tooling/gha-claude-post-review. */
|
|
3
4
|
export { CLAUDE_APP_TOKEN_EXCHANGE_URL, CLAUDE_OIDC_AUDIENCE, CLAUDE_POSTER_PERMISSIONS, GITHUB_INSTALLATION_TOKEN_URL, createActionsClaudeTokenIo, mintClaudeAppToken, oidcTokenRequest, revokeClaudeAppToken, withClaudeAppToken, } from './claude-token.mjs';
|
|
4
|
-
export { createGhExec, createGhPostReviewIo,
|
|
5
|
+
export { createGhExec, createGhPostReviewIo, postReviewWithTokenFromEnv, postWithGh, writePostedOutput, } from './github.mjs';
|
|
6
|
+
/** @deprecated Select an explicit posting adapter. */
|
|
7
|
+
export { postClaudeReviewFromEnv, postReviewFromEnv } from './github.mjs';
|
|
5
8
|
export { runPostReviewCli } from './cli.mjs';
|
|
@@ -4,5 +4,6 @@ export type ReviewPostToken = {
|
|
|
4
4
|
} | {
|
|
5
5
|
source: 'claude-app';
|
|
6
6
|
};
|
|
7
|
+
/** @deprecated Select an explicit posting adapter instead of the legacy token-source router. */
|
|
7
8
|
export declare function resolveReviewPostToken(env?: NodeJS.ProcessEnv): ReviewPostToken;
|
|
8
9
|
export declare function requireEnv(name: string, env?: NodeJS.ProcessEnv): string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ReviewPayloadError } from '../gha-review-payload/index.mjs';
|
|
2
|
+
/** @deprecated Select an explicit posting adapter instead of the legacy token-source router. */
|
|
2
3
|
export function resolveReviewPostToken(env = process.env) {
|
|
3
4
|
const source = env.CODE_REVIEW_TOKEN_SOURCE || 'claude-app';
|
|
4
5
|
if (source === 'github-token') {
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { visitRunSteps } from './shared.mjs';
|
|
2
|
+
const WORKSPACE_RE = /(?:\$\{?GITHUB_WORKSPACE\}?|\$\{\{\s*github\.workspace\s*\}\})/iu;
|
|
3
|
+
const DOCKER_GLOBAL_FLAG_OPTIONS = new Set(['--debug', '-D', '--help', '--tls', '--tlsverify']);
|
|
4
|
+
const DOCKER_GLOBAL_VALUE_OPTIONS = new Set([
|
|
5
|
+
'--config',
|
|
6
|
+
'--context',
|
|
7
|
+
'-c',
|
|
8
|
+
'--host',
|
|
9
|
+
'-H',
|
|
10
|
+
'--log-level',
|
|
11
|
+
'-l',
|
|
12
|
+
'--tlscacert',
|
|
13
|
+
'--tlscert',
|
|
14
|
+
'--tlskey',
|
|
15
|
+
]);
|
|
16
|
+
const FLAG_OPTIONS = new Set([
|
|
17
|
+
'--detach',
|
|
18
|
+
'-d',
|
|
19
|
+
'--init',
|
|
20
|
+
'--interactive',
|
|
21
|
+
'-i',
|
|
22
|
+
'--privileged',
|
|
23
|
+
'--publish-all',
|
|
24
|
+
'-P',
|
|
25
|
+
'--read-only',
|
|
26
|
+
'--rm',
|
|
27
|
+
'--tty',
|
|
28
|
+
'-t',
|
|
29
|
+
]);
|
|
30
|
+
export function checkDockerWorkspaceUserDocument(file, document, kind, errors) {
|
|
31
|
+
visitRunSteps(document, kind === 'action', (scope, index, step) => {
|
|
32
|
+
if (typeof step.run !== 'string')
|
|
33
|
+
return;
|
|
34
|
+
for (const block of dockerRunBlocks(step.run)) {
|
|
35
|
+
const options = dockerOptions(block);
|
|
36
|
+
if (!options)
|
|
37
|
+
continue;
|
|
38
|
+
const writable = options.volumes.filter((volume) => WORKSPACE_RE.test(volume) && !isReadOnlyVolume(volume));
|
|
39
|
+
if (writable.length === 0 || hasHostUserMapping(options.user))
|
|
40
|
+
continue;
|
|
41
|
+
errors.push(`::error file=${file}::${file}: ${scope} step ${index} runs Docker with a writable ` +
|
|
42
|
+
`GITHUB_WORKSPACE mount (${writable.join(', ')}) but no --user host UID:GID mapping. ` +
|
|
43
|
+
'Use --user "$(id -u):$(id -g)" or make the workspace mount read-only.');
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function shellTokens(input) {
|
|
48
|
+
const tokens = [];
|
|
49
|
+
let token = '';
|
|
50
|
+
let quote = '';
|
|
51
|
+
let substitutionDepth = 0;
|
|
52
|
+
let githubExpression = false;
|
|
53
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
54
|
+
const character = input[index];
|
|
55
|
+
if (githubExpression) {
|
|
56
|
+
token += character;
|
|
57
|
+
if (character === '}' && input[index + 1] === '}') {
|
|
58
|
+
token += '}';
|
|
59
|
+
index += 1;
|
|
60
|
+
githubExpression = false;
|
|
61
|
+
}
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (substitutionDepth > 0) {
|
|
65
|
+
token += character;
|
|
66
|
+
if (character === '(')
|
|
67
|
+
substitutionDepth += 1;
|
|
68
|
+
if (character === ')')
|
|
69
|
+
substitutionDepth -= 1;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (character === '$' && input[index + 1] === '{' && input[index + 2] === '{') {
|
|
73
|
+
token += '${{';
|
|
74
|
+
index += 2;
|
|
75
|
+
githubExpression = true;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (character === '$' && input[index + 1] === '(') {
|
|
79
|
+
token += '$(';
|
|
80
|
+
substitutionDepth = 1;
|
|
81
|
+
index += 1;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (quote) {
|
|
85
|
+
if (character === '\\' && input[index + 1] !== undefined)
|
|
86
|
+
token += input[++index];
|
|
87
|
+
else if (character === quote)
|
|
88
|
+
quote = '';
|
|
89
|
+
else
|
|
90
|
+
token += character;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (character === '"' || character === "'") {
|
|
94
|
+
quote = character;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (/\s/u.test(character)) {
|
|
98
|
+
if (token)
|
|
99
|
+
tokens.push(token);
|
|
100
|
+
token = '';
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (character === '#' && !token)
|
|
104
|
+
break;
|
|
105
|
+
if (character === ';' || character === '|' || character === '&') {
|
|
106
|
+
if (token)
|
|
107
|
+
tokens.push(token);
|
|
108
|
+
token = '';
|
|
109
|
+
if ((character === '&' || character === '|') && input[index + 1] === character)
|
|
110
|
+
index += 1;
|
|
111
|
+
tokens.push(character);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (character === '\\' && input[index + 1] !== undefined)
|
|
115
|
+
token += input[++index];
|
|
116
|
+
else
|
|
117
|
+
token += character;
|
|
118
|
+
}
|
|
119
|
+
if (token)
|
|
120
|
+
tokens.push(token);
|
|
121
|
+
return tokens;
|
|
122
|
+
}
|
|
123
|
+
function dockerRunBlocks(run) {
|
|
124
|
+
const blocks = [];
|
|
125
|
+
const lines = run.split('\n');
|
|
126
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
127
|
+
if (!/\bdocker\b/u.test(lines[index]))
|
|
128
|
+
continue;
|
|
129
|
+
let block = lines[index];
|
|
130
|
+
while (block.trimEnd().endsWith('\\') && index + 1 < lines.length) {
|
|
131
|
+
index += 1;
|
|
132
|
+
block = `${block.trimEnd().slice(0, -1)} ${lines[index]}`;
|
|
133
|
+
}
|
|
134
|
+
blocks.push(block);
|
|
135
|
+
}
|
|
136
|
+
return blocks;
|
|
137
|
+
}
|
|
138
|
+
function findDockerCommand(tokens) {
|
|
139
|
+
let commandStart = true;
|
|
140
|
+
for (const [index, token] of tokens.entries()) {
|
|
141
|
+
if (token === ';' || token === '|' || token === '&') {
|
|
142
|
+
commandStart = true;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (!commandStart)
|
|
146
|
+
continue;
|
|
147
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*=.*/u.test(token))
|
|
148
|
+
continue;
|
|
149
|
+
if (token === 'docker')
|
|
150
|
+
return index;
|
|
151
|
+
commandStart = false;
|
|
152
|
+
}
|
|
153
|
+
return -1;
|
|
154
|
+
}
|
|
155
|
+
function dockerOptions(block) {
|
|
156
|
+
const tokens = shellTokens(block);
|
|
157
|
+
const dockerIndex = findDockerCommand(tokens);
|
|
158
|
+
if (dockerIndex < 0)
|
|
159
|
+
return undefined;
|
|
160
|
+
let start = dockerIndex + 1;
|
|
161
|
+
while (start < tokens.length && tokens[start].startsWith('-')) {
|
|
162
|
+
const option = tokens[start];
|
|
163
|
+
const name = option.includes('=') ? option.slice(0, option.indexOf('=')) : option;
|
|
164
|
+
if (DOCKER_GLOBAL_FLAG_OPTIONS.has(name) || option.includes('='))
|
|
165
|
+
start += 1;
|
|
166
|
+
else if (DOCKER_GLOBAL_VALUE_OPTIONS.has(name))
|
|
167
|
+
start += 2;
|
|
168
|
+
else
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
if (tokens[start] === 'container')
|
|
172
|
+
start += 1;
|
|
173
|
+
if (tokens[start] !== 'run')
|
|
174
|
+
return undefined;
|
|
175
|
+
start += 1;
|
|
176
|
+
const volumes = [];
|
|
177
|
+
let user;
|
|
178
|
+
for (let index = start; index < tokens.length; index += 1) {
|
|
179
|
+
const option = tokens[index];
|
|
180
|
+
if (!option.startsWith('-'))
|
|
181
|
+
break;
|
|
182
|
+
const equals = option.indexOf('=');
|
|
183
|
+
const compact = option.startsWith('-v') || option.startsWith('-u');
|
|
184
|
+
const name = equals > 0
|
|
185
|
+
? option.slice(0, equals)
|
|
186
|
+
: compact && option.length > 2
|
|
187
|
+
? option.slice(0, 2)
|
|
188
|
+
: option;
|
|
189
|
+
const inlineValue = equals > 0
|
|
190
|
+
? option.slice(equals + 1)
|
|
191
|
+
: compact && option.length > 2
|
|
192
|
+
? option.slice(2)
|
|
193
|
+
: undefined;
|
|
194
|
+
const bundledFlags = /^-[diPt]{2,}$/u.test(option);
|
|
195
|
+
const value = inlineValue ?? (FLAG_OPTIONS.has(name) || bundledFlags ? undefined : tokens[++index]);
|
|
196
|
+
if ((name === '-v' || name === '--volume' || name === '--mount') && value !== undefined)
|
|
197
|
+
volumes.push(value);
|
|
198
|
+
if ((name === '-u' || name === '--user') && value !== undefined)
|
|
199
|
+
user = value;
|
|
200
|
+
}
|
|
201
|
+
return { user, volumes };
|
|
202
|
+
}
|
|
203
|
+
function hasHostUserMapping(value) {
|
|
204
|
+
return value !== undefined && /^\$\(\s*id\s+-u\s*\):\$\(\s*id\s+-g\s*\)$/u.test(value);
|
|
205
|
+
}
|
|
206
|
+
function isReadOnlyVolume(value) {
|
|
207
|
+
return /(?:^|:)ro(?:$|,)/u.test(value) || /(?:^|,)readonly(?:$|,)/u.test(value);
|
|
208
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SharedContext } from '../shared-context/index.mts';
|
|
2
|
+
export interface GhaWorkspacePolicyOptions {
|
|
3
|
+
workflowDirectories?: readonly string[];
|
|
4
|
+
actionDirectories?: readonly string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function checkGhaWorkspacePolicy(ctx: SharedContext, options?: GhaWorkspacePolicyOptions): Promise<{
|
|
7
|
+
errors: string[];
|
|
8
|
+
}>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { checkDockerWorkspaceUserDocument } from './docker-workspace-user.mjs';
|
|
2
|
+
import { ghaFileKind, loadWorkflowDocument } from './shared.mjs';
|
|
3
|
+
import { checkNoSparseCheckoutDocument } from './sparse-checkout.mjs';
|
|
4
|
+
export async function checkGhaWorkspacePolicy(ctx, options = {}) {
|
|
5
|
+
const errors = [];
|
|
6
|
+
if (!ctx.isInsideGitRepo)
|
|
7
|
+
return { errors };
|
|
8
|
+
for (const file of ctx.trackedFiles) {
|
|
9
|
+
const kind = ghaFileKind(file, options);
|
|
10
|
+
if (!kind)
|
|
11
|
+
continue;
|
|
12
|
+
const document = loadWorkflowDocument(ctx, file, errors);
|
|
13
|
+
checkNoSparseCheckoutDocument(file, document, kind, errors);
|
|
14
|
+
checkDockerWorkspaceUserDocument(file, document, kind, errors);
|
|
15
|
+
}
|
|
16
|
+
return { errors };
|
|
17
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SharedContext } from '../shared-context/index.mts';
|
|
2
|
+
import type { GhaWorkspacePolicyOptions } from './index.mts';
|
|
3
|
+
export type GhaFileKind = 'workflow' | 'action';
|
|
4
|
+
export declare function ghaFileKind(file: string, options: GhaWorkspacePolicyOptions): GhaFileKind | undefined;
|
|
5
|
+
export declare function loadWorkflowDocument(ctx: SharedContext, file: string, errors: string[]): unknown;
|
|
6
|
+
export declare function visitRunSteps(document: unknown, isAction: boolean, visitor: (scope: string, index: number, step: Record<string, unknown>) => void): void;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { parse as load } from 'yaml';
|
|
4
|
+
export function ghaFileKind(file, options) {
|
|
5
|
+
const workflowDirectories = options.workflowDirectories ?? ['.github/workflows'];
|
|
6
|
+
const actionDirectories = options.actionDirectories ?? ['.github/actions'];
|
|
7
|
+
if (workflowDirectories.some((directory) => file.startsWith(`${directory.replace(/\/$/u, '')}/`)) &&
|
|
8
|
+
/\.ya?ml$/u.test(file))
|
|
9
|
+
return 'workflow';
|
|
10
|
+
if (actionDirectories.some((directory) => file.startsWith(`${directory.replace(/\/$/u, '')}/`)) &&
|
|
11
|
+
/\/action\.ya?ml$/u.test(file))
|
|
12
|
+
return 'action';
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
export function loadWorkflowDocument(ctx, file, errors) {
|
|
16
|
+
try {
|
|
17
|
+
const content = ctx.readTrackedFile?.(file) ?? readFileSync(join(ctx.repoRoot, file), 'utf8');
|
|
18
|
+
return load(content);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
errors.push(`::error file=${file}::${file}: invalid YAML (${error.message})`);
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function visitRunSteps(document, isAction, visitor) {
|
|
26
|
+
if (!document || typeof document !== 'object')
|
|
27
|
+
return;
|
|
28
|
+
if (isAction) {
|
|
29
|
+
const runs = document.runs;
|
|
30
|
+
if (!runs || typeof runs !== 'object')
|
|
31
|
+
return;
|
|
32
|
+
visitSteps('action', runs.steps, visitor);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const jobs = document.jobs;
|
|
36
|
+
if (!jobs || typeof jobs !== 'object')
|
|
37
|
+
return;
|
|
38
|
+
for (const [jobId, value] of Object.entries(jobs)) {
|
|
39
|
+
if (!value || typeof value !== 'object')
|
|
40
|
+
continue;
|
|
41
|
+
const job = value;
|
|
42
|
+
if (typeof job.uses === 'string')
|
|
43
|
+
continue;
|
|
44
|
+
visitSteps(`job "${jobId}"`, job.steps, visitor);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function visitSteps(scope, value, visitor) {
|
|
48
|
+
if (!Array.isArray(value))
|
|
49
|
+
return;
|
|
50
|
+
value.forEach((step, index) => {
|
|
51
|
+
if (step && typeof step === 'object')
|
|
52
|
+
visitor(scope, index, step);
|
|
53
|
+
});
|
|
54
|
+
}
|