vouchington-tooling 0.0.17 → 0.0.19
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 +5 -2
- package/dist/cli/commands/post-review.d.mts +1 -0
- package/dist/cli/commands/post-review.mjs +4 -0
- package/dist/cli/commands/stage-review-payload.d.mts +1 -0
- package/dist/cli/commands/stage-review-payload.mjs +4 -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-post-review/claude-token.d.mts +25 -0
- package/dist/gha-post-review/claude-token.mjs +107 -0
- package/dist/gha-post-review/cli.d.mts +1 -0
- package/dist/gha-post-review/cli.mjs +22 -0
- package/dist/gha-post-review/github.d.mts +20 -0
- package/dist/gha-post-review/github.mjs +77 -0
- package/dist/gha-post-review/index.d.mts +9 -0
- package/dist/gha-post-review/index.mjs +5 -0
- package/dist/gha-post-review/post.d.mts +18 -0
- package/dist/gha-post-review/post.mjs +27 -0
- package/dist/gha-post-review/token.d.mts +8 -0
- package/dist/gha-post-review/token.mjs +20 -0
- package/dist/gha-review-payload/cli.d.mts +1 -0
- package/dist/gha-review-payload/cli.mjs +24 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +1 -0
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -41,6 +41,8 @@ vouchington install-playwright-chromium-arm64
|
|
|
41
41
|
vouchington ghcr-package-retention example%2Fapi
|
|
42
42
|
vouchington nuget-central-version trusted.props candidate.props metadata.json out.props
|
|
43
43
|
vouchington swift-semantic-equal BASE HEAD App.swift
|
|
44
|
+
vouchington post-review
|
|
45
|
+
vouchington stage-review-payload optional|required <source> <destination>
|
|
44
46
|
```
|
|
45
47
|
|
|
46
48
|
Host-lock environment:
|
|
@@ -91,6 +93,7 @@ import { parseCsvRows, streamCsvRows } from 'vouchington-tooling/csv'
|
|
|
91
93
|
import { readResponseBody } from 'vouchington-tooling/http-body'
|
|
92
94
|
import { runAstGrepRule } from 'vouchington-tooling/ast-grep-rule'
|
|
93
95
|
import { parseReviewPayload, remapReviewComments } from 'vouchington-tooling/gha-review-payload'
|
|
96
|
+
import { runPostReview } from 'vouchington-tooling/gha-post-review'
|
|
94
97
|
import { nextPageUrlFromLinkHeader } from 'vouchington-tooling/http-link-pagination'
|
|
95
98
|
import { cmdUpload, mintPresignedControl } from 'vouchington-tooling/coverage-transport'
|
|
96
99
|
import { pruneDeployedRuntimeDeps } from 'vouchington-tooling/pnpm-deploy'
|
|
@@ -107,5 +110,5 @@ import { validateResolvedPinDelta } from 'vouchington-tooling/swift-resolved-pin
|
|
|
107
110
|
```
|
|
108
111
|
|
|
109
112
|
The artifact, review-payload, HTTP body, and pagination APIs validate untrusted inputs at their
|
|
110
|
-
boundaries.
|
|
111
|
-
|
|
113
|
+
boundaries. Review posting lives in `gha-post-review` and talks to GitHub only through caller-supplied
|
|
114
|
+
credentials (job token or a minted Claude GitHub App token).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runPostReviewCommand(): Promise<number>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runStageReviewPayloadCommand(args: readonly string[]): number;
|
package/dist/cli/index.mjs
CHANGED
|
@@ -10,6 +10,8 @@ import { runPnpmInstallCli } from './commands/pnpm-install.mjs';
|
|
|
10
10
|
import { runRunnerPortPolicy } from './commands/runner-port-policy.mjs';
|
|
11
11
|
import { runScript } from './commands/spawn-script.mjs';
|
|
12
12
|
import { runNugetCentralVersionCommand } from './commands/nuget-central-version.mjs';
|
|
13
|
+
import { runPostReviewCommand } from './commands/post-review.mjs';
|
|
14
|
+
import { runStageReviewPayloadCommand } from './commands/stage-review-payload.mjs';
|
|
13
15
|
import { runSwiftSemanticEqualCommand } from './commands/swift-semantic-equal.mjs';
|
|
14
16
|
import { runVitestBlobManifestCommand } from './commands/vitest-blob-manifest.mjs';
|
|
15
17
|
import { runWithHostLock } from './commands/with-host-lock.mjs';
|
|
@@ -82,6 +84,10 @@ export function runCli(argv = process.argv) {
|
|
|
82
84
|
return runNugetCentralVersionCommand(parsed.args);
|
|
83
85
|
case 'swift-semantic-equal':
|
|
84
86
|
return runSwiftSemanticEqualCommand(parsed.args);
|
|
87
|
+
case 'post-review':
|
|
88
|
+
return runPostReviewCommand();
|
|
89
|
+
case 'stage-review-payload':
|
|
90
|
+
return runStageReviewPayloadCommand(parsed.args);
|
|
85
91
|
case 'http-origin':
|
|
86
92
|
return runHttpOrigin(parsed.field, parsed.value);
|
|
87
93
|
case 'gha-artifacts-cleanup':
|
package/dist/cli/parse.d.mts
CHANGED
package/dist/cli/parse.mjs
CHANGED
|
@@ -41,6 +41,10 @@ export function parseCli(argv) {
|
|
|
41
41
|
return { kind: 'nuget-central-version', args: rest };
|
|
42
42
|
if (command === 'swift-semantic-equal')
|
|
43
43
|
return { kind: 'swift-semantic-equal', args: rest };
|
|
44
|
+
if (command === 'post-review')
|
|
45
|
+
return { kind: 'post-review', args: rest };
|
|
46
|
+
if (command === 'stage-review-payload')
|
|
47
|
+
return { kind: 'stage-review-payload', args: rest };
|
|
44
48
|
if (command === 'http-origin')
|
|
45
49
|
return parseHttpOrigin(rest);
|
|
46
50
|
if (command === 'gha-artifacts-cleanup')
|
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 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 nuget-central-version Validate a Directory.Packages.props PackageVersion delta\n swift-semantic-equal Compare Swift sources ignoring comments and whitespace\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 [--tag-prefix PREFIX] [--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>...\nnuget-central-version <trusted-props> <candidate-props> <metadata-json> <output-props>\nswift-semantic-equal <base> <head> <file.swift>\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 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 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\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 [--tag-prefix PREFIX] [--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>...\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>\n";
|
|
2
2
|
export declare function printUsage(stream?: NodeJS.WritableStream): void;
|
package/dist/cli/usage.mjs
CHANGED
|
@@ -28,6 +28,8 @@ Commands:
|
|
|
28
28
|
ghcr-package-retention Delete old GHCR package versions past KEEP_MIN
|
|
29
29
|
nuget-central-version Validate a Directory.Packages.props PackageVersion delta
|
|
30
30
|
swift-semantic-equal Compare Swift sources ignoring comments and whitespace
|
|
31
|
+
post-review Post one COMMENT review from a staged payload file
|
|
32
|
+
stage-review-payload Validate a review payload file into a staging directory
|
|
31
33
|
|
|
32
34
|
Options:
|
|
33
35
|
-h, --help Show this help
|
|
@@ -78,6 +80,8 @@ install-playwright-chromium-arm64 [name:archive...]
|
|
|
78
80
|
ghcr-package-retention <url-encoded-package>...
|
|
79
81
|
nuget-central-version <trusted-props> <candidate-props> <metadata-json> <output-props>
|
|
80
82
|
swift-semantic-equal <base> <head> <file.swift>
|
|
83
|
+
post-review
|
|
84
|
+
stage-review-payload optional|required <source> <destination>
|
|
81
85
|
`;
|
|
82
86
|
export function printUsage(stream = process.stdout) {
|
|
83
87
|
stream.write(USAGE);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare const CLAUDE_OIDC_AUDIENCE = "claude-code-github-action";
|
|
2
|
+
export declare const CLAUDE_APP_TOKEN_EXCHANGE_URL = "https://api.anthropic.com/api/github/github-app-token-exchange";
|
|
3
|
+
export declare const CLAUDE_POSTER_PERMISSIONS: {
|
|
4
|
+
readonly contents: 'read';
|
|
5
|
+
readonly pull_requests: 'write';
|
|
6
|
+
};
|
|
7
|
+
export declare const GITHUB_INSTALLATION_TOKEN_URL = "https://api.github.com/installation/token";
|
|
8
|
+
export type FetchLike = (url: string, init: RequestInit) => Promise<{
|
|
9
|
+
ok: boolean;
|
|
10
|
+
status: number;
|
|
11
|
+
json(): Promise<unknown>;
|
|
12
|
+
}>;
|
|
13
|
+
export type ClaudeTokenIo = {
|
|
14
|
+
getOidcToken(): Promise<string>;
|
|
15
|
+
fetch: FetchLike;
|
|
16
|
+
mask(token: string): void;
|
|
17
|
+
};
|
|
18
|
+
export declare function oidcTokenRequest(env: NodeJS.ProcessEnv): {
|
|
19
|
+
url: string;
|
|
20
|
+
token: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function mintClaudeAppToken(io: ClaudeTokenIo): Promise<string>;
|
|
23
|
+
export declare function revokeClaudeAppToken(token: string, io: Pick<ClaudeTokenIo, 'fetch'>): Promise<void>;
|
|
24
|
+
export declare function withClaudeAppToken<T>(io: ClaudeTokenIo, fn: (token: string) => T | Promise<T>): Promise<T>;
|
|
25
|
+
export declare function createActionsClaudeTokenIo(env?: NodeJS.ProcessEnv, fetchImpl?: FetchLike): ClaudeTokenIo;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { ReviewPayloadError as PostReviewError } from '../gha-review-payload/index.mjs';
|
|
2
|
+
export const CLAUDE_OIDC_AUDIENCE = 'claude-code-github-action';
|
|
3
|
+
export const CLAUDE_APP_TOKEN_EXCHANGE_URL = 'https://api.anthropic.com/api/github/github-app-token-exchange';
|
|
4
|
+
// Anthropic rejects the exchange unless custom permissions include contents
|
|
5
|
+
// (read or write), even when the token is only used to POST /reviews.
|
|
6
|
+
export const CLAUDE_POSTER_PERMISSIONS = {
|
|
7
|
+
contents: 'read',
|
|
8
|
+
pull_requests: 'write',
|
|
9
|
+
};
|
|
10
|
+
export const GITHUB_INSTALLATION_TOKEN_URL = 'https://api.github.com/installation/token';
|
|
11
|
+
export function oidcTokenRequest(env) {
|
|
12
|
+
const base = env.ACTIONS_ID_TOKEN_REQUEST_URL;
|
|
13
|
+
const token = env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
|
14
|
+
if (!base || !token) {
|
|
15
|
+
throw new PostReviewError('OIDC token request env is missing.');
|
|
16
|
+
}
|
|
17
|
+
const separator = base.includes('?') ? '&' : '?';
|
|
18
|
+
return {
|
|
19
|
+
url: `${base}${separator}audience=${encodeURIComponent(CLAUDE_OIDC_AUDIENCE)}`,
|
|
20
|
+
token,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function readToken(payload) {
|
|
24
|
+
if (payload === null || typeof payload !== 'object')
|
|
25
|
+
return '';
|
|
26
|
+
const record = payload;
|
|
27
|
+
const token = record.token ?? record.app_token ?? record.value;
|
|
28
|
+
return typeof token === 'string' ? token : '';
|
|
29
|
+
}
|
|
30
|
+
function readErrorMessage(payload) {
|
|
31
|
+
if (payload === null || typeof payload !== 'object')
|
|
32
|
+
return '';
|
|
33
|
+
const record = payload;
|
|
34
|
+
if (typeof record.message === 'string' && record.message.length > 0)
|
|
35
|
+
return record.message;
|
|
36
|
+
const nested = record.error;
|
|
37
|
+
if (nested !== null && typeof nested === 'object') {
|
|
38
|
+
const message = nested.message;
|
|
39
|
+
if (typeof message === 'string')
|
|
40
|
+
return message;
|
|
41
|
+
}
|
|
42
|
+
return '';
|
|
43
|
+
}
|
|
44
|
+
function exchangeErrorMessage(status, payload) {
|
|
45
|
+
const detail = readErrorMessage(payload);
|
|
46
|
+
const suffix = detail.length > 0 ? `: ${detail}` : '';
|
|
47
|
+
return `Claude App token exchange failed (HTTP ${status})${suffix}.`;
|
|
48
|
+
}
|
|
49
|
+
export async function mintClaudeAppToken(io) {
|
|
50
|
+
const oidcToken = await io.getOidcToken();
|
|
51
|
+
const response = await io.fetch(CLAUDE_APP_TOKEN_EXCHANGE_URL, {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: {
|
|
54
|
+
Authorization: `Bearer ${oidcToken}`,
|
|
55
|
+
'Content-Type': 'application/json',
|
|
56
|
+
},
|
|
57
|
+
body: JSON.stringify({ permissions: CLAUDE_POSTER_PERMISSIONS }),
|
|
58
|
+
});
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
throw new PostReviewError(exchangeErrorMessage(response.status, await response.json().catch(() => null)));
|
|
61
|
+
}
|
|
62
|
+
const appToken = readToken(await response.json());
|
|
63
|
+
if (appToken.length === 0) {
|
|
64
|
+
throw new PostReviewError('Claude App token exchange returned no token.');
|
|
65
|
+
}
|
|
66
|
+
io.mask(appToken);
|
|
67
|
+
return appToken;
|
|
68
|
+
}
|
|
69
|
+
export async function revokeClaudeAppToken(token, io) {
|
|
70
|
+
await io.fetch(GITHUB_INSTALLATION_TOKEN_URL, {
|
|
71
|
+
method: 'DELETE',
|
|
72
|
+
headers: {
|
|
73
|
+
Accept: 'application/vnd.github+json',
|
|
74
|
+
Authorization: `Bearer ${token}`,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
export async function withClaudeAppToken(io, fn) {
|
|
79
|
+
const token = await mintClaudeAppToken(io);
|
|
80
|
+
try {
|
|
81
|
+
return await fn(token);
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
await revokeClaudeAppToken(token, io);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export function createActionsClaudeTokenIo(env = process.env, fetchImpl = fetch) {
|
|
88
|
+
return {
|
|
89
|
+
async getOidcToken() {
|
|
90
|
+
const request = oidcTokenRequest(env);
|
|
91
|
+
const response = await fetchImpl(request.url, {
|
|
92
|
+
headers: { Authorization: `Bearer ${request.token}` },
|
|
93
|
+
});
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
throw new PostReviewError(`OIDC token request failed (HTTP ${response.status}).`);
|
|
96
|
+
}
|
|
97
|
+
const jwt = readToken(await response.json());
|
|
98
|
+
if (jwt.length === 0)
|
|
99
|
+
throw new PostReviewError('OIDC token request returned no token.');
|
|
100
|
+
return jwt;
|
|
101
|
+
},
|
|
102
|
+
fetch: fetchImpl,
|
|
103
|
+
mask(token) {
|
|
104
|
+
process.stdout.write(`::add-mask::${token}\n`);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runPostReviewCli(env?: NodeJS.ProcessEnv): Promise<number>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { writePostedOutput, postReviewFromEnv } from './github.mjs';
|
|
3
|
+
export async function runPostReviewCli(env = process.env) {
|
|
4
|
+
try {
|
|
5
|
+
const result = await postReviewFromEnv(env);
|
|
6
|
+
writePostedOutput(result.posted, env.GITHUB_OUTPUT);
|
|
7
|
+
return 0;
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
11
|
+
return 1;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/* v8 ignore next 12 */
|
|
15
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
16
|
+
runPostReviewCli().then((code) => {
|
|
17
|
+
process.exitCode = code;
|
|
18
|
+
}, (error) => {
|
|
19
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
20
|
+
process.exitCode = 1;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { type PostResult, type PostReviewIo, type SanitizedReview } from './post.mts';
|
|
3
|
+
export type GhExec = (args: readonly string[], options?: {
|
|
4
|
+
input?: string;
|
|
5
|
+
env?: NodeJS.ProcessEnv;
|
|
6
|
+
}) => string;
|
|
7
|
+
export declare function createGhExec(exec?: typeof execFileSync): GhExec;
|
|
8
|
+
export declare function postWithGh(repository: string, prNumber: string, payload: SanitizedReview, token: string, exec: GhExec): PostResult;
|
|
9
|
+
export declare function writePostedOutput(posted: boolean, outputPath?: string | undefined): void;
|
|
10
|
+
export declare function createGhPostReviewIo(options: {
|
|
11
|
+
repository: string;
|
|
12
|
+
prNumber: string;
|
|
13
|
+
payloadPath: string;
|
|
14
|
+
payloadBytes: Buffer;
|
|
15
|
+
token: string;
|
|
16
|
+
exec: GhExec;
|
|
17
|
+
}): PostReviewIo;
|
|
18
|
+
export declare function postReviewFromEnv(env?: NodeJS.ProcessEnv, exec?: GhExec, claudeIo?: import("./claude-token.mts").ClaudeTokenIo): Promise<{
|
|
19
|
+
posted: boolean;
|
|
20
|
+
}>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { appendFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { parseReviewFilesJson } from '../gha-review-payload/index.mjs';
|
|
4
|
+
import { ReviewPayloadError } from '../gha-review-payload/index.mjs';
|
|
5
|
+
import { createActionsClaudeTokenIo, withClaudeAppToken } from './claude-token.mjs';
|
|
6
|
+
import { runPostReview } from './post.mjs';
|
|
7
|
+
import { requireEnv, resolveReviewPostToken } from './token.mjs';
|
|
8
|
+
import { readRegularReviewPayload } from '../gha-review-payload/index.mjs';
|
|
9
|
+
export function createGhExec(exec = execFileSync) {
|
|
10
|
+
return (args, options) => exec('gh', [...args], {
|
|
11
|
+
encoding: 'utf8',
|
|
12
|
+
input: options?.input,
|
|
13
|
+
env: options?.env ?? process.env,
|
|
14
|
+
}).trim();
|
|
15
|
+
}
|
|
16
|
+
export function postWithGh(repository, prNumber, payload, token, exec) {
|
|
17
|
+
try {
|
|
18
|
+
exec(['api', '--method', 'POST', `repos/${repository}/pulls/${prNumber}/reviews`, '--input', '-'], {
|
|
19
|
+
input: JSON.stringify(payload),
|
|
20
|
+
env: { ...process.env, GH_TOKEN: token, GITHUB_TOKEN: token },
|
|
21
|
+
});
|
|
22
|
+
return { ok: true, status: 201, body: '' };
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
const err = error;
|
|
26
|
+
const text = `${err.stdout ?? ''}${err.stderr ?? ''}${err.message ?? ''}`;
|
|
27
|
+
const status = Number(/HTTP\s+(\d{3})/u.exec(text)?.[1] ?? 0);
|
|
28
|
+
return { ok: false, status, body: text };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function writePostedOutput(posted, outputPath = process.env.GITHUB_OUTPUT) {
|
|
32
|
+
if (!outputPath)
|
|
33
|
+
return;
|
|
34
|
+
appendFileSync(outputPath, `posted=${posted ? 'true' : 'false'}\n`);
|
|
35
|
+
}
|
|
36
|
+
export function createGhPostReviewIo(options) {
|
|
37
|
+
const { repository, prNumber, payloadBytes, token, exec } = options;
|
|
38
|
+
return {
|
|
39
|
+
readFile() {
|
|
40
|
+
return payloadBytes;
|
|
41
|
+
},
|
|
42
|
+
removeFile(path) {
|
|
43
|
+
rmSync(path, { force: true });
|
|
44
|
+
},
|
|
45
|
+
getHeadSha() {
|
|
46
|
+
const sha = exec(['api', `repos/${repository}/pulls/${prNumber}`, '--jq', '.head.sha']);
|
|
47
|
+
if (!/^[0-9a-f]{40}$/u.test(sha)) {
|
|
48
|
+
throw new ReviewPayloadError(`Could not resolve PR head SHA (got "${sha}").`);
|
|
49
|
+
}
|
|
50
|
+
return sha;
|
|
51
|
+
},
|
|
52
|
+
listPullFiles() {
|
|
53
|
+
return parseReviewFilesJson(exec(['api', '--paginate', `repos/${repository}/pulls/${prNumber}/files?per_page=100`]));
|
|
54
|
+
},
|
|
55
|
+
postReview(payload) {
|
|
56
|
+
return postWithGh(repository, prNumber, payload, token, exec);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export async function postReviewFromEnv(env = process.env, exec = createGhExec(), claudeIo = createActionsClaudeTokenIo(env)) {
|
|
61
|
+
const repository = requireEnv('GITHUB_REPOSITORY', env);
|
|
62
|
+
const prNumber = requireEnv('PR_NUMBER', env);
|
|
63
|
+
const payloadPath = requireEnv('CODE_REVIEW_PAYLOAD_PATH', env);
|
|
64
|
+
const payloadBytes = readRegularReviewPayload(payloadPath, 'required');
|
|
65
|
+
const postWithToken = (token) => runPostReview(payloadPath, createGhPostReviewIo({
|
|
66
|
+
repository,
|
|
67
|
+
prNumber,
|
|
68
|
+
payloadPath,
|
|
69
|
+
payloadBytes,
|
|
70
|
+
token,
|
|
71
|
+
exec,
|
|
72
|
+
}));
|
|
73
|
+
const token = resolveReviewPostToken(env);
|
|
74
|
+
if (token.source === 'github-token')
|
|
75
|
+
return postWithToken(token.token);
|
|
76
|
+
return await withClaudeAppToken(claudeIo, postWithToken);
|
|
77
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { MAX_COMMENTS, MAX_PAYLOAD_BYTES, PostReviewError, runPostReview } from './post.mts';
|
|
2
|
+
export type { PostResult, PostReviewIo, PullFile, ReviewComment, SanitizedReview } from './post.mts';
|
|
3
|
+
export { requireEnv, resolveReviewPostToken } from './token.mts';
|
|
4
|
+
export type { ReviewPostToken } from './token.mts';
|
|
5
|
+
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';
|
|
6
|
+
export type { ClaudeTokenIo, FetchLike } from './claude-token.mts';
|
|
7
|
+
export { createGhExec, createGhPostReviewIo, postReviewFromEnv, postWithGh, writePostedOutput, } from './github.mts';
|
|
8
|
+
export type { GhExec } from './github.mts';
|
|
9
|
+
export { runPostReviewCli } from './cli.mts';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { MAX_COMMENTS, MAX_PAYLOAD_BYTES, PostReviewError, runPostReview } from './post.mjs';
|
|
2
|
+
export { requireEnv, resolveReviewPostToken } from './token.mjs';
|
|
3
|
+
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, postReviewFromEnv, postWithGh, writePostedOutput, } from './github.mjs';
|
|
5
|
+
export { runPostReviewCli } from './cli.mjs';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ReviewFile, type SanitizedReview } from '../gha-review-payload/index.mts';
|
|
2
|
+
export { MAX_REVIEW_COMMENTS as MAX_COMMENTS, MAX_REVIEW_PAYLOAD_BYTES as MAX_PAYLOAD_BYTES, ReviewPayloadError as PostReviewError, type ReviewComment, type SanitizedReview, } from '../gha-review-payload/index.mts';
|
|
3
|
+
export type PullFile = ReviewFile;
|
|
4
|
+
export type PostResult = {
|
|
5
|
+
ok: boolean;
|
|
6
|
+
status: number;
|
|
7
|
+
body: string;
|
|
8
|
+
};
|
|
9
|
+
export type PostReviewIo = {
|
|
10
|
+
readFile(path: string): Buffer;
|
|
11
|
+
removeFile(path: string): void;
|
|
12
|
+
getHeadSha(): string;
|
|
13
|
+
listPullFiles(): PullFile[];
|
|
14
|
+
postReview(payload: SanitizedReview): PostResult;
|
|
15
|
+
};
|
|
16
|
+
export declare function runPostReview(payloadPath: string, io: PostReviewIo): {
|
|
17
|
+
posted: boolean;
|
|
18
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ReviewPayloadError, bodyOnlyReviewFallback, indexReviewFiles, parseReviewPayload, remapReviewComments, } from '../gha-review-payload/index.mjs';
|
|
2
|
+
export { MAX_REVIEW_COMMENTS as MAX_COMMENTS, MAX_REVIEW_PAYLOAD_BYTES as MAX_PAYLOAD_BYTES, ReviewPayloadError as PostReviewError, } from '../gha-review-payload/index.mjs';
|
|
3
|
+
export function runPostReview(payloadPath, io) {
|
|
4
|
+
try {
|
|
5
|
+
let review = parseReviewPayload(io.readFile(payloadPath), io.getHeadSha());
|
|
6
|
+
try {
|
|
7
|
+
review = remapReviewComments(review, indexReviewFiles(io.listPullFiles()));
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
// Keep the parsed review when the PR file list is unavailable.
|
|
11
|
+
}
|
|
12
|
+
const first = io.postReview(review);
|
|
13
|
+
if (first.ok)
|
|
14
|
+
return { posted: true };
|
|
15
|
+
if (first.status !== 422) {
|
|
16
|
+
throw new ReviewPayloadError(`GitHub review POST failed (HTTP ${first.status}).`);
|
|
17
|
+
}
|
|
18
|
+
const retry = io.postReview(bodyOnlyReviewFallback(review, first.status));
|
|
19
|
+
if (!retry.ok) {
|
|
20
|
+
throw new ReviewPayloadError(`GitHub review POST retry failed (HTTP ${retry.status}).`);
|
|
21
|
+
}
|
|
22
|
+
return { posted: true };
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
io.removeFile(payloadPath);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type ReviewPostToken = {
|
|
2
|
+
source: 'github-token';
|
|
3
|
+
token: string;
|
|
4
|
+
} | {
|
|
5
|
+
source: 'claude-app';
|
|
6
|
+
};
|
|
7
|
+
export declare function resolveReviewPostToken(env?: NodeJS.ProcessEnv): ReviewPostToken;
|
|
8
|
+
export declare function requireEnv(name: string, env?: NodeJS.ProcessEnv): string;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ReviewPayloadError } from '../gha-review-payload/index.mjs';
|
|
2
|
+
export function resolveReviewPostToken(env = process.env) {
|
|
3
|
+
const source = env.CODE_REVIEW_TOKEN_SOURCE || 'claude-app';
|
|
4
|
+
if (source === 'github-token') {
|
|
5
|
+
const token = env.GH_TOKEN || env.GITHUB_TOKEN;
|
|
6
|
+
if (!token)
|
|
7
|
+
throw new ReviewPayloadError('GH_TOKEN or GITHUB_TOKEN is required.');
|
|
8
|
+
return { source, token };
|
|
9
|
+
}
|
|
10
|
+
if (source !== 'claude-app') {
|
|
11
|
+
throw new ReviewPayloadError(`Unknown CODE_REVIEW_TOKEN_SOURCE "${source}".`);
|
|
12
|
+
}
|
|
13
|
+
return { source: 'claude-app' };
|
|
14
|
+
}
|
|
15
|
+
export function requireEnv(name, env = process.env) {
|
|
16
|
+
const value = env[name];
|
|
17
|
+
if (!value)
|
|
18
|
+
throw new ReviewPayloadError(`${name} is required.`);
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runStageReviewPayloadCli(args: readonly string[]): number;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { ReviewPayloadError } from './payload.mjs';
|
|
3
|
+
import { stageReviewPayload, writeStagedOutput } from './file.mjs';
|
|
4
|
+
export function runStageReviewPayloadCli(args) {
|
|
5
|
+
try {
|
|
6
|
+
const [requirement, source, destination] = args;
|
|
7
|
+
if (requirement !== 'optional' && requirement !== 'required') {
|
|
8
|
+
throw new ReviewPayloadError('payload requirement must be optional or required.');
|
|
9
|
+
}
|
|
10
|
+
if (!source || !destination || args.length !== 3) {
|
|
11
|
+
throw new ReviewPayloadError('Usage: vouchington stage-review-payload optional|required <source> <destination>');
|
|
12
|
+
}
|
|
13
|
+
writeStagedOutput('staged', stageReviewPayload(source, destination, requirement) ? 'true' : 'false');
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
18
|
+
return 1;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/* v8 ignore next 3 */
|
|
22
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
23
|
+
process.exitCode = runStageReviewPayloadCli(process.argv.slice(2));
|
|
24
|
+
}
|
package/dist/index.d.mts
CHANGED
|
@@ -33,6 +33,8 @@ export { parseAstGrepRuleArgs, runAstGrepRule } from './ast-grep-rule/index.mts'
|
|
|
33
33
|
export type { AstGrepRuleInvocation, RunAstGrepRuleOptions } from './ast-grep-rule/index.mts';
|
|
34
34
|
export { bodyOnlyReviewFallback, indexReviewFiles, MAX_REVIEW_COMMENTS, MAX_REVIEW_PAYLOAD_BYTES, nearestReviewLine, parsePatchCommentable, parseReviewFilesJson, parseReviewPayload, readRegularReviewPayload, remapReviewComments, ReviewPayloadError, reviewCommentSubject, rewriteSnappedSuggestion, snapReviewNote, stageReviewPayload, writeStagedOutput, } from './gha-review-payload/index.mts';
|
|
35
35
|
export type { CommentableIndex, CommentableLine, LineKind, PayloadRequirement, ReviewComment, ReviewFile, ReviewSide, SanitizedReview, } from './gha-review-payload/index.mts';
|
|
36
|
+
export { CLAUDE_OIDC_AUDIENCE, createActionsClaudeTokenIo, mintClaudeAppToken, PostReviewError, requireEnv, resolveReviewPostToken, revokeClaudeAppToken, runPostReview, runPostReviewCli, withClaudeAppToken, } from './gha-post-review/index.mts';
|
|
37
|
+
export type { ClaudeTokenIo, PostResult, PostReviewIo, PullFile, ReviewPostToken, } from './gha-post-review/index.mts';
|
|
36
38
|
export { nextPageCursorFromLinkHeader, nextPageUrlFromLinkHeader, validatePaginationRequestUrl, } from './http-link-pagination/index.mts';
|
|
37
39
|
export { cmdDownloadCoverage, cmdDownloadVitestBlobs, cmdUpload, mintPresignedControl, transportObjectKeys, } from './coverage-transport/index.mts';
|
|
38
40
|
export type { ExpectedTransportIdentity, ObjectSigner, PresignIdentity, TransportControl, } from './coverage-transport/index.mts';
|
package/dist/index.mjs
CHANGED
|
@@ -19,6 +19,7 @@ export { escapeSpreadsheetFormula, parseCsvRows, streamCsvRows, stripCsvBom } fr
|
|
|
19
19
|
export { MissingResponseBodyError, readResponseBody, readResponseBodyAsBuffer, ResponseBodyTooLargeError, } from './http-body/index.mjs';
|
|
20
20
|
export { parseAstGrepRuleArgs, runAstGrepRule } from './ast-grep-rule/index.mjs';
|
|
21
21
|
export { bodyOnlyReviewFallback, indexReviewFiles, MAX_REVIEW_COMMENTS, MAX_REVIEW_PAYLOAD_BYTES, nearestReviewLine, parsePatchCommentable, parseReviewFilesJson, parseReviewPayload, readRegularReviewPayload, remapReviewComments, ReviewPayloadError, reviewCommentSubject, rewriteSnappedSuggestion, snapReviewNote, stageReviewPayload, writeStagedOutput, } from './gha-review-payload/index.mjs';
|
|
22
|
+
export { CLAUDE_OIDC_AUDIENCE, createActionsClaudeTokenIo, mintClaudeAppToken, PostReviewError, requireEnv, resolveReviewPostToken, revokeClaudeAppToken, runPostReview, runPostReviewCli, withClaudeAppToken, } from './gha-post-review/index.mjs';
|
|
22
23
|
export { nextPageCursorFromLinkHeader, nextPageUrlFromLinkHeader, validatePaginationRequestUrl, } from './http-link-pagination/index.mjs';
|
|
23
24
|
export { cmdDownloadCoverage, cmdDownloadVitestBlobs, cmdUpload, mintPresignedControl, transportObjectKeys, } from './coverage-transport/index.mjs';
|
|
24
25
|
export { EPOCH_PRUNED_AT, normalizeDeployedLayer, pruneDeployedRuntimeDeps, restoreDeployedWorkspacePackages, } from './pnpm-deploy/index.mjs';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vouchington-tooling",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"description": "Vouchington CLI and extractable tooling libraries.",
|
|
5
5
|
"homepage": "https://github.com/vouchington/vouchington-tooling/tree/main/packages/vouchington-tooling#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -125,6 +125,11 @@
|
|
|
125
125
|
"import": "./dist/gha-review-payload/index.mjs",
|
|
126
126
|
"default": "./dist/gha-review-payload/index.mjs"
|
|
127
127
|
},
|
|
128
|
+
"./gha-post-review": {
|
|
129
|
+
"types": "./dist/gha-post-review/index.d.mts",
|
|
130
|
+
"import": "./dist/gha-post-review/index.mjs",
|
|
131
|
+
"default": "./dist/gha-post-review/index.mjs"
|
|
132
|
+
},
|
|
128
133
|
"./http-link-pagination": {
|
|
129
134
|
"types": "./dist/http-link-pagination/index.d.mts",
|
|
130
135
|
"import": "./dist/http-link-pagination/index.mjs",
|