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.
@@ -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 = ["CMakeLists.txt", "meson.build", ...MAKEFILE_NAMES];
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,
@@ -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 = ["CMakeLists.txt", "meson.build", ...MAKEFILE_NAMES];
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,
@@ -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 = ["CMakeLists.txt", "meson.build", ...MAKEFILE_NAMES];
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,
@@ -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 = ["CMakeLists.txt", "meson.build", ...MAKEFILE_NAMES];
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.28",
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",