@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.mjs
CHANGED
|
@@ -13,6 +13,8 @@ import { execFileSync, spawnSync } from "node:child_process";
|
|
|
13
13
|
import { gte, minVersion, satisfies, valid, validRange } from "semver";
|
|
14
14
|
import { LineCounter, isAlias, isMap, isNode, isScalar, isSeq, parseAllDocuments, parseDocument } from "yaml";
|
|
15
15
|
import { pathToFileURL } from "node:url";
|
|
16
|
+
import { parse as parse$3 } from "acorn";
|
|
17
|
+
import { transformSync } from "esbuild";
|
|
16
18
|
//#region src/toolchain-bun.ts
|
|
17
19
|
const STABLE_RELEASE = /^\d+\.\d+\.\d+$/u;
|
|
18
20
|
/**
|
|
@@ -31,9 +33,9 @@ const repositoryBun = (floor, manifest) => {
|
|
|
31
33
|
};
|
|
32
34
|
//#endregion
|
|
33
35
|
//#region src/toolchain-consumer-checks.ts
|
|
34
|
-
const consumerRunnerVersion = "3.
|
|
36
|
+
const consumerRunnerVersion = "3.2.0";
|
|
35
37
|
const consumerCheckWorkflow = "stella/.github/.github/workflows/package-consumer-compat.yml";
|
|
36
|
-
const record$
|
|
38
|
+
const record$11 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37
39
|
const repositoryDirectory = (value) => typeof value === "string" && value !== "" && !value.includes("\\") && !value.includes("${{") && !value.split("").some((character) => {
|
|
38
40
|
const code = character.charCodeAt(0);
|
|
39
41
|
return code < 32 || code === 127;
|
|
@@ -119,12 +121,12 @@ const validConsumerCron = (text) => {
|
|
|
119
121
|
});
|
|
120
122
|
};
|
|
121
123
|
const nightlySchedule = (value) => {
|
|
122
|
-
if (!record$
|
|
124
|
+
if (!record$11(value)) return false;
|
|
123
125
|
const schedule = value["schedule"];
|
|
124
126
|
if (!Array.isArray(schedule) || schedule.length === 0) return false;
|
|
125
127
|
const coveredDays = /* @__PURE__ */ new Set();
|
|
126
128
|
for (const entry of schedule) {
|
|
127
|
-
if (!record$
|
|
129
|
+
if (!record$11(entry) || typeof entry["cron"] !== "string" || !validConsumerCron(entry["cron"])) return false;
|
|
128
130
|
const fields = entry["cron"].trim().split(/\s+/);
|
|
129
131
|
const selections = cronFields.map((field, index) => {
|
|
130
132
|
const text = fields.at(index);
|
|
@@ -140,7 +142,7 @@ const parseConsumerChecks = (input) => {
|
|
|
140
142
|
if (!Array.isArray(input)) throw new Error("consumerChecks must be an array");
|
|
141
143
|
const targets = /* @__PURE__ */ new Set();
|
|
142
144
|
return input.map((entry) => {
|
|
143
|
-
if (!record$
|
|
145
|
+
if (!record$11(entry) || Object.keys(entry).some((key) => ![
|
|
144
146
|
"workflow",
|
|
145
147
|
"job",
|
|
146
148
|
"packages",
|
|
@@ -178,9 +180,9 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
|
|
|
178
180
|
});
|
|
179
181
|
if (document.errors.length > 0) throw new Error("invalid workflow YAML");
|
|
180
182
|
const source = document.toJS({ maxAliasCount: 100 });
|
|
181
|
-
if (!record$
|
|
183
|
+
if (!record$11(source) || !record$11(source["jobs"])) continue;
|
|
182
184
|
for (const [jobName, job] of Object.entries(source["jobs"])) {
|
|
183
|
-
if (!record$
|
|
185
|
+
if (!record$11(job) || typeof job["uses"] !== "string") continue;
|
|
184
186
|
const [workflowPath, ref] = job["uses"].split("@");
|
|
185
187
|
if (workflowPath?.toLowerCase() !== "stella/.github/.github/workflows/package-consumer-compat.yml") continue;
|
|
186
188
|
const declaration = declarations.find((entry) => entry.workflow === workflow && entry.job === jobName);
|
|
@@ -194,7 +196,7 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
|
|
|
194
196
|
const approved = Object.entries(policy.actions).find(([name]) => name.toLowerCase() === consumerCheckWorkflow)?.[1];
|
|
195
197
|
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`);
|
|
196
198
|
const inputs = job["with"];
|
|
197
|
-
if (!record$
|
|
199
|
+
if (!record$11(inputs)) throw new Error(`${workflow}:${jobName} requires consumer inputs`);
|
|
198
200
|
const names = /* @__PURE__ */ new Set();
|
|
199
201
|
for (const key of Object.keys(inputs)) {
|
|
200
202
|
const normalized = key.toLowerCase();
|
|
@@ -264,7 +266,7 @@ const locatorValidators = {
|
|
|
264
266
|
"bun-source": /^(?:jobs\.[\w-]+|runs)\.steps\.[\w-]+$/
|
|
265
267
|
};
|
|
266
268
|
const knownKind = (value) => typeof value === "string" && Object.hasOwn(locatorValidators, value);
|
|
267
|
-
const record$
|
|
269
|
+
const record$10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
268
270
|
/** One reviewed declaration identifies one unresolved selector, never a rule. */
|
|
269
271
|
const parseDynamicSelectors = (input) => {
|
|
270
272
|
if (input === void 0) return [];
|
|
@@ -272,7 +274,7 @@ const parseDynamicSelectors = (input) => {
|
|
|
272
274
|
const result = [];
|
|
273
275
|
const keys = /* @__PURE__ */ new Set();
|
|
274
276
|
for (const entry of input) {
|
|
275
|
-
if (!record$
|
|
277
|
+
if (!record$10(entry) || Object.keys(entry).some((key) => ![
|
|
276
278
|
"path",
|
|
277
279
|
"kind",
|
|
278
280
|
"reason",
|
|
@@ -308,7 +310,7 @@ const parseDynamicSelectors = (input) => {
|
|
|
308
310
|
};
|
|
309
311
|
//#endregion
|
|
310
312
|
//#region src/toolchain-engine-floors.ts
|
|
311
|
-
const record$
|
|
313
|
+
const record$9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
312
314
|
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 !== "..");
|
|
313
315
|
const parseEngineFloors = (input) => {
|
|
314
316
|
if (input === void 0) return [];
|
|
@@ -316,7 +318,7 @@ const parseEngineFloors = (input) => {
|
|
|
316
318
|
const result = [];
|
|
317
319
|
const jobs = /* @__PURE__ */ new Set();
|
|
318
320
|
for (const entry of input) {
|
|
319
|
-
if (!record$
|
|
321
|
+
if (!record$9(entry) || Object.keys(entry).some((key) => ![
|
|
320
322
|
"package",
|
|
321
323
|
"workflow",
|
|
322
324
|
"job"
|
|
@@ -337,16 +339,16 @@ const resolveEngineFloor = (entry, files) => {
|
|
|
337
339
|
const workflowText = files[entry.workflow];
|
|
338
340
|
if (manifestText === void 0 || workflowText === void 0) throw new Error("engine floor package and workflow must be tracked");
|
|
339
341
|
const manifest = JSON.parse(manifestText);
|
|
340
|
-
if (!record$
|
|
342
|
+
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");
|
|
341
343
|
const range = manifest["engines"]["node"];
|
|
342
344
|
const minimum = validRange(range) === null ? null : minVersion(range);
|
|
343
345
|
if (minimum === null || minimum.prerelease.length !== 0) throw new Error("engine floor engines.node must declare a stable minimum");
|
|
344
346
|
const document = parseDocument(workflowText, { merge: true });
|
|
345
347
|
if (document.errors.length !== 0) throw new Error("engine floor workflow must be valid YAML");
|
|
346
348
|
const workflow = document.toJS({ maxAliasCount: 100 });
|
|
347
|
-
if (!record$
|
|
349
|
+
if (!record$9(workflow) || !record$9(workflow["jobs"])) throw new Error("engine floor job must exist in its tracked workflow");
|
|
348
350
|
const job = workflow["jobs"][entry.job];
|
|
349
|
-
if (!record$
|
|
351
|
+
if (!record$9(job)) throw new Error("engine floor job must exist in its tracked workflow");
|
|
350
352
|
if (!Array.isArray(job["steps"]) || Object.hasOwn(job, "uses")) throw new Error("engine floor job must declare steps and cannot reuse a workflow");
|
|
351
353
|
return {
|
|
352
354
|
...entry,
|
|
@@ -359,6 +361,38 @@ const engineFloorSelectorMatches = (value, floor) => {
|
|
|
359
361
|
return Number(value.split(".").at(0)) === floor.major && satisfies(value, floor.range);
|
|
360
362
|
};
|
|
361
363
|
//#endregion
|
|
364
|
+
//#region src/toolchain-typescript-source-api.ts
|
|
365
|
+
const readSourceApiInput = ({ root, source, tracked }) => {
|
|
366
|
+
if (!tracked.has(source)) return void 0;
|
|
367
|
+
const resolvedRoot = realpathSync(root);
|
|
368
|
+
const input = path.resolve(resolvedRoot, source);
|
|
369
|
+
try {
|
|
370
|
+
if (realpathSync(input) !== input || !lstatSync(input).isFile()) return void 0;
|
|
371
|
+
return readFileSync(input, "utf8");
|
|
372
|
+
} catch {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
const sourceApiImportProblem = ({ source, content, name }) => {
|
|
377
|
+
if (content === void 0) return "must be a tracked, readable regular source file without symlinks";
|
|
378
|
+
try {
|
|
379
|
+
const { code } = transformSync(content, {
|
|
380
|
+
sourcefile: source,
|
|
381
|
+
loader: source.endsWith("x") ? "tsx" : "ts",
|
|
382
|
+
format: "esm",
|
|
383
|
+
tsconfigRaw: { compilerOptions: { verbatimModuleSyntax: true } },
|
|
384
|
+
logLevel: "silent"
|
|
385
|
+
});
|
|
386
|
+
if (parse$3(code, {
|
|
387
|
+
ecmaVersion: "latest",
|
|
388
|
+
sourceType: "module"
|
|
389
|
+
}).body.some((node) => node.type === "ImportDeclaration" && node.source.value === name && node.specifiers.length > 0)) return void 0;
|
|
390
|
+
return `no longer has a static value import from ${name}; remove the declaration or name its current importer`;
|
|
391
|
+
} catch {
|
|
392
|
+
return "cannot parse the declared compiler API importer";
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
//#endregion
|
|
362
396
|
//#region src/toolchain-packages.ts
|
|
363
397
|
const packageRules = [
|
|
364
398
|
"bun-pins",
|
|
@@ -376,7 +410,7 @@ const installSections = [
|
|
|
376
410
|
"optionalDependencies"
|
|
377
411
|
];
|
|
378
412
|
const allowlistFile = "stll-toolchain.json";
|
|
379
|
-
const record$
|
|
413
|
+
const record$8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
380
414
|
/** `npm:typescript@7.0.2` installs a compiler under another name; returns its version. */
|
|
381
415
|
const typescriptAliasVersion = (spec) => {
|
|
382
416
|
if (typeof spec !== "string") return void 0;
|
|
@@ -406,22 +440,44 @@ const parseTypescriptAllowlist = (input) => {
|
|
|
406
440
|
return input.map((entry) => {
|
|
407
441
|
const shown = JSON.stringify(entry);
|
|
408
442
|
const text = (key) => {
|
|
409
|
-
const value = record$
|
|
443
|
+
const value = record$8(entry) ? entry[key] : void 0;
|
|
410
444
|
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
411
445
|
};
|
|
412
446
|
const workspace = text("workspace");
|
|
413
447
|
const name = text("name");
|
|
414
448
|
const spec = text("spec");
|
|
415
449
|
const dependency = text("dependency");
|
|
450
|
+
const sourceApi = record$8(entry) && entry["kind"] === "source-api";
|
|
451
|
+
const keys = sourceApi ? [
|
|
452
|
+
"workspace",
|
|
453
|
+
"name",
|
|
454
|
+
"spec",
|
|
455
|
+
"reason",
|
|
456
|
+
"kind",
|
|
457
|
+
"sources"
|
|
458
|
+
] : [...allowlistKeys, ...record$8(entry) && entry["kind"] === "dependency" ? ["kind"] : []];
|
|
416
459
|
const reason = text("reason");
|
|
417
|
-
if (!record$
|
|
460
|
+
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}`);
|
|
418
461
|
if (workspace !== "." && (path.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"`);
|
|
419
462
|
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`);
|
|
420
463
|
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>`);
|
|
421
|
-
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`);
|
|
464
|
+
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`);
|
|
422
465
|
const key = `${workspace}\0${name}`;
|
|
423
466
|
if (seen.has(key)) throw new Error(`typescriptAllowlist declares ${name} for ${workspace} more than once; keep one entry`);
|
|
424
467
|
seen.add(key);
|
|
468
|
+
if (sourceApi) {
|
|
469
|
+
const sources = entry["sources"];
|
|
470
|
+
if (!Array.isArray(sources) || sources.length === 0 || !sources.every((source) => typeof source === "string" && path.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`);
|
|
471
|
+
return {
|
|
472
|
+
kind: "source-api",
|
|
473
|
+
workspace,
|
|
474
|
+
name,
|
|
475
|
+
spec,
|
|
476
|
+
reason,
|
|
477
|
+
sources
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
if (dependency === void 0) throw new Error("Missing TypeScript dependency owner");
|
|
425
481
|
return {
|
|
426
482
|
workspace,
|
|
427
483
|
name,
|
|
@@ -990,10 +1046,10 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
990
1046
|
const document = pnpm ? parseDocument(text, { lineCounter }) : void 0;
|
|
991
1047
|
if (document !== void 0 && document.errors.length > 0) throw new Error("invalid YAML");
|
|
992
1048
|
const json = document === void 0 ? JSON.parse(text) : document.toJS();
|
|
993
|
-
if (!record$
|
|
1049
|
+
if (!record$8(json)) throw new Error("manifest must be an object");
|
|
994
1050
|
if (pnpm) {
|
|
995
|
-
if (json["catalog"] !== void 0 && !record$
|
|
996
|
-
if (json["catalogs"] !== void 0 && (!record$
|
|
1051
|
+
if (json["catalog"] !== void 0 && !record$8(json["catalog"])) throw new Error("catalog must be a mapping");
|
|
1052
|
+
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");
|
|
997
1053
|
if (json["packages"] !== void 0 && (!Array.isArray(json["packages"]) || !json["packages"].every((pattern) => typeof pattern === "string"))) throw new Error("workspace packages must be patterns");
|
|
998
1054
|
}
|
|
999
1055
|
manifests.set(file, json);
|
|
@@ -1007,13 +1063,13 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1007
1063
|
catalog: json["overrides"],
|
|
1008
1064
|
prefix: ["overrides"]
|
|
1009
1065
|
},
|
|
1010
|
-
...record$
|
|
1066
|
+
...record$8(json["catalogs"]) ? Object.entries(json["catalogs"]).map(([name, catalog]) => ({
|
|
1011
1067
|
catalog,
|
|
1012
1068
|
prefix: ["catalogs", name]
|
|
1013
1069
|
})) : []
|
|
1014
1070
|
];
|
|
1015
1071
|
for (const { catalog, prefix } of catalogEntries) {
|
|
1016
|
-
if (!record$
|
|
1072
|
+
if (!record$8(catalog)) continue;
|
|
1017
1073
|
for (const [name, value] of Object.entries(catalog)) {
|
|
1018
1074
|
const node = document.getIn([...prefix, name], true);
|
|
1019
1075
|
if (!isNode(node) || node.range === void 0 || node.range === null) continue;
|
|
@@ -1032,7 +1088,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1032
1088
|
});
|
|
1033
1089
|
}
|
|
1034
1090
|
}
|
|
1035
|
-
const catalogSources = (json) => [json, ...record$
|
|
1091
|
+
const catalogSources = (json) => [json, ...record$8(json["workspaces"]) ? [json["workspaces"]] : []];
|
|
1036
1092
|
const pnpmWorkspaceFor = (file) => {
|
|
1037
1093
|
let directory = path.posix.dirname(file);
|
|
1038
1094
|
while (true) {
|
|
@@ -1055,8 +1111,8 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1055
1111
|
patterns: workspace["packages"],
|
|
1056
1112
|
rootMembership: "implicit"
|
|
1057
1113
|
})) return void 0;
|
|
1058
|
-
if (name === "" || name === "default") return workspace["catalog"] ?? (record$
|
|
1059
|
-
return record$
|
|
1114
|
+
if (name === "" || name === "default") return workspace["catalog"] ?? (record$8(workspace["catalogs"]) ? workspace["catalogs"]["default"] : void 0);
|
|
1115
|
+
return record$8(workspace["catalogs"]) ? workspace["catalogs"][name] : void 0;
|
|
1060
1116
|
}
|
|
1061
1117
|
let directory = path.posix.dirname(file);
|
|
1062
1118
|
while (true) {
|
|
@@ -1064,7 +1120,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1064
1120
|
if (manifest !== void 0) {
|
|
1065
1121
|
for (const source of catalogSources(manifest)) {
|
|
1066
1122
|
if (name === "" && source["catalog"] !== void 0) return source["catalog"];
|
|
1067
|
-
if (record$
|
|
1123
|
+
if (record$8(source["catalogs"]) && source["catalogs"][name] !== void 0) return source["catalogs"][name];
|
|
1068
1124
|
}
|
|
1069
1125
|
if (manifest["workspaces"] !== void 0) return void 0;
|
|
1070
1126
|
}
|
|
@@ -1082,7 +1138,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1082
1138
|
};
|
|
1083
1139
|
visited.add(value);
|
|
1084
1140
|
const catalog = catalogFor(file, value.slice(8));
|
|
1085
|
-
if (!record$
|
|
1141
|
+
if (!record$8(catalog)) return {
|
|
1086
1142
|
value: void 0,
|
|
1087
1143
|
catalogs
|
|
1088
1144
|
};
|
|
@@ -1107,7 +1163,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1107
1163
|
const workspaces = manifests.get(path.posix.join(directory, "package.json"))?.["workspaces"];
|
|
1108
1164
|
if (workspaces !== void 0) return {
|
|
1109
1165
|
directory,
|
|
1110
|
-
patterns: record$
|
|
1166
|
+
patterns: record$8(workspaces) ? workspaces["packages"] : workspaces,
|
|
1111
1167
|
source: "json"
|
|
1112
1168
|
};
|
|
1113
1169
|
if (directory === ".") return void 0;
|
|
@@ -1141,7 +1197,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1141
1197
|
if (json === void 0) continue;
|
|
1142
1198
|
for (const section of installSections) {
|
|
1143
1199
|
const entries = json[section];
|
|
1144
|
-
if (!record$
|
|
1200
|
+
if (!record$8(entries) || !Object.hasOwn(entries, entry.name)) continue;
|
|
1145
1201
|
const { value, catalogs } = resolveThrough(file, entry.name, entries[entry.name]);
|
|
1146
1202
|
if (value !== entry.spec) continue;
|
|
1147
1203
|
for (const catalog of catalogs) {
|
|
@@ -1173,7 +1229,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1173
1229
|
});
|
|
1174
1230
|
return `${name} ${shown} in ${file} is not allowed: ${typescriptGuidance}; if a dependency of this workspace needs it, add ${suggestion} to typescriptAllowlist in ${allowlistFile}`;
|
|
1175
1231
|
}
|
|
1176
|
-
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`;
|
|
1232
|
+
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`;
|
|
1177
1233
|
};
|
|
1178
1234
|
const peerRange = (value) => typeof value === "string" && validRange(value) !== null;
|
|
1179
1235
|
for (const [file, json] of manifests) {
|
|
@@ -1186,7 +1242,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1186
1242
|
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)}`
|
|
1187
1243
|
});
|
|
1188
1244
|
const checkEntries = (entries, scope, location) => {
|
|
1189
|
-
if (!record$
|
|
1245
|
+
if (!record$8(entries)) return;
|
|
1190
1246
|
for (const [name, raw] of Object.entries(entries)) {
|
|
1191
1247
|
const value = resolve(file, name, raw);
|
|
1192
1248
|
if (isTypescriptInstall(name, value)) {
|
|
@@ -1226,13 +1282,13 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1226
1282
|
}
|
|
1227
1283
|
};
|
|
1228
1284
|
for (const key of installSections) checkEntries(json[key], "install");
|
|
1229
|
-
if (record$
|
|
1285
|
+
if (record$8(json["peerDependencies"])) for (const [name, value] of Object.entries(json["peerDependencies"])) {
|
|
1230
1286
|
const resolved = resolve(file, name, value);
|
|
1231
1287
|
if (isTypescriptInstall(name, resolved) && !peerRange(resolved)) checkEntries({ [name]: value }, "peer");
|
|
1232
1288
|
}
|
|
1233
1289
|
for (const source of catalogSources(json)) {
|
|
1234
1290
|
checkEntries(source["catalog"], "catalog");
|
|
1235
|
-
if (!record$
|
|
1291
|
+
if (!record$8(source["catalogs"])) continue;
|
|
1236
1292
|
for (const catalog of Object.values(source["catalogs"])) checkEntries(catalog, "catalog");
|
|
1237
1293
|
}
|
|
1238
1294
|
const ownedNames = /* @__PURE__ */ new Set([...pins.keys(), ...typescriptPackages]);
|
|
@@ -1244,11 +1300,11 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1244
1300
|
"optionalDependencies"
|
|
1245
1301
|
]) {
|
|
1246
1302
|
const entries = resolutionRoot[section];
|
|
1247
|
-
if (!record$
|
|
1303
|
+
if (!record$8(entries)) continue;
|
|
1248
1304
|
for (const [name, value] of Object.entries(entries)) resolutionDependencies[name] = resolve(file, name, value);
|
|
1249
1305
|
}
|
|
1250
1306
|
const checkResolutionMap = (entries, parent) => {
|
|
1251
|
-
if (!record$
|
|
1307
|
+
if (!record$8(entries)) return;
|
|
1252
1308
|
for (const [selector, replacement] of Object.entries(entries)) {
|
|
1253
1309
|
const target = selector === "." ? parent : selector;
|
|
1254
1310
|
if (target === void 0) continue;
|
|
@@ -1267,7 +1323,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1267
1323
|
});
|
|
1268
1324
|
continue;
|
|
1269
1325
|
}
|
|
1270
|
-
if (record$
|
|
1326
|
+
if (record$8(replacement)) {
|
|
1271
1327
|
checkResolutionMap(replacement, target);
|
|
1272
1328
|
continue;
|
|
1273
1329
|
}
|
|
@@ -1287,8 +1343,8 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1287
1343
|
};
|
|
1288
1344
|
checkResolutionMap(json["overrides"]);
|
|
1289
1345
|
checkResolutionMap(json["resolutions"]);
|
|
1290
|
-
if (record$
|
|
1291
|
-
const command = record$
|
|
1346
|
+
if (record$8(json["pnpm"])) checkResolutionMap(json["pnpm"]["overrides"]);
|
|
1347
|
+
const command = record$8(json["scripts"]) ? json["scripts"]["typecheck"] : void 0;
|
|
1292
1348
|
if (typeof command === "string") for (const words of compilerCommandSegments(command)) {
|
|
1293
1349
|
if (!invokesCompiler(words)) continue;
|
|
1294
1350
|
const normalized = words.join(" ");
|
|
@@ -1302,7 +1358,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1302
1358
|
});
|
|
1303
1359
|
break;
|
|
1304
1360
|
}
|
|
1305
|
-
if (record$
|
|
1361
|
+
if (record$8(json["peerDependencies"])) for (const [name, raw] of Object.entries(json["peerDependencies"])) {
|
|
1306
1362
|
if (!typescriptPackages.has(name)) continue;
|
|
1307
1363
|
const range = resolve(file, name, raw);
|
|
1308
1364
|
if (!peerRange(range)) continue;
|
|
@@ -1326,7 +1382,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1326
1382
|
message: `${name} peer support range must include ${release}, found ${range}`
|
|
1327
1383
|
});
|
|
1328
1384
|
}
|
|
1329
|
-
if (record$
|
|
1385
|
+
if (record$8(json["engines"]) && json["engines"]["node"] !== void 0 && (typeof json["engines"]["node"] !== "string" || !nodeSupportRangeMatches(policy.node, json["engines"]["node"]))) add({
|
|
1330
1386
|
file,
|
|
1331
1387
|
rule: "node-engine",
|
|
1332
1388
|
key: "node",
|
|
@@ -1340,9 +1396,23 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1340
1396
|
if (json === void 0) return `${file} is missing or invalid; remove the entry`;
|
|
1341
1397
|
const declares = (name) => installSections.some((section) => {
|
|
1342
1398
|
const entries = json[section];
|
|
1343
|
-
return record$
|
|
1399
|
+
return record$8(entries) && Object.hasOwn(entries, name);
|
|
1344
1400
|
});
|
|
1345
1401
|
if (!declares(entry.name)) return `${file} no longer declares ${entry.name} in dependencies, devDependencies or optionalDependencies; remove the entry`;
|
|
1402
|
+
if (entry.kind === "source-api") {
|
|
1403
|
+
for (const source of entry.sources) {
|
|
1404
|
+
let owner = path.posix.dirname(source);
|
|
1405
|
+
while (owner !== "." && !manifests.has(`${owner}/package.json`)) owner = path.posix.dirname(owner);
|
|
1406
|
+
if (owner !== entry.workspace) return `${source} belongs to workspace ${owner}, not ${entry.workspace}`;
|
|
1407
|
+
const problem = sourceApiImportProblem({
|
|
1408
|
+
source,
|
|
1409
|
+
content: files[source],
|
|
1410
|
+
name: entry.name
|
|
1411
|
+
});
|
|
1412
|
+
if (problem !== void 0) return `${source}: ${problem}`;
|
|
1413
|
+
}
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1346
1416
|
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`;
|
|
1347
1417
|
};
|
|
1348
1418
|
for (const entry of typescriptAllowlist) {
|
|
@@ -1379,9 +1449,9 @@ const extensionKinds = new Map(Object.entries({
|
|
|
1379
1449
|
const scriptSourceKind = (file) => extensionKinds.get(extname(file));
|
|
1380
1450
|
//#endregion
|
|
1381
1451
|
//#region src/toolchain-script-lifecycle.ts
|
|
1382
|
-
const record$
|
|
1452
|
+
const record$7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1383
1453
|
const declaredManager = (value) => typeof value === "string" ? /^(bun|npm|pnpm|yarn)@(\d+\.\d+\.\d+)(?:\+\S+)?$/u.exec(value) : null;
|
|
1384
|
-
const rootScriptManager = (manifest, context) => declaredManager(context.packageManager ?? (record$
|
|
1454
|
+
const rootScriptManager = (manifest, context) => declaredManager(context.packageManager ?? (record$7(manifest) ? manifest["packageManager"] : void 0))?.at(1);
|
|
1385
1455
|
const npmHooksEnabled = (file) => {
|
|
1386
1456
|
if (file?.type === "unknown") throw new Error("npm lifecycle configuration must be a tracked regular file");
|
|
1387
1457
|
if (file?.type !== "tracked") return true;
|
|
@@ -1412,14 +1482,18 @@ const yamlSettings = (file, manager) => {
|
|
|
1412
1482
|
} catch {
|
|
1413
1483
|
throw new Error(`Cannot parse ${manager} lifecycle configuration`);
|
|
1414
1484
|
}
|
|
1415
|
-
if (!record$
|
|
1485
|
+
if (!record$7(settings)) throw new Error(`${manager} lifecycle configuration must be a mapping`);
|
|
1416
1486
|
return settings;
|
|
1417
1487
|
};
|
|
1488
|
+
const pnpmLifecycleVersions = ["12.9.1", "12.15.0"];
|
|
1418
1489
|
const pnpmHooksEnabled = (manifest, context) => {
|
|
1419
1490
|
const declared = declaredManager(context.packageManager ?? manifest["packageManager"]);
|
|
1420
|
-
const dependency = [manifest["dependencies"], manifest["devDependencies"]].flatMap((field) => record$
|
|
1421
|
-
|
|
1422
|
-
|
|
1491
|
+
const dependency = [manifest["dependencies"], manifest["devDependencies"]].flatMap((field) => record$7(field) ? [field["pnpm"]] : []).find((value) => typeof value === "string");
|
|
1492
|
+
const version = declared?.at(1) === "pnpm" ? declared.at(2) : dependency;
|
|
1493
|
+
if (!pnpmLifecycleVersions.some((supported) => version === supported)) throw new Error(`pnpm lifecycle discovery requires declared pnpm@${pnpmLifecycleVersions.join(" or pnpm@")}`);
|
|
1494
|
+
const settings = yamlSettings(context.pnpmWorkspace, "pnpm");
|
|
1495
|
+
if ("include" in settings) throw new Error("Delegated pnpm settings require explicit lifecycle discovery support");
|
|
1496
|
+
const enabled = settings["enablePrePostScripts"];
|
|
1423
1497
|
if (enabled === void 0) return true;
|
|
1424
1498
|
if (typeof enabled !== "boolean") throw new Error("pnpm enablePrePostScripts must be a literal boolean");
|
|
1425
1499
|
return enabled;
|
|
@@ -1461,7 +1535,7 @@ const scriptLifecycleStages = ({ name, manager, manifest, scripts, context }) =>
|
|
|
1461
1535
|
};
|
|
1462
1536
|
//#endregion
|
|
1463
1537
|
//#region src/toolchain-lint-command.ts
|
|
1464
|
-
const record$
|
|
1538
|
+
const record$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1465
1539
|
const scriptManagers = /* @__PURE__ */ new Set([
|
|
1466
1540
|
"bun",
|
|
1467
1541
|
"npm",
|
|
@@ -1578,7 +1652,7 @@ const lintInventoryCommands = (manifest, forceEnabled = false, context = {}) =>
|
|
|
1578
1652
|
const errors = [];
|
|
1579
1653
|
const workspaceTasks = /* @__PURE__ */ new Set();
|
|
1580
1654
|
const filteredBuilds = /* @__PURE__ */ new Set();
|
|
1581
|
-
const declaresEngine = record$
|
|
1655
|
+
const declaresEngine = record$6(manifest) && [manifest["dependencies"], manifest["devDependencies"]].some((dependencies) => record$6(dependencies) && "oxlint-tsgolint" in dependencies);
|
|
1582
1656
|
const enabled = forceEnabled || declaresEngine;
|
|
1583
1657
|
if (!enabled) return {
|
|
1584
1658
|
commands,
|
|
@@ -1586,14 +1660,14 @@ const lintInventoryCommands = (manifest, forceEnabled = false, context = {}) =>
|
|
|
1586
1660
|
enabled,
|
|
1587
1661
|
workspaceTask: false
|
|
1588
1662
|
};
|
|
1589
|
-
if (!record$
|
|
1663
|
+
if (!record$6(manifest)) return {
|
|
1590
1664
|
commands,
|
|
1591
1665
|
errors: ["lint inventory requires an object package manifest"],
|
|
1592
1666
|
enabled,
|
|
1593
1667
|
workspaceTask: false
|
|
1594
1668
|
};
|
|
1595
1669
|
const scripts = manifest["scripts"];
|
|
1596
|
-
if (!record$
|
|
1670
|
+
if (!record$6(scripts)) return {
|
|
1597
1671
|
commands,
|
|
1598
1672
|
errors: ["type-aware linting requires a root lint script"],
|
|
1599
1673
|
enabled,
|
|
@@ -1735,7 +1809,7 @@ const checkProjectOwnership = ({ trackedFiles, lintedFiles, projectFiles }) => [
|
|
|
1735
1809
|
line: 1,
|
|
1736
1810
|
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)"
|
|
1737
1811
|
}));
|
|
1738
|
-
const record$
|
|
1812
|
+
const record$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1739
1813
|
const stringList = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
1740
1814
|
const readCommand = ({ root, executable, args, runtime }) => {
|
|
1741
1815
|
const result = spawnSync(runtime, [
|
|
@@ -1779,7 +1853,7 @@ const resolveProjectApi = (root) => {
|
|
|
1779
1853
|
for (const base of [import.meta.url, path.join(root, "package.json")]) try {
|
|
1780
1854
|
return pathToFileURL(createRequire(base).resolve("tsrs/unstable/async")).href;
|
|
1781
1855
|
} catch (error) {
|
|
1782
|
-
const code = record$
|
|
1856
|
+
const code = record$5(error) ? error["code"] : void 0;
|
|
1783
1857
|
if (code !== "MODULE_NOT_FOUND" && code !== "ERR_MODULE_NOT_FOUND") throw error;
|
|
1784
1858
|
}
|
|
1785
1859
|
throw new Error("configuration discovery needs tsrs: reinstall @stll/oxlint-config, which depends on it");
|
|
@@ -1802,7 +1876,7 @@ const readProjectConfig = (root, config) => {
|
|
|
1802
1876
|
if (result.error !== void 0) throw result.error;
|
|
1803
1877
|
if (result.status !== 0) throw new Error(`configuration discovery failed: ${(result.stderr + result.stdout).trim()}`);
|
|
1804
1878
|
const parsed = JSON.parse(result.stdout);
|
|
1805
|
-
if (!record$
|
|
1879
|
+
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");
|
|
1806
1880
|
return {
|
|
1807
1881
|
files: parsed["files"],
|
|
1808
1882
|
references: parsed["references"],
|
|
@@ -1827,7 +1901,7 @@ const lifecycleFile = (root, file, tracked) => {
|
|
|
1827
1901
|
};
|
|
1828
1902
|
/** Inventory and config expansion only: neither subprocess lints or typechecks. */
|
|
1829
1903
|
const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
1830
|
-
const packageManager = record$
|
|
1904
|
+
const packageManager = record$5(manifest) ? manifest["packageManager"] : void 0;
|
|
1831
1905
|
const managerUsesPnpmWorkspace = packageManager === void 0 || typeof packageManager === "string" && packageManager.startsWith("pnpm@");
|
|
1832
1906
|
const discovered = /* @__PURE__ */ new Map();
|
|
1833
1907
|
const workspaceMembers = (pnpmMembership) => {
|
|
@@ -1863,7 +1937,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
1863
1937
|
return nestedLifecycle;
|
|
1864
1938
|
};
|
|
1865
1939
|
const lifecycle = {
|
|
1866
|
-
packageManager: record$
|
|
1940
|
+
packageManager: record$5(manifest) ? manifest["packageManager"] : void 0,
|
|
1867
1941
|
npmrc: lifecycleFile(root, ".npmrc", trackedFiles),
|
|
1868
1942
|
pnpmWorkspace: lifecycleFile(root, "pnpm-workspace.yaml", trackedFiles),
|
|
1869
1943
|
yarnrc: lifecycleFile(root, ".yarnrc.yml", trackedFiles),
|
|
@@ -1874,7 +1948,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
1874
1948
|
if (member === void 0) throw new Error("Missing selected workspace");
|
|
1875
1949
|
const [directory, pkg] = member;
|
|
1876
1950
|
const scripts = pkg["scripts"];
|
|
1877
|
-
if (!record$
|
|
1951
|
+
if (!record$5(scripts) || typeof scripts["build"] !== "string") throw new Error("Filtered Bun build requires a literal build script");
|
|
1878
1952
|
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");
|
|
1879
1953
|
if (activeBuilds.has(directory)) throw new Error("Cyclic filtered Bun build delegation");
|
|
1880
1954
|
activeBuilds.add(directory);
|
|
@@ -1916,7 +1990,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
1916
1990
|
for (const [directory, workspace] of workspaceMembers(managerUsesPnpmWorkspace)) {
|
|
1917
1991
|
if (directory === ".") continue;
|
|
1918
1992
|
const file = `${directory}/package.json`;
|
|
1919
|
-
if (!record$
|
|
1993
|
+
if (!record$5(workspace["scripts"]) || workspace["scripts"]["lint"] === void 0) continue;
|
|
1920
1994
|
const nestedLifecycle = workspaceLifecycle(directory);
|
|
1921
1995
|
const nested = lintInventoryCommands(workspace, true, nestedLifecycle);
|
|
1922
1996
|
if (nested.workspaceTask) throw new Error(`nested workspace lint delegation is unsupported: ${file}`);
|
|
@@ -2025,7 +2099,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
2025
2099
|
};
|
|
2026
2100
|
//#endregion
|
|
2027
2101
|
//#region src/toolchain-rust.ts
|
|
2028
|
-
const record$
|
|
2102
|
+
const record$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2029
2103
|
/**
|
|
2030
2104
|
* Exceptions authorize one immutable release, never a channel or an entire
|
|
2031
2105
|
* rule. Expiry is scoped separately by checkRustExceptionExpiry.
|
|
@@ -2035,7 +2109,7 @@ const parseRustExceptions = (input) => {
|
|
|
2035
2109
|
if (!Array.isArray(input)) throw new Error("rustExceptions must be an array");
|
|
2036
2110
|
const pins = /* @__PURE__ */ new Set();
|
|
2037
2111
|
return input.map((entry) => {
|
|
2038
|
-
if (!record$
|
|
2112
|
+
if (!record$4(entry) || Object.keys(entry).length !== 4 || Object.keys(entry).some((key) => ![
|
|
2039
2113
|
"pin",
|
|
2040
2114
|
"reason",
|
|
2041
2115
|
"tracking",
|
|
@@ -2053,8 +2127,8 @@ const parseRustExceptions = (input) => {
|
|
|
2053
2127
|
/** Whether the base stll-toolchain.json declares this exception with the same expiry. */
|
|
2054
2128
|
const declaresException = (baseText, { pin, expires }) => {
|
|
2055
2129
|
const base = JSON.parse(baseText);
|
|
2056
|
-
const exceptions = record$
|
|
2057
|
-
return Array.isArray(exceptions) && exceptions.some((entry) => record$
|
|
2130
|
+
const exceptions = record$4(base) ? base["rustExceptions"] : void 0;
|
|
2131
|
+
return Array.isArray(exceptions) && exceptions.some((entry) => record$4(entry) && entry["pin"] === pin && entry["expires"] === expires);
|
|
2058
2132
|
};
|
|
2059
2133
|
/** An exception expires on its date; the scope decides whether that fails this run. */
|
|
2060
2134
|
const checkRustExceptionExpiry = ({ exceptions, now = /* @__PURE__ */ new Date(), scope }) => {
|
|
@@ -2192,7 +2266,9 @@ const checkRustReferences = (options) => {
|
|
|
2192
2266
|
}
|
|
2193
2267
|
if (token.value.startsWith("-")) continue;
|
|
2194
2268
|
selected++;
|
|
2195
|
-
|
|
2269
|
+
const installFixture = options.type === "file" && 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;
|
|
2270
|
+
if (installFixture !== void 0) options.onInstallFixture?.(installFixture);
|
|
2271
|
+
else select(token.value, "rustup toolchain", token.line);
|
|
2196
2272
|
if (firstSelectorOnly) break;
|
|
2197
2273
|
}
|
|
2198
2274
|
if (selected === 0 && requiresSelector) select(void 0, "rustup toolchain", firstLine);
|
|
@@ -2213,7 +2289,7 @@ const checkRustReferences = (options) => {
|
|
|
2213
2289
|
if (isShellScript(file, text)) shell(text);
|
|
2214
2290
|
if (/(?:^|\/)package\.json$/.test(file)) try {
|
|
2215
2291
|
const object = JSON.parse(text);
|
|
2216
|
-
if (record$
|
|
2292
|
+
if (record$4(object) && record$4(object["scripts"])) {
|
|
2217
2293
|
for (const [name, script] of Object.entries(object["scripts"])) if (typeof script === "string") shell(script, lineOf(`"${name}"`));
|
|
2218
2294
|
}
|
|
2219
2295
|
} catch {}
|
|
@@ -2224,7 +2300,7 @@ const checkRustReferences = (options) => {
|
|
|
2224
2300
|
const version = tag === void 0 ? void 0 : rustImageVersion(tag);
|
|
2225
2301
|
select(version, "Rust service image", lineOf(value));
|
|
2226
2302
|
} else if (Array.isArray(value)) value.forEach(images);
|
|
2227
|
-
else if (record$
|
|
2303
|
+
else if (record$4(value)) {
|
|
2228
2304
|
for (const [key, entry] of Object.entries(value)) if (key !== "name") images(entry);
|
|
2229
2305
|
}
|
|
2230
2306
|
};
|
|
@@ -2248,13 +2324,13 @@ const checkRustReferences = (options) => {
|
|
|
2248
2324
|
value.forEach(visit);
|
|
2249
2325
|
return;
|
|
2250
2326
|
}
|
|
2251
|
-
if (!record$
|
|
2252
|
-
if (record$
|
|
2327
|
+
if (!record$4(value)) return;
|
|
2328
|
+
if (record$4(value["env"]) && value["env"]["RUSTUP_TOOLCHAIN"] !== void 0) select(value["env"]["RUSTUP_TOOLCHAIN"], "RUSTUP_TOOLCHAIN", lineOf("RUSTUP_TOOLCHAIN"));
|
|
2253
2329
|
if (typeof value["run"] === "string") shell(value["run"], lineOf("run:"));
|
|
2254
2330
|
const uses = value["uses"];
|
|
2255
2331
|
if (typeof uses === "string") {
|
|
2256
2332
|
const action = uses.split("@").at(0)?.toLowerCase();
|
|
2257
|
-
const inputs = record$
|
|
2333
|
+
const inputs = record$4(value["with"]) ? value["with"] : {};
|
|
2258
2334
|
if (action === "dtolnay/rust-toolchain" || action === "actions-rust-lang/setup-rust-toolchain") {
|
|
2259
2335
|
const entries = Object.entries(inputs).filter(([key]) => key.toLowerCase() === "toolchain");
|
|
2260
2336
|
if (entries.length !== 1) select(void 0, `${action} requires an explicit exact toolchain input`, lineOf(uses));
|
|
@@ -2268,8 +2344,8 @@ const checkRustReferences = (options) => {
|
|
|
2268
2344
|
}
|
|
2269
2345
|
}
|
|
2270
2346
|
for (const [key, child] of Object.entries(value)) {
|
|
2271
|
-
if (key === "workflow_call" && record$
|
|
2272
|
-
for (const [input, declaration] of Object.entries(child["inputs"])) if (["toolchain", "rust-toolchain"].includes(input) && record$
|
|
2347
|
+
if (key === "workflow_call" && record$4(child) && record$4(child["inputs"])) {
|
|
2348
|
+
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:"));
|
|
2273
2349
|
}
|
|
2274
2350
|
visit(child);
|
|
2275
2351
|
}
|
|
@@ -2280,7 +2356,7 @@ const checkRustReferences = (options) => {
|
|
|
2280
2356
|
};
|
|
2281
2357
|
//#endregion
|
|
2282
2358
|
//#region src/toolchain-workflow-trust.ts
|
|
2283
|
-
const record$
|
|
2359
|
+
const record$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2284
2360
|
const booleanParts = (source, operator) => {
|
|
2285
2361
|
const parts = [];
|
|
2286
2362
|
let depth = 0;
|
|
@@ -2530,7 +2606,7 @@ const safeAction = (uses, inputs) => {
|
|
|
2530
2606
|
const approved = Object.entries(workflowTrustBootstrapCatalog.actions).find(([name]) => name === repository);
|
|
2531
2607
|
if (approved === void 0) return false;
|
|
2532
2608
|
if (inputs === void 0) return repository === "actions/checkout";
|
|
2533
|
-
if (!record$
|
|
2609
|
+
if (!record$3(inputs)) return false;
|
|
2534
2610
|
if (repository !== "actions/checkout") {
|
|
2535
2611
|
const version = inputs["bun-version"];
|
|
2536
2612
|
if (typeof version !== "string" || !/^v?[0-9]+(?:\.(?:[0-9]+|x)){0,2}$/u.test(version)) return false;
|
|
@@ -2543,7 +2619,7 @@ const safeAction = (uses, inputs) => {
|
|
|
2543
2619
|
].includes(typeof value) && (name !== "submodules" || value === false || value === "false"));
|
|
2544
2620
|
};
|
|
2545
2621
|
const safeShell = (shell) => shell === void 0 || shell === "bash" || shell === "sh";
|
|
2546
|
-
const safeEnvironment = (value) => value === void 0 || record$
|
|
2622
|
+
const safeEnvironment = (value) => value === void 0 || record$3(value) && Object.keys(value).every((name) => [
|
|
2547
2623
|
"HEAD_REPO",
|
|
2548
2624
|
"BASE_REPO",
|
|
2549
2625
|
"HAS_RUN_CI_LABEL",
|
|
@@ -2557,25 +2633,25 @@ const safeEnvironment = (value) => value === void 0 || record$2(value) && Object
|
|
|
2557
2633
|
"HAWK_VERSION",
|
|
2558
2634
|
"HAWK_X86_64_UNKNOWN_LINUX_GNU_SHA256"
|
|
2559
2635
|
].includes(name));
|
|
2560
|
-
const configuredShell = (value) => record$
|
|
2636
|
+
const configuredShell = (value) => record$3(value) && record$3(value["run"]) ? value["run"]["shell"] : void 0;
|
|
2561
2637
|
const workflowTrustRules = ["workflow-trust"];
|
|
2562
2638
|
const workflowEvents = (triggers) => {
|
|
2563
2639
|
if (typeof triggers === "string") return [triggers];
|
|
2564
2640
|
if (Array.isArray(triggers)) return triggers;
|
|
2565
|
-
if (record$
|
|
2641
|
+
if (record$3(triggers)) return Object.keys(triggers);
|
|
2566
2642
|
return [];
|
|
2567
2643
|
};
|
|
2568
2644
|
/** Enforce a workflow's declared trust boundary before repository dependency execution. */
|
|
2569
2645
|
const checkWorkflowTrust = (workflow) => {
|
|
2570
2646
|
const findings = [];
|
|
2571
|
-
if (!record$
|
|
2647
|
+
if (!record$3(workflow) || !record$3(workflow["jobs"])) return findings;
|
|
2572
2648
|
const jobs = workflow["jobs"];
|
|
2573
|
-
if (!record$
|
|
2649
|
+
if (!record$3(jobs["trust-check"])) return findings;
|
|
2574
2650
|
const triggers = workflow["on"];
|
|
2575
2651
|
const pullRequests = workflowEvents(triggers).filter((event) => event === "pull_request" || event === "pull_request_target");
|
|
2576
2652
|
if (pullRequests.length === 0) return findings;
|
|
2577
2653
|
for (const [name, job] of Object.entries(jobs)) {
|
|
2578
|
-
if (!record$
|
|
2654
|
+
if (!record$3(job)) continue;
|
|
2579
2655
|
const needs = job["needs"];
|
|
2580
2656
|
const ordered = name !== "trust-check" && (needs === "trust-check" || Array.isArray(needs) && needs.includes("trust-check"));
|
|
2581
2657
|
const permitsExecution = (stepCondition) => {
|
|
@@ -2612,7 +2688,7 @@ const checkWorkflowTrust = (workflow) => {
|
|
|
2612
2688
|
message
|
|
2613
2689
|
});
|
|
2614
2690
|
const services = job["services"];
|
|
2615
|
-
if (services !== void 0 && (!record$
|
|
2691
|
+
if (services !== void 0 && (!record$3(services) || Object.keys(services).length > 0)) findings.push({
|
|
2616
2692
|
type: "job",
|
|
2617
2693
|
property: "services",
|
|
2618
2694
|
job: name,
|
|
@@ -2621,7 +2697,7 @@ const checkWorkflowTrust = (workflow) => {
|
|
|
2621
2697
|
}
|
|
2622
2698
|
if (!Array.isArray(job["steps"])) continue;
|
|
2623
2699
|
for (const [index, step] of job["steps"].entries()) {
|
|
2624
|
-
if (!record$
|
|
2700
|
+
if (!record$3(step)) continue;
|
|
2625
2701
|
const run = step["run"];
|
|
2626
2702
|
const uses = step["uses"];
|
|
2627
2703
|
const unsafeRun = typeof run === "string" && (invokesRepositoryCode(run) || !safeShell(step["shell"] ?? configuredShell(job["defaults"]) ?? configuredShell(workflow["defaults"])) || ![
|
|
@@ -2659,7 +2735,7 @@ const runtimeRules = [
|
|
|
2659
2735
|
"action-pins",
|
|
2660
2736
|
...workflowTrustRules
|
|
2661
2737
|
];
|
|
2662
|
-
const record$
|
|
2738
|
+
const record$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2663
2739
|
const dockerEnvironmentAssignments = ({ source, escape }) => {
|
|
2664
2740
|
const words = [];
|
|
2665
2741
|
let word = "";
|
|
@@ -2792,13 +2868,15 @@ const pythonRequirementMatches = (requirement, version) => {
|
|
|
2792
2868
|
});
|
|
2793
2869
|
};
|
|
2794
2870
|
/** Check runtime declarations only in the files that declare or configure them. */
|
|
2795
|
-
const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, repository, onDelegated, dynamicSelectors = [], onDynamicSelector, rustExceptions = [], engineFloors = [], onEngineFloor }) => {
|
|
2871
|
+
const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, repository, onDelegated, dynamicSelectors = [], onDynamicSelector, rustExceptions = [], rustInstallFixtures = [], onRustInstallFixture, engineFloors = [], onEngineFloor }) => {
|
|
2796
2872
|
const diagnostics = checkRustReferences({
|
|
2797
2873
|
type: "file",
|
|
2798
2874
|
file,
|
|
2799
2875
|
text,
|
|
2800
2876
|
rust: policy.rust,
|
|
2801
|
-
exceptions: rustExceptions
|
|
2877
|
+
exceptions: rustExceptions,
|
|
2878
|
+
installFixtures: rustInstallFixtures,
|
|
2879
|
+
onInstallFixture: onRustInstallFixture
|
|
2802
2880
|
});
|
|
2803
2881
|
const name = path.posix.basename(file);
|
|
2804
2882
|
const lines = text.split(/\r?\n/);
|
|
@@ -2894,7 +2972,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2894
2972
|
const toolchain = parsed["toolchain"];
|
|
2895
2973
|
pin({
|
|
2896
2974
|
rule: "rust-version",
|
|
2897
|
-
value: record$
|
|
2975
|
+
value: record$2(toolchain) ? toolchain["channel"] : void 0,
|
|
2898
2976
|
expected: policy.rust,
|
|
2899
2977
|
label: "Rust channel",
|
|
2900
2978
|
line: lineOf("channel")
|
|
@@ -2922,15 +3000,15 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2922
3000
|
};
|
|
2923
3001
|
if (parsed !== void 0) {
|
|
2924
3002
|
const workspace = parsed["workspace"];
|
|
2925
|
-
const defaults = record$
|
|
2926
|
-
if (record$
|
|
3003
|
+
const defaults = record$2(workspace) ? workspace["package"] : void 0;
|
|
3004
|
+
if (record$2(defaults) && defaults["rust-version"] !== void 0) rustFloor(defaults["rust-version"]);
|
|
2927
3005
|
const pkg = parsed["package"];
|
|
2928
|
-
const version = record$
|
|
3006
|
+
const version = record$2(pkg) ? pkg["rust-version"] : void 0;
|
|
2929
3007
|
if (version !== void 0) {
|
|
2930
|
-
if (!record$
|
|
3008
|
+
if (!record$2(version)) rustFloor(version);
|
|
2931
3009
|
else {
|
|
2932
3010
|
let inherited;
|
|
2933
|
-
if (version["workspace"] === true && record$
|
|
3011
|
+
if (version["workspace"] === true && record$2(pkg)) {
|
|
2934
3012
|
const explicit = pkg["workspace"];
|
|
2935
3013
|
let directory = path.posix.dirname(file);
|
|
2936
3014
|
const explicitPath = typeof explicit === "string" && !explicit.includes("\\") && !explicit.includes(":") && !explicit.includes("${{") && !path.posix.isAbsolute(explicit) ? path.posix.normalize(path.posix.join(directory, explicit)) : void 0;
|
|
@@ -2938,9 +3016,9 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2938
3016
|
if (explicitPath !== void 0) directory = explicitPath;
|
|
2939
3017
|
while (true) {
|
|
2940
3018
|
const table = readManifest(path.posix.join(directory, "Cargo.toml"))?.["workspace"];
|
|
2941
|
-
if (record$
|
|
3019
|
+
if (record$2(table)) {
|
|
2942
3020
|
const fields = table["package"];
|
|
2943
|
-
inherited = record$
|
|
3021
|
+
inherited = record$2(fields) ? fields["rust-version"] : void 0;
|
|
2944
3022
|
break;
|
|
2945
3023
|
}
|
|
2946
3024
|
if (explicit !== void 0 || directory === ".") break;
|
|
@@ -2955,7 +3033,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2955
3033
|
}
|
|
2956
3034
|
if (name === "Pipfile") {
|
|
2957
3035
|
const requires = toml("python-version")?.["requires"];
|
|
2958
|
-
if (record$
|
|
3036
|
+
if (record$2(requires)) {
|
|
2959
3037
|
const minor = requires["python_version"];
|
|
2960
3038
|
if (minor !== void 0 && minor !== policy.python.split(".").slice(0, 2).join(".")) add({
|
|
2961
3039
|
rule: "python-version",
|
|
@@ -2974,7 +3052,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2974
3052
|
const parsed = toml("python-version");
|
|
2975
3053
|
if (parsed !== void 0) {
|
|
2976
3054
|
const project = parsed["project"];
|
|
2977
|
-
if (record$
|
|
3055
|
+
if (record$2(project) && project["requires-python"] !== void 0) {
|
|
2978
3056
|
if (!pythonRequirementMatches(project["requires-python"], policy.python)) add({
|
|
2979
3057
|
rule: "python-version",
|
|
2980
3058
|
line: lineOf("requires-python"),
|
|
@@ -2982,9 +3060,9 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2982
3060
|
});
|
|
2983
3061
|
}
|
|
2984
3062
|
const tool = parsed["tool"];
|
|
2985
|
-
const configuredUv = record$
|
|
3063
|
+
const configuredUv = record$2(tool) ? tool["uv"] : void 0;
|
|
2986
3064
|
const uv = name === "uv.toml" ? parsed : configuredUv;
|
|
2987
|
-
if (record$
|
|
3065
|
+
if (record$2(uv) && uv["python"] !== void 0) pin({
|
|
2988
3066
|
rule: "python-version",
|
|
2989
3067
|
value: uv["python"],
|
|
2990
3068
|
expected: policy.python,
|
|
@@ -2995,11 +3073,11 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2995
3073
|
}
|
|
2996
3074
|
if (isMiseConfigPath(file)) {
|
|
2997
3075
|
const tools = toml("runtime-manager")?.["tools"];
|
|
2998
|
-
if (record$
|
|
3076
|
+
if (record$2(tools)) for (const [tool, expected] of Object.entries(versions)) {
|
|
2999
3077
|
const configured = tools[tool];
|
|
3000
3078
|
if (configured !== void 0) pin({
|
|
3001
3079
|
rule: tool === "rust" || tool === "core:rust" ? "rust-version" : "runtime-manager",
|
|
3002
|
-
value: record$
|
|
3080
|
+
value: record$2(configured) ? configured["version"] : configured,
|
|
3003
3081
|
expected,
|
|
3004
3082
|
selector: nodeTools.has(tool) ? "node" : void 0,
|
|
3005
3083
|
label: `mise ${tool}`,
|
|
@@ -3347,7 +3425,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
3347
3425
|
return diagnostics;
|
|
3348
3426
|
}
|
|
3349
3427
|
const nodeLine = (node) => {
|
|
3350
|
-
if (!record$
|
|
3428
|
+
if (!record$2(node) || !Array.isArray(node["range"])) return 1;
|
|
3351
3429
|
const offset = node["range"].at(0);
|
|
3352
3430
|
return typeof offset === "number" ? text.slice(0, offset).split("\n").length : 1;
|
|
3353
3431
|
};
|
|
@@ -3461,7 +3539,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
3461
3539
|
const manifest = JSON.parse(content ?? "");
|
|
3462
3540
|
pin({
|
|
3463
3541
|
rule,
|
|
3464
|
-
value: record$
|
|
3542
|
+
value: record$2(manifest) ? manifest["packageManager"] : void 0,
|
|
3465
3543
|
expected: `bun@${policy.bun}`,
|
|
3466
3544
|
label: "setup-bun version file packageManager",
|
|
3467
3545
|
line
|
|
@@ -3661,10 +3739,21 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
3661
3739
|
const selector = staticRepositoryPath(value) ? path.posix.normalize(value) : void 0;
|
|
3662
3740
|
const providers = checkoutProvidersFor(selector);
|
|
3663
3741
|
const binding = selector === void 0 ? void 0 : providers.at(-1);
|
|
3664
|
-
if (selector === void 0 || unknownCheckoutDestination || binding
|
|
3665
|
-
|
|
3742
|
+
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)) {
|
|
3743
|
+
add({
|
|
3744
|
+
rule: "action-pins",
|
|
3745
|
+
line,
|
|
3746
|
+
message: "local actions require an unambiguous checkout of the current source snapshot"
|
|
3747
|
+
});
|
|
3748
|
+
return;
|
|
3749
|
+
}
|
|
3750
|
+
if (binding?.source === "delegated") onDelegated?.({
|
|
3751
|
+
path: file,
|
|
3666
3752
|
line,
|
|
3667
|
-
|
|
3753
|
+
tool: "action",
|
|
3754
|
+
selector,
|
|
3755
|
+
checkoutPath: binding.path,
|
|
3756
|
+
ref: binding.ref
|
|
3668
3757
|
});
|
|
3669
3758
|
return;
|
|
3670
3759
|
}
|
|
@@ -3962,6 +4051,54 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
3962
4051
|
return diagnostics;
|
|
3963
4052
|
};
|
|
3964
4053
|
//#endregion
|
|
4054
|
+
//#region src/toolchain-rust-fixtures.ts
|
|
4055
|
+
const record$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4056
|
+
const date = (value) => /^\d{4}-\d{2}-\d{2}$/.test(value) && Number.isFinite(Date.parse(value)) && new Date(value).toISOString().slice(0, 10) === value;
|
|
4057
|
+
/** Historical install probes do not authorize using an older compiler. */
|
|
4058
|
+
const parseRustInstallFixtures = (input) => {
|
|
4059
|
+
if (input === void 0) return [];
|
|
4060
|
+
if (!Array.isArray(input)) throw new Error("rustInstallFixtures must be an array");
|
|
4061
|
+
const identities = /* @__PURE__ */ new Set();
|
|
4062
|
+
return input.map((entry) => {
|
|
4063
|
+
if (!record$1(entry) || Object.keys(entry).length !== 5 || Object.keys(entry).some((key) => ![
|
|
4064
|
+
"path",
|
|
4065
|
+
"pin",
|
|
4066
|
+
"reason",
|
|
4067
|
+
"tracking",
|
|
4068
|
+
"expires"
|
|
4069
|
+
].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");
|
|
4070
|
+
const identity = `${entry["path"]}:${entry["pin"]}`;
|
|
4071
|
+
if (identities.has(identity)) throw new Error("Rust install fixture path/pin pairs must be unique");
|
|
4072
|
+
identities.add(identity);
|
|
4073
|
+
return {
|
|
4074
|
+
path: entry["path"],
|
|
4075
|
+
pin: entry["pin"],
|
|
4076
|
+
reason: entry["reason"],
|
|
4077
|
+
tracking: entry["tracking"],
|
|
4078
|
+
expires: entry["expires"]
|
|
4079
|
+
};
|
|
4080
|
+
});
|
|
4081
|
+
};
|
|
4082
|
+
const checkRustInstallFixtureExpiry = ({ fixtures, scope, now = /* @__PURE__ */ new Date() }) => {
|
|
4083
|
+
const errors = [];
|
|
4084
|
+
const warnings = [];
|
|
4085
|
+
for (const fixture of fixtures) {
|
|
4086
|
+
if (fixture.expires > now.toISOString().slice(0, 10)) continue;
|
|
4087
|
+
const verdict = overdueVerdict(scope, "stll-toolchain.json", (text) => {
|
|
4088
|
+
const base = JSON.parse(text);
|
|
4089
|
+
const declarations = record$1(base) ? base["rustInstallFixtures"] : void 0;
|
|
4090
|
+
return Array.isArray(declarations) && declarations.some((entry) => record$1(entry) && entry["path"] === fixture.path && entry["pin"] === fixture.pin && entry["expires"] === fixture.expires);
|
|
4091
|
+
});
|
|
4092
|
+
const message = `Rust install fixture ${fixture.path}:${fixture.pin} expired on ${fixture.expires}; remove it or renew the documented fixture boundary`;
|
|
4093
|
+
if (!verdict.fail) warnings.push(message);
|
|
4094
|
+
else errors.push(verdict.unavailable === void 0 ? message : `${message}; cannot determine whether this change adds it: ${verdict.unavailable}`);
|
|
4095
|
+
}
|
|
4096
|
+
return {
|
|
4097
|
+
errors,
|
|
4098
|
+
warnings
|
|
4099
|
+
};
|
|
4100
|
+
};
|
|
4101
|
+
//#endregion
|
|
3965
4102
|
//#region src/toolchain-guard.ts
|
|
3966
4103
|
const toolchainRules = [.../* @__PURE__ */ new Set([
|
|
3967
4104
|
...packageRules,
|
|
@@ -3976,7 +4113,7 @@ const record = (value) => typeof value === "object" && value !== null && !Array.
|
|
|
3976
4113
|
const array = (value) => Array.isArray(value);
|
|
3977
4114
|
/** Repository-owned declarations are explicit; malformed decisions fail closed. */
|
|
3978
4115
|
const parseToolchainConfiguration = (input) => {
|
|
3979
|
-
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");
|
|
4116
|
+
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");
|
|
3980
4117
|
const disabled = /* @__PURE__ */ new Set();
|
|
3981
4118
|
const optOuts = input["optOuts"] ?? [];
|
|
3982
4119
|
if (!array(optOuts)) throw new Error("optOuts must be an array");
|
|
@@ -3993,6 +4130,7 @@ const parseToolchainConfiguration = (input) => {
|
|
|
3993
4130
|
return {
|
|
3994
4131
|
disabled,
|
|
3995
4132
|
rustExceptions: parseRustExceptions(input["rustExceptions"]),
|
|
4133
|
+
rustInstallFixtures: parseRustInstallFixtures(input["rustInstallFixtures"]),
|
|
3996
4134
|
cloud: parseCloudSetup(input["cloud"]),
|
|
3997
4135
|
engineFloors: parseEngineFloors(input["engineFloors"]),
|
|
3998
4136
|
dynamicSelectors: parseDynamicSelectors(input["dynamicSelectors"]),
|
|
@@ -4085,11 +4223,20 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4085
4223
|
if (files["stll-toolchain.json"] !== void 0) try {
|
|
4086
4224
|
configuration = parseToolchainConfiguration(JSON.parse(files["stll-toolchain.json"]));
|
|
4087
4225
|
dynamicSelectors = configuration.dynamicSelectors;
|
|
4088
|
-
const
|
|
4226
|
+
const rustExpiry = checkRustExceptionExpiry({
|
|
4089
4227
|
exceptions: configuration.rustExceptions,
|
|
4090
4228
|
now,
|
|
4091
4229
|
scope: expiryScope
|
|
4092
4230
|
});
|
|
4231
|
+
const fixtureExpiry = checkRustInstallFixtureExpiry({
|
|
4232
|
+
fixtures: configuration.rustInstallFixtures,
|
|
4233
|
+
now,
|
|
4234
|
+
scope: expiryScope
|
|
4235
|
+
});
|
|
4236
|
+
const expiry = {
|
|
4237
|
+
errors: [...rustExpiry.errors, ...fixtureExpiry.errors],
|
|
4238
|
+
warnings: [...rustExpiry.warnings, ...fixtureExpiry.warnings]
|
|
4239
|
+
};
|
|
4093
4240
|
for (const message of expiry.errors) diagnostics.push({
|
|
4094
4241
|
rule: "configuration",
|
|
4095
4242
|
path: "stll-toolchain.json",
|
|
@@ -4111,6 +4258,23 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4111
4258
|
message: error instanceof Error ? error.message : String(error)
|
|
4112
4259
|
});
|
|
4113
4260
|
}
|
|
4261
|
+
for (const entry of configuration.typescriptAllowlist) {
|
|
4262
|
+
if (entry.kind !== "source-api") continue;
|
|
4263
|
+
for (const source of entry.sources) {
|
|
4264
|
+
const content = readSourceApiInput({
|
|
4265
|
+
root,
|
|
4266
|
+
source,
|
|
4267
|
+
tracked: trackedFiles
|
|
4268
|
+
});
|
|
4269
|
+
if (content !== void 0) files[source] = content;
|
|
4270
|
+
else diagnostics.push({
|
|
4271
|
+
rule: "configuration",
|
|
4272
|
+
path: source,
|
|
4273
|
+
line: 1,
|
|
4274
|
+
message: "TypeScript source API evidence must be tracked, readable, regular and free of symlinks"
|
|
4275
|
+
});
|
|
4276
|
+
}
|
|
4277
|
+
}
|
|
4114
4278
|
const cloud = configuration.cloud;
|
|
4115
4279
|
const script = files[cloudSetupPath];
|
|
4116
4280
|
if (cloud === void 0 && trackedFiles.has(".agents/cloud-setup.sh")) diagnostics.push({
|
|
@@ -4223,6 +4387,7 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4223
4387
|
trackedFiles
|
|
4224
4388
|
}));
|
|
4225
4389
|
}
|
|
4390
|
+
const usedRustFixtures = /* @__PURE__ */ new Set();
|
|
4226
4391
|
for (const [file, text] of Object.entries(files)) diagnostics.push(...checkRuntimeFile({
|
|
4227
4392
|
file,
|
|
4228
4393
|
text,
|
|
@@ -4235,6 +4400,8 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4235
4400
|
},
|
|
4236
4401
|
trackedFiles,
|
|
4237
4402
|
rustExceptions: configuration.rustExceptions,
|
|
4403
|
+
rustInstallFixtures: configuration.rustInstallFixtures,
|
|
4404
|
+
onRustInstallFixture: (entry) => usedRustFixtures.add(entry),
|
|
4238
4405
|
dynamicSelectors: dynamicSelectors.filter((entry) => entry.path === file),
|
|
4239
4406
|
onDynamicSelector: (entry) => selectorMatches.set(entry, (selectorMatches.get(entry) ?? 0) + 1),
|
|
4240
4407
|
repository,
|
|
@@ -4246,6 +4413,12 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4246
4413
|
},
|
|
4247
4414
|
readFile: (target) => files[target]
|
|
4248
4415
|
}));
|
|
4416
|
+
for (const entry of configuration.rustInstallFixtures) if (!usedRustFixtures.has(entry)) diagnostics.push({
|
|
4417
|
+
rule: "configuration",
|
|
4418
|
+
path: "stll-toolchain.json",
|
|
4419
|
+
line: 1,
|
|
4420
|
+
message: `Rust install fixture ${entry.path}:${entry.pin} must match a tracked rustup toolchain install probe`
|
|
4421
|
+
});
|
|
4249
4422
|
for (const entry of engineFloors) if (!exercisedFloors.has(entry) || mismatchedFloors.has(entry)) diagnostics.push({
|
|
4250
4423
|
rule: "configuration",
|
|
4251
4424
|
path: "stll-toolchain.json",
|