terminalhire 0.42.36 → 0.42.38
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/bin/jpi-claim.js +734 -114
- package/dist/bin/jpi-dispatch.js +748 -128
- package/dist/bin/jpi-mcp.js +745 -125
- package/dist/bin/jpi-run.js +777 -157
- package/package.json +1 -1
package/dist/bin/jpi-claim.js
CHANGED
|
@@ -5181,20 +5181,20 @@ var init_github_issue_status = __esm({
|
|
|
5181
5181
|
// ../../packages/core/src/credit.ts
|
|
5182
5182
|
function verifyClaimCredit(claim, facts) {
|
|
5183
5183
|
const reasons = [];
|
|
5184
|
-
const
|
|
5184
|
+
const fail3 = (code, message2) => reasons.push({ code, message: message2 });
|
|
5185
5185
|
const norm = (r) => r.trim().toLowerCase();
|
|
5186
5186
|
if (norm(facts.repo) !== norm(claim.repo))
|
|
5187
|
-
|
|
5188
|
-
if (!facts.merged)
|
|
5187
|
+
fail3("repo-mismatch", `PR is in ${facts.repo}, claim is against ${claim.repo}`);
|
|
5188
|
+
if (!facts.merged) fail3("not-merged", `PR #${facts.prNumber} is not merged`);
|
|
5189
5189
|
if (facts.authorId == null || facts.authorId !== claim.claimantId)
|
|
5190
|
-
|
|
5190
|
+
fail3("author-mismatch", `PR author id ${facts.authorId} !== claimant id ${claim.claimantId}`);
|
|
5191
5191
|
if (facts.merged && facts.mergedById != null && facts.authorId != null && facts.mergedById === facts.authorId)
|
|
5192
|
-
|
|
5192
|
+
fail3("self-merged", `PR was merged by its own author (id ${facts.authorId})`);
|
|
5193
5193
|
if (claim.claimedIssueNumber != null) {
|
|
5194
5194
|
if (facts.closesIssues.length === 0)
|
|
5195
|
-
|
|
5195
|
+
fail3("issue-linkage-missing", `PR closes no issue; claim names #${claim.claimedIssueNumber}`);
|
|
5196
5196
|
else if (!facts.closesIssues.includes(claim.claimedIssueNumber))
|
|
5197
|
-
|
|
5197
|
+
fail3(
|
|
5198
5198
|
"issue-linkage-mismatch",
|
|
5199
5199
|
`PR closes ${facts.closesIssues.map((n) => "#" + n).join(", ")}; claim names #${claim.claimedIssueNumber}`
|
|
5200
5200
|
);
|
|
@@ -26207,12 +26207,22 @@ function reportedCounts(classified, reparsed) {
|
|
|
26207
26207
|
function readCounts(stdout, stderr = "") {
|
|
26208
26208
|
const out = `${stdout}
|
|
26209
26209
|
${stderr}`;
|
|
26210
|
+
const matched = [];
|
|
26210
26211
|
for (const reader of READERS) {
|
|
26211
26212
|
const counts = reader.read(out);
|
|
26212
26213
|
if (counts)
|
|
26213
|
-
|
|
26214
|
+
matched.push({ reader, counts });
|
|
26214
26215
|
}
|
|
26215
|
-
|
|
26216
|
+
const first = matched[0];
|
|
26217
|
+
if (first === void 0)
|
|
26218
|
+
return null;
|
|
26219
|
+
const legs = matched.filter(({ reader }) => !matched.some((m) => reader.shadowedBy?.includes(m.reader.runner)));
|
|
26220
|
+
const partial = legs.length > 1 || (first.reader.summaries?.(out) ?? 1) > 1;
|
|
26221
|
+
return {
|
|
26222
|
+
...first.counts,
|
|
26223
|
+
runner: first.reader.runner,
|
|
26224
|
+
...partial ? { coverage: "partial" } : {}
|
|
26225
|
+
};
|
|
26216
26226
|
}
|
|
26217
26227
|
function coverageThresholdUnmet(facts, counts) {
|
|
26218
26228
|
if (counts === null || counts.runner !== "node-tap")
|
|
@@ -26371,7 +26381,7 @@ function isGreen(outcome) {
|
|
|
26371
26381
|
function isOurFault(outcome) {
|
|
26372
26382
|
return LIMIT_OWNER[outcome] === "ours";
|
|
26373
26383
|
}
|
|
26374
|
-
var VERIFICATION_OUTCOMES, int, withSuiteFailures, READERS, SUPPORTED_RUNNERS, COVERAGE_TABLE, EXEC_FAILURE, SUITE_REPORTED_FAILURE, MISSING_SYSTEM_DEPENDENCY, OFFLINE_BUILD_GAP, LIMIT_OWNER;
|
|
26384
|
+
var VERIFICATION_OUTCOMES, int, withSuiteFailures, countLines, READERS, SUPPORTED_RUNNERS, COVERAGE_TABLE, EXEC_FAILURE, SUITE_REPORTED_FAILURE, MISSING_SYSTEM_DEPENDENCY, OFFLINE_BUILD_GAP, LIMIT_OWNER;
|
|
26375
26385
|
var init_classify2 = __esm({
|
|
26376
26386
|
"../../packages/envrun/dist/classify.js"() {
|
|
26377
26387
|
"use strict";
|
|
@@ -26390,6 +26400,7 @@ var init_classify2 = __esm({
|
|
|
26390
26400
|
return counts;
|
|
26391
26401
|
return { ...counts, tests_failed: int(/(\d+) failed/.exec(suiteLine[1])) };
|
|
26392
26402
|
};
|
|
26403
|
+
countLines = (out, re) => (out.match(re) ?? []).length;
|
|
26393
26404
|
READERS = [
|
|
26394
26405
|
{
|
|
26395
26406
|
// `node --test` TAP. Anchored to line start, which is what makes it a
|
|
@@ -26397,11 +26408,14 @@ var init_classify2 = __esm({
|
|
|
26397
26408
|
runner: "node --test (TAP)",
|
|
26398
26409
|
read: (out) => {
|
|
26399
26410
|
const pass = /^# pass (\d+)$/m.exec(out);
|
|
26400
|
-
const
|
|
26401
|
-
if (!pass || !
|
|
26411
|
+
const fail3 = /^# fail (\d+)$/m.exec(out);
|
|
26412
|
+
if (!pass || !fail3)
|
|
26402
26413
|
return null;
|
|
26403
|
-
return { tests_passed: int(pass), tests_failed: int(
|
|
26404
|
-
}
|
|
26414
|
+
return { tests_passed: int(pass), tests_failed: int(fail3) };
|
|
26415
|
+
},
|
|
26416
|
+
// One `# pass N` per invocation, so a script that runs `node --test` once per file
|
|
26417
|
+
// prints many. envrun's own offline leg printed 67 and was read as the first one's 4.
|
|
26418
|
+
summaries: (out) => countLines(out, /^# pass \d+$/gm)
|
|
26405
26419
|
},
|
|
26406
26420
|
{
|
|
26407
26421
|
// node-tap's runner summary, which is NOT the `# pass N` / `# fail N` pair
|
|
@@ -26428,7 +26442,8 @@ var init_classify2 = __esm({
|
|
|
26428
26442
|
tests_passed: int(/\bpass: (\d+)/.exec(line[1])),
|
|
26429
26443
|
tests_failed: int(/\bfail: (\d+)/.exec(line[1]))
|
|
26430
26444
|
};
|
|
26431
|
-
}
|
|
26445
|
+
},
|
|
26446
|
+
summaries: (out) => countLines(out, /^# \{ total: \d+[^}\n]* \}$/gm)
|
|
26432
26447
|
},
|
|
26433
26448
|
{
|
|
26434
26449
|
// jest: `Tests: 1 failed, 2 passed, 3 total`
|
|
@@ -26441,7 +26456,8 @@ var init_classify2 = __esm({
|
|
|
26441
26456
|
tests_passed: int(/(\d+) passed/.exec(line[1])),
|
|
26442
26457
|
tests_failed: int(/(\d+) failed/.exec(line[1]))
|
|
26443
26458
|
}, /^Test Suites:\s+(.+?)\s*$/m.exec(out));
|
|
26444
|
-
}
|
|
26459
|
+
},
|
|
26460
|
+
summaries: (out) => countLines(out, /^Tests:\s+[^\n]*\btotal\b/gm)
|
|
26445
26461
|
},
|
|
26446
26462
|
{
|
|
26447
26463
|
// vitest: `Tests 3 passed (3)` / `Tests 1 failed | 2 passed (3)`
|
|
@@ -26458,7 +26474,12 @@ var init_classify2 = __esm({
|
|
|
26458
26474
|
if (!passed && !failed)
|
|
26459
26475
|
return null;
|
|
26460
26476
|
return withSuiteFailures({ tests_passed: int(passed), tests_failed: int(failed) }, files);
|
|
26461
|
-
}
|
|
26477
|
+
},
|
|
26478
|
+
// One invocation prints one `Test Files` line, and a numeric `Tests` line only when a
|
|
26479
|
+
// spec loaded (the every-spec-failed shape above has none). The larger of the two
|
|
26480
|
+
// counts invocations without counting a paired block twice; reading `Tests` alone
|
|
26481
|
+
// missed an all-failed run that followed a passing one (Codex review).
|
|
26482
|
+
summaries: (out) => Math.max(countLines(out, /^\s*Tests\s+[^\n]*\(\d+\)\s*$/gm), countLines(out, /^\s*Test Files\s+[^\n]*\(\d+\)\s*$/gm))
|
|
26462
26483
|
},
|
|
26463
26484
|
{
|
|
26464
26485
|
// pytest summary rule: `===== 297 passed in 0.54s =====`,
|
|
@@ -26481,7 +26502,10 @@ var init_classify2 = __esm({
|
|
|
26481
26502
|
if (!passed && !failed && !errors)
|
|
26482
26503
|
return null;
|
|
26483
26504
|
return { tests_passed: int(passed), tests_failed: int(failed) + int(errors) };
|
|
26484
|
-
}
|
|
26505
|
+
},
|
|
26506
|
+
// Only FINAL tallies, which carry pytest's `in 0.54s`. The interim banners the reader
|
|
26507
|
+
// skips above carry no duration, and counting them would mark every red run partial.
|
|
26508
|
+
summaries: (out) => Math.max(1, countLines(out, /^=+ [^\n]*?(?:passed|failed|error|no tests ran)[^\n]*? in [\d.]+s[^\n]*? =+$/gm))
|
|
26485
26509
|
},
|
|
26486
26510
|
{
|
|
26487
26511
|
// Maven surefire's per-module summary (TERM-1122), measured against surefire
|
|
@@ -26543,7 +26567,8 @@ var init_classify2 = __esm({
|
|
|
26543
26567
|
return null;
|
|
26544
26568
|
const failed = Number(m[1]);
|
|
26545
26569
|
return { tests_passed: Number(m[2]) - failed, tests_failed: failed };
|
|
26546
|
-
}
|
|
26570
|
+
},
|
|
26571
|
+
summaries: (out) => Math.max(1, countLines(out, /^\d+% tests passed, \d+ tests? failed out of \d+$/gm))
|
|
26547
26572
|
},
|
|
26548
26573
|
{
|
|
26549
26574
|
// mocha: ` 440 passing (1s)` and ` 2 failing`
|
|
@@ -26554,7 +26579,8 @@ var init_classify2 = __esm({
|
|
|
26554
26579
|
if (!passing && !failing)
|
|
26555
26580
|
return null;
|
|
26556
26581
|
return { tests_passed: int(passing), tests_failed: int(failing) };
|
|
26557
|
-
}
|
|
26582
|
+
},
|
|
26583
|
+
summaries: (out) => Math.max(countLines(out, /^\s*\d+ passing\b/gm), countLines(out, /^\s*\d+ failing\b/gm))
|
|
26558
26584
|
},
|
|
26559
26585
|
{
|
|
26560
26586
|
// ava: ` 3 tests passed` / ` 1 test failed`
|
|
@@ -26565,7 +26591,8 @@ var init_classify2 = __esm({
|
|
|
26565
26591
|
if (!passed && !failed)
|
|
26566
26592
|
return null;
|
|
26567
26593
|
return { tests_passed: int(passed), tests_failed: int(failed) };
|
|
26568
|
-
}
|
|
26594
|
+
},
|
|
26595
|
+
summaries: (out) => Math.max(countLines(out, /^\s*\d+ tests? passed\b/gm), countLines(out, /^\s*\d+ tests? failed\b/gm))
|
|
26569
26596
|
},
|
|
26570
26597
|
{
|
|
26571
26598
|
// uvu: a `Total:` / `Passed:` / `Skipped:` block.
|
|
@@ -26578,7 +26605,8 @@ var init_classify2 = __esm({
|
|
|
26578
26605
|
const t = int(total);
|
|
26579
26606
|
const p = int(passed);
|
|
26580
26607
|
return { tests_passed: p, tests_failed: Math.max(0, t - p) };
|
|
26581
|
-
}
|
|
26608
|
+
},
|
|
26609
|
+
summaries: (out) => countLines(out, /^\s*Total:\s+\d+/gm)
|
|
26582
26610
|
},
|
|
26583
26611
|
{
|
|
26584
26612
|
/**
|
|
@@ -26613,6 +26641,7 @@ var init_classify2 = __esm({
|
|
|
26613
26641
|
* those would double them against their parent.
|
|
26614
26642
|
*/
|
|
26615
26643
|
runner: "bare TAP",
|
|
26644
|
+
shadowedBy: ["node --test (TAP)", "node-tap"],
|
|
26616
26645
|
read: (out) => {
|
|
26617
26646
|
let n = 0;
|
|
26618
26647
|
let failed = 0;
|
|
@@ -29786,6 +29815,17 @@ var init_dsse = __esm({
|
|
|
29786
29815
|
}
|
|
29787
29816
|
});
|
|
29788
29817
|
|
|
29818
|
+
// ../../packages/attest/dist/dispatchedRun.js
|
|
29819
|
+
import { createHash as createHash7, createPublicKey as createPublicKey2 } from "crypto";
|
|
29820
|
+
var init_dispatchedRun = __esm({
|
|
29821
|
+
"../../packages/attest/dist/dispatchedRun.js"() {
|
|
29822
|
+
"use strict";
|
|
29823
|
+
init_keys();
|
|
29824
|
+
init_pae();
|
|
29825
|
+
init_types4();
|
|
29826
|
+
}
|
|
29827
|
+
});
|
|
29828
|
+
|
|
29789
29829
|
// ../../packages/attest/dist/sealedbox.js
|
|
29790
29830
|
import { createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3, diffieHellman, generateKeyPairSync as generateKeyPairSync2, hkdfSync as hkdfSync2, randomBytes as randomBytes9 } from "crypto";
|
|
29791
29831
|
var init_sealedbox = __esm({
|
|
@@ -29832,7 +29872,7 @@ var init_nonce = __esm({
|
|
|
29832
29872
|
});
|
|
29833
29873
|
|
|
29834
29874
|
// ../../packages/attest/dist/gceIdentity.js
|
|
29835
|
-
import { createHash as
|
|
29875
|
+
import { createHash as createHash8, createPublicKey as createPublicKey3, verify as cryptoVerify2 } from "crypto";
|
|
29836
29876
|
function expectedAudience(dispatchId) {
|
|
29837
29877
|
if (dispatchId === "")
|
|
29838
29878
|
throw new TypeError("expectedAudience: dispatchId is empty");
|
|
@@ -29917,7 +29957,7 @@ async function verifyGceIdentityToken(token, expectations, deps) {
|
|
|
29917
29957
|
}
|
|
29918
29958
|
let signatureValid = false;
|
|
29919
29959
|
try {
|
|
29920
|
-
const publicKey =
|
|
29960
|
+
const publicKey = createPublicKey3({ key: jwk, format: "jwk" });
|
|
29921
29961
|
signatureValid = cryptoVerify2("sha256", Buffer.from(`${headerB64}.${payloadB64}`, "utf8"), publicKey, signature);
|
|
29922
29962
|
} catch {
|
|
29923
29963
|
signatureValid = false;
|
|
@@ -29978,7 +30018,7 @@ async function verifyGceIdentityToken(token, expectations, deps) {
|
|
|
29978
30018
|
email: expectations.serviceAccountEmail,
|
|
29979
30019
|
iat,
|
|
29980
30020
|
exp,
|
|
29981
|
-
tokenSha256:
|
|
30021
|
+
tokenSha256: createHash8("sha256").update(token, "utf8").digest("hex")
|
|
29982
30022
|
}
|
|
29983
30023
|
};
|
|
29984
30024
|
}
|
|
@@ -29993,7 +30033,7 @@ var init_gceIdentity = __esm({
|
|
|
29993
30033
|
});
|
|
29994
30034
|
|
|
29995
30035
|
// ../../packages/attest/dist/confidentialSpace.js
|
|
29996
|
-
import { createHash as
|
|
30036
|
+
import { createHash as createHash9, createPublicKey as createPublicKey4, verify as cryptoVerify3 } from "crypto";
|
|
29997
30037
|
function refuse2(reason, detail) {
|
|
29998
30038
|
return { ok: false, reason, detail };
|
|
29999
30039
|
}
|
|
@@ -30067,7 +30107,7 @@ async function verifyConfidentialSpaceToken(token, expectations, deps) {
|
|
|
30067
30107
|
}
|
|
30068
30108
|
let signatureValid = false;
|
|
30069
30109
|
try {
|
|
30070
|
-
const publicKey =
|
|
30110
|
+
const publicKey = createPublicKey4({ key: jwk, format: "jwk" });
|
|
30071
30111
|
signatureValid = cryptoVerify3("sha256", Buffer.from(`${headerB64}.${payloadB64}`, "utf8"), publicKey, signature);
|
|
30072
30112
|
} catch {
|
|
30073
30113
|
signatureValid = false;
|
|
@@ -30167,7 +30207,7 @@ async function verifyConfidentialSpaceToken(token, expectations, deps) {
|
|
|
30167
30207
|
hwmodel,
|
|
30168
30208
|
iat,
|
|
30169
30209
|
exp,
|
|
30170
|
-
tokenSha256:
|
|
30210
|
+
tokenSha256: createHash9("sha256").update(token, "utf8").digest("hex")
|
|
30171
30211
|
}
|
|
30172
30212
|
};
|
|
30173
30213
|
}
|
|
@@ -30211,6 +30251,7 @@ var init_dist2 = __esm({
|
|
|
30211
30251
|
init_pae();
|
|
30212
30252
|
init_keys();
|
|
30213
30253
|
init_dsse();
|
|
30254
|
+
init_dispatchedRun();
|
|
30214
30255
|
init_sealedbox();
|
|
30215
30256
|
init_aead();
|
|
30216
30257
|
init_manifest();
|
|
@@ -30358,9 +30399,9 @@ function renderVerdictLine(r) {
|
|
|
30358
30399
|
const t = fmtMs(r.wallMs);
|
|
30359
30400
|
switch (r.outcome) {
|
|
30360
30401
|
case "completed":
|
|
30361
|
-
return `GREEN ${String(r.counts?.tests_passed ?? 0)} test(s) passed, none failed
|
|
30402
|
+
return `GREEN ${String(r.counts?.tests_passed ?? 0)} test(s) passed, none failed ` + (r.counts?.coverage === "partial" ? `(${PARTIAL_COUNTS_NOTE}) ` : "") + `\u2014 ${t}${r.preview ? ` \u2014 ${r.preview.url}` : ""}`;
|
|
30362
30403
|
case "tests-failed":
|
|
30363
|
-
return `RED ${r.counts ? `${String(r.counts.tests_failed)} test(s) failed` : `exit ${String(r.exitCode)}`} \u2014 ${t}${r.preview ? ` \u2014 ${r.preview.url}` : ""}`;
|
|
30404
|
+
return `RED ${r.counts ? `${String(r.counts.tests_failed)} test(s) failed` : `exit ${String(r.exitCode)}`}` + (r.counts?.coverage === "partial" ? ` (${PARTIAL_COUNTS_NOTE})` : "") + ` \u2014 ${t}${r.preview ? ` \u2014 ${r.preview.url}` : ""}`;
|
|
30364
30405
|
case "no-tests-observed":
|
|
30365
30406
|
return `NOTHING RAN the suite reported zero tests passed and zero failed \u2014 ${t}`;
|
|
30366
30407
|
case "counts-unparsed":
|
|
@@ -30414,7 +30455,7 @@ function exitCodeFor(r) {
|
|
|
30414
30455
|
return 2;
|
|
30415
30456
|
return exitCodeForOutcome(r.outcome);
|
|
30416
30457
|
}
|
|
30417
|
-
var RUN_TEST_COMMAND_SOURCES, RUN_IMAGE_SOURCES, RUN_RESULT_SCHEMA, RUN_RESULT_FIELDS, RENDER_NONE, FIELD_VIEWS;
|
|
30458
|
+
var RUN_TEST_COMMAND_SOURCES, RUN_IMAGE_SOURCES, RUN_RESULT_SCHEMA, PARTIAL_COUNTS_NOTE, RUN_RESULT_FIELDS, RENDER_NONE, FIELD_VIEWS;
|
|
30418
30459
|
var init_result = __esm({
|
|
30419
30460
|
"../../packages/envrun/dist/result.js"() {
|
|
30420
30461
|
"use strict";
|
|
@@ -30437,6 +30478,7 @@ var init_result = __esm({
|
|
|
30437
30478
|
"founder-declared"
|
|
30438
30479
|
];
|
|
30439
30480
|
RUN_RESULT_SCHEMA = "terminalhire.verification-run/1";
|
|
30481
|
+
PARTIAL_COUNTS_NOTE = "from one of several test summaries in the output, so not a total for the whole run";
|
|
30440
30482
|
RUN_RESULT_FIELDS = [
|
|
30441
30483
|
"schema",
|
|
30442
30484
|
"runId",
|
|
@@ -30482,7 +30524,7 @@ var init_result = __esm({
|
|
|
30482
30524
|
testCommand: (r) => r.testCommand === null ? null : `test command ${r.testCommand}`,
|
|
30483
30525
|
testOutputTail: RENDER_NONE,
|
|
30484
30526
|
// printed as a block below the fields, when red
|
|
30485
|
-
counts: (r) => r.counts === null ? null : `tests ${String(r.counts.tests_passed)} passed, ${String(r.counts.tests_failed)} failed (${r.counts.runner})
|
|
30527
|
+
counts: (r) => r.counts === null ? null : `tests ${String(r.counts.tests_passed)} passed, ${String(r.counts.tests_failed)} failed (${r.counts.runner})` + (r.counts.coverage === "partial" ? ` \u2014 ${PARTIAL_COUNTS_NOTE}` : ""),
|
|
30486
30528
|
wallMs: (r) => `round trip ${fmtMs(r.wallMs)}`,
|
|
30487
30529
|
installMs: (r) => r.installMs === void 0 ? null : `install step ${fmtMs(r.installMs)}`,
|
|
30488
30530
|
testMs: (r) => r.testMs === void 0 ? null : `test step ${fmtMs(r.testMs)}`,
|
|
@@ -30533,7 +30575,7 @@ var init_result = __esm({
|
|
|
30533
30575
|
});
|
|
30534
30576
|
|
|
30535
30577
|
// ../../packages/envrun/dist/attestation.js
|
|
30536
|
-
import { createHash as
|
|
30578
|
+
import { createHash as createHash10, randomBytes as randomBytes11 } from "crypto";
|
|
30537
30579
|
function contradicts(outcome, counts, exitCode) {
|
|
30538
30580
|
const budget = OUTCOME_TO_BUDGET[outcome];
|
|
30539
30581
|
if (budget === null)
|
|
@@ -30575,13 +30617,17 @@ function imageDigestOf(ref) {
|
|
|
30575
30617
|
return at === -1 ? null : ref.slice(at + 1);
|
|
30576
30618
|
}
|
|
30577
30619
|
function sha256Hex(data) {
|
|
30578
|
-
return
|
|
30620
|
+
return createHash10("sha256").update(typeof data === "string" ? Buffer.from(data, "utf8") : data).digest("hex");
|
|
30579
30621
|
}
|
|
30580
30622
|
function toTestRunResult(result, outputSha256) {
|
|
30581
30623
|
return {
|
|
30582
30624
|
exit_code: result.exitCode ?? -1,
|
|
30583
30625
|
...outputSha256 === void 0 ? {} : { output_sha256: outputSha256 },
|
|
30584
|
-
...result.counts === null ? {} : {
|
|
30626
|
+
...result.counts === null ? {} : {
|
|
30627
|
+
tests_passed: result.counts.tests_passed,
|
|
30628
|
+
tests_failed: result.counts.tests_failed,
|
|
30629
|
+
...result.counts.coverage === "partial" ? { tests_coverage: "partial" } : {}
|
|
30630
|
+
}
|
|
30585
30631
|
};
|
|
30586
30632
|
}
|
|
30587
30633
|
function toAcceptancePredicate(pair, opts = {}) {
|
|
@@ -30866,7 +30912,382 @@ var init_attestation2 = __esm({
|
|
|
30866
30912
|
}
|
|
30867
30913
|
});
|
|
30868
30914
|
|
|
30915
|
+
// ../../packages/envrun/dist/lockfileCheck.js
|
|
30916
|
+
function supportedLockfile(basename7) {
|
|
30917
|
+
return basename7 === "package-lock.json" || basename7 === "yarn.lock" ? basename7 : null;
|
|
30918
|
+
}
|
|
30919
|
+
function fail2(detail) {
|
|
30920
|
+
return { ok: false, detail };
|
|
30921
|
+
}
|
|
30922
|
+
function tarballBasename(name) {
|
|
30923
|
+
return name.startsWith("@") ? name.slice(name.indexOf("/") + 1) : name;
|
|
30924
|
+
}
|
|
30925
|
+
function resolvedProblem(label, path5, name, version, resolved) {
|
|
30926
|
+
if (typeof resolved !== "string") {
|
|
30927
|
+
return `\`${label}\` in ${path5} records no download address, so where it installs from could not be checked.`;
|
|
30928
|
+
}
|
|
30929
|
+
const tail2 = `${name}/-/${tarballBasename(name)}-${version}.tgz`;
|
|
30930
|
+
if (REGISTRY_ORIGINS.some((origin) => resolved === origin + tail2))
|
|
30931
|
+
return null;
|
|
30932
|
+
if (REGISTRY_ORIGINS.some((origin) => resolved.startsWith(origin))) {
|
|
30933
|
+
return `\`${label}\` in ${path5} resolves to a registry tarball for a different package or version than the entry records.`;
|
|
30934
|
+
}
|
|
30935
|
+
return `\`${label}\` in ${path5} resolves outside the npm registry. ${RULE}`;
|
|
30936
|
+
}
|
|
30937
|
+
function integrityProblem(label, path5, integrity, sha1Only) {
|
|
30938
|
+
if (typeof integrity === "string") {
|
|
30939
|
+
const tokens = integrity.split(" ");
|
|
30940
|
+
if (tokens.length > 0 && tokens.every((t) => SRI_TOKEN.test(t)))
|
|
30941
|
+
return null;
|
|
30942
|
+
return `\`${label}\` in ${path5} has an integrity field we could not read as a sha256, sha384 or sha512 hash.`;
|
|
30943
|
+
}
|
|
30944
|
+
const strength = "A change is accepted only when every new or changed entry records a sha256, sha384 or sha512 integrity hash.";
|
|
30945
|
+
return sha1Only ? `\`${label}\` in ${path5} carries only a SHA-1 checksum and no integrity field. ${strength}` : `\`${label}\` in ${path5} has no integrity field. ${strength}`;
|
|
30946
|
+
}
|
|
30947
|
+
function checkLockfileChange(input) {
|
|
30948
|
+
const kind = supportedLockfile(input.path.split("/").pop() ?? "");
|
|
30949
|
+
if (kind === "yarn.lock")
|
|
30950
|
+
return checkYarn(input);
|
|
30951
|
+
if (kind === "package-lock.json")
|
|
30952
|
+
return checkNpm(input);
|
|
30953
|
+
return fail2(`${input.path} is not a lockfile this check can read.`);
|
|
30954
|
+
}
|
|
30955
|
+
function isObject(value) {
|
|
30956
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
30957
|
+
}
|
|
30958
|
+
function sameJson(a, b) {
|
|
30959
|
+
if (a === b)
|
|
30960
|
+
return true;
|
|
30961
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
30962
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
|
|
30963
|
+
return false;
|
|
30964
|
+
return a.every((x, i) => sameJson(x, b[i]));
|
|
30965
|
+
}
|
|
30966
|
+
if (!isObject(a) || !isObject(b))
|
|
30967
|
+
return false;
|
|
30968
|
+
const keys = Object.keys(a);
|
|
30969
|
+
if (keys.length !== Object.keys(b).length)
|
|
30970
|
+
return false;
|
|
30971
|
+
return keys.every((k) => Object.prototype.hasOwnProperty.call(b, k) && sameJson(a[k], b[k]));
|
|
30972
|
+
}
|
|
30973
|
+
function parseObject(text) {
|
|
30974
|
+
try {
|
|
30975
|
+
const value = JSON.parse(text);
|
|
30976
|
+
return isObject(value) ? value : null;
|
|
30977
|
+
} catch {
|
|
30978
|
+
return null;
|
|
30979
|
+
}
|
|
30980
|
+
}
|
|
30981
|
+
function checkNpm({ path: path5, base, post }) {
|
|
30982
|
+
const before = parseObject(base);
|
|
30983
|
+
const after = parseObject(post);
|
|
30984
|
+
if (before === null || after === null) {
|
|
30985
|
+
return fail2(`${path5} could not be read as JSON, so its entries could not be checked.`);
|
|
30986
|
+
}
|
|
30987
|
+
for (const doc of [before, after]) {
|
|
30988
|
+
const version = doc["lockfileVersion"];
|
|
30989
|
+
if (version === 1) {
|
|
30990
|
+
return fail2(`${path5} is an npm lockfile at lockfileVersion 1. Only versions 2 and 3 record every package in a form we can check, so a change to it is refused.`);
|
|
30991
|
+
}
|
|
30992
|
+
if (version !== 2 && version !== 3) {
|
|
30993
|
+
return fail2(`${path5} does not declare lockfileVersion 2 or 3, so its entries could not be checked.`);
|
|
30994
|
+
}
|
|
30995
|
+
}
|
|
30996
|
+
for (const key of /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])) {
|
|
30997
|
+
if (!NPM_TOP_LEVEL.has(key) && !sameJson(before[key], after[key])) {
|
|
30998
|
+
return fail2(`${path5} changes its top-level "${key}" field, which is not a package entry this check can read.`);
|
|
30999
|
+
}
|
|
31000
|
+
}
|
|
31001
|
+
const beforePackages = before["packages"];
|
|
31002
|
+
const afterPackages = after["packages"];
|
|
31003
|
+
if (!isObject(beforePackages) || !isObject(afterPackages)) {
|
|
31004
|
+
return fail2(`${path5} has no "packages" section, so its entries could not be checked.`);
|
|
31005
|
+
}
|
|
31006
|
+
let judged = 0;
|
|
31007
|
+
for (const [key, entry] of Object.entries(afterPackages)) {
|
|
31008
|
+
if (Object.prototype.hasOwnProperty.call(beforePackages, key) && sameJson(beforePackages[key], entry)) {
|
|
31009
|
+
continue;
|
|
31010
|
+
}
|
|
31011
|
+
judged += 1;
|
|
31012
|
+
if (!isObject(entry))
|
|
31013
|
+
return fail2(`${path5} has an entry "${key}" that is not an object.`);
|
|
31014
|
+
if (key === "") {
|
|
31015
|
+
if ("resolved" in entry || "link" in entry) {
|
|
31016
|
+
return fail2(`${path5} gives the root project a download address or a link, which this check does not accept.`);
|
|
31017
|
+
}
|
|
31018
|
+
continue;
|
|
31019
|
+
}
|
|
31020
|
+
const problem = npmPackageProblem(path5, key, entry);
|
|
31021
|
+
if (problem !== null)
|
|
31022
|
+
return fail2(problem);
|
|
31023
|
+
}
|
|
31024
|
+
const legacy = after["dependencies"];
|
|
31025
|
+
if (legacy !== void 0) {
|
|
31026
|
+
const problem = npmLegacyProblem(path5, before["dependencies"], legacy);
|
|
31027
|
+
if (problem !== null)
|
|
31028
|
+
return fail2(problem);
|
|
31029
|
+
}
|
|
31030
|
+
return { ok: true, judged };
|
|
31031
|
+
}
|
|
31032
|
+
function npmPackageProblem(path5, key, entry) {
|
|
31033
|
+
const match2 = NPM_INSTALL_KEY.exec(key);
|
|
31034
|
+
const name = match2?.[1];
|
|
31035
|
+
if (name === void 0 || !PACKAGE_NAME.test(name)) {
|
|
31036
|
+
return `${path5} changes the entry "${key}", which is a workspace or local path rather than a package from the registry, so the change is refused.`;
|
|
31037
|
+
}
|
|
31038
|
+
if (entry["link"] !== void 0) {
|
|
31039
|
+
return `${path5} changes "${name}" into a link to a local directory, which this check does not accept.`;
|
|
31040
|
+
}
|
|
31041
|
+
if (entry["inBundle"] !== void 0) {
|
|
31042
|
+
return `${path5} changes "${name}", which comes bundled inside another package and so records no download address to check.`;
|
|
31043
|
+
}
|
|
31044
|
+
if (entry["name"] !== void 0 && entry["name"] !== name) {
|
|
31045
|
+
return `${path5} installs a package under the alias "${name}", which this check does not accept.`;
|
|
31046
|
+
}
|
|
31047
|
+
const version = entry["version"];
|
|
31048
|
+
if (typeof version !== "string" || !VERSION2.test(version)) {
|
|
31049
|
+
return `${path5} records "${name}" with no published version we can check.`;
|
|
31050
|
+
}
|
|
31051
|
+
const label = `${name}@${version}`;
|
|
31052
|
+
return resolvedProblem(label, path5, name, version, entry["resolved"]) ?? integrityProblem(label, path5, entry["integrity"], false);
|
|
31053
|
+
}
|
|
31054
|
+
function withoutNested(entry) {
|
|
31055
|
+
const copy = { ...entry };
|
|
31056
|
+
delete copy["dependencies"];
|
|
31057
|
+
return copy;
|
|
31058
|
+
}
|
|
31059
|
+
function npmLegacyProblem(path5, before, after) {
|
|
31060
|
+
if (!isObject(after))
|
|
31061
|
+
return `${path5} has a "dependencies" section that is not an object.`;
|
|
31062
|
+
const previous = isObject(before) ? before : {};
|
|
31063
|
+
for (const [name, entry] of Object.entries(after)) {
|
|
31064
|
+
if (!isObject(entry))
|
|
31065
|
+
return `${path5} has a "dependencies" entry "${name}" that is not an object.`;
|
|
31066
|
+
const old = previous[name];
|
|
31067
|
+
const unchanged = isObject(old) && sameJson(withoutNested(old), withoutNested(entry));
|
|
31068
|
+
if (!unchanged) {
|
|
31069
|
+
if (!PACKAGE_NAME.test(name))
|
|
31070
|
+
return `${path5} names a dependency "${name}" we could not read.`;
|
|
31071
|
+
if (entry["bundled"] !== void 0) {
|
|
31072
|
+
return `${path5} changes "${name}", which comes bundled inside another package and so records no download address to check.`;
|
|
31073
|
+
}
|
|
31074
|
+
const version = entry["version"];
|
|
31075
|
+
if (typeof version !== "string" || !VERSION2.test(version)) {
|
|
31076
|
+
return `${path5} records "${name}" with no published version we can check.`;
|
|
31077
|
+
}
|
|
31078
|
+
const label = `${name}@${version}`;
|
|
31079
|
+
const problem = resolvedProblem(label, path5, name, version, entry["resolved"]) ?? integrityProblem(label, path5, entry["integrity"], false);
|
|
31080
|
+
if (problem !== null)
|
|
31081
|
+
return problem;
|
|
31082
|
+
}
|
|
31083
|
+
const nested = entry["dependencies"];
|
|
31084
|
+
if (nested !== void 0) {
|
|
31085
|
+
const problem = npmLegacyProblem(path5, isObject(old) ? old["dependencies"] : void 0, nested);
|
|
31086
|
+
if (problem !== null)
|
|
31087
|
+
return problem;
|
|
31088
|
+
}
|
|
31089
|
+
}
|
|
31090
|
+
return null;
|
|
31091
|
+
}
|
|
31092
|
+
function unquote(value) {
|
|
31093
|
+
if (value.startsWith('"')) {
|
|
31094
|
+
try {
|
|
31095
|
+
const parsed = JSON.parse(value);
|
|
31096
|
+
return typeof parsed === "string" ? parsed : null;
|
|
31097
|
+
} catch {
|
|
31098
|
+
return null;
|
|
31099
|
+
}
|
|
31100
|
+
}
|
|
31101
|
+
return BARE_WORD.test(value) ? value : null;
|
|
31102
|
+
}
|
|
31103
|
+
function leadingPart(text) {
|
|
31104
|
+
if (text.startsWith('"')) {
|
|
31105
|
+
for (let i = 1; i < text.length; i += 1) {
|
|
31106
|
+
if (text[i] === "\\") {
|
|
31107
|
+
i += 1;
|
|
31108
|
+
continue;
|
|
31109
|
+
}
|
|
31110
|
+
if (text[i] === '"') {
|
|
31111
|
+
const value = unquote(text.slice(0, i + 1));
|
|
31112
|
+
return value === null ? null : { value, rest: text.slice(i + 1) };
|
|
31113
|
+
}
|
|
31114
|
+
}
|
|
31115
|
+
return null;
|
|
31116
|
+
}
|
|
31117
|
+
const bare = /^[^\s"]+/.exec(text)?.[0];
|
|
31118
|
+
if (bare === void 0 || !BARE_WORD.test(bare))
|
|
31119
|
+
return null;
|
|
31120
|
+
return { value: bare, rest: text.slice(bare.length) };
|
|
31121
|
+
}
|
|
31122
|
+
function mapLine(text) {
|
|
31123
|
+
const key = leadingPart(text);
|
|
31124
|
+
if (key === null || !key.rest.startsWith(" "))
|
|
31125
|
+
return null;
|
|
31126
|
+
const value = leadingPart(key.rest.slice(1));
|
|
31127
|
+
if (value === null || value.rest !== "")
|
|
31128
|
+
return null;
|
|
31129
|
+
return { key: key.value, value: value.value };
|
|
31130
|
+
}
|
|
31131
|
+
function parseYarn(path5, text, when) {
|
|
31132
|
+
const lines = text.split("\n");
|
|
31133
|
+
if (text.includes("\r")) {
|
|
31134
|
+
return { problem: `${path5} ${when} uses carriage returns, which this check does not read.` };
|
|
31135
|
+
}
|
|
31136
|
+
if (lines.some((l) => l === "__metadata:")) {
|
|
31137
|
+
return {
|
|
31138
|
+
problem: `${path5} ${when} is a lockfile from Yarn 2 or later. Only yarn v1 lockfiles are checked, so a change to it is refused.`
|
|
31139
|
+
};
|
|
31140
|
+
}
|
|
31141
|
+
if (!lines.some((l) => l === "# yarn lockfile v1")) {
|
|
31142
|
+
return {
|
|
31143
|
+
problem: `${path5} ${when} is not a yarn v1 lockfile, so its entries could not be checked.`
|
|
31144
|
+
};
|
|
31145
|
+
}
|
|
31146
|
+
const entries = /* @__PURE__ */ new Map();
|
|
31147
|
+
let header = null;
|
|
31148
|
+
let block = [];
|
|
31149
|
+
let fields = /* @__PURE__ */ new Map();
|
|
31150
|
+
let inMap = false;
|
|
31151
|
+
const close = () => {
|
|
31152
|
+
if (header === null)
|
|
31153
|
+
return null;
|
|
31154
|
+
if (entries.has(header))
|
|
31155
|
+
return `${path5} ${when} lists the entry ${header} twice.`;
|
|
31156
|
+
entries.set(header, { raw: block.join("\n"), fields });
|
|
31157
|
+
header = null;
|
|
31158
|
+
return null;
|
|
31159
|
+
};
|
|
31160
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
31161
|
+
const line = lines[i] ?? "";
|
|
31162
|
+
const unreadable = `${path5} ${when} could not be read at line ${String(i + 1)}, so its entries could not be checked.`;
|
|
31163
|
+
if (line === "") {
|
|
31164
|
+
const problem2 = close();
|
|
31165
|
+
if (problem2 !== null)
|
|
31166
|
+
return { problem: problem2 };
|
|
31167
|
+
continue;
|
|
31168
|
+
}
|
|
31169
|
+
if (header === null && line.startsWith("#"))
|
|
31170
|
+
continue;
|
|
31171
|
+
if (!line.startsWith(" ")) {
|
|
31172
|
+
const problem2 = close();
|
|
31173
|
+
if (problem2 !== null)
|
|
31174
|
+
return { problem: problem2 };
|
|
31175
|
+
if (!line.endsWith(":") || line.length < 2)
|
|
31176
|
+
return { problem: unreadable };
|
|
31177
|
+
header = line.slice(0, -1);
|
|
31178
|
+
block = [line];
|
|
31179
|
+
fields = /* @__PURE__ */ new Map();
|
|
31180
|
+
inMap = false;
|
|
31181
|
+
continue;
|
|
31182
|
+
}
|
|
31183
|
+
if (header === null)
|
|
31184
|
+
return { problem: unreadable };
|
|
31185
|
+
block.push(line);
|
|
31186
|
+
if (line.startsWith(" ")) {
|
|
31187
|
+
if (!inMap || mapLine(line.slice(4)) === null)
|
|
31188
|
+
return { problem: unreadable };
|
|
31189
|
+
continue;
|
|
31190
|
+
}
|
|
31191
|
+
const field = /^ {2}([A-Za-z]+)(?: (.+)|:)$/.exec(line);
|
|
31192
|
+
const key = field?.[1];
|
|
31193
|
+
if (field === null || key === void 0 || fields.has(key))
|
|
31194
|
+
return { problem: unreadable };
|
|
31195
|
+
if (field[2] === void 0) {
|
|
31196
|
+
if (!YARN_MAP_FIELDS.has(key))
|
|
31197
|
+
return { problem: unreadable };
|
|
31198
|
+
fields.set(key, "");
|
|
31199
|
+
inMap = true;
|
|
31200
|
+
continue;
|
|
31201
|
+
}
|
|
31202
|
+
const value = unquote(field[2]);
|
|
31203
|
+
if (!YARN_VALUE_FIELDS.has(key) || value === null)
|
|
31204
|
+
return { problem: unreadable };
|
|
31205
|
+
fields.set(key, value);
|
|
31206
|
+
inMap = false;
|
|
31207
|
+
}
|
|
31208
|
+
const problem = close();
|
|
31209
|
+
if (problem !== null)
|
|
31210
|
+
return { problem };
|
|
31211
|
+
return { entries };
|
|
31212
|
+
}
|
|
31213
|
+
function headerName(header) {
|
|
31214
|
+
let name = null;
|
|
31215
|
+
for (const part of header.split(", ")) {
|
|
31216
|
+
const spec = unquote(part);
|
|
31217
|
+
if (spec === null)
|
|
31218
|
+
return null;
|
|
31219
|
+
const at = spec.lastIndexOf("@");
|
|
31220
|
+
if (at <= 0)
|
|
31221
|
+
return null;
|
|
31222
|
+
const candidate = spec.slice(0, at);
|
|
31223
|
+
const range = spec.slice(at + 1);
|
|
31224
|
+
if (!PACKAGE_NAME.test(candidate) || /[:/#\\]/.test(range))
|
|
31225
|
+
return null;
|
|
31226
|
+
if (name !== null && name !== candidate)
|
|
31227
|
+
return null;
|
|
31228
|
+
name = candidate;
|
|
31229
|
+
}
|
|
31230
|
+
return name;
|
|
31231
|
+
}
|
|
31232
|
+
function checkYarn({ path: path5, base, post }) {
|
|
31233
|
+
const before = parseYarn(path5, base, "as it was");
|
|
31234
|
+
if ("problem" in before)
|
|
31235
|
+
return fail2(before.problem);
|
|
31236
|
+
const after = parseYarn(path5, post, "after the change");
|
|
31237
|
+
if ("problem" in after)
|
|
31238
|
+
return fail2(after.problem);
|
|
31239
|
+
let judged = 0;
|
|
31240
|
+
for (const [header, entry] of after.entries) {
|
|
31241
|
+
if (before.entries.get(header)?.raw === entry.raw)
|
|
31242
|
+
continue;
|
|
31243
|
+
judged += 1;
|
|
31244
|
+
const name = headerName(header);
|
|
31245
|
+
if (name === null) {
|
|
31246
|
+
return fail2(`The entry ${header} in ${path5} is not a plain package range from the registry, so the change is refused.`);
|
|
31247
|
+
}
|
|
31248
|
+
const version = entry.fields.get("version");
|
|
31249
|
+
if (version === void 0 || !VERSION2.test(version)) {
|
|
31250
|
+
return fail2(`${path5} records ${name} with no published version we can check.`);
|
|
31251
|
+
}
|
|
31252
|
+
const label = `${name}@${version}`;
|
|
31253
|
+
const resolved = entry.fields.get("resolved");
|
|
31254
|
+
const fragment = resolved === void 0 ? null : /^(.*)#([0-9a-f]{40})$/.exec(resolved);
|
|
31255
|
+
const url = fragment?.[1] ?? resolved;
|
|
31256
|
+
const problem = resolvedProblem(label, path5, name, version, url) ?? integrityProblem(label, path5, entry.fields.get("integrity"), fragment !== null);
|
|
31257
|
+
if (problem !== null)
|
|
31258
|
+
return fail2(problem);
|
|
31259
|
+
}
|
|
31260
|
+
return { ok: true, judged };
|
|
31261
|
+
}
|
|
31262
|
+
var REGISTRY_ORIGINS, PACKAGE_NAME, VERSION2, SRI_TOKEN, RULE, NPM_TOP_LEVEL, NPM_INSTALL_KEY, YARN_VALUE_FIELDS, YARN_MAP_FIELDS, BARE_WORD;
|
|
31263
|
+
var init_lockfileCheck = __esm({
|
|
31264
|
+
"../../packages/envrun/dist/lockfileCheck.js"() {
|
|
31265
|
+
"use strict";
|
|
31266
|
+
REGISTRY_ORIGINS = ["https://registry.npmjs.org/", "https://registry.yarnpkg.com/"];
|
|
31267
|
+
PACKAGE_NAME = /^(?:@[A-Za-z0-9][A-Za-z0-9._~-]*\/)?[A-Za-z0-9][A-Za-z0-9._~-]*$/;
|
|
31268
|
+
VERSION2 = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
31269
|
+
SRI_TOKEN = /^sha(?:256|384|512)-[A-Za-z0-9+/]+={0,2}$/;
|
|
31270
|
+
RULE = "Only a tarball on registry.npmjs.org or registry.yarnpkg.com, for the exact name and version the entry records, is accepted.";
|
|
31271
|
+
NPM_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
31272
|
+
"name",
|
|
31273
|
+
"version",
|
|
31274
|
+
"lockfileVersion",
|
|
31275
|
+
"requires",
|
|
31276
|
+
"packages",
|
|
31277
|
+
"dependencies"
|
|
31278
|
+
]);
|
|
31279
|
+
NPM_INSTALL_KEY = /^(?:node_modules\/(?:@[^/]+\/)?[^/]+\/)*node_modules\/((?:@[^/]+\/)?[^/]+)$/;
|
|
31280
|
+
YARN_VALUE_FIELDS = /* @__PURE__ */ new Set(["version", "resolved", "integrity"]);
|
|
31281
|
+
YARN_MAP_FIELDS = /* @__PURE__ */ new Set(["dependencies", "optionalDependencies"]);
|
|
31282
|
+
BARE_WORD = /^(?!true|false)[A-Za-z/.-][^:\s,"]*$/;
|
|
31283
|
+
}
|
|
31284
|
+
});
|
|
31285
|
+
|
|
30869
31286
|
// ../../packages/envrun/dist/boundary.js
|
|
31287
|
+
function lockfileRefusal(base) {
|
|
31288
|
+
const kind = LOCKFILE_KINDS[base];
|
|
31289
|
+
return kind === void 0 ? "is a dependency lockfile of a kind we do not check. Only package-lock.json (lockfileVersion 2 or 3) and yarn.lock (v1) changes are checked entry by entry, so a change to it is refused" : `is ${kind}. Only package-lock.json (lockfileVersion 2 or 3) and yarn.lock (v1) changes are checked entry by entry, so a change to it is refused`;
|
|
31290
|
+
}
|
|
30870
31291
|
function unquoteDiffPath(token) {
|
|
30871
31292
|
if (!token.startsWith('"'))
|
|
30872
31293
|
return token === "" ? null : token;
|
|
@@ -30972,18 +31393,29 @@ function parsePatchPaths(patch) {
|
|
|
30972
31393
|
let oldRemaining = 0;
|
|
30973
31394
|
let newRemaining = 0;
|
|
30974
31395
|
let inHunk = false;
|
|
30975
|
-
const
|
|
31396
|
+
const fail3 = (detail) => ({ failure: { code: "unparseable", detail } });
|
|
30976
31397
|
for (let i = 0; i < lines.length; i += 1) {
|
|
30977
31398
|
const line = lines[i];
|
|
30978
31399
|
if (line === void 0)
|
|
30979
31400
|
continue;
|
|
30980
31401
|
if (line.startsWith(DIFF_HEADER)) {
|
|
30981
31402
|
if (inHunk && (oldRemaining > 0 || newRemaining > 0)) {
|
|
30982
|
-
return
|
|
31403
|
+
return fail3("a change block ended before it delivered the lines it announced");
|
|
30983
31404
|
}
|
|
30984
31405
|
inHunk = false;
|
|
30985
31406
|
skippingBinary = false;
|
|
30986
|
-
entry = {
|
|
31407
|
+
entry = {
|
|
31408
|
+
rawPaths: [],
|
|
31409
|
+
binary: false,
|
|
31410
|
+
headerOld: null,
|
|
31411
|
+
headerNew: null,
|
|
31412
|
+
plusPath: null,
|
|
31413
|
+
createsFile: false,
|
|
31414
|
+
deletesFile: false,
|
|
31415
|
+
moved: false,
|
|
31416
|
+
changed: false,
|
|
31417
|
+
modes: []
|
|
31418
|
+
};
|
|
30987
31419
|
entries.push(entry);
|
|
30988
31420
|
const split2 = splitDiffHeader(line.slice(DIFF_HEADER.length));
|
|
30989
31421
|
if (split2 !== null) {
|
|
@@ -30991,14 +31423,18 @@ function parsePatchPaths(patch) {
|
|
|
30991
31423
|
const left = unquoteDiffPath(leftToken);
|
|
30992
31424
|
const right = unquoteDiffPath(rightToken);
|
|
30993
31425
|
if (left === null || right === null) {
|
|
30994
|
-
return
|
|
31426
|
+
return fail3("a file name in the patch is quoted in a form we cannot read back");
|
|
30995
31427
|
}
|
|
30996
31428
|
const oldPath = stripSidePrefix(left, "a/");
|
|
30997
31429
|
const newPath = stripSidePrefix(right, "b/");
|
|
30998
31430
|
if (oldPath === null || newPath === null) {
|
|
30999
|
-
return
|
|
31431
|
+
return fail3("the patch was written without the standard a/ and b/ file-name prefixes, so we cannot tell a prefix from a real directory");
|
|
31000
31432
|
}
|
|
31001
31433
|
entry.rawPaths.push(oldPath, newPath);
|
|
31434
|
+
entry.headerOld = oldPath;
|
|
31435
|
+
entry.headerNew = newPath;
|
|
31436
|
+
if (oldPath !== newPath)
|
|
31437
|
+
entry.moved = true;
|
|
31002
31438
|
}
|
|
31003
31439
|
continue;
|
|
31004
31440
|
}
|
|
@@ -31015,13 +31451,15 @@ function parsePatchPaths(patch) {
|
|
|
31015
31451
|
newRemaining -= 1;
|
|
31016
31452
|
} else if (marker === "-") {
|
|
31017
31453
|
oldRemaining -= 1;
|
|
31454
|
+
entry.changed = true;
|
|
31018
31455
|
} else if (marker === "+") {
|
|
31019
31456
|
newRemaining -= 1;
|
|
31457
|
+
entry.changed = true;
|
|
31020
31458
|
} else {
|
|
31021
|
-
return
|
|
31459
|
+
return fail3("a change block contains a line that is neither an addition, a removal nor context");
|
|
31022
31460
|
}
|
|
31023
31461
|
if (oldRemaining < 0 || newRemaining < 0) {
|
|
31024
|
-
return
|
|
31462
|
+
return fail3("a change block delivered more lines than it announced");
|
|
31025
31463
|
}
|
|
31026
31464
|
if (oldRemaining === 0 && newRemaining === 0)
|
|
31027
31465
|
inHunk = false;
|
|
@@ -31032,13 +31470,20 @@ function parsePatchPaths(patch) {
|
|
|
31032
31470
|
if (line.startsWith("@@")) {
|
|
31033
31471
|
const m = HUNK_HEADER.exec(line);
|
|
31034
31472
|
if (m === null) {
|
|
31035
|
-
return
|
|
31473
|
+
return fail3("a change block header is malformed, so we cannot tell which lines belong to it");
|
|
31036
31474
|
}
|
|
31037
31475
|
oldRemaining = m[2] === void 0 ? 1 : Number(m[2]);
|
|
31038
31476
|
newRemaining = m[4] === void 0 ? 1 : Number(m[4]);
|
|
31039
31477
|
inHunk = oldRemaining > 0 || newRemaining > 0;
|
|
31040
31478
|
continue;
|
|
31041
31479
|
}
|
|
31480
|
+
if (line.startsWith("new file mode"))
|
|
31481
|
+
entry.createsFile = true;
|
|
31482
|
+
if (line.startsWith("deleted file mode"))
|
|
31483
|
+
entry.deletesFile = true;
|
|
31484
|
+
const mode = /^(?:old mode|new mode|new file mode|deleted file mode) (\d+)$/.exec(line)?.[1] ?? /^index [0-9a-f]+\.\.[0-9a-f]+ (\d+)$/.exec(line)?.[1];
|
|
31485
|
+
if (mode !== void 0)
|
|
31486
|
+
entry.modes.push(mode);
|
|
31042
31487
|
if (line === "GIT binary patch" || line.startsWith("Binary files ")) {
|
|
31043
31488
|
entry.binary = true;
|
|
31044
31489
|
skippingBinary = true;
|
|
@@ -31049,41 +31494,49 @@ function parsePatchPaths(patch) {
|
|
|
31049
31494
|
const rest = line.slice(4);
|
|
31050
31495
|
const tab = rest.indexOf(" ");
|
|
31051
31496
|
const token = tab === -1 ? rest : rest.slice(0, tab);
|
|
31052
|
-
if (token === "/dev/null")
|
|
31497
|
+
if (token === "/dev/null") {
|
|
31498
|
+
if (sideMarker === "a/")
|
|
31499
|
+
entry.createsFile = true;
|
|
31500
|
+
else
|
|
31501
|
+
entry.deletesFile = true;
|
|
31053
31502
|
continue;
|
|
31503
|
+
}
|
|
31054
31504
|
const decoded = unquoteDiffPath(token);
|
|
31055
31505
|
if (decoded === null) {
|
|
31056
|
-
return
|
|
31506
|
+
return fail3("a file name in the patch is quoted in a form we cannot read back");
|
|
31057
31507
|
}
|
|
31058
31508
|
const stripped = stripSidePrefix(decoded, sideMarker);
|
|
31059
31509
|
if (stripped === null) {
|
|
31060
|
-
return
|
|
31510
|
+
return fail3("the patch was written without the standard a/ and b/ file-name prefixes, so we cannot tell a prefix from a real directory");
|
|
31061
31511
|
}
|
|
31062
31512
|
entry.rawPaths.push(stripped);
|
|
31513
|
+
if (sideMarker === "b/")
|
|
31514
|
+
entry.plusPath = stripped;
|
|
31063
31515
|
continue;
|
|
31064
31516
|
}
|
|
31065
31517
|
const moveMarker = ["rename from ", "rename to ", "copy from ", "copy to "].find((p) => line.startsWith(p));
|
|
31066
31518
|
if (moveMarker !== void 0) {
|
|
31067
31519
|
const decoded = unquoteDiffPath(line.slice(moveMarker.length));
|
|
31068
31520
|
if (decoded === null) {
|
|
31069
|
-
return
|
|
31521
|
+
return fail3("a file name in the patch is quoted in a form we cannot read back");
|
|
31070
31522
|
}
|
|
31071
31523
|
entry.rawPaths.push(decoded);
|
|
31524
|
+
entry.moved = true;
|
|
31072
31525
|
continue;
|
|
31073
31526
|
}
|
|
31074
31527
|
if (line.startsWith("+") || line.startsWith("-")) {
|
|
31075
|
-
return
|
|
31528
|
+
return fail3("the patch contains a changed line that belongs to no change block");
|
|
31076
31529
|
}
|
|
31077
31530
|
}
|
|
31078
31531
|
if (inHunk && (oldRemaining > 0 || newRemaining > 0)) {
|
|
31079
|
-
return
|
|
31532
|
+
return fail3("the patch ends in the middle of a change block");
|
|
31080
31533
|
}
|
|
31081
31534
|
if (entries.length === 0) {
|
|
31082
31535
|
return { failure: { code: "empty-patch", detail: "The patch contains no changes at all." } };
|
|
31083
31536
|
}
|
|
31084
31537
|
for (const e of entries) {
|
|
31085
31538
|
if (e.rawPaths.length === 0)
|
|
31086
|
-
return
|
|
31539
|
+
return fail3("a change in the patch names no file");
|
|
31087
31540
|
}
|
|
31088
31541
|
return { entries };
|
|
31089
31542
|
}
|
|
@@ -31108,6 +31561,10 @@ function preflightBoundary(input) {
|
|
|
31108
31561
|
const refusals = [];
|
|
31109
31562
|
const touched = /* @__PURE__ */ new Set();
|
|
31110
31563
|
const seenPaths = /* @__PURE__ */ new Set();
|
|
31564
|
+
const lockfileTouches = /* @__PURE__ */ new Map();
|
|
31565
|
+
const changedManifests = /* @__PURE__ */ new Set();
|
|
31566
|
+
const deniedPaths = /* @__PURE__ */ new Set();
|
|
31567
|
+
const nonFileLockfiles = /* @__PURE__ */ new Set();
|
|
31111
31568
|
for (const entry of parsed.entries) {
|
|
31112
31569
|
if (entry.binary) {
|
|
31113
31570
|
const name = entry.rawPaths[0] ?? "a file";
|
|
@@ -31135,20 +31592,55 @@ function preflightBoundary(input) {
|
|
|
31135
31592
|
seenPaths.add(path5);
|
|
31136
31593
|
const denied = DENY_RULES.find((rule) => rule.matches(path5, segments, base));
|
|
31137
31594
|
if (denied !== void 0) {
|
|
31138
|
-
|
|
31595
|
+
deniedPaths.add(path5);
|
|
31596
|
+
const why = typeof denied.why === "string" ? denied.why : denied.why(base);
|
|
31597
|
+
refusals.push(refusal(denied.code, path5, `${path5} ${why}.`));
|
|
31139
31598
|
continue;
|
|
31140
31599
|
}
|
|
31141
31600
|
if (!slice.has(path5)) {
|
|
31142
31601
|
refusals.push(refusal("out-of-slice", path5, `${path5} is not one of the files shared for this bounty, so a change to it is outside what the developer was asked to work on.`));
|
|
31143
31602
|
}
|
|
31144
31603
|
}
|
|
31604
|
+
const contentRaw = entry.deletesFile ? entry.headerOld : entry.plusPath ?? entry.headerNew;
|
|
31605
|
+
const contentPath = contentRaw === null ? null : normalizePath(contentRaw)?.path ?? null;
|
|
31606
|
+
const plainEdit = entry.changed && !entry.createsFile && !entry.deletesFile && !entry.moved;
|
|
31607
|
+
const nonFile = entry.modes.some((m) => NON_FILE_MODES.has(m));
|
|
31608
|
+
for (const { path: path5, base } of entryPaths) {
|
|
31609
|
+
if (supportedLockfile(base) === null)
|
|
31610
|
+
continue;
|
|
31611
|
+
if (nonFile)
|
|
31612
|
+
nonFileLockfiles.add(path5);
|
|
31613
|
+
const seen = lockfileTouches.get(path5) ?? { touches: 0, edits: 0 };
|
|
31614
|
+
const edit = plainEdit && contentPath === path5 ? 1 : 0;
|
|
31615
|
+
lockfileTouches.set(path5, { touches: seen.touches + 1, edits: seen.edits + edit });
|
|
31616
|
+
}
|
|
31617
|
+
if (contentPath !== null && contentPath.split("/").pop() === "package.json" && entry.changed) {
|
|
31618
|
+
changedManifests.add(contentPath);
|
|
31619
|
+
}
|
|
31620
|
+
}
|
|
31621
|
+
for (const [path5, { touches, edits }] of lockfileTouches) {
|
|
31622
|
+
if (deniedPaths.has(path5))
|
|
31623
|
+
continue;
|
|
31624
|
+
if (nonFileLockfiles.has(path5)) {
|
|
31625
|
+
refusals.push(refusal("lockfile", path5, `${path5} is a symbolic link or a submodule in this patch, not a regular file. A lockfile must be a file whose entries can be checked, so the change is refused.`));
|
|
31626
|
+
continue;
|
|
31627
|
+
}
|
|
31628
|
+
if (touches !== 1 || edits !== 1) {
|
|
31629
|
+
refusals.push(refusal("lockfile", path5, `${path5} is created, deleted, moved or copied by this patch, or named more than once. Only an edit to the contents of an existing lockfile is checked, so this change is refused.`));
|
|
31630
|
+
continue;
|
|
31631
|
+
}
|
|
31632
|
+
const manifest = `${path5.slice(0, path5.lastIndexOf("/") + 1)}package.json`;
|
|
31633
|
+
if (!changedManifests.has(manifest)) {
|
|
31634
|
+
refusals.push(refusal("lockfile", path5, `${path5} changed but ${manifest} did not. A lockfile change is accepted only beside a change to the package.json it records.`));
|
|
31635
|
+
}
|
|
31145
31636
|
}
|
|
31146
31637
|
return { refused: refusals.length > 0, refusals, touchedPaths: [...touched].sort() };
|
|
31147
31638
|
}
|
|
31148
|
-
var PATH_REFUSAL_CODES, LOCKFILE_NAMES, DENY_RULES, TEXT_ENCODER, TEXT_DECODER, C_ESCAPES, DIFF_HEADER, HUNK_HEADER;
|
|
31639
|
+
var PATH_REFUSAL_CODES, LOCKFILE_NAMES, LOCKFILE_KINDS, REGISTRY_CONFIG_NAMES, DENY_RULES, TEXT_ENCODER, TEXT_DECODER, C_ESCAPES, NON_FILE_MODES, DIFF_HEADER, HUNK_HEADER;
|
|
31149
31640
|
var init_boundary = __esm({
|
|
31150
31641
|
"../../packages/envrun/dist/boundary.js"() {
|
|
31151
31642
|
"use strict";
|
|
31643
|
+
init_lockfileCheck();
|
|
31152
31644
|
PATH_REFUSAL_CODES = [
|
|
31153
31645
|
"empty-patch",
|
|
31154
31646
|
"unparseable",
|
|
@@ -31157,6 +31649,7 @@ var init_boundary = __esm({
|
|
|
31157
31649
|
"out-of-slice",
|
|
31158
31650
|
"ci-config",
|
|
31159
31651
|
"lockfile",
|
|
31652
|
+
"registry-config",
|
|
31160
31653
|
"build-file",
|
|
31161
31654
|
"git-internals"
|
|
31162
31655
|
];
|
|
@@ -31173,6 +31666,22 @@ var init_boundary = __esm({
|
|
|
31173
31666
|
"go.sum",
|
|
31174
31667
|
"composer.lock"
|
|
31175
31668
|
]);
|
|
31669
|
+
LOCKFILE_KINDS = {
|
|
31670
|
+
"package-lock.json": "an npm lockfile",
|
|
31671
|
+
"npm-shrinkwrap.json": "an npm shrinkwrap file",
|
|
31672
|
+
"yarn.lock": "a yarn lockfile",
|
|
31673
|
+
"pnpm-lock.yaml": "a pnpm lockfile",
|
|
31674
|
+
"bun.lockb": "a bun lockfile",
|
|
31675
|
+
"bun.lock": "a bun lockfile",
|
|
31676
|
+
"Cargo.lock": "a Cargo lockfile",
|
|
31677
|
+
"poetry.lock": "a Poetry lockfile",
|
|
31678
|
+
"Pipfile.lock": "a Pipenv lockfile",
|
|
31679
|
+
"Gemfile.lock": "a Bundler lockfile",
|
|
31680
|
+
"go.sum": "a Go checksum file",
|
|
31681
|
+
"composer.lock": "a Composer lockfile",
|
|
31682
|
+
"uv.lock": "a uv lockfile"
|
|
31683
|
+
};
|
|
31684
|
+
REGISTRY_CONFIG_NAMES = /* @__PURE__ */ new Set([".npmrc", ".yarnrc", ".yarnrc.yml"]);
|
|
31176
31685
|
DENY_RULES = [
|
|
31177
31686
|
{
|
|
31178
31687
|
code: "git-internals",
|
|
@@ -31214,8 +31723,15 @@ var init_boundary = __esm({
|
|
|
31214
31723
|
// The `.lock` suffix is the server's rule since TERM-412; this copy missed it, so
|
|
31215
31724
|
// the pre-flight passed `uv.lock` and bun's text `bun.lock` that the server refuses
|
|
31216
31725
|
// (TERM-1235). The name set only covers lockfiles whose names do not end in `.lock`.
|
|
31217
|
-
|
|
31218
|
-
|
|
31726
|
+
// TERM-1331: package-lock.json and yarn.lock are judged in `preflightBoundary` below,
|
|
31727
|
+
// as the source judges them in `validatePatch`.
|
|
31728
|
+
matches: (_p, _segments, base) => (LOCKFILE_NAMES.has(base) || base.endsWith(".lock")) && supportedLockfile(base) === null,
|
|
31729
|
+
why: lockfileRefusal
|
|
31730
|
+
},
|
|
31731
|
+
{
|
|
31732
|
+
code: "registry-config",
|
|
31733
|
+
matches: (_p, _segments, base) => REGISTRY_CONFIG_NAMES.has(base),
|
|
31734
|
+
why: "configures where packages are installed from. A patch may not change it, because it could point the install at another registry"
|
|
31219
31735
|
}
|
|
31220
31736
|
];
|
|
31221
31737
|
TEXT_ENCODER = new TextEncoder();
|
|
@@ -31231,6 +31747,7 @@ var init_boundary = __esm({
|
|
|
31231
31747
|
'"': 34,
|
|
31232
31748
|
"\\": 92
|
|
31233
31749
|
};
|
|
31750
|
+
NON_FILE_MODES = /* @__PURE__ */ new Set(["120000", "160000"]);
|
|
31234
31751
|
DIFF_HEADER = "diff --git ";
|
|
31235
31752
|
HUNK_HEADER = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
|
|
31236
31753
|
}
|
|
@@ -31484,7 +32001,7 @@ var init_emptyGitConfig = __esm({
|
|
|
31484
32001
|
|
|
31485
32002
|
// ../../packages/envrun/dist/hostedVenue.js
|
|
31486
32003
|
import { spawn as spawn4, spawnSync as spawnSync5 } from "child_process";
|
|
31487
|
-
import { createHash as
|
|
32004
|
+
import { createHash as createHash11, X509Certificate } from "crypto";
|
|
31488
32005
|
import { chmodSync as chmodSync3, existsSync as existsSync12, mkdtempSync as mkdtempSync3, readFileSync as readFileSync12, rmSync as rmSync8, writeFileSync as writeFileSync14 } from "fs";
|
|
31489
32006
|
import { request as httpsRequest } from "https";
|
|
31490
32007
|
import { createServer } from "net";
|
|
@@ -32154,7 +32671,7 @@ function iapPortTunnelArgv(vm, project, zone, remotePort, localPort) {
|
|
|
32154
32671
|
}
|
|
32155
32672
|
function spkiSha256Hex(certDer) {
|
|
32156
32673
|
const spki = new X509Certificate(certDer).publicKey.export({ type: "spki", format: "der" });
|
|
32157
|
-
return
|
|
32674
|
+
return createHash11("sha256").update(spki).digest("hex");
|
|
32158
32675
|
}
|
|
32159
32676
|
function certMetadataValue(pem) {
|
|
32160
32677
|
return new X509Certificate(pem).raw.toString("base64url");
|
|
@@ -36049,7 +36566,7 @@ allprojects {
|
|
|
36049
36566
|
|
|
36050
36567
|
// ../../packages/envrun/dist/thrun.js
|
|
36051
36568
|
import { execFileSync, spawnSync as spawnSync6 } from "child_process";
|
|
36052
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync4, rmSync as rmSync9 } from "fs";
|
|
36569
|
+
import { closeSync as closeSync5, constants as fsConstants, existsSync as existsSync13, fstatSync as fstatSync2, lstatSync as lstatSync5, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync4, openSync as openSync5, readSync, rmSync as rmSync9 } from "fs";
|
|
36053
36570
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
36054
36571
|
import { tmpdir as tmpdir4 } from "os";
|
|
36055
36572
|
import { join as join29 } from "path";
|
|
@@ -36087,6 +36604,59 @@ function collectWorkingDiff(repoDir, opts = {}) {
|
|
|
36087
36604
|
}
|
|
36088
36605
|
return { patch: parts.join(""), headSha, diffBase, trackedChanged, untracked };
|
|
36089
36606
|
}
|
|
36607
|
+
function readLockfileBytes(file, path5) {
|
|
36608
|
+
const notRegular = `${path5} is a symbolic link or another special file, not a regular file, so its entries could not be checked.`;
|
|
36609
|
+
const tooLarge = `${path5} is larger than 64MB, so its entries were not checked.`;
|
|
36610
|
+
try {
|
|
36611
|
+
const before = lstatSync5(file);
|
|
36612
|
+
if (!before.isFile())
|
|
36613
|
+
return { refusal: notRegular };
|
|
36614
|
+
if (before.size > LOCKFILE_READ_CAP)
|
|
36615
|
+
return { refusal: tooLarge };
|
|
36616
|
+
const fd = openSync5(file, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0));
|
|
36617
|
+
try {
|
|
36618
|
+
const opened = fstatSync2(fd);
|
|
36619
|
+
if (!opened.isFile())
|
|
36620
|
+
return { refusal: notRegular };
|
|
36621
|
+
if (opened.size > LOCKFILE_READ_CAP)
|
|
36622
|
+
return { refusal: tooLarge };
|
|
36623
|
+
const buf = Buffer.alloc(LOCKFILE_READ_CAP + 1);
|
|
36624
|
+
let length = 0;
|
|
36625
|
+
for (; ; ) {
|
|
36626
|
+
const n = readSync(fd, buf, length, buf.length - length, null);
|
|
36627
|
+
if (n === 0)
|
|
36628
|
+
break;
|
|
36629
|
+
length += n;
|
|
36630
|
+
if (length > LOCKFILE_READ_CAP)
|
|
36631
|
+
return { refusal: tooLarge };
|
|
36632
|
+
}
|
|
36633
|
+
return { text: buf.subarray(0, length).toString("utf8") };
|
|
36634
|
+
} finally {
|
|
36635
|
+
closeSync5(fd);
|
|
36636
|
+
}
|
|
36637
|
+
} catch {
|
|
36638
|
+
return { refusal: `${path5} could not be read, so its entries could not be checked.` };
|
|
36639
|
+
}
|
|
36640
|
+
}
|
|
36641
|
+
function lockfileContentRefusals(cloneDir, before) {
|
|
36642
|
+
const refusals = [];
|
|
36643
|
+
for (const [path5, base] of before) {
|
|
36644
|
+
const post = readLockfileBytes(join29(cloneDir, path5), path5);
|
|
36645
|
+
const problem = "refusal" in base ? base.refusal : "refusal" in post ? post.refusal : null;
|
|
36646
|
+
if (problem !== null) {
|
|
36647
|
+
refusals.push({ code: "lockfile", path: path5, detail: problem });
|
|
36648
|
+
continue;
|
|
36649
|
+
}
|
|
36650
|
+
const result = checkLockfileChange({
|
|
36651
|
+
path: path5,
|
|
36652
|
+
base: base.text,
|
|
36653
|
+
post: post.text
|
|
36654
|
+
});
|
|
36655
|
+
if (!result.ok)
|
|
36656
|
+
refusals.push({ code: "lockfile", path: path5, detail: result.detail });
|
|
36657
|
+
}
|
|
36658
|
+
return refusals;
|
|
36659
|
+
}
|
|
36090
36660
|
function assertDiffBase(repoDir, base) {
|
|
36091
36661
|
if (!FULL_COMMIT_ID.test(base)) {
|
|
36092
36662
|
throw new ThRunError(`the diff base must be a full 40-character commit id, got ${JSON.stringify(base)}.`);
|
|
@@ -36753,10 +37323,11 @@ async function runVerification(req, ctx) {
|
|
|
36753
37323
|
progress("collect", "dispatched run: the tree is the stored commit itself");
|
|
36754
37324
|
}
|
|
36755
37325
|
const boundary = source.kind !== "working-diff" || diff === null || diff.patch.trim() === "" ? { refused: false, refusals: [], touchedPaths: [] } : preflightBoundary({ patch: diff.patch, sliceFiles: source.sliceFiles });
|
|
36756
|
-
const
|
|
37326
|
+
const pathPre = source.kind === "working-diff" && source.sliceFiles.length > 0 ? boundary : (() => {
|
|
36757
37327
|
const refusals = boundary.refusals.filter((r) => r.code !== "out-of-slice");
|
|
36758
37328
|
return { refused: refusals.length > 0, refusals, touchedPaths: boundary.touchedPaths };
|
|
36759
37329
|
})();
|
|
37330
|
+
const pre = pathPre;
|
|
36760
37331
|
ctx.touchedPaths = pre.touchedPaths;
|
|
36761
37332
|
if (pre.refused) {
|
|
36762
37333
|
const first = pre.refusals[0];
|
|
@@ -36801,9 +37372,37 @@ async function runVerification(req, ctx) {
|
|
|
36801
37372
|
if (hasBaselinePatch) {
|
|
36802
37373
|
applyPatch(cloneDir, baselinePatch ?? "", "baseline patch");
|
|
36803
37374
|
}
|
|
37375
|
+
const lockfileBase = /* @__PURE__ */ new Map();
|
|
37376
|
+
if (diff !== null) {
|
|
37377
|
+
for (const path5 of pre.touchedPaths) {
|
|
37378
|
+
if (supportedLockfile(path5.split("/").pop() ?? "") === null)
|
|
37379
|
+
continue;
|
|
37380
|
+
lockfileBase.set(path5, readLockfileBytes(join29(cloneDir, path5), path5));
|
|
37381
|
+
}
|
|
37382
|
+
}
|
|
36804
37383
|
if (diff !== null) {
|
|
36805
37384
|
applyPatch(cloneDir, diff.patch, "developer's working diff");
|
|
36806
37385
|
}
|
|
37386
|
+
const lockfileRefusals = lockfileContentRefusals(cloneDir, lockfileBase);
|
|
37387
|
+
if (lockfileRefusals.length > 0) {
|
|
37388
|
+
return {
|
|
37389
|
+
result: refusedRun({
|
|
37390
|
+
runId,
|
|
37391
|
+
claimId: req.claimId,
|
|
37392
|
+
reason: lockfileRefusals[0]?.detail ?? "a lockfile in the diff was refused",
|
|
37393
|
+
wallMs: Date.now() - startedAt,
|
|
37394
|
+
targetRepo: publishableTarget(req.targetRepo),
|
|
37395
|
+
targetSha: req.targetSha,
|
|
37396
|
+
boundaryRefusals: lockfileRefusals,
|
|
37397
|
+
touchedPaths: pre.touchedPaths
|
|
37398
|
+
}),
|
|
37399
|
+
preview: null,
|
|
37400
|
+
spec: null,
|
|
37401
|
+
verdict: null,
|
|
37402
|
+
diagnostic: null,
|
|
37403
|
+
venueIdentity: null
|
|
37404
|
+
};
|
|
37405
|
+
}
|
|
36807
37406
|
const patchSha256 = diff === null ? null : sha256Hex(diff.patch);
|
|
36808
37407
|
const treeDigest = patchedTreeDigest(cloneDir);
|
|
36809
37408
|
const baselinePatchSha256 = hasBaselinePatch ? sha256Hex(baselinePatch ?? "") : null;
|
|
@@ -37129,7 +37728,7 @@ async function prepareBaseTree(o) {
|
|
|
37129
37728
|
throw err;
|
|
37130
37729
|
}
|
|
37131
37730
|
}
|
|
37132
|
-
var ThRunError, OUTPUT_TAIL_BYTES, FULL_COMMIT_ID, ALLOWED_URL_SCHEMES, SCP_STYLE, URL_SCHEME, WINDOWS_ABSOLUTE, UNC_PATH, TRANSPORT_REASON, SHA_REASON, FULL_SHA, MIN_GIT_VERSION_FOR_END_OF_OPTIONS, CloneUnavailableError, GIT_ENV_ALLOWLIST, credentialFreeHomeDir, SSH_ISOLATION_ARGS, WITHHELD_SEGMENT, UNPARSEABLE_TARGET, DEFERRED_GATE_TIMEOUT_MS, FAILURE_LINE, REDACTED_TARGET_REPO, REDACTED_TARGET_SHA;
|
|
37731
|
+
var ThRunError, OUTPUT_TAIL_BYTES, LOCKFILE_READ_CAP, FULL_COMMIT_ID, ALLOWED_URL_SCHEMES, SCP_STYLE, URL_SCHEME, WINDOWS_ABSOLUTE, UNC_PATH, TRANSPORT_REASON, SHA_REASON, FULL_SHA, MIN_GIT_VERSION_FOR_END_OF_OPTIONS, CloneUnavailableError, GIT_ENV_ALLOWLIST, credentialFreeHomeDir, SSH_ISOLATION_ARGS, WITHHELD_SEGMENT, UNPARSEABLE_TARGET, DEFERRED_GATE_TIMEOUT_MS, FAILURE_LINE, REDACTED_TARGET_REPO, REDACTED_TARGET_SHA;
|
|
37133
37732
|
var init_thrun = __esm({
|
|
37134
37733
|
"../../packages/envrun/dist/thrun.js"() {
|
|
37135
37734
|
"use strict";
|
|
@@ -37138,6 +37737,7 @@ var init_thrun = __esm({
|
|
|
37138
37737
|
init_dist3();
|
|
37139
37738
|
init_attestation2();
|
|
37140
37739
|
init_boundary();
|
|
37740
|
+
init_lockfileCheck();
|
|
37141
37741
|
init_emptyGitConfig();
|
|
37142
37742
|
init_labels();
|
|
37143
37743
|
init_execute();
|
|
@@ -37149,6 +37749,7 @@ var init_thrun = __esm({
|
|
|
37149
37749
|
ThRunError = class extends Error {
|
|
37150
37750
|
};
|
|
37151
37751
|
OUTPUT_TAIL_BYTES = 4e3;
|
|
37752
|
+
LOCKFILE_READ_CAP = 64 * 1024 * 1024;
|
|
37152
37753
|
FULL_COMMIT_ID = /^[0-9a-f]{40}$/;
|
|
37153
37754
|
ALLOWED_URL_SCHEMES = /* @__PURE__ */ new Set(["https", "http", "ssh", "git"]);
|
|
37154
37755
|
SCP_STYLE = /^[A-Za-z0-9._~+-]+@[A-Za-z0-9._-]+:[^:]/;
|
|
@@ -38033,6 +38634,7 @@ __export(dist_exports, {
|
|
|
38033
38634
|
LabelWatch: () => LabelWatch,
|
|
38034
38635
|
MIN_GIT_VERSION_FOR_END_OF_OPTIONS: () => MIN_GIT_VERSION_FOR_END_OF_OPTIONS,
|
|
38035
38636
|
OUTCOME_TO_BUDGET: () => OUTCOME_TO_BUDGET,
|
|
38637
|
+
PARTIAL_COUNTS_NOTE: () => PARTIAL_COUNTS_NOTE,
|
|
38036
38638
|
PATH_REFUSAL_CODES: () => PATH_REFUSAL_CODES,
|
|
38037
38639
|
PLACEMENTS: () => PLACEMENTS,
|
|
38038
38640
|
PLACEMENT_ALIASES: () => PLACEMENT_ALIASES,
|
|
@@ -38079,6 +38681,7 @@ __export(dist_exports, {
|
|
|
38079
38681
|
census: () => census,
|
|
38080
38682
|
censusReport: () => censusReport,
|
|
38081
38683
|
censusTotal: () => censusTotal,
|
|
38684
|
+
checkLockfileChange: () => checkLockfileChange,
|
|
38082
38685
|
classifyBootFailure: () => classifyBootFailure,
|
|
38083
38686
|
classifyProbeFailure: () => classifyProbeFailure,
|
|
38084
38687
|
classifySingleRun: () => classifySingleRun,
|
|
@@ -38161,6 +38764,7 @@ __export(dist_exports, {
|
|
|
38161
38764
|
startDatabase: () => startDatabase,
|
|
38162
38765
|
startLocalPreview: () => startLocalPreview,
|
|
38163
38766
|
startPreview: () => startPreview,
|
|
38767
|
+
supportedLockfile: () => supportedLockfile,
|
|
38164
38768
|
targetCarriesCredential: () => targetCarriesCredential,
|
|
38165
38769
|
toAcceptancePredicate: () => toAcceptancePredicate,
|
|
38166
38770
|
toolingImageFor: () => toolingImageFor,
|
|
@@ -38179,6 +38783,7 @@ var init_dist4 = __esm({
|
|
|
38179
38783
|
init_result();
|
|
38180
38784
|
init_attestation2();
|
|
38181
38785
|
init_boundary();
|
|
38786
|
+
init_lockfileCheck();
|
|
38182
38787
|
init_placement();
|
|
38183
38788
|
init_gcpPlacement();
|
|
38184
38789
|
init_venue();
|
|
@@ -38473,17 +39078,17 @@ import {
|
|
|
38473
39078
|
mkdtempSync as mkdtempSync5,
|
|
38474
39079
|
renameSync as renameSync6,
|
|
38475
39080
|
existsSync as existsSync14,
|
|
38476
|
-
lstatSync as
|
|
39081
|
+
lstatSync as lstatSync6,
|
|
38477
39082
|
realpathSync as realpathSync3,
|
|
38478
39083
|
rmSync as rmSync10,
|
|
38479
39084
|
readdirSync as readdirSync5,
|
|
38480
39085
|
statSync as statSync6,
|
|
38481
|
-
openSync as
|
|
38482
|
-
readSync,
|
|
38483
|
-
closeSync as
|
|
39086
|
+
openSync as openSync6,
|
|
39087
|
+
readSync as readSync2,
|
|
39088
|
+
closeSync as closeSync6
|
|
38484
39089
|
} from "fs";
|
|
38485
39090
|
import { join as join31, dirname as dirname9, isAbsolute as isAbsolute5, resolve as pathResolve } from "path";
|
|
38486
|
-
import { createHash as
|
|
39091
|
+
import { createHash as createHash12 } from "crypto";
|
|
38487
39092
|
import { homedir as homedir14, hostname as osHostname } from "os";
|
|
38488
39093
|
import { execFile as execFile3, execFileSync as execFileSync2, spawnSync as spawnSync8 } from "child_process";
|
|
38489
39094
|
import { promisify as promisify3 } from "util";
|
|
@@ -39134,13 +39739,9 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
|
39134
39739
|
"open",
|
|
39135
39740
|
"reason",
|
|
39136
39741
|
"note",
|
|
39137
|
-
// TERM-1044. `--body` carries a
|
|
39138
|
-
//
|
|
39139
|
-
//
|
|
39140
|
-
//
|
|
39141
|
-
// `--sha` was here for `claim note approve` and went with it: the send moved to the
|
|
39142
|
-
// dashboard, so no CLI verb names a digest any more. Left in place it would parse a
|
|
39143
|
-
// flag nothing reads, which is how a removed feature looks half-removed.
|
|
39742
|
+
// TERM-1044. `--body` carries a note's text and takes the next token. `--note` above
|
|
39743
|
+
// is already taken by `claim resolve`, where it means something different, so this
|
|
39744
|
+
// does NOT reuse it. (`--sha` went with `claim note approve`; no verb names a digest.)
|
|
39144
39745
|
"body",
|
|
39145
39746
|
// TERM-1259. `claim resolve --reason setup-failed --requirement <field>`.
|
|
39146
39747
|
"requirement",
|
|
@@ -41951,7 +42552,7 @@ function writeDeliveredBrief(destDir, spec) {
|
|
|
41951
42552
|
function ensureExcludedPackDir(destDir) {
|
|
41952
42553
|
let occupant = null;
|
|
41953
42554
|
try {
|
|
41954
|
-
occupant =
|
|
42555
|
+
occupant = lstatSync6(join31(destDir, BRIEF_DIR));
|
|
41955
42556
|
} catch (err) {
|
|
41956
42557
|
if (err?.code !== "ENOENT") {
|
|
41957
42558
|
return {
|
|
@@ -41994,7 +42595,7 @@ function writePackFile(destDir, relPath, content, what) {
|
|
|
41994
42595
|
return { written: true, reason: null, sha256: sha256OfUtf8(content) };
|
|
41995
42596
|
}
|
|
41996
42597
|
function sha256OfUtf8(content) {
|
|
41997
|
-
return
|
|
42598
|
+
return createHash12("sha256").update(content, "utf8").digest("hex");
|
|
41998
42599
|
}
|
|
41999
42600
|
function writeWorkspacePack(destDir, spec, claim, delivery) {
|
|
42000
42601
|
if (delivery !== "full" && delivery !== "sparse") {
|
|
@@ -42297,7 +42898,7 @@ terminalhire claim: ${what} needs the stored credential, and the server says it
|
|
|
42297
42898
|
return true;
|
|
42298
42899
|
}
|
|
42299
42900
|
async function enrolHeldClaimReadToken(postingId) {
|
|
42300
|
-
const
|
|
42901
|
+
const fail3 = (lines) => {
|
|
42301
42902
|
for (const line of lines) console.error(line);
|
|
42302
42903
|
process.exit(1);
|
|
42303
42904
|
};
|
|
@@ -42310,7 +42911,7 @@ async function enrolHeldClaimReadToken(postingId) {
|
|
|
42310
42911
|
]
|
|
42311
42912
|
});
|
|
42312
42913
|
if (!proofToken) {
|
|
42313
|
-
|
|
42914
|
+
fail3(["terminalhire claim: could not link this machine to your claim (see above)."]);
|
|
42314
42915
|
}
|
|
42315
42916
|
let res;
|
|
42316
42917
|
try {
|
|
@@ -42321,7 +42922,7 @@ async function enrolHeldClaimReadToken(postingId) {
|
|
|
42321
42922
|
signal: AbortSignal.timeout(CLAIM_SYNC_WRITE_TIMEOUT_MS)
|
|
42322
42923
|
});
|
|
42323
42924
|
} catch (err) {
|
|
42324
|
-
|
|
42925
|
+
fail3([
|
|
42325
42926
|
`terminalhire claim: terminalhire is unreachable (${err instanceof Error ? err.message : String(err)}).`,
|
|
42326
42927
|
" Nothing was stored. Run the same command again."
|
|
42327
42928
|
]);
|
|
@@ -42333,21 +42934,21 @@ async function enrolHeldClaimReadToken(postingId) {
|
|
|
42333
42934
|
}
|
|
42334
42935
|
if (res.status === 404 && body?.error === "no-live-claim") {
|
|
42335
42936
|
const who = typeof body.claimantLogin === "string" ? `@${body.claimantLogin}` : "that account";
|
|
42336
|
-
|
|
42937
|
+
fail3([
|
|
42337
42938
|
`terminalhire claim: you confirmed in the browser as ${who}, and ${who} does not`,
|
|
42338
42939
|
" hold a live claim on this posting. Nothing was stored.",
|
|
42339
42940
|
" Sign the browser into the GitHub account that claimed it, then run this again."
|
|
42340
42941
|
]);
|
|
42341
42942
|
}
|
|
42342
42943
|
if (!res.ok || typeof body?.pushToken !== "string" || body.pushToken.length === 0) {
|
|
42343
|
-
|
|
42944
|
+
fail3([
|
|
42344
42945
|
`terminalhire claim: linking this machine failed (${res.status}${typeof body?.message === "string" ? `: ${body.message}` : ""}). Nothing was stored.`
|
|
42345
42946
|
]);
|
|
42346
42947
|
}
|
|
42347
42948
|
try {
|
|
42348
42949
|
await writePushTokenEnc(body.pushToken);
|
|
42349
42950
|
} catch (err) {
|
|
42350
|
-
|
|
42951
|
+
fail3([
|
|
42351
42952
|
`terminalhire claim: the credential could not be stored on this machine: ${err instanceof Error ? err.message : String(err)}`
|
|
42352
42953
|
]);
|
|
42353
42954
|
}
|
|
@@ -42842,18 +43443,18 @@ function refuseScreenshots(detail) {
|
|
|
42842
43443
|
}
|
|
42843
43444
|
function readFileCapped(path5, limit2) {
|
|
42844
43445
|
if (statSync6(path5).size > limit2) return null;
|
|
42845
|
-
const fd =
|
|
43446
|
+
const fd = openSync6(path5, "r");
|
|
42846
43447
|
try {
|
|
42847
43448
|
const buf = Buffer.allocUnsafe(limit2 + 1);
|
|
42848
43449
|
let n = 0;
|
|
42849
43450
|
while (n < buf.length) {
|
|
42850
|
-
const got =
|
|
43451
|
+
const got = readSync2(fd, buf, n, buf.length - n, n);
|
|
42851
43452
|
if (got === 0) break;
|
|
42852
43453
|
n += got;
|
|
42853
43454
|
}
|
|
42854
43455
|
return n > limit2 ? null : buf.subarray(0, n);
|
|
42855
43456
|
} finally {
|
|
42856
|
-
|
|
43457
|
+
closeSync6(fd);
|
|
42857
43458
|
}
|
|
42858
43459
|
}
|
|
42859
43460
|
function loadClaimScreenshots(specArg) {
|
|
@@ -42897,7 +43498,7 @@ function loadClaimScreenshots(specArg) {
|
|
|
42897
43498
|
items.push({
|
|
42898
43499
|
file,
|
|
42899
43500
|
caption: shot.caption,
|
|
42900
|
-
sha256:
|
|
43501
|
+
sha256: createHash12("sha256").update(bytes).digest("hex"),
|
|
42901
43502
|
bytes: bytes.byteLength,
|
|
42902
43503
|
width,
|
|
42903
43504
|
height
|
|
@@ -44166,26 +44767,27 @@ function resolutionReasonHelpLines() {
|
|
|
44166
44767
|
}
|
|
44167
44768
|
function noteUsageLines() {
|
|
44168
44769
|
return [
|
|
44169
|
-
'Usage: terminalhire claim note <id> --body "..."
|
|
44770
|
+
'Usage: terminalhire claim note <id> --body "..." send a note to the poster',
|
|
44170
44771
|
"",
|
|
44171
44772
|
" Tell the poster something about a claim you hold. Needs `terminalhire link`.",
|
|
44172
44773
|
"",
|
|
44173
|
-
" This
|
|
44174
|
-
"
|
|
44175
|
-
"
|
|
44774
|
+
" This SENDS. The server checks the text for credentials and pasted",
|
|
44775
|
+
" instructions, sends it to the poster, and this prints back the exact text",
|
|
44776
|
+
" that went out.",
|
|
44176
44777
|
"",
|
|
44177
|
-
"
|
|
44178
|
-
|
|
44179
|
-
"
|
|
44180
|
-
" one script could draft and send with nobody reading anything.",
|
|
44778
|
+
" Sending from the CLI is off until you turn it on once, in your browser:",
|
|
44779
|
+
' on the dashboard, Settings > "Let my CLI send notes to posters". That switch',
|
|
44780
|
+
" cannot be changed from this terminal, and you can turn it off there any time.",
|
|
44181
44781
|
"",
|
|
44182
|
-
"
|
|
44183
|
-
"
|
|
44184
|
-
|
|
44782
|
+
" Why a switch: a note is often written by an agent that has just read a",
|
|
44783
|
+
" repository nobody has checked, and that repository can carry text aimed at",
|
|
44784
|
+
' the agent. The check catches known shapes (keys, tokens, "paste your .env"),',
|
|
44785
|
+
" not everything. Turning the switch on says your CLI may send under your name.",
|
|
44786
|
+
" It does not prove a person read each note.",
|
|
44185
44787
|
"",
|
|
44186
|
-
"
|
|
44187
|
-
"
|
|
44188
|
-
"
|
|
44788
|
+
" Limits: 3 notes per claim and 10 per poster in any 24 hours. The same text",
|
|
44789
|
+
" sent twice on one claim within 24 hours goes once. The poster can report a",
|
|
44790
|
+
" note to us."
|
|
44189
44791
|
];
|
|
44190
44792
|
}
|
|
44191
44793
|
async function cmdNote(id, flags = {}, deps = {}) {
|
|
@@ -44221,11 +44823,33 @@ async function cmdNote(id, flags = {}, deps = {}) {
|
|
|
44221
44823
|
if (!linked) return 1;
|
|
44222
44824
|
const res = await postJson(
|
|
44223
44825
|
fetchImpl,
|
|
44224
|
-
`${CLAIM_SYNC_BASE4}/api/claim/note
|
|
44826
|
+
`${CLAIM_SYNC_BASE4}/api/claim/note`,
|
|
44225
44827
|
{ bountyId: founderPostingIdOf(claim), claimId, body },
|
|
44226
44828
|
linked
|
|
44227
44829
|
);
|
|
44228
44830
|
if (!res.ok) {
|
|
44831
|
+
const answer = res.answer ?? {};
|
|
44832
|
+
if (answer.reason === "no-grant") {
|
|
44833
|
+
err("terminalhire claim note: sending notes from the CLI is not turned on for your account.");
|
|
44834
|
+
err(" Turn it on once, in your browser, then run this again:");
|
|
44835
|
+
err(
|
|
44836
|
+
` ${CLAIM_SYNC_BASE4}${typeof answer.grantPath === "string" ? answer.grantPath : "/dashboard?tab=settings"}`
|
|
44837
|
+
);
|
|
44838
|
+
err(' Settings > "Let my CLI send notes to posters". Nothing was sent.');
|
|
44839
|
+
return 1;
|
|
44840
|
+
}
|
|
44841
|
+
if (answer.reason === "capped") {
|
|
44842
|
+
const where = answer.scope === "poster" ? "to this poster" : "on this claim";
|
|
44843
|
+
err(
|
|
44844
|
+
`terminalhire claim note: you have sent ${answer.limit ?? "the most"} notes ${where} in the last 24 hours, the most allowed.`
|
|
44845
|
+
);
|
|
44846
|
+
if (typeof answer.nextAllowedAt === "string") {
|
|
44847
|
+
err(` The next one can go at ${answer.nextAllowedAt}. Nothing was sent.`);
|
|
44848
|
+
} else {
|
|
44849
|
+
err(" Nothing was sent.");
|
|
44850
|
+
}
|
|
44851
|
+
return 1;
|
|
44852
|
+
}
|
|
44229
44853
|
err(`terminalhire claim note: ${res.message}`);
|
|
44230
44854
|
for (const f of res.findings ?? []) {
|
|
44231
44855
|
err(` line ${f.line}: ${f.label}`);
|
|
@@ -44233,18 +44857,16 @@ async function cmdNote(id, flags = {}, deps = {}) {
|
|
|
44233
44857
|
return 1;
|
|
44234
44858
|
}
|
|
44235
44859
|
log("");
|
|
44236
|
-
|
|
44860
|
+
if (res.body.alreadySent) {
|
|
44861
|
+
log(` Already sent. This exact text went to the poster at ${res.body.sentAt};`);
|
|
44862
|
+
log(" nothing new was sent.");
|
|
44863
|
+
} else {
|
|
44864
|
+
log(" Sent to the poster:");
|
|
44865
|
+
}
|
|
44237
44866
|
log("");
|
|
44238
44867
|
for (const line of String(res.body.body).split("\n")) log(` \u2502 ${line}`);
|
|
44239
44868
|
log("");
|
|
44240
|
-
log(`
|
|
44241
|
-
log(` sha: ${res.body.bodySha256}`);
|
|
44242
|
-
log("");
|
|
44243
|
-
log(" Read it. If it says what you mean and carries nothing private, send it here:");
|
|
44244
|
-
log(` ${CLAIM_SYNC_BASE4}/dashboard/notes/${res.body.draftId}`);
|
|
44245
|
-
log("");
|
|
44246
|
-
log(" There is no approve command. The page needs a value only it can issue, which");
|
|
44247
|
-
log(" is what stops this tool from sending a note it just wrote.");
|
|
44869
|
+
log(` note: ${res.body.eventId} sent: ${res.body.sentAt}`);
|
|
44248
44870
|
log("");
|
|
44249
44871
|
return 0;
|
|
44250
44872
|
}
|
|
@@ -44286,7 +44908,9 @@ async function postJson(fetchImpl, url, body, linked) {
|
|
|
44286
44908
|
return {
|
|
44287
44909
|
ok: false,
|
|
44288
44910
|
message: typeof answer?.error === "string" && answer.error ? answer.error : `terminalhire answered ${res.status}`,
|
|
44289
|
-
findings: Array.isArray(answer?.findings) ? answer.findings : []
|
|
44911
|
+
findings: Array.isArray(answer?.findings) ? answer.findings : [],
|
|
44912
|
+
// The whole refusal, for a caller that branches on its `reason` (TERM-1322).
|
|
44913
|
+
answer: answer && typeof answer === "object" ? answer : null
|
|
44290
44914
|
};
|
|
44291
44915
|
}
|
|
44292
44916
|
return { ok: true, body: answer ?? {} };
|
|
@@ -44505,12 +45129,8 @@ async function run() {
|
|
|
44505
45129
|
// is three years of muscle memory and `note` is what you leave.
|
|
44506
45130
|
case "note": {
|
|
44507
45131
|
if (positional[0] === "approve") {
|
|
44508
|
-
console.error(
|
|
44509
|
-
|
|
44510
|
-
);
|
|
44511
|
-
console.error(
|
|
44512
|
-
' Draft with `terminalhire claim note <id> --body "..."` \u2014 it prints the link.'
|
|
44513
|
-
);
|
|
45132
|
+
console.error("terminalhire claim note approve: removed. `claim note` sends directly.");
|
|
45133
|
+
console.error(' Run `terminalhire claim note <id> --body "..."`.');
|
|
44514
45134
|
process.exit(1);
|
|
44515
45135
|
}
|
|
44516
45136
|
const code = await cmdNote(positional[0], flags);
|