@stll/oxlint-config 0.15.0 → 1.0.1
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 +20 -1
- package/dist/cloud-setup-cli.mjs +1 -1
- package/dist/toolchain-changed.cjs +5 -22
- package/dist/toolchain-changed.cjs.map +1 -1
- package/dist/toolchain-changed.mjs +6 -23
- package/dist/toolchain-changed.mjs.map +1 -1
- package/dist/toolchain-guard.cjs +837 -121
- package/dist/toolchain-guard.cjs.map +1 -1
- package/dist/toolchain-guard.mjs +839 -123
- package/dist/toolchain-guard.mjs.map +1 -1
- package/dist/toolchain-inputs.cjs +10 -2
- package/dist/toolchain-inputs.cjs.map +1 -1
- package/dist/toolchain-inputs.mjs +5 -3
- package/dist/toolchain-inputs.mjs.map +1 -1
- package/dist/toolchain-schema.cjs +0 -3
- package/dist/toolchain-schema.cjs.map +1 -1
- package/dist/toolchain-schema.mjs +1 -4
- package/dist/toolchain-schema.mjs.map +1 -1
- package/dist/typecheck-parity.cjs +1 -1
- package/dist/typecheck-parity.cjs.map +1 -1
- package/dist/typecheck-parity.mjs +1 -1
- package/dist/typecheck-parity.mjs.map +1 -1
- package/package.json +1 -1
- package/toolchain.json +1 -2
package/dist/toolchain-guard.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { _ as
|
|
1
|
+
import { _ as nodeSelectorMatches, a as isPythonDependencyManifest, c as pythonDependencyManifestKind, d as isComposeDefinitionPath, f as isKubernetesDefinitionPath, h as generateCloudSetup, i as isMiseConfigPath, l as toolchainInputKind, m as cloudSetupPath, n as isDependabotGithubActionsPath, o as isShellScript, p as workspaceContains, r as isDockerDefinitionPath, s as javascriptDependencyLockfiles, t as githubAutomationFileKind, u as containerDocumentImages, v as nodeSupportRangeMatches, y as parseCloudSetup } from "./toolchain-inputs.mjs";
|
|
2
2
|
import { _ as parseConsumerFixtures, d as consumerPackageClosure, g as discoverConsumerPackages, i as assertConsumerFixtureKind, n as assertConsumerNodeSupport, o as assertConsumerFixtureSelection } from "./toolchain-schema.mjs";
|
|
3
|
-
import { readFileSync, realpathSync } from "node:fs";
|
|
3
|
+
import { closeSync, lstatSync, openSync, readFileSync, readSync, realpathSync } from "node:fs";
|
|
4
4
|
import { execFileSync } from "node:child_process";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { minVersion, satisfies, valid, validRange } from "semver";
|
|
@@ -9,9 +9,9 @@ import picomatch from "picomatch";
|
|
|
9
9
|
import { parse as parse$2 } from "smol-toml";
|
|
10
10
|
//#endregion
|
|
11
11
|
//#region src/toolchain-consumer-checks.ts
|
|
12
|
-
const consumerRunnerVersion = "0.
|
|
12
|
+
const consumerRunnerVersion = "1.0.1";
|
|
13
13
|
const consumerCheckWorkflow = "stella/.github/.github/workflows/package-consumer-compat.yml";
|
|
14
|
-
const record$
|
|
14
|
+
const record$7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15
15
|
const repositoryDirectory = (value) => typeof value === "string" && value !== "" && !value.includes("\\") && !value.includes("${{") && !value.split("").some((character) => {
|
|
16
16
|
const code = character.charCodeAt(0);
|
|
17
17
|
return code < 32 || code === 127;
|
|
@@ -97,12 +97,12 @@ const validConsumerCron = (text) => {
|
|
|
97
97
|
});
|
|
98
98
|
};
|
|
99
99
|
const nightlySchedule = (value) => {
|
|
100
|
-
if (!record$
|
|
100
|
+
if (!record$7(value)) return false;
|
|
101
101
|
const schedule = value["schedule"];
|
|
102
102
|
if (!Array.isArray(schedule) || schedule.length === 0) return false;
|
|
103
103
|
const coveredDays = /* @__PURE__ */ new Set();
|
|
104
104
|
for (const entry of schedule) {
|
|
105
|
-
if (!record$
|
|
105
|
+
if (!record$7(entry) || typeof entry["cron"] !== "string" || !validConsumerCron(entry["cron"])) return false;
|
|
106
106
|
const fields = entry["cron"].trim().split(/\s+/);
|
|
107
107
|
const selections = cronFields.map((field, index) => {
|
|
108
108
|
const text = fields.at(index);
|
|
@@ -118,7 +118,7 @@ const parseConsumerChecks = (input) => {
|
|
|
118
118
|
if (!Array.isArray(input)) throw new Error("consumerChecks must be an array");
|
|
119
119
|
const targets = /* @__PURE__ */ new Set();
|
|
120
120
|
return input.map((entry) => {
|
|
121
|
-
if (!record$
|
|
121
|
+
if (!record$7(entry) || Object.keys(entry).some((key) => ![
|
|
122
122
|
"workflow",
|
|
123
123
|
"job",
|
|
124
124
|
"packages",
|
|
@@ -156,9 +156,9 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
|
|
|
156
156
|
});
|
|
157
157
|
if (document.errors.length > 0) throw new Error("invalid workflow YAML");
|
|
158
158
|
const source = document.toJS({ maxAliasCount: 100 });
|
|
159
|
-
if (!record$
|
|
159
|
+
if (!record$7(source) || !record$7(source["jobs"])) continue;
|
|
160
160
|
for (const [jobName, job] of Object.entries(source["jobs"])) {
|
|
161
|
-
if (!record$
|
|
161
|
+
if (!record$7(job) || typeof job["uses"] !== "string") continue;
|
|
162
162
|
const [workflowPath, ref] = job["uses"].split("@");
|
|
163
163
|
if (workflowPath?.toLowerCase() !== "stella/.github/.github/workflows/package-consumer-compat.yml") continue;
|
|
164
164
|
const declaration = declarations.find((entry) => entry.workflow === workflow && entry.job === jobName);
|
|
@@ -172,7 +172,7 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
|
|
|
172
172
|
const approved = Object.entries(policy.actions).find(([name]) => name.toLowerCase() === consumerCheckWorkflow)?.[1];
|
|
173
173
|
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`);
|
|
174
174
|
const inputs = job["with"];
|
|
175
|
-
if (!record$
|
|
175
|
+
if (!record$7(inputs)) throw new Error(`${workflow}:${jobName} requires consumer inputs`);
|
|
176
176
|
const names = /* @__PURE__ */ new Set();
|
|
177
177
|
for (const key of Object.keys(inputs)) {
|
|
178
178
|
const normalized = key.toLowerCase();
|
|
@@ -256,16 +256,24 @@ const canonicalDockerRuntime = (image) => {
|
|
|
256
256
|
const registry = dockerHubRegistries.find((entry) => image.toLowerCase().startsWith(`${entry}/`));
|
|
257
257
|
return (registry === void 0 ? image : image.slice(registry.length + 1)).replace(/^library\//i, "");
|
|
258
258
|
};
|
|
259
|
+
/** Preserve Rust release identity across official Debian slim and Alpine variants. */
|
|
260
|
+
const rustImageVersion = (tag) => tag.replace(/-(?:alpine(?:\d+(?:\.\d+)*)?|(?:bookworm|bullseye|trixie|buster)(?:-slim)?|slim(?:-(?:bookworm|bullseye|trixie|buster))?)$/, "");
|
|
259
261
|
//#endregion
|
|
260
262
|
//#region src/toolchain-dependabot.ts
|
|
261
263
|
const dependabotRules = ["dependabot-policy"];
|
|
262
|
-
const record$
|
|
264
|
+
const record$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
263
265
|
const strings = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
264
266
|
const sameStrings = (value, expected) => {
|
|
265
267
|
if (!strings(value) || value.length !== expected.length) return false;
|
|
266
268
|
const sortedExpected = [...expected].sort();
|
|
267
269
|
return [...value].sort().every((entry, index) => entry === sortedExpected[index]);
|
|
268
270
|
};
|
|
271
|
+
const pinnedVersionUpdateTypes = [
|
|
272
|
+
"version-update:semver-major",
|
|
273
|
+
"version-update:semver-minor",
|
|
274
|
+
"version-update:semver-patch"
|
|
275
|
+
];
|
|
276
|
+
const versionOnlyIgnore = (entry) => record$6(entry) && Object.keys(entry).length === 2 && typeof entry["dependency-name"] === "string" && sameStrings(entry["update-types"], pinnedVersionUpdateTypes);
|
|
269
277
|
const directoryOf = (file) => {
|
|
270
278
|
const directory = path.posix.dirname(file);
|
|
271
279
|
return directory === "." ? "/" : `/${directory}`;
|
|
@@ -281,8 +289,8 @@ const workspaceTable = (parsed, ecosystem) => {
|
|
|
281
289
|
case "cargo": return parsed["workspace"];
|
|
282
290
|
case "uv": {
|
|
283
291
|
const tool = parsed["tool"];
|
|
284
|
-
const uv = record$
|
|
285
|
-
return record$
|
|
292
|
+
const uv = record$6(tool) ? tool["uv"] : void 0;
|
|
293
|
+
return record$6(uv) ? uv["workspace"] : void 0;
|
|
286
294
|
}
|
|
287
295
|
default: throw new Error("unknown workspace ecosystem", { cause: ecosystem });
|
|
288
296
|
}
|
|
@@ -302,7 +310,7 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
302
310
|
const document = parseDocument(text);
|
|
303
311
|
if (document.errors.length > 0) throw new Error(`${workspaceFile}:1: invalid pnpm workspace YAML`);
|
|
304
312
|
const parsed = document.toJS({ maxAliasCount: 100 });
|
|
305
|
-
if (!record$
|
|
313
|
+
if (!record$6(parsed)) throw new Error(`${workspaceFile}:1: pnpm workspace must contain an object`);
|
|
306
314
|
const patterns = parsed["packages"] ?? [];
|
|
307
315
|
if (!strings(patterns)) throw new Error(`${workspaceFile}:1: pnpm packages must be a string array`);
|
|
308
316
|
workspaces.set(directory, patterns.map((pattern) => pattern.startsWith("!") ? `!${path.posix.normalize(pattern.slice(1))}` : path.posix.normalize(pattern)));
|
|
@@ -314,13 +322,13 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
314
322
|
}
|
|
315
323
|
try {
|
|
316
324
|
const parsed = ecosystem === "npm" ? JSON.parse(files[file] ?? "") : parse$2(files[file] ?? "");
|
|
317
|
-
if (!record$
|
|
325
|
+
if (!record$6(parsed)) continue;
|
|
318
326
|
if (ecosystem === "npm" && typeof parsed["packageManager"] === "string" && parsed["packageManager"].startsWith("pnpm@")) continue;
|
|
319
327
|
const value = workspaceTable(parsed, ecosystem);
|
|
320
|
-
const patterns = record$
|
|
321
|
-
const members = ecosystem === "cargo" && record$
|
|
328
|
+
const patterns = record$6(value) ? value[ecosystem === "npm" ? "packages" : "members"] : value;
|
|
329
|
+
const members = ecosystem === "cargo" && record$6(value) && patterns === void 0 ? [] : patterns;
|
|
322
330
|
if (strings(members)) {
|
|
323
|
-
const excluded = ecosystem !== "npm" && record$
|
|
331
|
+
const excluded = ecosystem !== "npm" && record$6(value) && strings(value["exclude"]) ? value["exclude"].map((pattern) => `!${pattern}`) : [];
|
|
324
332
|
workspaces.set(path.posix.dirname(file), [...members, ...excluded]);
|
|
325
333
|
}
|
|
326
334
|
} catch {}
|
|
@@ -371,11 +379,11 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
371
379
|
for (const directory of memberDirectories) try {
|
|
372
380
|
const parsed = parse$2(files[path.posix.join(directory, "Cargo.toml")] ?? "");
|
|
373
381
|
const workspace = parse$2(files[path.posix.join(root, "Cargo.toml")] ?? "")["workspace"];
|
|
374
|
-
const inherited = record$
|
|
382
|
+
const inherited = record$6(workspace) ? workspace["dependencies"] : void 0;
|
|
375
383
|
const dependencyOwners = [parsed];
|
|
376
384
|
const targets = parsed["target"];
|
|
377
|
-
if (record$
|
|
378
|
-
for (const target of Object.values(targets)) if (record$
|
|
385
|
+
if (record$6(targets)) {
|
|
386
|
+
for (const target of Object.values(targets)) if (record$6(target)) dependencyOwners.push(target);
|
|
379
387
|
}
|
|
380
388
|
for (const owner of dependencyOwners) for (const key of [
|
|
381
389
|
"dependencies",
|
|
@@ -383,12 +391,12 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
383
391
|
"build-dependencies"
|
|
384
392
|
]) {
|
|
385
393
|
const dependencies = owner[key];
|
|
386
|
-
if (!record$
|
|
394
|
+
if (!record$6(dependencies)) continue;
|
|
387
395
|
for (const [name, dependency] of Object.entries(dependencies)) {
|
|
388
|
-
if (!record$
|
|
396
|
+
if (!record$6(dependency)) continue;
|
|
389
397
|
const isInherited = dependency["workspace"] === true;
|
|
390
|
-
const resolved = isInherited && record$
|
|
391
|
-
if (!record$
|
|
398
|
+
const resolved = isInherited && record$6(inherited) ? inherited[name] : dependency;
|
|
399
|
+
if (!record$6(resolved) || typeof resolved["path"] !== "string") continue;
|
|
392
400
|
if (path.posix.isAbsolute(resolved["path"]) || /\\|\$|^[A-Za-z]:/.test(resolved["path"])) continue;
|
|
393
401
|
const target = path.posix.normalize(path.posix.join(isInherited ? root : directory, resolved["path"]));
|
|
394
402
|
const relative = path.posix.relative(root, target);
|
|
@@ -416,7 +424,7 @@ const javascriptEcosystem = (directory, files) => {
|
|
|
416
424
|
if (files[path.posix.join(root, "bun.lock")] !== void 0) return "bun";
|
|
417
425
|
try {
|
|
418
426
|
const manifest = JSON.parse(files[path.posix.join(root, "package.json")] ?? "{}");
|
|
419
|
-
if (record$
|
|
427
|
+
if (record$6(manifest) && typeof manifest["packageManager"] === "string" && manifest["packageManager"].startsWith("bun@")) return "bun";
|
|
420
428
|
} catch {}
|
|
421
429
|
return "npm";
|
|
422
430
|
};
|
|
@@ -428,7 +436,7 @@ const pythonEcosystem = (file, files) => {
|
|
|
428
436
|
const pyproject = files[path.posix.join(root, "pyproject.toml")];
|
|
429
437
|
if (pyproject !== void 0) try {
|
|
430
438
|
const tool = parse$2(pyproject)["tool"];
|
|
431
|
-
if (record$
|
|
439
|
+
if (record$6(tool) && record$6(tool["uv"])) return "uv";
|
|
432
440
|
} catch {}
|
|
433
441
|
return "pip";
|
|
434
442
|
};
|
|
@@ -499,7 +507,7 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
499
507
|
add("cannot resolve Dependabot YAML aliases");
|
|
500
508
|
return diagnostics;
|
|
501
509
|
}
|
|
502
|
-
if (!record$
|
|
510
|
+
if (!record$6(parsed) || parsed["version"] !== 2 || !Array.isArray(parsed["updates"])) {
|
|
503
511
|
add("Dependabot must declare version: 2 and an updates array");
|
|
504
512
|
return diagnostics;
|
|
505
513
|
}
|
|
@@ -512,7 +520,7 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
512
520
|
});
|
|
513
521
|
if (legacyDocument.errors.length === 0) legacy = legacyDocument.toJS({ maxAliasCount: 100 });
|
|
514
522
|
} catch {}
|
|
515
|
-
const legacyUpdates = record$
|
|
523
|
+
const legacyUpdates = record$6(legacy) && Array.isArray(legacy["updates"]) ? legacy["updates"] : [];
|
|
516
524
|
const rootClaims = /* @__PURE__ */ new Map();
|
|
517
525
|
const directoryClaims = /* @__PURE__ */ new Map();
|
|
518
526
|
for (const [index, update] of parsed["updates"].entries()) {
|
|
@@ -526,7 +534,7 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
526
534
|
return offset === void 0 ? 1 : lineCounter.linePos(offset).line;
|
|
527
535
|
};
|
|
528
536
|
const report = (key, message) => add(message, lineOf(key));
|
|
529
|
-
if (!record$
|
|
537
|
+
if (!record$6(update) || typeof update["package-ecosystem"] !== "string") {
|
|
530
538
|
report("package-ecosystem", "each Dependabot update must declare a package ecosystem");
|
|
531
539
|
continue;
|
|
532
540
|
}
|
|
@@ -560,21 +568,22 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
560
568
|
covered.set(ecosystem, matches);
|
|
561
569
|
const schedule = update["schedule"];
|
|
562
570
|
const legacyUpdate = legacyUpdates.at(index);
|
|
563
|
-
const legacySchedule = record$
|
|
564
|
-
if (record$
|
|
565
|
-
if (!record$
|
|
571
|
+
const legacySchedule = record$6(legacyUpdate) ? legacyUpdate["schedule"] : void 0;
|
|
572
|
+
if (record$6(schedule) && schedule["time"] === policy.schedule.time && (!record$6(legacySchedule) || legacySchedule["time"] !== policy.schedule.time)) report("schedule", `${ecosystem}: quote schedule time to preserve its string value in YAML 1.1`);
|
|
573
|
+
if (!record$6(schedule) || Object.keys(schedule).length !== Object.keys(policy.schedule).length || Object.entries(policy.schedule).some(([key, value]) => schedule[key] !== value)) report("schedule", `${ecosystem}: schedule must match the shared policy`);
|
|
566
574
|
const cooldown = update["cooldown"];
|
|
567
|
-
if (!record$
|
|
575
|
+
if (!record$6(cooldown) || Object.keys(cooldown).length !== 1 || cooldown["default-days"] !== policy.cooldown.defaultDays) report("cooldown", `${ecosystem}: cooldown must contain only default-days: ${policy.cooldown.defaultDays}`);
|
|
568
576
|
const groups = update["groups"];
|
|
569
|
-
if (!record$
|
|
577
|
+
if (!record$6(groups) || Object.keys(groups).length !== Object.keys(policy.groups).length || Object.entries(policy.groups).some(([name, expected]) => {
|
|
570
578
|
const group = groups[name];
|
|
571
|
-
return !record$
|
|
579
|
+
return !record$6(group) || Object.keys(group).length !== 2 || !sameStrings(group["patterns"], expected.patterns) || !sameStrings(group["update-types"], expected.updateTypes);
|
|
572
580
|
})) report("groups", `${ecosystem}: groups must match the shared policy`);
|
|
573
581
|
const ignored = ignoredFor(ecosystem, policy);
|
|
574
582
|
const ignore = update["ignore"];
|
|
575
583
|
if (ignore !== void 0 && (!Array.isArray(ignore) || ignore.length === 0)) report("ignore", `${ecosystem}: omit ignore or provide a nonempty array`);
|
|
576
|
-
if (Array.isArray(ignore) && ignore.some((entry) => !record$
|
|
577
|
-
|
|
584
|
+
if (Array.isArray(ignore) && ignore.some((entry) => !record$6(entry) || typeof entry["dependency-name"] !== "string" || !ignored.includes(entry["dependency-name"]))) report("ignore", `${ecosystem}: ignore only packages owned by the shared policy`);
|
|
585
|
+
if (Array.isArray(ignore) && ignore.some((entry) => !versionOnlyIgnore(entry))) report("ignore", `${ecosystem}: each shared-pin ignore must contain only dependency-name and update-types for semver-major, semver-minor, and semver-patch version updates; security updates must remain enabled`);
|
|
586
|
+
for (const name of ignored) if (!Array.isArray(ignore) || !ignore.some((entry) => record$6(entry) && entry["dependency-name"] === name && versionOnlyIgnore(entry))) report("ignore", `${ecosystem}: ignore all version updates for shared pin ${name}`);
|
|
578
587
|
}
|
|
579
588
|
for (const [ecosystem, directories] of roots) for (const directory of directories) if (!covered.get(ecosystem)?.has(directory)) add(`add a ${ecosystem} update entry covering ${directory}`);
|
|
580
589
|
return diagnostics;
|
|
@@ -586,7 +595,7 @@ const locatorValidators = {
|
|
|
586
595
|
"bun-source": /^(?:jobs\.[\w-]+|runs)\.steps\.[\w-]+$/
|
|
587
596
|
};
|
|
588
597
|
const knownKind = (value) => typeof value === "string" && Object.hasOwn(locatorValidators, value);
|
|
589
|
-
const record$
|
|
598
|
+
const record$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
590
599
|
/** One reviewed declaration identifies one unresolved selector, never a rule. */
|
|
591
600
|
const parseDynamicSelectors = (input) => {
|
|
592
601
|
if (input === void 0) return [];
|
|
@@ -594,7 +603,7 @@ const parseDynamicSelectors = (input) => {
|
|
|
594
603
|
const result = [];
|
|
595
604
|
const keys = /* @__PURE__ */ new Set();
|
|
596
605
|
for (const entry of input) {
|
|
597
|
-
if (!record$
|
|
606
|
+
if (!record$5(entry) || Object.keys(entry).some((key) => ![
|
|
598
607
|
"path",
|
|
599
608
|
"kind",
|
|
600
609
|
"reason",
|
|
@@ -630,7 +639,7 @@ const parseDynamicSelectors = (input) => {
|
|
|
630
639
|
};
|
|
631
640
|
//#endregion
|
|
632
641
|
//#region src/toolchain-engine-floors.ts
|
|
633
|
-
const record$
|
|
642
|
+
const record$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
634
643
|
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 !== "..");
|
|
635
644
|
const parseEngineFloors = (input) => {
|
|
636
645
|
if (input === void 0) return [];
|
|
@@ -638,7 +647,7 @@ const parseEngineFloors = (input) => {
|
|
|
638
647
|
const result = [];
|
|
639
648
|
const jobs = /* @__PURE__ */ new Set();
|
|
640
649
|
for (const entry of input) {
|
|
641
|
-
if (!record$
|
|
650
|
+
if (!record$4(entry) || Object.keys(entry).some((key) => ![
|
|
642
651
|
"package",
|
|
643
652
|
"workflow",
|
|
644
653
|
"job"
|
|
@@ -659,16 +668,16 @@ const resolveEngineFloor = (entry, files) => {
|
|
|
659
668
|
const workflowText = files[entry.workflow];
|
|
660
669
|
if (manifestText === void 0 || workflowText === void 0) throw new Error("engine floor package and workflow must be tracked");
|
|
661
670
|
const manifest = JSON.parse(manifestText);
|
|
662
|
-
if (!record$
|
|
671
|
+
if (!record$4(manifest) || manifest["private"] === true || typeof manifest["name"] !== "string" || manifest["name"].trim() === "" || !record$4(manifest["engines"]) || typeof manifest["engines"]["node"] !== "string") throw new Error("engine floor package must be published and declare engines.node");
|
|
663
672
|
const range = manifest["engines"]["node"];
|
|
664
673
|
const minimum = validRange(range) === null ? null : minVersion(range);
|
|
665
674
|
if (minimum === null || minimum.prerelease.length !== 0) throw new Error("engine floor engines.node must declare a stable minimum");
|
|
666
675
|
const document = parseDocument(workflowText, { merge: true });
|
|
667
676
|
if (document.errors.length !== 0) throw new Error("engine floor workflow must be valid YAML");
|
|
668
677
|
const workflow = document.toJS({ maxAliasCount: 100 });
|
|
669
|
-
if (!record$
|
|
678
|
+
if (!record$4(workflow) || !record$4(workflow["jobs"])) throw new Error("engine floor job must exist in its tracked workflow");
|
|
670
679
|
const job = workflow["jobs"][entry.job];
|
|
671
|
-
if (!record$
|
|
680
|
+
if (!record$4(job)) throw new Error("engine floor job must exist in its tracked workflow");
|
|
672
681
|
if (!Array.isArray(job["steps"]) || Object.hasOwn(job, "uses")) throw new Error("engine floor job must declare steps and cannot reuse a workflow");
|
|
673
682
|
return {
|
|
674
683
|
...entry,
|
|
@@ -688,7 +697,7 @@ const packageRules = [
|
|
|
688
697
|
"typescript-layout",
|
|
689
698
|
"node-engine"
|
|
690
699
|
];
|
|
691
|
-
const record$
|
|
700
|
+
const record$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
692
701
|
const shellSubstitutions = (command) => {
|
|
693
702
|
const frames = [{
|
|
694
703
|
kind: "root",
|
|
@@ -749,43 +758,371 @@ const shellSubstitutions = (command) => {
|
|
|
749
758
|
for (const frame of frames.slice(1)) bodies.push(command.slice(frame.start));
|
|
750
759
|
return bodies;
|
|
751
760
|
};
|
|
752
|
-
const
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
761
|
+
const shellValueOptions = /* @__PURE__ */ new Set([
|
|
762
|
+
"--rcfile",
|
|
763
|
+
"--init-file",
|
|
764
|
+
"-o",
|
|
765
|
+
"+o",
|
|
766
|
+
"-O",
|
|
767
|
+
"+O"
|
|
768
|
+
]);
|
|
769
|
+
const shellExecutable = /^(?:\/(?:[^/]+\/)*)?(?:bash|dash|ksh|zsh|sh)$/;
|
|
770
|
+
const shellTokens = (source) => {
|
|
771
|
+
const tokens = [];
|
|
772
|
+
for (let index = 0; index < source.length;) {
|
|
773
|
+
const character = source.at(index);
|
|
774
|
+
if (character === void 0) break;
|
|
775
|
+
if (character === "\n") {
|
|
776
|
+
tokens.push({
|
|
777
|
+
kind: "boundary",
|
|
778
|
+
value: character,
|
|
779
|
+
index: index++
|
|
780
|
+
});
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
if (/\s/.test(character)) {
|
|
784
|
+
index++;
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
787
|
+
if (character === "#") {
|
|
788
|
+
const end = source.indexOf("\n", index);
|
|
789
|
+
const stop = end < 0 ? source.length : end;
|
|
790
|
+
tokens.push({
|
|
791
|
+
kind: "comment",
|
|
792
|
+
value: source.slice(index, stop),
|
|
793
|
+
index
|
|
794
|
+
});
|
|
795
|
+
index = stop;
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
const redirect = /^(?:\d*)(?:&>>|&>|<<<|<<-|<<|>>|>\||>&|<&|<>|>|<)/.exec(source.slice(index));
|
|
799
|
+
if (redirect !== null) {
|
|
800
|
+
tokens.push({
|
|
801
|
+
kind: "redirect",
|
|
802
|
+
value: redirect[0],
|
|
803
|
+
index
|
|
804
|
+
});
|
|
805
|
+
index += redirect[0].length;
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
const boundary = /^(?:&&|\|\||[();&|])/.exec(source.slice(index));
|
|
809
|
+
if (boundary !== null) {
|
|
810
|
+
tokens.push({
|
|
811
|
+
kind: "boundary",
|
|
812
|
+
value: boundary[0],
|
|
813
|
+
index
|
|
814
|
+
});
|
|
815
|
+
index += boundary[0].length;
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
const start = index;
|
|
819
|
+
const frames = [{
|
|
820
|
+
kind: "word",
|
|
821
|
+
quote: void 0,
|
|
822
|
+
depth: 0
|
|
823
|
+
}];
|
|
824
|
+
while (index < source.length) {
|
|
825
|
+
const frame = frames.at(-1);
|
|
826
|
+
const current = source.at(index);
|
|
827
|
+
if (frame === void 0 || current === void 0) break;
|
|
828
|
+
if (current === "\\" && frame.quote !== "'") {
|
|
829
|
+
index += 2;
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
if (current === "`" && frame.kind === "backticks") {
|
|
833
|
+
frames.pop();
|
|
834
|
+
index++;
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
if (current === frame.quote) {
|
|
838
|
+
frame.quote = void 0;
|
|
839
|
+
index++;
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
if (frame.quote === "'") {
|
|
843
|
+
index++;
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
if (current === "$" && source.at(index + 1) === "(") {
|
|
847
|
+
frames.push({
|
|
848
|
+
kind: "parentheses",
|
|
849
|
+
quote: void 0,
|
|
850
|
+
depth: 1
|
|
851
|
+
});
|
|
852
|
+
index += 2;
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
if (current === "`") {
|
|
856
|
+
frames.push({
|
|
857
|
+
kind: "backticks",
|
|
858
|
+
quote: void 0,
|
|
859
|
+
depth: 0
|
|
860
|
+
});
|
|
861
|
+
index++;
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
if (frame.quote !== void 0) {
|
|
865
|
+
index++;
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
868
|
+
if (current === "'" || current === "\"") {
|
|
869
|
+
frame.quote = current;
|
|
870
|
+
index++;
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
if (frame.kind === "parentheses") {
|
|
874
|
+
if (current === "(") frame.depth++;
|
|
875
|
+
if (current === ")" && --frame.depth === 0) frames.pop();
|
|
876
|
+
index++;
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
if (frame.kind === "word" && /[\s<>;&|()]/.test(current)) break;
|
|
880
|
+
index++;
|
|
881
|
+
}
|
|
882
|
+
const value = source.slice(start, index);
|
|
883
|
+
tokens.push({
|
|
884
|
+
kind: "word",
|
|
885
|
+
value,
|
|
886
|
+
index: start
|
|
887
|
+
});
|
|
759
888
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
889
|
+
let groupDepth = 0;
|
|
890
|
+
let redirectTarget = false;
|
|
891
|
+
const commandWords = [];
|
|
892
|
+
for (const token of tokens) {
|
|
893
|
+
if (token.kind === "comment") continue;
|
|
894
|
+
if (token.kind === "redirect") {
|
|
895
|
+
redirectTarget = true;
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
if (redirectTarget) {
|
|
899
|
+
redirectTarget = false;
|
|
900
|
+
if (token.kind === "word") continue;
|
|
901
|
+
}
|
|
902
|
+
if (token.kind === "boundary") {
|
|
903
|
+
commandWords.length = 0;
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
const functionBody = commandWords.length === 2 && commandWords.at(0) === "function";
|
|
907
|
+
const opensGroup = token.value === "{" && (commandWords.length === 0 || functionBody);
|
|
908
|
+
const closesGroup = token.value === "}" && groupDepth > 0 && commandWords.length === 0;
|
|
909
|
+
if (opensGroup || closesGroup) {
|
|
910
|
+
token.kind = "boundary";
|
|
911
|
+
groupDepth += opensGroup ? 1 : -1;
|
|
912
|
+
commandWords.length = 0;
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
if (commandWords.length === 0 && [
|
|
916
|
+
"if",
|
|
917
|
+
"then",
|
|
918
|
+
"elif",
|
|
919
|
+
"else",
|
|
920
|
+
"do",
|
|
921
|
+
"!",
|
|
922
|
+
"time"
|
|
923
|
+
].includes(token.value)) continue;
|
|
924
|
+
commandWords.push(token.value);
|
|
925
|
+
}
|
|
926
|
+
return tokens;
|
|
927
|
+
};
|
|
928
|
+
const shellArgumentTokens = (tokens) => {
|
|
929
|
+
const arguments_ = [];
|
|
930
|
+
let redirectTarget = false;
|
|
931
|
+
for (const token of tokens) {
|
|
932
|
+
if (token.kind === "comment") continue;
|
|
933
|
+
if (token.kind === "redirect") {
|
|
934
|
+
redirectTarget = true;
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
if (redirectTarget) {
|
|
938
|
+
redirectTarget = false;
|
|
939
|
+
if (token.kind === "word") continue;
|
|
940
|
+
}
|
|
941
|
+
arguments_.push(token);
|
|
942
|
+
}
|
|
943
|
+
return arguments_;
|
|
944
|
+
};
|
|
945
|
+
const shellReadsProgramStdin = (tokens) => {
|
|
946
|
+
const words = executableShellWords(shellArgumentTokens(tokens).map(({ value }) => value)).map(decodeShellWord);
|
|
947
|
+
if (!shellExecutable.test(words.at(0) ?? "")) return false;
|
|
948
|
+
let stdin = false;
|
|
949
|
+
let script = false;
|
|
950
|
+
for (let index = 1; index < words.length; index++) {
|
|
951
|
+
const option = words.at(index) ?? "";
|
|
952
|
+
if (option === "--" || option === "-") {
|
|
953
|
+
script = index + 1 < words.length;
|
|
954
|
+
break;
|
|
955
|
+
}
|
|
956
|
+
if (!/^[+-]/.test(option)) {
|
|
957
|
+
script = true;
|
|
958
|
+
break;
|
|
959
|
+
}
|
|
960
|
+
if (shellValueOptions.has(option)) {
|
|
961
|
+
index++;
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
if (/^-[^-]*c/.test(option)) return false;
|
|
965
|
+
if (/^-[^-]*s/.test(option)) stdin = true;
|
|
966
|
+
}
|
|
967
|
+
return stdin || !script;
|
|
968
|
+
};
|
|
969
|
+
const shellStdinHeredoc = (tokens) => {
|
|
970
|
+
const descriptors = /* @__PURE__ */ new Map();
|
|
971
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
972
|
+
const token = tokens.at(index);
|
|
973
|
+
if (token?.kind !== "redirect") continue;
|
|
974
|
+
const declaration = /^(\d*)(.+)$/.exec(token.value);
|
|
975
|
+
const operator = declaration?.[2];
|
|
976
|
+
if (operator === void 0) continue;
|
|
977
|
+
const descriptor = String(Number(declaration?.[1] || (operator.startsWith("<") ? "0" : "1")));
|
|
978
|
+
const target = tokens.at(++index);
|
|
979
|
+
if (operator === "<<" || operator === "<<-") {
|
|
980
|
+
descriptors.set(descriptor, token);
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
if (operator === "<&" || operator === ">&") {
|
|
984
|
+
const copy = /^(\d+)(-?)$/.exec(decodeShellWord(target?.value ?? ""));
|
|
985
|
+
const sourceDescriptor = copy?.[1] === void 0 ? void 0 : String(Number(copy[1]));
|
|
986
|
+
const source = sourceDescriptor === void 0 ? void 0 : descriptors.get(sourceDescriptor);
|
|
987
|
+
if (source === void 0) descriptors.delete(descriptor);
|
|
988
|
+
else descriptors.set(descriptor, source);
|
|
989
|
+
if (copy?.[2] === "-" && sourceDescriptor !== void 0) descriptors.delete(sourceDescriptor);
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
descriptors.delete(descriptor);
|
|
993
|
+
if (operator === "&>" || operator === "&>>") descriptors.delete("2");
|
|
994
|
+
}
|
|
995
|
+
return descriptors.get("0");
|
|
996
|
+
};
|
|
997
|
+
const shellExecutableSource = (command) => {
|
|
998
|
+
const pending = [];
|
|
999
|
+
return command.split(/\r?\n/).map((line) => {
|
|
1000
|
+
const here = pending.at(0);
|
|
1001
|
+
if (here !== void 0) {
|
|
1002
|
+
if ((here.stripTabs ? line.replace(/^\t+/, "") : line) === here.delimiter) {
|
|
1003
|
+
pending.shift();
|
|
1004
|
+
return "";
|
|
1005
|
+
}
|
|
1006
|
+
if (here.execute) return line;
|
|
1007
|
+
if (here.literal) return "";
|
|
1008
|
+
return shellSubstitutions(line).join("\n");
|
|
1009
|
+
}
|
|
1010
|
+
const words = shellTokens(line);
|
|
1011
|
+
let commandStart = 0;
|
|
1012
|
+
for (let index = 0; index < words.length; index++) {
|
|
1013
|
+
const match = words.at(index);
|
|
1014
|
+
const word = match?.value;
|
|
1015
|
+
if (word === void 0) continue;
|
|
1016
|
+
if (match?.kind === "comment") return line.slice(0, match.index);
|
|
1017
|
+
if (match?.kind === "boundary") {
|
|
1018
|
+
commandStart = index + 1;
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
const declaration = match?.kind === "redirect" ? /^(?:\d*)<<(-?)(.*)$/.exec(word) : null;
|
|
1022
|
+
let delimiter = declaration?.[2];
|
|
1023
|
+
if (delimiter === "") delimiter = words.at(++index)?.value;
|
|
1024
|
+
if (declaration !== null && delimiter !== void 0 && !delimiter.startsWith("<")) {
|
|
1025
|
+
const commandEnd = words.findIndex((entry, position) => position > index && (entry.kind === "boundary" || entry.kind === "comment"));
|
|
1026
|
+
const invocation = words.slice(commandStart, commandEnd < 0 ? void 0 : commandEnd);
|
|
1027
|
+
pending.push({
|
|
1028
|
+
delimiter: decodeShellWord(delimiter),
|
|
1029
|
+
stripTabs: declaration[1] === "-",
|
|
1030
|
+
literal: /['"\\]/.test(delimiter),
|
|
1031
|
+
execute: shellReadsProgramStdin(invocation) && shellStdinHeredoc(invocation) === match
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
return line;
|
|
1036
|
+
}).join("\n");
|
|
1037
|
+
};
|
|
1038
|
+
const executableShellWords = (words) => {
|
|
1039
|
+
let start = 0;
|
|
1040
|
+
const skipAssignments = () => {
|
|
1041
|
+
while (/^[A-Za-z_][A-Za-z\d_]*=/.test(decodeShellWord(words.at(start) ?? ""))) start += 1;
|
|
1042
|
+
};
|
|
1043
|
+
skipAssignments();
|
|
1044
|
+
while (/^(?:\/usr\/bin\/|\/bin\/)?env$/.test(decodeShellWord(words.at(start) ?? ""))) {
|
|
1045
|
+
start += 1;
|
|
1046
|
+
while (start < words.length) {
|
|
1047
|
+
const option = decodeShellWord(words.at(start) ?? "");
|
|
1048
|
+
if (option === "--") {
|
|
1049
|
+
start += 1;
|
|
1050
|
+
break;
|
|
1051
|
+
}
|
|
1052
|
+
if ([
|
|
1053
|
+
"-u",
|
|
1054
|
+
"--unset",
|
|
1055
|
+
"-C",
|
|
1056
|
+
"--chdir"
|
|
1057
|
+
].includes(option)) {
|
|
1058
|
+
start += 2;
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
if (option === "-S" || option === "--split-string" || option.startsWith("--split-string=") || option.startsWith("-S") && option.length > 2) {
|
|
1062
|
+
let source;
|
|
1063
|
+
if (option === "-S" || option === "--split-string") {
|
|
1064
|
+
source = decodeShellWord(words.at(start + 1) ?? "");
|
|
775
1065
|
start += 2;
|
|
776
|
-
|
|
1066
|
+
} else {
|
|
1067
|
+
source = option.startsWith("-S") ? option.slice(2) : option.slice(15);
|
|
1068
|
+
start++;
|
|
777
1069
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
1070
|
+
return executableShellWords([
|
|
1071
|
+
"env",
|
|
1072
|
+
...shellTokens(source).map(({ value }) => value),
|
|
1073
|
+
...words.slice(start)
|
|
1074
|
+
]);
|
|
783
1075
|
}
|
|
784
|
-
|
|
1076
|
+
if (option === "-" || option === "-i" || option === "--ignore-environment" || /^--(?:unset|chdir)=.+|^-[uC].+/.test(option)) {
|
|
1077
|
+
start += 1;
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
break;
|
|
785
1081
|
}
|
|
786
|
-
|
|
787
|
-
}
|
|
1082
|
+
skipAssignments();
|
|
1083
|
+
}
|
|
1084
|
+
return words.slice(start);
|
|
788
1085
|
};
|
|
1086
|
+
const shellCommandBody = (rawWords) => {
|
|
1087
|
+
const words = executableShellWords(rawWords.filter((word) => word !== "(" && word !== ")")).map(decodeShellWord);
|
|
1088
|
+
if (!shellExecutable.test(words.at(0) ?? "")) return void 0;
|
|
1089
|
+
for (let index = 1; index < words.length; index++) {
|
|
1090
|
+
const option = words.at(index);
|
|
1091
|
+
if (option === void 0 || option === "--" || !/^[+-]/.test(option)) return void 0;
|
|
1092
|
+
if (shellValueOptions.has(option)) {
|
|
1093
|
+
index++;
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
1096
|
+
if (/^-[^-]*c/.test(option)) return words.at(index + 1);
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
/** Shared executable shell words preserve assignments for runtime selectors. */
|
|
1100
|
+
const shellCommandSegments = (command, grouping = "boundaries") => {
|
|
1101
|
+
const segments = [[]];
|
|
1102
|
+
const executableSource = shellExecutableSource(command);
|
|
1103
|
+
const sources = /* @__PURE__ */ new Set([executableSource, ...shellSubstitutions(executableSource)]);
|
|
1104
|
+
for (const source of sources) {
|
|
1105
|
+
const firstSegment = segments.length;
|
|
1106
|
+
segments.push([]);
|
|
1107
|
+
for (const { kind, value: word } of shellArgumentTokens(shellTokens(source))) if (kind === "boundary") if ([
|
|
1108
|
+
"(",
|
|
1109
|
+
")",
|
|
1110
|
+
"{",
|
|
1111
|
+
"}"
|
|
1112
|
+
].includes(word) && grouping === "tokens") segments.at(-1)?.push(word);
|
|
1113
|
+
else segments.push([]);
|
|
1114
|
+
else segments.at(-1)?.push(word);
|
|
1115
|
+
for (const words of segments.slice(firstSegment)) {
|
|
1116
|
+
const body = shellCommandBody(words);
|
|
1117
|
+
if (body === void 0) continue;
|
|
1118
|
+
const executableBody = shellExecutableSource(body);
|
|
1119
|
+
sources.add(executableBody);
|
|
1120
|
+
for (const substitution of shellSubstitutions(executableBody)) sources.add(substitution);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
return segments;
|
|
1124
|
+
};
|
|
1125
|
+
const compilerCommandSegments = (command) => shellCommandSegments(command, "tokens").map(executableShellWords);
|
|
789
1126
|
const launcherValueOptions = new Map(Object.entries({
|
|
790
1127
|
npx: [
|
|
791
1128
|
"-p",
|
|
@@ -919,10 +1256,10 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
919
1256
|
const document = pnpm ? parseDocument(text, { lineCounter }) : void 0;
|
|
920
1257
|
if (document !== void 0 && document.errors.length > 0) throw new Error("invalid YAML");
|
|
921
1258
|
const json = document === void 0 ? JSON.parse(text) : document.toJS();
|
|
922
|
-
if (!record$
|
|
1259
|
+
if (!record$3(json)) throw new Error("manifest must be an object");
|
|
923
1260
|
if (pnpm) {
|
|
924
|
-
if (json["catalog"] !== void 0 && !record$
|
|
925
|
-
if (json["catalogs"] !== void 0 && (!record$
|
|
1261
|
+
if (json["catalog"] !== void 0 && !record$3(json["catalog"])) throw new Error("catalog must be a mapping");
|
|
1262
|
+
if (json["catalogs"] !== void 0 && (!record$3(json["catalogs"]) || Object.values(json["catalogs"]).some((catalog) => !record$3(catalog)))) throw new Error("named catalogs must be mappings");
|
|
926
1263
|
if (json["packages"] !== void 0 && (!Array.isArray(json["packages"]) || !json["packages"].every((pattern) => typeof pattern === "string"))) throw new Error("workspace packages must be patterns");
|
|
927
1264
|
}
|
|
928
1265
|
manifests.set(file, json);
|
|
@@ -936,13 +1273,13 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
936
1273
|
catalog: json["overrides"],
|
|
937
1274
|
prefix: ["overrides"]
|
|
938
1275
|
},
|
|
939
|
-
...record$
|
|
1276
|
+
...record$3(json["catalogs"]) ? Object.entries(json["catalogs"]).map(([name, catalog]) => ({
|
|
940
1277
|
catalog,
|
|
941
1278
|
prefix: ["catalogs", name]
|
|
942
1279
|
})) : []
|
|
943
1280
|
];
|
|
944
1281
|
for (const { catalog, prefix } of catalogEntries) {
|
|
945
|
-
if (!record$
|
|
1282
|
+
if (!record$3(catalog)) continue;
|
|
946
1283
|
for (const [name, value] of Object.entries(catalog)) {
|
|
947
1284
|
const node = document.getIn([...prefix, name], true);
|
|
948
1285
|
if (!isNode(node) || node.range === void 0 || node.range === null) continue;
|
|
@@ -961,7 +1298,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
961
1298
|
});
|
|
962
1299
|
}
|
|
963
1300
|
}
|
|
964
|
-
const catalogSources = (json) => [json, ...record$
|
|
1301
|
+
const catalogSources = (json) => [json, ...record$3(json["workspaces"]) ? [json["workspaces"]] : []];
|
|
965
1302
|
const pnpmWorkspaceFor = (file) => {
|
|
966
1303
|
let directory = path.posix.dirname(file);
|
|
967
1304
|
while (true) {
|
|
@@ -984,8 +1321,8 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
984
1321
|
patterns: workspace["packages"],
|
|
985
1322
|
rootMembership: "implicit"
|
|
986
1323
|
})) return void 0;
|
|
987
|
-
if (name === "" || name === "default") return workspace["catalog"] ?? (record$
|
|
988
|
-
return record$
|
|
1324
|
+
if (name === "" || name === "default") return workspace["catalog"] ?? (record$3(workspace["catalogs"]) ? workspace["catalogs"]["default"] : void 0);
|
|
1325
|
+
return record$3(workspace["catalogs"]) ? workspace["catalogs"][name] : void 0;
|
|
989
1326
|
}
|
|
990
1327
|
let directory = path.posix.dirname(file);
|
|
991
1328
|
while (true) {
|
|
@@ -993,7 +1330,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
993
1330
|
if (manifest !== void 0) {
|
|
994
1331
|
for (const source of catalogSources(manifest)) {
|
|
995
1332
|
if (name === "" && source["catalog"] !== void 0) return source["catalog"];
|
|
996
|
-
if (record$
|
|
1333
|
+
if (record$3(source["catalogs"]) && source["catalogs"][name] !== void 0) return source["catalogs"][name];
|
|
997
1334
|
}
|
|
998
1335
|
if (manifest["workspaces"] !== void 0) return void 0;
|
|
999
1336
|
}
|
|
@@ -1007,7 +1344,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1007
1344
|
if (visited.has(value)) return void 0;
|
|
1008
1345
|
visited.add(value);
|
|
1009
1346
|
const catalog = catalogFor(file, value.slice(8));
|
|
1010
|
-
value = record$
|
|
1347
|
+
value = record$3(catalog) ? catalog[name] : void 0;
|
|
1011
1348
|
}
|
|
1012
1349
|
return value;
|
|
1013
1350
|
};
|
|
@@ -1023,7 +1360,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1023
1360
|
const workspaces = manifests.get(path.posix.join(directory, "package.json"))?.["workspaces"];
|
|
1024
1361
|
if (workspaces !== void 0) return {
|
|
1025
1362
|
directory,
|
|
1026
|
-
patterns: record$
|
|
1363
|
+
patterns: record$3(workspaces) ? workspaces["packages"] : workspaces,
|
|
1027
1364
|
source: "json"
|
|
1028
1365
|
};
|
|
1029
1366
|
if (directory === ".") return void 0;
|
|
@@ -1079,7 +1416,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1079
1416
|
"peerDependencies"
|
|
1080
1417
|
]) {
|
|
1081
1418
|
const dependencies = json[section];
|
|
1082
|
-
if (!record$
|
|
1419
|
+
if (!record$3(dependencies)) continue;
|
|
1083
1420
|
for (const [name, value] of Object.entries(dependencies)) {
|
|
1084
1421
|
if (!tsSpecifiers.has(name)) continue;
|
|
1085
1422
|
const resolved = resolve(file, name, value);
|
|
@@ -1141,7 +1478,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1141
1478
|
message: `packageManager Bun version must be ${policy.bun}, found ${JSON.stringify(manager)}`
|
|
1142
1479
|
});
|
|
1143
1480
|
const checkEntries = (entries, location) => {
|
|
1144
|
-
if (!record$
|
|
1481
|
+
if (!record$3(entries)) return;
|
|
1145
1482
|
for (const [name, raw] of Object.entries(entries)) {
|
|
1146
1483
|
const value = resolve(file, name, raw);
|
|
1147
1484
|
const tsAllowed = tsSpecifiers.get(name);
|
|
@@ -1178,12 +1515,12 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1178
1515
|
"devDependencies",
|
|
1179
1516
|
"optionalDependencies"
|
|
1180
1517
|
]) checkEntries(json[key]);
|
|
1181
|
-
if (record$
|
|
1518
|
+
if (record$3(json["peerDependencies"])) {
|
|
1182
1519
|
for (const [name, value] of Object.entries(json["peerDependencies"])) if (tsSpecifiers.has(name) && !peerRange(resolve(file, name, value))) checkEntries({ [name]: value });
|
|
1183
1520
|
}
|
|
1184
1521
|
for (const source of catalogSources(json)) {
|
|
1185
1522
|
checkEntries(source["catalog"]);
|
|
1186
|
-
if (!record$
|
|
1523
|
+
if (!record$3(source["catalogs"])) continue;
|
|
1187
1524
|
for (const catalog of Object.values(source["catalogs"])) checkEntries(catalog);
|
|
1188
1525
|
}
|
|
1189
1526
|
const ownedNames = /* @__PURE__ */ new Set([...pins.keys(), ...tsSpecifiers.keys()]);
|
|
@@ -1195,7 +1532,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1195
1532
|
"optionalDependencies"
|
|
1196
1533
|
]) {
|
|
1197
1534
|
const entries = resolutionRoot[section];
|
|
1198
|
-
if (!record$
|
|
1535
|
+
if (!record$3(entries)) continue;
|
|
1199
1536
|
for (const [name, value] of Object.entries(entries)) resolutionDependencies[name] = resolve(file, name, value);
|
|
1200
1537
|
}
|
|
1201
1538
|
const affectedSpecifiers = /* @__PURE__ */ new Map();
|
|
@@ -1215,7 +1552,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1215
1552
|
"optionalDependencies"
|
|
1216
1553
|
]) {
|
|
1217
1554
|
const entries = member[section];
|
|
1218
|
-
if (!record$
|
|
1555
|
+
if (!record$3(entries)) continue;
|
|
1219
1556
|
for (const [name, value] of Object.entries(entries)) {
|
|
1220
1557
|
if (!tsSpecifiers.has(name)) continue;
|
|
1221
1558
|
const values = affectedSpecifiers.get(name) ?? /* @__PURE__ */ new Set();
|
|
@@ -1225,7 +1562,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1225
1562
|
}
|
|
1226
1563
|
}
|
|
1227
1564
|
const checkResolutionMap = (entries, parent) => {
|
|
1228
|
-
if (!record$
|
|
1565
|
+
if (!record$3(entries)) return;
|
|
1229
1566
|
for (const [selector, replacement] of Object.entries(entries)) {
|
|
1230
1567
|
const target = selector === "." ? parent : selector;
|
|
1231
1568
|
if (target === void 0) continue;
|
|
@@ -1248,7 +1585,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1248
1585
|
});
|
|
1249
1586
|
continue;
|
|
1250
1587
|
}
|
|
1251
|
-
if (record$
|
|
1588
|
+
if (record$3(replacement)) {
|
|
1252
1589
|
checkResolutionMap(replacement, target);
|
|
1253
1590
|
continue;
|
|
1254
1591
|
}
|
|
@@ -1282,7 +1619,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1282
1619
|
};
|
|
1283
1620
|
checkResolutionMap(json["overrides"]);
|
|
1284
1621
|
checkResolutionMap(json["resolutions"]);
|
|
1285
|
-
if (record$
|
|
1622
|
+
if (record$3(json["pnpm"])) checkResolutionMap(json["pnpm"]["overrides"]);
|
|
1286
1623
|
const localEntries = toolchainEntries(file, json);
|
|
1287
1624
|
const usesCompiler = policy.typescriptInstallLayouts.some((layout) => localEntries.some(({ name }) => name === layout.compilerPackage));
|
|
1288
1625
|
let selectedLayout = layoutFor(specifierMap(localEntries));
|
|
@@ -1306,7 +1643,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1306
1643
|
key: "dependencies",
|
|
1307
1644
|
message: "TypeScript compiler and compatibility packages must match one complete declared install layout"
|
|
1308
1645
|
});
|
|
1309
|
-
const command = record$
|
|
1646
|
+
const command = record$3(json["scripts"]) ? json["scripts"]["typecheck"] : void 0;
|
|
1310
1647
|
if (selectedLayout !== void 0 && typeof command === "string") {
|
|
1311
1648
|
const normalizedExpected = selectedLayout.typecheckCommand.replace(/\s+/g, " ").trim();
|
|
1312
1649
|
for (const words of compilerCommandSegments(command)) {
|
|
@@ -1324,7 +1661,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1324
1661
|
}
|
|
1325
1662
|
}
|
|
1326
1663
|
}
|
|
1327
|
-
if (record$
|
|
1664
|
+
if (record$3(json["peerDependencies"])) {
|
|
1328
1665
|
const peerLayout = selectedLayout ?? workspaceLayoutFor(file)?.layout;
|
|
1329
1666
|
for (const [name, raw] of Object.entries(json["peerDependencies"])) {
|
|
1330
1667
|
if (!tsSpecifiers.has(name)) continue;
|
|
@@ -1348,7 +1685,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1348
1685
|
});
|
|
1349
1686
|
}
|
|
1350
1687
|
}
|
|
1351
|
-
if (record$
|
|
1688
|
+
if (record$3(json["engines"]) && json["engines"]["node"] !== void 0 && (typeof json["engines"]["node"] !== "string" || !nodeSupportRangeMatches(policy.node, json["engines"]["node"]))) add({
|
|
1352
1689
|
file,
|
|
1353
1690
|
rule: "node-engine",
|
|
1354
1691
|
key: "node",
|
|
@@ -1359,6 +1696,232 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1359
1696
|
return diagnostics;
|
|
1360
1697
|
};
|
|
1361
1698
|
//#endregion
|
|
1699
|
+
//#region src/toolchain-rust.ts
|
|
1700
|
+
const record$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1701
|
+
/** Exceptions authorize one immutable release, never a channel or an entire rule. */
|
|
1702
|
+
const parseRustExceptions = (input, today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)) => {
|
|
1703
|
+
if (input === void 0) return [];
|
|
1704
|
+
if (!Array.isArray(input)) throw new Error("rustExceptions must be an array");
|
|
1705
|
+
const pins = /* @__PURE__ */ new Set();
|
|
1706
|
+
return input.map((entry) => {
|
|
1707
|
+
if (!record$2(entry) || Object.keys(entry).length !== 4 || Object.keys(entry).some((key) => ![
|
|
1708
|
+
"pin",
|
|
1709
|
+
"reason",
|
|
1710
|
+
"tracking",
|
|
1711
|
+
"expires"
|
|
1712
|
+
].includes(key)) || typeof entry["pin"] !== "string" || !/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/.test(entry["pin"]) || typeof entry["reason"] !== "string" || entry["reason"].trim() === "" || typeof entry["tracking"] !== "string" || !/^https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/issues\/[1-9]\d*$/.test(entry["tracking"]) || typeof entry["expires"] !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(entry["expires"]) || !Number.isFinite(Date.parse(entry["expires"])) || new Date(entry["expires"]).toISOString().slice(0, 10) !== entry["expires"] || entry["expires"] <= today || pins.has(entry["pin"])) throw new Error("each Rust exception requires a unique exact stable pin, reason, GitHub issue and future valid expiry date");
|
|
1713
|
+
pins.add(entry["pin"]);
|
|
1714
|
+
return {
|
|
1715
|
+
pin: entry["pin"],
|
|
1716
|
+
reason: entry["reason"],
|
|
1717
|
+
tracking: entry["tracking"],
|
|
1718
|
+
expires: entry["expires"]
|
|
1719
|
+
};
|
|
1720
|
+
});
|
|
1721
|
+
};
|
|
1722
|
+
/** Only executable fields are inspected: unrelated action inputs are not Rust selectors. */
|
|
1723
|
+
const checkRustReferences = (options) => {
|
|
1724
|
+
const { file, rust, exceptions } = options;
|
|
1725
|
+
const text = options.type === "file" ? options.text : "";
|
|
1726
|
+
const diagnostics = [];
|
|
1727
|
+
const select = (value, label, line = 1) => {
|
|
1728
|
+
if (value === rust || exceptions.some(({ pin }) => pin === value)) return;
|
|
1729
|
+
diagnostics.push({
|
|
1730
|
+
rule: "rust-version",
|
|
1731
|
+
path: file,
|
|
1732
|
+
line,
|
|
1733
|
+
message: `${label} must be Rust ${rust}, found ${String(value)}`
|
|
1734
|
+
});
|
|
1735
|
+
};
|
|
1736
|
+
const shell = (source, firstLine = 1) => {
|
|
1737
|
+
const commands = shellCommandSegments(source).map((words) => words.map((word) => ({
|
|
1738
|
+
value: decodeShellWord(word),
|
|
1739
|
+
line: firstLine
|
|
1740
|
+
})));
|
|
1741
|
+
for (const command of commands) {
|
|
1742
|
+
let position = 0;
|
|
1743
|
+
const prefixes = /* @__PURE__ */ new Set([
|
|
1744
|
+
"if",
|
|
1745
|
+
"then",
|
|
1746
|
+
"do",
|
|
1747
|
+
"elif",
|
|
1748
|
+
"!",
|
|
1749
|
+
"env",
|
|
1750
|
+
"sudo",
|
|
1751
|
+
"command",
|
|
1752
|
+
"exec",
|
|
1753
|
+
"time",
|
|
1754
|
+
"export",
|
|
1755
|
+
"readonly",
|
|
1756
|
+
"/usr/bin/env",
|
|
1757
|
+
"/bin/env"
|
|
1758
|
+
]);
|
|
1759
|
+
while (position < command.length) {
|
|
1760
|
+
const value = command.at(position)?.value;
|
|
1761
|
+
if (value?.startsWith("RUSTUP_TOOLCHAIN=")) select(value.slice(17), "RUSTUP_TOOLCHAIN", firstLine);
|
|
1762
|
+
if (value === "-u" || value === "--unset") {
|
|
1763
|
+
position += 2;
|
|
1764
|
+
continue;
|
|
1765
|
+
}
|
|
1766
|
+
if (value?.startsWith("--unset=") || value === "-i" || value === "--ignore-environment") {
|
|
1767
|
+
position++;
|
|
1768
|
+
continue;
|
|
1769
|
+
}
|
|
1770
|
+
if (value !== void 0 && (prefixes.has(value) || /^[A-Za-z_][A-Za-z_\d]*=/.test(value))) position++;
|
|
1771
|
+
else break;
|
|
1772
|
+
}
|
|
1773
|
+
const executable = command.at(position)?.value;
|
|
1774
|
+
const arguments_ = command.slice(position + 1);
|
|
1775
|
+
if (executable === "cargo" && arguments_.at(0)?.value.startsWith("+")) {
|
|
1776
|
+
const selector = arguments_.at(0);
|
|
1777
|
+
select(selector?.value.slice(1), "cargo toolchain", selector?.line);
|
|
1778
|
+
}
|
|
1779
|
+
if (executable !== "rustup") continue;
|
|
1780
|
+
let subcommandPosition = 0;
|
|
1781
|
+
while (subcommandPosition < arguments_.length) {
|
|
1782
|
+
const token = arguments_.at(subcommandPosition);
|
|
1783
|
+
if (token === void 0) break;
|
|
1784
|
+
if (token.value === "--") {
|
|
1785
|
+
subcommandPosition++;
|
|
1786
|
+
break;
|
|
1787
|
+
}
|
|
1788
|
+
if (token.value.startsWith("+")) select(token.value.slice(1), "rustup toolchain", token.line);
|
|
1789
|
+
else if (!token.value.startsWith("-")) break;
|
|
1790
|
+
subcommandPosition++;
|
|
1791
|
+
}
|
|
1792
|
+
const invocation = arguments_.slice(subcommandPosition);
|
|
1793
|
+
const subcommand = invocation.at(0)?.value;
|
|
1794
|
+
if (subcommand === "target" || subcommand === "component") {
|
|
1795
|
+
for (let index = 1; index < invocation.length; index++) {
|
|
1796
|
+
const token = invocation.at(index);
|
|
1797
|
+
if (token?.value === "--toolchain") select(invocation.at(++index)?.value, "rustup --toolchain", token.line);
|
|
1798
|
+
else if (token?.value.startsWith("--toolchain=")) select(token.value.slice(12), "rustup --toolchain", token.line);
|
|
1799
|
+
}
|
|
1800
|
+
continue;
|
|
1801
|
+
}
|
|
1802
|
+
let selectors;
|
|
1803
|
+
let requiresSelector = true;
|
|
1804
|
+
let firstSelectorOnly = false;
|
|
1805
|
+
if (subcommand === "default" || subcommand === "install" || subcommand === "add" || subcommand === "update") {
|
|
1806
|
+
selectors = invocation.slice(1);
|
|
1807
|
+
requiresSelector = subcommand !== "update";
|
|
1808
|
+
} else if (subcommand === "toolchain" && [
|
|
1809
|
+
"install",
|
|
1810
|
+
"add",
|
|
1811
|
+
"update"
|
|
1812
|
+
].includes(invocation.at(1)?.value ?? "")) {
|
|
1813
|
+
selectors = invocation.slice(2);
|
|
1814
|
+
requiresSelector = invocation.at(1)?.value !== "update";
|
|
1815
|
+
} else if (subcommand === "run") {
|
|
1816
|
+
selectors = invocation.slice(1);
|
|
1817
|
+
firstSelectorOnly = true;
|
|
1818
|
+
} else if (subcommand === "override" && invocation.at(1)?.value === "set") {
|
|
1819
|
+
selectors = invocation.slice(2);
|
|
1820
|
+
firstSelectorOnly = true;
|
|
1821
|
+
} else continue;
|
|
1822
|
+
let selected = 0;
|
|
1823
|
+
const optionArguments = /* @__PURE__ */ new Set([
|
|
1824
|
+
"--path",
|
|
1825
|
+
"--profile",
|
|
1826
|
+
"--component",
|
|
1827
|
+
"-c",
|
|
1828
|
+
"--target",
|
|
1829
|
+
"-t"
|
|
1830
|
+
]);
|
|
1831
|
+
for (let index = 0; index < selectors.length; index++) {
|
|
1832
|
+
const token = selectors.at(index);
|
|
1833
|
+
if (token === void 0) continue;
|
|
1834
|
+
if (optionArguments.has(token.value)) {
|
|
1835
|
+
index++;
|
|
1836
|
+
continue;
|
|
1837
|
+
}
|
|
1838
|
+
if (token.value.startsWith("-")) continue;
|
|
1839
|
+
selected++;
|
|
1840
|
+
select(token.value, "rustup toolchain", token.line);
|
|
1841
|
+
if (firstSelectorOnly) break;
|
|
1842
|
+
}
|
|
1843
|
+
if (selected === 0 && requiresSelector) select(void 0, "rustup toolchain", firstLine);
|
|
1844
|
+
}
|
|
1845
|
+
};
|
|
1846
|
+
if (options.type === "environment") {
|
|
1847
|
+
for (const { key, value } of options.assignments) if (key === "RUSTUP_TOOLCHAIN") select(value, key, options.line);
|
|
1848
|
+
return diagnostics;
|
|
1849
|
+
}
|
|
1850
|
+
if (options.type === "command") {
|
|
1851
|
+
shell(options.source, options.line);
|
|
1852
|
+
return diagnostics;
|
|
1853
|
+
}
|
|
1854
|
+
const lineOf = (token) => {
|
|
1855
|
+
const index = text.split(/\r?\n/).findIndex((line) => line.includes(token));
|
|
1856
|
+
return index < 0 ? 1 : index + 1;
|
|
1857
|
+
};
|
|
1858
|
+
if (isShellScript(file, text)) shell(text);
|
|
1859
|
+
if (/(?:^|\/)package\.json$/.test(file)) try {
|
|
1860
|
+
const object = JSON.parse(text);
|
|
1861
|
+
if (record$2(object) && record$2(object["scripts"])) {
|
|
1862
|
+
for (const [name, script] of Object.entries(object["scripts"])) if (typeof script === "string") shell(script, lineOf(`"${name}"`));
|
|
1863
|
+
}
|
|
1864
|
+
} catch {}
|
|
1865
|
+
if (/(?:^|\/)ci-service-images\.json$/.test(file)) {
|
|
1866
|
+
const images = (value) => {
|
|
1867
|
+
if (typeof value === "string" && /^rust(?=:|@|$)/.test(canonicalDockerRuntime(value))) {
|
|
1868
|
+
const tag = canonicalDockerRuntime(value).replace(/^rust:?/, "").split("@").at(0);
|
|
1869
|
+
const version = tag === void 0 ? void 0 : rustImageVersion(tag);
|
|
1870
|
+
select(version, "Rust service image", lineOf(value));
|
|
1871
|
+
} else if (Array.isArray(value)) value.forEach(images);
|
|
1872
|
+
else if (record$2(value)) Object.values(value).forEach(images);
|
|
1873
|
+
};
|
|
1874
|
+
try {
|
|
1875
|
+
images(JSON.parse(text));
|
|
1876
|
+
} catch {
|
|
1877
|
+
diagnostics.push({
|
|
1878
|
+
rule: "rust-version",
|
|
1879
|
+
path: file,
|
|
1880
|
+
line: 1,
|
|
1881
|
+
message: "invalid service image JSON"
|
|
1882
|
+
});
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
if (githubAutomationFileKind(file) === void 0) return diagnostics;
|
|
1886
|
+
const document = parseDocument(text);
|
|
1887
|
+
if (document.errors.length > 0) return diagnostics;
|
|
1888
|
+
try {
|
|
1889
|
+
const visit = (value) => {
|
|
1890
|
+
if (Array.isArray(value)) {
|
|
1891
|
+
value.forEach(visit);
|
|
1892
|
+
return;
|
|
1893
|
+
}
|
|
1894
|
+
if (!record$2(value)) return;
|
|
1895
|
+
if (record$2(value["env"]) && value["env"]["RUSTUP_TOOLCHAIN"] !== void 0) select(value["env"]["RUSTUP_TOOLCHAIN"], "RUSTUP_TOOLCHAIN", lineOf("RUSTUP_TOOLCHAIN"));
|
|
1896
|
+
if (typeof value["run"] === "string") shell(value["run"], lineOf("run:"));
|
|
1897
|
+
const uses = value["uses"];
|
|
1898
|
+
if (typeof uses === "string") {
|
|
1899
|
+
const action = uses.split("@").at(0)?.toLowerCase();
|
|
1900
|
+
const inputs = record$2(value["with"]) ? value["with"] : {};
|
|
1901
|
+
if (action === "dtolnay/rust-toolchain" || action === "actions-rust-lang/setup-rust-toolchain") {
|
|
1902
|
+
const entries = Object.entries(inputs).filter(([key]) => key.toLowerCase() === "toolchain");
|
|
1903
|
+
if (entries.length !== 1) select(void 0, `${action} requires an explicit exact toolchain input`, lineOf(uses));
|
|
1904
|
+
else {
|
|
1905
|
+
const selected = entries.at(0)?.at(1);
|
|
1906
|
+
const selectors = action === "actions-rust-lang/setup-rust-toolchain" && typeof selected === "string" ? selected.split(",").map((pin) => pin.trim()) : [selected];
|
|
1907
|
+
for (const selector of selectors) select(selector, `${action} toolchain`, lineOf("toolchain:"));
|
|
1908
|
+
}
|
|
1909
|
+
} else if (/(?:^|\/)\.github\/workflows\/[^@]+\.ya?ml(?:@|$)/.test(uses)) {
|
|
1910
|
+
for (const [key, selected] of Object.entries(inputs)) if (["toolchain", "rust-toolchain"].includes(key.toLowerCase())) select(selected, `reusable workflow ${key}`, lineOf(`${key}:`));
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1914
|
+
if (key === "workflow_call" && record$2(child) && record$2(child["inputs"])) {
|
|
1915
|
+
for (const [input, declaration] of Object.entries(child["inputs"])) if (["toolchain", "rust-toolchain"].includes(input) && record$2(declaration) && declaration["default"] !== void 0) select(declaration["default"], `reusable workflow default ${input}`, lineOf("default:"));
|
|
1916
|
+
}
|
|
1917
|
+
visit(child);
|
|
1918
|
+
}
|
|
1919
|
+
};
|
|
1920
|
+
visit(document.toJS({ maxAliasCount: 100 }));
|
|
1921
|
+
} catch {}
|
|
1922
|
+
return diagnostics;
|
|
1923
|
+
};
|
|
1924
|
+
//#endregion
|
|
1362
1925
|
//#region src/toolchain-runtime.ts
|
|
1363
1926
|
const runtimeRules = [
|
|
1364
1927
|
"bun-pins",
|
|
@@ -1371,6 +1934,69 @@ const runtimeRules = [
|
|
|
1371
1934
|
"action-pins"
|
|
1372
1935
|
];
|
|
1373
1936
|
const record$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1937
|
+
const dockerEnvironmentAssignments = ({ source, escape }) => {
|
|
1938
|
+
const words = [];
|
|
1939
|
+
let word = "";
|
|
1940
|
+
let quote;
|
|
1941
|
+
for (let index = 0; index < source.length; index++) {
|
|
1942
|
+
const character = source.at(index) ?? "";
|
|
1943
|
+
if (character === escape && quote !== "'") {
|
|
1944
|
+
word += character + (source.at(++index) ?? "");
|
|
1945
|
+
continue;
|
|
1946
|
+
}
|
|
1947
|
+
if (character === quote) quote = void 0;
|
|
1948
|
+
else if (quote === void 0 && (character === "'" || character === "\"")) quote = character;
|
|
1949
|
+
else if (quote === void 0 && /\s/.test(character)) {
|
|
1950
|
+
if (word !== "") words.push(word);
|
|
1951
|
+
word = "";
|
|
1952
|
+
continue;
|
|
1953
|
+
}
|
|
1954
|
+
word += character;
|
|
1955
|
+
}
|
|
1956
|
+
if (quote !== void 0) return void 0;
|
|
1957
|
+
if (word !== "") words.push(word);
|
|
1958
|
+
const decode = (raw) => {
|
|
1959
|
+
let value = "";
|
|
1960
|
+
let quoted;
|
|
1961
|
+
for (let index = 0; index < raw.length; index++) {
|
|
1962
|
+
const character = raw.at(index) ?? "";
|
|
1963
|
+
if (character === quoted) {
|
|
1964
|
+
quoted = void 0;
|
|
1965
|
+
continue;
|
|
1966
|
+
}
|
|
1967
|
+
if (quoted === void 0 && (character === "'" || character === "\"")) {
|
|
1968
|
+
quoted = character;
|
|
1969
|
+
continue;
|
|
1970
|
+
}
|
|
1971
|
+
const next = raw.at(index + 1);
|
|
1972
|
+
if (character === escape && quoted !== "'" && next !== void 0 && (quoted === void 0 || next === "\"" || next === "$" || next === escape)) {
|
|
1973
|
+
value += next;
|
|
1974
|
+
index++;
|
|
1975
|
+
} else value += character;
|
|
1976
|
+
}
|
|
1977
|
+
return value;
|
|
1978
|
+
};
|
|
1979
|
+
const first = words.at(0);
|
|
1980
|
+
if (first === void 0) return void 0;
|
|
1981
|
+
if (!first.includes("=")) {
|
|
1982
|
+
const legacy = /^(\S+)\s+([\s\S]*)$/.exec(source);
|
|
1983
|
+
if (legacy === null) return void 0;
|
|
1984
|
+
return [{
|
|
1985
|
+
key: decode(legacy[1] ?? ""),
|
|
1986
|
+
value: decode(legacy[2] ?? "")
|
|
1987
|
+
}];
|
|
1988
|
+
}
|
|
1989
|
+
const assignments = [];
|
|
1990
|
+
for (const raw of words) {
|
|
1991
|
+
const separator = raw.indexOf("=");
|
|
1992
|
+
if (separator < 1) return void 0;
|
|
1993
|
+
assignments.push({
|
|
1994
|
+
key: decode(raw.slice(0, separator)),
|
|
1995
|
+
value: decode(raw.slice(separator + 1))
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
return assignments;
|
|
1999
|
+
};
|
|
1374
2000
|
const pythonSelectorMatches = (value, expected) => value === expected || typeof value === "string" && /^\d+\.\d+$/.test(expected) && value.startsWith(`${expected}.`) && /^\d+\.\d+\.\d+$/.test(value);
|
|
1375
2001
|
const compareRelease = (left, right) => {
|
|
1376
2002
|
for (let index = 0; index < Math.max(left.length, right.length); index++) {
|
|
@@ -1440,8 +2066,14 @@ const pythonRequirementMatches = (requirement, version) => {
|
|
|
1440
2066
|
});
|
|
1441
2067
|
};
|
|
1442
2068
|
/** Check runtime declarations only in the files that declare or configure them. */
|
|
1443
|
-
const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, repository, onDelegated, dynamicSelectors = [], onDynamicSelector, engineFloors = [], onEngineFloor }) => {
|
|
1444
|
-
const diagnostics =
|
|
2069
|
+
const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, repository, onDelegated, dynamicSelectors = [], onDynamicSelector, rustExceptions = [], engineFloors = [], onEngineFloor }) => {
|
|
2070
|
+
const diagnostics = checkRustReferences({
|
|
2071
|
+
type: "file",
|
|
2072
|
+
file,
|
|
2073
|
+
text,
|
|
2074
|
+
rust: policy.rust,
|
|
2075
|
+
exceptions: rustExceptions
|
|
2076
|
+
});
|
|
1445
2077
|
const name = path.posix.basename(file);
|
|
1446
2078
|
const lines = text.split(/\r?\n/);
|
|
1447
2079
|
const add = ({ rule, line, message }) => {
|
|
@@ -1464,7 +2096,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1464
2096
|
};
|
|
1465
2097
|
const lineOf = (key) => Math.max(1, lines.findIndex((line) => line.includes(key)) + 1);
|
|
1466
2098
|
const pin = ({ rule, value, expected, label, line = 1, selector }) => {
|
|
1467
|
-
let matches = value === expected;
|
|
2099
|
+
let matches = value === expected || rule === "rust-version" && rustExceptions.some((entry) => entry.pin === value);
|
|
1468
2100
|
if (selector === "node") matches = nodeSelectorMatches(value, expected);
|
|
1469
2101
|
else if (label.includes("python")) matches = pythonSelectorMatches(value, expected);
|
|
1470
2102
|
if (!matches) add({
|
|
@@ -1534,12 +2166,10 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1534
2166
|
const parsed = toml("rust-version");
|
|
1535
2167
|
if (parsed !== void 0) {
|
|
1536
2168
|
const toolchain = parsed["toolchain"];
|
|
1537
|
-
const components = record$1(toolchain) ? toolchain["components"] : void 0;
|
|
1538
|
-
const expected = Array.isArray(components) && components.includes("rustc-dev") ? policy.rustCompilerDevelopment : policy.rust;
|
|
1539
2169
|
pin({
|
|
1540
2170
|
rule: "rust-version",
|
|
1541
2171
|
value: record$1(toolchain) ? toolchain["channel"] : void 0,
|
|
1542
|
-
expected,
|
|
2172
|
+
expected: policy.rust,
|
|
1543
2173
|
label: "Rust channel",
|
|
1544
2174
|
line: lineOf("channel")
|
|
1545
2175
|
});
|
|
@@ -1640,7 +2270,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1640
2270
|
if (record$1(tools)) for (const [tool, expected] of Object.entries(versions)) {
|
|
1641
2271
|
const configured = tools[tool];
|
|
1642
2272
|
if (configured !== void 0) pin({
|
|
1643
|
-
rule: "runtime-manager",
|
|
2273
|
+
rule: tool === "rust" || tool === "core:rust" ? "rust-version" : "runtime-manager",
|
|
1644
2274
|
value: record$1(configured) ? configured["version"] : configured,
|
|
1645
2275
|
expected,
|
|
1646
2276
|
selector: nodeTools.has(tool) ? "node" : void 0,
|
|
@@ -1655,7 +2285,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1655
2285
|
if (tool === void 0 || !(tool in versions)) return;
|
|
1656
2286
|
const expected = Object.entries(versions).find(([key]) => key === tool)?.[1];
|
|
1657
2287
|
if (expected !== void 0) pin({
|
|
1658
|
-
rule: "runtime-manager",
|
|
2288
|
+
rule: tool === "rust" || tool === "core:rust" ? "rust-version" : "runtime-manager",
|
|
1659
2289
|
value: fields.slice(1).join(" "),
|
|
1660
2290
|
expected,
|
|
1661
2291
|
selector: nodeTools.has(tool) ? "node" : void 0,
|
|
@@ -1665,11 +2295,12 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1665
2295
|
});
|
|
1666
2296
|
const checkRuntimeImage = ({ image, line, label, at }) => {
|
|
1667
2297
|
const unknownRuntime = () => {
|
|
1668
|
-
|
|
2298
|
+
const rustImage = typeof image === "string" && /^rust(?=:|@|$)/i.test(canonicalDockerRuntime(image));
|
|
2299
|
+
if (!rustImage && dynamic(image) && authorizeDynamic("image", line, at)) return;
|
|
1669
2300
|
add({
|
|
1670
|
-
rule: "runtime-docker",
|
|
2301
|
+
rule: rustImage ? "rust-version" : "runtime-docker",
|
|
1671
2302
|
line,
|
|
1672
|
-
message: `cannot determine ${label} runtime; use a static image or a scoped dynamicSelectors declaration`
|
|
2303
|
+
message: rustImage ? `cannot determine ${label} Rust release; use a static exact pin` : `cannot determine ${label} runtime; use a static image or a scoped dynamicSelectors declaration`
|
|
1673
2304
|
});
|
|
1674
2305
|
};
|
|
1675
2306
|
if (typeof image !== "string") {
|
|
@@ -1681,7 +2312,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1681
2312
|
const digestSeparator = structuralImage.indexOf("@");
|
|
1682
2313
|
const reference = digestSeparator < 0 ? canonical : canonical.slice(0, digestSeparator);
|
|
1683
2314
|
const structuralReference = digestSeparator < 0 ? structuralImage : structuralImage.slice(0, digestSeparator);
|
|
1684
|
-
const managedFamily = /^(node|python|oven\/bun)(?=:|$)/i.exec(structuralReference)?.[1];
|
|
2315
|
+
const managedFamily = /^(node|python|rust|oven\/bun)(?=:|$)/i.exec(structuralReference)?.[1];
|
|
1685
2316
|
const tagSeparator = managedFamily === void 0 ? structuralReference.lastIndexOf(":") : structuralReference.indexOf(":", managedFamily.length);
|
|
1686
2317
|
const hasTag = managedFamily === void 0 ? tagSeparator > structuralReference.lastIndexOf("/") : tagSeparator !== -1;
|
|
1687
2318
|
const imageRepository = hasTag ? reference.slice(0, tagSeparator) : reference;
|
|
@@ -1689,8 +2320,11 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1689
2320
|
unknownRuntime();
|
|
1690
2321
|
return;
|
|
1691
2322
|
}
|
|
1692
|
-
if (!/^(node|python|oven\/bun)$/i.test(imageRepository)) return;
|
|
2323
|
+
if (!/^(node|python|rust|oven\/bun)$/i.test(imageRepository)) return;
|
|
1693
2324
|
const tool = imageRepository.toLowerCase();
|
|
2325
|
+
let imageRule = "runtime-docker";
|
|
2326
|
+
if (tool === "rust") imageRule = "rust-version";
|
|
2327
|
+
else if (tool === "oven/bun") imageRule = "bun-pins";
|
|
1694
2328
|
if (image.includes("$")) {
|
|
1695
2329
|
unknownRuntime();
|
|
1696
2330
|
return;
|
|
@@ -1698,7 +2332,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1698
2332
|
const digest = digestSeparator < 0 ? void 0 : canonical.slice(digestSeparator + 1);
|
|
1699
2333
|
if (digest !== void 0 && !/^sha256:[a-fA-F\d]{64}$/.test(digest)) {
|
|
1700
2334
|
add({
|
|
1701
|
-
rule:
|
|
2335
|
+
rule: imageRule,
|
|
1702
2336
|
line,
|
|
1703
2337
|
message: `${label} ${tool} digest must be sha256 followed by 64 hexadecimal characters`
|
|
1704
2338
|
});
|
|
@@ -1706,14 +2340,15 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1706
2340
|
}
|
|
1707
2341
|
const tag = hasTag ? reference.slice(tagSeparator + 1) : void 0;
|
|
1708
2342
|
const variantSuffix = tool === "oven/bun" ? /-(?:alpine|slim|debian)$/ : /-(?:alpine(?:\d+(?:\.\d+)*)?|(?:bookworm|bullseye|trixie|buster)(?:-slim)?|slim)$/;
|
|
1709
|
-
const version = tag?.replace(variantSuffix, "");
|
|
2343
|
+
const version = tool === "rust" && tag !== void 0 ? rustImageVersion(tag) : tag?.replace(variantSuffix, "");
|
|
1710
2344
|
let expected = policy.bun;
|
|
1711
2345
|
if (tool === "node") expected = policy.node;
|
|
1712
2346
|
else if (tool === "python") expected = policy.python;
|
|
2347
|
+
else if (tool === "rust") expected = policy.rust;
|
|
1713
2348
|
if (digest !== void 0) {
|
|
1714
2349
|
if (version === void 0 || !/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/.test(version)) {
|
|
1715
2350
|
add({
|
|
1716
|
-
rule:
|
|
2351
|
+
rule: imageRule,
|
|
1717
2352
|
line,
|
|
1718
2353
|
message: `${label} ${tool} digest requires an exact stable patch tag matching ${expected}`
|
|
1719
2354
|
});
|
|
@@ -1731,7 +2366,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1731
2366
|
}
|
|
1732
2367
|
}
|
|
1733
2368
|
pin({
|
|
1734
|
-
rule:
|
|
2369
|
+
rule: imageRule,
|
|
1735
2370
|
value: version,
|
|
1736
2371
|
expected,
|
|
1737
2372
|
selector: tool === "node" ? "node" : void 0,
|
|
@@ -1748,6 +2383,17 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1748
2383
|
const heredocs = [];
|
|
1749
2384
|
let instruction = "";
|
|
1750
2385
|
let startLine = 1;
|
|
2386
|
+
let pendingRustRun;
|
|
2387
|
+
const checkRustCommand = (source, line) => {
|
|
2388
|
+
diagnostics.push(...checkRustReferences({
|
|
2389
|
+
type: "command",
|
|
2390
|
+
file,
|
|
2391
|
+
source,
|
|
2392
|
+
line,
|
|
2393
|
+
rust: policy.rust,
|
|
2394
|
+
exceptions: rustExceptions
|
|
2395
|
+
}));
|
|
2396
|
+
};
|
|
1751
2397
|
const expand = (value) => {
|
|
1752
2398
|
for (let count = 0; count <= variables.size; count++) {
|
|
1753
2399
|
const next = value.replace(/\$(?:\{([A-Za-z_][A-Za-z_\d]*)\}|([A-Za-z_][A-Za-z_\d]*))/g, (reference, braced, plain) => variables.get(braced ?? plain ?? "") ?? reference);
|
|
@@ -1834,7 +2480,49 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1834
2480
|
if (key !== void 0 && value !== void 0) variables.set(key, expand(value.replace(/^(["'])(.*)\1$/, "$2")));
|
|
1835
2481
|
return;
|
|
1836
2482
|
}
|
|
1837
|
-
|
|
2483
|
+
const run = /^\s*RUN\s+(.+)$/i.exec(content);
|
|
2484
|
+
if (run !== null) {
|
|
2485
|
+
let source = run[1] ?? "";
|
|
2486
|
+
source = source.replace(/^(?:--\S+\s+)*/, "");
|
|
2487
|
+
if (source.startsWith("[")) try {
|
|
2488
|
+
const words = JSON.parse(source);
|
|
2489
|
+
if (Array.isArray(words) && words.every((word) => typeof word === "string")) source = words.map((word) => `'${word.replaceAll("'", "'\\''")}'`).join(" ");
|
|
2490
|
+
} catch {}
|
|
2491
|
+
const implicit = /^(<<-?(?:'[^']+'|"[^"]+"|\S+))(?:\s+(.+))?$/.exec(source);
|
|
2492
|
+
if (implicit !== null && implicit[2] === void 0) source = `sh ${source}`;
|
|
2493
|
+
collectHeredocs(content, line);
|
|
2494
|
+
if (heredocs.length > 0) pendingRustRun = {
|
|
2495
|
+
source,
|
|
2496
|
+
line
|
|
2497
|
+
};
|
|
2498
|
+
else checkRustCommand(source, line);
|
|
2499
|
+
return;
|
|
2500
|
+
}
|
|
2501
|
+
const environment = /^\s*ENV\s+(.+)$/i.exec(content);
|
|
2502
|
+
if (environment !== null) {
|
|
2503
|
+
const assignments = dockerEnvironmentAssignments({
|
|
2504
|
+
source: environment[1] ?? "",
|
|
2505
|
+
escape
|
|
2506
|
+
});
|
|
2507
|
+
if (assignments === void 0) {
|
|
2508
|
+
add({
|
|
2509
|
+
rule: "runtime-docker",
|
|
2510
|
+
line,
|
|
2511
|
+
message: "invalid Docker ENV assignments"
|
|
2512
|
+
});
|
|
2513
|
+
return;
|
|
2514
|
+
}
|
|
2515
|
+
diagnostics.push(...checkRustReferences({
|
|
2516
|
+
type: "environment",
|
|
2517
|
+
file,
|
|
2518
|
+
assignments,
|
|
2519
|
+
line,
|
|
2520
|
+
rust: policy.rust,
|
|
2521
|
+
exceptions: rustExceptions
|
|
2522
|
+
}));
|
|
2523
|
+
return;
|
|
2524
|
+
}
|
|
2525
|
+
if (/^\s*COPY\s+/i.test(content)) {
|
|
1838
2526
|
collectHeredocs(content, line);
|
|
1839
2527
|
return;
|
|
1840
2528
|
}
|
|
@@ -1855,7 +2543,12 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1855
2543
|
lines.forEach((line, index) => {
|
|
1856
2544
|
const heredoc = heredocs.at(0);
|
|
1857
2545
|
if (heredoc !== void 0) {
|
|
2546
|
+
if (pendingRustRun !== void 0) pendingRustRun.source += `\n${line}`;
|
|
1858
2547
|
if ((heredoc.stripTabs ? line.replace(/^\t+/, "") : line) === heredoc.delimiter) heredocs.shift();
|
|
2548
|
+
if (heredocs.length === 0 && pendingRustRun !== void 0) {
|
|
2549
|
+
checkRustCommand(pendingRustRun.source, pendingRustRun.line);
|
|
2550
|
+
pendingRustRun = void 0;
|
|
2551
|
+
}
|
|
1859
2552
|
return;
|
|
1860
2553
|
}
|
|
1861
2554
|
if (line.trimStart().startsWith("#")) return;
|
|
@@ -2530,18 +3223,20 @@ const record = (value) => typeof value === "object" && value !== null && !Array.
|
|
|
2530
3223
|
const array = (value) => Array.isArray(value);
|
|
2531
3224
|
/** Repository-owned declarations are explicit; malformed decisions fail closed. */
|
|
2532
3225
|
const parseToolchainConfiguration = (input) => {
|
|
2533
|
-
if (!record(input) || Object.keys(input).some((key) => key !== "optOuts" && key !== "cloud" && key !== "engineFloors" && key !== "dynamicSelectors" && key !== "consumerChecks") || input["optOuts"] !== void 0 && !array(input["optOuts"])) throw new Error("stll-toolchain.json accepts only optOuts, cloud, engineFloors, dynamicSelectors and
|
|
3226
|
+
if (!record(input) || Object.keys(input).some((key) => key !== "optOuts" && key !== "cloud" && key !== "engineFloors" && key !== "dynamicSelectors" && key !== "consumerChecks" && key !== "rustExceptions") || input["optOuts"] !== void 0 && !array(input["optOuts"])) throw new Error("stll-toolchain.json accepts only optOuts, cloud, engineFloors, dynamicSelectors consumerChecks and rustExceptions declarations");
|
|
2534
3227
|
const disabled = /* @__PURE__ */ new Set();
|
|
2535
3228
|
const optOuts = input["optOuts"] ?? [];
|
|
2536
3229
|
if (!array(optOuts)) throw new Error("optOuts must be an array");
|
|
2537
3230
|
for (const entry of optOuts) {
|
|
2538
3231
|
if (!record(entry) || Object.keys(entry).some((key) => key !== "rule" && key !== "reason") || typeof entry["rule"] !== "string" || !toolchainRules.some((rule) => rule === entry["rule"]) || typeof entry["reason"] !== "string" || entry["reason"].trim() === "" || disabled.has(entry["rule"])) throw new Error("each opt-out requires a unique known rule and a nonempty reason");
|
|
3232
|
+
if (entry["rule"] === "rust-version") throw new Error("rust-version requires expiring exact-pin rustExceptions, not an opt-out");
|
|
2539
3233
|
if (entry["rule"] === "node-engine") throw new Error("node-engine cannot be opted out; engines.node must support the shared Node series");
|
|
2540
3234
|
if (entry["rule"] === "cloud-setup-drift") throw new Error("cloud-setup-drift cannot be opted out; generated setup must match shared policy");
|
|
2541
3235
|
disabled.add(entry["rule"]);
|
|
2542
3236
|
}
|
|
2543
3237
|
return {
|
|
2544
3238
|
disabled,
|
|
3239
|
+
rustExceptions: parseRustExceptions(input["rustExceptions"]),
|
|
2545
3240
|
cloud: parseCloudSetup(input["cloud"]),
|
|
2546
3241
|
engineFloors: parseEngineFloors(input["engineFloors"]),
|
|
2547
3242
|
dynamicSelectors: parseDynamicSelectors(input["dynamicSelectors"]),
|
|
@@ -2554,27 +3249,47 @@ const readToolchainInputs = (root) => {
|
|
|
2554
3249
|
const tracked = execFileSync("git", [
|
|
2555
3250
|
"ls-files",
|
|
2556
3251
|
"-z",
|
|
2557
|
-
"--cached"
|
|
3252
|
+
"--cached",
|
|
3253
|
+
"--stage"
|
|
2558
3254
|
], {
|
|
2559
3255
|
cwd: root,
|
|
2560
3256
|
encoding: "utf8"
|
|
2561
|
-
}).split("\0").filter((
|
|
3257
|
+
}).split("\0").filter((entry) => entry !== "" && !entry.startsWith("160000 ")).map((entry) => entry.slice(entry.indexOf(" ") + 1));
|
|
2562
3258
|
const files = {};
|
|
2563
3259
|
const trackedFiles = new Set(tracked);
|
|
2564
3260
|
const resolvedRoot = realpathSync(root);
|
|
2565
3261
|
for (const file of tracked) {
|
|
2566
3262
|
const kind = toolchainInputKind(file);
|
|
2567
|
-
|
|
3263
|
+
const shebangCandidate = kind === void 0 && path.posix.extname(file) === "" && !file.split("/").some((part) => part === "vendor" || part === "node_modules");
|
|
3264
|
+
if (kind === void 0 && !shebangCandidate) continue;
|
|
2568
3265
|
if (kind === "presence") {
|
|
2569
3266
|
files[file] = "";
|
|
2570
3267
|
continue;
|
|
2571
3268
|
}
|
|
2572
3269
|
try {
|
|
2573
|
-
const
|
|
3270
|
+
const inputPath = path.join(resolvedRoot, file);
|
|
3271
|
+
if (shebangCandidate) {
|
|
3272
|
+
const input = lstatSync(inputPath);
|
|
3273
|
+
if (!input.isFile() && !input.isSymbolicLink()) continue;
|
|
3274
|
+
}
|
|
3275
|
+
const resolvedFile = realpathSync(inputPath);
|
|
2574
3276
|
const relative = path.relative(resolvedRoot, resolvedFile);
|
|
2575
3277
|
if (path.isAbsolute(relative) || relative.split(path.sep).at(0) === ".." || !trackedFiles.has(relative.split(path.sep).join("/"))) throw new Error("Configuration target is outside the tracked tree");
|
|
3278
|
+
if (shebangCandidate) {
|
|
3279
|
+
if (!lstatSync(resolvedFile).isFile()) continue;
|
|
3280
|
+
const descriptor = openSync(resolvedFile, "r");
|
|
3281
|
+
const prefix = Buffer.alloc(512);
|
|
3282
|
+
let count;
|
|
3283
|
+
try {
|
|
3284
|
+
count = readSync(descriptor, prefix, 0, prefix.length, 0);
|
|
3285
|
+
} finally {
|
|
3286
|
+
closeSync(descriptor);
|
|
3287
|
+
}
|
|
3288
|
+
if (toolchainInputKind(file, prefix.toString("utf8", 0, count)) === void 0) continue;
|
|
3289
|
+
}
|
|
2576
3290
|
files[file] = readFileSync(resolvedFile, "utf8");
|
|
2577
3291
|
} catch {
|
|
3292
|
+
if (shebangCandidate) continue;
|
|
2578
3293
|
diagnostics.push({
|
|
2579
3294
|
rule: "configuration",
|
|
2580
3295
|
path: file,
|
|
@@ -2675,6 +3390,7 @@ const checkToolchain = ({ root, policy, onDelegated }) => {
|
|
|
2675
3390
|
}
|
|
2676
3391
|
},
|
|
2677
3392
|
trackedFiles,
|
|
3393
|
+
rustExceptions: configuration.rustExceptions,
|
|
2678
3394
|
dynamicSelectors: dynamicSelectors.filter((entry) => entry.path === file),
|
|
2679
3395
|
onDynamicSelector: (entry) => selectorMatches.set(entry, (selectorMatches.get(entry) ?? 0) + 1),
|
|
2680
3396
|
repository,
|