@stll/oxlint-config 1.9.0 → 2.0.0

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.
Files changed (67) hide show
  1. package/README.md +93 -63
  2. package/dist/cloud-setup-cli.cjs +1 -2
  3. package/dist/cloud-setup-cli.cjs.map +1 -1
  4. package/dist/cloud-setup-cli.mjs +1 -2
  5. package/dist/cloud-setup-cli.mjs.map +1 -1
  6. package/dist/dependabot.d.cts +0 -19
  7. package/dist/dependabot.d.mts +0 -19
  8. package/dist/git-tracked-output.cjs +1 -1
  9. package/dist/git-tracked-output.mjs +1 -1
  10. package/dist/publish-artifact-syntax.cjs +1 -1
  11. package/dist/publish-artifact-syntax.mjs +1 -1
  12. package/dist/publish-contract-cli.cjs +2 -2
  13. package/dist/publish-contract-cli.mjs +2 -2
  14. package/dist/quarantine-cli.cjs +3 -2
  15. package/dist/quarantine-cli.cjs.map +1 -1
  16. package/dist/quarantine-cli.mjs +3 -2
  17. package/dist/quarantine-cli.mjs.map +1 -1
  18. package/dist/quarantine-policy.d.cts +25 -2
  19. package/dist/quarantine-policy.d.mts +25 -2
  20. package/dist/quarantine-policy2.cjs +177 -5
  21. package/dist/quarantine-policy2.cjs.map +1 -1
  22. package/dist/quarantine-policy2.mjs +166 -6
  23. package/dist/quarantine-policy2.mjs.map +1 -1
  24. package/dist/toolchain-changed-cli.cjs +1477 -2
  25. package/dist/toolchain-changed-cli.cjs.map +1 -1
  26. package/dist/toolchain-changed-cli.mjs +1474 -1
  27. package/dist/toolchain-changed-cli.mjs.map +1 -1
  28. package/dist/toolchain-check-cli.cjs +2 -0
  29. package/dist/toolchain-check-cli.cjs.map +1 -1
  30. package/dist/toolchain-check-cli.mjs +2 -0
  31. package/dist/toolchain-check-cli.mjs.map +1 -1
  32. package/dist/toolchain-guard.cjs +351 -242
  33. package/dist/toolchain-guard.cjs.map +1 -1
  34. package/dist/toolchain-guard.mjs +347 -244
  35. package/dist/toolchain-guard.mjs.map +1 -1
  36. package/dist/toolchain-inputs.cjs +13 -40
  37. package/dist/toolchain-inputs.cjs.map +1 -1
  38. package/dist/toolchain-inputs.mjs +13 -40
  39. package/dist/toolchain-inputs.mjs.map +1 -1
  40. package/dist/toolchain-schema.cjs +2 -44
  41. package/dist/toolchain-schema.cjs.map +1 -1
  42. package/dist/toolchain-schema.mjs +2 -44
  43. package/dist/toolchain-schema.mjs.map +1 -1
  44. package/dist/typecheck-probe-cli.cjs +40 -2
  45. package/dist/typecheck-probe-cli.cjs.map +1 -1
  46. package/dist/typecheck-probe-cli.mjs +39 -1
  47. package/dist/typecheck-probe-cli.mjs.map +1 -1
  48. package/package.json +3 -4
  49. package/toolchain.json +2 -28
  50. package/dist/toolchain-bun.cjs +0 -34
  51. package/dist/toolchain-bun.cjs.map +0 -1
  52. package/dist/toolchain-bun.mjs +0 -23
  53. package/dist/toolchain-bun.mjs.map +0 -1
  54. package/dist/toolchain-changed.cjs +0 -1484
  55. package/dist/toolchain-changed.cjs.map +0 -1
  56. package/dist/toolchain-changed.mjs +0 -1477
  57. package/dist/toolchain-changed.mjs.map +0 -1
  58. package/dist/typecheck-parity-cli.cjs +0 -71
  59. package/dist/typecheck-parity-cli.cjs.map +0 -1
  60. package/dist/typecheck-parity-cli.d.cts +0 -1
  61. package/dist/typecheck-parity-cli.d.mts +0 -1
  62. package/dist/typecheck-parity-cli.mjs +0 -71
  63. package/dist/typecheck-parity-cli.mjs.map +0 -1
  64. package/dist/typecheck-parity.cjs +0 -908
  65. package/dist/typecheck-parity.cjs.map +0 -1
  66. package/dist/typecheck-parity.mjs +0 -897
  67. package/dist/typecheck-parity.mjs.map +0 -1
