@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.cjs
CHANGED
|
@@ -34,9 +34,9 @@ const repositoryBun = (floor, manifest) => {
|
|
|
34
34
|
};
|
|
35
35
|
//#endregion
|
|
36
36
|
//#region src/toolchain-consumer-checks.ts
|
|
37
|
-
const consumerRunnerVersion = "
|
|
37
|
+
const consumerRunnerVersion = "3.1.0";
|
|
38
38
|
const consumerCheckWorkflow = "stella/.github/.github/workflows/package-consumer-compat.yml";
|
|
39
|
-
const record$
|
|
39
|
+
const record$11 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
40
40
|
const repositoryDirectory = (value) => typeof value === "string" && value !== "" && !value.includes("\\") && !value.includes("${{") && !value.split("").some((character) => {
|
|
41
41
|
const code = character.charCodeAt(0);
|
|
42
42
|
return code < 32 || code === 127;
|
|
@@ -122,12 +122,12 @@ const validConsumerCron = (text) => {
|
|
|
122
122
|
});
|
|
123
123
|
};
|
|
124
124
|
const nightlySchedule = (value) => {
|
|
125
|
-
if (!record$
|
|
125
|
+
if (!record$11(value)) return false;
|
|
126
126
|
const schedule = value["schedule"];
|
|
127
127
|
if (!Array.isArray(schedule) || schedule.length === 0) return false;
|
|
128
128
|
const coveredDays = /* @__PURE__ */ new Set();
|
|
129
129
|
for (const entry of schedule) {
|
|
130
|
-
if (!record$
|
|
130
|
+
if (!record$11(entry) || typeof entry["cron"] !== "string" || !validConsumerCron(entry["cron"])) return false;
|
|
131
131
|
const fields = entry["cron"].trim().split(/\s+/);
|
|
132
132
|
const selections = cronFields.map((field, index) => {
|
|
133
133
|
const text = fields.at(index);
|
|
@@ -143,7 +143,7 @@ const parseConsumerChecks = (input) => {
|
|
|
143
143
|
if (!Array.isArray(input)) throw new Error("consumerChecks must be an array");
|
|
144
144
|
const targets = /* @__PURE__ */ new Set();
|
|
145
145
|
return input.map((entry) => {
|
|
146
|
-
if (!record$
|
|
146
|
+
if (!record$11(entry) || Object.keys(entry).some((key) => ![
|
|
147
147
|
"workflow",
|
|
148
148
|
"job",
|
|
149
149
|
"packages",
|
|
@@ -181,9 +181,9 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
|
|
|
181
181
|
});
|
|
182
182
|
if (document.errors.length > 0) throw new Error("invalid workflow YAML");
|
|
183
183
|
const source = document.toJS({ maxAliasCount: 100 });
|
|
184
|
-
if (!record$
|
|
184
|
+
if (!record$11(source) || !record$11(source["jobs"])) continue;
|
|
185
185
|
for (const [jobName, job] of Object.entries(source["jobs"])) {
|
|
186
|
-
if (!record$
|
|
186
|
+
if (!record$11(job) || typeof job["uses"] !== "string") continue;
|
|
187
187
|
const [workflowPath, ref] = job["uses"].split("@");
|
|
188
188
|
if (workflowPath?.toLowerCase() !== "stella/.github/.github/workflows/package-consumer-compat.yml") continue;
|
|
189
189
|
const declaration = declarations.find((entry) => entry.workflow === workflow && entry.job === jobName);
|
|
@@ -197,7 +197,7 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
|
|
|
197
197
|
const approved = Object.entries(policy.actions).find(([name]) => name.toLowerCase() === consumerCheckWorkflow)?.[1];
|
|
198
198
|
if (job["uses"].split("@").length !== 2 || ref === void 0 || !/^[a-f0-9]{40}$/.test(ref) || approved === void 0 || ref !== approved.sha || "steps" in job || "runs-on" in job) throw new Error(`${workflow}:${jobName} must invoke the approved immutable consumer workflow as a reusable job`);
|
|
199
199
|
const inputs = job["with"];
|
|
200
|
-
if (!record$
|
|
200
|
+
if (!record$11(inputs)) throw new Error(`${workflow}:${jobName} requires consumer inputs`);
|
|
201
201
|
const names = /* @__PURE__ */ new Set();
|
|
202
202
|
for (const key of Object.keys(inputs)) {
|
|
203
203
|
const normalized = key.toLowerCase();
|
|
@@ -267,7 +267,7 @@ const locatorValidators = {
|
|
|
267
267
|
"bun-source": /^(?:jobs\.[\w-]+|runs)\.steps\.[\w-]+$/
|
|
268
268
|
};
|
|
269
269
|
const knownKind = (value) => typeof value === "string" && Object.hasOwn(locatorValidators, value);
|
|
270
|
-
const record$
|
|
270
|
+
const record$10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
271
271
|
/** One reviewed declaration identifies one unresolved selector, never a rule. */
|
|
272
272
|
const parseDynamicSelectors = (input) => {
|
|
273
273
|
if (input === void 0) return [];
|
|
@@ -275,7 +275,7 @@ const parseDynamicSelectors = (input) => {
|
|
|
275
275
|
const result = [];
|
|
276
276
|
const keys = /* @__PURE__ */ new Set();
|
|
277
277
|
for (const entry of input) {
|
|
278
|
-
if (!record$
|
|
278
|
+
if (!record$10(entry) || Object.keys(entry).some((key) => ![
|
|
279
279
|
"path",
|
|
280
280
|
"kind",
|
|
281
281
|
"reason",
|
|
@@ -311,7 +311,7 @@ const parseDynamicSelectors = (input) => {
|
|
|
311
311
|
};
|
|
312
312
|
//#endregion
|
|
313
313
|
//#region src/toolchain-engine-floors.ts
|
|
314
|
-
const record$
|
|
314
|
+
const record$9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
315
315
|
const repositoryPath = (value) => typeof value === "string" && value !== "" && !/^[A-Za-z]:/.test(value) && !/[\\\s]/.test(value) && !Array.from(value).some((character) => character.charCodeAt(0) < 32) && value.split("/").every((part) => part !== "" && part !== "." && part !== "..");
|
|
316
316
|
const parseEngineFloors = (input) => {
|
|
317
317
|
if (input === void 0) return [];
|
|
@@ -319,7 +319,7 @@ const parseEngineFloors = (input) => {
|
|
|
319
319
|
const result = [];
|
|
320
320
|
const jobs = /* @__PURE__ */ new Set();
|
|
321
321
|
for (const entry of input) {
|
|
322
|
-
if (!record$
|
|
322
|
+
if (!record$9(entry) || Object.keys(entry).some((key) => ![
|
|
323
323
|
"package",
|
|
324
324
|
"workflow",
|
|
325
325
|
"job"
|
|
@@ -340,16 +340,16 @@ const resolveEngineFloor = (entry, files) => {
|
|
|
340
340
|
const workflowText = files[entry.workflow];
|
|
341
341
|
if (manifestText === void 0 || workflowText === void 0) throw new Error("engine floor package and workflow must be tracked");
|
|
342
342
|
const manifest = JSON.parse(manifestText);
|
|
343
|
-
if (!record$
|
|
343
|
+
if (!record$9(manifest) || manifest["private"] === true || typeof manifest["name"] !== "string" || manifest["name"].trim() === "" || !record$9(manifest["engines"]) || typeof manifest["engines"]["node"] !== "string") throw new Error("engine floor package must be published and declare engines.node");
|
|
344
344
|
const range = manifest["engines"]["node"];
|
|
345
345
|
const minimum = (0, semver.validRange)(range) === null ? null : (0, semver.minVersion)(range);
|
|
346
346
|
if (minimum === null || minimum.prerelease.length !== 0) throw new Error("engine floor engines.node must declare a stable minimum");
|
|
347
347
|
const document = (0, yaml.parseDocument)(workflowText, { merge: true });
|
|
348
348
|
if (document.errors.length !== 0) throw new Error("engine floor workflow must be valid YAML");
|
|
349
349
|
const workflow = document.toJS({ maxAliasCount: 100 });
|
|
350
|
-
if (!record$
|
|
350
|
+
if (!record$9(workflow) || !record$9(workflow["jobs"])) throw new Error("engine floor job must exist in its tracked workflow");
|
|
351
351
|
const job = workflow["jobs"][entry.job];
|
|
352
|
-
if (!record$
|
|
352
|
+
if (!record$9(job)) throw new Error("engine floor job must exist in its tracked workflow");
|
|
353
353
|
if (!Array.isArray(job["steps"]) || Object.hasOwn(job, "uses")) throw new Error("engine floor job must declare steps and cannot reuse a workflow");
|
|
354
354
|
return {
|
|
355
355
|
...entry,
|
|
@@ -379,7 +379,7 @@ const installSections = [
|
|
|
379
379
|
"optionalDependencies"
|
|
380
380
|
];
|
|
381
381
|
const allowlistFile = "stll-toolchain.json";
|
|
382
|
-
const record$
|
|
382
|
+
const record$8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
383
383
|
/** `npm:typescript@7.0.2` installs a compiler under another name; returns its version. */
|
|
384
384
|
const typescriptAliasVersion = (spec) => {
|
|
385
385
|
if (typeof spec !== "string") return void 0;
|
|
@@ -409,7 +409,7 @@ const parseTypescriptAllowlist = (input) => {
|
|
|
409
409
|
return input.map((entry) => {
|
|
410
410
|
const shown = JSON.stringify(entry);
|
|
411
411
|
const text = (key) => {
|
|
412
|
-
const value = record$
|
|
412
|
+
const value = record$8(entry) ? entry[key] : void 0;
|
|
413
413
|
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
414
414
|
};
|
|
415
415
|
const workspace = text("workspace");
|
|
@@ -417,7 +417,7 @@ const parseTypescriptAllowlist = (input) => {
|
|
|
417
417
|
const spec = text("spec");
|
|
418
418
|
const dependency = text("dependency");
|
|
419
419
|
const reason = text("reason");
|
|
420
|
-
if (!record$
|
|
420
|
+
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}`);
|
|
421
421
|
if (workspace !== "." && (node_path.default.posix.normalize(workspace) !== workspace || workspace.startsWith("/") || workspace.endsWith("/") || workspace === ".." || workspace.startsWith("../"))) throw new Error(`typescriptAllowlist entry ${shown}: workspace must be "." for the root or a normalized relative package directory such as "apps/web"`);
|
|
422
422
|
if (!exactTypescriptSpec(spec)) throw new Error(`typescriptAllowlist entry ${shown}: spec must be the exact spec the manifest resolves to, such as "6.0.3" or "npm:typescript@6.0.3", not a range or catalog reference`);
|
|
423
423
|
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>`);
|
|
@@ -993,10 +993,10 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
993
993
|
const document = pnpm ? (0, yaml.parseDocument)(text, { lineCounter }) : void 0;
|
|
994
994
|
if (document !== void 0 && document.errors.length > 0) throw new Error("invalid YAML");
|
|
995
995
|
const json = document === void 0 ? JSON.parse(text) : document.toJS();
|
|
996
|
-
if (!record$
|
|
996
|
+
if (!record$8(json)) throw new Error("manifest must be an object");
|
|
997
997
|
if (pnpm) {
|
|
998
|
-
if (json["catalog"] !== void 0 && !record$
|
|
999
|
-
if (json["catalogs"] !== void 0 && (!record$
|
|
998
|
+
if (json["catalog"] !== void 0 && !record$8(json["catalog"])) throw new Error("catalog must be a mapping");
|
|
999
|
+
if (json["catalogs"] !== void 0 && (!record$8(json["catalogs"]) || Object.values(json["catalogs"]).some((catalog) => !record$8(catalog)))) throw new Error("named catalogs must be mappings");
|
|
1000
1000
|
if (json["packages"] !== void 0 && (!Array.isArray(json["packages"]) || !json["packages"].every((pattern) => typeof pattern === "string"))) throw new Error("workspace packages must be patterns");
|
|
1001
1001
|
}
|
|
1002
1002
|
manifests.set(file, json);
|
|
@@ -1010,13 +1010,13 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1010
1010
|
catalog: json["overrides"],
|
|
1011
1011
|
prefix: ["overrides"]
|
|
1012
1012
|
},
|
|
1013
|
-
...record$
|
|
1013
|
+
...record$8(json["catalogs"]) ? Object.entries(json["catalogs"]).map(([name, catalog]) => ({
|
|
1014
1014
|
catalog,
|
|
1015
1015
|
prefix: ["catalogs", name]
|
|
1016
1016
|
})) : []
|
|
1017
1017
|
];
|
|
1018
1018
|
for (const { catalog, prefix } of catalogEntries) {
|
|
1019
|
-
if (!record$
|
|
1019
|
+
if (!record$8(catalog)) continue;
|
|
1020
1020
|
for (const [name, value] of Object.entries(catalog)) {
|
|
1021
1021
|
const node = document.getIn([...prefix, name], true);
|
|
1022
1022
|
if (!(0, yaml.isNode)(node) || node.range === void 0 || node.range === null) continue;
|
|
@@ -1035,7 +1035,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1035
1035
|
});
|
|
1036
1036
|
}
|
|
1037
1037
|
}
|
|
1038
|
-
const catalogSources = (json) => [json, ...record$
|
|
1038
|
+
const catalogSources = (json) => [json, ...record$8(json["workspaces"]) ? [json["workspaces"]] : []];
|
|
1039
1039
|
const pnpmWorkspaceFor = (file) => {
|
|
1040
1040
|
let directory = node_path.default.posix.dirname(file);
|
|
1041
1041
|
while (true) {
|
|
@@ -1058,8 +1058,8 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1058
1058
|
patterns: workspace["packages"],
|
|
1059
1059
|
rootMembership: "implicit"
|
|
1060
1060
|
})) return void 0;
|
|
1061
|
-
if (name === "" || name === "default") return workspace["catalog"] ?? (record$
|
|
1062
|
-
return record$
|
|
1061
|
+
if (name === "" || name === "default") return workspace["catalog"] ?? (record$8(workspace["catalogs"]) ? workspace["catalogs"]["default"] : void 0);
|
|
1062
|
+
return record$8(workspace["catalogs"]) ? workspace["catalogs"][name] : void 0;
|
|
1063
1063
|
}
|
|
1064
1064
|
let directory = node_path.default.posix.dirname(file);
|
|
1065
1065
|
while (true) {
|
|
@@ -1067,7 +1067,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1067
1067
|
if (manifest !== void 0) {
|
|
1068
1068
|
for (const source of catalogSources(manifest)) {
|
|
1069
1069
|
if (name === "" && source["catalog"] !== void 0) return source["catalog"];
|
|
1070
|
-
if (record$
|
|
1070
|
+
if (record$8(source["catalogs"]) && source["catalogs"][name] !== void 0) return source["catalogs"][name];
|
|
1071
1071
|
}
|
|
1072
1072
|
if (manifest["workspaces"] !== void 0) return void 0;
|
|
1073
1073
|
}
|
|
@@ -1085,7 +1085,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1085
1085
|
};
|
|
1086
1086
|
visited.add(value);
|
|
1087
1087
|
const catalog = catalogFor(file, value.slice(8));
|
|
1088
|
-
if (!record$
|
|
1088
|
+
if (!record$8(catalog)) return {
|
|
1089
1089
|
value: void 0,
|
|
1090
1090
|
catalogs
|
|
1091
1091
|
};
|
|
@@ -1110,7 +1110,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1110
1110
|
const workspaces = manifests.get(node_path.default.posix.join(directory, "package.json"))?.["workspaces"];
|
|
1111
1111
|
if (workspaces !== void 0) return {
|
|
1112
1112
|
directory,
|
|
1113
|
-
patterns: record$
|
|
1113
|
+
patterns: record$8(workspaces) ? workspaces["packages"] : workspaces,
|
|
1114
1114
|
source: "json"
|
|
1115
1115
|
};
|
|
1116
1116
|
if (directory === ".") return void 0;
|
|
@@ -1144,7 +1144,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1144
1144
|
if (json === void 0) continue;
|
|
1145
1145
|
for (const section of installSections) {
|
|
1146
1146
|
const entries = json[section];
|
|
1147
|
-
if (!record$
|
|
1147
|
+
if (!record$8(entries) || !Object.hasOwn(entries, entry.name)) continue;
|
|
1148
1148
|
const { value, catalogs } = resolveThrough(file, entry.name, entries[entry.name]);
|
|
1149
1149
|
if (value !== entry.spec) continue;
|
|
1150
1150
|
for (const catalog of catalogs) {
|
|
@@ -1189,7 +1189,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1189
1189
|
message: `packageManager Bun version must be ${policy.bun} (one exact release at or above the shared minimum, the same in every manifest), found ${JSON.stringify(manager)}`
|
|
1190
1190
|
});
|
|
1191
1191
|
const checkEntries = (entries, scope, location) => {
|
|
1192
|
-
if (!record$
|
|
1192
|
+
if (!record$8(entries)) return;
|
|
1193
1193
|
for (const [name, raw] of Object.entries(entries)) {
|
|
1194
1194
|
const value = resolve(file, name, raw);
|
|
1195
1195
|
if (isTypescriptInstall(name, value)) {
|
|
@@ -1229,13 +1229,13 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1229
1229
|
}
|
|
1230
1230
|
};
|
|
1231
1231
|
for (const key of installSections) checkEntries(json[key], "install");
|
|
1232
|
-
if (record$
|
|
1232
|
+
if (record$8(json["peerDependencies"])) for (const [name, value] of Object.entries(json["peerDependencies"])) {
|
|
1233
1233
|
const resolved = resolve(file, name, value);
|
|
1234
1234
|
if (isTypescriptInstall(name, resolved) && !peerRange(resolved)) checkEntries({ [name]: value }, "peer");
|
|
1235
1235
|
}
|
|
1236
1236
|
for (const source of catalogSources(json)) {
|
|
1237
1237
|
checkEntries(source["catalog"], "catalog");
|
|
1238
|
-
if (!record$
|
|
1238
|
+
if (!record$8(source["catalogs"])) continue;
|
|
1239
1239
|
for (const catalog of Object.values(source["catalogs"])) checkEntries(catalog, "catalog");
|
|
1240
1240
|
}
|
|
1241
1241
|
const ownedNames = /* @__PURE__ */ new Set([...pins.keys(), ...typescriptPackages]);
|
|
@@ -1247,11 +1247,11 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1247
1247
|
"optionalDependencies"
|
|
1248
1248
|
]) {
|
|
1249
1249
|
const entries = resolutionRoot[section];
|
|
1250
|
-
if (!record$
|
|
1250
|
+
if (!record$8(entries)) continue;
|
|
1251
1251
|
for (const [name, value] of Object.entries(entries)) resolutionDependencies[name] = resolve(file, name, value);
|
|
1252
1252
|
}
|
|
1253
1253
|
const checkResolutionMap = (entries, parent) => {
|
|
1254
|
-
if (!record$
|
|
1254
|
+
if (!record$8(entries)) return;
|
|
1255
1255
|
for (const [selector, replacement] of Object.entries(entries)) {
|
|
1256
1256
|
const target = selector === "." ? parent : selector;
|
|
1257
1257
|
if (target === void 0) continue;
|
|
@@ -1270,7 +1270,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1270
1270
|
});
|
|
1271
1271
|
continue;
|
|
1272
1272
|
}
|
|
1273
|
-
if (record$
|
|
1273
|
+
if (record$8(replacement)) {
|
|
1274
1274
|
checkResolutionMap(replacement, target);
|
|
1275
1275
|
continue;
|
|
1276
1276
|
}
|
|
@@ -1290,8 +1290,8 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1290
1290
|
};
|
|
1291
1291
|
checkResolutionMap(json["overrides"]);
|
|
1292
1292
|
checkResolutionMap(json["resolutions"]);
|
|
1293
|
-
if (record$
|
|
1294
|
-
const command = record$
|
|
1293
|
+
if (record$8(json["pnpm"])) checkResolutionMap(json["pnpm"]["overrides"]);
|
|
1294
|
+
const command = record$8(json["scripts"]) ? json["scripts"]["typecheck"] : void 0;
|
|
1295
1295
|
if (typeof command === "string") for (const words of compilerCommandSegments(command)) {
|
|
1296
1296
|
if (!invokesCompiler(words)) continue;
|
|
1297
1297
|
const normalized = words.join(" ");
|
|
@@ -1305,7 +1305,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1305
1305
|
});
|
|
1306
1306
|
break;
|
|
1307
1307
|
}
|
|
1308
|
-
if (record$
|
|
1308
|
+
if (record$8(json["peerDependencies"])) for (const [name, raw] of Object.entries(json["peerDependencies"])) {
|
|
1309
1309
|
if (!typescriptPackages.has(name)) continue;
|
|
1310
1310
|
const range = resolve(file, name, raw);
|
|
1311
1311
|
if (!peerRange(range)) continue;
|
|
@@ -1329,7 +1329,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1329
1329
|
message: `${name} peer support range must include ${release}, found ${range}`
|
|
1330
1330
|
});
|
|
1331
1331
|
}
|
|
1332
|
-
if (record$
|
|
1332
|
+
if (record$8(json["engines"]) && json["engines"]["node"] !== void 0 && (typeof json["engines"]["node"] !== "string" || !require_toolchain_inputs.nodeSupportRangeMatches(policy.node, json["engines"]["node"]))) add({
|
|
1333
1333
|
file,
|
|
1334
1334
|
rule: "node-engine",
|
|
1335
1335
|
key: "node",
|
|
@@ -1343,7 +1343,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1343
1343
|
if (json === void 0) return `${file} is missing or invalid; remove the entry`;
|
|
1344
1344
|
const declares = (name) => installSections.some((section) => {
|
|
1345
1345
|
const entries = json[section];
|
|
1346
|
-
return record$
|
|
1346
|
+
return record$8(entries) && Object.hasOwn(entries, name);
|
|
1347
1347
|
});
|
|
1348
1348
|
if (!declares(entry.name)) return `${file} no longer declares ${entry.name} in dependencies, devDependencies or optionalDependencies; remove the entry`;
|
|
1349
1349
|
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`;
|
|
@@ -1382,9 +1382,9 @@ const extensionKinds = new Map(Object.entries({
|
|
|
1382
1382
|
const scriptSourceKind = (file) => extensionKinds.get((0, node_path.extname)(file));
|
|
1383
1383
|
//#endregion
|
|
1384
1384
|
//#region src/toolchain-script-lifecycle.ts
|
|
1385
|
-
const record$
|
|
1385
|
+
const record$7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1386
1386
|
const declaredManager = (value) => typeof value === "string" ? /^(bun|npm|pnpm|yarn)@(\d+\.\d+\.\d+)(?:\+\S+)?$/u.exec(value) : null;
|
|
1387
|
-
const rootScriptManager = (manifest, context) => declaredManager(context.packageManager ?? (record$
|
|
1387
|
+
const rootScriptManager = (manifest, context) => declaredManager(context.packageManager ?? (record$7(manifest) ? manifest["packageManager"] : void 0))?.at(1);
|
|
1388
1388
|
const npmHooksEnabled = (file) => {
|
|
1389
1389
|
if (file?.type === "unknown") throw new Error("npm lifecycle configuration must be a tracked regular file");
|
|
1390
1390
|
if (file?.type !== "tracked") return true;
|
|
@@ -1415,12 +1415,12 @@ const yamlSettings = (file, manager) => {
|
|
|
1415
1415
|
} catch {
|
|
1416
1416
|
throw new Error(`Cannot parse ${manager} lifecycle configuration`);
|
|
1417
1417
|
}
|
|
1418
|
-
if (!record$
|
|
1418
|
+
if (!record$7(settings)) throw new Error(`${manager} lifecycle configuration must be a mapping`);
|
|
1419
1419
|
return settings;
|
|
1420
1420
|
};
|
|
1421
1421
|
const pnpmHooksEnabled = (manifest, context) => {
|
|
1422
1422
|
const declared = declaredManager(context.packageManager ?? manifest["packageManager"]);
|
|
1423
|
-
const dependency = [manifest["dependencies"], manifest["devDependencies"]].flatMap((field) => record$
|
|
1423
|
+
const dependency = [manifest["dependencies"], manifest["devDependencies"]].flatMap((field) => record$7(field) ? [field["pnpm"]] : []).find((value) => typeof value === "string");
|
|
1424
1424
|
if ((declared?.at(1) === "pnpm" ? declared.at(2) : dependency) !== "12.9.1") throw new Error("pnpm lifecycle discovery requires declared pnpm@12.9.1");
|
|
1425
1425
|
const enabled = yamlSettings(context.pnpmWorkspace, "pnpm")["enablePrePostScripts"];
|
|
1426
1426
|
if (enabled === void 0) return true;
|
|
@@ -1464,7 +1464,7 @@ const scriptLifecycleStages = ({ name, manager, manifest, scripts, context }) =>
|
|
|
1464
1464
|
};
|
|
1465
1465
|
//#endregion
|
|
1466
1466
|
//#region src/toolchain-lint-command.ts
|
|
1467
|
-
const record$
|
|
1467
|
+
const record$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1468
1468
|
const scriptManagers = /* @__PURE__ */ new Set([
|
|
1469
1469
|
"bun",
|
|
1470
1470
|
"npm",
|
|
@@ -1581,7 +1581,7 @@ const lintInventoryCommands = (manifest, forceEnabled = false, context = {}) =>
|
|
|
1581
1581
|
const errors = [];
|
|
1582
1582
|
const workspaceTasks = /* @__PURE__ */ new Set();
|
|
1583
1583
|
const filteredBuilds = /* @__PURE__ */ new Set();
|
|
1584
|
-
const declaresEngine = record$
|
|
1584
|
+
const declaresEngine = record$6(manifest) && [manifest["dependencies"], manifest["devDependencies"]].some((dependencies) => record$6(dependencies) && "oxlint-tsgolint" in dependencies);
|
|
1585
1585
|
const enabled = forceEnabled || declaresEngine;
|
|
1586
1586
|
if (!enabled) return {
|
|
1587
1587
|
commands,
|
|
@@ -1589,14 +1589,14 @@ const lintInventoryCommands = (manifest, forceEnabled = false, context = {}) =>
|
|
|
1589
1589
|
enabled,
|
|
1590
1590
|
workspaceTask: false
|
|
1591
1591
|
};
|
|
1592
|
-
if (!record$
|
|
1592
|
+
if (!record$6(manifest)) return {
|
|
1593
1593
|
commands,
|
|
1594
1594
|
errors: ["lint inventory requires an object package manifest"],
|
|
1595
1595
|
enabled,
|
|
1596
1596
|
workspaceTask: false
|
|
1597
1597
|
};
|
|
1598
1598
|
const scripts = manifest["scripts"];
|
|
1599
|
-
if (!record$
|
|
1599
|
+
if (!record$6(scripts)) return {
|
|
1600
1600
|
commands,
|
|
1601
1601
|
errors: ["type-aware linting requires a root lint script"],
|
|
1602
1602
|
enabled,
|
|
@@ -1738,7 +1738,7 @@ const checkProjectOwnership = ({ trackedFiles, lintedFiles, projectFiles }) => [
|
|
|
1738
1738
|
line: 1,
|
|
1739
1739
|
message: "type-aware lint would use an inferred project; include this file in a declared tsconfig project (for Node scripts, add scripts/tsconfig.json extending @stll/typescript-config/node.json; see the Node scripts recipe in stella/tooling README)"
|
|
1740
1740
|
}));
|
|
1741
|
-
const record$
|
|
1741
|
+
const record$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1742
1742
|
const stringList = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
1743
1743
|
const readCommand = ({ root, executable, args, runtime }) => {
|
|
1744
1744
|
const result = (0, node_child_process.spawnSync)(runtime, [
|
|
@@ -1782,7 +1782,7 @@ const resolveProjectApi = (root) => {
|
|
|
1782
1782
|
for (const base of [require("url").pathToFileURL(__filename).href, node_path.default.join(root, "package.json")]) try {
|
|
1783
1783
|
return (0, node_url.pathToFileURL)((0, node_module.createRequire)(base).resolve("tsrs/unstable/async")).href;
|
|
1784
1784
|
} catch (error) {
|
|
1785
|
-
const code = record$
|
|
1785
|
+
const code = record$5(error) ? error["code"] : void 0;
|
|
1786
1786
|
if (code !== "MODULE_NOT_FOUND" && code !== "ERR_MODULE_NOT_FOUND") throw error;
|
|
1787
1787
|
}
|
|
1788
1788
|
throw new Error("configuration discovery needs tsrs: reinstall @stll/oxlint-config, which depends on it");
|
|
@@ -1805,7 +1805,7 @@ const readProjectConfig = (root, config) => {
|
|
|
1805
1805
|
if (result.error !== void 0) throw result.error;
|
|
1806
1806
|
if (result.status !== 0) throw new Error(`configuration discovery failed: ${(result.stderr + result.stdout).trim()}`);
|
|
1807
1807
|
const parsed = JSON.parse(result.stdout);
|
|
1808
|
-
if (!record$
|
|
1808
|
+
if (!record$5(parsed) || !stringList(parsed["files"]) || !stringList(parsed["references"]) || typeof parsed["allowJs"] !== "boolean" || typeof parsed["checkJs"] !== "boolean") throw new Error("compiler configuration must list files and references");
|
|
1809
1809
|
return {
|
|
1810
1810
|
files: parsed["files"],
|
|
1811
1811
|
references: parsed["references"],
|
|
@@ -1830,7 +1830,7 @@ const lifecycleFile = (root, file, tracked) => {
|
|
|
1830
1830
|
};
|
|
1831
1831
|
/** Inventory and config expansion only: neither subprocess lints or typechecks. */
|
|
1832
1832
|
const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
1833
|
-
const packageManager = record$
|
|
1833
|
+
const packageManager = record$5(manifest) ? manifest["packageManager"] : void 0;
|
|
1834
1834
|
const managerUsesPnpmWorkspace = packageManager === void 0 || typeof packageManager === "string" && packageManager.startsWith("pnpm@");
|
|
1835
1835
|
const discovered = /* @__PURE__ */ new Map();
|
|
1836
1836
|
const workspaceMembers = (pnpmMembership) => {
|
|
@@ -1866,7 +1866,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
1866
1866
|
return nestedLifecycle;
|
|
1867
1867
|
};
|
|
1868
1868
|
const lifecycle = {
|
|
1869
|
-
packageManager: record$
|
|
1869
|
+
packageManager: record$5(manifest) ? manifest["packageManager"] : void 0,
|
|
1870
1870
|
npmrc: lifecycleFile(root, ".npmrc", trackedFiles),
|
|
1871
1871
|
pnpmWorkspace: lifecycleFile(root, "pnpm-workspace.yaml", trackedFiles),
|
|
1872
1872
|
yarnrc: lifecycleFile(root, ".yarnrc.yml", trackedFiles),
|
|
@@ -1877,7 +1877,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
1877
1877
|
if (member === void 0) throw new Error("Missing selected workspace");
|
|
1878
1878
|
const [directory, pkg] = member;
|
|
1879
1879
|
const scripts = pkg["scripts"];
|
|
1880
|
-
if (!record$
|
|
1880
|
+
if (!record$5(scripts) || typeof scripts["build"] !== "string") throw new Error("Filtered Bun build requires a literal build script");
|
|
1881
1881
|
if (["prebuild", "postbuild"].some((name) => scripts[name] !== void 0 && scripts[name] !== "")) throw new Error("Filtered Bun build lifecycle hooks are unsupported for lint inventory; invoke lint scopes explicitly");
|
|
1882
1882
|
if (activeBuilds.has(directory)) throw new Error("Cyclic filtered Bun build delegation");
|
|
1883
1883
|
activeBuilds.add(directory);
|
|
@@ -1919,7 +1919,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
1919
1919
|
for (const [directory, workspace] of workspaceMembers(managerUsesPnpmWorkspace)) {
|
|
1920
1920
|
if (directory === ".") continue;
|
|
1921
1921
|
const file = `${directory}/package.json`;
|
|
1922
|
-
if (!record$
|
|
1922
|
+
if (!record$5(workspace["scripts"]) || workspace["scripts"]["lint"] === void 0) continue;
|
|
1923
1923
|
const nestedLifecycle = workspaceLifecycle(directory);
|
|
1924
1924
|
const nested = lintInventoryCommands(workspace, true, nestedLifecycle);
|
|
1925
1925
|
if (nested.workspaceTask) throw new Error(`nested workspace lint delegation is unsupported: ${file}`);
|
|
@@ -2028,7 +2028,7 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
2028
2028
|
};
|
|
2029
2029
|
//#endregion
|
|
2030
2030
|
//#region src/toolchain-rust.ts
|
|
2031
|
-
const record$
|
|
2031
|
+
const record$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2032
2032
|
/**
|
|
2033
2033
|
* Exceptions authorize one immutable release, never a channel or an entire
|
|
2034
2034
|
* rule. Expiry is scoped separately by checkRustExceptionExpiry.
|
|
@@ -2038,7 +2038,7 @@ const parseRustExceptions = (input) => {
|
|
|
2038
2038
|
if (!Array.isArray(input)) throw new Error("rustExceptions must be an array");
|
|
2039
2039
|
const pins = /* @__PURE__ */ new Set();
|
|
2040
2040
|
return input.map((entry) => {
|
|
2041
|
-
if (!record$
|
|
2041
|
+
if (!record$4(entry) || Object.keys(entry).length !== 4 || Object.keys(entry).some((key) => ![
|
|
2042
2042
|
"pin",
|
|
2043
2043
|
"reason",
|
|
2044
2044
|
"tracking",
|
|
@@ -2056,8 +2056,8 @@ const parseRustExceptions = (input) => {
|
|
|
2056
2056
|
/** Whether the base stll-toolchain.json declares this exception with the same expiry. */
|
|
2057
2057
|
const declaresException = (baseText, { pin, expires }) => {
|
|
2058
2058
|
const base = JSON.parse(baseText);
|
|
2059
|
-
const exceptions = record$
|
|
2060
|
-
return Array.isArray(exceptions) && exceptions.some((entry) => record$
|
|
2059
|
+
const exceptions = record$4(base) ? base["rustExceptions"] : void 0;
|
|
2060
|
+
return Array.isArray(exceptions) && exceptions.some((entry) => record$4(entry) && entry["pin"] === pin && entry["expires"] === expires);
|
|
2061
2061
|
};
|
|
2062
2062
|
/** An exception expires on its date; the scope decides whether that fails this run. */
|
|
2063
2063
|
const checkRustExceptionExpiry = ({ exceptions, now = /* @__PURE__ */ new Date(), scope }) => {
|
|
@@ -2195,7 +2195,9 @@ const checkRustReferences = (options) => {
|
|
|
2195
2195
|
}
|
|
2196
2196
|
if (token.value.startsWith("-")) continue;
|
|
2197
2197
|
selected++;
|
|
2198
|
-
|
|
2198
|
+
const installFixture = options.type === "file" && require_toolchain_inputs.isShellScript(file, text) && subcommand === "toolchain" && invocation.at(1)?.value === "install" && !invocation.some((word) => word.value === "--default") ? options.installFixtures?.find((entry) => entry.path === file && entry.pin === token.value) : void 0;
|
|
2199
|
+
if (installFixture !== void 0) options.onInstallFixture?.(installFixture);
|
|
2200
|
+
else select(token.value, "rustup toolchain", token.line);
|
|
2199
2201
|
if (firstSelectorOnly) break;
|
|
2200
2202
|
}
|
|
2201
2203
|
if (selected === 0 && requiresSelector) select(void 0, "rustup toolchain", firstLine);
|
|
@@ -2216,7 +2218,7 @@ const checkRustReferences = (options) => {
|
|
|
2216
2218
|
if (require_toolchain_inputs.isShellScript(file, text)) shell(text);
|
|
2217
2219
|
if (/(?:^|\/)package\.json$/.test(file)) try {
|
|
2218
2220
|
const object = JSON.parse(text);
|
|
2219
|
-
if (record$
|
|
2221
|
+
if (record$4(object) && record$4(object["scripts"])) {
|
|
2220
2222
|
for (const [name, script] of Object.entries(object["scripts"])) if (typeof script === "string") shell(script, lineOf(`"${name}"`));
|
|
2221
2223
|
}
|
|
2222
2224
|
} catch {}
|
|
@@ -2227,7 +2229,7 @@ const checkRustReferences = (options) => {
|
|
|
2227
2229
|
const version = tag === void 0 ? void 0 : require_toolchain_dependabot.rustImageVersion(tag);
|
|
2228
2230
|
select(version, "Rust service image", lineOf(value));
|
|
2229
2231
|
} else if (Array.isArray(value)) value.forEach(images);
|
|
2230
|
-
else if (record$
|
|
2232
|
+
else if (record$4(value)) {
|
|
2231
2233
|
for (const [key, entry] of Object.entries(value)) if (key !== "name") images(entry);
|
|
2232
2234
|
}
|
|
2233
2235
|
};
|
|
@@ -2251,13 +2253,13 @@ const checkRustReferences = (options) => {
|
|
|
2251
2253
|
value.forEach(visit);
|
|
2252
2254
|
return;
|
|
2253
2255
|
}
|
|
2254
|
-
if (!record$
|
|
2255
|
-
if (record$
|
|
2256
|
+
if (!record$4(value)) return;
|
|
2257
|
+
if (record$4(value["env"]) && value["env"]["RUSTUP_TOOLCHAIN"] !== void 0) select(value["env"]["RUSTUP_TOOLCHAIN"], "RUSTUP_TOOLCHAIN", lineOf("RUSTUP_TOOLCHAIN"));
|
|
2256
2258
|
if (typeof value["run"] === "string") shell(value["run"], lineOf("run:"));
|
|
2257
2259
|
const uses = value["uses"];
|
|
2258
2260
|
if (typeof uses === "string") {
|
|
2259
2261
|
const action = uses.split("@").at(0)?.toLowerCase();
|
|
2260
|
-
const inputs = record$
|
|
2262
|
+
const inputs = record$4(value["with"]) ? value["with"] : {};
|
|
2261
2263
|
if (action === "dtolnay/rust-toolchain" || action === "actions-rust-lang/setup-rust-toolchain") {
|
|
2262
2264
|
const entries = Object.entries(inputs).filter(([key]) => key.toLowerCase() === "toolchain");
|
|
2263
2265
|
if (entries.length !== 1) select(void 0, `${action} requires an explicit exact toolchain input`, lineOf(uses));
|
|
@@ -2271,8 +2273,8 @@ const checkRustReferences = (options) => {
|
|
|
2271
2273
|
}
|
|
2272
2274
|
}
|
|
2273
2275
|
for (const [key, child] of Object.entries(value)) {
|
|
2274
|
-
if (key === "workflow_call" && record$
|
|
2275
|
-
for (const [input, declaration] of Object.entries(child["inputs"])) if (["toolchain", "rust-toolchain"].includes(input) && record$
|
|
2276
|
+
if (key === "workflow_call" && record$4(child) && record$4(child["inputs"])) {
|
|
2277
|
+
for (const [input, declaration] of Object.entries(child["inputs"])) if (["toolchain", "rust-toolchain"].includes(input) && record$4(declaration) && declaration["default"] !== void 0) select(declaration["default"], `reusable workflow default ${input}`, lineOf("default:"));
|
|
2276
2278
|
}
|
|
2277
2279
|
visit(child);
|
|
2278
2280
|
}
|
|
@@ -2283,7 +2285,7 @@ const checkRustReferences = (options) => {
|
|
|
2283
2285
|
};
|
|
2284
2286
|
//#endregion
|
|
2285
2287
|
//#region src/toolchain-workflow-trust.ts
|
|
2286
|
-
const record$
|
|
2288
|
+
const record$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2287
2289
|
const booleanParts = (source, operator) => {
|
|
2288
2290
|
const parts = [];
|
|
2289
2291
|
let depth = 0;
|
|
@@ -2533,7 +2535,7 @@ const safeAction = (uses, inputs) => {
|
|
|
2533
2535
|
const approved = Object.entries(workflowTrustBootstrapCatalog.actions).find(([name]) => name === repository);
|
|
2534
2536
|
if (approved === void 0) return false;
|
|
2535
2537
|
if (inputs === void 0) return repository === "actions/checkout";
|
|
2536
|
-
if (!record$
|
|
2538
|
+
if (!record$3(inputs)) return false;
|
|
2537
2539
|
if (repository !== "actions/checkout") {
|
|
2538
2540
|
const version = inputs["bun-version"];
|
|
2539
2541
|
if (typeof version !== "string" || !/^v?[0-9]+(?:\.(?:[0-9]+|x)){0,2}$/u.test(version)) return false;
|
|
@@ -2546,7 +2548,7 @@ const safeAction = (uses, inputs) => {
|
|
|
2546
2548
|
].includes(typeof value) && (name !== "submodules" || value === false || value === "false"));
|
|
2547
2549
|
};
|
|
2548
2550
|
const safeShell = (shell) => shell === void 0 || shell === "bash" || shell === "sh";
|
|
2549
|
-
const safeEnvironment = (value) => value === void 0 || record$
|
|
2551
|
+
const safeEnvironment = (value) => value === void 0 || record$3(value) && Object.keys(value).every((name) => [
|
|
2550
2552
|
"HEAD_REPO",
|
|
2551
2553
|
"BASE_REPO",
|
|
2552
2554
|
"HAS_RUN_CI_LABEL",
|
|
@@ -2560,25 +2562,25 @@ const safeEnvironment = (value) => value === void 0 || record$2(value) && Object
|
|
|
2560
2562
|
"HAWK_VERSION",
|
|
2561
2563
|
"HAWK_X86_64_UNKNOWN_LINUX_GNU_SHA256"
|
|
2562
2564
|
].includes(name));
|
|
2563
|
-
const configuredShell = (value) => record$
|
|
2565
|
+
const configuredShell = (value) => record$3(value) && record$3(value["run"]) ? value["run"]["shell"] : void 0;
|
|
2564
2566
|
const workflowTrustRules = ["workflow-trust"];
|
|
2565
2567
|
const workflowEvents = (triggers) => {
|
|
2566
2568
|
if (typeof triggers === "string") return [triggers];
|
|
2567
2569
|
if (Array.isArray(triggers)) return triggers;
|
|
2568
|
-
if (record$
|
|
2570
|
+
if (record$3(triggers)) return Object.keys(triggers);
|
|
2569
2571
|
return [];
|
|
2570
2572
|
};
|
|
2571
2573
|
/** Enforce a workflow's declared trust boundary before repository dependency execution. */
|
|
2572
2574
|
const checkWorkflowTrust = (workflow) => {
|
|
2573
2575
|
const findings = [];
|
|
2574
|
-
if (!record$
|
|
2576
|
+
if (!record$3(workflow) || !record$3(workflow["jobs"])) return findings;
|
|
2575
2577
|
const jobs = workflow["jobs"];
|
|
2576
|
-
if (!record$
|
|
2578
|
+
if (!record$3(jobs["trust-check"])) return findings;
|
|
2577
2579
|
const triggers = workflow["on"];
|
|
2578
2580
|
const pullRequests = workflowEvents(triggers).filter((event) => event === "pull_request" || event === "pull_request_target");
|
|
2579
2581
|
if (pullRequests.length === 0) return findings;
|
|
2580
2582
|
for (const [name, job] of Object.entries(jobs)) {
|
|
2581
|
-
if (!record$
|
|
2583
|
+
if (!record$3(job)) continue;
|
|
2582
2584
|
const needs = job["needs"];
|
|
2583
2585
|
const ordered = name !== "trust-check" && (needs === "trust-check" || Array.isArray(needs) && needs.includes("trust-check"));
|
|
2584
2586
|
const permitsExecution = (stepCondition) => {
|
|
@@ -2615,7 +2617,7 @@ const checkWorkflowTrust = (workflow) => {
|
|
|
2615
2617
|
message
|
|
2616
2618
|
});
|
|
2617
2619
|
const services = job["services"];
|
|
2618
|
-
if (services !== void 0 && (!record$
|
|
2620
|
+
if (services !== void 0 && (!record$3(services) || Object.keys(services).length > 0)) findings.push({
|
|
2619
2621
|
type: "job",
|
|
2620
2622
|
property: "services",
|
|
2621
2623
|
job: name,
|
|
@@ -2624,7 +2626,7 @@ const checkWorkflowTrust = (workflow) => {
|
|
|
2624
2626
|
}
|
|
2625
2627
|
if (!Array.isArray(job["steps"])) continue;
|
|
2626
2628
|
for (const [index, step] of job["steps"].entries()) {
|
|
2627
|
-
if (!record$
|
|
2629
|
+
if (!record$3(step)) continue;
|
|
2628
2630
|
const run = step["run"];
|
|
2629
2631
|
const uses = step["uses"];
|
|
2630
2632
|
const unsafeRun = typeof run === "string" && (invokesRepositoryCode(run) || !safeShell(step["shell"] ?? configuredShell(job["defaults"]) ?? configuredShell(workflow["defaults"])) || ![
|
|
@@ -2662,7 +2664,7 @@ const runtimeRules = [
|
|
|
2662
2664
|
"action-pins",
|
|
2663
2665
|
...workflowTrustRules
|
|
2664
2666
|
];
|
|
2665
|
-
const record$
|
|
2667
|
+
const record$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2666
2668
|
const dockerEnvironmentAssignments = ({ source, escape }) => {
|
|
2667
2669
|
const words = [];
|
|
2668
2670
|
let word = "";
|
|
@@ -2795,13 +2797,15 @@ const pythonRequirementMatches = (requirement, version) => {
|
|
|
2795
2797
|
});
|
|
2796
2798
|
};
|
|
2797
2799
|
/** Check runtime declarations only in the files that declare or configure them. */
|
|
2798
|
-
const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, repository, onDelegated, dynamicSelectors = [], onDynamicSelector, rustExceptions = [], engineFloors = [], onEngineFloor }) => {
|
|
2800
|
+
const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, repository, onDelegated, dynamicSelectors = [], onDynamicSelector, rustExceptions = [], rustInstallFixtures = [], onRustInstallFixture, engineFloors = [], onEngineFloor }) => {
|
|
2799
2801
|
const diagnostics = checkRustReferences({
|
|
2800
2802
|
type: "file",
|
|
2801
2803
|
file,
|
|
2802
2804
|
text,
|
|
2803
2805
|
rust: policy.rust,
|
|
2804
|
-
exceptions: rustExceptions
|
|
2806
|
+
exceptions: rustExceptions,
|
|
2807
|
+
installFixtures: rustInstallFixtures,
|
|
2808
|
+
onInstallFixture: onRustInstallFixture
|
|
2805
2809
|
});
|
|
2806
2810
|
const name = node_path.default.posix.basename(file);
|
|
2807
2811
|
const lines = text.split(/\r?\n/);
|
|
@@ -2897,7 +2901,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2897
2901
|
const toolchain = parsed["toolchain"];
|
|
2898
2902
|
pin({
|
|
2899
2903
|
rule: "rust-version",
|
|
2900
|
-
value: record$
|
|
2904
|
+
value: record$2(toolchain) ? toolchain["channel"] : void 0,
|
|
2901
2905
|
expected: policy.rust,
|
|
2902
2906
|
label: "Rust channel",
|
|
2903
2907
|
line: lineOf("channel")
|
|
@@ -2925,15 +2929,15 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2925
2929
|
};
|
|
2926
2930
|
if (parsed !== void 0) {
|
|
2927
2931
|
const workspace = parsed["workspace"];
|
|
2928
|
-
const defaults = record$
|
|
2929
|
-
if (record$
|
|
2932
|
+
const defaults = record$2(workspace) ? workspace["package"] : void 0;
|
|
2933
|
+
if (record$2(defaults) && defaults["rust-version"] !== void 0) rustFloor(defaults["rust-version"]);
|
|
2930
2934
|
const pkg = parsed["package"];
|
|
2931
|
-
const version = record$
|
|
2935
|
+
const version = record$2(pkg) ? pkg["rust-version"] : void 0;
|
|
2932
2936
|
if (version !== void 0) {
|
|
2933
|
-
if (!record$
|
|
2937
|
+
if (!record$2(version)) rustFloor(version);
|
|
2934
2938
|
else {
|
|
2935
2939
|
let inherited;
|
|
2936
|
-
if (version["workspace"] === true && record$
|
|
2940
|
+
if (version["workspace"] === true && record$2(pkg)) {
|
|
2937
2941
|
const explicit = pkg["workspace"];
|
|
2938
2942
|
let directory = node_path.default.posix.dirname(file);
|
|
2939
2943
|
const explicitPath = typeof explicit === "string" && !explicit.includes("\\") && !explicit.includes(":") && !explicit.includes("${{") && !node_path.default.posix.isAbsolute(explicit) ? node_path.default.posix.normalize(node_path.default.posix.join(directory, explicit)) : void 0;
|
|
@@ -2941,9 +2945,9 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2941
2945
|
if (explicitPath !== void 0) directory = explicitPath;
|
|
2942
2946
|
while (true) {
|
|
2943
2947
|
const table = readManifest(node_path.default.posix.join(directory, "Cargo.toml"))?.["workspace"];
|
|
2944
|
-
if (record$
|
|
2948
|
+
if (record$2(table)) {
|
|
2945
2949
|
const fields = table["package"];
|
|
2946
|
-
inherited = record$
|
|
2950
|
+
inherited = record$2(fields) ? fields["rust-version"] : void 0;
|
|
2947
2951
|
break;
|
|
2948
2952
|
}
|
|
2949
2953
|
if (explicit !== void 0 || directory === ".") break;
|
|
@@ -2958,7 +2962,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2958
2962
|
}
|
|
2959
2963
|
if (name === "Pipfile") {
|
|
2960
2964
|
const requires = toml("python-version")?.["requires"];
|
|
2961
|
-
if (record$
|
|
2965
|
+
if (record$2(requires)) {
|
|
2962
2966
|
const minor = requires["python_version"];
|
|
2963
2967
|
if (minor !== void 0 && minor !== policy.python.split(".").slice(0, 2).join(".")) add({
|
|
2964
2968
|
rule: "python-version",
|
|
@@ -2977,7 +2981,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2977
2981
|
const parsed = toml("python-version");
|
|
2978
2982
|
if (parsed !== void 0) {
|
|
2979
2983
|
const project = parsed["project"];
|
|
2980
|
-
if (record$
|
|
2984
|
+
if (record$2(project) && project["requires-python"] !== void 0) {
|
|
2981
2985
|
if (!pythonRequirementMatches(project["requires-python"], policy.python)) add({
|
|
2982
2986
|
rule: "python-version",
|
|
2983
2987
|
line: lineOf("requires-python"),
|
|
@@ -2985,9 +2989,9 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2985
2989
|
});
|
|
2986
2990
|
}
|
|
2987
2991
|
const tool = parsed["tool"];
|
|
2988
|
-
const configuredUv = record$
|
|
2992
|
+
const configuredUv = record$2(tool) ? tool["uv"] : void 0;
|
|
2989
2993
|
const uv = name === "uv.toml" ? parsed : configuredUv;
|
|
2990
|
-
if (record$
|
|
2994
|
+
if (record$2(uv) && uv["python"] !== void 0) pin({
|
|
2991
2995
|
rule: "python-version",
|
|
2992
2996
|
value: uv["python"],
|
|
2993
2997
|
expected: policy.python,
|
|
@@ -2998,11 +3002,11 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2998
3002
|
}
|
|
2999
3003
|
if (require_toolchain_inputs.isMiseConfigPath(file)) {
|
|
3000
3004
|
const tools = toml("runtime-manager")?.["tools"];
|
|
3001
|
-
if (record$
|
|
3005
|
+
if (record$2(tools)) for (const [tool, expected] of Object.entries(versions)) {
|
|
3002
3006
|
const configured = tools[tool];
|
|
3003
3007
|
if (configured !== void 0) pin({
|
|
3004
3008
|
rule: tool === "rust" || tool === "core:rust" ? "rust-version" : "runtime-manager",
|
|
3005
|
-
value: record$
|
|
3009
|
+
value: record$2(configured) ? configured["version"] : configured,
|
|
3006
3010
|
expected,
|
|
3007
3011
|
selector: nodeTools.has(tool) ? "node" : void 0,
|
|
3008
3012
|
label: `mise ${tool}`,
|
|
@@ -3350,7 +3354,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
3350
3354
|
return diagnostics;
|
|
3351
3355
|
}
|
|
3352
3356
|
const nodeLine = (node) => {
|
|
3353
|
-
if (!record$
|
|
3357
|
+
if (!record$2(node) || !Array.isArray(node["range"])) return 1;
|
|
3354
3358
|
const offset = node["range"].at(0);
|
|
3355
3359
|
return typeof offset === "number" ? text.slice(0, offset).split("\n").length : 1;
|
|
3356
3360
|
};
|
|
@@ -3464,7 +3468,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
3464
3468
|
const manifest = JSON.parse(content ?? "");
|
|
3465
3469
|
pin({
|
|
3466
3470
|
rule,
|
|
3467
|
-
value: record$
|
|
3471
|
+
value: record$2(manifest) ? manifest["packageManager"] : void 0,
|
|
3468
3472
|
expected: `bun@${policy.bun}`,
|
|
3469
3473
|
label: "setup-bun version file packageManager",
|
|
3470
3474
|
line
|
|
@@ -3664,10 +3668,21 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
3664
3668
|
const selector = staticRepositoryPath(value) ? node_path.default.posix.normalize(value) : void 0;
|
|
3665
3669
|
const providers = checkoutProvidersFor(selector);
|
|
3666
3670
|
const binding = selector === void 0 ? void 0 : providers.at(-1);
|
|
3667
|
-
if (selector === void 0 || unknownCheckoutDestination || binding
|
|
3668
|
-
|
|
3671
|
+
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)) {
|
|
3672
|
+
add({
|
|
3673
|
+
rule: "action-pins",
|
|
3674
|
+
line,
|
|
3675
|
+
message: "local actions require an unambiguous checkout of the current source snapshot"
|
|
3676
|
+
});
|
|
3677
|
+
return;
|
|
3678
|
+
}
|
|
3679
|
+
if (binding?.source === "delegated") onDelegated?.({
|
|
3680
|
+
path: file,
|
|
3669
3681
|
line,
|
|
3670
|
-
|
|
3682
|
+
tool: "action",
|
|
3683
|
+
selector,
|
|
3684
|
+
checkoutPath: binding.path,
|
|
3685
|
+
ref: binding.ref
|
|
3671
3686
|
});
|
|
3672
3687
|
return;
|
|
3673
3688
|
}
|
|
@@ -3965,6 +3980,54 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
3965
3980
|
return diagnostics;
|
|
3966
3981
|
};
|
|
3967
3982
|
//#endregion
|
|
3983
|
+
//#region src/toolchain-rust-fixtures.ts
|
|
3984
|
+
const record$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3985
|
+
const date = (value) => /^\d{4}-\d{2}-\d{2}$/.test(value) && Number.isFinite(Date.parse(value)) && new Date(value).toISOString().slice(0, 10) === value;
|
|
3986
|
+
/** Historical install probes do not authorize using an older compiler. */
|
|
3987
|
+
const parseRustInstallFixtures = (input) => {
|
|
3988
|
+
if (input === void 0) return [];
|
|
3989
|
+
if (!Array.isArray(input)) throw new Error("rustInstallFixtures must be an array");
|
|
3990
|
+
const identities = /* @__PURE__ */ new Set();
|
|
3991
|
+
return input.map((entry) => {
|
|
3992
|
+
if (!record$1(entry) || Object.keys(entry).length !== 5 || Object.keys(entry).some((key) => ![
|
|
3993
|
+
"path",
|
|
3994
|
+
"pin",
|
|
3995
|
+
"reason",
|
|
3996
|
+
"tracking",
|
|
3997
|
+
"expires"
|
|
3998
|
+
].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");
|
|
3999
|
+
const identity = `${entry["path"]}:${entry["pin"]}`;
|
|
4000
|
+
if (identities.has(identity)) throw new Error("Rust install fixture path/pin pairs must be unique");
|
|
4001
|
+
identities.add(identity);
|
|
4002
|
+
return {
|
|
4003
|
+
path: entry["path"],
|
|
4004
|
+
pin: entry["pin"],
|
|
4005
|
+
reason: entry["reason"],
|
|
4006
|
+
tracking: entry["tracking"],
|
|
4007
|
+
expires: entry["expires"]
|
|
4008
|
+
};
|
|
4009
|
+
});
|
|
4010
|
+
};
|
|
4011
|
+
const checkRustInstallFixtureExpiry = ({ fixtures, scope, now = /* @__PURE__ */ new Date() }) => {
|
|
4012
|
+
const errors = [];
|
|
4013
|
+
const warnings = [];
|
|
4014
|
+
for (const fixture of fixtures) {
|
|
4015
|
+
if (fixture.expires > now.toISOString().slice(0, 10)) continue;
|
|
4016
|
+
const verdict = require_quarantine_policy.overdueVerdict(scope, "stll-toolchain.json", (text) => {
|
|
4017
|
+
const base = JSON.parse(text);
|
|
4018
|
+
const declarations = record$1(base) ? base["rustInstallFixtures"] : void 0;
|
|
4019
|
+
return Array.isArray(declarations) && declarations.some((entry) => record$1(entry) && entry["path"] === fixture.path && entry["pin"] === fixture.pin && entry["expires"] === fixture.expires);
|
|
4020
|
+
});
|
|
4021
|
+
const message = `Rust install fixture ${fixture.path}:${fixture.pin} expired on ${fixture.expires}; remove it or renew the documented fixture boundary`;
|
|
4022
|
+
if (!verdict.fail) warnings.push(message);
|
|
4023
|
+
else errors.push(verdict.unavailable === void 0 ? message : `${message}; cannot determine whether this change adds it: ${verdict.unavailable}`);
|
|
4024
|
+
}
|
|
4025
|
+
return {
|
|
4026
|
+
errors,
|
|
4027
|
+
warnings
|
|
4028
|
+
};
|
|
4029
|
+
};
|
|
4030
|
+
//#endregion
|
|
3968
4031
|
//#region src/toolchain-guard.ts
|
|
3969
4032
|
const toolchainRules = [.../* @__PURE__ */ new Set([
|
|
3970
4033
|
...packageRules,
|
|
@@ -3979,7 +4042,7 @@ const record = (value) => typeof value === "object" && value !== null && !Array.
|
|
|
3979
4042
|
const array = (value) => Array.isArray(value);
|
|
3980
4043
|
/** Repository-owned declarations are explicit; malformed decisions fail closed. */
|
|
3981
4044
|
const parseToolchainConfiguration = (input) => {
|
|
3982
|
-
if (!record(input) || Object.keys(input).some((key) => key !== "optOuts" && key !== "cloud" && key !== "engineFloors" && key !== "dynamicSelectors" && key !== "consumerChecks" && key !== "rustExceptions" && key !== "dependabotIgnores" && key !== "typescriptAllowlist") || input["optOuts"] !== void 0 && !array(input["optOuts"])) throw new Error("stll-toolchain.json accepts only optOuts, cloud, engineFloors, dynamicSelectors, consumerChecks, rustExceptions, dependabotIgnores and typescriptAllowlist declarations");
|
|
4045
|
+
if (!record(input) || Object.keys(input).some((key) => key !== "optOuts" && key !== "cloud" && key !== "engineFloors" && key !== "dynamicSelectors" && key !== "consumerChecks" && key !== "rustExceptions" && key !== "rustInstallFixtures" && key !== "dependabotIgnores" && key !== "typescriptAllowlist") || input["optOuts"] !== void 0 && !array(input["optOuts"])) throw new Error("stll-toolchain.json accepts only optOuts, cloud, engineFloors, dynamicSelectors, consumerChecks, rustExceptions, rustInstallFixtures, dependabotIgnores and typescriptAllowlist declarations");
|
|
3983
4046
|
const disabled = /* @__PURE__ */ new Set();
|
|
3984
4047
|
const optOuts = input["optOuts"] ?? [];
|
|
3985
4048
|
if (!array(optOuts)) throw new Error("optOuts must be an array");
|
|
@@ -3996,6 +4059,7 @@ const parseToolchainConfiguration = (input) => {
|
|
|
3996
4059
|
return {
|
|
3997
4060
|
disabled,
|
|
3998
4061
|
rustExceptions: parseRustExceptions(input["rustExceptions"]),
|
|
4062
|
+
rustInstallFixtures: parseRustInstallFixtures(input["rustInstallFixtures"]),
|
|
3999
4063
|
cloud: require_toolchain_inputs.parseCloudSetup(input["cloud"]),
|
|
4000
4064
|
engineFloors: parseEngineFloors(input["engineFloors"]),
|
|
4001
4065
|
dynamicSelectors: parseDynamicSelectors(input["dynamicSelectors"]),
|
|
@@ -4088,11 +4152,20 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4088
4152
|
if (files["stll-toolchain.json"] !== void 0) try {
|
|
4089
4153
|
configuration = parseToolchainConfiguration(JSON.parse(files["stll-toolchain.json"]));
|
|
4090
4154
|
dynamicSelectors = configuration.dynamicSelectors;
|
|
4091
|
-
const
|
|
4155
|
+
const rustExpiry = checkRustExceptionExpiry({
|
|
4092
4156
|
exceptions: configuration.rustExceptions,
|
|
4093
4157
|
now,
|
|
4094
4158
|
scope: expiryScope
|
|
4095
4159
|
});
|
|
4160
|
+
const fixtureExpiry = checkRustInstallFixtureExpiry({
|
|
4161
|
+
fixtures: configuration.rustInstallFixtures,
|
|
4162
|
+
now,
|
|
4163
|
+
scope: expiryScope
|
|
4164
|
+
});
|
|
4165
|
+
const expiry = {
|
|
4166
|
+
errors: [...rustExpiry.errors, ...fixtureExpiry.errors],
|
|
4167
|
+
warnings: [...rustExpiry.warnings, ...fixtureExpiry.warnings]
|
|
4168
|
+
};
|
|
4096
4169
|
for (const message of expiry.errors) diagnostics.push({
|
|
4097
4170
|
rule: "configuration",
|
|
4098
4171
|
path: "stll-toolchain.json",
|
|
@@ -4226,6 +4299,7 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4226
4299
|
trackedFiles
|
|
4227
4300
|
}));
|
|
4228
4301
|
}
|
|
4302
|
+
const usedRustFixtures = /* @__PURE__ */ new Set();
|
|
4229
4303
|
for (const [file, text] of Object.entries(files)) diagnostics.push(...checkRuntimeFile({
|
|
4230
4304
|
file,
|
|
4231
4305
|
text,
|
|
@@ -4238,6 +4312,8 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4238
4312
|
},
|
|
4239
4313
|
trackedFiles,
|
|
4240
4314
|
rustExceptions: configuration.rustExceptions,
|
|
4315
|
+
rustInstallFixtures: configuration.rustInstallFixtures,
|
|
4316
|
+
onRustInstallFixture: (entry) => usedRustFixtures.add(entry),
|
|
4241
4317
|
dynamicSelectors: dynamicSelectors.filter((entry) => entry.path === file),
|
|
4242
4318
|
onDynamicSelector: (entry) => selectorMatches.set(entry, (selectorMatches.get(entry) ?? 0) + 1),
|
|
4243
4319
|
repository,
|
|
@@ -4249,6 +4325,12 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4249
4325
|
},
|
|
4250
4326
|
readFile: (target) => files[target]
|
|
4251
4327
|
}));
|
|
4328
|
+
for (const entry of configuration.rustInstallFixtures) if (!usedRustFixtures.has(entry)) diagnostics.push({
|
|
4329
|
+
rule: "configuration",
|
|
4330
|
+
path: "stll-toolchain.json",
|
|
4331
|
+
line: 1,
|
|
4332
|
+
message: `Rust install fixture ${entry.path}:${entry.pin} must match a tracked rustup toolchain install probe`
|
|
4333
|
+
});
|
|
4252
4334
|
for (const entry of engineFloors) if (!exercisedFloors.has(entry) || mismatchedFloors.has(entry)) diagnostics.push({
|
|
4253
4335
|
rule: "configuration",
|
|
4254
4336
|
path: "stll-toolchain.json",
|