@stll/oxlint-config 3.0.0 → 3.2.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.
- package/README.md +37 -2
- package/dist/consumer-compat-cli.cjs +12 -6
- package/dist/consumer-compat-cli.cjs.map +1 -1
- package/dist/consumer-compat-cli.mjs +12 -6
- package/dist/consumer-compat-cli.mjs.map +1 -1
- package/dist/toolchain-check-cli.cjs +1 -1
- package/dist/toolchain-check-cli.cjs.map +1 -1
- package/dist/toolchain-check-cli.mjs +1 -1
- package/dist/toolchain-check-cli.mjs.map +1 -1
- package/dist/toolchain-guard.cjs +279 -106
- package/dist/toolchain-guard.cjs.map +1 -1
- package/dist/toolchain-guard.mjs +279 -106
- package/dist/toolchain-guard.mjs.map +1 -1
- package/dist/toolchain-inputs.cjs +9 -2
- package/dist/toolchain-inputs.cjs.map +1 -1
- package/dist/toolchain-inputs.mjs +9 -2
- package/dist/toolchain-inputs.mjs.map +1 -1
- package/package.json +1 -1
- package/toolchain.json +1 -1
package/dist/toolchain-guard.cjs
CHANGED
|
@@ -16,6 +16,8 @@ let semver = require("semver");
|
|
|
16
16
|
let yaml = require("yaml");
|
|
17
17
|
let node_module = require("node:module");
|
|
18
18
|
let node_url = require("node:url");
|
|
19
|
+
let acorn = require("acorn");
|
|
20
|
+
let esbuild = require("esbuild");
|
|
19
21
|
//#region src/toolchain-bun.ts
|
|
20
22
|
const STABLE_RELEASE = /^\d+\.\d+\.\d+$/u;
|
|
21
23
|
/**
|
|
@@ -34,9 +36,9 @@ const repositoryBun = (floor, manifest) => {
|
|
|
34
36
|
};
|
|
35
37
|
//#endregion
|
|
36
38
|
//#region src/toolchain-consumer-checks.ts
|
|
37
|
-
const consumerRunnerVersion = "3.
|
|
39
|
+
const consumerRunnerVersion = "3.2.0";
|
|
38
40
|
const consumerCheckWorkflow = "stella/.github/.github/workflows/package-consumer-compat.yml";
|
|
39
|
-
const record$
|
|
41
|
+
const record$11 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
40
42
|
const repositoryDirectory = (value) => typeof value === "string" && value !== "" && !value.includes("\\") && !value.includes("${{") && !value.split("").some((character) => {
|
|
41
43
|
const code = character.charCodeAt(0);
|
|
42
44
|
return code < 32 || code === 127;
|
|
@@ -122,12 +124,12 @@ const validConsumerCron = (text) => {
|
|
|
122
124
|
});
|
|
123
125
|
};
|
|
124
126
|
const nightlySchedule = (value) => {
|
|
125
|
-
if (!record$
|
|
127
|
+
if (!record$11(value)) return false;
|
|
126
128
|
const schedule = value["schedule"];
|
|
127
129
|
if (!Array.isArray(schedule) || schedule.length === 0) return false;
|
|
128
130
|
const coveredDays = /* @__PURE__ */ new Set();
|
|
129
131
|
for (const entry of schedule) {
|
|
130
|
-
if (!record$
|
|
132
|
+
if (!record$11(entry) || typeof entry["cron"] !== "string" || !validConsumerCron(entry["cron"])) return false;
|
|
131
133
|
const fields = entry["cron"].trim().split(/\s+/);
|
|
132
134
|
const selections = cronFields.map((field, index) => {
|
|
133
135
|
const text = fields.at(index);
|
|
@@ -143,7 +145,7 @@ const parseConsumerChecks = (input) => {
|
|
|
143
145
|
if (!Array.isArray(input)) throw new Error("consumerChecks must be an array");
|
|
144
146
|
const targets = /* @__PURE__ */ new Set();
|
|
145
147
|
return input.map((entry) => {
|
|
146
|
-
if (!record$
|
|
148
|
+
if (!record$11(entry) || Object.keys(entry).some((key) => ![
|
|
147
149
|
"workflow",
|
|
148
150
|
"job",
|
|
149
151
|
"packages",
|
|
@@ -181,9 +183,9 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
|
|
|
181
183
|
});
|
|
182
184
|
if (document.errors.length > 0) throw new Error("invalid workflow YAML");
|
|
183
185
|
const source = document.toJS({ maxAliasCount: 100 });
|
|
184
|
-
if (!record$
|
|
186
|
+
if (!record$11(source) || !record$11(source["jobs"])) continue;
|
|
185
187
|
for (const [jobName, job] of Object.entries(source["jobs"])) {
|
|
186
|
-
if (!record$
|
|
188
|
+
if (!record$11(job) || typeof job["uses"] !== "string") continue;
|
|
187
189
|
const [workflowPath, ref] = job["uses"].split("@");
|
|
188
190
|
if (workflowPath?.toLowerCase() !== "stella/.github/.github/workflows/package-consumer-compat.yml") continue;
|
|
189
191
|
const declaration = declarations.find((entry) => entry.workflow === workflow && entry.job === jobName);
|
|
@@ -197,7 +199,7 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
|
|
|
197
199
|
const approved = Object.entries(policy.actions).find(([name]) => name.toLowerCase() === consumerCheckWorkflow)?.[1];
|
|
198
200
|
if (job["uses"].split("@").length !== 2 || ref === void 0 || !/^[a-f0-9]{40}$/.test(ref) || approved === void 0 || ref !== approved.sha || "steps" in job || "runs-on" in job) throw new Error(`${workflow}:${jobName} must invoke the approved immutable consumer workflow as a reusable job`);
|
|
199
201
|
const inputs = job["with"];
|
|
200
|
-
if (!record$
|
|
202
|
+
if (!record$11(inputs)) throw new Error(`${workflow}:${jobName} requires consumer inputs`);
|
|
201
203
|
const names = /* @__PURE__ */ new Set();
|
|
202
204
|
for (const key of Object.keys(inputs)) {
|
|
203
205
|
const normalized = key.toLowerCase();
|
|
@@ -267,7 +269,7 @@ const locatorValidators = {
|
|
|
267
269
|
"bun-source": /^(?:jobs\.[\w-]+|runs)\.steps\.[\w-]+$/
|
|
268
270
|
};
|
|
269
271
|
const knownKind = (value) => typeof value === "string" && Object.hasOwn(locatorValidators, value);
|
|
270
|
-
const record$
|
|
272
|
+
const record$10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
271
273
|
/** One reviewed declaration identifies one unresolved selector, never a rule. */
|
|
272
274
|
const parseDynamicSelectors = (input) => {
|
|
273
275
|
if (input === void 0) return [];
|
|
@@ -275,7 +277,7 @@ const parseDynamicSelectors = (input) => {
|
|
|
275
277
|
const result = [];
|
|
276
278
|
const keys = /* @__PURE__ */ new Set();
|
|
277
279
|
for (const entry of input) {
|
|
278
|
-
if (!record$
|
|
280
|
+
if (!record$10(entry) || Object.keys(entry).some((key) => ![
|
|
279
281
|
"path",
|
|
280
282
|
"kind",
|
|
281
283
|
"reason",
|
|
@@ -311,7 +313,7 @@ const parseDynamicSelectors = (input) => {
|
|
|
311
313
|
};
|
|
312
314
|
//#endregion
|
|
313
315
|
//#region src/toolchain-engine-floors.ts
|
|
314
|
-
const record$
|
|
316
|
+
const record$9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
315
317
|
const repositoryPath = (value) => typeof value === "string" && value !== "" && !/^[A-Za-z]:/.test(value) && !/[\\\s]/.test(value) && !Array.from(value).some((character) => character.charCodeAt(0) < 32) && value.split("/").every((part) => part !== "" && part !== "." && part !== "..");
|
|
316
318
|
const parseEngineFloors = (input) => {
|
|
317
319
|
if (input === void 0) return [];
|
|
@@ -319,7 +321,7 @@ const parseEngineFloors = (input) => {
|
|
|
319
321
|
const result = [];
|
|
320
322
|
const jobs = /* @__PURE__ */ new Set();
|
|
321
323
|
for (const entry of input) {
|
|
322
|
-
if (!record$
|
|
324
|
+
if (!record$9(entry) || Object.keys(entry).some((key) => ![
|
|
323
325
|
"package",
|
|
324
326
|
"workflow",
|
|
325
327
|
"job"
|
|
@@ -340,16 +342,16 @@ const resolveEngineFloor = (entry, files) => {
|
|
|
340
342
|
const workflowText = files[entry.workflow];
|
|
341
343
|
if (manifestText === void 0 || workflowText === void 0) throw new Error("engine floor package and workflow must be tracked");
|
|
342
344
|
const manifest = JSON.parse(manifestText);
|
|
343
|
-
if (!record$
|
|
345
|
+
if (!record$9(manifest) || manifest["private"] === true || typeof manifest["name"] !== "string" || manifest["name"].trim() === "" || !record$9(manifest["engines"]) || typeof manifest["engines"]["node"] !== "string") throw new Error("engine floor package must be published and declare engines.node");
|
|
344
346
|
const range = manifest["engines"]["node"];
|
|
345
347
|
const minimum = (0, semver.validRange)(range) === null ? null : (0, semver.minVersion)(range);
|
|
346
348
|
if (minimum === null || minimum.prerelease.length !== 0) throw new Error("engine floor engines.node must declare a stable minimum");
|
|
347
349
|
const document = (0, yaml.parseDocument)(workflowText, { merge: true });
|
|
348
350
|
if (document.errors.length !== 0) throw new Error("engine floor workflow must be valid YAML");
|
|
349
351
|
const workflow = document.toJS({ maxAliasCount: 100 });
|
|
350
|
-
if (!record$
|
|
352
|
+
if (!record$9(workflow) || !record$9(workflow["jobs"])) throw new Error("engine floor job must exist in its tracked workflow");
|
|
351
353
|
const job = workflow["jobs"][entry.job];
|
|
352
|
-
if (!record$
|
|
354
|
+
if (!record$9(job)) throw new Error("engine floor job must exist in its tracked workflow");
|
|
353
355
|
if (!Array.isArray(job["steps"]) || Object.hasOwn(job, "uses")) throw new Error("engine floor job must declare steps and cannot reuse a workflow");
|
|
354
356
|
return {
|
|
355
357
|
...entry,
|
|
@@ -362,6 +364,38 @@ const engineFloorSelectorMatches = (value, floor) => {
|
|
|
362
364
|
return Number(value.split(".").at(0)) === floor.major && (0, semver.satisfies)(value, floor.range);
|
|
363
365
|
};
|
|
364
366
|
//#endregion
|
|
367
|
+
//#region src/toolchain-typescript-source-api.ts
|
|
368
|
+
const readSourceApiInput = ({ root, source, tracked }) => {
|
|
369
|
+
if (!tracked.has(source)) return void 0;
|
|
370
|
+
const resolvedRoot = (0, node_fs.realpathSync)(root);
|
|
371
|
+
const input = node_path.default.resolve(resolvedRoot, source);
|
|
372
|
+
try {
|
|
373
|
+
if ((0, node_fs.realpathSync)(input) !== input || !(0, node_fs.lstatSync)(input).isFile()) return void 0;
|
|
374
|
+
return (0, node_fs.readFileSync)(input, "utf8");
|
|
375
|
+
} catch {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
const sourceApiImportProblem = ({ source, content, name }) => {
|
|
380
|
+
if (content === void 0) return "must be a tracked, readable regular source file without symlinks";
|
|
381
|
+
try {
|
|
382
|
+
const { code } = (0, esbuild.transformSync)(content, {
|
|
383
|
+
sourcefile: source,
|
|
384
|
+
loader: source.endsWith("x") ? "tsx" : "ts",
|
|
385
|
+
format: "esm",
|
|
386
|
+
tsconfigRaw: { compilerOptions: { verbatimModuleSyntax: true } },
|
|
387
|
+
logLevel: "silent"
|
|
388
|
+
});
|
|
389
|
+
if ((0, acorn.parse)(code, {
|
|
390
|
+
ecmaVersion: "latest",
|
|
391
|
+
sourceType: "module"
|
|
392
|
+
}).body.some((node) => node.type === "ImportDeclaration" && node.source.value === name && node.specifiers.length > 0)) return void 0;
|
|
393
|
+
return `no longer has a static value import from ${name}; remove the declaration or name its current importer`;
|
|
394
|
+
} catch {
|
|
395
|
+
return "cannot parse the declared compiler API importer";
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
//#endregion
|
|
365
399
|
//#region src/toolchain-packages.ts
|
|
366
400
|
const packageRules = [
|
|
367
401
|
"bun-pins",
|
|
@@ -379,7 +413,7 @@ const installSections = [
|
|
|
379
413
|
"optionalDependencies"
|
|
380
414
|
];
|
|
381
415
|
const allowlistFile = "stll-toolchain.json";
|
|
382
|
-
const record$
|
|
416
|
+
const record$8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
383
417
|
/** `npm:typescript@7.0.2` installs a compiler under another name; returns its version. */
|
|
384
418
|
const typescriptAliasVersion = (spec) => {
|
|
385
419
|
if (typeof spec !== "string") return void 0;
|
|
@@ -409,22 +443,44 @@ const parseTypescriptAllowlist = (input) => {
|
|
|
409
443
|
return input.map((entry) => {
|
|
410
444
|
const shown = JSON.stringify(entry);
|
|
411
445
|
const text = (key) => {
|
|
412
|
-
const value = record$
|
|
446
|
+
const value = record$8(entry) ? entry[key] : void 0;
|
|
413
447
|
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
414
448
|
};
|
|
415
449
|
const workspace = text("workspace");
|
|
416
450
|
const name = text("name");
|
|
417
451
|
const spec = text("spec");
|
|
418
452
|
const dependency = text("dependency");
|
|
453
|
+
const sourceApi = record$8(entry) && entry["kind"] === "source-api";
|
|
454
|
+
const keys = sourceApi ? [
|
|
455
|
+
"workspace",
|
|
456
|
+
"name",
|
|
457
|
+
"spec",
|
|
458
|
+
"reason",
|
|
459
|
+
"kind",
|
|
460
|
+
"sources"
|
|
461
|
+
] : [...allowlistKeys, ...record$8(entry) && entry["kind"] === "dependency" ? ["kind"] : []];
|
|
419
462
|
const reason = text("reason");
|
|
420
|
-
if (!record$
|
|
463
|
+
if (!record$8(entry) || Object.keys(entry).length !== keys.length || Object.keys(entry).some((key) => !keys.includes(key)) || workspace === void 0 || name === void 0 || spec === void 0 || !sourceApi && dependency === void 0 || reason === void 0) throw new Error(`typescriptAllowlist entry ${shown} must have exactly workspace, name, spec, dependency and reason as nonempty strings: ${allowlistFormat}`);
|
|
421
464
|
if (workspace !== "." && (node_path.default.posix.normalize(workspace) !== workspace || workspace.startsWith("/") || workspace.endsWith("/") || workspace === ".." || workspace.startsWith("../"))) throw new Error(`typescriptAllowlist entry ${shown}: workspace must be "." for the root or a normalized relative package directory such as "apps/web"`);
|
|
422
465
|
if (!exactTypescriptSpec(spec)) throw new Error(`typescriptAllowlist entry ${shown}: spec must be the exact spec the manifest resolves to, such as "6.0.3" or "npm:typescript@6.0.3", not a range or catalog reference`);
|
|
423
466
|
if (!isTypescriptInstall(name, spec)) throw new Error(`typescriptAllowlist entry ${shown}: name must be typescript or @typescript/native, or an alias whose spec is npm:typescript@<version> or npm:@typescript/native@<version>`);
|
|
424
|
-
if (typescriptPackages.has(dependency) || dependency === name) throw new Error(`typescriptAllowlist entry ${shown}: dependency must name the package in that workspace that needs ${name}, not a TypeScript package`);
|
|
467
|
+
if (dependency !== void 0 && (typescriptPackages.has(dependency) || dependency === name)) throw new Error(`typescriptAllowlist entry ${shown}: dependency must name the package in that workspace that needs ${name}, not a TypeScript package`);
|
|
425
468
|
const key = `${workspace}\0${name}`;
|
|
426
469
|
if (seen.has(key)) throw new Error(`typescriptAllowlist declares ${name} for ${workspace} more than once; keep one entry`);
|
|
427
470
|
seen.add(key);
|
|
471
|
+
if (sourceApi) {
|
|
472
|
+
const sources = entry["sources"];
|
|
473
|
+
if (!Array.isArray(sources) || sources.length === 0 || !sources.every((source) => typeof source === "string" && node_path.default.posix.normalize(source) === source && !source.startsWith("/") && source !== ".." && !source.startsWith("../") && !source.includes("\\") && !/[?*{}[\]]/.test(source) && /\.[cm]?[jt]sx?$/.test(source) && !/\.d\.[cm]?ts$/.test(source) && !source.split("/").some((part) => ["node_modules", "vendor"].includes(part))) || new Set(sources).size !== sources.length) throw new Error(`typescriptAllowlist source-api entry ${shown} requires unique normalized source file paths`);
|
|
474
|
+
return {
|
|
475
|
+
kind: "source-api",
|
|
476
|
+
workspace,
|
|
477
|
+
name,
|
|
478
|
+
spec,
|
|
479
|
+
reason,
|
|
480
|
+
sources
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
if (dependency === void 0) throw new Error("Missing TypeScript dependency owner");
|
|
428
484
|
return {
|
|
429
485
|
workspace,
|
|
430
486
|
name,
|
|
@@ -993,10 +1049,10 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
993
1049
|
const document = pnpm ? (0, yaml.parseDocument)(text, { lineCounter }) : void 0;
|
|
994
1050
|
if (document !== void 0 && document.errors.length > 0) throw new Error("invalid YAML");
|
|
995
1051
|
const json = document === void 0 ? JSON.parse(text) : document.toJS();
|
|
996
|
-
if (!record$
|
|
1052
|
+
if (!record$8(json)) throw new Error("manifest must be an object");
|
|
997
1053
|
if (pnpm) {
|
|
998
|
-
if (json["catalog"] !== void 0 && !record$
|
|
999
|
-
if (json["catalogs"] !== void 0 && (!record$
|
|
1054
|
+
if (json["catalog"] !== void 0 && !record$8(json["catalog"])) throw new Error("catalog must be a mapping");
|
|
1055
|
+
if (json["catalogs"] !== void 0 && (!record$8(json["catalogs"]) || Object.values(json["catalogs"]).some((catalog) => !record$8(catalog)))) throw new Error("named catalogs must be mappings");
|
|
1000
1056
|
if (json["packages"] !== void 0 && (!Array.isArray(json["packages"]) || !json["packages"].every((pattern) => typeof pattern === "string"))) throw new Error("workspace packages must be patterns");
|
|
1001
1057
|
}
|
|
1002
1058
|
manifests.set(file, json);
|
|
@@ -1010,13 +1066,13 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1010
1066
|
catalog: json["overrides"],
|
|
1011
1067
|
prefix: ["overrides"]
|
|
1012
1068
|
},
|
|
1013
|
-
...record$
|
|
1069
|
+
...record$8(json["catalogs"]) ? Object.entries(json["catalogs"]).map(([name, catalog]) => ({
|
|
1014
1070
|
catalog,
|
|
1015
1071
|
prefix: ["catalogs", name]
|
|
1016
1072
|
})) : []
|
|
1017
1073
|
];
|
|
1018
1074
|
for (const { catalog, prefix } of catalogEntries) {
|
|
1019
|
-
if (!record$
|
|
1075
|
+
if (!record$8(catalog)) continue;
|
|
1020
1076
|
for (const [name, value] of Object.entries(catalog)) {
|
|
1021
1077
|
const node = document.getIn([...prefix, name], true);
|
|
1022
1078
|
if (!(0, yaml.isNode)(node) || node.range === void 0 || node.range === null) continue;
|
|
@@ -1035,7 +1091,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1035
1091
|
});
|
|
1036
1092
|
}
|
|
1037
1093
|
}
|
|
1038
|
-
const catalogSources = (json) => [json, ...record$
|
|
1094
|
+
const catalogSources = (json) => [json, ...record$8(json["workspaces"]) ? [json["workspaces"]] : []];
|
|
1039
1095
|
const pnpmWorkspaceFor = (file) => {
|
|
1040
1096
|
let directory = node_path.default.posix.dirname(file);
|
|
1041
1097
|
while (true) {
|
|
@@ -1058,8 +1114,8 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1058
1114
|
patterns: workspace["packages"],
|
|
1059
1115
|
rootMembership: "implicit"
|
|
1060
1116
|
})) return void 0;
|
|
1061
|
-
if (name === "" || name === "default") return workspace["catalog"] ?? (record$
|
|
1062
|
-
return record$
|
|
1117
|
+
if (name === "" || name === "default") return workspace["catalog"] ?? (record$8(workspace["catalogs"]) ? workspace["catalogs"]["default"] : void 0);
|
|
1118
|
+
return record$8(workspace["catalogs"]) ? workspace["catalogs"][name] : void 0;
|
|
1063
1119
|
}
|
|
1064
1120
|
let directory = node_path.default.posix.dirname(file);
|
|
1065
1121
|
while (true) {
|
|
@@ -1067,7 +1123,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1067
1123
|
if (manifest !== void 0) {
|
|
1068
1124
|
for (const source of catalogSources(manifest)) {
|
|
1069
1125
|
if (name === "" && source["catalog"] !== void 0) return source["catalog"];
|
|
1070
|
-
if (record$
|
|
1126
|
+
if (record$8(source["catalogs"]) && source["catalogs"][name] !== void 0) return source["catalogs"][name];
|
|
1071
1127
|
}
|
|
1072
1128
|
if (manifest["workspaces"] !== void 0) return void 0;
|
|
1073
1129
|
}
|
|
@@ -1085,7 +1141,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1085
1141
|
};
|
|
1086
1142
|
visited.add(value);
|
|
1087
1143
|
const catalog = catalogFor(file, value.slice(8));
|
|
1088
|
-
if (!record$
|
|
1144
|
+
if (!record$8(catalog)) return {
|
|
1089
1145
|
value: void 0,
|
|
1090
1146
|
catalogs
|
|
1091
1147
|
};
|
|
@@ -1110,7 +1166,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1110
1166
|
const workspaces = manifests.get(node_path.default.posix.join(directory, "package.json"))?.["workspaces"];
|
|
1111
1167
|
if (workspaces !== void 0) return {
|
|
1112
1168
|
directory,
|
|
1113
|
-
patterns: record$
|
|
1169
|
+
patterns: record$8(workspaces) ? workspaces["packages"] : workspaces,
|
|
1114
1170
|
source: "json"
|
|
1115
1171
|
};
|
|
1116
1172
|
if (directory === ".") return void 0;
|
|
@@ -1144,7 +1200,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1144
1200
|
if (json === void 0) continue;
|
|
1145
1201
|
for (const section of installSections) {
|
|
1146
1202
|
const entries = json[section];
|
|
1147
|
-
if (!record$
|
|
1203
|
+
if (!record$8(entries) || !Object.hasOwn(entries, entry.name)) continue;
|
|
1148
1204
|
const { value, catalogs } = resolveThrough(file, entry.name, entries[entry.name]);
|
|
1149
1205
|
if (value !== entry.spec) continue;
|
|
1150
1206
|
for (const catalog of catalogs) {
|
|
@@ -1176,7 +1232,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1176
1232
|
});
|
|
1177
1233
|
return `${name} ${shown} in ${file} is not allowed: ${typescriptGuidance}; if a dependency of this workspace needs it, add ${suggestion} to typescriptAllowlist in ${allowlistFile}`;
|
|
1178
1234
|
}
|
|
1179
|
-
return value === entry.spec ? void 0 : `${name} in ${file} resolves to ${shown}, but typescriptAllowlist in ${allowlistFile} allows ${entry.spec} for ${entry.dependency}; declare ${entry.spec} or update the entry's spec`;
|
|
1235
|
+
return value === entry.spec ? void 0 : `${name} in ${file} resolves to ${shown}, but typescriptAllowlist in ${allowlistFile} allows ${entry.spec} for ${entry.kind === "source-api" ? "source API imports" : entry.dependency}; declare ${entry.spec} or update the entry's spec`;
|
|
1180
1236
|
};
|
|
1181
1237
|
const peerRange = (value) => typeof value === "string" && (0, semver.validRange)(value) !== null;
|
|
1182
1238
|
for (const [file, json] of manifests) {
|
|
@@ -1189,7 +1245,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1189
1245
|
message: `packageManager Bun version must be ${policy.bun} (one exact release at or above the shared minimum, the same in every manifest), found ${JSON.stringify(manager)}`
|
|
1190
1246
|
});
|
|
1191
1247
|
const checkEntries = (entries, scope, location) => {
|
|
1192
|
-
if (!record$
|
|
1248
|
+
if (!record$8(entries)) return;
|
|
1193
1249
|
for (const [name, raw] of Object.entries(entries)) {
|
|
1194
1250
|
const value = resolve(file, name, raw);
|
|
1195
1251
|
if (isTypescriptInstall(name, value)) {
|
|
@@ -1229,13 +1285,13 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1229
1285
|
}
|
|
1230
1286
|
};
|
|
1231
1287
|
for (const key of installSections) checkEntries(json[key], "install");
|
|
1232
|
-
if (record$
|
|
1288
|
+
if (record$8(json["peerDependencies"])) for (const [name, value] of Object.entries(json["peerDependencies"])) {
|
|
1233
1289
|
const resolved = resolve(file, name, value);
|
|
1234
1290
|
if (isTypescriptInstall(name, resolved) && !peerRange(resolved)) checkEntries({ [name]: value }, "peer");
|
|
1235
1291
|
}
|
|
1236
1292
|
for (const source of catalogSources(json)) {
|
|
1237
1293
|
checkEntries(source["catalog"], "catalog");
|
|
1238
|
-
if (!record$
|
|
1294
|
+
if (!record$8(source["catalogs"])) continue;
|
|
1239
1295
|
for (const catalog of Object.values(source["catalogs"])) checkEntries(catalog, "catalog");
|
|
1240
1296
|
}
|
|
1241
1297
|
const ownedNames = /* @__PURE__ */ new Set([...pins.keys(), ...typescriptPackages]);
|
|
@@ -1247,11 +1303,11 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1247
1303
|
"optionalDependencies"
|
|
1248
1304
|
]) {
|
|
1249
1305
|
const entries = resolutionRoot[section];
|
|
1250
|
-
if (!record$
|
|
1306
|
+
if (!record$8(entries)) continue;
|
|
1251
1307
|
for (const [name, value] of Object.entries(entries)) resolutionDependencies[name] = resolve(file, name, value);
|
|
1252
1308
|
}
|
|
1253
1309
|
const checkResolutionMap = (entries, parent) => {
|
|
1254
|
-
if (!record$
|
|
1310
|
+
if (!record$8(entries)) return;
|
|
1255
1311
|
for (const [selector, replacement] of Object.entries(entries)) {
|
|
1256
1312
|
const target = selector === "." ? parent : selector;
|
|
1257
1313
|
if (target === void 0) continue;
|
|
@@ -1270,7 +1326,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1270
1326
|
});
|
|
1271
1327
|
continue;
|
|
1272
1328
|
}
|
|
1273
|
-
if (record$
|
|
1329
|
+
if (record$8(replacement)) {
|
|
1274
1330
|
checkResolutionMap(replacement, target);
|
|
1275
1331
|
continue;
|
|
1276
1332
|
}
|
|
@@ -1290,8 +1346,8 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1290
1346
|
};
|
|
1291
1347
|
checkResolutionMap(json["overrides"]);
|
|
1292
1348
|
checkResolutionMap(json["resolutions"]);
|
|
1293
|
-
if (record$
|
|
1294
|
-
const command = record$
|
|
1349
|
+
if (record$8(json["pnpm"])) checkResolutionMap(json["pnpm"]["overrides"]);
|
|
1350
|
+
const command = record$8(json["scripts"]) ? json["scripts"]["typecheck"] : void 0;
|
|
1295
1351
|
if (typeof command === "string") for (const words of compilerCommandSegments(command)) {
|
|
1296
1352
|
if (!invokesCompiler(words)) continue;
|
|
1297
1353
|
const normalized = words.join(" ");
|
|
@@ -1305,7 +1361,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1305
1361
|
});
|
|
1306
1362
|
break;
|
|
1307
1363
|
}
|
|
1308
|
-
if (record$
|
|
1364
|
+
if (record$8(json["peerDependencies"])) for (const [name, raw] of Object.entries(json["peerDependencies"])) {
|
|
1309
1365
|
if (!typescriptPackages.has(name)) continue;
|
|
1310
1366
|
const range = resolve(file, name, raw);
|
|
1311
1367
|
if (!peerRange(range)) continue;
|
|
@@ -1329,7 +1385,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1329
1385
|
message: `${name} peer support range must include ${release}, found ${range}`
|
|
1330
1386
|
});
|
|
1331
1387
|
}
|
|
1332
|
-
if (record$
|
|
1388
|
+
if (record$8(json["engines"]) && json["engines"]["node"] !== void 0 && (typeof json["engines"]["node"] !== "string" || !require_toolchain_inputs.nodeSupportRangeMatches(policy.node, json["engines"]["node"]))) add({
|
|
1333
1389
|
file,
|
|
1334
1390
|
rule: "node-engine",
|
|
1335
1391
|
key: "node",
|
|
@@ -1343,9 +1399,23 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1343
1399
|
if (json === void 0) return `${file} is missing or invalid; remove the entry`;
|
|
1344
1400
|
const declares = (name) => installSections.some((section) => {
|
|
1345
1401
|
const entries = json[section];
|
|
1346
|
-
return record$
|
|
1402
|
+
return record$8(entries) && Object.hasOwn(entries, name);
|
|
1347
1403
|
});
|
|
1348
1404
|
if (!declares(entry.name)) return `${file} no longer declares ${entry.name} in dependencies, devDependencies or optionalDependencies; remove the entry`;
|
|
1405
|
+
if (entry.kind === "source-api") {
|
|
1406
|
+
for (const source of entry.sources) {
|
|
1407
|
+
let owner = node_path.default.posix.dirname(source);
|
|
1408
|
+
while (owner !== "." && !manifests.has(`${owner}/package.json`)) owner = node_path.default.posix.dirname(owner);
|
|
1409
|
+
if (owner !== entry.workspace) return `${source} belongs to workspace ${owner}, not ${entry.workspace}`;
|
|
1410
|
+
const problem = sourceApiImportProblem({
|
|
1411
|
+
source,
|
|
1412
|
+
content: files[source],
|
|
1413
|
+
name: entry.name
|
|
1414
|
+
});
|
|
1415
|
+
if (problem !== void 0) return `${source}: ${problem}`;
|
|
1416
|
+
}
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1349
1419
|
if (!declares(entry.dependency)) return `${file} no longer depends on ${entry.dependency}; remove the entry and the ${entry.name} declaration, or name the dependency that still needs it`;
|
|
1350
1420
|
};
|
|
1351
1421
|
for (const entry of typescriptAllowlist) {
|
|
@@ -1382,9 +1452,9 @@ const extensionKinds = new Map(Object.entries({
|
|
|
1382
1452
|
const scriptSourceKind = (file) => extensionKinds.get((0, node_path.extname)(file));
|
|
1383
1453
|
//#endregion
|
|
1384
1454
|
//#region src/toolchain-script-lifecycle.ts
|
|
1385
|
-
const record$
|
|
1455
|
+
const record$7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1386
1456
|
const declaredManager = (value) => typeof value === "string" ? /^(bun|npm|pnpm|yarn)@(\d+\.\d+\.\d+)(?:\+\S+)?$/u.exec(value) : null;
|
|
1387
|
-
const rootScriptManager = (manifest, context) => declaredManager(context.packageManager ?? (record$
|
|
1457
|
+
const rootScriptManager = (manifest, context) => declaredManager(context.packageManager ?? (record$7(manifest) ? manifest["packageManager"] : void 0))?.at(1);
|
|
1388
1458
|
const npmHooksEnabled = (file) => {
|
|
1389
1459
|
if (file?.type === "unknown") throw new Error("npm lifecycle configuration must be a tracked regular file");
|
|
1390
1460
|
if (file?.type !== "tracked") return true;
|
|
@@ -1415,14 +1485,18 @@ const yamlSettings = (file, manager) => {
|
|
|
1415
1485
|
} catch {
|
|
1416
1486
|
throw new Error(`Cannot parse ${manager} lifecycle configuration`);
|
|
1417
1487
|
}
|
|
1418
|
-
if (!record$
|
|
1488
|
+
if (!record$7(settings)) throw new Error(`${manager} lifecycle configuration must be a mapping`);
|
|
1419
1489
|
return settings;
|
|
1420
1490
|
};
|
|
1491
|
+
const pnpmLifecycleVersions = ["12.9.1", "12.15.0"];
|
|
1421
1492
|
const pnpmHooksEnabled = (manifest, context) => {
|
|
1422
1493
|
const declared = declaredManager(context.packageManager ?? manifest["packageManager"]);
|
|
1423
|
-
const dependency = [manifest["dependencies"], manifest["devDependencies"]].flatMap((field) => record$
|
|
1424
|
-
|
|
1425
|
-
|
|
1494
|
+
const dependency = [manifest["dependencies"], manifest["devDependencies"]].flatMap((field) => record$7(field) ? [field["pnpm"]] : []).find((value) => typeof value === "string");
|
|
1495
|
+
const version = declared?.at(1) === "pnpm" ? declared.at(2) : dependency;
|
|
1496
|
+
if (!pnpmLifecycleVersions.some((supported) => version === supported)) throw new Error(`pnpm lifecycle discovery requires declared pnpm@${pnpmLifecycleVersions.join(" or pnpm@")}`);
|
|
1497
|
+
const settings = yamlSettings(context.pnpmWorkspace, "pnpm");
|
|
1498
|
+
if ("include" in settings) throw new Error("Delegated pnpm settings require explicit lifecycle discovery support");
|
|
1499
|
+
const enabled = settings["enablePrePostScripts"];
|
|
1426
1500
|
if (enabled === void 0) return true;
|
|
1427
1501
|
if (typeof enabled !== "boolean") throw new Error("pnpm enablePrePostScripts must be a literal boolean");
|
|
1428
1502
|
return enabled;
|
|
@@ -1464,7 +1538,7 @@ const scriptLifecycleStages = ({ name, manager, manifest, scripts, context }) =>
|
|
|
1464
1538
|
};
|
|
1465
1539
|
//#endregion
|
|
1466
1540
|
//#region src/toolchain-lint-command.ts
|
|
1467
|
-
const record$
|
|
1541
|
+
const record$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1468
1542
|
const scriptManagers = /* @__PURE__ */ new Set([
|
|
1469
1543
|
"bun",
|
|
1470
1544
|
"npm",
|
|
@@ -1581,7 +1655,7 @@ const lintInventoryCommands = (manifest, forceEnabled = false, context = {}) =>
|
|
|
1581
1655
|
const errors = [];
|
|
1582
1656
|
const workspaceTasks = /* @__PURE__ */ new Set();
|
|
1583
1657
|
const filteredBuilds = /* @__PURE__ */ new Set();
|
|
1584
|
-
const declaresEngine = record$
|
|
1658
|
+
const declaresEngine = record$6(manifest) && [manifest["dependencies"], manifest["devDependencies"]].some((dependencies) => record$6(dependencies) && "oxlint-tsgolint" in dependencies);
|
|
1585
1659
|
const enabled = forceEnabled || declaresEngine;
|
|
1586
1660
|
if (!enabled) return {
|
|
1587
1661
|
commands,
|
|
@@ -1589,14 +1663,14 @@ const lintInventoryCommands = (manifest, forceEnabled = false, context = {}) =>
|
|
|
1589
1663
|
enabled,
|
|
1590
1664
|
workspaceTask: false
|
|
1591
1665
|
};
|
|
1592
|
-
if (!record$
|
|
1666
|
+
if (!record$6(manifest)) return {
|
|
1593
1667
|
commands,
|
|
1594
1668
|
errors: ["lint inventory requires an object package manifest"],
|
|
1595
1669
|
enabled,
|
|
1596
1670
|
workspaceTask: false
|
|
1597
1671
|
};
|
|
1598
1672
|
const scripts = manifest["scripts"];
|
|
1599
|
-
if (!record$
|
|
1673
|
+
if (!record$6(scripts)) return {
|
|
1600
1674
|
commands,
|
|
1601
1675
|
errors: ["type-aware linting requires a root lint script"],
|
|
1602
1676
|
enabled,
|
|
@@ -1738,7 +1812,7 @@ const checkProjectOwnership = ({ trackedFiles, lintedFiles, projectFiles }) => [
|
|
|
1738
1812
|
line: 1,
|
|
1739
1813
|
message: "type-aware lint would use an inferred project; include this file in a declared tsconfig project (for Node scripts, add scripts/tsconfig.json extending @stll/typescript-config/node.json; see the Node scripts recipe in stella/tooling README)"
|
|
1740
1814
|
}));
|
|
1741
|
-
const record$
|
|
1815
|
+
const record$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1742
1816
|
const stringList = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
1743
1817
|
const readCommand = ({ root, executable, args, runtime }) => {
|
|
1744
1818
|
const result = (0, node_child_process.spawnSync)(runtime, [
|
|
@@ -1782,7 +1856,7 @@ const resolveProjectApi = (root) => {
|
|
|
1782
1856
|
for (const base of [require("url").pathToFileURL(__filename).href, node_path.default.join(root, "package.json")]) try {
|
|
1783
1857
|
return (0, node_url.pathToFileURL)((0, node_module.createRequire)(base).resolve("tsrs/unstable/async")).href;
|
|
1784
1858
|
} catch (error) {
|
|
1785
|
-
const code = record$
|
|
1859
|
+
const code = record$5(error) ? error["code"] : void 0;
|
|
1786
1860
|
if (code !== "MODULE_NOT_FOUND" && code !== "ERR_MODULE_NOT_FOUND") throw error;
|
|
1787
1861
|
}
|
|
1788
1862
|
throw new Error("configuration discovery needs tsrs: reinstall @stll/oxlint-config, which depends on it");
|
|
@@ -1805,7 +1879,7 @@ const readProjectConfig = (root, config) => {
|
|
|
1805
1879
|
if (result.error !== void 0) throw result.error;
|
|
1806
1880
|
if (result.status !== 0) throw new Error(`configuration discovery failed: ${(result.stderr + result.stdout).trim()}`);
|
|
1807
1881
|
const parsed = JSON.parse(result.stdout);
|
|
1808
|
-
if (!record$
|
|
1882
|
+
if (!record$5(parsed) || !stringList(parsed["files"]) || !stringList(parsed["references"]) || typeof parsed["allowJs"] !== "boolean" || typeof parsed["checkJs"] !== "boolean") throw new Error("compiler configuration must list files and references");
|
|
1809
1883
|
return {
|
|
1810
1884
|
files: parsed["files"],
|
|
1811
1885
|
references: parsed["references"],
|
|
@@ -1830,7 +1904,7 @@ const lifecycleFile = (root, file, tracked) => {
|
|
|
1830
1904
|
};
|
|
1831
1905
|
/** Inventory and config expansion only: neither subprocess lints or typechecks. */
|
|
1832
1906
|
const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
1833
|
-
const packageManager = record$
|
|
1907
|
+
const packageManager = record$5(manifest) ? manifest["packageManager"] : void 0;
|
|
1834
1908
|
const managerUsesPnpmWorkspace = packageManager === void 0 || typeof packageManager === "string" && packageManager.startsWith("pnpm@");
|
|
1835
1909
|
const discovered = /* @__PURE__ */ new Map();
|
|
1836
1910
|
const workspaceMembers = (pnpmMembership) => {
|
|
@@ -1866,7 +1940,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
1866
1940
|
return nestedLifecycle;
|
|
1867
1941
|
};
|
|
1868
1942
|
const lifecycle = {
|
|
1869
|
-
packageManager: record$
|
|
1943
|
+
packageManager: record$5(manifest) ? manifest["packageManager"] : void 0,
|
|
1870
1944
|
npmrc: lifecycleFile(root, ".npmrc", trackedFiles),
|
|
1871
1945
|
pnpmWorkspace: lifecycleFile(root, "pnpm-workspace.yaml", trackedFiles),
|
|
1872
1946
|
yarnrc: lifecycleFile(root, ".yarnrc.yml", trackedFiles),
|
|
@@ -1877,7 +1951,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
1877
1951
|
if (member === void 0) throw new Error("Missing selected workspace");
|
|
1878
1952
|
const [directory, pkg] = member;
|
|
1879
1953
|
const scripts = pkg["scripts"];
|
|
1880
|
-
if (!record$
|
|
1954
|
+
if (!record$5(scripts) || typeof scripts["build"] !== "string") throw new Error("Filtered Bun build requires a literal build script");
|
|
1881
1955
|
if (["prebuild", "postbuild"].some((name) => scripts[name] !== void 0 && scripts[name] !== "")) throw new Error("Filtered Bun build lifecycle hooks are unsupported for lint inventory; invoke lint scopes explicitly");
|
|
1882
1956
|
if (activeBuilds.has(directory)) throw new Error("Cyclic filtered Bun build delegation");
|
|
1883
1957
|
activeBuilds.add(directory);
|
|
@@ -1919,7 +1993,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
1919
1993
|
for (const [directory, workspace] of workspaceMembers(managerUsesPnpmWorkspace)) {
|
|
1920
1994
|
if (directory === ".") continue;
|
|
1921
1995
|
const file = `${directory}/package.json`;
|
|
1922
|
-
if (!record$
|
|
1996
|
+
if (!record$5(workspace["scripts"]) || workspace["scripts"]["lint"] === void 0) continue;
|
|
1923
1997
|
const nestedLifecycle = workspaceLifecycle(directory);
|
|
1924
1998
|
const nested = lintInventoryCommands(workspace, true, nestedLifecycle);
|
|
1925
1999
|
if (nested.workspaceTask) throw new Error(`nested workspace lint delegation is unsupported: ${file}`);
|
|
@@ -2028,7 +2102,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
2028
2102
|
};
|
|
2029
2103
|
//#endregion
|
|
2030
2104
|
//#region src/toolchain-rust.ts
|
|
2031
|
-
const record$
|
|
2105
|
+
const record$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2032
2106
|
/**
|
|
2033
2107
|
* Exceptions authorize one immutable release, never a channel or an entire
|
|
2034
2108
|
* rule. Expiry is scoped separately by checkRustExceptionExpiry.
|
|
@@ -2038,7 +2112,7 @@ const parseRustExceptions = (input) => {
|
|
|
2038
2112
|
if (!Array.isArray(input)) throw new Error("rustExceptions must be an array");
|
|
2039
2113
|
const pins = /* @__PURE__ */ new Set();
|
|
2040
2114
|
return input.map((entry) => {
|
|
2041
|
-
if (!record$
|
|
2115
|
+
if (!record$4(entry) || Object.keys(entry).length !== 4 || Object.keys(entry).some((key) => ![
|
|
2042
2116
|
"pin",
|
|
2043
2117
|
"reason",
|
|
2044
2118
|
"tracking",
|
|
@@ -2056,8 +2130,8 @@ const parseRustExceptions = (input) => {
|
|
|
2056
2130
|
/** Whether the base stll-toolchain.json declares this exception with the same expiry. */
|
|
2057
2131
|
const declaresException = (baseText, { pin, expires }) => {
|
|
2058
2132
|
const base = JSON.parse(baseText);
|
|
2059
|
-
const exceptions = record$
|
|
2060
|
-
return Array.isArray(exceptions) && exceptions.some((entry) => record$
|
|
2133
|
+
const exceptions = record$4(base) ? base["rustExceptions"] : void 0;
|
|
2134
|
+
return Array.isArray(exceptions) && exceptions.some((entry) => record$4(entry) && entry["pin"] === pin && entry["expires"] === expires);
|
|
2061
2135
|
};
|
|
2062
2136
|
/** An exception expires on its date; the scope decides whether that fails this run. */
|
|
2063
2137
|
const checkRustExceptionExpiry = ({ exceptions, now = /* @__PURE__ */ new Date(), scope }) => {
|
|
@@ -2195,7 +2269,9 @@ const checkRustReferences = (options) => {
|
|
|
2195
2269
|
}
|
|
2196
2270
|
if (token.value.startsWith("-")) continue;
|
|
2197
2271
|
selected++;
|
|
2198
|
-
|
|
2272
|
+
const installFixture = options.type === "file" && require_toolchain_inputs.isShellScript(file, text) && subcommand === "toolchain" && invocation.at(1)?.value === "install" && !invocation.some((word) => word.value === "--default") ? options.installFixtures?.find((entry) => entry.path === file && entry.pin === token.value) : void 0;
|
|
2273
|
+
if (installFixture !== void 0) options.onInstallFixture?.(installFixture);
|
|
2274
|
+
else select(token.value, "rustup toolchain", token.line);
|
|
2199
2275
|
if (firstSelectorOnly) break;
|
|
2200
2276
|
}
|
|
2201
2277
|
if (selected === 0 && requiresSelector) select(void 0, "rustup toolchain", firstLine);
|
|
@@ -2216,7 +2292,7 @@ const checkRustReferences = (options) => {
|
|
|
2216
2292
|
if (require_toolchain_inputs.isShellScript(file, text)) shell(text);
|
|
2217
2293
|
if (/(?:^|\/)package\.json$/.test(file)) try {
|
|
2218
2294
|
const object = JSON.parse(text);
|
|
2219
|
-
if (record$
|
|
2295
|
+
if (record$4(object) && record$4(object["scripts"])) {
|
|
2220
2296
|
for (const [name, script] of Object.entries(object["scripts"])) if (typeof script === "string") shell(script, lineOf(`"${name}"`));
|
|
2221
2297
|
}
|
|
2222
2298
|
} catch {}
|
|
@@ -2227,7 +2303,7 @@ const checkRustReferences = (options) => {
|
|
|
2227
2303
|
const version = tag === void 0 ? void 0 : require_toolchain_dependabot.rustImageVersion(tag);
|
|
2228
2304
|
select(version, "Rust service image", lineOf(value));
|
|
2229
2305
|
} else if (Array.isArray(value)) value.forEach(images);
|
|
2230
|
-
else if (record$
|
|
2306
|
+
else if (record$4(value)) {
|
|
2231
2307
|
for (const [key, entry] of Object.entries(value)) if (key !== "name") images(entry);
|
|
2232
2308
|
}
|
|
2233
2309
|
};
|
|
@@ -2251,13 +2327,13 @@ const checkRustReferences = (options) => {
|
|
|
2251
2327
|
value.forEach(visit);
|
|
2252
2328
|
return;
|
|
2253
2329
|
}
|
|
2254
|
-
if (!record$
|
|
2255
|
-
if (record$
|
|
2330
|
+
if (!record$4(value)) return;
|
|
2331
|
+
if (record$4(value["env"]) && value["env"]["RUSTUP_TOOLCHAIN"] !== void 0) select(value["env"]["RUSTUP_TOOLCHAIN"], "RUSTUP_TOOLCHAIN", lineOf("RUSTUP_TOOLCHAIN"));
|
|
2256
2332
|
if (typeof value["run"] === "string") shell(value["run"], lineOf("run:"));
|
|
2257
2333
|
const uses = value["uses"];
|
|
2258
2334
|
if (typeof uses === "string") {
|
|
2259
2335
|
const action = uses.split("@").at(0)?.toLowerCase();
|
|
2260
|
-
const inputs = record$
|
|
2336
|
+
const inputs = record$4(value["with"]) ? value["with"] : {};
|
|
2261
2337
|
if (action === "dtolnay/rust-toolchain" || action === "actions-rust-lang/setup-rust-toolchain") {
|
|
2262
2338
|
const entries = Object.entries(inputs).filter(([key]) => key.toLowerCase() === "toolchain");
|
|
2263
2339
|
if (entries.length !== 1) select(void 0, `${action} requires an explicit exact toolchain input`, lineOf(uses));
|
|
@@ -2271,8 +2347,8 @@ const checkRustReferences = (options) => {
|
|
|
2271
2347
|
}
|
|
2272
2348
|
}
|
|
2273
2349
|
for (const [key, child] of Object.entries(value)) {
|
|
2274
|
-
if (key === "workflow_call" && record$
|
|
2275
|
-
for (const [input, declaration] of Object.entries(child["inputs"])) if (["toolchain", "rust-toolchain"].includes(input) && record$
|
|
2350
|
+
if (key === "workflow_call" && record$4(child) && record$4(child["inputs"])) {
|
|
2351
|
+
for (const [input, declaration] of Object.entries(child["inputs"])) if (["toolchain", "rust-toolchain"].includes(input) && record$4(declaration) && declaration["default"] !== void 0) select(declaration["default"], `reusable workflow default ${input}`, lineOf("default:"));
|
|
2276
2352
|
}
|
|
2277
2353
|
visit(child);
|
|
2278
2354
|
}
|
|
@@ -2283,7 +2359,7 @@ const checkRustReferences = (options) => {
|
|
|
2283
2359
|
};
|
|
2284
2360
|
//#endregion
|
|
2285
2361
|
//#region src/toolchain-workflow-trust.ts
|
|
2286
|
-
const record$
|
|
2362
|
+
const record$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2287
2363
|
const booleanParts = (source, operator) => {
|
|
2288
2364
|
const parts = [];
|
|
2289
2365
|
let depth = 0;
|
|
@@ -2533,7 +2609,7 @@ const safeAction = (uses, inputs) => {
|
|
|
2533
2609
|
const approved = Object.entries(workflowTrustBootstrapCatalog.actions).find(([name]) => name === repository);
|
|
2534
2610
|
if (approved === void 0) return false;
|
|
2535
2611
|
if (inputs === void 0) return repository === "actions/checkout";
|
|
2536
|
-
if (!record$
|
|
2612
|
+
if (!record$3(inputs)) return false;
|
|
2537
2613
|
if (repository !== "actions/checkout") {
|
|
2538
2614
|
const version = inputs["bun-version"];
|
|
2539
2615
|
if (typeof version !== "string" || !/^v?[0-9]+(?:\.(?:[0-9]+|x)){0,2}$/u.test(version)) return false;
|
|
@@ -2546,7 +2622,7 @@ const safeAction = (uses, inputs) => {
|
|
|
2546
2622
|
].includes(typeof value) && (name !== "submodules" || value === false || value === "false"));
|
|
2547
2623
|
};
|
|
2548
2624
|
const safeShell = (shell) => shell === void 0 || shell === "bash" || shell === "sh";
|
|
2549
|
-
const safeEnvironment = (value) => value === void 0 || record$
|
|
2625
|
+
const safeEnvironment = (value) => value === void 0 || record$3(value) && Object.keys(value).every((name) => [
|
|
2550
2626
|
"HEAD_REPO",
|
|
2551
2627
|
"BASE_REPO",
|
|
2552
2628
|
"HAS_RUN_CI_LABEL",
|
|
@@ -2560,25 +2636,25 @@ const safeEnvironment = (value) => value === void 0 || record$2(value) && Object
|
|
|
2560
2636
|
"HAWK_VERSION",
|
|
2561
2637
|
"HAWK_X86_64_UNKNOWN_LINUX_GNU_SHA256"
|
|
2562
2638
|
].includes(name));
|
|
2563
|
-
const configuredShell = (value) => record$
|
|
2639
|
+
const configuredShell = (value) => record$3(value) && record$3(value["run"]) ? value["run"]["shell"] : void 0;
|
|
2564
2640
|
const workflowTrustRules = ["workflow-trust"];
|
|
2565
2641
|
const workflowEvents = (triggers) => {
|
|
2566
2642
|
if (typeof triggers === "string") return [triggers];
|
|
2567
2643
|
if (Array.isArray(triggers)) return triggers;
|
|
2568
|
-
if (record$
|
|
2644
|
+
if (record$3(triggers)) return Object.keys(triggers);
|
|
2569
2645
|
return [];
|
|
2570
2646
|
};
|
|
2571
2647
|
/** Enforce a workflow's declared trust boundary before repository dependency execution. */
|
|
2572
2648
|
const checkWorkflowTrust = (workflow) => {
|
|
2573
2649
|
const findings = [];
|
|
2574
|
-
if (!record$
|
|
2650
|
+
if (!record$3(workflow) || !record$3(workflow["jobs"])) return findings;
|
|
2575
2651
|
const jobs = workflow["jobs"];
|
|
2576
|
-
if (!record$
|
|
2652
|
+
if (!record$3(jobs["trust-check"])) return findings;
|
|
2577
2653
|
const triggers = workflow["on"];
|
|
2578
2654
|
const pullRequests = workflowEvents(triggers).filter((event) => event === "pull_request" || event === "pull_request_target");
|
|
2579
2655
|
if (pullRequests.length === 0) return findings;
|
|
2580
2656
|
for (const [name, job] of Object.entries(jobs)) {
|
|
2581
|
-
if (!record$
|
|
2657
|
+
if (!record$3(job)) continue;
|
|
2582
2658
|
const needs = job["needs"];
|
|
2583
2659
|
const ordered = name !== "trust-check" && (needs === "trust-check" || Array.isArray(needs) && needs.includes("trust-check"));
|
|
2584
2660
|
const permitsExecution = (stepCondition) => {
|
|
@@ -2615,7 +2691,7 @@ const checkWorkflowTrust = (workflow) => {
|
|
|
2615
2691
|
message
|
|
2616
2692
|
});
|
|
2617
2693
|
const services = job["services"];
|
|
2618
|
-
if (services !== void 0 && (!record$
|
|
2694
|
+
if (services !== void 0 && (!record$3(services) || Object.keys(services).length > 0)) findings.push({
|
|
2619
2695
|
type: "job",
|
|
2620
2696
|
property: "services",
|
|
2621
2697
|
job: name,
|
|
@@ -2624,7 +2700,7 @@ const checkWorkflowTrust = (workflow) => {
|
|
|
2624
2700
|
}
|
|
2625
2701
|
if (!Array.isArray(job["steps"])) continue;
|
|
2626
2702
|
for (const [index, step] of job["steps"].entries()) {
|
|
2627
|
-
if (!record$
|
|
2703
|
+
if (!record$3(step)) continue;
|
|
2628
2704
|
const run = step["run"];
|
|
2629
2705
|
const uses = step["uses"];
|
|
2630
2706
|
const unsafeRun = typeof run === "string" && (invokesRepositoryCode(run) || !safeShell(step["shell"] ?? configuredShell(job["defaults"]) ?? configuredShell(workflow["defaults"])) || ![
|
|
@@ -2662,7 +2738,7 @@ const runtimeRules = [
|
|
|
2662
2738
|
"action-pins",
|
|
2663
2739
|
...workflowTrustRules
|
|
2664
2740
|
];
|
|
2665
|
-
const record$
|
|
2741
|
+
const record$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2666
2742
|
const dockerEnvironmentAssignments = ({ source, escape }) => {
|
|
2667
2743
|
const words = [];
|
|
2668
2744
|
let word = "";
|
|
@@ -2795,13 +2871,15 @@ const pythonRequirementMatches = (requirement, version) => {
|
|
|
2795
2871
|
});
|
|
2796
2872
|
};
|
|
2797
2873
|
/** Check runtime declarations only in the files that declare or configure them. */
|
|
2798
|
-
const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, repository, onDelegated, dynamicSelectors = [], onDynamicSelector, rustExceptions = [], engineFloors = [], onEngineFloor }) => {
|
|
2874
|
+
const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, repository, onDelegated, dynamicSelectors = [], onDynamicSelector, rustExceptions = [], rustInstallFixtures = [], onRustInstallFixture, engineFloors = [], onEngineFloor }) => {
|
|
2799
2875
|
const diagnostics = checkRustReferences({
|
|
2800
2876
|
type: "file",
|
|
2801
2877
|
file,
|
|
2802
2878
|
text,
|
|
2803
2879
|
rust: policy.rust,
|
|
2804
|
-
exceptions: rustExceptions
|
|
2880
|
+
exceptions: rustExceptions,
|
|
2881
|
+
installFixtures: rustInstallFixtures,
|
|
2882
|
+
onInstallFixture: onRustInstallFixture
|
|
2805
2883
|
});
|
|
2806
2884
|
const name = node_path.default.posix.basename(file);
|
|
2807
2885
|
const lines = text.split(/\r?\n/);
|
|
@@ -2897,7 +2975,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2897
2975
|
const toolchain = parsed["toolchain"];
|
|
2898
2976
|
pin({
|
|
2899
2977
|
rule: "rust-version",
|
|
2900
|
-
value: record$
|
|
2978
|
+
value: record$2(toolchain) ? toolchain["channel"] : void 0,
|
|
2901
2979
|
expected: policy.rust,
|
|
2902
2980
|
label: "Rust channel",
|
|
2903
2981
|
line: lineOf("channel")
|
|
@@ -2925,15 +3003,15 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2925
3003
|
};
|
|
2926
3004
|
if (parsed !== void 0) {
|
|
2927
3005
|
const workspace = parsed["workspace"];
|
|
2928
|
-
const defaults = record$
|
|
2929
|
-
if (record$
|
|
3006
|
+
const defaults = record$2(workspace) ? workspace["package"] : void 0;
|
|
3007
|
+
if (record$2(defaults) && defaults["rust-version"] !== void 0) rustFloor(defaults["rust-version"]);
|
|
2930
3008
|
const pkg = parsed["package"];
|
|
2931
|
-
const version = record$
|
|
3009
|
+
const version = record$2(pkg) ? pkg["rust-version"] : void 0;
|
|
2932
3010
|
if (version !== void 0) {
|
|
2933
|
-
if (!record$
|
|
3011
|
+
if (!record$2(version)) rustFloor(version);
|
|
2934
3012
|
else {
|
|
2935
3013
|
let inherited;
|
|
2936
|
-
if (version["workspace"] === true && record$
|
|
3014
|
+
if (version["workspace"] === true && record$2(pkg)) {
|
|
2937
3015
|
const explicit = pkg["workspace"];
|
|
2938
3016
|
let directory = node_path.default.posix.dirname(file);
|
|
2939
3017
|
const explicitPath = typeof explicit === "string" && !explicit.includes("\\") && !explicit.includes(":") && !explicit.includes("${{") && !node_path.default.posix.isAbsolute(explicit) ? node_path.default.posix.normalize(node_path.default.posix.join(directory, explicit)) : void 0;
|
|
@@ -2941,9 +3019,9 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2941
3019
|
if (explicitPath !== void 0) directory = explicitPath;
|
|
2942
3020
|
while (true) {
|
|
2943
3021
|
const table = readManifest(node_path.default.posix.join(directory, "Cargo.toml"))?.["workspace"];
|
|
2944
|
-
if (record$
|
|
3022
|
+
if (record$2(table)) {
|
|
2945
3023
|
const fields = table["package"];
|
|
2946
|
-
inherited = record$
|
|
3024
|
+
inherited = record$2(fields) ? fields["rust-version"] : void 0;
|
|
2947
3025
|
break;
|
|
2948
3026
|
}
|
|
2949
3027
|
if (explicit !== void 0 || directory === ".") break;
|
|
@@ -2958,7 +3036,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2958
3036
|
}
|
|
2959
3037
|
if (name === "Pipfile") {
|
|
2960
3038
|
const requires = toml("python-version")?.["requires"];
|
|
2961
|
-
if (record$
|
|
3039
|
+
if (record$2(requires)) {
|
|
2962
3040
|
const minor = requires["python_version"];
|
|
2963
3041
|
if (minor !== void 0 && minor !== policy.python.split(".").slice(0, 2).join(".")) add({
|
|
2964
3042
|
rule: "python-version",
|
|
@@ -2977,7 +3055,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2977
3055
|
const parsed = toml("python-version");
|
|
2978
3056
|
if (parsed !== void 0) {
|
|
2979
3057
|
const project = parsed["project"];
|
|
2980
|
-
if (record$
|
|
3058
|
+
if (record$2(project) && project["requires-python"] !== void 0) {
|
|
2981
3059
|
if (!pythonRequirementMatches(project["requires-python"], policy.python)) add({
|
|
2982
3060
|
rule: "python-version",
|
|
2983
3061
|
line: lineOf("requires-python"),
|
|
@@ -2985,9 +3063,9 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2985
3063
|
});
|
|
2986
3064
|
}
|
|
2987
3065
|
const tool = parsed["tool"];
|
|
2988
|
-
const configuredUv = record$
|
|
3066
|
+
const configuredUv = record$2(tool) ? tool["uv"] : void 0;
|
|
2989
3067
|
const uv = name === "uv.toml" ? parsed : configuredUv;
|
|
2990
|
-
if (record$
|
|
3068
|
+
if (record$2(uv) && uv["python"] !== void 0) pin({
|
|
2991
3069
|
rule: "python-version",
|
|
2992
3070
|
value: uv["python"],
|
|
2993
3071
|
expected: policy.python,
|
|
@@ -2998,11 +3076,11 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2998
3076
|
}
|
|
2999
3077
|
if (require_toolchain_inputs.isMiseConfigPath(file)) {
|
|
3000
3078
|
const tools = toml("runtime-manager")?.["tools"];
|
|
3001
|
-
if (record$
|
|
3079
|
+
if (record$2(tools)) for (const [tool, expected] of Object.entries(versions)) {
|
|
3002
3080
|
const configured = tools[tool];
|
|
3003
3081
|
if (configured !== void 0) pin({
|
|
3004
3082
|
rule: tool === "rust" || tool === "core:rust" ? "rust-version" : "runtime-manager",
|
|
3005
|
-
value: record$
|
|
3083
|
+
value: record$2(configured) ? configured["version"] : configured,
|
|
3006
3084
|
expected,
|
|
3007
3085
|
selector: nodeTools.has(tool) ? "node" : void 0,
|
|
3008
3086
|
label: `mise ${tool}`,
|
|
@@ -3350,7 +3428,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
3350
3428
|
return diagnostics;
|
|
3351
3429
|
}
|
|
3352
3430
|
const nodeLine = (node) => {
|
|
3353
|
-
if (!record$
|
|
3431
|
+
if (!record$2(node) || !Array.isArray(node["range"])) return 1;
|
|
3354
3432
|
const offset = node["range"].at(0);
|
|
3355
3433
|
return typeof offset === "number" ? text.slice(0, offset).split("\n").length : 1;
|
|
3356
3434
|
};
|
|
@@ -3464,7 +3542,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
3464
3542
|
const manifest = JSON.parse(content ?? "");
|
|
3465
3543
|
pin({
|
|
3466
3544
|
rule,
|
|
3467
|
-
value: record$
|
|
3545
|
+
value: record$2(manifest) ? manifest["packageManager"] : void 0,
|
|
3468
3546
|
expected: `bun@${policy.bun}`,
|
|
3469
3547
|
label: "setup-bun version file packageManager",
|
|
3470
3548
|
line
|
|
@@ -3664,10 +3742,21 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
3664
3742
|
const selector = staticRepositoryPath(value) ? node_path.default.posix.normalize(value) : void 0;
|
|
3665
3743
|
const providers = checkoutProvidersFor(selector);
|
|
3666
3744
|
const binding = selector === void 0 ? void 0 : providers.at(-1);
|
|
3667
|
-
if (selector === void 0 || unknownCheckoutDestination || binding
|
|
3668
|
-
|
|
3745
|
+
if (selector === void 0 || unknownCheckoutDestination || binding?.source === "untrusted" || binding?.source === "delegated" && !/^(?:inputs\.[A-Za-z_][A-Za-z0-9_]*|github\.event\.workflow_run\.head_sha)$/.test(dynamicRefBody(binding.ref) ?? "") || hasAmbiguousCheckoutWriters(selector, providers)) {
|
|
3746
|
+
add({
|
|
3747
|
+
rule: "action-pins",
|
|
3748
|
+
line,
|
|
3749
|
+
message: "local actions require an unambiguous checkout of the current source snapshot"
|
|
3750
|
+
});
|
|
3751
|
+
return;
|
|
3752
|
+
}
|
|
3753
|
+
if (binding?.source === "delegated") onDelegated?.({
|
|
3754
|
+
path: file,
|
|
3669
3755
|
line,
|
|
3670
|
-
|
|
3756
|
+
tool: "action",
|
|
3757
|
+
selector,
|
|
3758
|
+
checkoutPath: binding.path,
|
|
3759
|
+
ref: binding.ref
|
|
3671
3760
|
});
|
|
3672
3761
|
return;
|
|
3673
3762
|
}
|
|
@@ -3965,6 +4054,54 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
3965
4054
|
return diagnostics;
|
|
3966
4055
|
};
|
|
3967
4056
|
//#endregion
|
|
4057
|
+
//#region src/toolchain-rust-fixtures.ts
|
|
4058
|
+
const record$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4059
|
+
const date = (value) => /^\d{4}-\d{2}-\d{2}$/.test(value) && Number.isFinite(Date.parse(value)) && new Date(value).toISOString().slice(0, 10) === value;
|
|
4060
|
+
/** Historical install probes do not authorize using an older compiler. */
|
|
4061
|
+
const parseRustInstallFixtures = (input) => {
|
|
4062
|
+
if (input === void 0) return [];
|
|
4063
|
+
if (!Array.isArray(input)) throw new Error("rustInstallFixtures must be an array");
|
|
4064
|
+
const identities = /* @__PURE__ */ new Set();
|
|
4065
|
+
return input.map((entry) => {
|
|
4066
|
+
if (!record$1(entry) || Object.keys(entry).length !== 5 || Object.keys(entry).some((key) => ![
|
|
4067
|
+
"path",
|
|
4068
|
+
"pin",
|
|
4069
|
+
"reason",
|
|
4070
|
+
"tracking",
|
|
4071
|
+
"expires"
|
|
4072
|
+
].includes(key)) || typeof entry["path"] !== "string" || !/^(?:[A-Za-z0-9_-]+\/)*[A-Za-z0-9_-]+\.sh$/.test(entry["path"]) || typeof entry["pin"] !== "string" || !(/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/.test(entry["pin"]) || entry["pin"].startsWith("nightly-") && date(entry["pin"].slice(8))) || typeof entry["reason"] !== "string" || entry["reason"].trim() === "" || typeof entry["tracking"] !== "string" || !/^https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/(?:issues|pull)\/[1-9]\d*$/.test(entry["tracking"]) || typeof entry["expires"] !== "string" || !date(entry["expires"])) throw new Error("each Rust install fixture requires an exact shell path, stable or dated nightly pin, reason, GitHub tracking URL and valid expiry date");
|
|
4073
|
+
const identity = `${entry["path"]}:${entry["pin"]}`;
|
|
4074
|
+
if (identities.has(identity)) throw new Error("Rust install fixture path/pin pairs must be unique");
|
|
4075
|
+
identities.add(identity);
|
|
4076
|
+
return {
|
|
4077
|
+
path: entry["path"],
|
|
4078
|
+
pin: entry["pin"],
|
|
4079
|
+
reason: entry["reason"],
|
|
4080
|
+
tracking: entry["tracking"],
|
|
4081
|
+
expires: entry["expires"]
|
|
4082
|
+
};
|
|
4083
|
+
});
|
|
4084
|
+
};
|
|
4085
|
+
const checkRustInstallFixtureExpiry = ({ fixtures, scope, now = /* @__PURE__ */ new Date() }) => {
|
|
4086
|
+
const errors = [];
|
|
4087
|
+
const warnings = [];
|
|
4088
|
+
for (const fixture of fixtures) {
|
|
4089
|
+
if (fixture.expires > now.toISOString().slice(0, 10)) continue;
|
|
4090
|
+
const verdict = require_quarantine_policy.overdueVerdict(scope, "stll-toolchain.json", (text) => {
|
|
4091
|
+
const base = JSON.parse(text);
|
|
4092
|
+
const declarations = record$1(base) ? base["rustInstallFixtures"] : void 0;
|
|
4093
|
+
return Array.isArray(declarations) && declarations.some((entry) => record$1(entry) && entry["path"] === fixture.path && entry["pin"] === fixture.pin && entry["expires"] === fixture.expires);
|
|
4094
|
+
});
|
|
4095
|
+
const message = `Rust install fixture ${fixture.path}:${fixture.pin} expired on ${fixture.expires}; remove it or renew the documented fixture boundary`;
|
|
4096
|
+
if (!verdict.fail) warnings.push(message);
|
|
4097
|
+
else errors.push(verdict.unavailable === void 0 ? message : `${message}; cannot determine whether this change adds it: ${verdict.unavailable}`);
|
|
4098
|
+
}
|
|
4099
|
+
return {
|
|
4100
|
+
errors,
|
|
4101
|
+
warnings
|
|
4102
|
+
};
|
|
4103
|
+
};
|
|
4104
|
+
//#endregion
|
|
3968
4105
|
//#region src/toolchain-guard.ts
|
|
3969
4106
|
const toolchainRules = [.../* @__PURE__ */ new Set([
|
|
3970
4107
|
...packageRules,
|
|
@@ -3979,7 +4116,7 @@ const record = (value) => typeof value === "object" && value !== null && !Array.
|
|
|
3979
4116
|
const array = (value) => Array.isArray(value);
|
|
3980
4117
|
/** Repository-owned declarations are explicit; malformed decisions fail closed. */
|
|
3981
4118
|
const parseToolchainConfiguration = (input) => {
|
|
3982
|
-
if (!record(input) || Object.keys(input).some((key) => key !== "optOuts" && key !== "cloud" && key !== "engineFloors" && key !== "dynamicSelectors" && key !== "consumerChecks" && key !== "rustExceptions" && key !== "dependabotIgnores" && key !== "typescriptAllowlist") || input["optOuts"] !== void 0 && !array(input["optOuts"])) throw new Error("stll-toolchain.json accepts only optOuts, cloud, engineFloors, dynamicSelectors, consumerChecks, rustExceptions, dependabotIgnores and typescriptAllowlist declarations");
|
|
4119
|
+
if (!record(input) || Object.keys(input).some((key) => key !== "optOuts" && key !== "cloud" && key !== "engineFloors" && key !== "dynamicSelectors" && key !== "consumerChecks" && key !== "rustExceptions" && key !== "rustInstallFixtures" && key !== "dependabotIgnores" && key !== "typescriptAllowlist") || input["optOuts"] !== void 0 && !array(input["optOuts"])) throw new Error("stll-toolchain.json accepts only optOuts, cloud, engineFloors, dynamicSelectors, consumerChecks, rustExceptions, rustInstallFixtures, dependabotIgnores and typescriptAllowlist declarations");
|
|
3983
4120
|
const disabled = /* @__PURE__ */ new Set();
|
|
3984
4121
|
const optOuts = input["optOuts"] ?? [];
|
|
3985
4122
|
if (!array(optOuts)) throw new Error("optOuts must be an array");
|
|
@@ -3996,6 +4133,7 @@ const parseToolchainConfiguration = (input) => {
|
|
|
3996
4133
|
return {
|
|
3997
4134
|
disabled,
|
|
3998
4135
|
rustExceptions: parseRustExceptions(input["rustExceptions"]),
|
|
4136
|
+
rustInstallFixtures: parseRustInstallFixtures(input["rustInstallFixtures"]),
|
|
3999
4137
|
cloud: require_toolchain_inputs.parseCloudSetup(input["cloud"]),
|
|
4000
4138
|
engineFloors: parseEngineFloors(input["engineFloors"]),
|
|
4001
4139
|
dynamicSelectors: parseDynamicSelectors(input["dynamicSelectors"]),
|
|
@@ -4088,11 +4226,20 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4088
4226
|
if (files["stll-toolchain.json"] !== void 0) try {
|
|
4089
4227
|
configuration = parseToolchainConfiguration(JSON.parse(files["stll-toolchain.json"]));
|
|
4090
4228
|
dynamicSelectors = configuration.dynamicSelectors;
|
|
4091
|
-
const
|
|
4229
|
+
const rustExpiry = checkRustExceptionExpiry({
|
|
4092
4230
|
exceptions: configuration.rustExceptions,
|
|
4093
4231
|
now,
|
|
4094
4232
|
scope: expiryScope
|
|
4095
4233
|
});
|
|
4234
|
+
const fixtureExpiry = checkRustInstallFixtureExpiry({
|
|
4235
|
+
fixtures: configuration.rustInstallFixtures,
|
|
4236
|
+
now,
|
|
4237
|
+
scope: expiryScope
|
|
4238
|
+
});
|
|
4239
|
+
const expiry = {
|
|
4240
|
+
errors: [...rustExpiry.errors, ...fixtureExpiry.errors],
|
|
4241
|
+
warnings: [...rustExpiry.warnings, ...fixtureExpiry.warnings]
|
|
4242
|
+
};
|
|
4096
4243
|
for (const message of expiry.errors) diagnostics.push({
|
|
4097
4244
|
rule: "configuration",
|
|
4098
4245
|
path: "stll-toolchain.json",
|
|
@@ -4114,6 +4261,23 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4114
4261
|
message: error instanceof Error ? error.message : String(error)
|
|
4115
4262
|
});
|
|
4116
4263
|
}
|
|
4264
|
+
for (const entry of configuration.typescriptAllowlist) {
|
|
4265
|
+
if (entry.kind !== "source-api") continue;
|
|
4266
|
+
for (const source of entry.sources) {
|
|
4267
|
+
const content = readSourceApiInput({
|
|
4268
|
+
root,
|
|
4269
|
+
source,
|
|
4270
|
+
tracked: trackedFiles
|
|
4271
|
+
});
|
|
4272
|
+
if (content !== void 0) files[source] = content;
|
|
4273
|
+
else diagnostics.push({
|
|
4274
|
+
rule: "configuration",
|
|
4275
|
+
path: source,
|
|
4276
|
+
line: 1,
|
|
4277
|
+
message: "TypeScript source API evidence must be tracked, readable, regular and free of symlinks"
|
|
4278
|
+
});
|
|
4279
|
+
}
|
|
4280
|
+
}
|
|
4117
4281
|
const cloud = configuration.cloud;
|
|
4118
4282
|
const script = files[require_toolchain_inputs.cloudSetupPath];
|
|
4119
4283
|
if (cloud === void 0 && trackedFiles.has(".agents/cloud-setup.sh")) diagnostics.push({
|
|
@@ -4226,6 +4390,7 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4226
4390
|
trackedFiles
|
|
4227
4391
|
}));
|
|
4228
4392
|
}
|
|
4393
|
+
const usedRustFixtures = /* @__PURE__ */ new Set();
|
|
4229
4394
|
for (const [file, text] of Object.entries(files)) diagnostics.push(...checkRuntimeFile({
|
|
4230
4395
|
file,
|
|
4231
4396
|
text,
|
|
@@ -4238,6 +4403,8 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4238
4403
|
},
|
|
4239
4404
|
trackedFiles,
|
|
4240
4405
|
rustExceptions: configuration.rustExceptions,
|
|
4406
|
+
rustInstallFixtures: configuration.rustInstallFixtures,
|
|
4407
|
+
onRustInstallFixture: (entry) => usedRustFixtures.add(entry),
|
|
4241
4408
|
dynamicSelectors: dynamicSelectors.filter((entry) => entry.path === file),
|
|
4242
4409
|
onDynamicSelector: (entry) => selectorMatches.set(entry, (selectorMatches.get(entry) ?? 0) + 1),
|
|
4243
4410
|
repository,
|
|
@@ -4249,6 +4416,12 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4249
4416
|
},
|
|
4250
4417
|
readFile: (target) => files[target]
|
|
4251
4418
|
}));
|
|
4419
|
+
for (const entry of configuration.rustInstallFixtures) if (!usedRustFixtures.has(entry)) diagnostics.push({
|
|
4420
|
+
rule: "configuration",
|
|
4421
|
+
path: "stll-toolchain.json",
|
|
4422
|
+
line: 1,
|
|
4423
|
+
message: `Rust install fixture ${entry.path}:${entry.pin} must match a tracked rustup toolchain install probe`
|
|
4424
|
+
});
|
|
4252
4425
|
for (const entry of engineFloors) if (!exercisedFloors.has(entry) || mismatchedFloors.has(entry)) diagnostics.push({
|
|
4253
4426
|
rule: "configuration",
|
|
4254
4427
|
path: "stll-toolchain.json",
|