terminalhire 0.42.28 → 0.42.29
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 +183 -2
- package/dist/bin/jpi-dispatch.js +183 -2
- package/dist/bin/jpi-mcp.js +183 -2
- package/dist/bin/jpi-run.js +183 -2
- package/dist/bin/proxy/egressProxy.js +5 -0
- package/package.json +1 -1
package/dist/bin/jpi-claim.js
CHANGED
|
@@ -27022,6 +27022,11 @@ var init_egressProxy = __esm({
|
|
|
27022
27022
|
"use strict";
|
|
27023
27023
|
DEFAULT_INSTALL_ALLOWLIST = [
|
|
27024
27024
|
"registry.npmjs.org",
|
|
27025
|
+
// TERM-1240. `yarn install` is derived for every repo with a yarn.lock, and yarn
|
|
27026
|
+
// classic writes this host into each lockfile entry — so every yarn repo could start
|
|
27027
|
+
// and could never install, the TERM-1139 shape again. It serves the same packages as
|
|
27028
|
+
// the npm registry, so the grant widens where a package comes from, not what can come.
|
|
27029
|
+
"registry.yarnpkg.com",
|
|
27025
27030
|
"pypi.org",
|
|
27026
27031
|
"files.pythonhosted.org",
|
|
27027
27032
|
"proxy.golang.org",
|
|
@@ -33046,6 +33051,151 @@ function nodeInstallCommand(repo) {
|
|
|
33046
33051
|
return { command: "npm install", from: "manifest" };
|
|
33047
33052
|
return null;
|
|
33048
33053
|
}
|
|
33054
|
+
function lockfileGaps(repo) {
|
|
33055
|
+
const pkg = readJsonObject(repo, "package.json");
|
|
33056
|
+
if (pkg === null)
|
|
33057
|
+
return null;
|
|
33058
|
+
const wanted = [];
|
|
33059
|
+
for (const section of DEPENDENCY_SECTIONS) {
|
|
33060
|
+
const deps = readObjectField(pkg, section);
|
|
33061
|
+
if (deps === null)
|
|
33062
|
+
continue;
|
|
33063
|
+
for (const [name, range] of Object.entries(deps)) {
|
|
33064
|
+
if (typeof range === "string")
|
|
33065
|
+
wanted.push([name, range]);
|
|
33066
|
+
}
|
|
33067
|
+
}
|
|
33068
|
+
if (wanted.length === 0)
|
|
33069
|
+
return null;
|
|
33070
|
+
const check = (lockfile, listed) => {
|
|
33071
|
+
if (listed === null)
|
|
33072
|
+
return null;
|
|
33073
|
+
const missing = wanted.filter(([n, r]) => !listed(n, r)).map(([n, r]) => `${n}@${r}`);
|
|
33074
|
+
return missing.length === 0 ? null : { lockfile, missing };
|
|
33075
|
+
};
|
|
33076
|
+
for (const lockfile of ["package-lock.json", "npm-shrinkwrap.json"]) {
|
|
33077
|
+
if (repo.exists(lockfile))
|
|
33078
|
+
return check(lockfile, npmLockListing(repo, lockfile));
|
|
33079
|
+
}
|
|
33080
|
+
if (repo.exists("yarn.lock"))
|
|
33081
|
+
return check("yarn.lock", yarnLockListing(repo.readText("yarn.lock")));
|
|
33082
|
+
if (repo.exists("pnpm-lock.yaml")) {
|
|
33083
|
+
return check("pnpm-lock.yaml", pnpmLockListing(repo.readText("pnpm-lock.yaml")));
|
|
33084
|
+
}
|
|
33085
|
+
return null;
|
|
33086
|
+
}
|
|
33087
|
+
function npmLockListing(repo, lockfile) {
|
|
33088
|
+
const lock = readJsonObject(repo, lockfile);
|
|
33089
|
+
if (lock === null)
|
|
33090
|
+
return null;
|
|
33091
|
+
const root = readObjectField(readObjectField(lock, "packages"), "");
|
|
33092
|
+
if (root !== null) {
|
|
33093
|
+
return (name, range) => DEPENDENCY_SECTIONS.some((s) => readStringField(readObjectField(root, s), name) === range);
|
|
33094
|
+
}
|
|
33095
|
+
const v1 = readObjectField(lock, "dependencies");
|
|
33096
|
+
if (v1 === null)
|
|
33097
|
+
return null;
|
|
33098
|
+
return (name) => readObjectField(v1, name) !== null;
|
|
33099
|
+
}
|
|
33100
|
+
function yarnLockListing(text) {
|
|
33101
|
+
if (text === null || !/^(# yarn lockfile v1|__metadata:)/m.test(text))
|
|
33102
|
+
return null;
|
|
33103
|
+
const specs = /* @__PURE__ */ new Set();
|
|
33104
|
+
for (const line of text.split(/\r?\n/)) {
|
|
33105
|
+
if (line === "" || line.startsWith(" ") || line.startsWith("#") || !line.endsWith(":"))
|
|
33106
|
+
continue;
|
|
33107
|
+
for (const raw of line.slice(0, -1).split(",")) {
|
|
33108
|
+
const spec = raw.trim().replace(/^"|"$/g, "");
|
|
33109
|
+
specs.add(spec);
|
|
33110
|
+
const berry = /^(@?[^@]+)@npm:(.+)$/.exec(spec);
|
|
33111
|
+
if (berry !== null)
|
|
33112
|
+
specs.add(`${berry[1]}@${berry[2]}`);
|
|
33113
|
+
}
|
|
33114
|
+
}
|
|
33115
|
+
return (name, range) => specs.has(`${name}@${range}`);
|
|
33116
|
+
}
|
|
33117
|
+
function pnpmLockListing(text) {
|
|
33118
|
+
if (text === null || !/^lockfileVersion:/m.test(text))
|
|
33119
|
+
return null;
|
|
33120
|
+
const lines = pnpmRootLines(text.split(/\r?\n/));
|
|
33121
|
+
if (lines === null)
|
|
33122
|
+
return null;
|
|
33123
|
+
const unquote = (s) => s.replace(/^['"]|['"]$/g, "");
|
|
33124
|
+
return (name, range) => lines.some((line, i) => {
|
|
33125
|
+
if (unquote(line) === `${name}:` || line === `'${name}':` || line === `"${name}":`) {
|
|
33126
|
+
const next = lines[i + 1] ?? "";
|
|
33127
|
+
return next.startsWith("specifier:") && unquote(next.slice(10).trim()) === range;
|
|
33128
|
+
}
|
|
33129
|
+
const flat = /^(['"]?)(.+)\1:\s*(.+)$/.exec(line);
|
|
33130
|
+
return flat !== null && flat[2] === name && unquote(flat[3] ?? "") === range;
|
|
33131
|
+
});
|
|
33132
|
+
}
|
|
33133
|
+
function pnpmRootLines(raw) {
|
|
33134
|
+
const importers = raw.indexOf("importers:");
|
|
33135
|
+
if (importers === -1) {
|
|
33136
|
+
const end = raw.indexOf("packages:");
|
|
33137
|
+
return (end === -1 ? raw : raw.slice(0, end)).map((l) => l.trim());
|
|
33138
|
+
}
|
|
33139
|
+
const start = raw.findIndex((l, i) => i > importers && /^ {2}(['"]?)\.\1:\s*$/.test(l));
|
|
33140
|
+
if (start === -1)
|
|
33141
|
+
return null;
|
|
33142
|
+
const out = [];
|
|
33143
|
+
for (const line of raw.slice(start + 1)) {
|
|
33144
|
+
if (line.trim() === "")
|
|
33145
|
+
continue;
|
|
33146
|
+
if (!line.startsWith(" "))
|
|
33147
|
+
break;
|
|
33148
|
+
out.push(line.trim());
|
|
33149
|
+
}
|
|
33150
|
+
return out;
|
|
33151
|
+
}
|
|
33152
|
+
function unfrozenInstall(command) {
|
|
33153
|
+
const lead = /^(?:(?:sudo|time)\s+|env(?:\s+[A-Za-z_]\w*=\S*)*\s+)*/.exec(command)?.[0] ?? "";
|
|
33154
|
+
const rest = command.slice(lead.length);
|
|
33155
|
+
const end = installEnd(rest);
|
|
33156
|
+
const head = rest.slice(0, end);
|
|
33157
|
+
const tail2 = rest.slice(end);
|
|
33158
|
+
const dropped = (flag) => head.replace(new RegExp(`\\s--${flag}(=\\S*)?(?=\\s|$)`, "g"), "");
|
|
33159
|
+
if (/^npm ci(\s|$)/.test(head)) {
|
|
33160
|
+
return lead + head.replace(/^npm ci/, "npm install --no-audit --no-fund") + tail2;
|
|
33161
|
+
}
|
|
33162
|
+
if (/^pnpm\b/.test(head)) {
|
|
33163
|
+
if (/\s--no-frozen-lockfile(?=\s|$)/.test(head))
|
|
33164
|
+
return command;
|
|
33165
|
+
const bare = dropped("frozen-lockfile");
|
|
33166
|
+
const withFlag = bare.replace(/^pnpm (install-test|install|it|i)(?=\s|$)/, "$& --no-frozen-lockfile");
|
|
33167
|
+
if (withFlag !== bare)
|
|
33168
|
+
return lead + withFlag + tail2;
|
|
33169
|
+
return bare === head ? command : `${lead}${bare} --no-frozen-lockfile${tail2}`;
|
|
33170
|
+
}
|
|
33171
|
+
if (/^yarn\b/.test(head)) {
|
|
33172
|
+
const bare = dropped("frozen-lockfile").replace(/\s--immutable(=\S*)?(?=\s|$)/g, "");
|
|
33173
|
+
const assigns = lead === "" || /(^|\s)env(\s+[A-Za-z_]\w*=\S*)*\s+$/.test(lead);
|
|
33174
|
+
return `${lead}${assigns ? "" : "env "}${YARN_MUTABLE} ${bare}${tail2}`;
|
|
33175
|
+
}
|
|
33176
|
+
return command;
|
|
33177
|
+
}
|
|
33178
|
+
function installEnd(command) {
|
|
33179
|
+
let quote = null;
|
|
33180
|
+
for (let i = 0; i < command.length; i += 1) {
|
|
33181
|
+
const c = command[i];
|
|
33182
|
+
if (quote !== null) {
|
|
33183
|
+
if (c === quote)
|
|
33184
|
+
quote = null;
|
|
33185
|
+
else if (c === "\\" && quote === '"')
|
|
33186
|
+
i += 1;
|
|
33187
|
+
continue;
|
|
33188
|
+
}
|
|
33189
|
+
if (c === "\\") {
|
|
33190
|
+
i += 1;
|
|
33191
|
+
} else if (c === '"' || c === "'") {
|
|
33192
|
+
quote = c;
|
|
33193
|
+
} else if (c === ";" || c === "&" && command[i + 1] === "&" || c === "|" && command[i + 1] === "|") {
|
|
33194
|
+
return command.slice(0, i).trimEnd().length;
|
|
33195
|
+
}
|
|
33196
|
+
}
|
|
33197
|
+
return command.length;
|
|
33198
|
+
}
|
|
33049
33199
|
function pythonTestCommand(repo) {
|
|
33050
33200
|
const pyproject = repo.readText("pyproject.toml") ?? "";
|
|
33051
33201
|
if (pyproject.includes("[tool.pytest.ini_options]"))
|
|
@@ -33286,7 +33436,7 @@ function matchFirst(text, pattern) {
|
|
|
33286
33436
|
const match2 = pattern.exec(text);
|
|
33287
33437
|
return match2 === null ? null : match2[1];
|
|
33288
33438
|
}
|
|
33289
|
-
var RUNTIME_MANIFESTS, MAKEFILE_NAMES, C_FAMILY_SOURCE, MANIFEST_FILENAMES, JVM_BUILD_FILES, NPM_PLACEHOLDER_TEST, REQUIREMENT_SPECIFIER, CPP_BUILD_FILES, CMAKE_DECLARES_TESTS, EXACT_VERSION;
|
|
33439
|
+
var RUNTIME_MANIFESTS, MAKEFILE_NAMES, C_FAMILY_SOURCE, MANIFEST_FILENAMES, JVM_BUILD_FILES, NPM_PLACEHOLDER_TEST, DEPENDENCY_SECTIONS, YARN_MUTABLE, REQUIREMENT_SPECIFIER, CPP_BUILD_FILES, CMAKE_DECLARES_TESTS, EXACT_VERSION;
|
|
33290
33440
|
var init_manifest2 = __esm({
|
|
33291
33441
|
"../../packages/envspec/dist/manifest.js"() {
|
|
33292
33442
|
"use strict";
|
|
@@ -33307,8 +33457,19 @@ var init_manifest2 = __esm({
|
|
|
33307
33457
|
MANIFEST_FILENAMES = RUNTIME_MANIFESTS.flatMap((m) => m.files).concat(["*.csproj", "*.fsproj", "*.sln"], MAKEFILE_NAMES).sort();
|
|
33308
33458
|
JVM_BUILD_FILES = ["build.gradle", "build.gradle.kts", "pom.xml"];
|
|
33309
33459
|
NPM_PLACEHOLDER_TEST = /^echo\s+["']?Error:\s*no test specified["']?\s*&&\s*exit\s+1$/;
|
|
33460
|
+
DEPENDENCY_SECTIONS = [
|
|
33461
|
+
"dependencies",
|
|
33462
|
+
"devDependencies",
|
|
33463
|
+
"optionalDependencies",
|
|
33464
|
+
"peerDependencies"
|
|
33465
|
+
];
|
|
33466
|
+
YARN_MUTABLE = "YARN_ENABLE_IMMUTABLE_INSTALLS=false";
|
|
33310
33467
|
REQUIREMENT_SPECIFIER = /^[A-Za-z0-9._-]+(\[[A-Za-z0-9._,-]+\])?([<>=!~]=?[A-Za-z0-9._*+-]+(,[<>=!~]=?[A-Za-z0-9._*+-]+)*)?$/;
|
|
33311
|
-
CPP_BUILD_FILES = [
|
|
33468
|
+
CPP_BUILD_FILES = [
|
|
33469
|
+
"CMakeLists.txt",
|
|
33470
|
+
"meson.build",
|
|
33471
|
+
...MAKEFILE_NAMES
|
|
33472
|
+
];
|
|
33312
33473
|
CMAKE_DECLARES_TESTS = /\benable_testing\s*\(|\binclude\s*\(\s*CTest\s*\)|\badd_test\s*\(/;
|
|
33313
33474
|
EXACT_VERSION = /^v?(\d+(?:\.\d+){0,2})$/;
|
|
33314
33475
|
}
|
|
@@ -33760,6 +33921,15 @@ function deriveFromReader(repo) {
|
|
|
33760
33921
|
derivedFrom.add(fallback.from);
|
|
33761
33922
|
}
|
|
33762
33923
|
}
|
|
33924
|
+
let installUnfrozen = null;
|
|
33925
|
+
if (installCommand !== null && runtime === "node") {
|
|
33926
|
+
const unfrozen = unfrozenInstall(installCommand);
|
|
33927
|
+
const gaps = unfrozen === installCommand ? null : lockfileGaps(repo);
|
|
33928
|
+
if (gaps !== null) {
|
|
33929
|
+
installCommand = unfrozen;
|
|
33930
|
+
installUnfrozen = { lockfile: gaps.lockfile, missing: gaps.missing };
|
|
33931
|
+
}
|
|
33932
|
+
}
|
|
33763
33933
|
if (installCommand === null) {
|
|
33764
33934
|
unresolved.push({
|
|
33765
33935
|
kind: "no-install-command",
|
|
@@ -33806,6 +33976,7 @@ function deriveFromReader(repo) {
|
|
|
33806
33976
|
runtime,
|
|
33807
33977
|
runtimeVersion: version.value,
|
|
33808
33978
|
installCommand,
|
|
33979
|
+
...installUnfrozen === null ? {} : { installUnfrozen },
|
|
33809
33980
|
testCommand,
|
|
33810
33981
|
services,
|
|
33811
33982
|
migrations,
|
|
@@ -34591,6 +34762,12 @@ function resolveRunEnvironment(derived, req) {
|
|
|
34591
34762
|
) : "detected";
|
|
34592
34763
|
return { spec, imageSource };
|
|
34593
34764
|
}
|
|
34765
|
+
function installUnfrozenNote(spec) {
|
|
34766
|
+
const u = spec.installUnfrozen;
|
|
34767
|
+
if (u === void 0)
|
|
34768
|
+
return null;
|
|
34769
|
+
return `${u.lockfile} does not list ${u.missing.join(", ")}, so the install ran as \`${String(spec.installCommand)}\` without its frozen-lockfile check. Regenerate ${u.lockfile} before merging.`;
|
|
34770
|
+
}
|
|
34594
34771
|
async function verifyWorkingDiff(req) {
|
|
34595
34772
|
const ctx = {
|
|
34596
34773
|
startedAt: Date.now(),
|
|
@@ -34790,6 +34967,9 @@ async function runVerification(req, ctx) {
|
|
|
34790
34967
|
const derived = deriveEnvironmentSpec(cloneDir);
|
|
34791
34968
|
const { spec, imageSource } = resolveRunEnvironment(derived, req);
|
|
34792
34969
|
progress("derive", `runtime=${spec.runtime} install=${String(spec.installCommand)} test=${String(spec.testCommand)}`);
|
|
34970
|
+
const unfrozenNote = installUnfrozenNote(spec);
|
|
34971
|
+
if (unfrozenNote !== null)
|
|
34972
|
+
progress("derive", unfrozenNote);
|
|
34793
34973
|
refuseUnbuildableSpec(spec);
|
|
34794
34974
|
const image = placement.imageFor(spec.runtime, req.image, spec.runtimeVersion, imageVariantFor(spec));
|
|
34795
34975
|
const resolved = await resolveLease(placement, runId);
|
|
@@ -35922,6 +36102,7 @@ __export(dist_exports, {
|
|
|
35922
36102
|
imageVariantFor: () => imageVariantFor,
|
|
35923
36103
|
installCommandFor: () => installCommandFor,
|
|
35924
36104
|
installLocalMigrationTooling: () => installLocalMigrationTooling,
|
|
36105
|
+
installUnfrozenNote: () => installUnfrozenNote,
|
|
35925
36106
|
isBookkeepingTable: () => isBookkeepingTable,
|
|
35926
36107
|
isCommandUnavailable: () => isCommandUnavailable,
|
|
35927
36108
|
isGreen: () => isGreen,
|
package/dist/bin/jpi-dispatch.js
CHANGED
|
@@ -32765,6 +32765,11 @@ var init_egressProxy = __esm({
|
|
|
32765
32765
|
"use strict";
|
|
32766
32766
|
DEFAULT_INSTALL_ALLOWLIST = [
|
|
32767
32767
|
"registry.npmjs.org",
|
|
32768
|
+
// TERM-1240. `yarn install` is derived for every repo with a yarn.lock, and yarn
|
|
32769
|
+
// classic writes this host into each lockfile entry — so every yarn repo could start
|
|
32770
|
+
// and could never install, the TERM-1139 shape again. It serves the same packages as
|
|
32771
|
+
// the npm registry, so the grant widens where a package comes from, not what can come.
|
|
32772
|
+
"registry.yarnpkg.com",
|
|
32768
32773
|
"pypi.org",
|
|
32769
32774
|
"files.pythonhosted.org",
|
|
32770
32775
|
"proxy.golang.org",
|
|
@@ -38789,6 +38794,151 @@ function nodeInstallCommand(repo) {
|
|
|
38789
38794
|
return { command: "npm install", from: "manifest" };
|
|
38790
38795
|
return null;
|
|
38791
38796
|
}
|
|
38797
|
+
function lockfileGaps(repo) {
|
|
38798
|
+
const pkg = readJsonObject(repo, "package.json");
|
|
38799
|
+
if (pkg === null)
|
|
38800
|
+
return null;
|
|
38801
|
+
const wanted = [];
|
|
38802
|
+
for (const section2 of DEPENDENCY_SECTIONS) {
|
|
38803
|
+
const deps = readObjectField(pkg, section2);
|
|
38804
|
+
if (deps === null)
|
|
38805
|
+
continue;
|
|
38806
|
+
for (const [name, range] of Object.entries(deps)) {
|
|
38807
|
+
if (typeof range === "string")
|
|
38808
|
+
wanted.push([name, range]);
|
|
38809
|
+
}
|
|
38810
|
+
}
|
|
38811
|
+
if (wanted.length === 0)
|
|
38812
|
+
return null;
|
|
38813
|
+
const check = (lockfile, listed) => {
|
|
38814
|
+
if (listed === null)
|
|
38815
|
+
return null;
|
|
38816
|
+
const missing = wanted.filter(([n, r]) => !listed(n, r)).map(([n, r]) => `${n}@${r}`);
|
|
38817
|
+
return missing.length === 0 ? null : { lockfile, missing };
|
|
38818
|
+
};
|
|
38819
|
+
for (const lockfile of ["package-lock.json", "npm-shrinkwrap.json"]) {
|
|
38820
|
+
if (repo.exists(lockfile))
|
|
38821
|
+
return check(lockfile, npmLockListing(repo, lockfile));
|
|
38822
|
+
}
|
|
38823
|
+
if (repo.exists("yarn.lock"))
|
|
38824
|
+
return check("yarn.lock", yarnLockListing(repo.readText("yarn.lock")));
|
|
38825
|
+
if (repo.exists("pnpm-lock.yaml")) {
|
|
38826
|
+
return check("pnpm-lock.yaml", pnpmLockListing(repo.readText("pnpm-lock.yaml")));
|
|
38827
|
+
}
|
|
38828
|
+
return null;
|
|
38829
|
+
}
|
|
38830
|
+
function npmLockListing(repo, lockfile) {
|
|
38831
|
+
const lock = readJsonObject(repo, lockfile);
|
|
38832
|
+
if (lock === null)
|
|
38833
|
+
return null;
|
|
38834
|
+
const root = readObjectField(readObjectField(lock, "packages"), "");
|
|
38835
|
+
if (root !== null) {
|
|
38836
|
+
return (name, range) => DEPENDENCY_SECTIONS.some((s) => readStringField(readObjectField(root, s), name) === range);
|
|
38837
|
+
}
|
|
38838
|
+
const v1 = readObjectField(lock, "dependencies");
|
|
38839
|
+
if (v1 === null)
|
|
38840
|
+
return null;
|
|
38841
|
+
return (name) => readObjectField(v1, name) !== null;
|
|
38842
|
+
}
|
|
38843
|
+
function yarnLockListing(text) {
|
|
38844
|
+
if (text === null || !/^(# yarn lockfile v1|__metadata:)/m.test(text))
|
|
38845
|
+
return null;
|
|
38846
|
+
const specs = /* @__PURE__ */ new Set();
|
|
38847
|
+
for (const line of text.split(/\r?\n/)) {
|
|
38848
|
+
if (line === "" || line.startsWith(" ") || line.startsWith("#") || !line.endsWith(":"))
|
|
38849
|
+
continue;
|
|
38850
|
+
for (const raw of line.slice(0, -1).split(",")) {
|
|
38851
|
+
const spec = raw.trim().replace(/^"|"$/g, "");
|
|
38852
|
+
specs.add(spec);
|
|
38853
|
+
const berry = /^(@?[^@]+)@npm:(.+)$/.exec(spec);
|
|
38854
|
+
if (berry !== null)
|
|
38855
|
+
specs.add(`${berry[1]}@${berry[2]}`);
|
|
38856
|
+
}
|
|
38857
|
+
}
|
|
38858
|
+
return (name, range) => specs.has(`${name}@${range}`);
|
|
38859
|
+
}
|
|
38860
|
+
function pnpmLockListing(text) {
|
|
38861
|
+
if (text === null || !/^lockfileVersion:/m.test(text))
|
|
38862
|
+
return null;
|
|
38863
|
+
const lines = pnpmRootLines(text.split(/\r?\n/));
|
|
38864
|
+
if (lines === null)
|
|
38865
|
+
return null;
|
|
38866
|
+
const unquote = (s) => s.replace(/^['"]|['"]$/g, "");
|
|
38867
|
+
return (name, range) => lines.some((line, i) => {
|
|
38868
|
+
if (unquote(line) === `${name}:` || line === `'${name}':` || line === `"${name}":`) {
|
|
38869
|
+
const next = lines[i + 1] ?? "";
|
|
38870
|
+
return next.startsWith("specifier:") && unquote(next.slice(10).trim()) === range;
|
|
38871
|
+
}
|
|
38872
|
+
const flat = /^(['"]?)(.+)\1:\s*(.+)$/.exec(line);
|
|
38873
|
+
return flat !== null && flat[2] === name && unquote(flat[3] ?? "") === range;
|
|
38874
|
+
});
|
|
38875
|
+
}
|
|
38876
|
+
function pnpmRootLines(raw) {
|
|
38877
|
+
const importers = raw.indexOf("importers:");
|
|
38878
|
+
if (importers === -1) {
|
|
38879
|
+
const end = raw.indexOf("packages:");
|
|
38880
|
+
return (end === -1 ? raw : raw.slice(0, end)).map((l) => l.trim());
|
|
38881
|
+
}
|
|
38882
|
+
const start = raw.findIndex((l, i) => i > importers && /^ {2}(['"]?)\.\1:\s*$/.test(l));
|
|
38883
|
+
if (start === -1)
|
|
38884
|
+
return null;
|
|
38885
|
+
const out = [];
|
|
38886
|
+
for (const line of raw.slice(start + 1)) {
|
|
38887
|
+
if (line.trim() === "")
|
|
38888
|
+
continue;
|
|
38889
|
+
if (!line.startsWith(" "))
|
|
38890
|
+
break;
|
|
38891
|
+
out.push(line.trim());
|
|
38892
|
+
}
|
|
38893
|
+
return out;
|
|
38894
|
+
}
|
|
38895
|
+
function unfrozenInstall(command) {
|
|
38896
|
+
const lead = /^(?:(?:sudo|time)\s+|env(?:\s+[A-Za-z_]\w*=\S*)*\s+)*/.exec(command)?.[0] ?? "";
|
|
38897
|
+
const rest = command.slice(lead.length);
|
|
38898
|
+
const end = installEnd(rest);
|
|
38899
|
+
const head = rest.slice(0, end);
|
|
38900
|
+
const tail2 = rest.slice(end);
|
|
38901
|
+
const dropped = (flag) => head.replace(new RegExp(`\\s--${flag}(=\\S*)?(?=\\s|$)`, "g"), "");
|
|
38902
|
+
if (/^npm ci(\s|$)/.test(head)) {
|
|
38903
|
+
return lead + head.replace(/^npm ci/, "npm install --no-audit --no-fund") + tail2;
|
|
38904
|
+
}
|
|
38905
|
+
if (/^pnpm\b/.test(head)) {
|
|
38906
|
+
if (/\s--no-frozen-lockfile(?=\s|$)/.test(head))
|
|
38907
|
+
return command;
|
|
38908
|
+
const bare = dropped("frozen-lockfile");
|
|
38909
|
+
const withFlag = bare.replace(/^pnpm (install-test|install|it|i)(?=\s|$)/, "$& --no-frozen-lockfile");
|
|
38910
|
+
if (withFlag !== bare)
|
|
38911
|
+
return lead + withFlag + tail2;
|
|
38912
|
+
return bare === head ? command : `${lead}${bare} --no-frozen-lockfile${tail2}`;
|
|
38913
|
+
}
|
|
38914
|
+
if (/^yarn\b/.test(head)) {
|
|
38915
|
+
const bare = dropped("frozen-lockfile").replace(/\s--immutable(=\S*)?(?=\s|$)/g, "");
|
|
38916
|
+
const assigns = lead === "" || /(^|\s)env(\s+[A-Za-z_]\w*=\S*)*\s+$/.test(lead);
|
|
38917
|
+
return `${lead}${assigns ? "" : "env "}${YARN_MUTABLE} ${bare}${tail2}`;
|
|
38918
|
+
}
|
|
38919
|
+
return command;
|
|
38920
|
+
}
|
|
38921
|
+
function installEnd(command) {
|
|
38922
|
+
let quote = null;
|
|
38923
|
+
for (let i = 0; i < command.length; i += 1) {
|
|
38924
|
+
const c = command[i];
|
|
38925
|
+
if (quote !== null) {
|
|
38926
|
+
if (c === quote)
|
|
38927
|
+
quote = null;
|
|
38928
|
+
else if (c === "\\" && quote === '"')
|
|
38929
|
+
i += 1;
|
|
38930
|
+
continue;
|
|
38931
|
+
}
|
|
38932
|
+
if (c === "\\") {
|
|
38933
|
+
i += 1;
|
|
38934
|
+
} else if (c === '"' || c === "'") {
|
|
38935
|
+
quote = c;
|
|
38936
|
+
} else if (c === ";" || c === "&" && command[i + 1] === "&" || c === "|" && command[i + 1] === "|") {
|
|
38937
|
+
return command.slice(0, i).trimEnd().length;
|
|
38938
|
+
}
|
|
38939
|
+
}
|
|
38940
|
+
return command.length;
|
|
38941
|
+
}
|
|
38792
38942
|
function pythonTestCommand(repo) {
|
|
38793
38943
|
const pyproject = repo.readText("pyproject.toml") ?? "";
|
|
38794
38944
|
if (pyproject.includes("[tool.pytest.ini_options]"))
|
|
@@ -39029,7 +39179,7 @@ function matchFirst(text, pattern) {
|
|
|
39029
39179
|
const match2 = pattern.exec(text);
|
|
39030
39180
|
return match2 === null ? null : match2[1];
|
|
39031
39181
|
}
|
|
39032
|
-
var RUNTIME_MANIFESTS, MAKEFILE_NAMES, C_FAMILY_SOURCE, MANIFEST_FILENAMES, JVM_BUILD_FILES, NPM_PLACEHOLDER_TEST, REQUIREMENT_SPECIFIER, CPP_BUILD_FILES, CMAKE_DECLARES_TESTS, EXACT_VERSION;
|
|
39182
|
+
var RUNTIME_MANIFESTS, MAKEFILE_NAMES, C_FAMILY_SOURCE, MANIFEST_FILENAMES, JVM_BUILD_FILES, NPM_PLACEHOLDER_TEST, DEPENDENCY_SECTIONS, YARN_MUTABLE, REQUIREMENT_SPECIFIER, CPP_BUILD_FILES, CMAKE_DECLARES_TESTS, EXACT_VERSION;
|
|
39033
39183
|
var init_manifest2 = __esm({
|
|
39034
39184
|
"../../packages/envspec/dist/manifest.js"() {
|
|
39035
39185
|
"use strict";
|
|
@@ -39050,8 +39200,19 @@ var init_manifest2 = __esm({
|
|
|
39050
39200
|
MANIFEST_FILENAMES = RUNTIME_MANIFESTS.flatMap((m) => m.files).concat(["*.csproj", "*.fsproj", "*.sln"], MAKEFILE_NAMES).sort();
|
|
39051
39201
|
JVM_BUILD_FILES = ["build.gradle", "build.gradle.kts", "pom.xml"];
|
|
39052
39202
|
NPM_PLACEHOLDER_TEST = /^echo\s+["']?Error:\s*no test specified["']?\s*&&\s*exit\s+1$/;
|
|
39203
|
+
DEPENDENCY_SECTIONS = [
|
|
39204
|
+
"dependencies",
|
|
39205
|
+
"devDependencies",
|
|
39206
|
+
"optionalDependencies",
|
|
39207
|
+
"peerDependencies"
|
|
39208
|
+
];
|
|
39209
|
+
YARN_MUTABLE = "YARN_ENABLE_IMMUTABLE_INSTALLS=false";
|
|
39053
39210
|
REQUIREMENT_SPECIFIER = /^[A-Za-z0-9._-]+(\[[A-Za-z0-9._,-]+\])?([<>=!~]=?[A-Za-z0-9._*+-]+(,[<>=!~]=?[A-Za-z0-9._*+-]+)*)?$/;
|
|
39054
|
-
CPP_BUILD_FILES = [
|
|
39211
|
+
CPP_BUILD_FILES = [
|
|
39212
|
+
"CMakeLists.txt",
|
|
39213
|
+
"meson.build",
|
|
39214
|
+
...MAKEFILE_NAMES
|
|
39215
|
+
];
|
|
39055
39216
|
CMAKE_DECLARES_TESTS = /\benable_testing\s*\(|\binclude\s*\(\s*CTest\s*\)|\badd_test\s*\(/;
|
|
39056
39217
|
EXACT_VERSION = /^v?(\d+(?:\.\d+){0,2})$/;
|
|
39057
39218
|
}
|
|
@@ -39503,6 +39664,15 @@ function deriveFromReader(repo) {
|
|
|
39503
39664
|
derivedFrom.add(fallback.from);
|
|
39504
39665
|
}
|
|
39505
39666
|
}
|
|
39667
|
+
let installUnfrozen = null;
|
|
39668
|
+
if (installCommand !== null && runtime === "node") {
|
|
39669
|
+
const unfrozen = unfrozenInstall(installCommand);
|
|
39670
|
+
const gaps = unfrozen === installCommand ? null : lockfileGaps(repo);
|
|
39671
|
+
if (gaps !== null) {
|
|
39672
|
+
installCommand = unfrozen;
|
|
39673
|
+
installUnfrozen = { lockfile: gaps.lockfile, missing: gaps.missing };
|
|
39674
|
+
}
|
|
39675
|
+
}
|
|
39506
39676
|
if (installCommand === null) {
|
|
39507
39677
|
unresolved.push({
|
|
39508
39678
|
kind: "no-install-command",
|
|
@@ -39549,6 +39719,7 @@ function deriveFromReader(repo) {
|
|
|
39549
39719
|
runtime,
|
|
39550
39720
|
runtimeVersion: version2.value,
|
|
39551
39721
|
installCommand,
|
|
39722
|
+
...installUnfrozen === null ? {} : { installUnfrozen },
|
|
39552
39723
|
testCommand,
|
|
39553
39724
|
services,
|
|
39554
39725
|
migrations,
|
|
@@ -40334,6 +40505,12 @@ function resolveRunEnvironment(derived, req) {
|
|
|
40334
40505
|
) : "detected";
|
|
40335
40506
|
return { spec, imageSource };
|
|
40336
40507
|
}
|
|
40508
|
+
function installUnfrozenNote(spec) {
|
|
40509
|
+
const u = spec.installUnfrozen;
|
|
40510
|
+
if (u === void 0)
|
|
40511
|
+
return null;
|
|
40512
|
+
return `${u.lockfile} does not list ${u.missing.join(", ")}, so the install ran as \`${String(spec.installCommand)}\` without its frozen-lockfile check. Regenerate ${u.lockfile} before merging.`;
|
|
40513
|
+
}
|
|
40337
40514
|
async function verifyWorkingDiff(req) {
|
|
40338
40515
|
const ctx = {
|
|
40339
40516
|
startedAt: Date.now(),
|
|
@@ -40533,6 +40710,9 @@ async function runVerification(req, ctx) {
|
|
|
40533
40710
|
const derived = deriveEnvironmentSpec(cloneDir);
|
|
40534
40711
|
const { spec, imageSource } = resolveRunEnvironment(derived, req);
|
|
40535
40712
|
progress("derive", `runtime=${spec.runtime} install=${String(spec.installCommand)} test=${String(spec.testCommand)}`);
|
|
40713
|
+
const unfrozenNote = installUnfrozenNote(spec);
|
|
40714
|
+
if (unfrozenNote !== null)
|
|
40715
|
+
progress("derive", unfrozenNote);
|
|
40536
40716
|
refuseUnbuildableSpec(spec);
|
|
40537
40717
|
const image = placement.imageFor(spec.runtime, req.image, spec.runtimeVersion, imageVariantFor(spec));
|
|
40538
40718
|
const resolved = await resolveLease(placement, runId);
|
|
@@ -41665,6 +41845,7 @@ __export(dist_exports, {
|
|
|
41665
41845
|
imageVariantFor: () => imageVariantFor,
|
|
41666
41846
|
installCommandFor: () => installCommandFor,
|
|
41667
41847
|
installLocalMigrationTooling: () => installLocalMigrationTooling,
|
|
41848
|
+
installUnfrozenNote: () => installUnfrozenNote,
|
|
41668
41849
|
isBookkeepingTable: () => isBookkeepingTable,
|
|
41669
41850
|
isCommandUnavailable: () => isCommandUnavailable,
|
|
41670
41851
|
isGreen: () => isGreen,
|
package/dist/bin/jpi-mcp.js
CHANGED
|
@@ -27540,6 +27540,11 @@ var init_egressProxy = __esm({
|
|
|
27540
27540
|
"use strict";
|
|
27541
27541
|
DEFAULT_INSTALL_ALLOWLIST = [
|
|
27542
27542
|
"registry.npmjs.org",
|
|
27543
|
+
// TERM-1240. `yarn install` is derived for every repo with a yarn.lock, and yarn
|
|
27544
|
+
// classic writes this host into each lockfile entry — so every yarn repo could start
|
|
27545
|
+
// and could never install, the TERM-1139 shape again. It serves the same packages as
|
|
27546
|
+
// the npm registry, so the grant widens where a package comes from, not what can come.
|
|
27547
|
+
"registry.yarnpkg.com",
|
|
27543
27548
|
"pypi.org",
|
|
27544
27549
|
"files.pythonhosted.org",
|
|
27545
27550
|
"proxy.golang.org",
|
|
@@ -33564,6 +33569,151 @@ function nodeInstallCommand(repo) {
|
|
|
33564
33569
|
return { command: "npm install", from: "manifest" };
|
|
33565
33570
|
return null;
|
|
33566
33571
|
}
|
|
33572
|
+
function lockfileGaps(repo) {
|
|
33573
|
+
const pkg = readJsonObject(repo, "package.json");
|
|
33574
|
+
if (pkg === null)
|
|
33575
|
+
return null;
|
|
33576
|
+
const wanted = [];
|
|
33577
|
+
for (const section of DEPENDENCY_SECTIONS) {
|
|
33578
|
+
const deps = readObjectField(pkg, section);
|
|
33579
|
+
if (deps === null)
|
|
33580
|
+
continue;
|
|
33581
|
+
for (const [name, range] of Object.entries(deps)) {
|
|
33582
|
+
if (typeof range === "string")
|
|
33583
|
+
wanted.push([name, range]);
|
|
33584
|
+
}
|
|
33585
|
+
}
|
|
33586
|
+
if (wanted.length === 0)
|
|
33587
|
+
return null;
|
|
33588
|
+
const check = (lockfile, listed) => {
|
|
33589
|
+
if (listed === null)
|
|
33590
|
+
return null;
|
|
33591
|
+
const missing = wanted.filter(([n, r]) => !listed(n, r)).map(([n, r]) => `${n}@${r}`);
|
|
33592
|
+
return missing.length === 0 ? null : { lockfile, missing };
|
|
33593
|
+
};
|
|
33594
|
+
for (const lockfile of ["package-lock.json", "npm-shrinkwrap.json"]) {
|
|
33595
|
+
if (repo.exists(lockfile))
|
|
33596
|
+
return check(lockfile, npmLockListing(repo, lockfile));
|
|
33597
|
+
}
|
|
33598
|
+
if (repo.exists("yarn.lock"))
|
|
33599
|
+
return check("yarn.lock", yarnLockListing(repo.readText("yarn.lock")));
|
|
33600
|
+
if (repo.exists("pnpm-lock.yaml")) {
|
|
33601
|
+
return check("pnpm-lock.yaml", pnpmLockListing(repo.readText("pnpm-lock.yaml")));
|
|
33602
|
+
}
|
|
33603
|
+
return null;
|
|
33604
|
+
}
|
|
33605
|
+
function npmLockListing(repo, lockfile) {
|
|
33606
|
+
const lock = readJsonObject(repo, lockfile);
|
|
33607
|
+
if (lock === null)
|
|
33608
|
+
return null;
|
|
33609
|
+
const root = readObjectField(readObjectField(lock, "packages"), "");
|
|
33610
|
+
if (root !== null) {
|
|
33611
|
+
return (name, range) => DEPENDENCY_SECTIONS.some((s) => readStringField(readObjectField(root, s), name) === range);
|
|
33612
|
+
}
|
|
33613
|
+
const v1 = readObjectField(lock, "dependencies");
|
|
33614
|
+
if (v1 === null)
|
|
33615
|
+
return null;
|
|
33616
|
+
return (name) => readObjectField(v1, name) !== null;
|
|
33617
|
+
}
|
|
33618
|
+
function yarnLockListing(text) {
|
|
33619
|
+
if (text === null || !/^(# yarn lockfile v1|__metadata:)/m.test(text))
|
|
33620
|
+
return null;
|
|
33621
|
+
const specs = /* @__PURE__ */ new Set();
|
|
33622
|
+
for (const line of text.split(/\r?\n/)) {
|
|
33623
|
+
if (line === "" || line.startsWith(" ") || line.startsWith("#") || !line.endsWith(":"))
|
|
33624
|
+
continue;
|
|
33625
|
+
for (const raw of line.slice(0, -1).split(",")) {
|
|
33626
|
+
const spec = raw.trim().replace(/^"|"$/g, "");
|
|
33627
|
+
specs.add(spec);
|
|
33628
|
+
const berry = /^(@?[^@]+)@npm:(.+)$/.exec(spec);
|
|
33629
|
+
if (berry !== null)
|
|
33630
|
+
specs.add(`${berry[1]}@${berry[2]}`);
|
|
33631
|
+
}
|
|
33632
|
+
}
|
|
33633
|
+
return (name, range) => specs.has(`${name}@${range}`);
|
|
33634
|
+
}
|
|
33635
|
+
function pnpmLockListing(text) {
|
|
33636
|
+
if (text === null || !/^lockfileVersion:/m.test(text))
|
|
33637
|
+
return null;
|
|
33638
|
+
const lines = pnpmRootLines(text.split(/\r?\n/));
|
|
33639
|
+
if (lines === null)
|
|
33640
|
+
return null;
|
|
33641
|
+
const unquote = (s) => s.replace(/^['"]|['"]$/g, "");
|
|
33642
|
+
return (name, range) => lines.some((line, i) => {
|
|
33643
|
+
if (unquote(line) === `${name}:` || line === `'${name}':` || line === `"${name}":`) {
|
|
33644
|
+
const next = lines[i + 1] ?? "";
|
|
33645
|
+
return next.startsWith("specifier:") && unquote(next.slice(10).trim()) === range;
|
|
33646
|
+
}
|
|
33647
|
+
const flat = /^(['"]?)(.+)\1:\s*(.+)$/.exec(line);
|
|
33648
|
+
return flat !== null && flat[2] === name && unquote(flat[3] ?? "") === range;
|
|
33649
|
+
});
|
|
33650
|
+
}
|
|
33651
|
+
function pnpmRootLines(raw) {
|
|
33652
|
+
const importers = raw.indexOf("importers:");
|
|
33653
|
+
if (importers === -1) {
|
|
33654
|
+
const end = raw.indexOf("packages:");
|
|
33655
|
+
return (end === -1 ? raw : raw.slice(0, end)).map((l) => l.trim());
|
|
33656
|
+
}
|
|
33657
|
+
const start = raw.findIndex((l, i) => i > importers && /^ {2}(['"]?)\.\1:\s*$/.test(l));
|
|
33658
|
+
if (start === -1)
|
|
33659
|
+
return null;
|
|
33660
|
+
const out = [];
|
|
33661
|
+
for (const line of raw.slice(start + 1)) {
|
|
33662
|
+
if (line.trim() === "")
|
|
33663
|
+
continue;
|
|
33664
|
+
if (!line.startsWith(" "))
|
|
33665
|
+
break;
|
|
33666
|
+
out.push(line.trim());
|
|
33667
|
+
}
|
|
33668
|
+
return out;
|
|
33669
|
+
}
|
|
33670
|
+
function unfrozenInstall(command) {
|
|
33671
|
+
const lead = /^(?:(?:sudo|time)\s+|env(?:\s+[A-Za-z_]\w*=\S*)*\s+)*/.exec(command)?.[0] ?? "";
|
|
33672
|
+
const rest = command.slice(lead.length);
|
|
33673
|
+
const end = installEnd(rest);
|
|
33674
|
+
const head = rest.slice(0, end);
|
|
33675
|
+
const tail2 = rest.slice(end);
|
|
33676
|
+
const dropped = (flag) => head.replace(new RegExp(`\\s--${flag}(=\\S*)?(?=\\s|$)`, "g"), "");
|
|
33677
|
+
if (/^npm ci(\s|$)/.test(head)) {
|
|
33678
|
+
return lead + head.replace(/^npm ci/, "npm install --no-audit --no-fund") + tail2;
|
|
33679
|
+
}
|
|
33680
|
+
if (/^pnpm\b/.test(head)) {
|
|
33681
|
+
if (/\s--no-frozen-lockfile(?=\s|$)/.test(head))
|
|
33682
|
+
return command;
|
|
33683
|
+
const bare = dropped("frozen-lockfile");
|
|
33684
|
+
const withFlag = bare.replace(/^pnpm (install-test|install|it|i)(?=\s|$)/, "$& --no-frozen-lockfile");
|
|
33685
|
+
if (withFlag !== bare)
|
|
33686
|
+
return lead + withFlag + tail2;
|
|
33687
|
+
return bare === head ? command : `${lead}${bare} --no-frozen-lockfile${tail2}`;
|
|
33688
|
+
}
|
|
33689
|
+
if (/^yarn\b/.test(head)) {
|
|
33690
|
+
const bare = dropped("frozen-lockfile").replace(/\s--immutable(=\S*)?(?=\s|$)/g, "");
|
|
33691
|
+
const assigns = lead === "" || /(^|\s)env(\s+[A-Za-z_]\w*=\S*)*\s+$/.test(lead);
|
|
33692
|
+
return `${lead}${assigns ? "" : "env "}${YARN_MUTABLE} ${bare}${tail2}`;
|
|
33693
|
+
}
|
|
33694
|
+
return command;
|
|
33695
|
+
}
|
|
33696
|
+
function installEnd(command) {
|
|
33697
|
+
let quote = null;
|
|
33698
|
+
for (let i = 0; i < command.length; i += 1) {
|
|
33699
|
+
const c = command[i];
|
|
33700
|
+
if (quote !== null) {
|
|
33701
|
+
if (c === quote)
|
|
33702
|
+
quote = null;
|
|
33703
|
+
else if (c === "\\" && quote === '"')
|
|
33704
|
+
i += 1;
|
|
33705
|
+
continue;
|
|
33706
|
+
}
|
|
33707
|
+
if (c === "\\") {
|
|
33708
|
+
i += 1;
|
|
33709
|
+
} else if (c === '"' || c === "'") {
|
|
33710
|
+
quote = c;
|
|
33711
|
+
} else if (c === ";" || c === "&" && command[i + 1] === "&" || c === "|" && command[i + 1] === "|") {
|
|
33712
|
+
return command.slice(0, i).trimEnd().length;
|
|
33713
|
+
}
|
|
33714
|
+
}
|
|
33715
|
+
return command.length;
|
|
33716
|
+
}
|
|
33567
33717
|
function pythonTestCommand(repo) {
|
|
33568
33718
|
const pyproject = repo.readText("pyproject.toml") ?? "";
|
|
33569
33719
|
if (pyproject.includes("[tool.pytest.ini_options]"))
|
|
@@ -33804,7 +33954,7 @@ function matchFirst(text, pattern) {
|
|
|
33804
33954
|
const match2 = pattern.exec(text);
|
|
33805
33955
|
return match2 === null ? null : match2[1];
|
|
33806
33956
|
}
|
|
33807
|
-
var RUNTIME_MANIFESTS, MAKEFILE_NAMES, C_FAMILY_SOURCE, MANIFEST_FILENAMES, JVM_BUILD_FILES, NPM_PLACEHOLDER_TEST, REQUIREMENT_SPECIFIER, CPP_BUILD_FILES, CMAKE_DECLARES_TESTS, EXACT_VERSION;
|
|
33957
|
+
var RUNTIME_MANIFESTS, MAKEFILE_NAMES, C_FAMILY_SOURCE, MANIFEST_FILENAMES, JVM_BUILD_FILES, NPM_PLACEHOLDER_TEST, DEPENDENCY_SECTIONS, YARN_MUTABLE, REQUIREMENT_SPECIFIER, CPP_BUILD_FILES, CMAKE_DECLARES_TESTS, EXACT_VERSION;
|
|
33808
33958
|
var init_manifest2 = __esm({
|
|
33809
33959
|
"../../packages/envspec/dist/manifest.js"() {
|
|
33810
33960
|
"use strict";
|
|
@@ -33825,8 +33975,19 @@ var init_manifest2 = __esm({
|
|
|
33825
33975
|
MANIFEST_FILENAMES = RUNTIME_MANIFESTS.flatMap((m) => m.files).concat(["*.csproj", "*.fsproj", "*.sln"], MAKEFILE_NAMES).sort();
|
|
33826
33976
|
JVM_BUILD_FILES = ["build.gradle", "build.gradle.kts", "pom.xml"];
|
|
33827
33977
|
NPM_PLACEHOLDER_TEST = /^echo\s+["']?Error:\s*no test specified["']?\s*&&\s*exit\s+1$/;
|
|
33978
|
+
DEPENDENCY_SECTIONS = [
|
|
33979
|
+
"dependencies",
|
|
33980
|
+
"devDependencies",
|
|
33981
|
+
"optionalDependencies",
|
|
33982
|
+
"peerDependencies"
|
|
33983
|
+
];
|
|
33984
|
+
YARN_MUTABLE = "YARN_ENABLE_IMMUTABLE_INSTALLS=false";
|
|
33828
33985
|
REQUIREMENT_SPECIFIER = /^[A-Za-z0-9._-]+(\[[A-Za-z0-9._,-]+\])?([<>=!~]=?[A-Za-z0-9._*+-]+(,[<>=!~]=?[A-Za-z0-9._*+-]+)*)?$/;
|
|
33829
|
-
CPP_BUILD_FILES = [
|
|
33986
|
+
CPP_BUILD_FILES = [
|
|
33987
|
+
"CMakeLists.txt",
|
|
33988
|
+
"meson.build",
|
|
33989
|
+
...MAKEFILE_NAMES
|
|
33990
|
+
];
|
|
33830
33991
|
CMAKE_DECLARES_TESTS = /\benable_testing\s*\(|\binclude\s*\(\s*CTest\s*\)|\badd_test\s*\(/;
|
|
33831
33992
|
EXACT_VERSION = /^v?(\d+(?:\.\d+){0,2})$/;
|
|
33832
33993
|
}
|
|
@@ -34278,6 +34439,15 @@ function deriveFromReader(repo) {
|
|
|
34278
34439
|
derivedFrom.add(fallback.from);
|
|
34279
34440
|
}
|
|
34280
34441
|
}
|
|
34442
|
+
let installUnfrozen = null;
|
|
34443
|
+
if (installCommand !== null && runtime === "node") {
|
|
34444
|
+
const unfrozen = unfrozenInstall(installCommand);
|
|
34445
|
+
const gaps = unfrozen === installCommand ? null : lockfileGaps(repo);
|
|
34446
|
+
if (gaps !== null) {
|
|
34447
|
+
installCommand = unfrozen;
|
|
34448
|
+
installUnfrozen = { lockfile: gaps.lockfile, missing: gaps.missing };
|
|
34449
|
+
}
|
|
34450
|
+
}
|
|
34281
34451
|
if (installCommand === null) {
|
|
34282
34452
|
unresolved.push({
|
|
34283
34453
|
kind: "no-install-command",
|
|
@@ -34324,6 +34494,7 @@ function deriveFromReader(repo) {
|
|
|
34324
34494
|
runtime,
|
|
34325
34495
|
runtimeVersion: version2.value,
|
|
34326
34496
|
installCommand,
|
|
34497
|
+
...installUnfrozen === null ? {} : { installUnfrozen },
|
|
34327
34498
|
testCommand,
|
|
34328
34499
|
services,
|
|
34329
34500
|
migrations,
|
|
@@ -35109,6 +35280,12 @@ function resolveRunEnvironment(derived, req) {
|
|
|
35109
35280
|
) : "detected";
|
|
35110
35281
|
return { spec, imageSource };
|
|
35111
35282
|
}
|
|
35283
|
+
function installUnfrozenNote(spec) {
|
|
35284
|
+
const u = spec.installUnfrozen;
|
|
35285
|
+
if (u === void 0)
|
|
35286
|
+
return null;
|
|
35287
|
+
return `${u.lockfile} does not list ${u.missing.join(", ")}, so the install ran as \`${String(spec.installCommand)}\` without its frozen-lockfile check. Regenerate ${u.lockfile} before merging.`;
|
|
35288
|
+
}
|
|
35112
35289
|
async function verifyWorkingDiff(req) {
|
|
35113
35290
|
const ctx = {
|
|
35114
35291
|
startedAt: Date.now(),
|
|
@@ -35308,6 +35485,9 @@ async function runVerification(req, ctx) {
|
|
|
35308
35485
|
const derived = deriveEnvironmentSpec(cloneDir);
|
|
35309
35486
|
const { spec, imageSource } = resolveRunEnvironment(derived, req);
|
|
35310
35487
|
progress("derive", `runtime=${spec.runtime} install=${String(spec.installCommand)} test=${String(spec.testCommand)}`);
|
|
35488
|
+
const unfrozenNote = installUnfrozenNote(spec);
|
|
35489
|
+
if (unfrozenNote !== null)
|
|
35490
|
+
progress("derive", unfrozenNote);
|
|
35311
35491
|
refuseUnbuildableSpec(spec);
|
|
35312
35492
|
const image = placement.imageFor(spec.runtime, req.image, spec.runtimeVersion, imageVariantFor(spec));
|
|
35313
35493
|
const resolved = await resolveLease(placement, runId);
|
|
@@ -36440,6 +36620,7 @@ __export(dist_exports, {
|
|
|
36440
36620
|
imageVariantFor: () => imageVariantFor,
|
|
36441
36621
|
installCommandFor: () => installCommandFor,
|
|
36442
36622
|
installLocalMigrationTooling: () => installLocalMigrationTooling,
|
|
36623
|
+
installUnfrozenNote: () => installUnfrozenNote,
|
|
36443
36624
|
isBookkeepingTable: () => isBookkeepingTable,
|
|
36444
36625
|
isCommandUnavailable: () => isCommandUnavailable,
|
|
36445
36626
|
isGreen: () => isGreen,
|
package/dist/bin/jpi-run.js
CHANGED
|
@@ -1015,6 +1015,11 @@ var init_egressProxy = __esm({
|
|
|
1015
1015
|
"use strict";
|
|
1016
1016
|
DEFAULT_INSTALL_ALLOWLIST = [
|
|
1017
1017
|
"registry.npmjs.org",
|
|
1018
|
+
// TERM-1240. `yarn install` is derived for every repo with a yarn.lock, and yarn
|
|
1019
|
+
// classic writes this host into each lockfile entry — so every yarn repo could start
|
|
1020
|
+
// and could never install, the TERM-1139 shape again. It serves the same packages as
|
|
1021
|
+
// the npm registry, so the grant widens where a package comes from, not what can come.
|
|
1022
|
+
"registry.yarnpkg.com",
|
|
1018
1023
|
"pypi.org",
|
|
1019
1024
|
"files.pythonhosted.org",
|
|
1020
1025
|
"proxy.golang.org",
|
|
@@ -7039,6 +7044,151 @@ function nodeInstallCommand(repo) {
|
|
|
7039
7044
|
return { command: "npm install", from: "manifest" };
|
|
7040
7045
|
return null;
|
|
7041
7046
|
}
|
|
7047
|
+
function lockfileGaps(repo) {
|
|
7048
|
+
const pkg = readJsonObject(repo, "package.json");
|
|
7049
|
+
if (pkg === null)
|
|
7050
|
+
return null;
|
|
7051
|
+
const wanted = [];
|
|
7052
|
+
for (const section of DEPENDENCY_SECTIONS) {
|
|
7053
|
+
const deps = readObjectField(pkg, section);
|
|
7054
|
+
if (deps === null)
|
|
7055
|
+
continue;
|
|
7056
|
+
for (const [name, range] of Object.entries(deps)) {
|
|
7057
|
+
if (typeof range === "string")
|
|
7058
|
+
wanted.push([name, range]);
|
|
7059
|
+
}
|
|
7060
|
+
}
|
|
7061
|
+
if (wanted.length === 0)
|
|
7062
|
+
return null;
|
|
7063
|
+
const check = (lockfile, listed) => {
|
|
7064
|
+
if (listed === null)
|
|
7065
|
+
return null;
|
|
7066
|
+
const missing = wanted.filter(([n, r]) => !listed(n, r)).map(([n, r]) => `${n}@${r}`);
|
|
7067
|
+
return missing.length === 0 ? null : { lockfile, missing };
|
|
7068
|
+
};
|
|
7069
|
+
for (const lockfile of ["package-lock.json", "npm-shrinkwrap.json"]) {
|
|
7070
|
+
if (repo.exists(lockfile))
|
|
7071
|
+
return check(lockfile, npmLockListing(repo, lockfile));
|
|
7072
|
+
}
|
|
7073
|
+
if (repo.exists("yarn.lock"))
|
|
7074
|
+
return check("yarn.lock", yarnLockListing(repo.readText("yarn.lock")));
|
|
7075
|
+
if (repo.exists("pnpm-lock.yaml")) {
|
|
7076
|
+
return check("pnpm-lock.yaml", pnpmLockListing(repo.readText("pnpm-lock.yaml")));
|
|
7077
|
+
}
|
|
7078
|
+
return null;
|
|
7079
|
+
}
|
|
7080
|
+
function npmLockListing(repo, lockfile) {
|
|
7081
|
+
const lock = readJsonObject(repo, lockfile);
|
|
7082
|
+
if (lock === null)
|
|
7083
|
+
return null;
|
|
7084
|
+
const root = readObjectField(readObjectField(lock, "packages"), "");
|
|
7085
|
+
if (root !== null) {
|
|
7086
|
+
return (name, range) => DEPENDENCY_SECTIONS.some((s) => readStringField(readObjectField(root, s), name) === range);
|
|
7087
|
+
}
|
|
7088
|
+
const v1 = readObjectField(lock, "dependencies");
|
|
7089
|
+
if (v1 === null)
|
|
7090
|
+
return null;
|
|
7091
|
+
return (name) => readObjectField(v1, name) !== null;
|
|
7092
|
+
}
|
|
7093
|
+
function yarnLockListing(text) {
|
|
7094
|
+
if (text === null || !/^(# yarn lockfile v1|__metadata:)/m.test(text))
|
|
7095
|
+
return null;
|
|
7096
|
+
const specs = /* @__PURE__ */ new Set();
|
|
7097
|
+
for (const line of text.split(/\r?\n/)) {
|
|
7098
|
+
if (line === "" || line.startsWith(" ") || line.startsWith("#") || !line.endsWith(":"))
|
|
7099
|
+
continue;
|
|
7100
|
+
for (const raw of line.slice(0, -1).split(",")) {
|
|
7101
|
+
const spec = raw.trim().replace(/^"|"$/g, "");
|
|
7102
|
+
specs.add(spec);
|
|
7103
|
+
const berry = /^(@?[^@]+)@npm:(.+)$/.exec(spec);
|
|
7104
|
+
if (berry !== null)
|
|
7105
|
+
specs.add(`${berry[1]}@${berry[2]}`);
|
|
7106
|
+
}
|
|
7107
|
+
}
|
|
7108
|
+
return (name, range) => specs.has(`${name}@${range}`);
|
|
7109
|
+
}
|
|
7110
|
+
function pnpmLockListing(text) {
|
|
7111
|
+
if (text === null || !/^lockfileVersion:/m.test(text))
|
|
7112
|
+
return null;
|
|
7113
|
+
const lines = pnpmRootLines(text.split(/\r?\n/));
|
|
7114
|
+
if (lines === null)
|
|
7115
|
+
return null;
|
|
7116
|
+
const unquote = (s) => s.replace(/^['"]|['"]$/g, "");
|
|
7117
|
+
return (name, range) => lines.some((line, i) => {
|
|
7118
|
+
if (unquote(line) === `${name}:` || line === `'${name}':` || line === `"${name}":`) {
|
|
7119
|
+
const next = lines[i + 1] ?? "";
|
|
7120
|
+
return next.startsWith("specifier:") && unquote(next.slice(10).trim()) === range;
|
|
7121
|
+
}
|
|
7122
|
+
const flat = /^(['"]?)(.+)\1:\s*(.+)$/.exec(line);
|
|
7123
|
+
return flat !== null && flat[2] === name && unquote(flat[3] ?? "") === range;
|
|
7124
|
+
});
|
|
7125
|
+
}
|
|
7126
|
+
function pnpmRootLines(raw) {
|
|
7127
|
+
const importers = raw.indexOf("importers:");
|
|
7128
|
+
if (importers === -1) {
|
|
7129
|
+
const end = raw.indexOf("packages:");
|
|
7130
|
+
return (end === -1 ? raw : raw.slice(0, end)).map((l) => l.trim());
|
|
7131
|
+
}
|
|
7132
|
+
const start = raw.findIndex((l, i) => i > importers && /^ {2}(['"]?)\.\1:\s*$/.test(l));
|
|
7133
|
+
if (start === -1)
|
|
7134
|
+
return null;
|
|
7135
|
+
const out = [];
|
|
7136
|
+
for (const line of raw.slice(start + 1)) {
|
|
7137
|
+
if (line.trim() === "")
|
|
7138
|
+
continue;
|
|
7139
|
+
if (!line.startsWith(" "))
|
|
7140
|
+
break;
|
|
7141
|
+
out.push(line.trim());
|
|
7142
|
+
}
|
|
7143
|
+
return out;
|
|
7144
|
+
}
|
|
7145
|
+
function unfrozenInstall(command) {
|
|
7146
|
+
const lead = /^(?:(?:sudo|time)\s+|env(?:\s+[A-Za-z_]\w*=\S*)*\s+)*/.exec(command)?.[0] ?? "";
|
|
7147
|
+
const rest = command.slice(lead.length);
|
|
7148
|
+
const end = installEnd(rest);
|
|
7149
|
+
const head = rest.slice(0, end);
|
|
7150
|
+
const tail2 = rest.slice(end);
|
|
7151
|
+
const dropped = (flag) => head.replace(new RegExp(`\\s--${flag}(=\\S*)?(?=\\s|$)`, "g"), "");
|
|
7152
|
+
if (/^npm ci(\s|$)/.test(head)) {
|
|
7153
|
+
return lead + head.replace(/^npm ci/, "npm install --no-audit --no-fund") + tail2;
|
|
7154
|
+
}
|
|
7155
|
+
if (/^pnpm\b/.test(head)) {
|
|
7156
|
+
if (/\s--no-frozen-lockfile(?=\s|$)/.test(head))
|
|
7157
|
+
return command;
|
|
7158
|
+
const bare = dropped("frozen-lockfile");
|
|
7159
|
+
const withFlag = bare.replace(/^pnpm (install-test|install|it|i)(?=\s|$)/, "$& --no-frozen-lockfile");
|
|
7160
|
+
if (withFlag !== bare)
|
|
7161
|
+
return lead + withFlag + tail2;
|
|
7162
|
+
return bare === head ? command : `${lead}${bare} --no-frozen-lockfile${tail2}`;
|
|
7163
|
+
}
|
|
7164
|
+
if (/^yarn\b/.test(head)) {
|
|
7165
|
+
const bare = dropped("frozen-lockfile").replace(/\s--immutable(=\S*)?(?=\s|$)/g, "");
|
|
7166
|
+
const assigns = lead === "" || /(^|\s)env(\s+[A-Za-z_]\w*=\S*)*\s+$/.test(lead);
|
|
7167
|
+
return `${lead}${assigns ? "" : "env "}${YARN_MUTABLE} ${bare}${tail2}`;
|
|
7168
|
+
}
|
|
7169
|
+
return command;
|
|
7170
|
+
}
|
|
7171
|
+
function installEnd(command) {
|
|
7172
|
+
let quote = null;
|
|
7173
|
+
for (let i = 0; i < command.length; i += 1) {
|
|
7174
|
+
const c = command[i];
|
|
7175
|
+
if (quote !== null) {
|
|
7176
|
+
if (c === quote)
|
|
7177
|
+
quote = null;
|
|
7178
|
+
else if (c === "\\" && quote === '"')
|
|
7179
|
+
i += 1;
|
|
7180
|
+
continue;
|
|
7181
|
+
}
|
|
7182
|
+
if (c === "\\") {
|
|
7183
|
+
i += 1;
|
|
7184
|
+
} else if (c === '"' || c === "'") {
|
|
7185
|
+
quote = c;
|
|
7186
|
+
} else if (c === ";" || c === "&" && command[i + 1] === "&" || c === "|" && command[i + 1] === "|") {
|
|
7187
|
+
return command.slice(0, i).trimEnd().length;
|
|
7188
|
+
}
|
|
7189
|
+
}
|
|
7190
|
+
return command.length;
|
|
7191
|
+
}
|
|
7042
7192
|
function pythonTestCommand(repo) {
|
|
7043
7193
|
const pyproject = repo.readText("pyproject.toml") ?? "";
|
|
7044
7194
|
if (pyproject.includes("[tool.pytest.ini_options]"))
|
|
@@ -7279,7 +7429,7 @@ function matchFirst(text, pattern) {
|
|
|
7279
7429
|
const match = pattern.exec(text);
|
|
7280
7430
|
return match === null ? null : match[1];
|
|
7281
7431
|
}
|
|
7282
|
-
var RUNTIME_MANIFESTS, MAKEFILE_NAMES, C_FAMILY_SOURCE, MANIFEST_FILENAMES, JVM_BUILD_FILES, NPM_PLACEHOLDER_TEST, REQUIREMENT_SPECIFIER, CPP_BUILD_FILES, CMAKE_DECLARES_TESTS, EXACT_VERSION;
|
|
7432
|
+
var RUNTIME_MANIFESTS, MAKEFILE_NAMES, C_FAMILY_SOURCE, MANIFEST_FILENAMES, JVM_BUILD_FILES, NPM_PLACEHOLDER_TEST, DEPENDENCY_SECTIONS, YARN_MUTABLE, REQUIREMENT_SPECIFIER, CPP_BUILD_FILES, CMAKE_DECLARES_TESTS, EXACT_VERSION;
|
|
7283
7433
|
var init_manifest2 = __esm({
|
|
7284
7434
|
"../../packages/envspec/dist/manifest.js"() {
|
|
7285
7435
|
"use strict";
|
|
@@ -7300,8 +7450,19 @@ var init_manifest2 = __esm({
|
|
|
7300
7450
|
MANIFEST_FILENAMES = RUNTIME_MANIFESTS.flatMap((m) => m.files).concat(["*.csproj", "*.fsproj", "*.sln"], MAKEFILE_NAMES).sort();
|
|
7301
7451
|
JVM_BUILD_FILES = ["build.gradle", "build.gradle.kts", "pom.xml"];
|
|
7302
7452
|
NPM_PLACEHOLDER_TEST = /^echo\s+["']?Error:\s*no test specified["']?\s*&&\s*exit\s+1$/;
|
|
7453
|
+
DEPENDENCY_SECTIONS = [
|
|
7454
|
+
"dependencies",
|
|
7455
|
+
"devDependencies",
|
|
7456
|
+
"optionalDependencies",
|
|
7457
|
+
"peerDependencies"
|
|
7458
|
+
];
|
|
7459
|
+
YARN_MUTABLE = "YARN_ENABLE_IMMUTABLE_INSTALLS=false";
|
|
7303
7460
|
REQUIREMENT_SPECIFIER = /^[A-Za-z0-9._-]+(\[[A-Za-z0-9._,-]+\])?([<>=!~]=?[A-Za-z0-9._*+-]+(,[<>=!~]=?[A-Za-z0-9._*+-]+)*)?$/;
|
|
7304
|
-
CPP_BUILD_FILES = [
|
|
7461
|
+
CPP_BUILD_FILES = [
|
|
7462
|
+
"CMakeLists.txt",
|
|
7463
|
+
"meson.build",
|
|
7464
|
+
...MAKEFILE_NAMES
|
|
7465
|
+
];
|
|
7305
7466
|
CMAKE_DECLARES_TESTS = /\benable_testing\s*\(|\binclude\s*\(\s*CTest\s*\)|\badd_test\s*\(/;
|
|
7306
7467
|
EXACT_VERSION = /^v?(\d+(?:\.\d+){0,2})$/;
|
|
7307
7468
|
}
|
|
@@ -7753,6 +7914,15 @@ function deriveFromReader(repo) {
|
|
|
7753
7914
|
derivedFrom.add(fallback.from);
|
|
7754
7915
|
}
|
|
7755
7916
|
}
|
|
7917
|
+
let installUnfrozen = null;
|
|
7918
|
+
if (installCommand !== null && runtime === "node") {
|
|
7919
|
+
const unfrozen = unfrozenInstall(installCommand);
|
|
7920
|
+
const gaps = unfrozen === installCommand ? null : lockfileGaps(repo);
|
|
7921
|
+
if (gaps !== null) {
|
|
7922
|
+
installCommand = unfrozen;
|
|
7923
|
+
installUnfrozen = { lockfile: gaps.lockfile, missing: gaps.missing };
|
|
7924
|
+
}
|
|
7925
|
+
}
|
|
7756
7926
|
if (installCommand === null) {
|
|
7757
7927
|
unresolved.push({
|
|
7758
7928
|
kind: "no-install-command",
|
|
@@ -7799,6 +7969,7 @@ function deriveFromReader(repo) {
|
|
|
7799
7969
|
runtime,
|
|
7800
7970
|
runtimeVersion: version.value,
|
|
7801
7971
|
installCommand,
|
|
7972
|
+
...installUnfrozen === null ? {} : { installUnfrozen },
|
|
7802
7973
|
testCommand,
|
|
7803
7974
|
services,
|
|
7804
7975
|
migrations,
|
|
@@ -8584,6 +8755,12 @@ function resolveRunEnvironment(derived, req) {
|
|
|
8584
8755
|
) : "detected";
|
|
8585
8756
|
return { spec, imageSource };
|
|
8586
8757
|
}
|
|
8758
|
+
function installUnfrozenNote(spec) {
|
|
8759
|
+
const u = spec.installUnfrozen;
|
|
8760
|
+
if (u === void 0)
|
|
8761
|
+
return null;
|
|
8762
|
+
return `${u.lockfile} does not list ${u.missing.join(", ")}, so the install ran as \`${String(spec.installCommand)}\` without its frozen-lockfile check. Regenerate ${u.lockfile} before merging.`;
|
|
8763
|
+
}
|
|
8587
8764
|
async function verifyWorkingDiff(req) {
|
|
8588
8765
|
const ctx = {
|
|
8589
8766
|
startedAt: Date.now(),
|
|
@@ -8783,6 +8960,9 @@ async function runVerification(req, ctx) {
|
|
|
8783
8960
|
const derived = deriveEnvironmentSpec(cloneDir);
|
|
8784
8961
|
const { spec, imageSource } = resolveRunEnvironment(derived, req);
|
|
8785
8962
|
progress("derive", `runtime=${spec.runtime} install=${String(spec.installCommand)} test=${String(spec.testCommand)}`);
|
|
8963
|
+
const unfrozenNote = installUnfrozenNote(spec);
|
|
8964
|
+
if (unfrozenNote !== null)
|
|
8965
|
+
progress("derive", unfrozenNote);
|
|
8786
8966
|
refuseUnbuildableSpec(spec);
|
|
8787
8967
|
const image = placement.imageFor(spec.runtime, req.image, spec.runtimeVersion, imageVariantFor(spec));
|
|
8788
8968
|
const resolved = await resolveLease(placement, runId);
|
|
@@ -9915,6 +10095,7 @@ __export(dist_exports, {
|
|
|
9915
10095
|
imageVariantFor: () => imageVariantFor,
|
|
9916
10096
|
installCommandFor: () => installCommandFor,
|
|
9917
10097
|
installLocalMigrationTooling: () => installLocalMigrationTooling,
|
|
10098
|
+
installUnfrozenNote: () => installUnfrozenNote,
|
|
9918
10099
|
isBookkeepingTable: () => isBookkeepingTable,
|
|
9919
10100
|
isCommandUnavailable: () => isCommandUnavailable,
|
|
9920
10101
|
isGreen: () => isGreen,
|
|
@@ -20,6 +20,11 @@ import http from 'node:http';
|
|
|
20
20
|
/** Registry hosts a dependency install legitimately needs. */
|
|
21
21
|
export const DEFAULT_INSTALL_ALLOWLIST = [
|
|
22
22
|
'registry.npmjs.org',
|
|
23
|
+
// TERM-1240. `yarn install` is derived for every repo with a yarn.lock, and yarn
|
|
24
|
+
// classic writes this host into each lockfile entry — so every yarn repo could start
|
|
25
|
+
// and could never install, the TERM-1139 shape again. It serves the same packages as
|
|
26
|
+
// the npm registry, so the grant widens where a package comes from, not what can come.
|
|
27
|
+
'registry.yarnpkg.com',
|
|
23
28
|
'pypi.org',
|
|
24
29
|
'files.pythonhosted.org',
|
|
25
30
|
'proxy.golang.org',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terminalhire",
|
|
3
|
-
"version": "0.42.
|
|
3
|
+
"version": "0.42.29",
|
|
4
4
|
"description": "Developer jobs and paid bounties, matched in the terminal — ambient matches in the Claude Code spinner. Claim a scoped bounty and get paid when the poster accepts it.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|