@stll/oxlint-config 0.14.1 → 1.0.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 +42 -1
- package/dist/cloud-setup-cli.mjs +1 -1
- package/dist/index.cjs +50 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -41
- package/dist/index.d.mts +46 -41
- package/dist/index.mjs +50 -41
- package/dist/index.mjs.map +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 +830 -121
- package/dist/toolchain-guard.cjs.map +1 -1
- package/dist/toolchain-guard.mjs +832 -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.0";
|
|
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,10 +256,12 @@ 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;
|
|
@@ -281,8 +283,8 @@ const workspaceTable = (parsed, ecosystem) => {
|
|
|
281
283
|
case "cargo": return parsed["workspace"];
|
|
282
284
|
case "uv": {
|
|
283
285
|
const tool = parsed["tool"];
|
|
284
|
-
const uv = record$
|
|
285
|
-
return record$
|
|
286
|
+
const uv = record$6(tool) ? tool["uv"] : void 0;
|
|
287
|
+
return record$6(uv) ? uv["workspace"] : void 0;
|
|
286
288
|
}
|
|
287
289
|
default: throw new Error("unknown workspace ecosystem", { cause: ecosystem });
|
|
288
290
|
}
|
|
@@ -302,7 +304,7 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
302
304
|
const document = parseDocument(text);
|
|
303
305
|
if (document.errors.length > 0) throw new Error(`${workspaceFile}:1: invalid pnpm workspace YAML`);
|
|
304
306
|
const parsed = document.toJS({ maxAliasCount: 100 });
|
|
305
|
-
if (!record$
|
|
307
|
+
if (!record$6(parsed)) throw new Error(`${workspaceFile}:1: pnpm workspace must contain an object`);
|
|
306
308
|
const patterns = parsed["packages"] ?? [];
|
|
307
309
|
if (!strings(patterns)) throw new Error(`${workspaceFile}:1: pnpm packages must be a string array`);
|
|
308
310
|
workspaces.set(directory, patterns.map((pattern) => pattern.startsWith("!") ? `!${path.posix.normalize(pattern.slice(1))}` : path.posix.normalize(pattern)));
|
|
@@ -314,13 +316,13 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
314
316
|
}
|
|
315
317
|
try {
|
|
316
318
|
const parsed = ecosystem === "npm" ? JSON.parse(files[file] ?? "") : parse$2(files[file] ?? "");
|
|
317
|
-
if (!record$
|
|
319
|
+
if (!record$6(parsed)) continue;
|
|
318
320
|
if (ecosystem === "npm" && typeof parsed["packageManager"] === "string" && parsed["packageManager"].startsWith("pnpm@")) continue;
|
|
319
321
|
const value = workspaceTable(parsed, ecosystem);
|
|
320
|
-
const patterns = record$
|
|
321
|
-
const members = ecosystem === "cargo" && record$
|
|
322
|
+
const patterns = record$6(value) ? value[ecosystem === "npm" ? "packages" : "members"] : value;
|
|
323
|
+
const members = ecosystem === "cargo" && record$6(value) && patterns === void 0 ? [] : patterns;
|
|
322
324
|
if (strings(members)) {
|
|
323
|
-
const excluded = ecosystem !== "npm" && record$
|
|
325
|
+
const excluded = ecosystem !== "npm" && record$6(value) && strings(value["exclude"]) ? value["exclude"].map((pattern) => `!${pattern}`) : [];
|
|
324
326
|
workspaces.set(path.posix.dirname(file), [...members, ...excluded]);
|
|
325
327
|
}
|
|
326
328
|
} catch {}
|
|
@@ -371,11 +373,11 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
371
373
|
for (const directory of memberDirectories) try {
|
|
372
374
|
const parsed = parse$2(files[path.posix.join(directory, "Cargo.toml")] ?? "");
|
|
373
375
|
const workspace = parse$2(files[path.posix.join(root, "Cargo.toml")] ?? "")["workspace"];
|
|
374
|
-
const inherited = record$
|
|
376
|
+
const inherited = record$6(workspace) ? workspace["dependencies"] : void 0;
|
|
375
377
|
const dependencyOwners = [parsed];
|
|
376
378
|
const targets = parsed["target"];
|
|
377
|
-
if (record$
|
|
378
|
-
for (const target of Object.values(targets)) if (record$
|
|
379
|
+
if (record$6(targets)) {
|
|
380
|
+
for (const target of Object.values(targets)) if (record$6(target)) dependencyOwners.push(target);
|
|
379
381
|
}
|
|
380
382
|
for (const owner of dependencyOwners) for (const key of [
|
|
381
383
|
"dependencies",
|
|
@@ -383,12 +385,12 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
383
385
|
"build-dependencies"
|
|
384
386
|
]) {
|
|
385
387
|
const dependencies = owner[key];
|
|
386
|
-
if (!record$
|
|
388
|
+
if (!record$6(dependencies)) continue;
|
|
387
389
|
for (const [name, dependency] of Object.entries(dependencies)) {
|
|
388
|
-
if (!record$
|
|
390
|
+
if (!record$6(dependency)) continue;
|
|
389
391
|
const isInherited = dependency["workspace"] === true;
|
|
390
|
-
const resolved = isInherited && record$
|
|
391
|
-
if (!record$
|
|
392
|
+
const resolved = isInherited && record$6(inherited) ? inherited[name] : dependency;
|
|
393
|
+
if (!record$6(resolved) || typeof resolved["path"] !== "string") continue;
|
|
392
394
|
if (path.posix.isAbsolute(resolved["path"]) || /\\|\$|^[A-Za-z]:/.test(resolved["path"])) continue;
|
|
393
395
|
const target = path.posix.normalize(path.posix.join(isInherited ? root : directory, resolved["path"]));
|
|
394
396
|
const relative = path.posix.relative(root, target);
|
|
@@ -416,7 +418,7 @@ const javascriptEcosystem = (directory, files) => {
|
|
|
416
418
|
if (files[path.posix.join(root, "bun.lock")] !== void 0) return "bun";
|
|
417
419
|
try {
|
|
418
420
|
const manifest = JSON.parse(files[path.posix.join(root, "package.json")] ?? "{}");
|
|
419
|
-
if (record$
|
|
421
|
+
if (record$6(manifest) && typeof manifest["packageManager"] === "string" && manifest["packageManager"].startsWith("bun@")) return "bun";
|
|
420
422
|
} catch {}
|
|
421
423
|
return "npm";
|
|
422
424
|
};
|
|
@@ -428,7 +430,7 @@ const pythonEcosystem = (file, files) => {
|
|
|
428
430
|
const pyproject = files[path.posix.join(root, "pyproject.toml")];
|
|
429
431
|
if (pyproject !== void 0) try {
|
|
430
432
|
const tool = parse$2(pyproject)["tool"];
|
|
431
|
-
if (record$
|
|
433
|
+
if (record$6(tool) && record$6(tool["uv"])) return "uv";
|
|
432
434
|
} catch {}
|
|
433
435
|
return "pip";
|
|
434
436
|
};
|
|
@@ -499,7 +501,7 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
499
501
|
add("cannot resolve Dependabot YAML aliases");
|
|
500
502
|
return diagnostics;
|
|
501
503
|
}
|
|
502
|
-
if (!record$
|
|
504
|
+
if (!record$6(parsed) || parsed["version"] !== 2 || !Array.isArray(parsed["updates"])) {
|
|
503
505
|
add("Dependabot must declare version: 2 and an updates array");
|
|
504
506
|
return diagnostics;
|
|
505
507
|
}
|
|
@@ -512,7 +514,7 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
512
514
|
});
|
|
513
515
|
if (legacyDocument.errors.length === 0) legacy = legacyDocument.toJS({ maxAliasCount: 100 });
|
|
514
516
|
} catch {}
|
|
515
|
-
const legacyUpdates = record$
|
|
517
|
+
const legacyUpdates = record$6(legacy) && Array.isArray(legacy["updates"]) ? legacy["updates"] : [];
|
|
516
518
|
const rootClaims = /* @__PURE__ */ new Map();
|
|
517
519
|
const directoryClaims = /* @__PURE__ */ new Map();
|
|
518
520
|
for (const [index, update] of parsed["updates"].entries()) {
|
|
@@ -526,7 +528,7 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
526
528
|
return offset === void 0 ? 1 : lineCounter.linePos(offset).line;
|
|
527
529
|
};
|
|
528
530
|
const report = (key, message) => add(message, lineOf(key));
|
|
529
|
-
if (!record$
|
|
531
|
+
if (!record$6(update) || typeof update["package-ecosystem"] !== "string") {
|
|
530
532
|
report("package-ecosystem", "each Dependabot update must declare a package ecosystem");
|
|
531
533
|
continue;
|
|
532
534
|
}
|
|
@@ -560,21 +562,21 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
560
562
|
covered.set(ecosystem, matches);
|
|
561
563
|
const schedule = update["schedule"];
|
|
562
564
|
const legacyUpdate = legacyUpdates.at(index);
|
|
563
|
-
const legacySchedule = record$
|
|
564
|
-
if (record$
|
|
565
|
-
if (!record$
|
|
565
|
+
const legacySchedule = record$6(legacyUpdate) ? legacyUpdate["schedule"] : void 0;
|
|
566
|
+
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`);
|
|
567
|
+
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
568
|
const cooldown = update["cooldown"];
|
|
567
|
-
if (!record$
|
|
569
|
+
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
570
|
const groups = update["groups"];
|
|
569
|
-
if (!record$
|
|
571
|
+
if (!record$6(groups) || Object.keys(groups).length !== Object.keys(policy.groups).length || Object.entries(policy.groups).some(([name, expected]) => {
|
|
570
572
|
const group = groups[name];
|
|
571
|
-
return !record$
|
|
573
|
+
return !record$6(group) || Object.keys(group).length !== 2 || !sameStrings(group["patterns"], expected.patterns) || !sameStrings(group["update-types"], expected.updateTypes);
|
|
572
574
|
})) report("groups", `${ecosystem}: groups must match the shared policy`);
|
|
573
575
|
const ignored = ignoredFor(ecosystem, policy);
|
|
574
576
|
const ignore = update["ignore"];
|
|
575
577
|
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
|
-
for (const name of ignored) if (!Array.isArray(ignore) || !ignore.some((entry) => record$
|
|
578
|
+
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`);
|
|
579
|
+
for (const name of ignored) if (!Array.isArray(ignore) || !ignore.some((entry) => record$6(entry) && entry["dependency-name"] === name && Object.keys(entry).length === 1)) report("ignore", `${ecosystem}: ignore all updates for shared pin ${name}`);
|
|
578
580
|
}
|
|
579
581
|
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
582
|
return diagnostics;
|
|
@@ -586,7 +588,7 @@ const locatorValidators = {
|
|
|
586
588
|
"bun-source": /^(?:jobs\.[\w-]+|runs)\.steps\.[\w-]+$/
|
|
587
589
|
};
|
|
588
590
|
const knownKind = (value) => typeof value === "string" && Object.hasOwn(locatorValidators, value);
|
|
589
|
-
const record$
|
|
591
|
+
const record$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
590
592
|
/** One reviewed declaration identifies one unresolved selector, never a rule. */
|
|
591
593
|
const parseDynamicSelectors = (input) => {
|
|
592
594
|
if (input === void 0) return [];
|
|
@@ -594,7 +596,7 @@ const parseDynamicSelectors = (input) => {
|
|
|
594
596
|
const result = [];
|
|
595
597
|
const keys = /* @__PURE__ */ new Set();
|
|
596
598
|
for (const entry of input) {
|
|
597
|
-
if (!record$
|
|
599
|
+
if (!record$5(entry) || Object.keys(entry).some((key) => ![
|
|
598
600
|
"path",
|
|
599
601
|
"kind",
|
|
600
602
|
"reason",
|
|
@@ -630,7 +632,7 @@ const parseDynamicSelectors = (input) => {
|
|
|
630
632
|
};
|
|
631
633
|
//#endregion
|
|
632
634
|
//#region src/toolchain-engine-floors.ts
|
|
633
|
-
const record$
|
|
635
|
+
const record$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
634
636
|
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
637
|
const parseEngineFloors = (input) => {
|
|
636
638
|
if (input === void 0) return [];
|
|
@@ -638,7 +640,7 @@ const parseEngineFloors = (input) => {
|
|
|
638
640
|
const result = [];
|
|
639
641
|
const jobs = /* @__PURE__ */ new Set();
|
|
640
642
|
for (const entry of input) {
|
|
641
|
-
if (!record$
|
|
643
|
+
if (!record$4(entry) || Object.keys(entry).some((key) => ![
|
|
642
644
|
"package",
|
|
643
645
|
"workflow",
|
|
644
646
|
"job"
|
|
@@ -659,16 +661,16 @@ const resolveEngineFloor = (entry, files) => {
|
|
|
659
661
|
const workflowText = files[entry.workflow];
|
|
660
662
|
if (manifestText === void 0 || workflowText === void 0) throw new Error("engine floor package and workflow must be tracked");
|
|
661
663
|
const manifest = JSON.parse(manifestText);
|
|
662
|
-
if (!record$
|
|
664
|
+
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
665
|
const range = manifest["engines"]["node"];
|
|
664
666
|
const minimum = validRange(range) === null ? null : minVersion(range);
|
|
665
667
|
if (minimum === null || minimum.prerelease.length !== 0) throw new Error("engine floor engines.node must declare a stable minimum");
|
|
666
668
|
const document = parseDocument(workflowText, { merge: true });
|
|
667
669
|
if (document.errors.length !== 0) throw new Error("engine floor workflow must be valid YAML");
|
|
668
670
|
const workflow = document.toJS({ maxAliasCount: 100 });
|
|
669
|
-
if (!record$
|
|
671
|
+
if (!record$4(workflow) || !record$4(workflow["jobs"])) throw new Error("engine floor job must exist in its tracked workflow");
|
|
670
672
|
const job = workflow["jobs"][entry.job];
|
|
671
|
-
if (!record$
|
|
673
|
+
if (!record$4(job)) throw new Error("engine floor job must exist in its tracked workflow");
|
|
672
674
|
if (!Array.isArray(job["steps"]) || Object.hasOwn(job, "uses")) throw new Error("engine floor job must declare steps and cannot reuse a workflow");
|
|
673
675
|
return {
|
|
674
676
|
...entry,
|
|
@@ -688,7 +690,7 @@ const packageRules = [
|
|
|
688
690
|
"typescript-layout",
|
|
689
691
|
"node-engine"
|
|
690
692
|
];
|
|
691
|
-
const record$
|
|
693
|
+
const record$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
692
694
|
const shellSubstitutions = (command) => {
|
|
693
695
|
const frames = [{
|
|
694
696
|
kind: "root",
|
|
@@ -749,43 +751,371 @@ const shellSubstitutions = (command) => {
|
|
|
749
751
|
for (const frame of frames.slice(1)) bodies.push(command.slice(frame.start));
|
|
750
752
|
return bodies;
|
|
751
753
|
};
|
|
752
|
-
const
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
754
|
+
const shellValueOptions = /* @__PURE__ */ new Set([
|
|
755
|
+
"--rcfile",
|
|
756
|
+
"--init-file",
|
|
757
|
+
"-o",
|
|
758
|
+
"+o",
|
|
759
|
+
"-O",
|
|
760
|
+
"+O"
|
|
761
|
+
]);
|
|
762
|
+
const shellExecutable = /^(?:\/(?:[^/]+\/)*)?(?:bash|dash|ksh|zsh|sh)$/;
|
|
763
|
+
const shellTokens = (source) => {
|
|
764
|
+
const tokens = [];
|
|
765
|
+
for (let index = 0; index < source.length;) {
|
|
766
|
+
const character = source.at(index);
|
|
767
|
+
if (character === void 0) break;
|
|
768
|
+
if (character === "\n") {
|
|
769
|
+
tokens.push({
|
|
770
|
+
kind: "boundary",
|
|
771
|
+
value: character,
|
|
772
|
+
index: index++
|
|
773
|
+
});
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
if (/\s/.test(character)) {
|
|
777
|
+
index++;
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
if (character === "#") {
|
|
781
|
+
const end = source.indexOf("\n", index);
|
|
782
|
+
const stop = end < 0 ? source.length : end;
|
|
783
|
+
tokens.push({
|
|
784
|
+
kind: "comment",
|
|
785
|
+
value: source.slice(index, stop),
|
|
786
|
+
index
|
|
787
|
+
});
|
|
788
|
+
index = stop;
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
const redirect = /^(?:\d*)(?:&>>|&>|<<<|<<-|<<|>>|>\||>&|<&|<>|>|<)/.exec(source.slice(index));
|
|
792
|
+
if (redirect !== null) {
|
|
793
|
+
tokens.push({
|
|
794
|
+
kind: "redirect",
|
|
795
|
+
value: redirect[0],
|
|
796
|
+
index
|
|
797
|
+
});
|
|
798
|
+
index += redirect[0].length;
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
const boundary = /^(?:&&|\|\||[();&|])/.exec(source.slice(index));
|
|
802
|
+
if (boundary !== null) {
|
|
803
|
+
tokens.push({
|
|
804
|
+
kind: "boundary",
|
|
805
|
+
value: boundary[0],
|
|
806
|
+
index
|
|
807
|
+
});
|
|
808
|
+
index += boundary[0].length;
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
const start = index;
|
|
812
|
+
const frames = [{
|
|
813
|
+
kind: "word",
|
|
814
|
+
quote: void 0,
|
|
815
|
+
depth: 0
|
|
816
|
+
}];
|
|
817
|
+
while (index < source.length) {
|
|
818
|
+
const frame = frames.at(-1);
|
|
819
|
+
const current = source.at(index);
|
|
820
|
+
if (frame === void 0 || current === void 0) break;
|
|
821
|
+
if (current === "\\" && frame.quote !== "'") {
|
|
822
|
+
index += 2;
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
if (current === "`" && frame.kind === "backticks") {
|
|
826
|
+
frames.pop();
|
|
827
|
+
index++;
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
if (current === frame.quote) {
|
|
831
|
+
frame.quote = void 0;
|
|
832
|
+
index++;
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
if (frame.quote === "'") {
|
|
836
|
+
index++;
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
if (current === "$" && source.at(index + 1) === "(") {
|
|
840
|
+
frames.push({
|
|
841
|
+
kind: "parentheses",
|
|
842
|
+
quote: void 0,
|
|
843
|
+
depth: 1
|
|
844
|
+
});
|
|
845
|
+
index += 2;
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
if (current === "`") {
|
|
849
|
+
frames.push({
|
|
850
|
+
kind: "backticks",
|
|
851
|
+
quote: void 0,
|
|
852
|
+
depth: 0
|
|
853
|
+
});
|
|
854
|
+
index++;
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
if (frame.quote !== void 0) {
|
|
858
|
+
index++;
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
if (current === "'" || current === "\"") {
|
|
862
|
+
frame.quote = current;
|
|
863
|
+
index++;
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
if (frame.kind === "parentheses") {
|
|
867
|
+
if (current === "(") frame.depth++;
|
|
868
|
+
if (current === ")" && --frame.depth === 0) frames.pop();
|
|
869
|
+
index++;
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
if (frame.kind === "word" && /[\s<>;&|()]/.test(current)) break;
|
|
873
|
+
index++;
|
|
874
|
+
}
|
|
875
|
+
const value = source.slice(start, index);
|
|
876
|
+
tokens.push({
|
|
877
|
+
kind: "word",
|
|
878
|
+
value,
|
|
879
|
+
index: start
|
|
880
|
+
});
|
|
759
881
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
882
|
+
let groupDepth = 0;
|
|
883
|
+
let redirectTarget = false;
|
|
884
|
+
const commandWords = [];
|
|
885
|
+
for (const token of tokens) {
|
|
886
|
+
if (token.kind === "comment") continue;
|
|
887
|
+
if (token.kind === "redirect") {
|
|
888
|
+
redirectTarget = true;
|
|
889
|
+
continue;
|
|
890
|
+
}
|
|
891
|
+
if (redirectTarget) {
|
|
892
|
+
redirectTarget = false;
|
|
893
|
+
if (token.kind === "word") continue;
|
|
894
|
+
}
|
|
895
|
+
if (token.kind === "boundary") {
|
|
896
|
+
commandWords.length = 0;
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
const functionBody = commandWords.length === 2 && commandWords.at(0) === "function";
|
|
900
|
+
const opensGroup = token.value === "{" && (commandWords.length === 0 || functionBody);
|
|
901
|
+
const closesGroup = token.value === "}" && groupDepth > 0 && commandWords.length === 0;
|
|
902
|
+
if (opensGroup || closesGroup) {
|
|
903
|
+
token.kind = "boundary";
|
|
904
|
+
groupDepth += opensGroup ? 1 : -1;
|
|
905
|
+
commandWords.length = 0;
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
if (commandWords.length === 0 && [
|
|
909
|
+
"if",
|
|
910
|
+
"then",
|
|
911
|
+
"elif",
|
|
912
|
+
"else",
|
|
913
|
+
"do",
|
|
914
|
+
"!",
|
|
915
|
+
"time"
|
|
916
|
+
].includes(token.value)) continue;
|
|
917
|
+
commandWords.push(token.value);
|
|
918
|
+
}
|
|
919
|
+
return tokens;
|
|
920
|
+
};
|
|
921
|
+
const shellArgumentTokens = (tokens) => {
|
|
922
|
+
const arguments_ = [];
|
|
923
|
+
let redirectTarget = false;
|
|
924
|
+
for (const token of tokens) {
|
|
925
|
+
if (token.kind === "comment") continue;
|
|
926
|
+
if (token.kind === "redirect") {
|
|
927
|
+
redirectTarget = true;
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
if (redirectTarget) {
|
|
931
|
+
redirectTarget = false;
|
|
932
|
+
if (token.kind === "word") continue;
|
|
933
|
+
}
|
|
934
|
+
arguments_.push(token);
|
|
935
|
+
}
|
|
936
|
+
return arguments_;
|
|
937
|
+
};
|
|
938
|
+
const shellReadsProgramStdin = (tokens) => {
|
|
939
|
+
const words = executableShellWords(shellArgumentTokens(tokens).map(({ value }) => value)).map(decodeShellWord);
|
|
940
|
+
if (!shellExecutable.test(words.at(0) ?? "")) return false;
|
|
941
|
+
let stdin = false;
|
|
942
|
+
let script = false;
|
|
943
|
+
for (let index = 1; index < words.length; index++) {
|
|
944
|
+
const option = words.at(index) ?? "";
|
|
945
|
+
if (option === "--" || option === "-") {
|
|
946
|
+
script = index + 1 < words.length;
|
|
947
|
+
break;
|
|
948
|
+
}
|
|
949
|
+
if (!/^[+-]/.test(option)) {
|
|
950
|
+
script = true;
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
if (shellValueOptions.has(option)) {
|
|
954
|
+
index++;
|
|
955
|
+
continue;
|
|
956
|
+
}
|
|
957
|
+
if (/^-[^-]*c/.test(option)) return false;
|
|
958
|
+
if (/^-[^-]*s/.test(option)) stdin = true;
|
|
959
|
+
}
|
|
960
|
+
return stdin || !script;
|
|
961
|
+
};
|
|
962
|
+
const shellStdinHeredoc = (tokens) => {
|
|
963
|
+
const descriptors = /* @__PURE__ */ new Map();
|
|
964
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
965
|
+
const token = tokens.at(index);
|
|
966
|
+
if (token?.kind !== "redirect") continue;
|
|
967
|
+
const declaration = /^(\d*)(.+)$/.exec(token.value);
|
|
968
|
+
const operator = declaration?.[2];
|
|
969
|
+
if (operator === void 0) continue;
|
|
970
|
+
const descriptor = String(Number(declaration?.[1] || (operator.startsWith("<") ? "0" : "1")));
|
|
971
|
+
const target = tokens.at(++index);
|
|
972
|
+
if (operator === "<<" || operator === "<<-") {
|
|
973
|
+
descriptors.set(descriptor, token);
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
976
|
+
if (operator === "<&" || operator === ">&") {
|
|
977
|
+
const copy = /^(\d+)(-?)$/.exec(decodeShellWord(target?.value ?? ""));
|
|
978
|
+
const sourceDescriptor = copy?.[1] === void 0 ? void 0 : String(Number(copy[1]));
|
|
979
|
+
const source = sourceDescriptor === void 0 ? void 0 : descriptors.get(sourceDescriptor);
|
|
980
|
+
if (source === void 0) descriptors.delete(descriptor);
|
|
981
|
+
else descriptors.set(descriptor, source);
|
|
982
|
+
if (copy?.[2] === "-" && sourceDescriptor !== void 0) descriptors.delete(sourceDescriptor);
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
985
|
+
descriptors.delete(descriptor);
|
|
986
|
+
if (operator === "&>" || operator === "&>>") descriptors.delete("2");
|
|
987
|
+
}
|
|
988
|
+
return descriptors.get("0");
|
|
989
|
+
};
|
|
990
|
+
const shellExecutableSource = (command) => {
|
|
991
|
+
const pending = [];
|
|
992
|
+
return command.split(/\r?\n/).map((line) => {
|
|
993
|
+
const here = pending.at(0);
|
|
994
|
+
if (here !== void 0) {
|
|
995
|
+
if ((here.stripTabs ? line.replace(/^\t+/, "") : line) === here.delimiter) {
|
|
996
|
+
pending.shift();
|
|
997
|
+
return "";
|
|
998
|
+
}
|
|
999
|
+
if (here.execute) return line;
|
|
1000
|
+
if (here.literal) return "";
|
|
1001
|
+
return shellSubstitutions(line).join("\n");
|
|
1002
|
+
}
|
|
1003
|
+
const words = shellTokens(line);
|
|
1004
|
+
let commandStart = 0;
|
|
1005
|
+
for (let index = 0; index < words.length; index++) {
|
|
1006
|
+
const match = words.at(index);
|
|
1007
|
+
const word = match?.value;
|
|
1008
|
+
if (word === void 0) continue;
|
|
1009
|
+
if (match?.kind === "comment") return line.slice(0, match.index);
|
|
1010
|
+
if (match?.kind === "boundary") {
|
|
1011
|
+
commandStart = index + 1;
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
const declaration = match?.kind === "redirect" ? /^(?:\d*)<<(-?)(.*)$/.exec(word) : null;
|
|
1015
|
+
let delimiter = declaration?.[2];
|
|
1016
|
+
if (delimiter === "") delimiter = words.at(++index)?.value;
|
|
1017
|
+
if (declaration !== null && delimiter !== void 0 && !delimiter.startsWith("<")) {
|
|
1018
|
+
const commandEnd = words.findIndex((entry, position) => position > index && (entry.kind === "boundary" || entry.kind === "comment"));
|
|
1019
|
+
const invocation = words.slice(commandStart, commandEnd < 0 ? void 0 : commandEnd);
|
|
1020
|
+
pending.push({
|
|
1021
|
+
delimiter: decodeShellWord(delimiter),
|
|
1022
|
+
stripTabs: declaration[1] === "-",
|
|
1023
|
+
literal: /['"\\]/.test(delimiter),
|
|
1024
|
+
execute: shellReadsProgramStdin(invocation) && shellStdinHeredoc(invocation) === match
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
return line;
|
|
1029
|
+
}).join("\n");
|
|
1030
|
+
};
|
|
1031
|
+
const executableShellWords = (words) => {
|
|
1032
|
+
let start = 0;
|
|
1033
|
+
const skipAssignments = () => {
|
|
1034
|
+
while (/^[A-Za-z_][A-Za-z\d_]*=/.test(decodeShellWord(words.at(start) ?? ""))) start += 1;
|
|
1035
|
+
};
|
|
1036
|
+
skipAssignments();
|
|
1037
|
+
while (/^(?:\/usr\/bin\/|\/bin\/)?env$/.test(decodeShellWord(words.at(start) ?? ""))) {
|
|
1038
|
+
start += 1;
|
|
1039
|
+
while (start < words.length) {
|
|
1040
|
+
const option = decodeShellWord(words.at(start) ?? "");
|
|
1041
|
+
if (option === "--") {
|
|
1042
|
+
start += 1;
|
|
1043
|
+
break;
|
|
1044
|
+
}
|
|
1045
|
+
if ([
|
|
1046
|
+
"-u",
|
|
1047
|
+
"--unset",
|
|
1048
|
+
"-C",
|
|
1049
|
+
"--chdir"
|
|
1050
|
+
].includes(option)) {
|
|
1051
|
+
start += 2;
|
|
1052
|
+
continue;
|
|
1053
|
+
}
|
|
1054
|
+
if (option === "-S" || option === "--split-string" || option.startsWith("--split-string=") || option.startsWith("-S") && option.length > 2) {
|
|
1055
|
+
let source;
|
|
1056
|
+
if (option === "-S" || option === "--split-string") {
|
|
1057
|
+
source = decodeShellWord(words.at(start + 1) ?? "");
|
|
775
1058
|
start += 2;
|
|
776
|
-
|
|
1059
|
+
} else {
|
|
1060
|
+
source = option.startsWith("-S") ? option.slice(2) : option.slice(15);
|
|
1061
|
+
start++;
|
|
777
1062
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
1063
|
+
return executableShellWords([
|
|
1064
|
+
"env",
|
|
1065
|
+
...shellTokens(source).map(({ value }) => value),
|
|
1066
|
+
...words.slice(start)
|
|
1067
|
+
]);
|
|
783
1068
|
}
|
|
784
|
-
|
|
1069
|
+
if (option === "-" || option === "-i" || option === "--ignore-environment" || /^--(?:unset|chdir)=.+|^-[uC].+/.test(option)) {
|
|
1070
|
+
start += 1;
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
break;
|
|
785
1074
|
}
|
|
786
|
-
|
|
787
|
-
}
|
|
1075
|
+
skipAssignments();
|
|
1076
|
+
}
|
|
1077
|
+
return words.slice(start);
|
|
1078
|
+
};
|
|
1079
|
+
const shellCommandBody = (rawWords) => {
|
|
1080
|
+
const words = executableShellWords(rawWords.filter((word) => word !== "(" && word !== ")")).map(decodeShellWord);
|
|
1081
|
+
if (!shellExecutable.test(words.at(0) ?? "")) return void 0;
|
|
1082
|
+
for (let index = 1; index < words.length; index++) {
|
|
1083
|
+
const option = words.at(index);
|
|
1084
|
+
if (option === void 0 || option === "--" || !/^[+-]/.test(option)) return void 0;
|
|
1085
|
+
if (shellValueOptions.has(option)) {
|
|
1086
|
+
index++;
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
if (/^-[^-]*c/.test(option)) return words.at(index + 1);
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
/** Shared executable shell words preserve assignments for runtime selectors. */
|
|
1093
|
+
const shellCommandSegments = (command, grouping = "boundaries") => {
|
|
1094
|
+
const segments = [[]];
|
|
1095
|
+
const executableSource = shellExecutableSource(command);
|
|
1096
|
+
const sources = /* @__PURE__ */ new Set([executableSource, ...shellSubstitutions(executableSource)]);
|
|
1097
|
+
for (const source of sources) {
|
|
1098
|
+
const firstSegment = segments.length;
|
|
1099
|
+
segments.push([]);
|
|
1100
|
+
for (const { kind, value: word } of shellArgumentTokens(shellTokens(source))) if (kind === "boundary") if ([
|
|
1101
|
+
"(",
|
|
1102
|
+
")",
|
|
1103
|
+
"{",
|
|
1104
|
+
"}"
|
|
1105
|
+
].includes(word) && grouping === "tokens") segments.at(-1)?.push(word);
|
|
1106
|
+
else segments.push([]);
|
|
1107
|
+
else segments.at(-1)?.push(word);
|
|
1108
|
+
for (const words of segments.slice(firstSegment)) {
|
|
1109
|
+
const body = shellCommandBody(words);
|
|
1110
|
+
if (body === void 0) continue;
|
|
1111
|
+
const executableBody = shellExecutableSource(body);
|
|
1112
|
+
sources.add(executableBody);
|
|
1113
|
+
for (const substitution of shellSubstitutions(executableBody)) sources.add(substitution);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
return segments;
|
|
788
1117
|
};
|
|
1118
|
+
const compilerCommandSegments = (command) => shellCommandSegments(command, "tokens").map(executableShellWords);
|
|
789
1119
|
const launcherValueOptions = new Map(Object.entries({
|
|
790
1120
|
npx: [
|
|
791
1121
|
"-p",
|
|
@@ -919,10 +1249,10 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
919
1249
|
const document = pnpm ? parseDocument(text, { lineCounter }) : void 0;
|
|
920
1250
|
if (document !== void 0 && document.errors.length > 0) throw new Error("invalid YAML");
|
|
921
1251
|
const json = document === void 0 ? JSON.parse(text) : document.toJS();
|
|
922
|
-
if (!record$
|
|
1252
|
+
if (!record$3(json)) throw new Error("manifest must be an object");
|
|
923
1253
|
if (pnpm) {
|
|
924
|
-
if (json["catalog"] !== void 0 && !record$
|
|
925
|
-
if (json["catalogs"] !== void 0 && (!record$
|
|
1254
|
+
if (json["catalog"] !== void 0 && !record$3(json["catalog"])) throw new Error("catalog must be a mapping");
|
|
1255
|
+
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
1256
|
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
1257
|
}
|
|
928
1258
|
manifests.set(file, json);
|
|
@@ -936,13 +1266,13 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
936
1266
|
catalog: json["overrides"],
|
|
937
1267
|
prefix: ["overrides"]
|
|
938
1268
|
},
|
|
939
|
-
...record$
|
|
1269
|
+
...record$3(json["catalogs"]) ? Object.entries(json["catalogs"]).map(([name, catalog]) => ({
|
|
940
1270
|
catalog,
|
|
941
1271
|
prefix: ["catalogs", name]
|
|
942
1272
|
})) : []
|
|
943
1273
|
];
|
|
944
1274
|
for (const { catalog, prefix } of catalogEntries) {
|
|
945
|
-
if (!record$
|
|
1275
|
+
if (!record$3(catalog)) continue;
|
|
946
1276
|
for (const [name, value] of Object.entries(catalog)) {
|
|
947
1277
|
const node = document.getIn([...prefix, name], true);
|
|
948
1278
|
if (!isNode(node) || node.range === void 0 || node.range === null) continue;
|
|
@@ -961,7 +1291,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
961
1291
|
});
|
|
962
1292
|
}
|
|
963
1293
|
}
|
|
964
|
-
const catalogSources = (json) => [json, ...record$
|
|
1294
|
+
const catalogSources = (json) => [json, ...record$3(json["workspaces"]) ? [json["workspaces"]] : []];
|
|
965
1295
|
const pnpmWorkspaceFor = (file) => {
|
|
966
1296
|
let directory = path.posix.dirname(file);
|
|
967
1297
|
while (true) {
|
|
@@ -984,8 +1314,8 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
984
1314
|
patterns: workspace["packages"],
|
|
985
1315
|
rootMembership: "implicit"
|
|
986
1316
|
})) return void 0;
|
|
987
|
-
if (name === "" || name === "default") return workspace["catalog"] ?? (record$
|
|
988
|
-
return record$
|
|
1317
|
+
if (name === "" || name === "default") return workspace["catalog"] ?? (record$3(workspace["catalogs"]) ? workspace["catalogs"]["default"] : void 0);
|
|
1318
|
+
return record$3(workspace["catalogs"]) ? workspace["catalogs"][name] : void 0;
|
|
989
1319
|
}
|
|
990
1320
|
let directory = path.posix.dirname(file);
|
|
991
1321
|
while (true) {
|
|
@@ -993,7 +1323,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
993
1323
|
if (manifest !== void 0) {
|
|
994
1324
|
for (const source of catalogSources(manifest)) {
|
|
995
1325
|
if (name === "" && source["catalog"] !== void 0) return source["catalog"];
|
|
996
|
-
if (record$
|
|
1326
|
+
if (record$3(source["catalogs"]) && source["catalogs"][name] !== void 0) return source["catalogs"][name];
|
|
997
1327
|
}
|
|
998
1328
|
if (manifest["workspaces"] !== void 0) return void 0;
|
|
999
1329
|
}
|
|
@@ -1007,7 +1337,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1007
1337
|
if (visited.has(value)) return void 0;
|
|
1008
1338
|
visited.add(value);
|
|
1009
1339
|
const catalog = catalogFor(file, value.slice(8));
|
|
1010
|
-
value = record$
|
|
1340
|
+
value = record$3(catalog) ? catalog[name] : void 0;
|
|
1011
1341
|
}
|
|
1012
1342
|
return value;
|
|
1013
1343
|
};
|
|
@@ -1023,7 +1353,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1023
1353
|
const workspaces = manifests.get(path.posix.join(directory, "package.json"))?.["workspaces"];
|
|
1024
1354
|
if (workspaces !== void 0) return {
|
|
1025
1355
|
directory,
|
|
1026
|
-
patterns: record$
|
|
1356
|
+
patterns: record$3(workspaces) ? workspaces["packages"] : workspaces,
|
|
1027
1357
|
source: "json"
|
|
1028
1358
|
};
|
|
1029
1359
|
if (directory === ".") return void 0;
|
|
@@ -1079,7 +1409,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1079
1409
|
"peerDependencies"
|
|
1080
1410
|
]) {
|
|
1081
1411
|
const dependencies = json[section];
|
|
1082
|
-
if (!record$
|
|
1412
|
+
if (!record$3(dependencies)) continue;
|
|
1083
1413
|
for (const [name, value] of Object.entries(dependencies)) {
|
|
1084
1414
|
if (!tsSpecifiers.has(name)) continue;
|
|
1085
1415
|
const resolved = resolve(file, name, value);
|
|
@@ -1141,7 +1471,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1141
1471
|
message: `packageManager Bun version must be ${policy.bun}, found ${JSON.stringify(manager)}`
|
|
1142
1472
|
});
|
|
1143
1473
|
const checkEntries = (entries, location) => {
|
|
1144
|
-
if (!record$
|
|
1474
|
+
if (!record$3(entries)) return;
|
|
1145
1475
|
for (const [name, raw] of Object.entries(entries)) {
|
|
1146
1476
|
const value = resolve(file, name, raw);
|
|
1147
1477
|
const tsAllowed = tsSpecifiers.get(name);
|
|
@@ -1178,12 +1508,12 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1178
1508
|
"devDependencies",
|
|
1179
1509
|
"optionalDependencies"
|
|
1180
1510
|
]) checkEntries(json[key]);
|
|
1181
|
-
if (record$
|
|
1511
|
+
if (record$3(json["peerDependencies"])) {
|
|
1182
1512
|
for (const [name, value] of Object.entries(json["peerDependencies"])) if (tsSpecifiers.has(name) && !peerRange(resolve(file, name, value))) checkEntries({ [name]: value });
|
|
1183
1513
|
}
|
|
1184
1514
|
for (const source of catalogSources(json)) {
|
|
1185
1515
|
checkEntries(source["catalog"]);
|
|
1186
|
-
if (!record$
|
|
1516
|
+
if (!record$3(source["catalogs"])) continue;
|
|
1187
1517
|
for (const catalog of Object.values(source["catalogs"])) checkEntries(catalog);
|
|
1188
1518
|
}
|
|
1189
1519
|
const ownedNames = /* @__PURE__ */ new Set([...pins.keys(), ...tsSpecifiers.keys()]);
|
|
@@ -1195,7 +1525,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1195
1525
|
"optionalDependencies"
|
|
1196
1526
|
]) {
|
|
1197
1527
|
const entries = resolutionRoot[section];
|
|
1198
|
-
if (!record$
|
|
1528
|
+
if (!record$3(entries)) continue;
|
|
1199
1529
|
for (const [name, value] of Object.entries(entries)) resolutionDependencies[name] = resolve(file, name, value);
|
|
1200
1530
|
}
|
|
1201
1531
|
const affectedSpecifiers = /* @__PURE__ */ new Map();
|
|
@@ -1215,7 +1545,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1215
1545
|
"optionalDependencies"
|
|
1216
1546
|
]) {
|
|
1217
1547
|
const entries = member[section];
|
|
1218
|
-
if (!record$
|
|
1548
|
+
if (!record$3(entries)) continue;
|
|
1219
1549
|
for (const [name, value] of Object.entries(entries)) {
|
|
1220
1550
|
if (!tsSpecifiers.has(name)) continue;
|
|
1221
1551
|
const values = affectedSpecifiers.get(name) ?? /* @__PURE__ */ new Set();
|
|
@@ -1225,7 +1555,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1225
1555
|
}
|
|
1226
1556
|
}
|
|
1227
1557
|
const checkResolutionMap = (entries, parent) => {
|
|
1228
|
-
if (!record$
|
|
1558
|
+
if (!record$3(entries)) return;
|
|
1229
1559
|
for (const [selector, replacement] of Object.entries(entries)) {
|
|
1230
1560
|
const target = selector === "." ? parent : selector;
|
|
1231
1561
|
if (target === void 0) continue;
|
|
@@ -1248,7 +1578,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1248
1578
|
});
|
|
1249
1579
|
continue;
|
|
1250
1580
|
}
|
|
1251
|
-
if (record$
|
|
1581
|
+
if (record$3(replacement)) {
|
|
1252
1582
|
checkResolutionMap(replacement, target);
|
|
1253
1583
|
continue;
|
|
1254
1584
|
}
|
|
@@ -1282,7 +1612,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1282
1612
|
};
|
|
1283
1613
|
checkResolutionMap(json["overrides"]);
|
|
1284
1614
|
checkResolutionMap(json["resolutions"]);
|
|
1285
|
-
if (record$
|
|
1615
|
+
if (record$3(json["pnpm"])) checkResolutionMap(json["pnpm"]["overrides"]);
|
|
1286
1616
|
const localEntries = toolchainEntries(file, json);
|
|
1287
1617
|
const usesCompiler = policy.typescriptInstallLayouts.some((layout) => localEntries.some(({ name }) => name === layout.compilerPackage));
|
|
1288
1618
|
let selectedLayout = layoutFor(specifierMap(localEntries));
|
|
@@ -1306,7 +1636,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1306
1636
|
key: "dependencies",
|
|
1307
1637
|
message: "TypeScript compiler and compatibility packages must match one complete declared install layout"
|
|
1308
1638
|
});
|
|
1309
|
-
const command = record$
|
|
1639
|
+
const command = record$3(json["scripts"]) ? json["scripts"]["typecheck"] : void 0;
|
|
1310
1640
|
if (selectedLayout !== void 0 && typeof command === "string") {
|
|
1311
1641
|
const normalizedExpected = selectedLayout.typecheckCommand.replace(/\s+/g, " ").trim();
|
|
1312
1642
|
for (const words of compilerCommandSegments(command)) {
|
|
@@ -1324,7 +1654,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1324
1654
|
}
|
|
1325
1655
|
}
|
|
1326
1656
|
}
|
|
1327
|
-
if (record$
|
|
1657
|
+
if (record$3(json["peerDependencies"])) {
|
|
1328
1658
|
const peerLayout = selectedLayout ?? workspaceLayoutFor(file)?.layout;
|
|
1329
1659
|
for (const [name, raw] of Object.entries(json["peerDependencies"])) {
|
|
1330
1660
|
if (!tsSpecifiers.has(name)) continue;
|
|
@@ -1348,7 +1678,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1348
1678
|
});
|
|
1349
1679
|
}
|
|
1350
1680
|
}
|
|
1351
|
-
if (record$
|
|
1681
|
+
if (record$3(json["engines"]) && json["engines"]["node"] !== void 0 && (typeof json["engines"]["node"] !== "string" || !nodeSupportRangeMatches(policy.node, json["engines"]["node"]))) add({
|
|
1352
1682
|
file,
|
|
1353
1683
|
rule: "node-engine",
|
|
1354
1684
|
key: "node",
|
|
@@ -1359,6 +1689,232 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1359
1689
|
return diagnostics;
|
|
1360
1690
|
};
|
|
1361
1691
|
//#endregion
|
|
1692
|
+
//#region src/toolchain-rust.ts
|
|
1693
|
+
const record$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1694
|
+
/** Exceptions authorize one immutable release, never a channel or an entire rule. */
|
|
1695
|
+
const parseRustExceptions = (input, today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)) => {
|
|
1696
|
+
if (input === void 0) return [];
|
|
1697
|
+
if (!Array.isArray(input)) throw new Error("rustExceptions must be an array");
|
|
1698
|
+
const pins = /* @__PURE__ */ new Set();
|
|
1699
|
+
return input.map((entry) => {
|
|
1700
|
+
if (!record$2(entry) || Object.keys(entry).length !== 4 || Object.keys(entry).some((key) => ![
|
|
1701
|
+
"pin",
|
|
1702
|
+
"reason",
|
|
1703
|
+
"tracking",
|
|
1704
|
+
"expires"
|
|
1705
|
+
].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");
|
|
1706
|
+
pins.add(entry["pin"]);
|
|
1707
|
+
return {
|
|
1708
|
+
pin: entry["pin"],
|
|
1709
|
+
reason: entry["reason"],
|
|
1710
|
+
tracking: entry["tracking"],
|
|
1711
|
+
expires: entry["expires"]
|
|
1712
|
+
};
|
|
1713
|
+
});
|
|
1714
|
+
};
|
|
1715
|
+
/** Only executable fields are inspected: unrelated action inputs are not Rust selectors. */
|
|
1716
|
+
const checkRustReferences = (options) => {
|
|
1717
|
+
const { file, rust, exceptions } = options;
|
|
1718
|
+
const text = options.type === "file" ? options.text : "";
|
|
1719
|
+
const diagnostics = [];
|
|
1720
|
+
const select = (value, label, line = 1) => {
|
|
1721
|
+
if (value === rust || exceptions.some(({ pin }) => pin === value)) return;
|
|
1722
|
+
diagnostics.push({
|
|
1723
|
+
rule: "rust-version",
|
|
1724
|
+
path: file,
|
|
1725
|
+
line,
|
|
1726
|
+
message: `${label} must be Rust ${rust}, found ${String(value)}`
|
|
1727
|
+
});
|
|
1728
|
+
};
|
|
1729
|
+
const shell = (source, firstLine = 1) => {
|
|
1730
|
+
const commands = shellCommandSegments(source).map((words) => words.map((word) => ({
|
|
1731
|
+
value: decodeShellWord(word),
|
|
1732
|
+
line: firstLine
|
|
1733
|
+
})));
|
|
1734
|
+
for (const command of commands) {
|
|
1735
|
+
let position = 0;
|
|
1736
|
+
const prefixes = /* @__PURE__ */ new Set([
|
|
1737
|
+
"if",
|
|
1738
|
+
"then",
|
|
1739
|
+
"do",
|
|
1740
|
+
"elif",
|
|
1741
|
+
"!",
|
|
1742
|
+
"env",
|
|
1743
|
+
"sudo",
|
|
1744
|
+
"command",
|
|
1745
|
+
"exec",
|
|
1746
|
+
"time",
|
|
1747
|
+
"export",
|
|
1748
|
+
"readonly",
|
|
1749
|
+
"/usr/bin/env",
|
|
1750
|
+
"/bin/env"
|
|
1751
|
+
]);
|
|
1752
|
+
while (position < command.length) {
|
|
1753
|
+
const value = command.at(position)?.value;
|
|
1754
|
+
if (value?.startsWith("RUSTUP_TOOLCHAIN=")) select(value.slice(17), "RUSTUP_TOOLCHAIN", firstLine);
|
|
1755
|
+
if (value === "-u" || value === "--unset") {
|
|
1756
|
+
position += 2;
|
|
1757
|
+
continue;
|
|
1758
|
+
}
|
|
1759
|
+
if (value?.startsWith("--unset=") || value === "-i" || value === "--ignore-environment") {
|
|
1760
|
+
position++;
|
|
1761
|
+
continue;
|
|
1762
|
+
}
|
|
1763
|
+
if (value !== void 0 && (prefixes.has(value) || /^[A-Za-z_][A-Za-z_\d]*=/.test(value))) position++;
|
|
1764
|
+
else break;
|
|
1765
|
+
}
|
|
1766
|
+
const executable = command.at(position)?.value;
|
|
1767
|
+
const arguments_ = command.slice(position + 1);
|
|
1768
|
+
if (executable === "cargo" && arguments_.at(0)?.value.startsWith("+")) {
|
|
1769
|
+
const selector = arguments_.at(0);
|
|
1770
|
+
select(selector?.value.slice(1), "cargo toolchain", selector?.line);
|
|
1771
|
+
}
|
|
1772
|
+
if (executable !== "rustup") continue;
|
|
1773
|
+
let subcommandPosition = 0;
|
|
1774
|
+
while (subcommandPosition < arguments_.length) {
|
|
1775
|
+
const token = arguments_.at(subcommandPosition);
|
|
1776
|
+
if (token === void 0) break;
|
|
1777
|
+
if (token.value === "--") {
|
|
1778
|
+
subcommandPosition++;
|
|
1779
|
+
break;
|
|
1780
|
+
}
|
|
1781
|
+
if (token.value.startsWith("+")) select(token.value.slice(1), "rustup toolchain", token.line);
|
|
1782
|
+
else if (!token.value.startsWith("-")) break;
|
|
1783
|
+
subcommandPosition++;
|
|
1784
|
+
}
|
|
1785
|
+
const invocation = arguments_.slice(subcommandPosition);
|
|
1786
|
+
const subcommand = invocation.at(0)?.value;
|
|
1787
|
+
if (subcommand === "target" || subcommand === "component") {
|
|
1788
|
+
for (let index = 1; index < invocation.length; index++) {
|
|
1789
|
+
const token = invocation.at(index);
|
|
1790
|
+
if (token?.value === "--toolchain") select(invocation.at(++index)?.value, "rustup --toolchain", token.line);
|
|
1791
|
+
else if (token?.value.startsWith("--toolchain=")) select(token.value.slice(12), "rustup --toolchain", token.line);
|
|
1792
|
+
}
|
|
1793
|
+
continue;
|
|
1794
|
+
}
|
|
1795
|
+
let selectors;
|
|
1796
|
+
let requiresSelector = true;
|
|
1797
|
+
let firstSelectorOnly = false;
|
|
1798
|
+
if (subcommand === "default" || subcommand === "install" || subcommand === "add" || subcommand === "update") {
|
|
1799
|
+
selectors = invocation.slice(1);
|
|
1800
|
+
requiresSelector = subcommand !== "update";
|
|
1801
|
+
} else if (subcommand === "toolchain" && [
|
|
1802
|
+
"install",
|
|
1803
|
+
"add",
|
|
1804
|
+
"update"
|
|
1805
|
+
].includes(invocation.at(1)?.value ?? "")) {
|
|
1806
|
+
selectors = invocation.slice(2);
|
|
1807
|
+
requiresSelector = invocation.at(1)?.value !== "update";
|
|
1808
|
+
} else if (subcommand === "run") {
|
|
1809
|
+
selectors = invocation.slice(1);
|
|
1810
|
+
firstSelectorOnly = true;
|
|
1811
|
+
} else if (subcommand === "override" && invocation.at(1)?.value === "set") {
|
|
1812
|
+
selectors = invocation.slice(2);
|
|
1813
|
+
firstSelectorOnly = true;
|
|
1814
|
+
} else continue;
|
|
1815
|
+
let selected = 0;
|
|
1816
|
+
const optionArguments = /* @__PURE__ */ new Set([
|
|
1817
|
+
"--path",
|
|
1818
|
+
"--profile",
|
|
1819
|
+
"--component",
|
|
1820
|
+
"-c",
|
|
1821
|
+
"--target",
|
|
1822
|
+
"-t"
|
|
1823
|
+
]);
|
|
1824
|
+
for (let index = 0; index < selectors.length; index++) {
|
|
1825
|
+
const token = selectors.at(index);
|
|
1826
|
+
if (token === void 0) continue;
|
|
1827
|
+
if (optionArguments.has(token.value)) {
|
|
1828
|
+
index++;
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1831
|
+
if (token.value.startsWith("-")) continue;
|
|
1832
|
+
selected++;
|
|
1833
|
+
select(token.value, "rustup toolchain", token.line);
|
|
1834
|
+
if (firstSelectorOnly) break;
|
|
1835
|
+
}
|
|
1836
|
+
if (selected === 0 && requiresSelector) select(void 0, "rustup toolchain", firstLine);
|
|
1837
|
+
}
|
|
1838
|
+
};
|
|
1839
|
+
if (options.type === "environment") {
|
|
1840
|
+
for (const { key, value } of options.assignments) if (key === "RUSTUP_TOOLCHAIN") select(value, key, options.line);
|
|
1841
|
+
return diagnostics;
|
|
1842
|
+
}
|
|
1843
|
+
if (options.type === "command") {
|
|
1844
|
+
shell(options.source, options.line);
|
|
1845
|
+
return diagnostics;
|
|
1846
|
+
}
|
|
1847
|
+
const lineOf = (token) => {
|
|
1848
|
+
const index = text.split(/\r?\n/).findIndex((line) => line.includes(token));
|
|
1849
|
+
return index < 0 ? 1 : index + 1;
|
|
1850
|
+
};
|
|
1851
|
+
if (isShellScript(file, text)) shell(text);
|
|
1852
|
+
if (/(?:^|\/)package\.json$/.test(file)) try {
|
|
1853
|
+
const object = JSON.parse(text);
|
|
1854
|
+
if (record$2(object) && record$2(object["scripts"])) {
|
|
1855
|
+
for (const [name, script] of Object.entries(object["scripts"])) if (typeof script === "string") shell(script, lineOf(`"${name}"`));
|
|
1856
|
+
}
|
|
1857
|
+
} catch {}
|
|
1858
|
+
if (/(?:^|\/)ci-service-images\.json$/.test(file)) {
|
|
1859
|
+
const images = (value) => {
|
|
1860
|
+
if (typeof value === "string" && /^rust(?=:|@|$)/.test(canonicalDockerRuntime(value))) {
|
|
1861
|
+
const tag = canonicalDockerRuntime(value).replace(/^rust:?/, "").split("@").at(0);
|
|
1862
|
+
const version = tag === void 0 ? void 0 : rustImageVersion(tag);
|
|
1863
|
+
select(version, "Rust service image", lineOf(value));
|
|
1864
|
+
} else if (Array.isArray(value)) value.forEach(images);
|
|
1865
|
+
else if (record$2(value)) Object.values(value).forEach(images);
|
|
1866
|
+
};
|
|
1867
|
+
try {
|
|
1868
|
+
images(JSON.parse(text));
|
|
1869
|
+
} catch {
|
|
1870
|
+
diagnostics.push({
|
|
1871
|
+
rule: "rust-version",
|
|
1872
|
+
path: file,
|
|
1873
|
+
line: 1,
|
|
1874
|
+
message: "invalid service image JSON"
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
if (githubAutomationFileKind(file) === void 0) return diagnostics;
|
|
1879
|
+
const document = parseDocument(text);
|
|
1880
|
+
if (document.errors.length > 0) return diagnostics;
|
|
1881
|
+
try {
|
|
1882
|
+
const visit = (value) => {
|
|
1883
|
+
if (Array.isArray(value)) {
|
|
1884
|
+
value.forEach(visit);
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1887
|
+
if (!record$2(value)) return;
|
|
1888
|
+
if (record$2(value["env"]) && value["env"]["RUSTUP_TOOLCHAIN"] !== void 0) select(value["env"]["RUSTUP_TOOLCHAIN"], "RUSTUP_TOOLCHAIN", lineOf("RUSTUP_TOOLCHAIN"));
|
|
1889
|
+
if (typeof value["run"] === "string") shell(value["run"], lineOf("run:"));
|
|
1890
|
+
const uses = value["uses"];
|
|
1891
|
+
if (typeof uses === "string") {
|
|
1892
|
+
const action = uses.split("@").at(0)?.toLowerCase();
|
|
1893
|
+
const inputs = record$2(value["with"]) ? value["with"] : {};
|
|
1894
|
+
if (action === "dtolnay/rust-toolchain" || action === "actions-rust-lang/setup-rust-toolchain") {
|
|
1895
|
+
const entries = Object.entries(inputs).filter(([key]) => key.toLowerCase() === "toolchain");
|
|
1896
|
+
if (entries.length !== 1) select(void 0, `${action} requires an explicit exact toolchain input`, lineOf(uses));
|
|
1897
|
+
else {
|
|
1898
|
+
const selected = entries.at(0)?.at(1);
|
|
1899
|
+
const selectors = action === "actions-rust-lang/setup-rust-toolchain" && typeof selected === "string" ? selected.split(",").map((pin) => pin.trim()) : [selected];
|
|
1900
|
+
for (const selector of selectors) select(selector, `${action} toolchain`, lineOf("toolchain:"));
|
|
1901
|
+
}
|
|
1902
|
+
} else if (/(?:^|\/)\.github\/workflows\/[^@]+\.ya?ml(?:@|$)/.test(uses)) {
|
|
1903
|
+
for (const [key, selected] of Object.entries(inputs)) if (["toolchain", "rust-toolchain"].includes(key.toLowerCase())) select(selected, `reusable workflow ${key}`, lineOf(`${key}:`));
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1907
|
+
if (key === "workflow_call" && record$2(child) && record$2(child["inputs"])) {
|
|
1908
|
+
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:"));
|
|
1909
|
+
}
|
|
1910
|
+
visit(child);
|
|
1911
|
+
}
|
|
1912
|
+
};
|
|
1913
|
+
visit(document.toJS({ maxAliasCount: 100 }));
|
|
1914
|
+
} catch {}
|
|
1915
|
+
return diagnostics;
|
|
1916
|
+
};
|
|
1917
|
+
//#endregion
|
|
1362
1918
|
//#region src/toolchain-runtime.ts
|
|
1363
1919
|
const runtimeRules = [
|
|
1364
1920
|
"bun-pins",
|
|
@@ -1371,6 +1927,69 @@ const runtimeRules = [
|
|
|
1371
1927
|
"action-pins"
|
|
1372
1928
|
];
|
|
1373
1929
|
const record$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1930
|
+
const dockerEnvironmentAssignments = ({ source, escape }) => {
|
|
1931
|
+
const words = [];
|
|
1932
|
+
let word = "";
|
|
1933
|
+
let quote;
|
|
1934
|
+
for (let index = 0; index < source.length; index++) {
|
|
1935
|
+
const character = source.at(index) ?? "";
|
|
1936
|
+
if (character === escape && quote !== "'") {
|
|
1937
|
+
word += character + (source.at(++index) ?? "");
|
|
1938
|
+
continue;
|
|
1939
|
+
}
|
|
1940
|
+
if (character === quote) quote = void 0;
|
|
1941
|
+
else if (quote === void 0 && (character === "'" || character === "\"")) quote = character;
|
|
1942
|
+
else if (quote === void 0 && /\s/.test(character)) {
|
|
1943
|
+
if (word !== "") words.push(word);
|
|
1944
|
+
word = "";
|
|
1945
|
+
continue;
|
|
1946
|
+
}
|
|
1947
|
+
word += character;
|
|
1948
|
+
}
|
|
1949
|
+
if (quote !== void 0) return void 0;
|
|
1950
|
+
if (word !== "") words.push(word);
|
|
1951
|
+
const decode = (raw) => {
|
|
1952
|
+
let value = "";
|
|
1953
|
+
let quoted;
|
|
1954
|
+
for (let index = 0; index < raw.length; index++) {
|
|
1955
|
+
const character = raw.at(index) ?? "";
|
|
1956
|
+
if (character === quoted) {
|
|
1957
|
+
quoted = void 0;
|
|
1958
|
+
continue;
|
|
1959
|
+
}
|
|
1960
|
+
if (quoted === void 0 && (character === "'" || character === "\"")) {
|
|
1961
|
+
quoted = character;
|
|
1962
|
+
continue;
|
|
1963
|
+
}
|
|
1964
|
+
const next = raw.at(index + 1);
|
|
1965
|
+
if (character === escape && quoted !== "'" && next !== void 0 && (quoted === void 0 || next === "\"" || next === "$" || next === escape)) {
|
|
1966
|
+
value += next;
|
|
1967
|
+
index++;
|
|
1968
|
+
} else value += character;
|
|
1969
|
+
}
|
|
1970
|
+
return value;
|
|
1971
|
+
};
|
|
1972
|
+
const first = words.at(0);
|
|
1973
|
+
if (first === void 0) return void 0;
|
|
1974
|
+
if (!first.includes("=")) {
|
|
1975
|
+
const legacy = /^(\S+)\s+([\s\S]*)$/.exec(source);
|
|
1976
|
+
if (legacy === null) return void 0;
|
|
1977
|
+
return [{
|
|
1978
|
+
key: decode(legacy[1] ?? ""),
|
|
1979
|
+
value: decode(legacy[2] ?? "")
|
|
1980
|
+
}];
|
|
1981
|
+
}
|
|
1982
|
+
const assignments = [];
|
|
1983
|
+
for (const raw of words) {
|
|
1984
|
+
const separator = raw.indexOf("=");
|
|
1985
|
+
if (separator < 1) return void 0;
|
|
1986
|
+
assignments.push({
|
|
1987
|
+
key: decode(raw.slice(0, separator)),
|
|
1988
|
+
value: decode(raw.slice(separator + 1))
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
return assignments;
|
|
1992
|
+
};
|
|
1374
1993
|
const pythonSelectorMatches = (value, expected) => value === expected || typeof value === "string" && /^\d+\.\d+$/.test(expected) && value.startsWith(`${expected}.`) && /^\d+\.\d+\.\d+$/.test(value);
|
|
1375
1994
|
const compareRelease = (left, right) => {
|
|
1376
1995
|
for (let index = 0; index < Math.max(left.length, right.length); index++) {
|
|
@@ -1440,8 +2059,14 @@ const pythonRequirementMatches = (requirement, version) => {
|
|
|
1440
2059
|
});
|
|
1441
2060
|
};
|
|
1442
2061
|
/** 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 =
|
|
2062
|
+
const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, repository, onDelegated, dynamicSelectors = [], onDynamicSelector, rustExceptions = [], engineFloors = [], onEngineFloor }) => {
|
|
2063
|
+
const diagnostics = checkRustReferences({
|
|
2064
|
+
type: "file",
|
|
2065
|
+
file,
|
|
2066
|
+
text,
|
|
2067
|
+
rust: policy.rust,
|
|
2068
|
+
exceptions: rustExceptions
|
|
2069
|
+
});
|
|
1445
2070
|
const name = path.posix.basename(file);
|
|
1446
2071
|
const lines = text.split(/\r?\n/);
|
|
1447
2072
|
const add = ({ rule, line, message }) => {
|
|
@@ -1464,7 +2089,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1464
2089
|
};
|
|
1465
2090
|
const lineOf = (key) => Math.max(1, lines.findIndex((line) => line.includes(key)) + 1);
|
|
1466
2091
|
const pin = ({ rule, value, expected, label, line = 1, selector }) => {
|
|
1467
|
-
let matches = value === expected;
|
|
2092
|
+
let matches = value === expected || rule === "rust-version" && rustExceptions.some((entry) => entry.pin === value);
|
|
1468
2093
|
if (selector === "node") matches = nodeSelectorMatches(value, expected);
|
|
1469
2094
|
else if (label.includes("python")) matches = pythonSelectorMatches(value, expected);
|
|
1470
2095
|
if (!matches) add({
|
|
@@ -1534,12 +2159,10 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1534
2159
|
const parsed = toml("rust-version");
|
|
1535
2160
|
if (parsed !== void 0) {
|
|
1536
2161
|
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
2162
|
pin({
|
|
1540
2163
|
rule: "rust-version",
|
|
1541
2164
|
value: record$1(toolchain) ? toolchain["channel"] : void 0,
|
|
1542
|
-
expected,
|
|
2165
|
+
expected: policy.rust,
|
|
1543
2166
|
label: "Rust channel",
|
|
1544
2167
|
line: lineOf("channel")
|
|
1545
2168
|
});
|
|
@@ -1640,7 +2263,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1640
2263
|
if (record$1(tools)) for (const [tool, expected] of Object.entries(versions)) {
|
|
1641
2264
|
const configured = tools[tool];
|
|
1642
2265
|
if (configured !== void 0) pin({
|
|
1643
|
-
rule: "runtime-manager",
|
|
2266
|
+
rule: tool === "rust" || tool === "core:rust" ? "rust-version" : "runtime-manager",
|
|
1644
2267
|
value: record$1(configured) ? configured["version"] : configured,
|
|
1645
2268
|
expected,
|
|
1646
2269
|
selector: nodeTools.has(tool) ? "node" : void 0,
|
|
@@ -1655,7 +2278,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1655
2278
|
if (tool === void 0 || !(tool in versions)) return;
|
|
1656
2279
|
const expected = Object.entries(versions).find(([key]) => key === tool)?.[1];
|
|
1657
2280
|
if (expected !== void 0) pin({
|
|
1658
|
-
rule: "runtime-manager",
|
|
2281
|
+
rule: tool === "rust" || tool === "core:rust" ? "rust-version" : "runtime-manager",
|
|
1659
2282
|
value: fields.slice(1).join(" "),
|
|
1660
2283
|
expected,
|
|
1661
2284
|
selector: nodeTools.has(tool) ? "node" : void 0,
|
|
@@ -1665,11 +2288,12 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1665
2288
|
});
|
|
1666
2289
|
const checkRuntimeImage = ({ image, line, label, at }) => {
|
|
1667
2290
|
const unknownRuntime = () => {
|
|
1668
|
-
|
|
2291
|
+
const rustImage = typeof image === "string" && /^rust(?=:|@|$)/i.test(canonicalDockerRuntime(image));
|
|
2292
|
+
if (!rustImage && dynamic(image) && authorizeDynamic("image", line, at)) return;
|
|
1669
2293
|
add({
|
|
1670
|
-
rule: "runtime-docker",
|
|
2294
|
+
rule: rustImage ? "rust-version" : "runtime-docker",
|
|
1671
2295
|
line,
|
|
1672
|
-
message: `cannot determine ${label} runtime; use a static image or a scoped dynamicSelectors declaration`
|
|
2296
|
+
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
2297
|
});
|
|
1674
2298
|
};
|
|
1675
2299
|
if (typeof image !== "string") {
|
|
@@ -1681,7 +2305,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1681
2305
|
const digestSeparator = structuralImage.indexOf("@");
|
|
1682
2306
|
const reference = digestSeparator < 0 ? canonical : canonical.slice(0, digestSeparator);
|
|
1683
2307
|
const structuralReference = digestSeparator < 0 ? structuralImage : structuralImage.slice(0, digestSeparator);
|
|
1684
|
-
const managedFamily = /^(node|python|oven\/bun)(?=:|$)/i.exec(structuralReference)?.[1];
|
|
2308
|
+
const managedFamily = /^(node|python|rust|oven\/bun)(?=:|$)/i.exec(structuralReference)?.[1];
|
|
1685
2309
|
const tagSeparator = managedFamily === void 0 ? structuralReference.lastIndexOf(":") : structuralReference.indexOf(":", managedFamily.length);
|
|
1686
2310
|
const hasTag = managedFamily === void 0 ? tagSeparator > structuralReference.lastIndexOf("/") : tagSeparator !== -1;
|
|
1687
2311
|
const imageRepository = hasTag ? reference.slice(0, tagSeparator) : reference;
|
|
@@ -1689,8 +2313,11 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1689
2313
|
unknownRuntime();
|
|
1690
2314
|
return;
|
|
1691
2315
|
}
|
|
1692
|
-
if (!/^(node|python|oven\/bun)$/i.test(imageRepository)) return;
|
|
2316
|
+
if (!/^(node|python|rust|oven\/bun)$/i.test(imageRepository)) return;
|
|
1693
2317
|
const tool = imageRepository.toLowerCase();
|
|
2318
|
+
let imageRule = "runtime-docker";
|
|
2319
|
+
if (tool === "rust") imageRule = "rust-version";
|
|
2320
|
+
else if (tool === "oven/bun") imageRule = "bun-pins";
|
|
1694
2321
|
if (image.includes("$")) {
|
|
1695
2322
|
unknownRuntime();
|
|
1696
2323
|
return;
|
|
@@ -1698,7 +2325,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1698
2325
|
const digest = digestSeparator < 0 ? void 0 : canonical.slice(digestSeparator + 1);
|
|
1699
2326
|
if (digest !== void 0 && !/^sha256:[a-fA-F\d]{64}$/.test(digest)) {
|
|
1700
2327
|
add({
|
|
1701
|
-
rule:
|
|
2328
|
+
rule: imageRule,
|
|
1702
2329
|
line,
|
|
1703
2330
|
message: `${label} ${tool} digest must be sha256 followed by 64 hexadecimal characters`
|
|
1704
2331
|
});
|
|
@@ -1706,14 +2333,15 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1706
2333
|
}
|
|
1707
2334
|
const tag = hasTag ? reference.slice(tagSeparator + 1) : void 0;
|
|
1708
2335
|
const variantSuffix = tool === "oven/bun" ? /-(?:alpine|slim|debian)$/ : /-(?:alpine(?:\d+(?:\.\d+)*)?|(?:bookworm|bullseye|trixie|buster)(?:-slim)?|slim)$/;
|
|
1709
|
-
const version = tag?.replace(variantSuffix, "");
|
|
2336
|
+
const version = tool === "rust" && tag !== void 0 ? rustImageVersion(tag) : tag?.replace(variantSuffix, "");
|
|
1710
2337
|
let expected = policy.bun;
|
|
1711
2338
|
if (tool === "node") expected = policy.node;
|
|
1712
2339
|
else if (tool === "python") expected = policy.python;
|
|
2340
|
+
else if (tool === "rust") expected = policy.rust;
|
|
1713
2341
|
if (digest !== void 0) {
|
|
1714
2342
|
if (version === void 0 || !/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/.test(version)) {
|
|
1715
2343
|
add({
|
|
1716
|
-
rule:
|
|
2344
|
+
rule: imageRule,
|
|
1717
2345
|
line,
|
|
1718
2346
|
message: `${label} ${tool} digest requires an exact stable patch tag matching ${expected}`
|
|
1719
2347
|
});
|
|
@@ -1731,7 +2359,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1731
2359
|
}
|
|
1732
2360
|
}
|
|
1733
2361
|
pin({
|
|
1734
|
-
rule:
|
|
2362
|
+
rule: imageRule,
|
|
1735
2363
|
value: version,
|
|
1736
2364
|
expected,
|
|
1737
2365
|
selector: tool === "node" ? "node" : void 0,
|
|
@@ -1748,6 +2376,17 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1748
2376
|
const heredocs = [];
|
|
1749
2377
|
let instruction = "";
|
|
1750
2378
|
let startLine = 1;
|
|
2379
|
+
let pendingRustRun;
|
|
2380
|
+
const checkRustCommand = (source, line) => {
|
|
2381
|
+
diagnostics.push(...checkRustReferences({
|
|
2382
|
+
type: "command",
|
|
2383
|
+
file,
|
|
2384
|
+
source,
|
|
2385
|
+
line,
|
|
2386
|
+
rust: policy.rust,
|
|
2387
|
+
exceptions: rustExceptions
|
|
2388
|
+
}));
|
|
2389
|
+
};
|
|
1751
2390
|
const expand = (value) => {
|
|
1752
2391
|
for (let count = 0; count <= variables.size; count++) {
|
|
1753
2392
|
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 +2473,49 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1834
2473
|
if (key !== void 0 && value !== void 0) variables.set(key, expand(value.replace(/^(["'])(.*)\1$/, "$2")));
|
|
1835
2474
|
return;
|
|
1836
2475
|
}
|
|
1837
|
-
|
|
2476
|
+
const run = /^\s*RUN\s+(.+)$/i.exec(content);
|
|
2477
|
+
if (run !== null) {
|
|
2478
|
+
let source = run[1] ?? "";
|
|
2479
|
+
source = source.replace(/^(?:--\S+\s+)*/, "");
|
|
2480
|
+
if (source.startsWith("[")) try {
|
|
2481
|
+
const words = JSON.parse(source);
|
|
2482
|
+
if (Array.isArray(words) && words.every((word) => typeof word === "string")) source = words.map((word) => `'${word.replaceAll("'", "'\\''")}'`).join(" ");
|
|
2483
|
+
} catch {}
|
|
2484
|
+
const implicit = /^(<<-?(?:'[^']+'|"[^"]+"|\S+))(?:\s+(.+))?$/.exec(source);
|
|
2485
|
+
if (implicit !== null && implicit[2] === void 0) source = `sh ${source}`;
|
|
2486
|
+
collectHeredocs(content, line);
|
|
2487
|
+
if (heredocs.length > 0) pendingRustRun = {
|
|
2488
|
+
source,
|
|
2489
|
+
line
|
|
2490
|
+
};
|
|
2491
|
+
else checkRustCommand(source, line);
|
|
2492
|
+
return;
|
|
2493
|
+
}
|
|
2494
|
+
const environment = /^\s*ENV\s+(.+)$/i.exec(content);
|
|
2495
|
+
if (environment !== null) {
|
|
2496
|
+
const assignments = dockerEnvironmentAssignments({
|
|
2497
|
+
source: environment[1] ?? "",
|
|
2498
|
+
escape
|
|
2499
|
+
});
|
|
2500
|
+
if (assignments === void 0) {
|
|
2501
|
+
add({
|
|
2502
|
+
rule: "runtime-docker",
|
|
2503
|
+
line,
|
|
2504
|
+
message: "invalid Docker ENV assignments"
|
|
2505
|
+
});
|
|
2506
|
+
return;
|
|
2507
|
+
}
|
|
2508
|
+
diagnostics.push(...checkRustReferences({
|
|
2509
|
+
type: "environment",
|
|
2510
|
+
file,
|
|
2511
|
+
assignments,
|
|
2512
|
+
line,
|
|
2513
|
+
rust: policy.rust,
|
|
2514
|
+
exceptions: rustExceptions
|
|
2515
|
+
}));
|
|
2516
|
+
return;
|
|
2517
|
+
}
|
|
2518
|
+
if (/^\s*COPY\s+/i.test(content)) {
|
|
1838
2519
|
collectHeredocs(content, line);
|
|
1839
2520
|
return;
|
|
1840
2521
|
}
|
|
@@ -1855,7 +2536,12 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1855
2536
|
lines.forEach((line, index) => {
|
|
1856
2537
|
const heredoc = heredocs.at(0);
|
|
1857
2538
|
if (heredoc !== void 0) {
|
|
2539
|
+
if (pendingRustRun !== void 0) pendingRustRun.source += `\n${line}`;
|
|
1858
2540
|
if ((heredoc.stripTabs ? line.replace(/^\t+/, "") : line) === heredoc.delimiter) heredocs.shift();
|
|
2541
|
+
if (heredocs.length === 0 && pendingRustRun !== void 0) {
|
|
2542
|
+
checkRustCommand(pendingRustRun.source, pendingRustRun.line);
|
|
2543
|
+
pendingRustRun = void 0;
|
|
2544
|
+
}
|
|
1859
2545
|
return;
|
|
1860
2546
|
}
|
|
1861
2547
|
if (line.trimStart().startsWith("#")) return;
|
|
@@ -2530,18 +3216,20 @@ const record = (value) => typeof value === "object" && value !== null && !Array.
|
|
|
2530
3216
|
const array = (value) => Array.isArray(value);
|
|
2531
3217
|
/** Repository-owned declarations are explicit; malformed decisions fail closed. */
|
|
2532
3218
|
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
|
|
3219
|
+
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
3220
|
const disabled = /* @__PURE__ */ new Set();
|
|
2535
3221
|
const optOuts = input["optOuts"] ?? [];
|
|
2536
3222
|
if (!array(optOuts)) throw new Error("optOuts must be an array");
|
|
2537
3223
|
for (const entry of optOuts) {
|
|
2538
3224
|
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");
|
|
3225
|
+
if (entry["rule"] === "rust-version") throw new Error("rust-version requires expiring exact-pin rustExceptions, not an opt-out");
|
|
2539
3226
|
if (entry["rule"] === "node-engine") throw new Error("node-engine cannot be opted out; engines.node must support the shared Node series");
|
|
2540
3227
|
if (entry["rule"] === "cloud-setup-drift") throw new Error("cloud-setup-drift cannot be opted out; generated setup must match shared policy");
|
|
2541
3228
|
disabled.add(entry["rule"]);
|
|
2542
3229
|
}
|
|
2543
3230
|
return {
|
|
2544
3231
|
disabled,
|
|
3232
|
+
rustExceptions: parseRustExceptions(input["rustExceptions"]),
|
|
2545
3233
|
cloud: parseCloudSetup(input["cloud"]),
|
|
2546
3234
|
engineFloors: parseEngineFloors(input["engineFloors"]),
|
|
2547
3235
|
dynamicSelectors: parseDynamicSelectors(input["dynamicSelectors"]),
|
|
@@ -2554,27 +3242,47 @@ const readToolchainInputs = (root) => {
|
|
|
2554
3242
|
const tracked = execFileSync("git", [
|
|
2555
3243
|
"ls-files",
|
|
2556
3244
|
"-z",
|
|
2557
|
-
"--cached"
|
|
3245
|
+
"--cached",
|
|
3246
|
+
"--stage"
|
|
2558
3247
|
], {
|
|
2559
3248
|
cwd: root,
|
|
2560
3249
|
encoding: "utf8"
|
|
2561
|
-
}).split("\0").filter((
|
|
3250
|
+
}).split("\0").filter((entry) => entry !== "" && !entry.startsWith("160000 ")).map((entry) => entry.slice(entry.indexOf(" ") + 1));
|
|
2562
3251
|
const files = {};
|
|
2563
3252
|
const trackedFiles = new Set(tracked);
|
|
2564
3253
|
const resolvedRoot = realpathSync(root);
|
|
2565
3254
|
for (const file of tracked) {
|
|
2566
3255
|
const kind = toolchainInputKind(file);
|
|
2567
|
-
|
|
3256
|
+
const shebangCandidate = kind === void 0 && path.posix.extname(file) === "" && !file.split("/").some((part) => part === "vendor" || part === "node_modules");
|
|
3257
|
+
if (kind === void 0 && !shebangCandidate) continue;
|
|
2568
3258
|
if (kind === "presence") {
|
|
2569
3259
|
files[file] = "";
|
|
2570
3260
|
continue;
|
|
2571
3261
|
}
|
|
2572
3262
|
try {
|
|
2573
|
-
const
|
|
3263
|
+
const inputPath = path.join(resolvedRoot, file);
|
|
3264
|
+
if (shebangCandidate) {
|
|
3265
|
+
const input = lstatSync(inputPath);
|
|
3266
|
+
if (!input.isFile() && !input.isSymbolicLink()) continue;
|
|
3267
|
+
}
|
|
3268
|
+
const resolvedFile = realpathSync(inputPath);
|
|
2574
3269
|
const relative = path.relative(resolvedRoot, resolvedFile);
|
|
2575
3270
|
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");
|
|
3271
|
+
if (shebangCandidate) {
|
|
3272
|
+
if (!lstatSync(resolvedFile).isFile()) continue;
|
|
3273
|
+
const descriptor = openSync(resolvedFile, "r");
|
|
3274
|
+
const prefix = Buffer.alloc(512);
|
|
3275
|
+
let count;
|
|
3276
|
+
try {
|
|
3277
|
+
count = readSync(descriptor, prefix, 0, prefix.length, 0);
|
|
3278
|
+
} finally {
|
|
3279
|
+
closeSync(descriptor);
|
|
3280
|
+
}
|
|
3281
|
+
if (toolchainInputKind(file, prefix.toString("utf8", 0, count)) === void 0) continue;
|
|
3282
|
+
}
|
|
2576
3283
|
files[file] = readFileSync(resolvedFile, "utf8");
|
|
2577
3284
|
} catch {
|
|
3285
|
+
if (shebangCandidate) continue;
|
|
2578
3286
|
diagnostics.push({
|
|
2579
3287
|
rule: "configuration",
|
|
2580
3288
|
path: file,
|
|
@@ -2675,6 +3383,7 @@ const checkToolchain = ({ root, policy, onDelegated }) => {
|
|
|
2675
3383
|
}
|
|
2676
3384
|
},
|
|
2677
3385
|
trackedFiles,
|
|
3386
|
+
rustExceptions: configuration.rustExceptions,
|
|
2678
3387
|
dynamicSelectors: dynamicSelectors.filter((entry) => entry.path === file),
|
|
2679
3388
|
onDynamicSelector: (entry) => selectorMatches.set(entry, (selectorMatches.get(entry) ?? 0) + 1),
|
|
2680
3389
|
repository,
|