@jphutchins/code-review 0.1.0-alpha.30 → 0.1.0-alpha.32
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/dist/index.js +118 -48
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -404,6 +404,7 @@ var renderStraysSection = (strays) => {
|
|
|
404
404
|
};
|
|
405
405
|
var asRecord = (u) => typeof u === "object" && u !== null && !Array.isArray(u) ? u : null;
|
|
406
406
|
var errMsg = (e) => e instanceof Error ? e.message : String(e);
|
|
407
|
+
var annotationSafe = (msg) => msg.replaceAll(/[\r\n]+/g, " ");
|
|
407
408
|
var tryParseJson = (text) => {
|
|
408
409
|
try {
|
|
409
410
|
return { ok: true, value: JSON.parse(text) };
|
|
@@ -972,16 +973,32 @@ var pad2 = (n) => String(n).padStart(2, "0");
|
|
|
972
973
|
var formatUtc = (d) => `${String(d.getUTCFullYear())}-${pad2(d.getUTCMonth() + 1)}-${pad2(d.getUTCDate())} ${pad2(d.getUTCHours())}:${pad2(d.getUTCMinutes())} UTC`;
|
|
973
974
|
|
|
974
975
|
// src/notice.ts
|
|
975
|
-
var
|
|
976
|
+
var NOTICE_KINDS = [
|
|
977
|
+
"security-blocked",
|
|
978
|
+
"triage-error",
|
|
979
|
+
"setup-failed",
|
|
980
|
+
"checkout-failed",
|
|
981
|
+
"no-output"
|
|
982
|
+
];
|
|
983
|
+
var isNoticeKind = (s) => NOTICE_KINDS.some((k) => k === s);
|
|
976
984
|
var blockquote = (text) => text.replaceAll("\n", "\n> ");
|
|
985
|
+
var reasoned = (lead, noReason, reasons) => typeof reasons === "string" && reasons.trim() !== "" ? `${lead}
|
|
986
|
+
|
|
987
|
+
> ${blockquote(reasons)}` : noReason;
|
|
977
988
|
var noticeSummary = (kind, reasons) => {
|
|
978
989
|
switch (kind) {
|
|
979
990
|
case "security-blocked":
|
|
980
|
-
return
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
991
|
+
return reasoned(
|
|
992
|
+
"### \u{1F6D1} Code review skipped by the security gate\n\nThe diff was flagged as unsafe to apply and execute:",
|
|
993
|
+
"### \u{1F6D1} Code review skipped by the security gate\n\nThe security triage returned an unsafe verdict without a reason. See workflow logs.",
|
|
994
|
+
reasons
|
|
995
|
+
);
|
|
996
|
+
case "triage-error":
|
|
997
|
+
return reasoned(
|
|
998
|
+
"### \u{1F6E0}\uFE0F Security gate could not evaluate\n\nThe security triage could not produce a verdict (operational error), so the review failed closed \u2014 this is an infrastructure failure, not a finding about this diff. Re-run to retry a transient fault; a persistent one is a configuration issue (see the workflow logs). The triage step reported:",
|
|
999
|
+
"### \u{1F6E0}\uFE0F Security gate could not evaluate\n\nThe security triage could not produce a verdict (operational error), so the review failed closed \u2014 this is an infrastructure failure, not a finding about this diff. Re-run to retry a transient fault; a persistent one is a configuration issue (see the workflow logs).",
|
|
1000
|
+
reasons
|
|
1001
|
+
);
|
|
985
1002
|
case "setup-failed":
|
|
986
1003
|
return "### \u{1F6E0}\uFE0F Review did not run\n\nThe review job failed before the security triage could run (e.g. dependency install or environment setup). See the workflow logs \u2014 this is an infrastructure failure, not a security verdict.";
|
|
987
1004
|
case "checkout-failed":
|
|
@@ -990,15 +1007,21 @@ The diff was flagged as unsafe to apply and execute:
|
|
|
990
1007
|
return "### \u26A0\uFE0F Review did not complete\n\nThe diff passed triage but the review produced no output. See workflow logs.";
|
|
991
1008
|
}
|
|
992
1009
|
};
|
|
993
|
-
var
|
|
1010
|
+
var noticeEnvelope = (summary) => ({
|
|
994
1011
|
schema_version: DEFAULT_SCHEMA_VERSION,
|
|
995
|
-
findings: noticeFindings(
|
|
1012
|
+
findings: noticeFindings(summary),
|
|
996
1013
|
models: [],
|
|
997
1014
|
turns: 0,
|
|
998
1015
|
duration_ms: 0,
|
|
999
1016
|
vendor_cost_usd: null,
|
|
1000
1017
|
incomplete: true
|
|
1001
1018
|
});
|
|
1019
|
+
var buildNoticeEnvelope = (kind, reasons) => noticeEnvelope(noticeSummary(kind, reasons));
|
|
1020
|
+
var buildUnknownNoticeEnvelope = (kind) => noticeEnvelope(
|
|
1021
|
+
`### \u26A0\uFE0F Review could not be rendered
|
|
1022
|
+
|
|
1023
|
+
The workflow asked for an unrecognized notice kind (\`${kind}\`) \u2014 the pinned code-review CLI is older than the workflow calling it (check that its version matches). Failing closed. See the workflow logs.`
|
|
1024
|
+
);
|
|
1002
1025
|
var identity = (decoded) => decoded;
|
|
1003
1026
|
var findingsTable = [
|
|
1004
1027
|
{
|
|
@@ -1082,33 +1105,75 @@ var resolvers = {
|
|
|
1082
1105
|
prices: (raw) => resolveSingleVersion("prices", raw)
|
|
1083
1106
|
};
|
|
1084
1107
|
var resolve = (kind, raw) => resolvers[kind](raw);
|
|
1085
|
-
var
|
|
1108
|
+
var MAX_BUFFER = 100 * 1024 * 1024;
|
|
1109
|
+
var MAX_TIMEOUT_MS = 2147483647;
|
|
1110
|
+
var parseTimeoutMs = (raw, fallback) => {
|
|
1111
|
+
if (raw === void 0 || !/^\d+$/.test(raw)) return fallback;
|
|
1112
|
+
const parsed = Number(raw);
|
|
1113
|
+
return parsed > 0 && parsed <= MAX_TIMEOUT_MS ? parsed : fallback;
|
|
1114
|
+
};
|
|
1115
|
+
var SUBPROCESS_TIMEOUT_ENV = "CODE_REVIEW_SUBPROCESS_TIMEOUT_MS";
|
|
1116
|
+
var DEFAULT_SUBPROCESS_TIMEOUT_MS = 12e4;
|
|
1117
|
+
var subprocessTimeoutMs = () => parseTimeoutMs(process.env[SUBPROCESS_TIMEOUT_ENV], DEFAULT_SUBPROCESS_TIMEOUT_MS);
|
|
1118
|
+
var classifyExecError = (err, stderr, timeoutMs) => {
|
|
1119
|
+
const e = err;
|
|
1120
|
+
const stderrStr = stderr.trim();
|
|
1121
|
+
if (e.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER")
|
|
1122
|
+
return `output exceeded ${String(MAX_BUFFER)} bytes (killed)`;
|
|
1123
|
+
if (e.killed === true)
|
|
1124
|
+
return `no response within ${String(timeoutMs)}ms (killed a hung child)${stderrStr ? `: ${stderrStr}` : ""}`;
|
|
1125
|
+
return stderrStr || errMsg(err);
|
|
1126
|
+
};
|
|
1127
|
+
var execFileWithTimeout = (spec) => new Promise((resolve3, reject) => {
|
|
1086
1128
|
const child = execFile(
|
|
1087
|
-
|
|
1088
|
-
[
|
|
1089
|
-
{
|
|
1129
|
+
spec.command,
|
|
1130
|
+
[...spec.args],
|
|
1131
|
+
{
|
|
1132
|
+
...spec.env ? { env: { ...process.env, ...spec.env } } : {},
|
|
1133
|
+
encoding: "utf-8",
|
|
1134
|
+
maxBuffer: MAX_BUFFER,
|
|
1135
|
+
timeout: spec.timeoutMs,
|
|
1136
|
+
killSignal: "SIGKILL"
|
|
1137
|
+
},
|
|
1090
1138
|
(err, stdout, stderr) => {
|
|
1091
|
-
if (err)
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
resolve3(stdout);
|
|
1097
|
-
}
|
|
1139
|
+
if (err)
|
|
1140
|
+
reject(
|
|
1141
|
+
new Error(`${spec.label} failed: ${classifyExecError(err, stderr, spec.timeoutMs)}`)
|
|
1142
|
+
);
|
|
1143
|
+
else resolve3(stdout);
|
|
1098
1144
|
}
|
|
1099
1145
|
);
|
|
1100
|
-
if (stdin !== void 0) {
|
|
1101
|
-
child.stdin?.
|
|
1146
|
+
if (spec.stdin !== void 0) {
|
|
1147
|
+
child.stdin?.on("error", () => void 0);
|
|
1148
|
+
child.stdin?.end(spec.stdin);
|
|
1102
1149
|
}
|
|
1103
1150
|
});
|
|
1104
1151
|
|
|
1152
|
+
// src/gh.ts
|
|
1153
|
+
var describeEndpoint = (args) => args.find((a) => a === "graphql" || a.includes("/") && !a.startsWith("-")) ?? args[0] ?? "(no endpoint)";
|
|
1154
|
+
var runGhApi = (args, stdin, env) => execFileWithTimeout({
|
|
1155
|
+
command: "gh",
|
|
1156
|
+
args: ["api", ...args],
|
|
1157
|
+
label: `gh api ${describeEndpoint(args)}`,
|
|
1158
|
+
timeoutMs: subprocessTimeoutMs(),
|
|
1159
|
+
env,
|
|
1160
|
+
stdin
|
|
1161
|
+
});
|
|
1162
|
+
|
|
1105
1163
|
// src/pr.ts
|
|
1106
1164
|
var CANDIDATE_JQ = ".[] | {number: .number, state: .state, headRef: .head.ref, headSha: .head.sha}";
|
|
1107
1165
|
var parseCandidates = (stdout) => parseJsonl(stdout);
|
|
1166
|
+
var fetchDirectCandidates = async (repo, headSha, ghApi) => {
|
|
1167
|
+
try {
|
|
1168
|
+
return parseCandidates(
|
|
1169
|
+
await ghApi([`repos/${repo}/commits/${headSha}/pulls`, "--jq", CANDIDATE_JQ])
|
|
1170
|
+
);
|
|
1171
|
+
} catch {
|
|
1172
|
+
return [];
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1108
1175
|
var fetchPrCandidates = async (repo, headSha, ghApi) => {
|
|
1109
|
-
const direct =
|
|
1110
|
-
await ghApi([`repos/${repo}/commits/${headSha}/pulls`, "--jq", CANDIDATE_JQ])
|
|
1111
|
-
);
|
|
1176
|
+
const direct = await fetchDirectCandidates(repo, headSha, ghApi);
|
|
1112
1177
|
if (direct.length > 0) return direct;
|
|
1113
1178
|
const open = parseCandidates(
|
|
1114
1179
|
await ghApi([
|
|
@@ -1957,21 +2022,11 @@ stacked=${String(result.stacked)}
|
|
|
1957
2022
|
`;
|
|
1958
2023
|
}
|
|
1959
2024
|
};
|
|
1960
|
-
var runGit = (args) =>
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
(err, stdout, stderr) => {
|
|
1966
|
-
if (err) {
|
|
1967
|
-
const stderrStr = typeof stderr === "string" && stderr.trim() ? stderr.trim() : "";
|
|
1968
|
-
const errStr = err instanceof Error ? err.message : "unknown error";
|
|
1969
|
-
reject(new Error(`git ${args.join(" ")} failed: ${stderrStr || errStr}`));
|
|
1970
|
-
} else {
|
|
1971
|
-
resolve3(stdout);
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
);
|
|
2025
|
+
var runGit = (args) => execFileWithTimeout({
|
|
2026
|
+
command: "git",
|
|
2027
|
+
args,
|
|
2028
|
+
label: `git ${args.join(" ")}`,
|
|
2029
|
+
timeoutMs: subprocessTimeoutMs()
|
|
1975
2030
|
});
|
|
1976
2031
|
var PrMetaCodec = t.type({
|
|
1977
2032
|
changed_files: t.number,
|
|
@@ -3240,25 +3295,36 @@ var adaptCmd = defineCommand({
|
|
|
3240
3295
|
var noticeCmd = defineCommand({
|
|
3241
3296
|
meta: {
|
|
3242
3297
|
name: "notice",
|
|
3243
|
-
description: "Emit an abstract envelope for a run that produced no completed review (security block, setup failure,
|
|
3298
|
+
description: "Emit an abstract envelope for a run that produced no completed review (security block, triage error, setup failure, checkout failure, or empty run) \u2014 flagged incomplete so the commenter renders it honestly and won't bury a real review"
|
|
3244
3299
|
},
|
|
3245
3300
|
args: {
|
|
3246
3301
|
kind: {
|
|
3247
3302
|
type: "positional",
|
|
3248
|
-
description:
|
|
3303
|
+
description: `One of: ${NOTICE_KINDS.join(", ")} (an unrecognized kind renders a generic incomplete notice rather than failing \u2014 the pinned CLI is older than the workflow)`,
|
|
3249
3304
|
required: true
|
|
3250
3305
|
},
|
|
3251
3306
|
reasons: {
|
|
3252
3307
|
type: "string",
|
|
3253
|
-
description: "security-blocked only: the triage's fail-closed reason string (empty/omitted \u21D2 the no-reason wording)"
|
|
3308
|
+
description: "security-blocked / triage-error only: the triage's fail-closed reason string (empty/omitted \u21D2 the no-reason wording)"
|
|
3254
3309
|
}
|
|
3255
3310
|
},
|
|
3311
|
+
// An unrecognized kind degrades to a generic incomplete notice instead of exiting non-zero: a
|
|
3312
|
+
// `notice <kind>` call under the workflow's `set -euo pipefail` must never crash the assemble
|
|
3313
|
+
// step into posting nothing, and an unknown kind almost always means version skew, not a typo.
|
|
3256
3314
|
run: ({ args }) => {
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3315
|
+
if (!isNoticeKind(args.kind)) {
|
|
3316
|
+
process.stderr.write(
|
|
3317
|
+
`::warning::code-review notice: unrecognized kind "${annotationSafe(args.kind)}" \u2014 the pinned CLI is older than the workflow calling it; rendering a generic incomplete notice
|
|
3318
|
+
`
|
|
3319
|
+
);
|
|
3320
|
+
process.stdout.write(`${JSON.stringify(buildUnknownNoticeEnvelope(args.kind), null, 2)}
|
|
3261
3321
|
`);
|
|
3322
|
+
return;
|
|
3323
|
+
}
|
|
3324
|
+
process.stdout.write(
|
|
3325
|
+
`${JSON.stringify(buildNoticeEnvelope(args.kind, args.reasons), null, 2)}
|
|
3326
|
+
`
|
|
3327
|
+
);
|
|
3262
3328
|
}
|
|
3263
3329
|
});
|
|
3264
3330
|
var isExtractSchemaKind = (s) => s === "findings" || s === "triage";
|
|
@@ -3685,9 +3751,13 @@ var announceCmd = defineCommand({
|
|
|
3685
3751
|
runUrl: args["run-url"],
|
|
3686
3752
|
headBranch: args["head-branch"]
|
|
3687
3753
|
}).catch(
|
|
3688
|
-
(err) =>
|
|
3689
|
-
|
|
3754
|
+
(err) => (
|
|
3755
|
+
// `::warning::` so a persistently broken announce shows up in the run's annotations, not only
|
|
3756
|
+
// buried in the step log — `announce` otherwise reports success while having posted nothing.
|
|
3757
|
+
process.stderr.write(
|
|
3758
|
+
`::warning::code-review announce: could not post the in-progress sticky (${annotationSafe(errMsg(err))}) \u2014 continuing (cosmetic)
|
|
3690
3759
|
`
|
|
3760
|
+
)
|
|
3691
3761
|
)
|
|
3692
3762
|
);
|
|
3693
3763
|
}
|