terminalhire 0.42.37 → 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 +682 -97
- package/dist/bin/jpi-dispatch.js +696 -111
- package/dist/bin/jpi-mcp.js +693 -108
- package/dist/bin/jpi-run.js +725 -140
- 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
|
);
|
|
@@ -26408,10 +26408,10 @@ var init_classify2 = __esm({
|
|
|
26408
26408
|
runner: "node --test (TAP)",
|
|
26409
26409
|
read: (out) => {
|
|
26410
26410
|
const pass = /^# pass (\d+)$/m.exec(out);
|
|
26411
|
-
const
|
|
26412
|
-
if (!pass || !
|
|
26411
|
+
const fail3 = /^# fail (\d+)$/m.exec(out);
|
|
26412
|
+
if (!pass || !fail3)
|
|
26413
26413
|
return null;
|
|
26414
|
-
return { tests_passed: int(pass), tests_failed: int(
|
|
26414
|
+
return { tests_passed: int(pass), tests_failed: int(fail3) };
|
|
26415
26415
|
},
|
|
26416
26416
|
// One `# pass N` per invocation, so a script that runs `node --test` once per file
|
|
26417
26417
|
// prints many. envrun's own offline leg printed 67 and was read as the first one's 4.
|
|
@@ -29815,6 +29815,17 @@ var init_dsse = __esm({
|
|
|
29815
29815
|
}
|
|
29816
29816
|
});
|
|
29817
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
|
+
|
|
29818
29829
|
// ../../packages/attest/dist/sealedbox.js
|
|
29819
29830
|
import { createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3, diffieHellman, generateKeyPairSync as generateKeyPairSync2, hkdfSync as hkdfSync2, randomBytes as randomBytes9 } from "crypto";
|
|
29820
29831
|
var init_sealedbox = __esm({
|
|
@@ -29861,7 +29872,7 @@ var init_nonce = __esm({
|
|
|
29861
29872
|
});
|
|
29862
29873
|
|
|
29863
29874
|
// ../../packages/attest/dist/gceIdentity.js
|
|
29864
|
-
import { createHash as
|
|
29875
|
+
import { createHash as createHash8, createPublicKey as createPublicKey3, verify as cryptoVerify2 } from "crypto";
|
|
29865
29876
|
function expectedAudience(dispatchId) {
|
|
29866
29877
|
if (dispatchId === "")
|
|
29867
29878
|
throw new TypeError("expectedAudience: dispatchId is empty");
|
|
@@ -29946,7 +29957,7 @@ async function verifyGceIdentityToken(token, expectations, deps) {
|
|
|
29946
29957
|
}
|
|
29947
29958
|
let signatureValid = false;
|
|
29948
29959
|
try {
|
|
29949
|
-
const publicKey =
|
|
29960
|
+
const publicKey = createPublicKey3({ key: jwk, format: "jwk" });
|
|
29950
29961
|
signatureValid = cryptoVerify2("sha256", Buffer.from(`${headerB64}.${payloadB64}`, "utf8"), publicKey, signature);
|
|
29951
29962
|
} catch {
|
|
29952
29963
|
signatureValid = false;
|
|
@@ -30007,7 +30018,7 @@ async function verifyGceIdentityToken(token, expectations, deps) {
|
|
|
30007
30018
|
email: expectations.serviceAccountEmail,
|
|
30008
30019
|
iat,
|
|
30009
30020
|
exp,
|
|
30010
|
-
tokenSha256:
|
|
30021
|
+
tokenSha256: createHash8("sha256").update(token, "utf8").digest("hex")
|
|
30011
30022
|
}
|
|
30012
30023
|
};
|
|
30013
30024
|
}
|
|
@@ -30022,7 +30033,7 @@ var init_gceIdentity = __esm({
|
|
|
30022
30033
|
});
|
|
30023
30034
|
|
|
30024
30035
|
// ../../packages/attest/dist/confidentialSpace.js
|
|
30025
|
-
import { createHash as
|
|
30036
|
+
import { createHash as createHash9, createPublicKey as createPublicKey4, verify as cryptoVerify3 } from "crypto";
|
|
30026
30037
|
function refuse2(reason, detail) {
|
|
30027
30038
|
return { ok: false, reason, detail };
|
|
30028
30039
|
}
|
|
@@ -30096,7 +30107,7 @@ async function verifyConfidentialSpaceToken(token, expectations, deps) {
|
|
|
30096
30107
|
}
|
|
30097
30108
|
let signatureValid = false;
|
|
30098
30109
|
try {
|
|
30099
|
-
const publicKey =
|
|
30110
|
+
const publicKey = createPublicKey4({ key: jwk, format: "jwk" });
|
|
30100
30111
|
signatureValid = cryptoVerify3("sha256", Buffer.from(`${headerB64}.${payloadB64}`, "utf8"), publicKey, signature);
|
|
30101
30112
|
} catch {
|
|
30102
30113
|
signatureValid = false;
|
|
@@ -30196,7 +30207,7 @@ async function verifyConfidentialSpaceToken(token, expectations, deps) {
|
|
|
30196
30207
|
hwmodel,
|
|
30197
30208
|
iat,
|
|
30198
30209
|
exp,
|
|
30199
|
-
tokenSha256:
|
|
30210
|
+
tokenSha256: createHash9("sha256").update(token, "utf8").digest("hex")
|
|
30200
30211
|
}
|
|
30201
30212
|
};
|
|
30202
30213
|
}
|
|
@@ -30240,6 +30251,7 @@ var init_dist2 = __esm({
|
|
|
30240
30251
|
init_pae();
|
|
30241
30252
|
init_keys();
|
|
30242
30253
|
init_dsse();
|
|
30254
|
+
init_dispatchedRun();
|
|
30243
30255
|
init_sealedbox();
|
|
30244
30256
|
init_aead();
|
|
30245
30257
|
init_manifest();
|
|
@@ -30563,7 +30575,7 @@ var init_result = __esm({
|
|
|
30563
30575
|
});
|
|
30564
30576
|
|
|
30565
30577
|
// ../../packages/envrun/dist/attestation.js
|
|
30566
|
-
import { createHash as
|
|
30578
|
+
import { createHash as createHash10, randomBytes as randomBytes11 } from "crypto";
|
|
30567
30579
|
function contradicts(outcome, counts, exitCode) {
|
|
30568
30580
|
const budget = OUTCOME_TO_BUDGET[outcome];
|
|
30569
30581
|
if (budget === null)
|
|
@@ -30605,7 +30617,7 @@ function imageDigestOf(ref) {
|
|
|
30605
30617
|
return at === -1 ? null : ref.slice(at + 1);
|
|
30606
30618
|
}
|
|
30607
30619
|
function sha256Hex(data) {
|
|
30608
|
-
return
|
|
30620
|
+
return createHash10("sha256").update(typeof data === "string" ? Buffer.from(data, "utf8") : data).digest("hex");
|
|
30609
30621
|
}
|
|
30610
30622
|
function toTestRunResult(result, outputSha256) {
|
|
30611
30623
|
return {
|
|
@@ -30900,7 +30912,382 @@ var init_attestation2 = __esm({
|
|
|
30900
30912
|
}
|
|
30901
30913
|
});
|
|
30902
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
|
+
|
|
30903
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
|
+
}
|
|
30904
31291
|
function unquoteDiffPath(token) {
|
|
30905
31292
|
if (!token.startsWith('"'))
|
|
30906
31293
|
return token === "" ? null : token;
|
|
@@ -31006,18 +31393,29 @@ function parsePatchPaths(patch) {
|
|
|
31006
31393
|
let oldRemaining = 0;
|
|
31007
31394
|
let newRemaining = 0;
|
|
31008
31395
|
let inHunk = false;
|
|
31009
|
-
const
|
|
31396
|
+
const fail3 = (detail) => ({ failure: { code: "unparseable", detail } });
|
|
31010
31397
|
for (let i = 0; i < lines.length; i += 1) {
|
|
31011
31398
|
const line = lines[i];
|
|
31012
31399
|
if (line === void 0)
|
|
31013
31400
|
continue;
|
|
31014
31401
|
if (line.startsWith(DIFF_HEADER)) {
|
|
31015
31402
|
if (inHunk && (oldRemaining > 0 || newRemaining > 0)) {
|
|
31016
|
-
return
|
|
31403
|
+
return fail3("a change block ended before it delivered the lines it announced");
|
|
31017
31404
|
}
|
|
31018
31405
|
inHunk = false;
|
|
31019
31406
|
skippingBinary = false;
|
|
31020
|
-
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
|
+
};
|
|
31021
31419
|
entries.push(entry);
|
|
31022
31420
|
const split2 = splitDiffHeader(line.slice(DIFF_HEADER.length));
|
|
31023
31421
|
if (split2 !== null) {
|
|
@@ -31025,14 +31423,18 @@ function parsePatchPaths(patch) {
|
|
|
31025
31423
|
const left = unquoteDiffPath(leftToken);
|
|
31026
31424
|
const right = unquoteDiffPath(rightToken);
|
|
31027
31425
|
if (left === null || right === null) {
|
|
31028
|
-
return
|
|
31426
|
+
return fail3("a file name in the patch is quoted in a form we cannot read back");
|
|
31029
31427
|
}
|
|
31030
31428
|
const oldPath = stripSidePrefix(left, "a/");
|
|
31031
31429
|
const newPath = stripSidePrefix(right, "b/");
|
|
31032
31430
|
if (oldPath === null || newPath === null) {
|
|
31033
|
-
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");
|
|
31034
31432
|
}
|
|
31035
31433
|
entry.rawPaths.push(oldPath, newPath);
|
|
31434
|
+
entry.headerOld = oldPath;
|
|
31435
|
+
entry.headerNew = newPath;
|
|
31436
|
+
if (oldPath !== newPath)
|
|
31437
|
+
entry.moved = true;
|
|
31036
31438
|
}
|
|
31037
31439
|
continue;
|
|
31038
31440
|
}
|
|
@@ -31049,13 +31451,15 @@ function parsePatchPaths(patch) {
|
|
|
31049
31451
|
newRemaining -= 1;
|
|
31050
31452
|
} else if (marker === "-") {
|
|
31051
31453
|
oldRemaining -= 1;
|
|
31454
|
+
entry.changed = true;
|
|
31052
31455
|
} else if (marker === "+") {
|
|
31053
31456
|
newRemaining -= 1;
|
|
31457
|
+
entry.changed = true;
|
|
31054
31458
|
} else {
|
|
31055
|
-
return
|
|
31459
|
+
return fail3("a change block contains a line that is neither an addition, a removal nor context");
|
|
31056
31460
|
}
|
|
31057
31461
|
if (oldRemaining < 0 || newRemaining < 0) {
|
|
31058
|
-
return
|
|
31462
|
+
return fail3("a change block delivered more lines than it announced");
|
|
31059
31463
|
}
|
|
31060
31464
|
if (oldRemaining === 0 && newRemaining === 0)
|
|
31061
31465
|
inHunk = false;
|
|
@@ -31066,13 +31470,20 @@ function parsePatchPaths(patch) {
|
|
|
31066
31470
|
if (line.startsWith("@@")) {
|
|
31067
31471
|
const m = HUNK_HEADER.exec(line);
|
|
31068
31472
|
if (m === null) {
|
|
31069
|
-
return
|
|
31473
|
+
return fail3("a change block header is malformed, so we cannot tell which lines belong to it");
|
|
31070
31474
|
}
|
|
31071
31475
|
oldRemaining = m[2] === void 0 ? 1 : Number(m[2]);
|
|
31072
31476
|
newRemaining = m[4] === void 0 ? 1 : Number(m[4]);
|
|
31073
31477
|
inHunk = oldRemaining > 0 || newRemaining > 0;
|
|
31074
31478
|
continue;
|
|
31075
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);
|
|
31076
31487
|
if (line === "GIT binary patch" || line.startsWith("Binary files ")) {
|
|
31077
31488
|
entry.binary = true;
|
|
31078
31489
|
skippingBinary = true;
|
|
@@ -31083,41 +31494,49 @@ function parsePatchPaths(patch) {
|
|
|
31083
31494
|
const rest = line.slice(4);
|
|
31084
31495
|
const tab = rest.indexOf(" ");
|
|
31085
31496
|
const token = tab === -1 ? rest : rest.slice(0, tab);
|
|
31086
|
-
if (token === "/dev/null")
|
|
31497
|
+
if (token === "/dev/null") {
|
|
31498
|
+
if (sideMarker === "a/")
|
|
31499
|
+
entry.createsFile = true;
|
|
31500
|
+
else
|
|
31501
|
+
entry.deletesFile = true;
|
|
31087
31502
|
continue;
|
|
31503
|
+
}
|
|
31088
31504
|
const decoded = unquoteDiffPath(token);
|
|
31089
31505
|
if (decoded === null) {
|
|
31090
|
-
return
|
|
31506
|
+
return fail3("a file name in the patch is quoted in a form we cannot read back");
|
|
31091
31507
|
}
|
|
31092
31508
|
const stripped = stripSidePrefix(decoded, sideMarker);
|
|
31093
31509
|
if (stripped === null) {
|
|
31094
|
-
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");
|
|
31095
31511
|
}
|
|
31096
31512
|
entry.rawPaths.push(stripped);
|
|
31513
|
+
if (sideMarker === "b/")
|
|
31514
|
+
entry.plusPath = stripped;
|
|
31097
31515
|
continue;
|
|
31098
31516
|
}
|
|
31099
31517
|
const moveMarker = ["rename from ", "rename to ", "copy from ", "copy to "].find((p) => line.startsWith(p));
|
|
31100
31518
|
if (moveMarker !== void 0) {
|
|
31101
31519
|
const decoded = unquoteDiffPath(line.slice(moveMarker.length));
|
|
31102
31520
|
if (decoded === null) {
|
|
31103
|
-
return
|
|
31521
|
+
return fail3("a file name in the patch is quoted in a form we cannot read back");
|
|
31104
31522
|
}
|
|
31105
31523
|
entry.rawPaths.push(decoded);
|
|
31524
|
+
entry.moved = true;
|
|
31106
31525
|
continue;
|
|
31107
31526
|
}
|
|
31108
31527
|
if (line.startsWith("+") || line.startsWith("-")) {
|
|
31109
|
-
return
|
|
31528
|
+
return fail3("the patch contains a changed line that belongs to no change block");
|
|
31110
31529
|
}
|
|
31111
31530
|
}
|
|
31112
31531
|
if (inHunk && (oldRemaining > 0 || newRemaining > 0)) {
|
|
31113
|
-
return
|
|
31532
|
+
return fail3("the patch ends in the middle of a change block");
|
|
31114
31533
|
}
|
|
31115
31534
|
if (entries.length === 0) {
|
|
31116
31535
|
return { failure: { code: "empty-patch", detail: "The patch contains no changes at all." } };
|
|
31117
31536
|
}
|
|
31118
31537
|
for (const e of entries) {
|
|
31119
31538
|
if (e.rawPaths.length === 0)
|
|
31120
|
-
return
|
|
31539
|
+
return fail3("a change in the patch names no file");
|
|
31121
31540
|
}
|
|
31122
31541
|
return { entries };
|
|
31123
31542
|
}
|
|
@@ -31142,6 +31561,10 @@ function preflightBoundary(input) {
|
|
|
31142
31561
|
const refusals = [];
|
|
31143
31562
|
const touched = /* @__PURE__ */ new Set();
|
|
31144
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();
|
|
31145
31568
|
for (const entry of parsed.entries) {
|
|
31146
31569
|
if (entry.binary) {
|
|
31147
31570
|
const name = entry.rawPaths[0] ?? "a file";
|
|
@@ -31169,20 +31592,55 @@ function preflightBoundary(input) {
|
|
|
31169
31592
|
seenPaths.add(path5);
|
|
31170
31593
|
const denied = DENY_RULES.find((rule) => rule.matches(path5, segments, base));
|
|
31171
31594
|
if (denied !== void 0) {
|
|
31172
|
-
|
|
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}.`));
|
|
31173
31598
|
continue;
|
|
31174
31599
|
}
|
|
31175
31600
|
if (!slice.has(path5)) {
|
|
31176
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.`));
|
|
31177
31602
|
}
|
|
31178
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
|
+
}
|
|
31179
31636
|
}
|
|
31180
31637
|
return { refused: refusals.length > 0, refusals, touchedPaths: [...touched].sort() };
|
|
31181
31638
|
}
|
|
31182
|
-
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;
|
|
31183
31640
|
var init_boundary = __esm({
|
|
31184
31641
|
"../../packages/envrun/dist/boundary.js"() {
|
|
31185
31642
|
"use strict";
|
|
31643
|
+
init_lockfileCheck();
|
|
31186
31644
|
PATH_REFUSAL_CODES = [
|
|
31187
31645
|
"empty-patch",
|
|
31188
31646
|
"unparseable",
|
|
@@ -31191,6 +31649,7 @@ var init_boundary = __esm({
|
|
|
31191
31649
|
"out-of-slice",
|
|
31192
31650
|
"ci-config",
|
|
31193
31651
|
"lockfile",
|
|
31652
|
+
"registry-config",
|
|
31194
31653
|
"build-file",
|
|
31195
31654
|
"git-internals"
|
|
31196
31655
|
];
|
|
@@ -31207,6 +31666,22 @@ var init_boundary = __esm({
|
|
|
31207
31666
|
"go.sum",
|
|
31208
31667
|
"composer.lock"
|
|
31209
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"]);
|
|
31210
31685
|
DENY_RULES = [
|
|
31211
31686
|
{
|
|
31212
31687
|
code: "git-internals",
|
|
@@ -31248,8 +31723,15 @@ var init_boundary = __esm({
|
|
|
31248
31723
|
// The `.lock` suffix is the server's rule since TERM-412; this copy missed it, so
|
|
31249
31724
|
// the pre-flight passed `uv.lock` and bun's text `bun.lock` that the server refuses
|
|
31250
31725
|
// (TERM-1235). The name set only covers lockfiles whose names do not end in `.lock`.
|
|
31251
|
-
|
|
31252
|
-
|
|
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"
|
|
31253
31735
|
}
|
|
31254
31736
|
];
|
|
31255
31737
|
TEXT_ENCODER = new TextEncoder();
|
|
@@ -31265,6 +31747,7 @@ var init_boundary = __esm({
|
|
|
31265
31747
|
'"': 34,
|
|
31266
31748
|
"\\": 92
|
|
31267
31749
|
};
|
|
31750
|
+
NON_FILE_MODES = /* @__PURE__ */ new Set(["120000", "160000"]);
|
|
31268
31751
|
DIFF_HEADER = "diff --git ";
|
|
31269
31752
|
HUNK_HEADER = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
|
|
31270
31753
|
}
|
|
@@ -31518,7 +32001,7 @@ var init_emptyGitConfig = __esm({
|
|
|
31518
32001
|
|
|
31519
32002
|
// ../../packages/envrun/dist/hostedVenue.js
|
|
31520
32003
|
import { spawn as spawn4, spawnSync as spawnSync5 } from "child_process";
|
|
31521
|
-
import { createHash as
|
|
32004
|
+
import { createHash as createHash11, X509Certificate } from "crypto";
|
|
31522
32005
|
import { chmodSync as chmodSync3, existsSync as existsSync12, mkdtempSync as mkdtempSync3, readFileSync as readFileSync12, rmSync as rmSync8, writeFileSync as writeFileSync14 } from "fs";
|
|
31523
32006
|
import { request as httpsRequest } from "https";
|
|
31524
32007
|
import { createServer } from "net";
|
|
@@ -32188,7 +32671,7 @@ function iapPortTunnelArgv(vm, project, zone, remotePort, localPort) {
|
|
|
32188
32671
|
}
|
|
32189
32672
|
function spkiSha256Hex(certDer) {
|
|
32190
32673
|
const spki = new X509Certificate(certDer).publicKey.export({ type: "spki", format: "der" });
|
|
32191
|
-
return
|
|
32674
|
+
return createHash11("sha256").update(spki).digest("hex");
|
|
32192
32675
|
}
|
|
32193
32676
|
function certMetadataValue(pem) {
|
|
32194
32677
|
return new X509Certificate(pem).raw.toString("base64url");
|
|
@@ -36083,7 +36566,7 @@ allprojects {
|
|
|
36083
36566
|
|
|
36084
36567
|
// ../../packages/envrun/dist/thrun.js
|
|
36085
36568
|
import { execFileSync, spawnSync as spawnSync6 } from "child_process";
|
|
36086
|
-
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";
|
|
36087
36570
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
36088
36571
|
import { tmpdir as tmpdir4 } from "os";
|
|
36089
36572
|
import { join as join29 } from "path";
|
|
@@ -36121,6 +36604,59 @@ function collectWorkingDiff(repoDir, opts = {}) {
|
|
|
36121
36604
|
}
|
|
36122
36605
|
return { patch: parts.join(""), headSha, diffBase, trackedChanged, untracked };
|
|
36123
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
|
+
}
|
|
36124
36660
|
function assertDiffBase(repoDir, base) {
|
|
36125
36661
|
if (!FULL_COMMIT_ID.test(base)) {
|
|
36126
36662
|
throw new ThRunError(`the diff base must be a full 40-character commit id, got ${JSON.stringify(base)}.`);
|
|
@@ -36787,10 +37323,11 @@ async function runVerification(req, ctx) {
|
|
|
36787
37323
|
progress("collect", "dispatched run: the tree is the stored commit itself");
|
|
36788
37324
|
}
|
|
36789
37325
|
const boundary = source.kind !== "working-diff" || diff === null || diff.patch.trim() === "" ? { refused: false, refusals: [], touchedPaths: [] } : preflightBoundary({ patch: diff.patch, sliceFiles: source.sliceFiles });
|
|
36790
|
-
const
|
|
37326
|
+
const pathPre = source.kind === "working-diff" && source.sliceFiles.length > 0 ? boundary : (() => {
|
|
36791
37327
|
const refusals = boundary.refusals.filter((r) => r.code !== "out-of-slice");
|
|
36792
37328
|
return { refused: refusals.length > 0, refusals, touchedPaths: boundary.touchedPaths };
|
|
36793
37329
|
})();
|
|
37330
|
+
const pre = pathPre;
|
|
36794
37331
|
ctx.touchedPaths = pre.touchedPaths;
|
|
36795
37332
|
if (pre.refused) {
|
|
36796
37333
|
const first = pre.refusals[0];
|
|
@@ -36835,9 +37372,37 @@ async function runVerification(req, ctx) {
|
|
|
36835
37372
|
if (hasBaselinePatch) {
|
|
36836
37373
|
applyPatch(cloneDir, baselinePatch ?? "", "baseline patch");
|
|
36837
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
|
+
}
|
|
36838
37383
|
if (diff !== null) {
|
|
36839
37384
|
applyPatch(cloneDir, diff.patch, "developer's working diff");
|
|
36840
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
|
+
}
|
|
36841
37406
|
const patchSha256 = diff === null ? null : sha256Hex(diff.patch);
|
|
36842
37407
|
const treeDigest = patchedTreeDigest(cloneDir);
|
|
36843
37408
|
const baselinePatchSha256 = hasBaselinePatch ? sha256Hex(baselinePatch ?? "") : null;
|
|
@@ -37163,7 +37728,7 @@ async function prepareBaseTree(o) {
|
|
|
37163
37728
|
throw err;
|
|
37164
37729
|
}
|
|
37165
37730
|
}
|
|
37166
|
-
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;
|
|
37167
37732
|
var init_thrun = __esm({
|
|
37168
37733
|
"../../packages/envrun/dist/thrun.js"() {
|
|
37169
37734
|
"use strict";
|
|
@@ -37172,6 +37737,7 @@ var init_thrun = __esm({
|
|
|
37172
37737
|
init_dist3();
|
|
37173
37738
|
init_attestation2();
|
|
37174
37739
|
init_boundary();
|
|
37740
|
+
init_lockfileCheck();
|
|
37175
37741
|
init_emptyGitConfig();
|
|
37176
37742
|
init_labels();
|
|
37177
37743
|
init_execute();
|
|
@@ -37183,6 +37749,7 @@ var init_thrun = __esm({
|
|
|
37183
37749
|
ThRunError = class extends Error {
|
|
37184
37750
|
};
|
|
37185
37751
|
OUTPUT_TAIL_BYTES = 4e3;
|
|
37752
|
+
LOCKFILE_READ_CAP = 64 * 1024 * 1024;
|
|
37186
37753
|
FULL_COMMIT_ID = /^[0-9a-f]{40}$/;
|
|
37187
37754
|
ALLOWED_URL_SCHEMES = /* @__PURE__ */ new Set(["https", "http", "ssh", "git"]);
|
|
37188
37755
|
SCP_STYLE = /^[A-Za-z0-9._~+-]+@[A-Za-z0-9._-]+:[^:]/;
|
|
@@ -38114,6 +38681,7 @@ __export(dist_exports, {
|
|
|
38114
38681
|
census: () => census,
|
|
38115
38682
|
censusReport: () => censusReport,
|
|
38116
38683
|
censusTotal: () => censusTotal,
|
|
38684
|
+
checkLockfileChange: () => checkLockfileChange,
|
|
38117
38685
|
classifyBootFailure: () => classifyBootFailure,
|
|
38118
38686
|
classifyProbeFailure: () => classifyProbeFailure,
|
|
38119
38687
|
classifySingleRun: () => classifySingleRun,
|
|
@@ -38196,6 +38764,7 @@ __export(dist_exports, {
|
|
|
38196
38764
|
startDatabase: () => startDatabase,
|
|
38197
38765
|
startLocalPreview: () => startLocalPreview,
|
|
38198
38766
|
startPreview: () => startPreview,
|
|
38767
|
+
supportedLockfile: () => supportedLockfile,
|
|
38199
38768
|
targetCarriesCredential: () => targetCarriesCredential,
|
|
38200
38769
|
toAcceptancePredicate: () => toAcceptancePredicate,
|
|
38201
38770
|
toolingImageFor: () => toolingImageFor,
|
|
@@ -38214,6 +38783,7 @@ var init_dist4 = __esm({
|
|
|
38214
38783
|
init_result();
|
|
38215
38784
|
init_attestation2();
|
|
38216
38785
|
init_boundary();
|
|
38786
|
+
init_lockfileCheck();
|
|
38217
38787
|
init_placement();
|
|
38218
38788
|
init_gcpPlacement();
|
|
38219
38789
|
init_venue();
|
|
@@ -38508,17 +39078,17 @@ import {
|
|
|
38508
39078
|
mkdtempSync as mkdtempSync5,
|
|
38509
39079
|
renameSync as renameSync6,
|
|
38510
39080
|
existsSync as existsSync14,
|
|
38511
|
-
lstatSync as
|
|
39081
|
+
lstatSync as lstatSync6,
|
|
38512
39082
|
realpathSync as realpathSync3,
|
|
38513
39083
|
rmSync as rmSync10,
|
|
38514
39084
|
readdirSync as readdirSync5,
|
|
38515
39085
|
statSync as statSync6,
|
|
38516
|
-
openSync as
|
|
38517
|
-
readSync,
|
|
38518
|
-
closeSync as
|
|
39086
|
+
openSync as openSync6,
|
|
39087
|
+
readSync as readSync2,
|
|
39088
|
+
closeSync as closeSync6
|
|
38519
39089
|
} from "fs";
|
|
38520
39090
|
import { join as join31, dirname as dirname9, isAbsolute as isAbsolute5, resolve as pathResolve } from "path";
|
|
38521
|
-
import { createHash as
|
|
39091
|
+
import { createHash as createHash12 } from "crypto";
|
|
38522
39092
|
import { homedir as homedir14, hostname as osHostname } from "os";
|
|
38523
39093
|
import { execFile as execFile3, execFileSync as execFileSync2, spawnSync as spawnSync8 } from "child_process";
|
|
38524
39094
|
import { promisify as promisify3 } from "util";
|
|
@@ -39169,13 +39739,9 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
|
39169
39739
|
"open",
|
|
39170
39740
|
"reason",
|
|
39171
39741
|
"note",
|
|
39172
|
-
// TERM-1044. `--body` carries a
|
|
39173
|
-
//
|
|
39174
|
-
//
|
|
39175
|
-
//
|
|
39176
|
-
// `--sha` was here for `claim note approve` and went with it: the send moved to the
|
|
39177
|
-
// dashboard, so no CLI verb names a digest any more. Left in place it would parse a
|
|
39178
|
-
// 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.)
|
|
39179
39745
|
"body",
|
|
39180
39746
|
// TERM-1259. `claim resolve --reason setup-failed --requirement <field>`.
|
|
39181
39747
|
"requirement",
|
|
@@ -41986,7 +42552,7 @@ function writeDeliveredBrief(destDir, spec) {
|
|
|
41986
42552
|
function ensureExcludedPackDir(destDir) {
|
|
41987
42553
|
let occupant = null;
|
|
41988
42554
|
try {
|
|
41989
|
-
occupant =
|
|
42555
|
+
occupant = lstatSync6(join31(destDir, BRIEF_DIR));
|
|
41990
42556
|
} catch (err) {
|
|
41991
42557
|
if (err?.code !== "ENOENT") {
|
|
41992
42558
|
return {
|
|
@@ -42029,7 +42595,7 @@ function writePackFile(destDir, relPath, content, what) {
|
|
|
42029
42595
|
return { written: true, reason: null, sha256: sha256OfUtf8(content) };
|
|
42030
42596
|
}
|
|
42031
42597
|
function sha256OfUtf8(content) {
|
|
42032
|
-
return
|
|
42598
|
+
return createHash12("sha256").update(content, "utf8").digest("hex");
|
|
42033
42599
|
}
|
|
42034
42600
|
function writeWorkspacePack(destDir, spec, claim, delivery) {
|
|
42035
42601
|
if (delivery !== "full" && delivery !== "sparse") {
|
|
@@ -42332,7 +42898,7 @@ terminalhire claim: ${what} needs the stored credential, and the server says it
|
|
|
42332
42898
|
return true;
|
|
42333
42899
|
}
|
|
42334
42900
|
async function enrolHeldClaimReadToken(postingId) {
|
|
42335
|
-
const
|
|
42901
|
+
const fail3 = (lines) => {
|
|
42336
42902
|
for (const line of lines) console.error(line);
|
|
42337
42903
|
process.exit(1);
|
|
42338
42904
|
};
|
|
@@ -42345,7 +42911,7 @@ async function enrolHeldClaimReadToken(postingId) {
|
|
|
42345
42911
|
]
|
|
42346
42912
|
});
|
|
42347
42913
|
if (!proofToken) {
|
|
42348
|
-
|
|
42914
|
+
fail3(["terminalhire claim: could not link this machine to your claim (see above)."]);
|
|
42349
42915
|
}
|
|
42350
42916
|
let res;
|
|
42351
42917
|
try {
|
|
@@ -42356,7 +42922,7 @@ async function enrolHeldClaimReadToken(postingId) {
|
|
|
42356
42922
|
signal: AbortSignal.timeout(CLAIM_SYNC_WRITE_TIMEOUT_MS)
|
|
42357
42923
|
});
|
|
42358
42924
|
} catch (err) {
|
|
42359
|
-
|
|
42925
|
+
fail3([
|
|
42360
42926
|
`terminalhire claim: terminalhire is unreachable (${err instanceof Error ? err.message : String(err)}).`,
|
|
42361
42927
|
" Nothing was stored. Run the same command again."
|
|
42362
42928
|
]);
|
|
@@ -42368,21 +42934,21 @@ async function enrolHeldClaimReadToken(postingId) {
|
|
|
42368
42934
|
}
|
|
42369
42935
|
if (res.status === 404 && body?.error === "no-live-claim") {
|
|
42370
42936
|
const who = typeof body.claimantLogin === "string" ? `@${body.claimantLogin}` : "that account";
|
|
42371
|
-
|
|
42937
|
+
fail3([
|
|
42372
42938
|
`terminalhire claim: you confirmed in the browser as ${who}, and ${who} does not`,
|
|
42373
42939
|
" hold a live claim on this posting. Nothing was stored.",
|
|
42374
42940
|
" Sign the browser into the GitHub account that claimed it, then run this again."
|
|
42375
42941
|
]);
|
|
42376
42942
|
}
|
|
42377
42943
|
if (!res.ok || typeof body?.pushToken !== "string" || body.pushToken.length === 0) {
|
|
42378
|
-
|
|
42944
|
+
fail3([
|
|
42379
42945
|
`terminalhire claim: linking this machine failed (${res.status}${typeof body?.message === "string" ? `: ${body.message}` : ""}). Nothing was stored.`
|
|
42380
42946
|
]);
|
|
42381
42947
|
}
|
|
42382
42948
|
try {
|
|
42383
42949
|
await writePushTokenEnc(body.pushToken);
|
|
42384
42950
|
} catch (err) {
|
|
42385
|
-
|
|
42951
|
+
fail3([
|
|
42386
42952
|
`terminalhire claim: the credential could not be stored on this machine: ${err instanceof Error ? err.message : String(err)}`
|
|
42387
42953
|
]);
|
|
42388
42954
|
}
|
|
@@ -42877,18 +43443,18 @@ function refuseScreenshots(detail) {
|
|
|
42877
43443
|
}
|
|
42878
43444
|
function readFileCapped(path5, limit2) {
|
|
42879
43445
|
if (statSync6(path5).size > limit2) return null;
|
|
42880
|
-
const fd =
|
|
43446
|
+
const fd = openSync6(path5, "r");
|
|
42881
43447
|
try {
|
|
42882
43448
|
const buf = Buffer.allocUnsafe(limit2 + 1);
|
|
42883
43449
|
let n = 0;
|
|
42884
43450
|
while (n < buf.length) {
|
|
42885
|
-
const got =
|
|
43451
|
+
const got = readSync2(fd, buf, n, buf.length - n, n);
|
|
42886
43452
|
if (got === 0) break;
|
|
42887
43453
|
n += got;
|
|
42888
43454
|
}
|
|
42889
43455
|
return n > limit2 ? null : buf.subarray(0, n);
|
|
42890
43456
|
} finally {
|
|
42891
|
-
|
|
43457
|
+
closeSync6(fd);
|
|
42892
43458
|
}
|
|
42893
43459
|
}
|
|
42894
43460
|
function loadClaimScreenshots(specArg) {
|
|
@@ -42932,7 +43498,7 @@ function loadClaimScreenshots(specArg) {
|
|
|
42932
43498
|
items.push({
|
|
42933
43499
|
file,
|
|
42934
43500
|
caption: shot.caption,
|
|
42935
|
-
sha256:
|
|
43501
|
+
sha256: createHash12("sha256").update(bytes).digest("hex"),
|
|
42936
43502
|
bytes: bytes.byteLength,
|
|
42937
43503
|
width,
|
|
42938
43504
|
height
|
|
@@ -44201,26 +44767,27 @@ function resolutionReasonHelpLines() {
|
|
|
44201
44767
|
}
|
|
44202
44768
|
function noteUsageLines() {
|
|
44203
44769
|
return [
|
|
44204
|
-
'Usage: terminalhire claim note <id> --body "..."
|
|
44770
|
+
'Usage: terminalhire claim note <id> --body "..." send a note to the poster',
|
|
44205
44771
|
"",
|
|
44206
44772
|
" Tell the poster something about a claim you hold. Needs `terminalhire link`.",
|
|
44207
44773
|
"",
|
|
44208
|
-
" This
|
|
44209
|
-
"
|
|
44210
|
-
"
|
|
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.",
|
|
44211
44777
|
"",
|
|
44212
|
-
"
|
|
44213
|
-
|
|
44214
|
-
"
|
|
44215
|
-
" 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.",
|
|
44216
44781
|
"",
|
|
44217
|
-
"
|
|
44218
|
-
"
|
|
44219
|
-
|
|
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.",
|
|
44220
44787
|
"",
|
|
44221
|
-
"
|
|
44222
|
-
"
|
|
44223
|
-
"
|
|
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."
|
|
44224
44791
|
];
|
|
44225
44792
|
}
|
|
44226
44793
|
async function cmdNote(id, flags = {}, deps = {}) {
|
|
@@ -44256,11 +44823,33 @@ async function cmdNote(id, flags = {}, deps = {}) {
|
|
|
44256
44823
|
if (!linked) return 1;
|
|
44257
44824
|
const res = await postJson(
|
|
44258
44825
|
fetchImpl,
|
|
44259
|
-
`${CLAIM_SYNC_BASE4}/api/claim/note
|
|
44826
|
+
`${CLAIM_SYNC_BASE4}/api/claim/note`,
|
|
44260
44827
|
{ bountyId: founderPostingIdOf(claim), claimId, body },
|
|
44261
44828
|
linked
|
|
44262
44829
|
);
|
|
44263
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
|
+
}
|
|
44264
44853
|
err(`terminalhire claim note: ${res.message}`);
|
|
44265
44854
|
for (const f of res.findings ?? []) {
|
|
44266
44855
|
err(` line ${f.line}: ${f.label}`);
|
|
@@ -44268,18 +44857,16 @@ async function cmdNote(id, flags = {}, deps = {}) {
|
|
|
44268
44857
|
return 1;
|
|
44269
44858
|
}
|
|
44270
44859
|
log("");
|
|
44271
|
-
|
|
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
|
+
}
|
|
44272
44866
|
log("");
|
|
44273
44867
|
for (const line of String(res.body.body).split("\n")) log(` \u2502 ${line}`);
|
|
44274
44868
|
log("");
|
|
44275
|
-
log(`
|
|
44276
|
-
log(` sha: ${res.body.bodySha256}`);
|
|
44277
|
-
log("");
|
|
44278
|
-
log(" Read it. If it says what you mean and carries nothing private, send it here:");
|
|
44279
|
-
log(` ${CLAIM_SYNC_BASE4}/dashboard/notes/${res.body.draftId}`);
|
|
44280
|
-
log("");
|
|
44281
|
-
log(" There is no approve command. The page needs a value only it can issue, which");
|
|
44282
|
-
log(" is what stops this tool from sending a note it just wrote.");
|
|
44869
|
+
log(` note: ${res.body.eventId} sent: ${res.body.sentAt}`);
|
|
44283
44870
|
log("");
|
|
44284
44871
|
return 0;
|
|
44285
44872
|
}
|
|
@@ -44321,7 +44908,9 @@ async function postJson(fetchImpl, url, body, linked) {
|
|
|
44321
44908
|
return {
|
|
44322
44909
|
ok: false,
|
|
44323
44910
|
message: typeof answer?.error === "string" && answer.error ? answer.error : `terminalhire answered ${res.status}`,
|
|
44324
|
-
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
|
|
44325
44914
|
};
|
|
44326
44915
|
}
|
|
44327
44916
|
return { ok: true, body: answer ?? {} };
|
|
@@ -44540,12 +45129,8 @@ async function run() {
|
|
|
44540
45129
|
// is three years of muscle memory and `note` is what you leave.
|
|
44541
45130
|
case "note": {
|
|
44542
45131
|
if (positional[0] === "approve") {
|
|
44543
|
-
console.error(
|
|
44544
|
-
|
|
44545
|
-
);
|
|
44546
|
-
console.error(
|
|
44547
|
-
' Draft with `terminalhire claim note <id> --body "..."` \u2014 it prints the link.'
|
|
44548
|
-
);
|
|
45132
|
+
console.error("terminalhire claim note approve: removed. `claim note` sends directly.");
|
|
45133
|
+
console.error(' Run `terminalhire claim note <id> --body "..."`.');
|
|
44549
45134
|
process.exit(1);
|
|
44550
45135
|
}
|
|
44551
45136
|
const code = await cmdNote(positional[0], flags);
|