@jphutchins/code-review 0.1.0-alpha.53 → 0.1.0-alpha.54
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 +80 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/comment.eta +5 -1
package/dist/index.js
CHANGED
|
@@ -568,6 +568,7 @@ var findingPayload = (finding, schemaVersion) => Buffer.from(
|
|
|
568
568
|
JSON.stringify({ schema_version: schemaVersion, findings: [finding] }),
|
|
569
569
|
"utf-8"
|
|
570
570
|
).toString("base64");
|
|
571
|
+
var lineRange = (startLine, endLine, separator) => startLine === endLine ? String(startLine) : `${String(startLine)}${separator}${String(endLine)}`;
|
|
571
572
|
var findingPointer = (finding, schemaVersion, jsonUrl) => {
|
|
572
573
|
const payload = findingPayload(finding, schemaVersion);
|
|
573
574
|
if (payload.length > INLINE_EMBED_LIMIT_CHARS) {
|
|
@@ -1232,17 +1233,39 @@ var fetchThreadComments = async (ghApi, repo, prNumber) => {
|
|
|
1232
1233
|
|
|
1233
1234
|
// src/render.ts
|
|
1234
1235
|
var escapePipes = (text) => text.replace(/\|/g, "\\|");
|
|
1235
|
-
var
|
|
1236
|
+
var encodeAutolinkParens = (url) => url.replace(/\(/g, "%28").replace(/\)/g, "%29");
|
|
1237
|
+
var linkSafeUrl = (url) => encodeAutolinkParens(escapeCodeBackticks(url));
|
|
1238
|
+
var permalinkFor = (base, f, anchor) => {
|
|
1239
|
+
if (f.path === "" || f.side === "LEFT") return void 0;
|
|
1240
|
+
const path = f.path.split("/").map(
|
|
1241
|
+
(segment) => encodeAutolinkParens(
|
|
1242
|
+
encodeURIComponent(
|
|
1243
|
+
segment.replace(
|
|
1244
|
+
/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,
|
|
1245
|
+
"\uFFFD"
|
|
1246
|
+
)
|
|
1247
|
+
)
|
|
1248
|
+
// Markdown emphasis/syntax characters: `__` runs split the bare URL across CommonMark
|
|
1249
|
+
// nodes and truncate the autolink, and a path-only link ending in one of these loses its
|
|
1250
|
+
// tail to the autolinker's trailing-punctuation trim (issue #231 r4).
|
|
1251
|
+
).replace(/_/g, "%5F").replace(/\*/g, "%2A").replace(/'/g, "%27").replace(/!/g, "%21")
|
|
1252
|
+
).join("/");
|
|
1253
|
+
return anchor ? `${base}${path}#L${lineRange(f.start_line, f.end_line, "-L")}` : `${base}${path}`;
|
|
1254
|
+
};
|
|
1236
1255
|
var CARRIED_TOTAL_CHARS = 4e4;
|
|
1237
1256
|
var SUPPRESSED_NIT_BLOCK_OVERHEAD = 280;
|
|
1238
|
-
var sanitizeFinding = (f, answeredNotes) => {
|
|
1257
|
+
var sanitizeFinding = (f, answeredNotes, permalinkBase, unanchored) => {
|
|
1239
1258
|
const key2 = answeredNoteKey(f);
|
|
1259
|
+
const anchored = permalinkBase !== void 0 && !(unanchored?.has(f) ?? false);
|
|
1260
|
+
const permalink = permalinkBase === void 0 ? void 0 : permalinkFor(permalinkBase, f, anchored);
|
|
1240
1261
|
return {
|
|
1241
1262
|
...f,
|
|
1242
1263
|
title: escapePipes(f.title),
|
|
1243
1264
|
path: escapeCodeBackticks(f.path),
|
|
1244
1265
|
...f.code !== void 0 ? { code: escapeCodeBackticks(f.code), codeKey: f.code } : {},
|
|
1245
1266
|
...f.code_url !== void 0 ? { code_url: linkSafeUrl(f.code_url) } : {},
|
|
1267
|
+
rangeLabel: lineRange(f.start_line, f.end_line, "\u2013"),
|
|
1268
|
+
...permalink !== void 0 ? { permalink, permalinkAnchored: anchored } : {},
|
|
1246
1269
|
patchProjection: projectPatch(f.patch, "comment-body"),
|
|
1247
1270
|
answeredNote: answeredNotes !== void 0 && Object.prototype.hasOwnProperty.call(answeredNotes, key2) ? answeredNotes[key2] ?? "" : ""
|
|
1248
1271
|
};
|
|
@@ -1313,6 +1336,8 @@ var render = (input) => {
|
|
|
1313
1336
|
},
|
|
1314
1337
|
{ list: [], used: 0, dropped: 0 }
|
|
1315
1338
|
);
|
|
1339
|
+
const permalinkBase = input.repo !== void 0 && input.repo !== "" && input.reviewedSha !== void 0 && input.reviewedSha !== "" ? `https://github.com/${input.repo}/blob/${input.reviewedSha}/` : void 0;
|
|
1340
|
+
const unanchored = new Set(input.unanchoredStrays ?? []);
|
|
1316
1341
|
return eta.renderString(input.template, {
|
|
1317
1342
|
findings: input.findings,
|
|
1318
1343
|
envelope: input.envelope,
|
|
@@ -1333,7 +1358,9 @@ var render = (input) => {
|
|
|
1333
1358
|
postedAt: input.postedAt ?? "",
|
|
1334
1359
|
severityCounts,
|
|
1335
1360
|
convergenceSummary: !isFullReviewRound ? "" : convergence ? convergenceBadge(convergence) : convergenceSummary(input.findings, input.convergenceThreshold),
|
|
1336
|
-
strays: (input.strays ?? []).map(
|
|
1361
|
+
strays: (input.strays ?? []).map(
|
|
1362
|
+
(f) => sanitizeFinding(f, input.answeredNotes, permalinkBase, unanchored)
|
|
1363
|
+
),
|
|
1337
1364
|
suppressedNits: suppressedBudget.list,
|
|
1338
1365
|
carriedDroppedNits: suppressedBudget.dropped,
|
|
1339
1366
|
nitVisibilityFloor: input.nitVisibilityFloor ?? DEFAULT_NIT_VISIBILITY_FLOOR,
|
|
@@ -2083,14 +2110,32 @@ var execFileWithTimeout = (spec) => new Promise((resolve4, reject) => {
|
|
|
2083
2110
|
|
|
2084
2111
|
// src/gh.ts
|
|
2085
2112
|
var describeEndpoint = (args) => args.find((a) => a === "graphql" || a.includes("/") && !a.startsWith("-")) ?? args[0] ?? "(no endpoint)";
|
|
2086
|
-
var
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2113
|
+
var flagFirst;
|
|
2114
|
+
var withEscapeRetry = async (run2, idempotent) => {
|
|
2115
|
+
if (flagFirst) return run2(true);
|
|
2116
|
+
try {
|
|
2117
|
+
return await run2(false);
|
|
2118
|
+
} catch (err) {
|
|
2119
|
+
if (errMsg(err).includes("--allow-escape-sequences")) {
|
|
2120
|
+
if (!idempotent) throw err;
|
|
2121
|
+
flagFirst = true;
|
|
2122
|
+
return run2(true);
|
|
2123
|
+
}
|
|
2124
|
+
throw err;
|
|
2125
|
+
}
|
|
2126
|
+
};
|
|
2127
|
+
var isIdempotentCall = (args) => !args.includes("--input") && !args.includes("--method") && !args.includes("graphql");
|
|
2128
|
+
var runGhApi = (args, stdin, env) => withEscapeRetry(
|
|
2129
|
+
(withFlag) => execFileWithTimeout({
|
|
2130
|
+
command: "gh",
|
|
2131
|
+
args: ["api", ...withFlag ? ["--allow-escape-sequences"] : [], ...args],
|
|
2132
|
+
label: `gh api ${describeEndpoint(args)}`,
|
|
2133
|
+
timeoutMs: subprocessTimeoutMs(),
|
|
2134
|
+
env,
|
|
2135
|
+
stdin
|
|
2136
|
+
}),
|
|
2137
|
+
isIdempotentCall(args)
|
|
2138
|
+
);
|
|
2094
2139
|
var run = promisify(execFile);
|
|
2095
2140
|
var STEP_TIMEOUT_MS = 6e4;
|
|
2096
2141
|
var MARKER_URL = /<!-- code-review:findings-json (https?:\/\/[^\s>]+) -->/;
|
|
@@ -2143,11 +2188,14 @@ var readArtifactFindings = (ghApiPath) => {
|
|
|
2143
2188
|
};
|
|
2144
2189
|
};
|
|
2145
2190
|
var ghArtifactReader = readArtifactFindings(async (url, outPath) => {
|
|
2146
|
-
const { stdout } = await
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2191
|
+
const { stdout } = await withEscapeRetry(
|
|
2192
|
+
(withFlag) => run("gh", ["api", ...withFlag ? ["--allow-escape-sequences"] : [], url], {
|
|
2193
|
+
encoding: "buffer",
|
|
2194
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
2195
|
+
timeout: STEP_TIMEOUT_MS
|
|
2196
|
+
}),
|
|
2197
|
+
true
|
|
2198
|
+
);
|
|
2151
2199
|
await writeFile(outPath, stdout);
|
|
2152
2200
|
});
|
|
2153
2201
|
var withoutKey = (doc, key2) => Object.fromEntries(Object.entries(doc).filter(([k]) => k !== key2));
|
|
@@ -2862,6 +2910,7 @@ ${dropNote}` : ""}`,
|
|
|
2862
2910
|
template,
|
|
2863
2911
|
route: effectiveRoute,
|
|
2864
2912
|
reviewedSha: input.headSha,
|
|
2913
|
+
repo: input.headRepo || input.repo,
|
|
2865
2914
|
effort: input.effort,
|
|
2866
2915
|
sameRootNotes: {},
|
|
2867
2916
|
// The answered-state honesty rules apply on EVERY surface that renders the filtered
|
|
@@ -2954,6 +3003,7 @@ ${dropNote}` : ""}`,
|
|
|
2954
3003
|
template,
|
|
2955
3004
|
route: effectiveRoute,
|
|
2956
3005
|
reviewedSha: input.headSha,
|
|
3006
|
+
repo: input.headRepo || input.repo,
|
|
2957
3007
|
effort: input.effort,
|
|
2958
3008
|
testReport,
|
|
2959
3009
|
clocDiff,
|
|
@@ -2980,11 +3030,12 @@ ${dropNote}` : ""}`,
|
|
|
2980
3030
|
|
|
2981
3031
|
> **Note:** ${String(longFiles.length)} suggestion(s) exceeded GitHub's ~10-line inline suggestion limit and were omitted from the inline comments; the affected findings remain in the review.
|
|
2982
3032
|
` : "";
|
|
2983
|
-
const renderBody = (inlineDisposition, reviewUrl2, straysOverride, unanchoredCount2) => formatMarkdown(
|
|
3033
|
+
const renderBody = (inlineDisposition, reviewUrl2, straysOverride, unanchoredCount2, unanchoredStrays) => formatMarkdown(
|
|
2984
3034
|
render({
|
|
2985
3035
|
...commonRenderInput,
|
|
2986
3036
|
...straysOverride ? { strays: straysOverride } : {},
|
|
2987
3037
|
...unanchoredCount2 !== void 0 ? { unanchoredCount: unanchoredCount2 } : {},
|
|
3038
|
+
...unanchoredStrays !== void 0 && unanchoredStrays.length > 0 ? { unanchoredStrays } : {},
|
|
2988
3039
|
inlineDisposition,
|
|
2989
3040
|
reviewUrl: reviewUrl2
|
|
2990
3041
|
}) + longFilesNote
|
|
@@ -3041,7 +3092,7 @@ ${dropNote}` : ""}`,
|
|
|
3041
3092
|
await patchComment(
|
|
3042
3093
|
input.repo,
|
|
3043
3094
|
stickyRef.id,
|
|
3044
|
-
renderBody(finalDisposition, reviewUrl, finalStrays, unanchoredCount),
|
|
3095
|
+
renderBody(finalDisposition, reviewUrl, finalStrays, unanchoredCount, unposted),
|
|
3045
3096
|
ghApi
|
|
3046
3097
|
);
|
|
3047
3098
|
process.stderr.write(
|
|
@@ -3060,7 +3111,13 @@ ${dropNote}` : ""}`,
|
|
|
3060
3111
|
() => renderBody(
|
|
3061
3112
|
{ kind: "whole-document", inlineCount: inlinePosted, rejectedCount: unanchoredCount },
|
|
3062
3113
|
reviewUrl,
|
|
3063
|
-
visibleFindings
|
|
3114
|
+
visibleFindings,
|
|
3115
|
+
void 0,
|
|
3116
|
+
// The rejected-anchor invariant holds on EVERY surface, the run summary included — this
|
|
3117
|
+
// document deliberately carries every finding, so the GitHub-rejected ones must keep their
|
|
3118
|
+
// path-only links here too (issue #231 r2). Unconditional: renderBody's own gate treats an
|
|
3119
|
+
// empty array exactly like absence, so the ternary was a duplicated decision (issue #231 r3).
|
|
3120
|
+
unposted
|
|
3064
3121
|
)
|
|
3065
3122
|
);
|
|
3066
3123
|
};
|
|
@@ -5447,6 +5504,10 @@ var postCmd = defineCommand({
|
|
|
5447
5504
|
const priceResolution = resolvePrices(args.prices);
|
|
5448
5505
|
await post({
|
|
5449
5506
|
repo: args.repo,
|
|
5507
|
+
// The workflow's post step threads HEAD_REPO env (the fork's owner/name) — a finding
|
|
5508
|
+
// permalink targets the tree the reviewed SHA lives in (issue #231 r1). Absent/empty ⇒ the
|
|
5509
|
+
// base repo. Env rather than a flag: an older pinned CLI simply ignores it.
|
|
5510
|
+
headRepo: process.env["HEAD_REPO"] || void 0,
|
|
5450
5511
|
headSha: args["head-sha"],
|
|
5451
5512
|
botLogin: args["bot-login"] || "github-actions[bot]",
|
|
5452
5513
|
findingsPath: args.findings,
|