@@ -1,6 +1,1481 @@
1
1
  #!/usr/bin/env node
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const require_toolchain_changed = require("./toolchain-changed.cjs");
3
+ const require_rolldown_runtime = require("./rolldown-runtime.cjs");
4
+ const require_jsonc = require("./jsonc.cjs");
5
+ const require_toolchain_inputs = require("./toolchain-inputs.cjs");
6
+ const require_compiler_packages = require("./compiler-packages.cjs");
7
+ let node_path = require("node:path");
8
+ node_path = require_rolldown_runtime.__toESM(node_path, 1);
9
+ let node_child_process = require("node:child_process");
10
+ let smol_toml = require("smol-toml");
11
+ let semver = require("semver");
12
+ let node_util = require("node:util");
13
+ let yaml = require("yaml");
14
+ //#region src/toolchain-changed-locks.ts
15
+ const toolchainChangedTools = [
16
+ "bun",
17
+ "node",
18
+ "typescript",
19
+ "oxlint",
20
+ "oxlint-tsgolint",
21
+ "shared"
22
+ ];
23
+ const changedRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
24
+ const changedPackageTool = (name) => {
25
+ if (require_compiler_packages.isCompilerPackage(name)) return "typescript";
26
+ if (name === "oxlint" || name === "oxlint-tsgolint") return name;
27
+ if (name.startsWith("@stll/") || name === "@oxlint/plugins" || name === "lefthook") return "shared";
28
+ };
29
+ /** Compiler patch declarations retain hashes as well as referenced source paths. */
30
+ const changedCompilerPatches = ({ value, stringsArePaths, compilerNames }) => {
31
+ const patches = [];
32
+ if (!changedRecord(value)) return patches;
33
+ const declarations = value["patchedDependencies"];
34
+ if (declarations === void 0) return patches;
35
+ if (!changedRecord(declarations)) throw new Error("Unclassifiable patchedDependencies declaration");
36
+ for (const [descriptor, metadata] of Object.entries(declarations)) {
37
+ const name = /^(@[^/]+\/[^@]+|[^@]+)(?:@.*)?$/.exec(descriptor)?.[1];
38
+ if (!name || changedPackageTool(name) !== "typescript" && !compilerNames.has(name)) continue;
39
+ let reference;
40
+ if (typeof metadata === "string") {
41
+ if (metadata.trim() === "") throw new Error(`Empty compiler patch metadata: ${descriptor}`);
42
+ if (stringsArePaths) reference = metadata;
43
+ } else if (changedRecord(metadata)) {
44
+ const declaredPath = metadata["path"];
45
+ if (declaredPath !== void 0) {
46
+ if (typeof declaredPath !== "string") throw new Error(`Invalid compiler patch path: ${descriptor}`);
47
+ reference = declaredPath;
48
+ }
49
+ } else throw new Error(`Invalid compiler patch metadata: ${descriptor}`);
50
+ if (stringsArePaths && reference === void 0) throw new Error(`Missing compiler patch path: ${descriptor}`);
51
+ if (reference === void 0) patches.push({
52
+ name: descriptor,
53
+ metadata
54
+ });
55
+ else patches.push({
56
+ name: descriptor,
57
+ metadata,
58
+ reference
59
+ });
60
+ }
61
+ return patches;
62
+ };
63
+ const changedYarnSelectors = (location) => location.replace(/"/g, "").split(/,\s*/).map((selector) => selector.trim());
64
+ const packageDescriptor = (descriptor) => {
65
+ const match = /^(@[^/]+\/[^@]+|[^@]+)@(.+)$/.exec(descriptor.replace(/^\//, ""));
66
+ const name = match?.[1];
67
+ const version = match?.[2];
68
+ if (!name || !version) return void 0;
69
+ return {
70
+ name,
71
+ version
72
+ };
73
+ };
74
+ const dependencyName = (location) => {
75
+ const parts = location.split("/");
76
+ const last = parts.at(-1);
77
+ const scope = parts.at(-2);
78
+ return scope?.startsWith("@") ? `${scope}/${last}` : last ?? location;
79
+ };
80
+ const exactVersion = (value) => {
81
+ const version = value.split("(")[0];
82
+ if (version && (0, semver.valid)(version) === version) return version;
83
+ throw new Error(`Unresolved toolchain lock version: ${value}`);
84
+ };
85
+ const stableIdentity = (value) => {
86
+ if (Array.isArray(value)) return JSON.stringify(value.map(stableIdentity));
87
+ if (changedRecord(value)) return JSON.stringify(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => [key, stableIdentity(entry)]));
88
+ if (value === void 0) return "undefined";
89
+ return JSON.stringify(value);
90
+ };
91
+ const validIntegrity = (value) => value.split(/\s+/).some((item) => {
92
+ const match = /^(sha1|sha256|sha384|sha512)-([A-Za-z0-9+/]+={0,2})$/.exec(item);
93
+ const algorithm = match?.[1];
94
+ const encoded = match?.[2];
95
+ if (!algorithm || !encoded) return false;
96
+ const bytes = Buffer.from(encoded, "base64");
97
+ if (bytes.toString("base64") !== encoded) return false;
98
+ switch (algorithm) {
99
+ case "sha1": return bytes.length === 20;
100
+ case "sha256": return bytes.length === 32;
101
+ case "sha384": return bytes.length === 48;
102
+ case "sha512": return bytes.length === 64;
103
+ default: return false;
104
+ }
105
+ });
106
+ const lockSourceProof = (raw) => {
107
+ const sources = [];
108
+ const evidence = [];
109
+ const commits = [];
110
+ const source = (value) => {
111
+ if (typeof value !== "string") return;
112
+ sources.push(value);
113
+ const commit = /#(?:commit=)?([a-f0-9]{40}|[a-f0-9]{64})(?:[&#]|$)/i.exec(value)?.[1];
114
+ if (commit) commits.push(`commit:${commit.toLowerCase()}`);
115
+ };
116
+ const digest = (key, value) => {
117
+ if (typeof value !== "string") return;
118
+ if (key === "integrity" && validIntegrity(value) || key === "checksum" && /^(?:[a-f0-9]+\/)?[a-f0-9]{128}$/i.test(value) || key === "shasum" && /^[a-f0-9]{40}$/i.test(value)) evidence.push(`${key}:${value}`);
119
+ if (key === "commit" && /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/i.test(value)) commits.push(`commit:${value.toLowerCase()}`);
120
+ };
121
+ const metadata = (value) => {
122
+ if (!changedRecord(value)) return;
123
+ for (const key of [
124
+ "resolved",
125
+ "resolution",
126
+ "tarball",
127
+ "repo",
128
+ "version"
129
+ ]) source(value[key]);
130
+ for (const key of [
131
+ "integrity",
132
+ "checksum",
133
+ "shasum",
134
+ "commit"
135
+ ]) digest(key, value[key]);
136
+ if (changedRecord(value["resolution"])) {
137
+ const resolution = value["resolution"];
138
+ for (const key of [
139
+ "tarball",
140
+ "repo",
141
+ "directory"
142
+ ]) source(resolution[key]);
143
+ for (const key of ["integrity", "commit"]) digest(key, resolution[key]);
144
+ if (resolution["type"] === "directory" || typeof resolution["directory"] === "string") sources.push("file:");
145
+ if (resolution["type"] === "git") sources.push("git:");
146
+ }
147
+ if (value["link"] === true) sources.push("link:");
148
+ };
149
+ if (Array.isArray(raw)) {
150
+ source(raw[0]);
151
+ source(raw[1]);
152
+ digest("integrity", raw[3]);
153
+ } else if (changedRecord(raw)) {
154
+ if (changedRecord(raw["metadata"])) {
155
+ metadata(raw["metadata"]);
156
+ const descriptor = raw["descriptor"];
157
+ source(typeof descriptor === "string" ? descriptor.replace(/^\//, "") : descriptor);
158
+ } else metadata(raw);
159
+ }
160
+ if (sources.some((value) => {
161
+ if (/(?:^|@)(?:link:|portal:|workspace:)/i.test(value)) return true;
162
+ const local = /(?:^|@)file:(.*)$/i.exec(value)?.[1];
163
+ if (local !== void 0) return !/\.(?:tgz|tar\.gz)(?:[?#].*)?$/i.test(local);
164
+ return /^(?:\.{1,2}\/|\/)/.test(value) && !/\.(?:tgz|tar\.gz)(?:[?#].*)?$/i.test(value);
165
+ })) return { type: "unidentified" };
166
+ if (sources.some((value) => /(?:^|@)(?:git(?:\+[^:]+)?:|github:|gitlab:|bitbucket:|ssh:)|(?:github\.com|gitlab\.com)[:/]|(?:^|@)git@|^[\w.-]+@(?!(?:npm|patch|file|link|portal|workspace|https?|git(?:\+[^:]+)?|github|gitlab|bitbucket|ssh):)[^/:]+:|\.git(?:#|$)/i.test(value))) {
167
+ const proof = commits;
168
+ return proof.length > 0 ? {
169
+ type: "immutable",
170
+ identity: stableIdentity(proof.sort())
171
+ } : { type: "unidentified" };
172
+ }
173
+ if (sources.some((value) => /(?:^|@)file:/i.test(value) || /(?:^|@)https?:/i.test(value) && !/(?:^|@)https:\/\/(?:registry\.npmjs\.org|registry\.yarnpkg\.com)\//i.test(value)) && evidence.length === 0) return { type: "unidentified" };
174
+ if (evidence.length > 0) return {
175
+ type: "immutable",
176
+ identity: stableIdentity(evidence.sort())
177
+ };
178
+ return { type: "registry" };
179
+ };
180
+ const addResolution = ({ raw, resolutions, name, version, dependency, location }) => {
181
+ const tool = changedPackageTool(name) ?? changedPackageTool(dependency);
182
+ if (tool === void 0) return;
183
+ const sourceProof = lockSourceProof(raw);
184
+ if (tool === "typescript" && sourceProof.type === "unidentified") throw new Error(`Compiler source has no immutable identity: ${name}`);
185
+ const identity = stableIdentity(raw);
186
+ if (version.startsWith("workspace:") || version.startsWith("link:")) {
187
+ if (tool !== "shared") throw new Error(`Unresolved compiler workspace lock version: ${name}`);
188
+ resolutions.push({
189
+ tool,
190
+ name,
191
+ version,
192
+ dependency,
193
+ location,
194
+ identity,
195
+ sourceProof
196
+ });
197
+ return;
198
+ }
199
+ resolutions.push({
200
+ tool,
201
+ name,
202
+ version: exactVersion(version),
203
+ identity,
204
+ sourceProof,
205
+ dependency,
206
+ location
207
+ });
208
+ };
209
+ const dependencyFields = [
210
+ "dependencies",
211
+ "devDependencies",
212
+ "optionalDependencies"
213
+ ];
214
+ const importerDependencies = (value) => {
215
+ if (!changedRecord(value)) throw new Error("Invalid lockfile importer");
216
+ const result = {};
217
+ for (const field of dependencyFields) {
218
+ const dependencies = value[field];
219
+ if (dependencies === void 0) continue;
220
+ if (!changedRecord(dependencies)) throw new Error("Invalid lockfile dependency table");
221
+ for (const [name, metadata] of Object.entries(dependencies)) if (typeof metadata === "string") result[name] = metadata;
222
+ else if (changedRecord(metadata) && typeof metadata["version"] === "string") result[name] = metadata["version"];
223
+ else if (changedPackageTool(name) !== void 0) throw new Error(`Unresolved lockfile importer dependency: ${name}`);
224
+ }
225
+ return result;
226
+ };
227
+ const BUN_TEXT_LOCKFILE_VERSIONS = /* @__PURE__ */ new Set([
228
+ 0,
229
+ 1,
230
+ 2
231
+ ]);
232
+ const parseChangedLock = ({ file, text }) => {
233
+ const resolutions = [];
234
+ const importers = {};
235
+ if (file.endsWith("bun.lock")) {
236
+ const parsed = require_jsonc.parseJsonc(text);
237
+ if (!changedRecord(parsed) || !BUN_TEXT_LOCKFILE_VERSIONS.has(Number(parsed["lockfileVersion"])) || !changedRecord(parsed["packages"]) || !changedRecord(parsed["workspaces"])) throw new Error("Unsupported Bun lockfile shape");
238
+ for (const [directory, workspace] of Object.entries(parsed["workspaces"])) importers[directory || "."] = importerDependencies(workspace);
239
+ for (const [location, value] of Object.entries(parsed["packages"])) {
240
+ const dependency = dependencyName(location);
241
+ const first = Array.isArray(value) ? value.at(0) : void 0;
242
+ const descriptor = typeof first === "string" ? packageDescriptor(first) : void 0;
243
+ if (descriptor !== void 0) addResolution({
244
+ resolutions,
245
+ ...descriptor,
246
+ dependency,
247
+ location,
248
+ raw: value
249
+ });
250
+ else if (changedPackageTool(dependency) !== void 0) throw new Error(`Invalid Bun toolchain lock entry: ${location}`);
251
+ }
252
+ } else if (file.endsWith("pnpm-lock.yaml")) {
253
+ const document = (0, yaml.parseDocument)(text);
254
+ if (document.errors.length > 0) throw new Error("Invalid pnpm lockfile YAML");
255
+ const parsed = document.toJS({ maxAliasCount: 100 });
256
+ if (!changedRecord(parsed) || ![
257
+ 5.4,
258
+ 6,
259
+ 9
260
+ ].includes(Number(parsed["lockfileVersion"]))) throw new Error("Unsupported pnpm lockfile shape");
261
+ const sourceImporters = changedRecord(parsed["importers"]) ? parsed["importers"] : { ".": parsed };
262
+ for (const [directory, importer] of Object.entries(sourceImporters)) importers[directory] = importerDependencies(importer);
263
+ const packages = parsed["packages"] ?? {};
264
+ if (!changedRecord(packages)) throw new Error("Invalid pnpm lockfile packages");
265
+ for (const [location, metadata] of Object.entries(packages)) {
266
+ let descriptor = packageDescriptor(location);
267
+ if (descriptor === void 0) {
268
+ const legacy = /^(.*)\/([^/]+)$/.exec(location.replace(/^\//, ""));
269
+ if (legacy?.[1] && legacy[2]) descriptor = {
270
+ name: legacy[1],
271
+ version: legacy[2]
272
+ };
273
+ }
274
+ if (!descriptor) continue;
275
+ const version = changedRecord(metadata) && typeof metadata["version"] === "string" ? metadata["version"] : descriptor.version;
276
+ const baseDescriptor = location.split("(").at(0);
277
+ const snapshots = changedRecord(parsed["snapshots"]) ? Object.fromEntries(Object.entries(parsed["snapshots"]).filter(([key]) => key.split("(").at(0) === baseDescriptor)) : {};
278
+ addResolution({
279
+ resolutions,
280
+ name: descriptor.name,
281
+ raw: {
282
+ descriptor: location,
283
+ metadata,
284
+ snapshots
285
+ },
286
+ version,
287
+ dependency: descriptor.name,
288
+ location
289
+ });
290
+ }
291
+ } else if (file.endsWith("yarn.lock")) {
292
+ const classic = /^# yarn lockfile v1\s*$/m.test(text);
293
+ let yaml$1 = text;
294
+ if (classic) yaml$1 = text.split(/\r?\n/).map((line) => {
295
+ if (line === "" || /^\s*#/.test(line)) return line;
296
+ if (!/^\s/.test(line) && line.endsWith(":")) return `${JSON.stringify(line.slice(0, -1))}:`;
297
+ const field = /^(\s+)([A-Za-z][\w-]*|"(?:[^"\\]|\\.)*") (.+)$/.exec(line);
298
+ if (field) return `${field[1]}${field[2]}: ${field[3]}`;
299
+ return line;
300
+ }).join("\n");
301
+ const document = (0, yaml.parseDocument)(yaml$1);
302
+ if (document.errors.length > 0) throw new Error("Invalid Yarn lockfile");
303
+ const parsed = document.toJS({ maxAliasCount: 100 });
304
+ if (!changedRecord(parsed) || !classic && !changedRecord(parsed["__metadata"])) throw new Error("Unsupported Yarn lockfile shape");
305
+ for (const [location, metadata] of Object.entries(parsed)) {
306
+ if (location === "__metadata") continue;
307
+ const selectors = changedYarnSelectors(location);
308
+ const descriptors = selectors.map(packageDescriptor).filter((item) => item !== void 0);
309
+ for (const descriptor of descriptors) {
310
+ const alias = descriptor.version.startsWith("npm:") ? packageDescriptor(descriptor.version.slice(4)) : void 0;
311
+ const dependency = descriptor.name;
312
+ const resolution = changedRecord(metadata) && typeof metadata["resolution"] === "string" ? packageDescriptor(metadata["resolution"]) : void 0;
313
+ let name = (resolution?.version.startsWith("npm:") ? packageDescriptor(resolution.version.slice(4)) : void 0)?.name ?? resolution?.name ?? dependency;
314
+ if (alias && !require_compiler_packages.isCompilerPackage(name)) name = alias.name;
315
+ const patchDescriptor = descriptor.version.startsWith("patch:") ? packageDescriptor(descriptor.version.slice(6)) : void 0;
316
+ if ((require_compiler_packages.isCompilerPackage(name) || require_compiler_packages.isCompilerPackage(dependency) || patchDescriptor !== void 0 && require_compiler_packages.isCompilerPackage(patchDescriptor.name)) && (descriptor.version.startsWith("patch:") || resolution?.version.startsWith("patch:"))) throw new Error(`Yarn compiler patch identity requires tracked patch bytes: ${location}`);
317
+ if (changedPackageTool(name) === void 0 && changedPackageTool(dependency) === void 0) continue;
318
+ if (!changedRecord(metadata) || typeof metadata["version"] !== "string") throw new Error(`Missing Yarn toolchain version: ${location}`);
319
+ if (!classic && typeof metadata["resolution"] !== "string") throw new Error(`Missing Yarn compiler resolution: ${location}`);
320
+ addResolution({
321
+ resolutions,
322
+ name,
323
+ version: metadata["version"],
324
+ dependency,
325
+ location: selectors.join(", "),
326
+ raw: {
327
+ selectors,
328
+ metadata
329
+ }
330
+ });
331
+ }
332
+ }
333
+ } else {
334
+ const parsed = require_jsonc.parseJsonc(text);
335
+ if (!changedRecord(parsed) || ![
336
+ 1,
337
+ 2,
338
+ 3
339
+ ].includes(Number(parsed["lockfileVersion"]))) throw new Error("Unsupported npm lockfile shape");
340
+ if (changedRecord(parsed["packages"])) for (const [location, metadata] of Object.entries(parsed["packages"])) {
341
+ if (!changedRecord(metadata)) throw new Error("Invalid npm lockfile package");
342
+ if (!location.includes("node_modules/")) {
343
+ const ownName = metadata["name"];
344
+ if (typeof ownName === "string" && require_compiler_packages.isCompilerPackage(ownName)) throw new Error(`Compiler directory lock entry has no tracked byte identity: ${location || "."}`);
345
+ importers[location || "."] = importerDependencies(metadata);
346
+ continue;
347
+ }
348
+ const dependency = dependencyName(location);
349
+ const name = typeof metadata["name"] === "string" ? metadata["name"] : dependency;
350
+ if (metadata["link"] === true) {
351
+ addResolution({
352
+ resolutions,
353
+ name,
354
+ raw: metadata,
355
+ version: `link:${String(metadata["resolved"])}`,
356
+ dependency,
357
+ location
358
+ });
359
+ continue;
360
+ }
361
+ if (typeof metadata["version"] === "string") addResolution({
362
+ resolutions,
363
+ name,
364
+ raw: metadata,
365
+ version: metadata["version"],
366
+ dependency,
367
+ location
368
+ });
369
+ else if (changedPackageTool(name) !== void 0 || changedPackageTool(dependency) !== void 0) throw new Error(`Missing npm toolchain version: ${name}`);
370
+ }
371
+ else if (changedRecord(parsed["dependencies"])) {
372
+ const visit = ({ dependencies, parent }) => {
373
+ for (const [dependency, metadata] of Object.entries(dependencies)) {
374
+ if (!changedRecord(metadata)) throw new Error("Invalid npm lockfile dependency");
375
+ const location = `${parent}node_modules/${dependency}`;
376
+ const version = metadata["version"];
377
+ const alias = typeof version === "string" && version.startsWith("npm:") ? packageDescriptor(version.slice(4)) : void 0;
378
+ if (typeof version === "string") addResolution({
379
+ resolutions,
380
+ name: alias?.name ?? dependency,
381
+ raw: metadata,
382
+ version: alias?.version ?? version,
383
+ dependency,
384
+ location
385
+ });
386
+ else if (changedPackageTool(dependency) !== void 0) throw new Error(`Missing npm toolchain version: ${dependency}`);
387
+ if (changedRecord(metadata["dependencies"])) visit({
388
+ dependencies: metadata["dependencies"],
389
+ parent: `${location}/`
390
+ });
391
+ }
392
+ };
393
+ visit({
394
+ dependencies: parsed["dependencies"],
395
+ parent: ""
396
+ });
397
+ } else throw new Error("Missing npm lockfile dependency resolutions");
398
+ }
399
+ return {
400
+ resolutions,
401
+ importers
402
+ };
403
+ };
404
+ //#endregion
405
+ //#region src/toolchain-changed.ts
406
+ const git = (0, node_util.promisify)(node_child_process.execFile);
407
+ const otherRuntimeFiles = /* @__PURE__ */ new Set([
408
+ ".python-version",
409
+ "rust-toolchain",
410
+ "rust-toolchain.toml",
411
+ ".go-version",
412
+ "go.mod",
413
+ "go.work",
414
+ ".java-version",
415
+ ".ruby-version",
416
+ ".sdkmanrc",
417
+ ".dotnet-version",
418
+ "global.json",
419
+ ".terraform-version",
420
+ ".deno-version"
421
+ ]);
422
+ const containerDefinition = (file) => require_toolchain_inputs.isDockerDefinitionPath(file) || require_toolchain_inputs.isComposeDefinitionPath(file) || require_toolchain_inputs.isKubernetesDefinitionPath(file);
423
+ const compilerPatchPackageFile = (file, compilerNames) => {
424
+ if (!file.endsWith(".patch")) return false;
425
+ const target = node_path.default.posix.basename(file).split("++").at(-1);
426
+ if (target === void 0) return false;
427
+ const encoded = /^(@[^+]+\+[^+]+|[^+]+)\+/.exec(target)?.[1];
428
+ return encoded !== void 0 && (changedPackageTool(encoded.replace("+", "/")) === "typescript" || compilerNames.has(encoded.replace("+", "/")));
429
+ };
430
+ const trackedInput = (file) => {
431
+ if (file.split("/").some((part) => part === "node_modules" || part === "vendor")) return false;
432
+ const name = node_path.default.posix.basename(file);
433
+ return [
434
+ "package.json",
435
+ "bun.lock",
436
+ "bun.lockb",
437
+ "pnpm-lock.yaml",
438
+ "package-lock.json",
439
+ "npm-shrinkwrap.json",
440
+ "yarn.lock",
441
+ "pnpm-workspace.yaml",
442
+ ".node-version",
443
+ ".nvmrc",
444
+ ".bun-version",
445
+ ".tool-versions",
446
+ "toolchain.json",
447
+ ".npmrc"
448
+ ].includes(name) || file.endsWith(".patch") || otherRuntimeFiles.has(name) || require_toolchain_inputs.isMiseConfigPath(file) || require_toolchain_inputs.githubAutomationFileKind(file) !== void 0 || containerDefinition(file);
449
+ };
450
+ const readTree = async ({ repo, ref }) => {
451
+ const { stdout } = await git("git", [
452
+ "ls-tree",
453
+ "-rz",
454
+ "--full-tree",
455
+ ref
456
+ ], {
457
+ cwd: repo,
458
+ encoding: "utf8",
459
+ maxBuffer: 33554432
460
+ });
461
+ const entries = /* @__PURE__ */ new Map();
462
+ for (const entry of stdout.split("\0")) {
463
+ if (entry === "") continue;
464
+ const tab = entry.indexOf(" ");
465
+ if (tab < 0) throw new Error("Invalid Git tree response");
466
+ const file = entry.slice(tab + 1);
467
+ const [mode, type, oid] = entry.slice(0, tab).split(" ");
468
+ if (type !== "blob" && !trackedInput(file)) continue;
469
+ if (type !== "blob" || !mode || !oid || !/^[a-f0-9]{40,64}$/.test(oid)) throw new Error("Unreadable tracked toolchain input");
470
+ entries.set(file, {
471
+ mode,
472
+ oid
473
+ });
474
+ }
475
+ return entries;
476
+ };
477
+ const readBlobs = async ({ repo, oids }) => {
478
+ if (oids.length === 0) return /* @__PURE__ */ new Map();
479
+ const data = await new Promise((resolve, reject) => {
480
+ const child = (0, node_child_process.spawn)("git", ["cat-file", "--batch"], {
481
+ cwd: repo,
482
+ stdio: [
483
+ "pipe",
484
+ "pipe",
485
+ "pipe"
486
+ ]
487
+ });
488
+ const chunks = [];
489
+ let size = 0;
490
+ child.stdout.on("data", (chunk) => {
491
+ size += chunk.length;
492
+ if (size > 134217728) {
493
+ child.kill();
494
+ reject(/* @__PURE__ */ new Error("Toolchain Git snapshot exceeds the read limit"));
495
+ return;
496
+ }
497
+ chunks.push(chunk);
498
+ });
499
+ child.stderr.resume();
500
+ child.on("error", reject);
501
+ child.on("close", (code) => code === 0 ? resolve(Buffer.concat(chunks)) : reject(/* @__PURE__ */ new Error("Unable to read Git snapshot blobs")));
502
+ child.stdin.on("error", reject);
503
+ child.stdin.end(`${oids.join("\n")}\n`);
504
+ });
505
+ const blobs = /* @__PURE__ */ new Map();
506
+ let offset = 0;
507
+ for (const oid of oids) {
508
+ const end = data.indexOf(10, offset);
509
+ if (end < 0) throw new Error("Invalid Git blob response");
510
+ const [actual, type, length] = data.subarray(offset, end).toString("utf8").split(" ");
511
+ const size = Number(length);
512
+ if (actual !== oid || type !== "blob" || !Number.isSafeInteger(size) || size < 0 || end + size + 1 >= data.length) throw new Error("Unreadable Git blob response");
513
+ offset = end + 1;
514
+ blobs.set(oid, data.subarray(offset, offset + size).toString("utf8"));
515
+ offset += size + 1;
516
+ }
517
+ if (offset !== data.length) throw new Error("Unexpected Git blob response");
518
+ return blobs;
519
+ };
520
+ const snapshotText = ({ snapshot, file, visited = /* @__PURE__ */ new Set() }) => {
521
+ if (visited.has(file)) throw new Error("Cyclic tracked toolchain file reference");
522
+ visited.add(file);
523
+ const entry = snapshot.entries.get(file);
524
+ const text = entry && snapshot.blobs.get(entry.oid);
525
+ if (!entry || text === void 0) throw new Error(`Missing tracked toolchain input: ${file}`);
526
+ if (entry.mode !== "120000") return text;
527
+ const target = node_path.default.posix.normalize(node_path.default.posix.join(node_path.default.posix.dirname(file), text));
528
+ if (node_path.default.posix.isAbsolute(text) || target === ".." || target.startsWith("../")) throw new Error("Toolchain file reference leaves the tracked snapshot");
529
+ return snapshotText({
530
+ snapshot,
531
+ file: target,
532
+ visited
533
+ });
534
+ };
535
+ /**
536
+ * What one `actions/checkout` step puts where, or `undefined` for any other step.
537
+ *
538
+ * A selector such as `base/package.json` after a checkout of this repository
539
+ * with `path: base` names the repository's own `package.json` at another ref,
540
+ * so it is read as that tracked file. A path built from an expression could
541
+ * replace any directory, so it is `unknown`.
542
+ */
543
+ const checkoutStep = (step) => {
544
+ if (!changedRecord(step)) return void 0;
545
+ const uses = step["uses"];
546
+ if (typeof uses !== "string" || !uses.startsWith("actions/checkout@")) return void 0;
547
+ const options = changedRecord(step["with"]) ? step["with"] : {};
548
+ const repository = options["repository"];
549
+ const sameRepository = repository === void 0 || repository === "${{ github.repository }}";
550
+ const checkoutPath = options["path"];
551
+ if (checkoutPath === void 0) return {
552
+ kind: "root",
553
+ sameRepository
554
+ };
555
+ if (typeof checkoutPath !== "string" || checkoutPath.includes("${{") || node_path.default.posix.isAbsolute(checkoutPath)) return { kind: "unknown" };
556
+ const normalized = node_path.default.posix.normalize(checkoutPath).replace(/\/+$/, "");
557
+ if (normalized === "" || normalized === ".") return {
558
+ kind: "root",
559
+ sameRepository
560
+ };
561
+ if (normalized.split("/").includes("..")) return { kind: "unknown" };
562
+ return {
563
+ kind: "path",
564
+ path: normalized,
565
+ sameRepository
566
+ };
567
+ };
568
+ /**
569
+ * The tracked file a declared selector names under the checkouts preceding
570
+ * its step. The most specific checkout directory decides; a selector inside
571
+ * another repository's checkout is not a tracked file, whatever this
572
+ * repository tracks at the same path.
573
+ */
574
+ const resolveCheckoutSelector = (declared, checkouts) => {
575
+ const file = node_path.default.posix.normalize(declared);
576
+ let owner;
577
+ for (const directory of checkouts.keys()) if ((directory === "" || file.startsWith(`${directory}/`)) && (owner === void 0 || directory.length > owner.length)) owner = directory;
578
+ if (owner === void 0) return file;
579
+ if (checkouts.get(owner) !== true) throw new Error(`Setup version-file names a file outside this repository: ${declared}`);
580
+ return owner === "" ? file : file.slice(owner.length + 1);
581
+ };
582
+ /** Visit each step with only the checkouts that precede it. */
583
+ const visitStepsInOrder = (steps, checkouts, visitStep) => {
584
+ const scope = new Map(checkouts);
585
+ for (const step of steps) {
586
+ visitStep(step, scope);
587
+ const checkout = checkoutStep(step);
588
+ if (checkout === void 0) continue;
589
+ if (checkout.kind !== "path") {
590
+ scope.clear();
591
+ scope.set("", checkout.kind === "root" && checkout.sameRepository);
592
+ continue;
593
+ }
594
+ for (const directory of scope.keys()) if (directory === checkout.path || directory.startsWith(`${checkout.path}/`)) scope.delete(directory);
595
+ scope.set(checkout.path, checkout.sameRepository);
596
+ }
597
+ };
598
+ const selectorFiles = (snapshot) => {
599
+ const files = /* @__PURE__ */ new Map();
600
+ const visit = (value, checkouts = /* @__PURE__ */ new Map()) => {
601
+ if (Array.isArray(value)) {
602
+ for (const entry of value) visit(entry, checkouts);
603
+ return;
604
+ }
605
+ if (!changedRecord(value)) return;
606
+ for (const [key, entry] of Object.entries(value)) {
607
+ if (key === "steps" && Array.isArray(entry)) {
608
+ visitStepsInOrder(entry, checkouts, visit);
609
+ continue;
610
+ }
611
+ const input = key.toLowerCase();
612
+ if (!/(?:^|-)version-file$/.test(input) && ![
613
+ "toolchain-file",
614
+ "rust-toolchain-file",
615
+ "global-json-file"
616
+ ].includes(input)) {
617
+ visit(entry, checkouts);
618
+ continue;
619
+ }
620
+ if (typeof entry !== "string" || entry.trim() === "" || entry.includes("${{") || node_path.default.posix.isAbsolute(entry) || entry.split("/").includes("..")) throw new Error("Unclassifiable setup version-file declaration");
621
+ const file = resolveCheckoutSelector(entry, checkouts);
622
+ if (file === "." || file.split("/").some((part) => part === "vendor" || part === "node_modules")) throw new Error("Setup version-file leaves the tracked toolchain scope");
623
+ let tool = "shared";
624
+ if (input === "node-version-file") tool = "node";
625
+ else if (input === "bun-version-file") tool = "bun";
626
+ const selected = files.get(file) ?? /* @__PURE__ */ new Set();
627
+ selected.add(tool);
628
+ files.set(file, selected);
629
+ }
630
+ };
631
+ for (const file of snapshot.entries.keys()) {
632
+ if (require_toolchain_inputs.githubAutomationFileKind(file) === void 0) continue;
633
+ const document = (0, yaml.parseDocument)(snapshotText({
634
+ snapshot,
635
+ file
636
+ }));
637
+ if (document.errors.length > 0) throw new Error("Unreadable workflow declaration");
638
+ visit(document.toJS({ maxAliasCount: 100 }));
639
+ }
640
+ return files;
641
+ };
642
+ const compilerInstallationNames = (snapshot) => {
643
+ const names = /* @__PURE__ */ new Set();
644
+ const collectCatalogs = (value) => {
645
+ if (!changedRecord(value)) return;
646
+ const visit = (entry) => {
647
+ if (!changedRecord(entry)) return;
648
+ for (const [name, specifier] of Object.entries(entry)) if (typeof specifier === "string" && changedPackageTool(packageTarget({
649
+ name,
650
+ specifier
651
+ })) === "typescript") names.add(name);
652
+ else visit(specifier);
653
+ };
654
+ visit(value["catalog"]);
655
+ visit(value["catalogs"]);
656
+ };
657
+ for (const file of snapshot.entries.keys()) {
658
+ const basename = node_path.default.posix.basename(file);
659
+ if (basename === "package.json") {
660
+ const manifest = require_jsonc.parseJsonc(snapshotText({
661
+ snapshot,
662
+ file
663
+ }));
664
+ if (!changedRecord(manifest)) throw new Error(`Invalid tracked manifest: ${file}`);
665
+ if (typeof manifest["name"] === "string" && changedPackageTool(manifest["name"]) === "typescript") throw new Error(`Tracked compiler source has no immutable artifact identity: ${file}`);
666
+ collectCatalogs(manifest);
667
+ collectCatalogs(manifest["workspaces"]);
668
+ for (const field of installedFields) {
669
+ const dependencies = manifest[field];
670
+ if (!changedRecord(dependencies)) continue;
671
+ for (const [name, specifier] of Object.entries(dependencies)) if (typeof specifier === "string" && changedPackageTool(packageTarget({
672
+ name,
673
+ specifier
674
+ })) === "typescript") names.add(name);
675
+ }
676
+ } else if (basename === "pnpm-workspace.yaml") {
677
+ const document = (0, yaml.parseDocument)(snapshotText({
678
+ snapshot,
679
+ file
680
+ }));
681
+ if (document.errors.length > 0) throw new Error(`Invalid catalog YAML: ${file}`);
682
+ collectCatalogs(document.toJS({ maxAliasCount: 100 }));
683
+ } else if ([
684
+ "bun.lock",
685
+ "pnpm-lock.yaml",
686
+ "package-lock.json",
687
+ "npm-shrinkwrap.json",
688
+ "yarn.lock"
689
+ ].includes(basename)) {
690
+ const lock = parseChangedLock({
691
+ file,
692
+ text: snapshotText({
693
+ snapshot,
694
+ file
695
+ })
696
+ });
697
+ for (const resolution of lock.resolutions) if (resolution.tool === "typescript") names.add(resolution.dependency);
698
+ for (const dependencies of Object.values(lock.importers)) for (const [name, version] of Object.entries(dependencies)) if (changedPackageTool(importerRegistryName({
699
+ dependency: name,
700
+ version
701
+ }) ?? name) === "typescript") names.add(name);
702
+ }
703
+ }
704
+ return names;
705
+ };
706
+ const effectiveCatalogSpecifier = ({ snapshot, file, dependency, specifier }) => {
707
+ if (!specifier.startsWith("catalog:")) return specifier;
708
+ const catalog = specifier.slice(8);
709
+ const owners = [...snapshot.entries.keys()].filter((candidate) => ["package.json", "pnpm-workspace.yaml"].includes(node_path.default.posix.basename(candidate)) && isAncestor(node_path.default.posix.dirname(candidate), node_path.default.posix.dirname(file))).sort((a, b) => directoryDepth(node_path.default.posix.dirname(b)) - directoryDepth(node_path.default.posix.dirname(a)) || Number(b.endsWith("pnpm-workspace.yaml")) - Number(a.endsWith("pnpm-workspace.yaml")));
710
+ for (const owner of owners) {
711
+ const text = snapshotText({
712
+ snapshot,
713
+ file: owner
714
+ });
715
+ let value;
716
+ if (owner.endsWith(".yaml")) {
717
+ const document = (0, yaml.parseDocument)(text);
718
+ if (document.errors.length > 0) throw new Error(`Invalid catalog YAML: ${owner}`);
719
+ value = document.toJS({ maxAliasCount: 100 });
720
+ } else value = require_jsonc.parseJsonc(text);
721
+ if (!changedRecord(value)) continue;
722
+ const sources = [value];
723
+ if (changedRecord(value["workspaces"])) sources.push(value["workspaces"]);
724
+ for (const source of sources) {
725
+ let declarations;
726
+ if (catalog === "") declarations = source["catalog"];
727
+ else if (changedRecord(source["catalogs"])) declarations = source["catalogs"][catalog];
728
+ if (declarations === void 0) continue;
729
+ if (!changedRecord(declarations) || typeof declarations[dependency] !== "string") throw new Error(`Missing effective catalog dependency: ${file}:${dependency}`);
730
+ const effective = declarations[dependency];
731
+ if (effective.startsWith("catalog:")) throw new Error(`Recursive compiler catalog: ${file}:${dependency}`);
732
+ return effective;
733
+ }
734
+ }
735
+ throw new Error(`Missing effective catalog: ${file}:${dependency}`);
736
+ };
737
+ const compilerPatches = (snapshot) => {
738
+ const compilerNames = compilerInstallationNames(snapshot);
739
+ const result = [];
740
+ for (const file of snapshot.entries.keys()) {
741
+ const name = node_path.default.posix.basename(file);
742
+ if (![
743
+ "package.json",
744
+ "pnpm-workspace.yaml",
745
+ "pnpm-lock.yaml",
746
+ "bun.lock"
747
+ ].includes(name)) continue;
748
+ const text = snapshotText({
749
+ snapshot,
750
+ file
751
+ });
752
+ let value;
753
+ if (name.endsWith(".yaml")) {
754
+ const document = (0, yaml.parseDocument)(text);
755
+ if (document.errors.length > 0) throw new Error(`Invalid compiler patch YAML: ${file}`);
756
+ value = document.toJS({ maxAliasCount: 100 });
757
+ } else value = require_jsonc.parseJsonc(text);
758
+ const sources = [value];
759
+ if (name === "package.json" && changedRecord(value)) sources.push(value["pnpm"]);
760
+ for (const source of sources) for (const patch of changedCompilerPatches({
761
+ value: source,
762
+ compilerNames,
763
+ stringsArePaths: name === "package.json" || name === "pnpm-workspace.yaml"
764
+ })) {
765
+ if (patch.reference === void 0) {
766
+ result.push({
767
+ file,
768
+ name: patch.name,
769
+ metadata: patch.metadata
770
+ });
771
+ continue;
772
+ }
773
+ const reference = node_path.default.posix.normalize(node_path.default.posix.join(node_path.default.posix.dirname(file), patch.reference));
774
+ if (patch.reference.trim() === "" || node_path.default.posix.isAbsolute(patch.reference) || patch.reference.includes("\\") || patch.reference.split("").some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127) || reference === ".." || reference.startsWith("../")) throw new Error(`Compiler patch reference leaves the tracked snapshot: ${file}:${patch.name}`);
775
+ result.push({
776
+ file,
777
+ name: patch.name,
778
+ metadata: patch.metadata,
779
+ reference
780
+ });
781
+ }
782
+ if (name === "pnpm-lock.yaml" && changedRecord(value) && changedRecord(value["snapshots"])) for (const descriptor of Object.keys(value["snapshots"])) {
783
+ const packageName = /^(@[^/]+\/[^@]+|[^@]+)@/.exec(descriptor)?.[1];
784
+ if (packageName && (changedPackageTool(packageName) === "typescript" || compilerNames.has(packageName)) && descriptor.includes("patch_hash=")) result.push({
785
+ file,
786
+ name: descriptor,
787
+ metadata: descriptor
788
+ });
789
+ }
790
+ }
791
+ return result;
792
+ };
793
+ const readSnapshots = async ({ repo, trees }) => {
794
+ const selected = trees.map((tree) => new Map([...tree].filter(([file]) => trackedInput(file))));
795
+ const blobs = /* @__PURE__ */ new Map();
796
+ for (;;) {
797
+ const pending = [...new Set(selected.flatMap((entries) => [...entries.values()].map((entry) => entry.oid)))].filter((oid) => !blobs.has(oid));
798
+ for (const [oid, text] of await readBlobs({
799
+ repo,
800
+ oids: pending
801
+ })) blobs.set(oid, text);
802
+ let expanded = false;
803
+ for (let index = 0; index < selected.length; index += 1) {
804
+ const entries = selected.at(index);
805
+ const tree = trees.at(index);
806
+ if (!entries || !tree) throw new Error("Missing Git tree");
807
+ const required = /* @__PURE__ */ new Set();
808
+ const requiredPatches = /* @__PURE__ */ new Set();
809
+ for (const [file, entry] of entries) {
810
+ if (entry.mode !== "120000") continue;
811
+ const text = blobs.get(entry.oid);
812
+ if (text === void 0) throw new Error("Missing tracked symlink");
813
+ const target = node_path.default.posix.normalize(node_path.default.posix.join(node_path.default.posix.dirname(file), text));
814
+ if (node_path.default.posix.isAbsolute(text) || target === ".." || target.startsWith("../")) throw new Error("Toolchain symlink leaves the tracked tree");
815
+ required.add(target);
816
+ }
817
+ if (![...required].some((file) => !entries.has(file))) {
818
+ const snapshot = {
819
+ entries,
820
+ blobs
821
+ };
822
+ for (const file of selectorFiles(snapshot).keys()) required.add(file);
823
+ for (const patch of compilerPatches(snapshot)) if (patch.reference !== void 0) {
824
+ required.add(patch.reference);
825
+ requiredPatches.add(patch.reference);
826
+ }
827
+ }
828
+ for (const file of required) {
829
+ if (entries.has(file)) continue;
830
+ const entry = tree.get(file);
831
+ if (!entry) throw new Error(requiredPatches.has(file) ? `Missing tracked compiler patch: ${file}` : `Missing tracked toolchain selector file: ${file}`);
832
+ entries.set(file, entry);
833
+ expanded = true;
834
+ }
835
+ }
836
+ if (!expanded) return selected.map((entries) => ({
837
+ entries,
838
+ blobs
839
+ }));
840
+ }
841
+ };
842
+ const stableJson = (value) => {
843
+ if (Array.isArray(value)) return JSON.stringify(value.map(stableJson));
844
+ if (changedRecord(value)) return JSON.stringify(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, stableJson(item)]));
845
+ if (value === void 0) return "undefined";
846
+ return JSON.stringify(value);
847
+ };
848
+ const inventory = () => ({
849
+ bun: /* @__PURE__ */ new Set(),
850
+ node: /* @__PURE__ */ new Set(),
851
+ typescript: /* @__PURE__ */ new Set(),
852
+ oxlint: /* @__PURE__ */ new Set(),
853
+ "oxlint-tsgolint": /* @__PURE__ */ new Set(),
854
+ shared: /* @__PURE__ */ new Set()
855
+ });
856
+ const installedFields = [
857
+ "dependencies",
858
+ "devDependencies",
859
+ "optionalDependencies"
860
+ ];
861
+ const exactBun = (value) => stableVersion(value);
862
+ const nonRegistryResolution = (specifier) => /^(?:link:|workspace:|file:|portal:|catalog:|git:|github:|gitlab:|bitbucket:|git\+|https?:|ssh:|git@)/i.test(specifier);
863
+ const packageTarget = ({ name, specifier }) => {
864
+ if (!specifier.startsWith("npm:")) return name;
865
+ const target = /^npm:((?:@[^/@\s]+\/)?[^/@\s]+)(?:@.*)?$/.exec(specifier)?.[1];
866
+ if (target === void 0) throw new Error("Unclassifiable npm alias declaration");
867
+ return target;
868
+ };
869
+ /** Importers contain resolved descriptors, not declarations; classify protocols first. */
870
+ const importerRegistryName = ({ dependency, version }) => {
871
+ if (nonRegistryResolution(version)) return dependency;
872
+ if (version.startsWith("npm:")) return packageTarget({
873
+ name: dependency,
874
+ specifier: version
875
+ });
876
+ const descriptor = version.split("(").at(0) ?? "";
877
+ return /^((?:@[^/@\s]+\/)?[^/@\s]+)@/.exec(descriptor)?.[1];
878
+ };
879
+ const directoryDepth = (directory) => directory.split("/").filter((part) => part !== "." && part !== "").length;
880
+ const isAncestor = (directory, child) => {
881
+ const relative = node_path.default.posix.relative(directory, child);
882
+ return relative !== ".." && !relative.startsWith("../") && !node_path.default.posix.isAbsolute(relative);
883
+ };
884
+ const boundResolution = ({ lockFile, lock, file, manifest, dependency, name }) => {
885
+ const directory = node_path.default.posix.relative(node_path.default.posix.dirname(lockFile), node_path.default.posix.dirname(file)) || ".";
886
+ const importerVersion = lock.importers[directory]?.[dependency];
887
+ const importerName = node_path.default.posix.basename(lockFile) === "pnpm-lock.yaml" && importerVersion !== void 0 ? importerRegistryName({
888
+ dependency,
889
+ version: importerVersion
890
+ }) : void 0;
891
+ const candidates = lock.resolutions.filter((item) => item.name === (importerName ?? name) || item.dependency === dependency);
892
+ if (node_path.default.posix.basename(lockFile) === "pnpm-lock.yaml") {
893
+ const value = lock.importers[directory]?.[dependency];
894
+ if (value === void 0) throw new Error(`Missing resolved importer: ${file}:${dependency}`);
895
+ const descriptor = value.replace(/^npm:/, "");
896
+ const resolvedVersion = (descriptor.startsWith(`${importerName ?? name}@`) ? descriptor.slice((importerName ?? name).length + 1) : descriptor).split("(").at(0);
897
+ const matched = candidates.filter((item) => item.version === resolvedVersion);
898
+ if (new Set(matched.map((item) => `${item.name}@${item.version}`)).size === 1) return matched;
899
+ throw new Error(`Ambiguous resolved importer: ${file}:${dependency}`);
900
+ }
901
+ if (node_path.default.posix.basename(lockFile) === "yarn.lock") {
902
+ const declarations = installedFields.flatMap((field) => {
903
+ const source = manifest[field];
904
+ return changedRecord(source) && typeof source[dependency] === "string" ? [source[dependency]] : [];
905
+ });
906
+ const declaredSelectors = new Set(declarations.flatMap((specifier) => [`${dependency}@${specifier}`, `${dependency}@npm:${specifier}`].flatMap(changedYarnSelectors)));
907
+ const matched = candidates.filter((item) => changedYarnSelectors(item.location).some((selector) => declaredSelectors.has(selector)));
908
+ if (matched.length > 0) return matched;
909
+ throw new Error(`Missing resolved Yarn compiler declaration: ${file}:${dependency}`);
910
+ }
911
+ const locations = [];
912
+ if (node_path.default.posix.basename(lockFile) === "bun.lock") {
913
+ if (directory !== "." && typeof manifest["name"] !== "string" && candidates.length > 1) throw new Error(`Ambiguous Bun workspace resolution: ${file}:${dependency}`);
914
+ if (directory !== "." && typeof manifest["name"] === "string") locations.push(`${manifest["name"]}/${dependency}`);
915
+ locations.push(dependency);
916
+ } else {
917
+ let current = directory;
918
+ for (;;) {
919
+ locations.push(node_path.default.posix.join(current, "node_modules", dependency));
920
+ if (current === ".") break;
921
+ current = node_path.default.posix.dirname(current);
922
+ }
923
+ }
924
+ for (const location of locations) {
925
+ const matched = candidates.filter((item) => item.location === location);
926
+ if (matched.length === 1) return matched;
927
+ if (matched.length > 1) throw new Error(`Ambiguous lock location: ${file}:${dependency}`);
928
+ }
929
+ throw new Error(`Missing resolved toolchain dependency: ${file}:${dependency}`);
930
+ };
931
+ const stableVersion = (value) => {
932
+ if (typeof value !== "string") throw new Error("Runtime selector is not an exact stable version");
933
+ const version = value.trim().replace(/^v/, "");
934
+ if (!/^\d+\.\d+\.\d+$/.test(version) || (0, semver.valid)(version) !== version) throw new Error("Runtime selector is not an exact stable version");
935
+ return version;
936
+ };
937
+ const setupRuntimeVersion = (value) => {
938
+ if (typeof value !== "string" && typeof value !== "number") throw new Error("Unclassifiable setup runtime selector");
939
+ const selector = String(value).trim();
940
+ return stableVersion(selector);
941
+ };
942
+ const setupRuntimeFileVersion = ({ snapshot, file, runtime }) => {
943
+ const text = snapshotText({
944
+ snapshot,
945
+ file
946
+ }).trim();
947
+ if (runtime === "bun" && file.endsWith(".json")) {
948
+ const manifest = require_jsonc.parseJsonc(text);
949
+ const manager = changedRecord(manifest) ? manifest["packageManager"] : void 0;
950
+ if (typeof manager !== "string" || !manager.startsWith("bun@")) throw new Error("Bun selector manifest lacks an exact Bun packageManager");
951
+ return stableVersion(manager.slice(4));
952
+ }
953
+ if (runtime === "go" && node_path.default.posix.basename(file) === "go.mod") {
954
+ const goLines = text.split(/\r?\n/).filter((line) => /^\s*go\b/.test(line));
955
+ const toolchainLines = text.split(/\r?\n/).filter((line) => /^\s*toolchain\b/.test(line));
956
+ if (goLines.length !== 1 || toolchainLines.length > 1) throw new Error("Unclassifiable Go runtime directives");
957
+ const version = /^\s*go\s+(\S+)\s*(?:\/\/.*)?$/.exec(goLines.at(0) ?? "")?.[1];
958
+ const go = stableVersion(version);
959
+ if (toolchainLines.length === 0) return go;
960
+ const toolchain = /^\s*toolchain\s+(\S+)\s*(?:\/\/.*)?$/.exec(toolchainLines.at(0) ?? "")?.[1];
961
+ if (toolchain === void 0) throw new Error("Unclassifiable Go toolchain directive");
962
+ if (!toolchain.startsWith("go")) throw new Error("Unclassifiable Go toolchain directive");
963
+ return stableVersion(toolchain.slice(2));
964
+ }
965
+ if (file.endsWith(".toml")) {
966
+ const value = (0, smol_toml.parse)(text);
967
+ if (!changedRecord(value)) throw new Error("Unclassifiable setup runtime TOML");
968
+ if (runtime === "rust" && changedRecord(value["toolchain"])) return setupRuntimeVersion(value["toolchain"]["channel"]);
969
+ if (runtime === "python") return setupRuntimeVersion(value["python-version"]);
970
+ throw new Error("Unclassifiable setup runtime TOML");
971
+ }
972
+ return setupRuntimeVersion(text);
973
+ };
974
+ const setupRuntimeVersions = (snapshot) => {
975
+ const versions = [];
976
+ const step = (value, file, checkouts) => {
977
+ if (!changedRecord(value) || typeof value["uses"] !== "string") return;
978
+ const action = value["uses"].split("@").at(0)?.toLowerCase();
979
+ let runtime = /(?:^|\/)setup-([^/]+)$/.exec(action ?? "")?.[1];
980
+ const wrapped = [
981
+ "bun",
982
+ "node",
983
+ "python",
984
+ "go",
985
+ "java"
986
+ ].find((candidate) => runtime?.startsWith(`${candidate}-`));
987
+ if (wrapped !== void 0) runtime = wrapped;
988
+ if (action === "dtolnay/rust-toolchain") runtime = "rust";
989
+ if (runtime === void 0) return;
990
+ const raw = value["with"];
991
+ if (!changedRecord(raw)) throw new Error(`Missing setup runtime selector: ${file}:${runtime}`);
992
+ const inputs = /* @__PURE__ */ new Map();
993
+ for (const [key, input] of Object.entries(raw)) {
994
+ const folded = key.toLowerCase();
995
+ if (inputs.has(folded)) throw new Error(`Duplicate setup input: ${file}:${folded}`);
996
+ inputs.set(folded, input);
997
+ }
998
+ let selected = false;
999
+ for (const [key, input] of inputs) {
1000
+ if (!/(?:^|-)version(?:-file)?$/.test(key) && ![
1001
+ "toolchain",
1002
+ "toolchain-file",
1003
+ "rust-toolchain-file"
1004
+ ].includes(key)) continue;
1005
+ if ([
1006
+ "bun",
1007
+ "node",
1008
+ "python",
1009
+ "go",
1010
+ "java"
1011
+ ].includes(runtime)) {
1012
+ if (key === `${runtime}-version` || key === `${runtime}-version-file`) selected = true;
1013
+ } else if (runtime === "rust") {
1014
+ if ([
1015
+ "toolchain",
1016
+ "toolchain-file",
1017
+ "rust-toolchain-file",
1018
+ "rust-version",
1019
+ "rust-version-file"
1020
+ ].includes(key)) selected = true;
1021
+ } else if (key === "version" || key === "version-file" || key === `${runtime}-version` || key === `${runtime}-version-file`) selected = true;
1022
+ const inputRuntime = [
1023
+ "bun",
1024
+ "node",
1025
+ "python",
1026
+ "go",
1027
+ "java",
1028
+ "rust"
1029
+ ].find((candidate) => key.startsWith(`${candidate}-`)) ?? runtime;
1030
+ let version;
1031
+ if (key.endsWith("-file")) {
1032
+ if (typeof input !== "string" || input.trim() === "" || input.includes("${{")) throw new Error("Unclassifiable setup version-file declaration");
1033
+ version = setupRuntimeFileVersion({
1034
+ snapshot,
1035
+ file: resolveCheckoutSelector(input, checkouts),
1036
+ runtime: inputRuntime
1037
+ });
1038
+ } else version = setupRuntimeVersion(input);
1039
+ versions.push({
1040
+ file,
1041
+ runtime: inputRuntime,
1042
+ version
1043
+ });
1044
+ }
1045
+ if (!selected) throw new Error(`Missing setup runtime selector: ${file}:${runtime}`);
1046
+ };
1047
+ const visit = (value, file) => {
1048
+ if (Array.isArray(value)) {
1049
+ for (const entry of value) visit(entry, file);
1050
+ return;
1051
+ }
1052
+ if (!changedRecord(value)) return;
1053
+ for (const [key, entry] of Object.entries(value)) if (key === "steps" && Array.isArray(entry)) visitStepsInOrder(entry, /* @__PURE__ */ new Map(), (child, checkouts) => step(child, file, checkouts));
1054
+ else visit(entry, file);
1055
+ };
1056
+ for (const file of snapshot.entries.keys()) {
1057
+ if (require_toolchain_inputs.githubAutomationFileKind(file) === void 0) continue;
1058
+ const document = (0, yaml.parseDocument)(snapshotText({
1059
+ snapshot,
1060
+ file
1061
+ }));
1062
+ if (document.errors.length > 0) throw new Error("Unreadable workflow declaration");
1063
+ visit(document.toJS({ maxAliasCount: 100 }), file);
1064
+ }
1065
+ return versions;
1066
+ };
1067
+ const stableBunImageVersion = (image) => {
1068
+ const match = /^oven\/bun(?::([^@]+))?(?:@[^\s]+)?$/.exec(image);
1069
+ if (match === null) return void 0;
1070
+ const version = match[1]?.replace(/-(?:alpine|slim|debian|distroless)$/, "");
1071
+ return stableVersion(version);
1072
+ };
1073
+ const assertLiteralDockerImages = (text) => {
1074
+ const defaults = /* @__PURE__ */ new Map();
1075
+ for (const line of text.replace(/\\\r?\n/g, " ").split(/\r?\n/)) {
1076
+ const arg = /^\s*ARG\s+([A-Za-z_]\w*)(?:=(\S+))?\s*(?:#.*)?$/i.exec(line);
1077
+ if (arg?.[1]) {
1078
+ const value = arg[2];
1079
+ if (value !== void 0 && /^[A-Za-z0-9_.:/@+-]+$/.test(value)) defaults.set(arg[1], value);
1080
+ else defaults.delete(arg[1]);
1081
+ }
1082
+ const image = /^\s*FROM\s+(?:--[^\s]+\s+)*(\S+)/i.exec(line)?.[1];
1083
+ if (image === void 0) continue;
1084
+ const expanded = image.replace(/\$\{([A-Za-z_]\w*)\}|\$([A-Za-z_]\w*)/g, (original, braced, plain) => defaults.get(braced ?? plain ?? "") ?? original);
1085
+ if (expanded.includes("$")) throw new Error("Unclassifiable Docker image selector");
1086
+ stableBunImageVersion(expanded);
1087
+ }
1088
+ };
1089
+ const parseSnapshot = (snapshot) => {
1090
+ const tools = inventory();
1091
+ const bunVersions = /* @__PURE__ */ new Set();
1092
+ const bunRanges = [];
1093
+ const manifests = /* @__PURE__ */ new Map();
1094
+ const locks = /* @__PURE__ */ new Map();
1095
+ const workspacePatterns = /* @__PURE__ */ new Map();
1096
+ const typescript = /* @__PURE__ */ new Set();
1097
+ const selectedFiles = selectorFiles(snapshot);
1098
+ for (const { file, runtime, version } of setupRuntimeVersions(snapshot)) {
1099
+ let tool = "shared";
1100
+ if (runtime === "bun") tool = "bun";
1101
+ else if (runtime === "node") tool = "node";
1102
+ tools[tool].add(`setup-runtime:${file}:${runtime}:${version}`);
1103
+ }
1104
+ const compilerNames = compilerInstallationNames(snapshot);
1105
+ for (const patch of compilerPatches(snapshot)) {
1106
+ tools.typescript.add(`compiler-patch:${patch.file}:${patch.name}:${stableJson(patch.metadata)}`);
1107
+ if (patch.reference !== void 0) tools.typescript.add(`compiler-patch-source:${patch.reference}:${snapshotText({
1108
+ snapshot,
1109
+ file: patch.reference
1110
+ })}`);
1111
+ }
1112
+ const addBun = (value) => {
1113
+ const version = exactBun(value);
1114
+ bunVersions.add(version);
1115
+ tools.bun.add(version);
1116
+ };
1117
+ const catalogs = (file, value) => {
1118
+ for (const field of ["catalog", "catalogs"]) {
1119
+ if (value[field] === void 0) continue;
1120
+ tools.shared.add(`${file}:${field}:${stableJson(value[field])}`);
1121
+ const visit = (entry, at) => {
1122
+ if (!changedRecord(entry)) return;
1123
+ for (const [name, specifier] of Object.entries(entry)) {
1124
+ const target = typeof specifier === "string" ? packageTarget({
1125
+ name,
1126
+ specifier
1127
+ }) : name;
1128
+ if (changedPackageTool(target) === "typescript") tools.typescript.add(`${file}:${at}:${name}:${stableJson(specifier)}`);
1129
+ visit(specifier, `${at}:${name}`);
1130
+ }
1131
+ };
1132
+ visit(value[field], field);
1133
+ }
1134
+ };
1135
+ for (const file of snapshot.entries.keys()) {
1136
+ const text = snapshotText({
1137
+ snapshot,
1138
+ file
1139
+ });
1140
+ const name = node_path.default.posix.basename(file);
1141
+ if (compilerPatchPackageFile(file, compilerNames)) tools.typescript.add(`patch-package:${file}:${text}`);
1142
+ for (const tool of selectedFiles.get(file) ?? []) tools[tool].add(`selector-file:${file}:${text}`);
1143
+ if (name === "package.json") {
1144
+ const manifest = require_jsonc.parseJsonc(text);
1145
+ if (!changedRecord(manifest)) throw new Error(`Invalid tracked manifest: ${file}`);
1146
+ if (typeof manifest["name"] === "string" && changedPackageTool(manifest["name"]) === "typescript") throw new Error(`Tracked compiler source has no immutable artifact identity: ${file}`);
1147
+ manifests.set(file, manifest);
1148
+ catalogs(file, manifest);
1149
+ if (changedRecord(manifest["workspaces"])) catalogs(`${file}:workspaces`, manifest["workspaces"]);
1150
+ } else if (name === ".npmrc") for (const line of text.split(/\r?\n/)) {
1151
+ if (!/^\s*(?:use-node-version|node-version)\s*=/i.test(line)) continue;
1152
+ if (/\$\{/.test(line)) throw new Error("Unclassifiable npm Node selector");
1153
+ const selector = /^\s*(?:use-node-version|node-version)\s*=\s*(.*?)\s*$/i.exec(line)?.[1];
1154
+ tools.node.add(`${file}:${stableVersion(selector)}`);
1155
+ }
1156
+ else if (name === "pnpm-workspace.yaml") {
1157
+ const document = (0, yaml.parseDocument)(text);
1158
+ if (document.errors.length > 0) throw new Error("Invalid pnpm workspace YAML");
1159
+ const workspace = document.toJS({ maxAliasCount: 100 });
1160
+ if (!changedRecord(workspace)) throw new Error("Invalid pnpm workspace configuration");
1161
+ workspacePatterns.set(node_path.default.posix.dirname(file), workspace["packages"]);
1162
+ catalogs(file, workspace);
1163
+ for (const key of ["useNodeVersion", "nodeVersion"]) if (workspace[key] !== void 0) tools.node.add(`${file}:${key}:${stableVersion(workspace[key])}`);
1164
+ } else if ([
1165
+ "bun.lock",
1166
+ "pnpm-lock.yaml",
1167
+ "package-lock.json",
1168
+ "npm-shrinkwrap.json",
1169
+ "yarn.lock"
1170
+ ].includes(name)) locks.set(file, parseChangedLock({
1171
+ file,
1172
+ text
1173
+ }));
1174
+ else if (name === "bun.lockb") {
1175
+ if (!snapshot.entries.has(node_path.default.posix.join(node_path.default.posix.dirname(file), "bun.lock"))) throw new Error("Binary Bun lockfile cannot be resolved without executing Bun");
1176
+ } else if (name === ".bun-version") addBun(text.trim());
1177
+ else if (otherRuntimeFiles.has(name)) tools.shared.add(`${file}:${text}`);
1178
+ else if (name === ".node-version" || name === ".nvmrc") tools.node.add(`${file}:${stableVersion(text)}`);
1179
+ else if (name === "toolchain.json") {
1180
+ const policy = require_jsonc.parseJsonc(text);
1181
+ if (!changedRecord(policy)) throw new Error("Invalid shared toolchain policy");
1182
+ tools.shared.add(`${file}:${stableJson(policy)}`);
1183
+ if (typeof policy["bun"] === "string") addBun(policy["bun"]);
1184
+ } else if (name === ".tool-versions") {
1185
+ tools.bun.add(`${file}:${text}`);
1186
+ tools.typescript.add(`${file}:${text}`);
1187
+ for (const line of text.split(/\r?\n/)) {
1188
+ const [tool, ...versions] = line.trim().split(/\s+/);
1189
+ const value = versions.join(" ").split("#")[0]?.trim();
1190
+ if (tool === "bun" && value) addBun(value);
1191
+ if (tool === "node" || tool === "nodejs") tools.node.add(`${file}:${stableVersion(value)}`);
1192
+ }
1193
+ } else if (require_toolchain_inputs.isMiseConfigPath(file)) {
1194
+ tools.bun.add(`${file}:${text}`);
1195
+ tools.typescript.add(`${file}:${text}`);
1196
+ const parsed = (0, smol_toml.parse)(text);
1197
+ if (changedRecord(parsed) && changedRecord(parsed["tools"])) for (const [tool, selector] of Object.entries(parsed["tools"])) {
1198
+ const value = changedRecord(selector) ? selector["version"] : selector;
1199
+ const values = Array.isArray(value) ? value : [value];
1200
+ if (tool === "bun" || tool === "core:bun") for (const version of values) {
1201
+ if (typeof version !== "string") throw new Error("Unresolved mise Bun selector");
1202
+ addBun(version);
1203
+ }
1204
+ if ([
1205
+ "node",
1206
+ "nodejs",
1207
+ "core:node"
1208
+ ].includes(tool)) {
1209
+ if (values.length === 0) throw new Error("Unresolved mise Node selector");
1210
+ for (const version of values) tools.node.add(`${file}:${stableVersion(version)}`);
1211
+ }
1212
+ }
1213
+ } else if (require_toolchain_inputs.githubAutomationFileKind(file) !== void 0) {
1214
+ tools.shared.add(`${file}:${text}`);
1215
+ tools.bun.add(`${file}:${text}`);
1216
+ tools.typescript.add(`${file}:${text}`);
1217
+ } else if (require_toolchain_inputs.isDockerDefinitionPath(file)) {
1218
+ assertLiteralDockerImages(text);
1219
+ tools.shared.add(`${file}:${text}`);
1220
+ tools.node.add(`${file}:${text}`);
1221
+ tools.bun.add(`${file}:${text}`);
1222
+ tools.typescript.add(`${file}:${text}`);
1223
+ for (const match of text.matchAll(/^\s*FROM\s+(?:--[^\s]+\s+)*oven\/bun:([\w.+-]+)/gim)) {
1224
+ const tag = match[1];
1225
+ if (tag !== void 0) {
1226
+ const version = stableBunImageVersion(`oven/bun:${tag}`);
1227
+ if (version !== void 0) addBun(version);
1228
+ }
1229
+ }
1230
+ } else if (containerDefinition(file)) for (const document of (0, yaml.parseAllDocuments)(text)) {
1231
+ if (document.errors.length > 0) {
1232
+ if (require_toolchain_inputs.isComposeDefinitionPath(file) || /(?:^|\n)\s*(?:apiVersion|kind)\s*:/.test(text)) throw new Error(`Unreadable container declaration: ${file}`);
1233
+ continue;
1234
+ }
1235
+ const value = document.toJS({ maxAliasCount: 100 });
1236
+ const container = require_toolchain_inputs.containerDocumentImages(value, file);
1237
+ if (container === void 0) {
1238
+ if (require_toolchain_inputs.isComposeDefinitionPath(file)) throw new Error(`Unclassifiable Compose declaration: ${file}`);
1239
+ continue;
1240
+ }
1241
+ for (const { image } of container.images) {
1242
+ if (typeof image !== "string" || image.includes("$")) throw new Error(`Unclassifiable container image selector: ${file}`);
1243
+ stableBunImageVersion(image);
1244
+ }
1245
+ for (const tool of [
1246
+ "bun",
1247
+ "node",
1248
+ "typescript",
1249
+ "shared"
1250
+ ]) tools[tool].add(`${file}:${text}`);
1251
+ }
1252
+ }
1253
+ const activeManifest = (file) => {
1254
+ if (file === "package.json") return true;
1255
+ const directory = node_path.default.posix.dirname(file);
1256
+ if ([
1257
+ "bun.lock",
1258
+ "pnpm-lock.yaml",
1259
+ "package-lock.json",
1260
+ "npm-shrinkwrap.json",
1261
+ "yarn.lock"
1262
+ ].some((name) => locks.has(node_path.default.posix.join(directory, name)))) return true;
1263
+ const owners = [...manifests].filter(([ownerFile, owner]) => owner["workspaces"] !== void 0 && isAncestor(node_path.default.posix.dirname(ownerFile), directory)).map(([ownerFile, owner]) => ({
1264
+ directory: node_path.default.posix.dirname(ownerFile),
1265
+ patterns: changedRecord(owner["workspaces"]) ? owner["workspaces"]["packages"] : owner["workspaces"]
1266
+ }));
1267
+ const pnpmOwner = [...workspacePatterns].filter(([ownerDirectory]) => isAncestor(ownerDirectory, directory)).sort(([a], [b]) => directoryDepth(b) - directoryDepth(a)).at(0);
1268
+ if (pnpmOwner) return require_toolchain_inputs.workspaceContains({
1269
+ directory: pnpmOwner[0],
1270
+ file,
1271
+ patterns: pnpmOwner[1],
1272
+ rootMembership: "implicit"
1273
+ });
1274
+ const owner = owners.sort((a, b) => directoryDepth(b.directory) - directoryDepth(a.directory)).at(0);
1275
+ if (owner) return require_toolchain_inputs.workspaceContains({
1276
+ directory: owner.directory,
1277
+ file,
1278
+ patterns: owner.patterns,
1279
+ rootMembership: "implicit"
1280
+ });
1281
+ for (const [lockFile, lock] of locks) {
1282
+ const relative = node_path.default.posix.relative(node_path.default.posix.dirname(lockFile), directory) || ".";
1283
+ if (Object.hasOwn(lock.importers, relative)) return true;
1284
+ }
1285
+ return false;
1286
+ };
1287
+ for (const [file, manifest] of manifests) {
1288
+ const active = activeManifest(file);
1289
+ const manager = manifest["packageManager"];
1290
+ if (manager !== void 0) {
1291
+ if (typeof manager !== "string") throw new Error(`Unclassifiable package manager: ${file}`);
1292
+ if (manager.startsWith("bun@")) addBun(manager.slice(4));
1293
+ else tools.shared.add(`${file}:packageManager:${manager}`);
1294
+ }
1295
+ for (const field of ["volta", "devEngines"]) {
1296
+ if (manifest[field] === void 0) continue;
1297
+ for (const tool of [
1298
+ "bun",
1299
+ "node",
1300
+ "typescript",
1301
+ "shared"
1302
+ ]) tools[tool].add(`${file}:${field}:${stableJson(manifest[field])}`);
1303
+ }
1304
+ const engines = manifest["engines"];
1305
+ if (changedRecord(engines)) {
1306
+ if (typeof engines["node"] === "string") tools.node.add(`${file}:${engines["node"]}`);
1307
+ if (typeof engines["bun"] === "string") {
1308
+ if (!(0, semver.validRange)(engines["bun"])) throw new Error("Invalid Bun engine selector");
1309
+ bunRanges.push(engines["bun"]);
1310
+ tools.bun.add(`${file}:engines.bun:${engines["bun"]}`);
1311
+ if ((0, semver.valid)(engines["bun"]) !== null) addBun(engines["bun"]);
1312
+ }
1313
+ }
1314
+ for (const field of active ? ["peerDependencies"] : [...installedFields, "peerDependencies"]) {
1315
+ const declarations = manifest[field];
1316
+ if (declarations === void 0) continue;
1317
+ if (!changedRecord(declarations)) throw new Error(`Invalid toolchain declarations: ${file}:${field}`);
1318
+ for (const [dependency, specifier] of Object.entries(declarations)) {
1319
+ if (typeof specifier !== "string") throw new Error(`Unclassifiable toolchain declaration: ${file}:${dependency}`);
1320
+ const tool = changedPackageTool(packageTarget({
1321
+ name: dependency,
1322
+ specifier
1323
+ })) ?? changedPackageTool(dependency) ?? (compilerNames.has(dependency) ? "typescript" : void 0);
1324
+ if (tool !== void 0) tools[tool].add(`${file}:${field}:${dependency}:${specifier}`);
1325
+ }
1326
+ }
1327
+ if (!active) continue;
1328
+ const ownName = manifest["name"];
1329
+ const ownVersion = manifest["version"];
1330
+ if (typeof ownName === "string" && changedPackageTool(ownName) === "shared" && typeof ownVersion === "string") tools.shared.add(`${ownName}@${ownVersion}`);
1331
+ for (const field of installedFields) {
1332
+ const dependencies = manifest[field];
1333
+ if (dependencies === void 0) continue;
1334
+ if (!changedRecord(dependencies)) throw new Error(`Invalid manifest dependency table: ${file}`);
1335
+ for (const [dependency, specifier] of Object.entries(dependencies)) {
1336
+ if (typeof specifier !== "string") throw new Error(`Invalid dependency specifier: ${file}`);
1337
+ const declaredTarget = packageTarget({
1338
+ name: dependency,
1339
+ specifier
1340
+ });
1341
+ if (changedPackageTool(declaredTarget) === void 0 && changedPackageTool(dependency) === void 0 && !compilerNames.has(dependency)) continue;
1342
+ const effectiveSpecifier = effectiveCatalogSpecifier({
1343
+ snapshot,
1344
+ file,
1345
+ dependency,
1346
+ specifier
1347
+ });
1348
+ const name = packageTarget({
1349
+ name: dependency,
1350
+ specifier: effectiveSpecifier
1351
+ });
1352
+ const tool = changedPackageTool(name) ?? changedPackageTool(dependency) ?? (compilerNames.has(dependency) ? "typescript" : void 0);
1353
+ if (tool === void 0) continue;
1354
+ const sourceSpecifier = effectiveSpecifier.startsWith("npm:") ? effectiveSpecifier.slice(4 + name.length + 1) : effectiveSpecifier;
1355
+ if (tool === "typescript" && (/^(?:link:|portal:|workspace:)/i.test(sourceSpecifier) || /^(?:file:|\.{1,2}\/|\/)/i.test(sourceSpecifier) && !/\.(?:tgz|tar\.gz)(?:[?#].*)?$/i.test(sourceSpecifier))) throw new Error(`Compiler source has no immutable identity: ${file}:${dependency}`);
1356
+ if (tool === "typescript") tools.typescript.add(`${file}:${field}:${dependency}:specifier:${specifier}`);
1357
+ const candidates = [...locks].filter(([lockFile]) => {
1358
+ const relative = node_path.default.posix.relative(node_path.default.posix.dirname(lockFile), node_path.default.posix.dirname(file));
1359
+ return relative !== ".." && !relative.startsWith("../");
1360
+ }).sort(([a], [b]) => directoryDepth(node_path.default.posix.dirname(b)) - directoryDepth(node_path.default.posix.dirname(a)));
1361
+ const nearest = candidates.at(0);
1362
+ if (!nearest) throw new Error(`Missing toolchain lockfile: ${file}:${dependency}`);
1363
+ const lockDirectory = node_path.default.posix.dirname(nearest[0]);
1364
+ const ownerManager = [...manifests].filter(([ownerFile, ownerManifest]) => typeof ownerManifest["packageManager"] === "string" && isAncestor(node_path.default.posix.dirname(ownerFile), node_path.default.posix.dirname(file))).sort(([a], [b]) => directoryDepth(node_path.default.posix.dirname(b)) - directoryDepth(node_path.default.posix.dirname(a))).at(0)?.[1]["packageManager"];
1365
+ const selectedLocks = candidates.filter(([candidate]) => node_path.default.posix.dirname(candidate) === lockDirectory).filter(([candidate]) => {
1366
+ if (typeof ownerManager !== "string") return true;
1367
+ const lockName = node_path.default.posix.basename(candidate);
1368
+ if (ownerManager.startsWith("bun@")) return lockName === "bun.lock";
1369
+ if (ownerManager.startsWith("pnpm@")) return lockName === "pnpm-lock.yaml";
1370
+ if (ownerManager.startsWith("yarn@")) return lockName === "yarn.lock";
1371
+ if (ownerManager.startsWith("npm@")) return lockName === "package-lock.json" || lockName === "npm-shrinkwrap.json";
1372
+ return false;
1373
+ });
1374
+ const selected = selectedLocks.at(0);
1375
+ if (selectedLocks.length !== 1 || !selected) throw new Error(`Ambiguous or missing effective lockfile: ${file}:${dependency}`);
1376
+ const [lockFile, lock] = selected;
1377
+ const resolved = boundResolution({
1378
+ lockFile,
1379
+ lock,
1380
+ file,
1381
+ manifest,
1382
+ dependency,
1383
+ name
1384
+ });
1385
+ if (tool === "typescript" && resolved.some((item) => item.name !== name)) throw new Error(`Compiler lock name does not match effective declaration: ${file}:${dependency}`);
1386
+ if (tool === "typescript" && nonRegistryResolution(sourceSpecifier) && resolved.some((item) => item.sourceProof.type !== "immutable")) throw new Error(`Compiler source has no immutable identity: ${file}:${dependency}`);
1387
+ if (tool === "typescript" && /^(?:git(?:\+[^:]+)?:|github:|gitlab:|bitbucket:|ssh:|git@)/i.test(sourceSpecifier) && resolved.some((item) => !/(?:git(?:\+[^:]+)?:|github:|gitlab:|bitbucket:|ssh:|git@|\.git(?:#|["\\]))/i.test(item.identity))) throw new Error(`Compiler Git source has no matching immutable identity: ${file}:${dependency}`);
1388
+ const range = effectiveSpecifier.startsWith("npm:") ? effectiveSpecifier.slice(effectiveSpecifier.lastIndexOf("@") + 1) : effectiveSpecifier;
1389
+ const resolvedVersion = (version) => version.startsWith("workspace:") ? manifests.get(node_path.default.posix.join(lockDirectory, version.slice(10), "package.json"))?.["version"] : version;
1390
+ if ((0, semver.validRange)(range) && !resolved.some((item) => {
1391
+ const version = resolvedVersion(item.version);
1392
+ return typeof version === "string" && (0, semver.valid)(version) !== null && (0, semver.satisfies)(version, range);
1393
+ })) throw new Error(`Toolchain lock resolution does not satisfy manifest: ${file}:${dependency}`);
1394
+ for (const item of resolved) tools[tool].add(`${file}:${dependency}=>${item.location}:${item.name}@${item.version}:${item.identity}`);
1395
+ }
1396
+ }
1397
+ }
1398
+ for (const [lockFile, lock] of locks) {
1399
+ for (const item of lock.resolutions) {
1400
+ tools[item.tool].add(`${lockFile}:${item.location}:${item.dependency}=>${item.name}@${item.version}:${item.identity}`);
1401
+ if (item.tool === "typescript") typescript.add(`${item.name}@${item.version}`);
1402
+ }
1403
+ if (node_path.default.posix.basename(lockFile) !== "pnpm-lock.yaml") continue;
1404
+ for (const [directory, dependencies] of Object.entries(lock.importers)) for (const [dependency, version] of Object.entries(dependencies)) {
1405
+ const namedTool = changedPackageTool(dependency);
1406
+ const nonRegistry = nonRegistryResolution(version);
1407
+ const registryName = importerRegistryName({
1408
+ dependency,
1409
+ version
1410
+ });
1411
+ const tool = (registryName === void 0 ? void 0 : changedPackageTool(registryName)) ?? namedTool ?? (nonRegistry ? void 0 : lock.resolutions.find((item) => item.dependency === dependency)?.tool);
1412
+ if (tool !== void 0) tools[tool].add(`${lockFile}:importer:${directory}:${dependency}:${version}`);
1413
+ }
1414
+ }
1415
+ if (bunVersions.size > 1) throw new Error("Conflicting Bun runtime versions in tracked declarations");
1416
+ const bun = [...bunVersions].sort();
1417
+ if (bunRanges.length > 0 && bun.length === 0) throw new Error("Bun engine range has no exact runtime declaration");
1418
+ for (const range of bunRanges) if (!bun.every((version) => (0, semver.satisfies)(version, range))) throw new Error("Bun runtime does not satisfy tracked engine range");
1419
+ return {
1420
+ tools,
1421
+ current: {
1422
+ bun,
1423
+ typescript: [...typescript].sort()
1424
+ }
1425
+ };
1426
+ };
1427
+ const detectToolchainChanges = async ({ repo, since }) => {
1428
+ try {
1429
+ const refs = await Promise.all([since, "HEAD"].map(async (ref) => {
1430
+ const { stdout } = await git("git", [
1431
+ "rev-parse",
1432
+ "--verify",
1433
+ "--end-of-options",
1434
+ `${ref}^{commit}`
1435
+ ], {
1436
+ cwd: repo,
1437
+ encoding: "utf8"
1438
+ });
1439
+ const oid = stdout.trim();
1440
+ if (!/^[a-f0-9]{40,64}$/.test(oid)) throw new Error("Invalid Git commit response");
1441
+ return oid;
1442
+ }));
1443
+ const previousRef = refs.at(0);
1444
+ const currentRef = refs.at(1);
1445
+ if (!previousRef || !currentRef) throw new Error("Missing Git comparison snapshot");
1446
+ const [previousEntries, currentEntries] = await Promise.all([readTree({
1447
+ repo,
1448
+ ref: previousRef
1449
+ }), readTree({
1450
+ repo,
1451
+ ref: currentRef
1452
+ })]);
1453
+ const snapshots = await readSnapshots({
1454
+ repo,
1455
+ trees: [previousEntries, currentEntries]
1456
+ });
1457
+ const previousSnapshot = snapshots.at(0);
1458
+ const currentSnapshot = snapshots.at(1);
1459
+ if (!previousSnapshot || !currentSnapshot) throw new Error("Missing toolchain snapshot");
1460
+ const previous = parseSnapshot(previousSnapshot);
1461
+ const current = parseSnapshot(currentSnapshot);
1462
+ const tools = toolchainChangedTools.filter((tool) => stableJson([...previous.tools[tool]].sort()) !== stableJson([...current.tools[tool]].sort()));
1463
+ return {
1464
+ status: "compared",
1465
+ changed: tools.length > 0,
1466
+ tools,
1467
+ current: current.current
1468
+ };
1469
+ } catch (error) {
1470
+ return {
1471
+ status: "unreadable",
1472
+ changed: true,
1473
+ tools: [...toolchainChangedTools],
1474
+ error: error instanceof Error ? error.message : "Unreadable toolchain Git snapshot"
1475
+ };
1476
+ }
1477
+ };
1478
+ //#endregion
4
1479
  //#region src/toolchain-changed-args.ts
5
1480
  const changedUsage = "Usage: stll-toolchain-changed --since <git-ref>\n stll-toolchain-changed --help";
6
1481
  const parseChangedArguments = (args) => {
@@ -20,7 +1495,7 @@ const main = async () => {
20
1495
  process.stdout.write(`${changedUsage}\n`);
21
1496
  return;
22
1497
  }
23
- process.stdout.write(`${JSON.stringify(await require_toolchain_changed.detectToolchainChanges({
1498
+ process.stdout.write(`${JSON.stringify(await detectToolchainChanges({
24
1499
  repo: process.cwd(),
25
1500
  since: args.since
26
1501
  }))}\n`);