@stll/oxlint-config 2.1.3 → 3.1.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 +10 -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 +184 -102
- package/dist/toolchain-guard.cjs.map +1 -1
- package/dist/toolchain-guard.mjs +184 -102
- 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 +4 -4
- package/toolchain.json +6 -6
package/dist/toolchain-guard.mjs
CHANGED
|
@@ -31,9 +31,9 @@ const repositoryBun = (floor, manifest) => {
|
|
|
31
31
|
};
|
|
32
32
|
//#endregion
|
|
33
33
|
//#region src/toolchain-consumer-checks.ts
|
|
34
|
-
const consumerRunnerVersion = "
|
|
34
|
+
const consumerRunnerVersion = "3.1.0";
|
|
35
35
|
const consumerCheckWorkflow = "stella/.github/.github/workflows/package-consumer-compat.yml";
|
|
36
|
-
const record$
|
|
36
|
+
const record$11 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37
37
|
const repositoryDirectory = (value) => typeof value === "string" && value !== "" && !value.includes("\\") && !value.includes("${{") && !value.split("").some((character) => {
|
|
38
38
|
const code = character.charCodeAt(0);
|
|
39
39
|
return code < 32 || code === 127;
|
|
@@ -119,12 +119,12 @@ const validConsumerCron = (text) => {
|
|
|
119
119
|
});
|
|
120
120
|
};
|
|
121
121
|
const nightlySchedule = (value) => {
|
|
122
|
-
if (!record$
|
|
122
|
+
if (!record$11(value)) return false;
|
|
123
123
|
const schedule = value["schedule"];
|
|
124
124
|
if (!Array.isArray(schedule) || schedule.length === 0) return false;
|
|
125
125
|
const coveredDays = /* @__PURE__ */ new Set();
|
|
126
126
|
for (const entry of schedule) {
|
|
127
|
-
if (!record$
|
|
127
|
+
if (!record$11(entry) || typeof entry["cron"] !== "string" || !validConsumerCron(entry["cron"])) return false;
|
|
128
128
|
const fields = entry["cron"].trim().split(/\s+/);
|
|
129
129
|
const selections = cronFields.map((field, index) => {
|
|
130
130
|
const text = fields.at(index);
|
|
@@ -140,7 +140,7 @@ const parseConsumerChecks = (input) => {
|
|
|
140
140
|
if (!Array.isArray(input)) throw new Error("consumerChecks must be an array");
|
|
141
141
|
const targets = /* @__PURE__ */ new Set();
|
|
142
142
|
return input.map((entry) => {
|
|
143
|
-
if (!record$
|
|
143
|
+
if (!record$11(entry) || Object.keys(entry).some((key) => ![
|
|
144
144
|
"workflow",
|
|
145
145
|
"job",
|
|
146
146
|
"packages",
|
|
@@ -178,9 +178,9 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
|
|
|
178
178
|
});
|
|
179
179
|
if (document.errors.length > 0) throw new Error("invalid workflow YAML");
|
|
180
180
|
const source = document.toJS({ maxAliasCount: 100 });
|
|
181
|
-
if (!record$
|
|
181
|
+
if (!record$11(source) || !record$11(source["jobs"])) continue;
|
|
182
182
|
for (const [jobName, job] of Object.entries(source["jobs"])) {
|
|
183
|
-
if (!record$
|
|
183
|
+
if (!record$11(job) || typeof job["uses"] !== "string") continue;
|
|
184
184
|
const [workflowPath, ref] = job["uses"].split("@");
|
|
185
185
|
if (workflowPath?.toLowerCase() !== "stella/.github/.github/workflows/package-consumer-compat.yml") continue;
|
|
186
186
|
const declaration = declarations.find((entry) => entry.workflow === workflow && entry.job === jobName);
|
|
@@ -194,7 +194,7 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
|
|
|
194
194
|
const approved = Object.entries(policy.actions).find(([name]) => name.toLowerCase() === consumerCheckWorkflow)?.[1];
|
|
195
195
|
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
196
|
const inputs = job["with"];
|
|
197
|
-
if (!record$
|
|
197
|
+
if (!record$11(inputs)) throw new Error(`${workflow}:${jobName} requires consumer inputs`);
|
|
198
198
|
const names = /* @__PURE__ */ new Set();
|
|
199
199
|
for (const key of Object.keys(inputs)) {
|
|
200
200
|
const normalized = key.toLowerCase();
|
|
@@ -264,7 +264,7 @@ const locatorValidators = {
|
|
|
264
264
|
"bun-source": /^(?:jobs\.[\w-]+|runs)\.steps\.[\w-]+$/
|
|
265
265
|
};
|
|
266
266
|
const knownKind = (value) => typeof value === "string" && Object.hasOwn(locatorValidators, value);
|
|
267
|
-
const record$
|
|
267
|
+
const record$10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
268
268
|
/** One reviewed declaration identifies one unresolved selector, never a rule. */
|
|
269
269
|
const parseDynamicSelectors = (input) => {
|
|
270
270
|
if (input === void 0) return [];
|
|
@@ -272,7 +272,7 @@ const parseDynamicSelectors = (input) => {
|
|
|
272
272
|
const result = [];
|
|
273
273
|
const keys = /* @__PURE__ */ new Set();
|
|
274
274
|
for (const entry of input) {
|
|
275
|
-
if (!record$
|
|
275
|
+
if (!record$10(entry) || Object.keys(entry).some((key) => ![
|
|
276
276
|
"path",
|
|
277
277
|
"kind",
|
|
278
278
|
"reason",
|
|
@@ -308,7 +308,7 @@ const parseDynamicSelectors = (input) => {
|
|
|
308
308
|
};
|
|
309
309
|
//#endregion
|
|
310
310
|
//#region src/toolchain-engine-floors.ts
|
|
311
|
-
const record$
|
|
311
|
+
const record$9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
312
312
|
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
313
|
const parseEngineFloors = (input) => {
|
|
314
314
|
if (input === void 0) return [];
|
|
@@ -316,7 +316,7 @@ const parseEngineFloors = (input) => {
|
|
|
316
316
|
const result = [];
|
|
317
317
|
const jobs = /* @__PURE__ */ new Set();
|
|
318
318
|
for (const entry of input) {
|
|
319
|
-
if (!record$
|
|
319
|
+
if (!record$9(entry) || Object.keys(entry).some((key) => ![
|
|
320
320
|
"package",
|
|
321
321
|
"workflow",
|
|
322
322
|
"job"
|
|
@@ -337,16 +337,16 @@ const resolveEngineFloor = (entry, files) => {
|
|
|
337
337
|
const workflowText = files[entry.workflow];
|
|
338
338
|
if (manifestText === void 0 || workflowText === void 0) throw new Error("engine floor package and workflow must be tracked");
|
|
339
339
|
const manifest = JSON.parse(manifestText);
|
|
340
|
-
if (!record$
|
|
340
|
+
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
341
|
const range = manifest["engines"]["node"];
|
|
342
342
|
const minimum = validRange(range) === null ? null : minVersion(range);
|
|
343
343
|
if (minimum === null || minimum.prerelease.length !== 0) throw new Error("engine floor engines.node must declare a stable minimum");
|
|
344
344
|
const document = parseDocument(workflowText, { merge: true });
|
|
345
345
|
if (document.errors.length !== 0) throw new Error("engine floor workflow must be valid YAML");
|
|
346
346
|
const workflow = document.toJS({ maxAliasCount: 100 });
|
|
347
|
-
if (!record$
|
|
347
|
+
if (!record$9(workflow) || !record$9(workflow["jobs"])) throw new Error("engine floor job must exist in its tracked workflow");
|
|
348
348
|
const job = workflow["jobs"][entry.job];
|
|
349
|
-
if (!record$
|
|
349
|
+
if (!record$9(job)) throw new Error("engine floor job must exist in its tracked workflow");
|
|
350
350
|
if (!Array.isArray(job["steps"]) || Object.hasOwn(job, "uses")) throw new Error("engine floor job must declare steps and cannot reuse a workflow");
|
|
351
351
|
return {
|
|
352
352
|
...entry,
|
|
@@ -376,7 +376,7 @@ const installSections = [
|
|
|
376
376
|
"optionalDependencies"
|
|
377
377
|
];
|
|
378
378
|
const allowlistFile = "stll-toolchain.json";
|
|
379
|
-
const record$
|
|
379
|
+
const record$8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
380
380
|
/** `npm:typescript@7.0.2` installs a compiler under another name; returns its version. */
|
|
381
381
|
const typescriptAliasVersion = (spec) => {
|
|
382
382
|
if (typeof spec !== "string") return void 0;
|
|
@@ -406,7 +406,7 @@ const parseTypescriptAllowlist = (input) => {
|
|
|
406
406
|
return input.map((entry) => {
|
|
407
407
|
const shown = JSON.stringify(entry);
|
|
408
408
|
const text = (key) => {
|
|
409
|
-
const value = record$
|
|
409
|
+
const value = record$8(entry) ? entry[key] : void 0;
|
|
410
410
|
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
411
411
|
};
|
|
412
412
|
const workspace = text("workspace");
|
|
@@ -414,7 +414,7 @@ const parseTypescriptAllowlist = (input) => {
|
|
|
414
414
|
const spec = text("spec");
|
|
415
415
|
const dependency = text("dependency");
|
|
416
416
|
const reason = text("reason");
|
|
417
|
-
if (!record$
|
|
417
|
+
if (!record$8(entry) || Object.keys(entry).length !== allowlistKeys.length || workspace === void 0 || name === void 0 || spec === void 0 || 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
418
|
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
419
|
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
420
|
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>`);
|
|
@@ -990,10 +990,10 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
990
990
|
const document = pnpm ? parseDocument(text, { lineCounter }) : void 0;
|
|
991
991
|
if (document !== void 0 && document.errors.length > 0) throw new Error("invalid YAML");
|
|
992
992
|
const json = document === void 0 ? JSON.parse(text) : document.toJS();
|
|
993
|
-
if (!record$
|
|
993
|
+
if (!record$8(json)) throw new Error("manifest must be an object");
|
|
994
994
|
if (pnpm) {
|
|
995
|
-
if (json["catalog"] !== void 0 && !record$
|
|
996
|
-
if (json["catalogs"] !== void 0 && (!record$
|
|
995
|
+
if (json["catalog"] !== void 0 && !record$8(json["catalog"])) throw new Error("catalog must be a mapping");
|
|
996
|
+
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
997
|
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
998
|
}
|
|
999
999
|
manifests.set(file, json);
|
|
@@ -1007,13 +1007,13 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1007
1007
|
catalog: json["overrides"],
|
|
1008
1008
|
prefix: ["overrides"]
|
|
1009
1009
|
},
|
|
1010
|
-
...record$
|
|
1010
|
+
...record$8(json["catalogs"]) ? Object.entries(json["catalogs"]).map(([name, catalog]) => ({
|
|
1011
1011
|
catalog,
|
|
1012
1012
|
prefix: ["catalogs", name]
|
|
1013
1013
|
})) : []
|
|
1014
1014
|
];
|
|
1015
1015
|
for (const { catalog, prefix } of catalogEntries) {
|
|
1016
|
-
if (!record$
|
|
1016
|
+
if (!record$8(catalog)) continue;
|
|
1017
1017
|
for (const [name, value] of Object.entries(catalog)) {
|
|
1018
1018
|
const node = document.getIn([...prefix, name], true);
|
|
1019
1019
|
if (!isNode(node) || node.range === void 0 || node.range === null) continue;
|
|
@@ -1032,7 +1032,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1032
1032
|
});
|
|
1033
1033
|
}
|
|
1034
1034
|
}
|
|
1035
|
-
const catalogSources = (json) => [json, ...record$
|
|
1035
|
+
const catalogSources = (json) => [json, ...record$8(json["workspaces"]) ? [json["workspaces"]] : []];
|
|
1036
1036
|
const pnpmWorkspaceFor = (file) => {
|
|
1037
1037
|
let directory = path.posix.dirname(file);
|
|
1038
1038
|
while (true) {
|
|
@@ -1055,8 +1055,8 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1055
1055
|
patterns: workspace["packages"],
|
|
1056
1056
|
rootMembership: "implicit"
|
|
1057
1057
|
})) return void 0;
|
|
1058
|
-
if (name === "" || name === "default") return workspace["catalog"] ?? (record$
|
|
1059
|
-
return record$
|
|
1058
|
+
if (name === "" || name === "default") return workspace["catalog"] ?? (record$8(workspace["catalogs"]) ? workspace["catalogs"]["default"] : void 0);
|
|
1059
|
+
return record$8(workspace["catalogs"]) ? workspace["catalogs"][name] : void 0;
|
|
1060
1060
|
}
|
|
1061
1061
|
let directory = path.posix.dirname(file);
|
|
1062
1062
|
while (true) {
|
|
@@ -1064,7 +1064,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1064
1064
|
if (manifest !== void 0) {
|
|
1065
1065
|
for (const source of catalogSources(manifest)) {
|
|
1066
1066
|
if (name === "" && source["catalog"] !== void 0) return source["catalog"];
|
|
1067
|
-
if (record$
|
|
1067
|
+
if (record$8(source["catalogs"]) && source["catalogs"][name] !== void 0) return source["catalogs"][name];
|
|
1068
1068
|
}
|
|
1069
1069
|
if (manifest["workspaces"] !== void 0) return void 0;
|
|
1070
1070
|
}
|
|
@@ -1082,7 +1082,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1082
1082
|
};
|
|
1083
1083
|
visited.add(value);
|
|
1084
1084
|
const catalog = catalogFor(file, value.slice(8));
|
|
1085
|
-
if (!record$
|
|
1085
|
+
if (!record$8(catalog)) return {
|
|
1086
1086
|
value: void 0,
|
|
1087
1087
|
catalogs
|
|
1088
1088
|
};
|
|
@@ -1107,7 +1107,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1107
1107
|
const workspaces = manifests.get(path.posix.join(directory, "package.json"))?.["workspaces"];
|
|
1108
1108
|
if (workspaces !== void 0) return {
|
|
1109
1109
|
directory,
|
|
1110
|
-
patterns: record$
|
|
1110
|
+
patterns: record$8(workspaces) ? workspaces["packages"] : workspaces,
|
|
1111
1111
|
source: "json"
|
|
1112
1112
|
};
|
|
1113
1113
|
if (directory === ".") return void 0;
|
|
@@ -1141,7 +1141,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1141
1141
|
if (json === void 0) continue;
|
|
1142
1142
|
for (const section of installSections) {
|
|
1143
1143
|
const entries = json[section];
|
|
1144
|
-
if (!record$
|
|
1144
|
+
if (!record$8(entries) || !Object.hasOwn(entries, entry.name)) continue;
|
|
1145
1145
|
const { value, catalogs } = resolveThrough(file, entry.name, entries[entry.name]);
|
|
1146
1146
|
if (value !== entry.spec) continue;
|
|
1147
1147
|
for (const catalog of catalogs) {
|
|
@@ -1186,7 +1186,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1186
1186
|
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
1187
|
});
|
|
1188
1188
|
const checkEntries = (entries, scope, location) => {
|
|
1189
|
-
if (!record$
|
|
1189
|
+
if (!record$8(entries)) return;
|
|
1190
1190
|
for (const [name, raw] of Object.entries(entries)) {
|
|
1191
1191
|
const value = resolve(file, name, raw);
|
|
1192
1192
|
if (isTypescriptInstall(name, value)) {
|
|
@@ -1226,13 +1226,13 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1226
1226
|
}
|
|
1227
1227
|
};
|
|
1228
1228
|
for (const key of installSections) checkEntries(json[key], "install");
|
|
1229
|
-
if (record$
|
|
1229
|
+
if (record$8(json["peerDependencies"])) for (const [name, value] of Object.entries(json["peerDependencies"])) {
|
|
1230
1230
|
const resolved = resolve(file, name, value);
|
|
1231
1231
|
if (isTypescriptInstall(name, resolved) && !peerRange(resolved)) checkEntries({ [name]: value }, "peer");
|
|
1232
1232
|
}
|
|
1233
1233
|
for (const source of catalogSources(json)) {
|
|
1234
1234
|
checkEntries(source["catalog"], "catalog");
|
|
1235
|
-
if (!record$
|
|
1235
|
+
if (!record$8(source["catalogs"])) continue;
|
|
1236
1236
|
for (const catalog of Object.values(source["catalogs"])) checkEntries(catalog, "catalog");
|
|
1237
1237
|
}
|
|
1238
1238
|
const ownedNames = /* @__PURE__ */ new Set([...pins.keys(), ...typescriptPackages]);
|
|
@@ -1244,11 +1244,11 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1244
1244
|
"optionalDependencies"
|
|
1245
1245
|
]) {
|
|
1246
1246
|
const entries = resolutionRoot[section];
|
|
1247
|
-
if (!record$
|
|
1247
|
+
if (!record$8(entries)) continue;
|
|
1248
1248
|
for (const [name, value] of Object.entries(entries)) resolutionDependencies[name] = resolve(file, name, value);
|
|
1249
1249
|
}
|
|
1250
1250
|
const checkResolutionMap = (entries, parent) => {
|
|
1251
|
-
if (!record$
|
|
1251
|
+
if (!record$8(entries)) return;
|
|
1252
1252
|
for (const [selector, replacement] of Object.entries(entries)) {
|
|
1253
1253
|
const target = selector === "." ? parent : selector;
|
|
1254
1254
|
if (target === void 0) continue;
|
|
@@ -1267,7 +1267,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1267
1267
|
});
|
|
1268
1268
|
continue;
|
|
1269
1269
|
}
|
|
1270
|
-
if (record$
|
|
1270
|
+
if (record$8(replacement)) {
|
|
1271
1271
|
checkResolutionMap(replacement, target);
|
|
1272
1272
|
continue;
|
|
1273
1273
|
}
|
|
@@ -1287,8 +1287,8 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1287
1287
|
};
|
|
1288
1288
|
checkResolutionMap(json["overrides"]);
|
|
1289
1289
|
checkResolutionMap(json["resolutions"]);
|
|
1290
|
-
if (record$
|
|
1291
|
-
const command = record$
|
|
1290
|
+
if (record$8(json["pnpm"])) checkResolutionMap(json["pnpm"]["overrides"]);
|
|
1291
|
+
const command = record$8(json["scripts"]) ? json["scripts"]["typecheck"] : void 0;
|
|
1292
1292
|
if (typeof command === "string") for (const words of compilerCommandSegments(command)) {
|
|
1293
1293
|
if (!invokesCompiler(words)) continue;
|
|
1294
1294
|
const normalized = words.join(" ");
|
|
@@ -1302,7 +1302,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1302
1302
|
});
|
|
1303
1303
|
break;
|
|
1304
1304
|
}
|
|
1305
|
-
if (record$
|
|
1305
|
+
if (record$8(json["peerDependencies"])) for (const [name, raw] of Object.entries(json["peerDependencies"])) {
|
|
1306
1306
|
if (!typescriptPackages.has(name)) continue;
|
|
1307
1307
|
const range = resolve(file, name, raw);
|
|
1308
1308
|
if (!peerRange(range)) continue;
|
|
@@ -1326,7 +1326,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1326
1326
|
message: `${name} peer support range must include ${release}, found ${range}`
|
|
1327
1327
|
});
|
|
1328
1328
|
}
|
|
1329
|
-
if (record$
|
|
1329
|
+
if (record$8(json["engines"]) && json["engines"]["node"] !== void 0 && (typeof json["engines"]["node"] !== "string" || !nodeSupportRangeMatches(policy.node, json["engines"]["node"]))) add({
|
|
1330
1330
|
file,
|
|
1331
1331
|
rule: "node-engine",
|
|
1332
1332
|
key: "node",
|
|
@@ -1340,7 +1340,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1340
1340
|
if (json === void 0) return `${file} is missing or invalid; remove the entry`;
|
|
1341
1341
|
const declares = (name) => installSections.some((section) => {
|
|
1342
1342
|
const entries = json[section];
|
|
1343
|
-
return record$
|
|
1343
|
+
return record$8(entries) && Object.hasOwn(entries, name);
|
|
1344
1344
|
});
|
|
1345
1345
|
if (!declares(entry.name)) return `${file} no longer declares ${entry.name} in dependencies, devDependencies or optionalDependencies; remove the entry`;
|
|
1346
1346
|
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`;
|
|
@@ -1379,9 +1379,9 @@ const extensionKinds = new Map(Object.entries({
|
|
|
1379
1379
|
const scriptSourceKind = (file) => extensionKinds.get(extname(file));
|
|
1380
1380
|
//#endregion
|
|
1381
1381
|
//#region src/toolchain-script-lifecycle.ts
|
|
1382
|
-
const record$
|
|
1382
|
+
const record$7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1383
1383
|
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$
|
|
1384
|
+
const rootScriptManager = (manifest, context) => declaredManager(context.packageManager ?? (record$7(manifest) ? manifest["packageManager"] : void 0))?.at(1);
|
|
1385
1385
|
const npmHooksEnabled = (file) => {
|
|
1386
1386
|
if (file?.type === "unknown") throw new Error("npm lifecycle configuration must be a tracked regular file");
|
|
1387
1387
|
if (file?.type !== "tracked") return true;
|
|
@@ -1412,12 +1412,12 @@ const yamlSettings = (file, manager) => {
|
|
|
1412
1412
|
} catch {
|
|
1413
1413
|
throw new Error(`Cannot parse ${manager} lifecycle configuration`);
|
|
1414
1414
|
}
|
|
1415
|
-
if (!record$
|
|
1415
|
+
if (!record$7(settings)) throw new Error(`${manager} lifecycle configuration must be a mapping`);
|
|
1416
1416
|
return settings;
|
|
1417
1417
|
};
|
|
1418
1418
|
const pnpmHooksEnabled = (manifest, context) => {
|
|
1419
1419
|
const declared = declaredManager(context.packageManager ?? manifest["packageManager"]);
|
|
1420
|
-
const dependency = [manifest["dependencies"], manifest["devDependencies"]].flatMap((field) => record$
|
|
1420
|
+
const dependency = [manifest["dependencies"], manifest["devDependencies"]].flatMap((field) => record$7(field) ? [field["pnpm"]] : []).find((value) => typeof value === "string");
|
|
1421
1421
|
if ((declared?.at(1) === "pnpm" ? declared.at(2) : dependency) !== "12.9.1") throw new Error("pnpm lifecycle discovery requires declared pnpm@12.9.1");
|
|
1422
1422
|
const enabled = yamlSettings(context.pnpmWorkspace, "pnpm")["enablePrePostScripts"];
|
|
1423
1423
|
if (enabled === void 0) return true;
|
|
@@ -1461,7 +1461,7 @@ const scriptLifecycleStages = ({ name, manager, manifest, scripts, context }) =>
|
|
|
1461
1461
|
};
|
|
1462
1462
|
//#endregion
|
|
1463
1463
|
//#region src/toolchain-lint-command.ts
|
|
1464
|
-
const record$
|
|
1464
|
+
const record$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1465
1465
|
const scriptManagers = /* @__PURE__ */ new Set([
|
|
1466
1466
|
"bun",
|
|
1467
1467
|
"npm",
|
|
@@ -1578,7 +1578,7 @@ const lintInventoryCommands = (manifest, forceEnabled = false, context = {}) =>
|
|
|
1578
1578
|
const errors = [];
|
|
1579
1579
|
const workspaceTasks = /* @__PURE__ */ new Set();
|
|
1580
1580
|
const filteredBuilds = /* @__PURE__ */ new Set();
|
|
1581
|
-
const declaresEngine = record$
|
|
1581
|
+
const declaresEngine = record$6(manifest) && [manifest["dependencies"], manifest["devDependencies"]].some((dependencies) => record$6(dependencies) && "oxlint-tsgolint" in dependencies);
|
|
1582
1582
|
const enabled = forceEnabled || declaresEngine;
|
|
1583
1583
|
if (!enabled) return {
|
|
1584
1584
|
commands,
|
|
@@ -1586,14 +1586,14 @@ const lintInventoryCommands = (manifest, forceEnabled = false, context = {}) =>
|
|
|
1586
1586
|
enabled,
|
|
1587
1587
|
workspaceTask: false
|
|
1588
1588
|
};
|
|
1589
|
-
if (!record$
|
|
1589
|
+
if (!record$6(manifest)) return {
|
|
1590
1590
|
commands,
|
|
1591
1591
|
errors: ["lint inventory requires an object package manifest"],
|
|
1592
1592
|
enabled,
|
|
1593
1593
|
workspaceTask: false
|
|
1594
1594
|
};
|
|
1595
1595
|
const scripts = manifest["scripts"];
|
|
1596
|
-
if (!record$
|
|
1596
|
+
if (!record$6(scripts)) return {
|
|
1597
1597
|
commands,
|
|
1598
1598
|
errors: ["type-aware linting requires a root lint script"],
|
|
1599
1599
|
enabled,
|
|
@@ -1735,7 +1735,7 @@ const checkProjectOwnership = ({ trackedFiles, lintedFiles, projectFiles }) => [
|
|
|
1735
1735
|
line: 1,
|
|
1736
1736
|
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
1737
|
}));
|
|
1738
|
-
const record$
|
|
1738
|
+
const record$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1739
1739
|
const stringList = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
1740
1740
|
const readCommand = ({ root, executable, args, runtime }) => {
|
|
1741
1741
|
const result = spawnSync(runtime, [
|
|
@@ -1779,7 +1779,7 @@ const resolveProjectApi = (root) => {
|
|
|
1779
1779
|
for (const base of [import.meta.url, path.join(root, "package.json")]) try {
|
|
1780
1780
|
return pathToFileURL(createRequire(base).resolve("tsrs/unstable/async")).href;
|
|
1781
1781
|
} catch (error) {
|
|
1782
|
-
const code = record$
|
|
1782
|
+
const code = record$5(error) ? error["code"] : void 0;
|
|
1783
1783
|
if (code !== "MODULE_NOT_FOUND" && code !== "ERR_MODULE_NOT_FOUND") throw error;
|
|
1784
1784
|
}
|
|
1785
1785
|
throw new Error("configuration discovery needs tsrs: reinstall @stll/oxlint-config, which depends on it");
|
|
@@ -1802,7 +1802,7 @@ const readProjectConfig = (root, config) => {
|
|
|
1802
1802
|
if (result.error !== void 0) throw result.error;
|
|
1803
1803
|
if (result.status !== 0) throw new Error(`configuration discovery failed: ${(result.stderr + result.stdout).trim()}`);
|
|
1804
1804
|
const parsed = JSON.parse(result.stdout);
|
|
1805
|
-
if (!record$
|
|
1805
|
+
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
1806
|
return {
|
|
1807
1807
|
files: parsed["files"],
|
|
1808
1808
|
references: parsed["references"],
|
|
@@ -1827,7 +1827,7 @@ const lifecycleFile = (root, file, tracked) => {
|
|
|
1827
1827
|
};
|
|
1828
1828
|
/** Inventory and config expansion only: neither subprocess lints or typechecks. */
|
|
1829
1829
|
const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
1830
|
-
const packageManager = record$
|
|
1830
|
+
const packageManager = record$5(manifest) ? manifest["packageManager"] : void 0;
|
|
1831
1831
|
const managerUsesPnpmWorkspace = packageManager === void 0 || typeof packageManager === "string" && packageManager.startsWith("pnpm@");
|
|
1832
1832
|
const discovered = /* @__PURE__ */ new Map();
|
|
1833
1833
|
const workspaceMembers = (pnpmMembership) => {
|
|
@@ -1863,7 +1863,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
1863
1863
|
return nestedLifecycle;
|
|
1864
1864
|
};
|
|
1865
1865
|
const lifecycle = {
|
|
1866
|
-
packageManager: record$
|
|
1866
|
+
packageManager: record$5(manifest) ? manifest["packageManager"] : void 0,
|
|
1867
1867
|
npmrc: lifecycleFile(root, ".npmrc", trackedFiles),
|
|
1868
1868
|
pnpmWorkspace: lifecycleFile(root, "pnpm-workspace.yaml", trackedFiles),
|
|
1869
1869
|
yarnrc: lifecycleFile(root, ".yarnrc.yml", trackedFiles),
|
|
@@ -1874,7 +1874,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
1874
1874
|
if (member === void 0) throw new Error("Missing selected workspace");
|
|
1875
1875
|
const [directory, pkg] = member;
|
|
1876
1876
|
const scripts = pkg["scripts"];
|
|
1877
|
-
if (!record$
|
|
1877
|
+
if (!record$5(scripts) || typeof scripts["build"] !== "string") throw new Error("Filtered Bun build requires a literal build script");
|
|
1878
1878
|
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
1879
|
if (activeBuilds.has(directory)) throw new Error("Cyclic filtered Bun build delegation");
|
|
1880
1880
|
activeBuilds.add(directory);
|
|
@@ -1916,7 +1916,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
1916
1916
|
for (const [directory, workspace] of workspaceMembers(managerUsesPnpmWorkspace)) {
|
|
1917
1917
|
if (directory === ".") continue;
|
|
1918
1918
|
const file = `${directory}/package.json`;
|
|
1919
|
-
if (!record$
|
|
1919
|
+
if (!record$5(workspace["scripts"]) || workspace["scripts"]["lint"] === void 0) continue;
|
|
1920
1920
|
const nestedLifecycle = workspaceLifecycle(directory);
|
|
1921
1921
|
const nested = lintInventoryCommands(workspace, true, nestedLifecycle);
|
|
1922
1922
|
if (nested.workspaceTask) throw new Error(`nested workspace lint delegation is unsupported: ${file}`);
|
|
@@ -2025,7 +2025,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
2025
2025
|
};
|
|
2026
2026
|
//#endregion
|
|
2027
2027
|
//#region src/toolchain-rust.ts
|
|
2028
|
-
const record$
|
|
2028
|
+
const record$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2029
2029
|
/**
|
|
2030
2030
|
* Exceptions authorize one immutable release, never a channel or an entire
|
|
2031
2031
|
* rule. Expiry is scoped separately by checkRustExceptionExpiry.
|
|
@@ -2035,7 +2035,7 @@ const parseRustExceptions = (input) => {
|
|
|
2035
2035
|
if (!Array.isArray(input)) throw new Error("rustExceptions must be an array");
|
|
2036
2036
|
const pins = /* @__PURE__ */ new Set();
|
|
2037
2037
|
return input.map((entry) => {
|
|
2038
|
-
if (!record$
|
|
2038
|
+
if (!record$4(entry) || Object.keys(entry).length !== 4 || Object.keys(entry).some((key) => ![
|
|
2039
2039
|
"pin",
|
|
2040
2040
|
"reason",
|
|
2041
2041
|
"tracking",
|
|
@@ -2053,8 +2053,8 @@ const parseRustExceptions = (input) => {
|
|
|
2053
2053
|
/** Whether the base stll-toolchain.json declares this exception with the same expiry. */
|
|
2054
2054
|
const declaresException = (baseText, { pin, expires }) => {
|
|
2055
2055
|
const base = JSON.parse(baseText);
|
|
2056
|
-
const exceptions = record$
|
|
2057
|
-
return Array.isArray(exceptions) && exceptions.some((entry) => record$
|
|
2056
|
+
const exceptions = record$4(base) ? base["rustExceptions"] : void 0;
|
|
2057
|
+
return Array.isArray(exceptions) && exceptions.some((entry) => record$4(entry) && entry["pin"] === pin && entry["expires"] === expires);
|
|
2058
2058
|
};
|
|
2059
2059
|
/** An exception expires on its date; the scope decides whether that fails this run. */
|
|
2060
2060
|
const checkRustExceptionExpiry = ({ exceptions, now = /* @__PURE__ */ new Date(), scope }) => {
|
|
@@ -2192,7 +2192,9 @@ const checkRustReferences = (options) => {
|
|
|
2192
2192
|
}
|
|
2193
2193
|
if (token.value.startsWith("-")) continue;
|
|
2194
2194
|
selected++;
|
|
2195
|
-
|
|
2195
|
+
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;
|
|
2196
|
+
if (installFixture !== void 0) options.onInstallFixture?.(installFixture);
|
|
2197
|
+
else select(token.value, "rustup toolchain", token.line);
|
|
2196
2198
|
if (firstSelectorOnly) break;
|
|
2197
2199
|
}
|
|
2198
2200
|
if (selected === 0 && requiresSelector) select(void 0, "rustup toolchain", firstLine);
|
|
@@ -2213,7 +2215,7 @@ const checkRustReferences = (options) => {
|
|
|
2213
2215
|
if (isShellScript(file, text)) shell(text);
|
|
2214
2216
|
if (/(?:^|\/)package\.json$/.test(file)) try {
|
|
2215
2217
|
const object = JSON.parse(text);
|
|
2216
|
-
if (record$
|
|
2218
|
+
if (record$4(object) && record$4(object["scripts"])) {
|
|
2217
2219
|
for (const [name, script] of Object.entries(object["scripts"])) if (typeof script === "string") shell(script, lineOf(`"${name}"`));
|
|
2218
2220
|
}
|
|
2219
2221
|
} catch {}
|
|
@@ -2224,7 +2226,7 @@ const checkRustReferences = (options) => {
|
|
|
2224
2226
|
const version = tag === void 0 ? void 0 : rustImageVersion(tag);
|
|
2225
2227
|
select(version, "Rust service image", lineOf(value));
|
|
2226
2228
|
} else if (Array.isArray(value)) value.forEach(images);
|
|
2227
|
-
else if (record$
|
|
2229
|
+
else if (record$4(value)) {
|
|
2228
2230
|
for (const [key, entry] of Object.entries(value)) if (key !== "name") images(entry);
|
|
2229
2231
|
}
|
|
2230
2232
|
};
|
|
@@ -2248,13 +2250,13 @@ const checkRustReferences = (options) => {
|
|
|
2248
2250
|
value.forEach(visit);
|
|
2249
2251
|
return;
|
|
2250
2252
|
}
|
|
2251
|
-
if (!record$
|
|
2252
|
-
if (record$
|
|
2253
|
+
if (!record$4(value)) return;
|
|
2254
|
+
if (record$4(value["env"]) && value["env"]["RUSTUP_TOOLCHAIN"] !== void 0) select(value["env"]["RUSTUP_TOOLCHAIN"], "RUSTUP_TOOLCHAIN", lineOf("RUSTUP_TOOLCHAIN"));
|
|
2253
2255
|
if (typeof value["run"] === "string") shell(value["run"], lineOf("run:"));
|
|
2254
2256
|
const uses = value["uses"];
|
|
2255
2257
|
if (typeof uses === "string") {
|
|
2256
2258
|
const action = uses.split("@").at(0)?.toLowerCase();
|
|
2257
|
-
const inputs = record$
|
|
2259
|
+
const inputs = record$4(value["with"]) ? value["with"] : {};
|
|
2258
2260
|
if (action === "dtolnay/rust-toolchain" || action === "actions-rust-lang/setup-rust-toolchain") {
|
|
2259
2261
|
const entries = Object.entries(inputs).filter(([key]) => key.toLowerCase() === "toolchain");
|
|
2260
2262
|
if (entries.length !== 1) select(void 0, `${action} requires an explicit exact toolchain input`, lineOf(uses));
|
|
@@ -2268,8 +2270,8 @@ const checkRustReferences = (options) => {
|
|
|
2268
2270
|
}
|
|
2269
2271
|
}
|
|
2270
2272
|
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$
|
|
2273
|
+
if (key === "workflow_call" && record$4(child) && record$4(child["inputs"])) {
|
|
2274
|
+
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
2275
|
}
|
|
2274
2276
|
visit(child);
|
|
2275
2277
|
}
|
|
@@ -2280,7 +2282,7 @@ const checkRustReferences = (options) => {
|
|
|
2280
2282
|
};
|
|
2281
2283
|
//#endregion
|
|
2282
2284
|
//#region src/toolchain-workflow-trust.ts
|
|
2283
|
-
const record$
|
|
2285
|
+
const record$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2284
2286
|
const booleanParts = (source, operator) => {
|
|
2285
2287
|
const parts = [];
|
|
2286
2288
|
let depth = 0;
|
|
@@ -2530,7 +2532,7 @@ const safeAction = (uses, inputs) => {
|
|
|
2530
2532
|
const approved = Object.entries(workflowTrustBootstrapCatalog.actions).find(([name]) => name === repository);
|
|
2531
2533
|
if (approved === void 0) return false;
|
|
2532
2534
|
if (inputs === void 0) return repository === "actions/checkout";
|
|
2533
|
-
if (!record$
|
|
2535
|
+
if (!record$3(inputs)) return false;
|
|
2534
2536
|
if (repository !== "actions/checkout") {
|
|
2535
2537
|
const version = inputs["bun-version"];
|
|
2536
2538
|
if (typeof version !== "string" || !/^v?[0-9]+(?:\.(?:[0-9]+|x)){0,2}$/u.test(version)) return false;
|
|
@@ -2543,7 +2545,7 @@ const safeAction = (uses, inputs) => {
|
|
|
2543
2545
|
].includes(typeof value) && (name !== "submodules" || value === false || value === "false"));
|
|
2544
2546
|
};
|
|
2545
2547
|
const safeShell = (shell) => shell === void 0 || shell === "bash" || shell === "sh";
|
|
2546
|
-
const safeEnvironment = (value) => value === void 0 || record$
|
|
2548
|
+
const safeEnvironment = (value) => value === void 0 || record$3(value) && Object.keys(value).every((name) => [
|
|
2547
2549
|
"HEAD_REPO",
|
|
2548
2550
|
"BASE_REPO",
|
|
2549
2551
|
"HAS_RUN_CI_LABEL",
|
|
@@ -2557,25 +2559,25 @@ const safeEnvironment = (value) => value === void 0 || record$2(value) && Object
|
|
|
2557
2559
|
"HAWK_VERSION",
|
|
2558
2560
|
"HAWK_X86_64_UNKNOWN_LINUX_GNU_SHA256"
|
|
2559
2561
|
].includes(name));
|
|
2560
|
-
const configuredShell = (value) => record$
|
|
2562
|
+
const configuredShell = (value) => record$3(value) && record$3(value["run"]) ? value["run"]["shell"] : void 0;
|
|
2561
2563
|
const workflowTrustRules = ["workflow-trust"];
|
|
2562
2564
|
const workflowEvents = (triggers) => {
|
|
2563
2565
|
if (typeof triggers === "string") return [triggers];
|
|
2564
2566
|
if (Array.isArray(triggers)) return triggers;
|
|
2565
|
-
if (record$
|
|
2567
|
+
if (record$3(triggers)) return Object.keys(triggers);
|
|
2566
2568
|
return [];
|
|
2567
2569
|
};
|
|
2568
2570
|
/** Enforce a workflow's declared trust boundary before repository dependency execution. */
|
|
2569
2571
|
const checkWorkflowTrust = (workflow) => {
|
|
2570
2572
|
const findings = [];
|
|
2571
|
-
if (!record$
|
|
2573
|
+
if (!record$3(workflow) || !record$3(workflow["jobs"])) return findings;
|
|
2572
2574
|
const jobs = workflow["jobs"];
|
|
2573
|
-
if (!record$
|
|
2575
|
+
if (!record$3(jobs["trust-check"])) return findings;
|
|
2574
2576
|
const triggers = workflow["on"];
|
|
2575
2577
|
const pullRequests = workflowEvents(triggers).filter((event) => event === "pull_request" || event === "pull_request_target");
|
|
2576
2578
|
if (pullRequests.length === 0) return findings;
|
|
2577
2579
|
for (const [name, job] of Object.entries(jobs)) {
|
|
2578
|
-
if (!record$
|
|
2580
|
+
if (!record$3(job)) continue;
|
|
2579
2581
|
const needs = job["needs"];
|
|
2580
2582
|
const ordered = name !== "trust-check" && (needs === "trust-check" || Array.isArray(needs) && needs.includes("trust-check"));
|
|
2581
2583
|
const permitsExecution = (stepCondition) => {
|
|
@@ -2612,7 +2614,7 @@ const checkWorkflowTrust = (workflow) => {
|
|
|
2612
2614
|
message
|
|
2613
2615
|
});
|
|
2614
2616
|
const services = job["services"];
|
|
2615
|
-
if (services !== void 0 && (!record$
|
|
2617
|
+
if (services !== void 0 && (!record$3(services) || Object.keys(services).length > 0)) findings.push({
|
|
2616
2618
|
type: "job",
|
|
2617
2619
|
property: "services",
|
|
2618
2620
|
job: name,
|
|
@@ -2621,7 +2623,7 @@ const checkWorkflowTrust = (workflow) => {
|
|
|
2621
2623
|
}
|
|
2622
2624
|
if (!Array.isArray(job["steps"])) continue;
|
|
2623
2625
|
for (const [index, step] of job["steps"].entries()) {
|
|
2624
|
-
if (!record$
|
|
2626
|
+
if (!record$3(step)) continue;
|
|
2625
2627
|
const run = step["run"];
|
|
2626
2628
|
const uses = step["uses"];
|
|
2627
2629
|
const unsafeRun = typeof run === "string" && (invokesRepositoryCode(run) || !safeShell(step["shell"] ?? configuredShell(job["defaults"]) ?? configuredShell(workflow["defaults"])) || ![
|
|
@@ -2659,7 +2661,7 @@ const runtimeRules = [
|
|
|
2659
2661
|
"action-pins",
|
|
2660
2662
|
...workflowTrustRules
|
|
2661
2663
|
];
|
|
2662
|
-
const record$
|
|
2664
|
+
const record$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2663
2665
|
const dockerEnvironmentAssignments = ({ source, escape }) => {
|
|
2664
2666
|
const words = [];
|
|
2665
2667
|
let word = "";
|
|
@@ -2792,13 +2794,15 @@ const pythonRequirementMatches = (requirement, version) => {
|
|
|
2792
2794
|
});
|
|
2793
2795
|
};
|
|
2794
2796
|
/** 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 }) => {
|
|
2797
|
+
const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, repository, onDelegated, dynamicSelectors = [], onDynamicSelector, rustExceptions = [], rustInstallFixtures = [], onRustInstallFixture, engineFloors = [], onEngineFloor }) => {
|
|
2796
2798
|
const diagnostics = checkRustReferences({
|
|
2797
2799
|
type: "file",
|
|
2798
2800
|
file,
|
|
2799
2801
|
text,
|
|
2800
2802
|
rust: policy.rust,
|
|
2801
|
-
exceptions: rustExceptions
|
|
2803
|
+
exceptions: rustExceptions,
|
|
2804
|
+
installFixtures: rustInstallFixtures,
|
|
2805
|
+
onInstallFixture: onRustInstallFixture
|
|
2802
2806
|
});
|
|
2803
2807
|
const name = path.posix.basename(file);
|
|
2804
2808
|
const lines = text.split(/\r?\n/);
|
|
@@ -2894,7 +2898,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2894
2898
|
const toolchain = parsed["toolchain"];
|
|
2895
2899
|
pin({
|
|
2896
2900
|
rule: "rust-version",
|
|
2897
|
-
value: record$
|
|
2901
|
+
value: record$2(toolchain) ? toolchain["channel"] : void 0,
|
|
2898
2902
|
expected: policy.rust,
|
|
2899
2903
|
label: "Rust channel",
|
|
2900
2904
|
line: lineOf("channel")
|
|
@@ -2922,15 +2926,15 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2922
2926
|
};
|
|
2923
2927
|
if (parsed !== void 0) {
|
|
2924
2928
|
const workspace = parsed["workspace"];
|
|
2925
|
-
const defaults = record$
|
|
2926
|
-
if (record$
|
|
2929
|
+
const defaults = record$2(workspace) ? workspace["package"] : void 0;
|
|
2930
|
+
if (record$2(defaults) && defaults["rust-version"] !== void 0) rustFloor(defaults["rust-version"]);
|
|
2927
2931
|
const pkg = parsed["package"];
|
|
2928
|
-
const version = record$
|
|
2932
|
+
const version = record$2(pkg) ? pkg["rust-version"] : void 0;
|
|
2929
2933
|
if (version !== void 0) {
|
|
2930
|
-
if (!record$
|
|
2934
|
+
if (!record$2(version)) rustFloor(version);
|
|
2931
2935
|
else {
|
|
2932
2936
|
let inherited;
|
|
2933
|
-
if (version["workspace"] === true && record$
|
|
2937
|
+
if (version["workspace"] === true && record$2(pkg)) {
|
|
2934
2938
|
const explicit = pkg["workspace"];
|
|
2935
2939
|
let directory = path.posix.dirname(file);
|
|
2936
2940
|
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 +2942,9 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2938
2942
|
if (explicitPath !== void 0) directory = explicitPath;
|
|
2939
2943
|
while (true) {
|
|
2940
2944
|
const table = readManifest(path.posix.join(directory, "Cargo.toml"))?.["workspace"];
|
|
2941
|
-
if (record$
|
|
2945
|
+
if (record$2(table)) {
|
|
2942
2946
|
const fields = table["package"];
|
|
2943
|
-
inherited = record$
|
|
2947
|
+
inherited = record$2(fields) ? fields["rust-version"] : void 0;
|
|
2944
2948
|
break;
|
|
2945
2949
|
}
|
|
2946
2950
|
if (explicit !== void 0 || directory === ".") break;
|
|
@@ -2955,7 +2959,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2955
2959
|
}
|
|
2956
2960
|
if (name === "Pipfile") {
|
|
2957
2961
|
const requires = toml("python-version")?.["requires"];
|
|
2958
|
-
if (record$
|
|
2962
|
+
if (record$2(requires)) {
|
|
2959
2963
|
const minor = requires["python_version"];
|
|
2960
2964
|
if (minor !== void 0 && minor !== policy.python.split(".").slice(0, 2).join(".")) add({
|
|
2961
2965
|
rule: "python-version",
|
|
@@ -2974,7 +2978,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2974
2978
|
const parsed = toml("python-version");
|
|
2975
2979
|
if (parsed !== void 0) {
|
|
2976
2980
|
const project = parsed["project"];
|
|
2977
|
-
if (record$
|
|
2981
|
+
if (record$2(project) && project["requires-python"] !== void 0) {
|
|
2978
2982
|
if (!pythonRequirementMatches(project["requires-python"], policy.python)) add({
|
|
2979
2983
|
rule: "python-version",
|
|
2980
2984
|
line: lineOf("requires-python"),
|
|
@@ -2982,9 +2986,9 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2982
2986
|
});
|
|
2983
2987
|
}
|
|
2984
2988
|
const tool = parsed["tool"];
|
|
2985
|
-
const configuredUv = record$
|
|
2989
|
+
const configuredUv = record$2(tool) ? tool["uv"] : void 0;
|
|
2986
2990
|
const uv = name === "uv.toml" ? parsed : configuredUv;
|
|
2987
|
-
if (record$
|
|
2991
|
+
if (record$2(uv) && uv["python"] !== void 0) pin({
|
|
2988
2992
|
rule: "python-version",
|
|
2989
2993
|
value: uv["python"],
|
|
2990
2994
|
expected: policy.python,
|
|
@@ -2995,11 +2999,11 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2995
2999
|
}
|
|
2996
3000
|
if (isMiseConfigPath(file)) {
|
|
2997
3001
|
const tools = toml("runtime-manager")?.["tools"];
|
|
2998
|
-
if (record$
|
|
3002
|
+
if (record$2(tools)) for (const [tool, expected] of Object.entries(versions)) {
|
|
2999
3003
|
const configured = tools[tool];
|
|
3000
3004
|
if (configured !== void 0) pin({
|
|
3001
3005
|
rule: tool === "rust" || tool === "core:rust" ? "rust-version" : "runtime-manager",
|
|
3002
|
-
value: record$
|
|
3006
|
+
value: record$2(configured) ? configured["version"] : configured,
|
|
3003
3007
|
expected,
|
|
3004
3008
|
selector: nodeTools.has(tool) ? "node" : void 0,
|
|
3005
3009
|
label: `mise ${tool}`,
|
|
@@ -3347,7 +3351,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
3347
3351
|
return diagnostics;
|
|
3348
3352
|
}
|
|
3349
3353
|
const nodeLine = (node) => {
|
|
3350
|
-
if (!record$
|
|
3354
|
+
if (!record$2(node) || !Array.isArray(node["range"])) return 1;
|
|
3351
3355
|
const offset = node["range"].at(0);
|
|
3352
3356
|
return typeof offset === "number" ? text.slice(0, offset).split("\n").length : 1;
|
|
3353
3357
|
};
|
|
@@ -3461,7 +3465,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
3461
3465
|
const manifest = JSON.parse(content ?? "");
|
|
3462
3466
|
pin({
|
|
3463
3467
|
rule,
|
|
3464
|
-
value: record$
|
|
3468
|
+
value: record$2(manifest) ? manifest["packageManager"] : void 0,
|
|
3465
3469
|
expected: `bun@${policy.bun}`,
|
|
3466
3470
|
label: "setup-bun version file packageManager",
|
|
3467
3471
|
line
|
|
@@ -3661,10 +3665,21 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
3661
3665
|
const selector = staticRepositoryPath(value) ? path.posix.normalize(value) : void 0;
|
|
3662
3666
|
const providers = checkoutProvidersFor(selector);
|
|
3663
3667
|
const binding = selector === void 0 ? void 0 : providers.at(-1);
|
|
3664
|
-
if (selector === void 0 || unknownCheckoutDestination || binding
|
|
3665
|
-
|
|
3668
|
+
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)) {
|
|
3669
|
+
add({
|
|
3670
|
+
rule: "action-pins",
|
|
3671
|
+
line,
|
|
3672
|
+
message: "local actions require an unambiguous checkout of the current source snapshot"
|
|
3673
|
+
});
|
|
3674
|
+
return;
|
|
3675
|
+
}
|
|
3676
|
+
if (binding?.source === "delegated") onDelegated?.({
|
|
3677
|
+
path: file,
|
|
3666
3678
|
line,
|
|
3667
|
-
|
|
3679
|
+
tool: "action",
|
|
3680
|
+
selector,
|
|
3681
|
+
checkoutPath: binding.path,
|
|
3682
|
+
ref: binding.ref
|
|
3668
3683
|
});
|
|
3669
3684
|
return;
|
|
3670
3685
|
}
|
|
@@ -3962,6 +3977,54 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
3962
3977
|
return diagnostics;
|
|
3963
3978
|
};
|
|
3964
3979
|
//#endregion
|
|
3980
|
+
//#region src/toolchain-rust-fixtures.ts
|
|
3981
|
+
const record$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3982
|
+
const date = (value) => /^\d{4}-\d{2}-\d{2}$/.test(value) && Number.isFinite(Date.parse(value)) && new Date(value).toISOString().slice(0, 10) === value;
|
|
3983
|
+
/** Historical install probes do not authorize using an older compiler. */
|
|
3984
|
+
const parseRustInstallFixtures = (input) => {
|
|
3985
|
+
if (input === void 0) return [];
|
|
3986
|
+
if (!Array.isArray(input)) throw new Error("rustInstallFixtures must be an array");
|
|
3987
|
+
const identities = /* @__PURE__ */ new Set();
|
|
3988
|
+
return input.map((entry) => {
|
|
3989
|
+
if (!record$1(entry) || Object.keys(entry).length !== 5 || Object.keys(entry).some((key) => ![
|
|
3990
|
+
"path",
|
|
3991
|
+
"pin",
|
|
3992
|
+
"reason",
|
|
3993
|
+
"tracking",
|
|
3994
|
+
"expires"
|
|
3995
|
+
].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");
|
|
3996
|
+
const identity = `${entry["path"]}:${entry["pin"]}`;
|
|
3997
|
+
if (identities.has(identity)) throw new Error("Rust install fixture path/pin pairs must be unique");
|
|
3998
|
+
identities.add(identity);
|
|
3999
|
+
return {
|
|
4000
|
+
path: entry["path"],
|
|
4001
|
+
pin: entry["pin"],
|
|
4002
|
+
reason: entry["reason"],
|
|
4003
|
+
tracking: entry["tracking"],
|
|
4004
|
+
expires: entry["expires"]
|
|
4005
|
+
};
|
|
4006
|
+
});
|
|
4007
|
+
};
|
|
4008
|
+
const checkRustInstallFixtureExpiry = ({ fixtures, scope, now = /* @__PURE__ */ new Date() }) => {
|
|
4009
|
+
const errors = [];
|
|
4010
|
+
const warnings = [];
|
|
4011
|
+
for (const fixture of fixtures) {
|
|
4012
|
+
if (fixture.expires > now.toISOString().slice(0, 10)) continue;
|
|
4013
|
+
const verdict = overdueVerdict(scope, "stll-toolchain.json", (text) => {
|
|
4014
|
+
const base = JSON.parse(text);
|
|
4015
|
+
const declarations = record$1(base) ? base["rustInstallFixtures"] : void 0;
|
|
4016
|
+
return Array.isArray(declarations) && declarations.some((entry) => record$1(entry) && entry["path"] === fixture.path && entry["pin"] === fixture.pin && entry["expires"] === fixture.expires);
|
|
4017
|
+
});
|
|
4018
|
+
const message = `Rust install fixture ${fixture.path}:${fixture.pin} expired on ${fixture.expires}; remove it or renew the documented fixture boundary`;
|
|
4019
|
+
if (!verdict.fail) warnings.push(message);
|
|
4020
|
+
else errors.push(verdict.unavailable === void 0 ? message : `${message}; cannot determine whether this change adds it: ${verdict.unavailable}`);
|
|
4021
|
+
}
|
|
4022
|
+
return {
|
|
4023
|
+
errors,
|
|
4024
|
+
warnings
|
|
4025
|
+
};
|
|
4026
|
+
};
|
|
4027
|
+
//#endregion
|
|
3965
4028
|
//#region src/toolchain-guard.ts
|
|
3966
4029
|
const toolchainRules = [.../* @__PURE__ */ new Set([
|
|
3967
4030
|
...packageRules,
|
|
@@ -3976,7 +4039,7 @@ const record = (value) => typeof value === "object" && value !== null && !Array.
|
|
|
3976
4039
|
const array = (value) => Array.isArray(value);
|
|
3977
4040
|
/** Repository-owned declarations are explicit; malformed decisions fail closed. */
|
|
3978
4041
|
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");
|
|
4042
|
+
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
4043
|
const disabled = /* @__PURE__ */ new Set();
|
|
3981
4044
|
const optOuts = input["optOuts"] ?? [];
|
|
3982
4045
|
if (!array(optOuts)) throw new Error("optOuts must be an array");
|
|
@@ -3993,6 +4056,7 @@ const parseToolchainConfiguration = (input) => {
|
|
|
3993
4056
|
return {
|
|
3994
4057
|
disabled,
|
|
3995
4058
|
rustExceptions: parseRustExceptions(input["rustExceptions"]),
|
|
4059
|
+
rustInstallFixtures: parseRustInstallFixtures(input["rustInstallFixtures"]),
|
|
3996
4060
|
cloud: parseCloudSetup(input["cloud"]),
|
|
3997
4061
|
engineFloors: parseEngineFloors(input["engineFloors"]),
|
|
3998
4062
|
dynamicSelectors: parseDynamicSelectors(input["dynamicSelectors"]),
|
|
@@ -4085,11 +4149,20 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4085
4149
|
if (files["stll-toolchain.json"] !== void 0) try {
|
|
4086
4150
|
configuration = parseToolchainConfiguration(JSON.parse(files["stll-toolchain.json"]));
|
|
4087
4151
|
dynamicSelectors = configuration.dynamicSelectors;
|
|
4088
|
-
const
|
|
4152
|
+
const rustExpiry = checkRustExceptionExpiry({
|
|
4089
4153
|
exceptions: configuration.rustExceptions,
|
|
4090
4154
|
now,
|
|
4091
4155
|
scope: expiryScope
|
|
4092
4156
|
});
|
|
4157
|
+
const fixtureExpiry = checkRustInstallFixtureExpiry({
|
|
4158
|
+
fixtures: configuration.rustInstallFixtures,
|
|
4159
|
+
now,
|
|
4160
|
+
scope: expiryScope
|
|
4161
|
+
});
|
|
4162
|
+
const expiry = {
|
|
4163
|
+
errors: [...rustExpiry.errors, ...fixtureExpiry.errors],
|
|
4164
|
+
warnings: [...rustExpiry.warnings, ...fixtureExpiry.warnings]
|
|
4165
|
+
};
|
|
4093
4166
|
for (const message of expiry.errors) diagnostics.push({
|
|
4094
4167
|
rule: "configuration",
|
|
4095
4168
|
path: "stll-toolchain.json",
|
|
@@ -4223,6 +4296,7 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4223
4296
|
trackedFiles
|
|
4224
4297
|
}));
|
|
4225
4298
|
}
|
|
4299
|
+
const usedRustFixtures = /* @__PURE__ */ new Set();
|
|
4226
4300
|
for (const [file, text] of Object.entries(files)) diagnostics.push(...checkRuntimeFile({
|
|
4227
4301
|
file,
|
|
4228
4302
|
text,
|
|
@@ -4235,6 +4309,8 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4235
4309
|
},
|
|
4236
4310
|
trackedFiles,
|
|
4237
4311
|
rustExceptions: configuration.rustExceptions,
|
|
4312
|
+
rustInstallFixtures: configuration.rustInstallFixtures,
|
|
4313
|
+
onRustInstallFixture: (entry) => usedRustFixtures.add(entry),
|
|
4238
4314
|
dynamicSelectors: dynamicSelectors.filter((entry) => entry.path === file),
|
|
4239
4315
|
onDynamicSelector: (entry) => selectorMatches.set(entry, (selectorMatches.get(entry) ?? 0) + 1),
|
|
4240
4316
|
repository,
|
|
@@ -4246,6 +4322,12 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4246
4322
|
},
|
|
4247
4323
|
readFile: (target) => files[target]
|
|
4248
4324
|
}));
|
|
4325
|
+
for (const entry of configuration.rustInstallFixtures) if (!usedRustFixtures.has(entry)) diagnostics.push({
|
|
4326
|
+
rule: "configuration",
|
|
4327
|
+
path: "stll-toolchain.json",
|
|
4328
|
+
line: 1,
|
|
4329
|
+
message: `Rust install fixture ${entry.path}:${entry.pin} must match a tracked rustup toolchain install probe`
|
|
4330
|
+
});
|
|
4249
4331
|
for (const entry of engineFloors) if (!exercisedFloors.has(entry) || mismatchedFloors.has(entry)) diagnostics.push({
|
|
4250
4332
|
rule: "configuration",
|
|
4251
4333
|
path: "stll-toolchain.json",
|