@stll/oxlint-config 0.15.0 → 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 +15 -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 +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.cjs
CHANGED
|
@@ -12,9 +12,9 @@ picomatch = require_rolldown_runtime.__toESM(picomatch, 1);
|
|
|
12
12
|
let smol_toml = require("smol-toml");
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/toolchain-consumer-checks.ts
|
|
15
|
-
const consumerRunnerVersion = "0.
|
|
15
|
+
const consumerRunnerVersion = "1.0.0";
|
|
16
16
|
const consumerCheckWorkflow = "stella/.github/.github/workflows/package-consumer-compat.yml";
|
|
17
|
-
const record$
|
|
17
|
+
const record$7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18
18
|
const repositoryDirectory = (value) => typeof value === "string" && value !== "" && !value.includes("\\") && !value.includes("${{") && !value.split("").some((character) => {
|
|
19
19
|
const code = character.charCodeAt(0);
|
|
20
20
|
return code < 32 || code === 127;
|
|
@@ -100,12 +100,12 @@ const validConsumerCron = (text) => {
|
|
|
100
100
|
});
|
|
101
101
|
};
|
|
102
102
|
const nightlySchedule = (value) => {
|
|
103
|
-
if (!record$
|
|
103
|
+
if (!record$7(value)) return false;
|
|
104
104
|
const schedule = value["schedule"];
|
|
105
105
|
if (!Array.isArray(schedule) || schedule.length === 0) return false;
|
|
106
106
|
const coveredDays = /* @__PURE__ */ new Set();
|
|
107
107
|
for (const entry of schedule) {
|
|
108
|
-
if (!record$
|
|
108
|
+
if (!record$7(entry) || typeof entry["cron"] !== "string" || !validConsumerCron(entry["cron"])) return false;
|
|
109
109
|
const fields = entry["cron"].trim().split(/\s+/);
|
|
110
110
|
const selections = cronFields.map((field, index) => {
|
|
111
111
|
const text = fields.at(index);
|
|
@@ -121,7 +121,7 @@ const parseConsumerChecks = (input) => {
|
|
|
121
121
|
if (!Array.isArray(input)) throw new Error("consumerChecks must be an array");
|
|
122
122
|
const targets = /* @__PURE__ */ new Set();
|
|
123
123
|
return input.map((entry) => {
|
|
124
|
-
if (!record$
|
|
124
|
+
if (!record$7(entry) || Object.keys(entry).some((key) => ![
|
|
125
125
|
"workflow",
|
|
126
126
|
"job",
|
|
127
127
|
"packages",
|
|
@@ -159,9 +159,9 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
|
|
|
159
159
|
});
|
|
160
160
|
if (document.errors.length > 0) throw new Error("invalid workflow YAML");
|
|
161
161
|
const source = document.toJS({ maxAliasCount: 100 });
|
|
162
|
-
if (!record$
|
|
162
|
+
if (!record$7(source) || !record$7(source["jobs"])) continue;
|
|
163
163
|
for (const [jobName, job] of Object.entries(source["jobs"])) {
|
|
164
|
-
if (!record$
|
|
164
|
+
if (!record$7(job) || typeof job["uses"] !== "string") continue;
|
|
165
165
|
const [workflowPath, ref] = job["uses"].split("@");
|
|
166
166
|
if (workflowPath?.toLowerCase() !== "stella/.github/.github/workflows/package-consumer-compat.yml") continue;
|
|
167
167
|
const declaration = declarations.find((entry) => entry.workflow === workflow && entry.job === jobName);
|
|
@@ -175,7 +175,7 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
|
|
|
175
175
|
const approved = Object.entries(policy.actions).find(([name]) => name.toLowerCase() === consumerCheckWorkflow)?.[1];
|
|
176
176
|
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`);
|
|
177
177
|
const inputs = job["with"];
|
|
178
|
-
if (!record$
|
|
178
|
+
if (!record$7(inputs)) throw new Error(`${workflow}:${jobName} requires consumer inputs`);
|
|
179
179
|
const names = /* @__PURE__ */ new Set();
|
|
180
180
|
for (const key of Object.keys(inputs)) {
|
|
181
181
|
const normalized = key.toLowerCase();
|
|
@@ -259,10 +259,12 @@ const canonicalDockerRuntime = (image) => {
|
|
|
259
259
|
const registry = dockerHubRegistries.find((entry) => image.toLowerCase().startsWith(`${entry}/`));
|
|
260
260
|
return (registry === void 0 ? image : image.slice(registry.length + 1)).replace(/^library\//i, "");
|
|
261
261
|
};
|
|
262
|
+
/** Preserve Rust release identity across official Debian slim and Alpine variants. */
|
|
263
|
+
const rustImageVersion = (tag) => tag.replace(/-(?:alpine(?:\d+(?:\.\d+)*)?|(?:bookworm|bullseye|trixie|buster)(?:-slim)?|slim(?:-(?:bookworm|bullseye|trixie|buster))?)$/, "");
|
|
262
264
|
//#endregion
|
|
263
265
|
//#region src/toolchain-dependabot.ts
|
|
264
266
|
const dependabotRules = ["dependabot-policy"];
|
|
265
|
-
const record$
|
|
267
|
+
const record$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
266
268
|
const strings = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
267
269
|
const sameStrings = (value, expected) => {
|
|
268
270
|
if (!strings(value) || value.length !== expected.length) return false;
|
|
@@ -284,8 +286,8 @@ const workspaceTable = (parsed, ecosystem) => {
|
|
|
284
286
|
case "cargo": return parsed["workspace"];
|
|
285
287
|
case "uv": {
|
|
286
288
|
const tool = parsed["tool"];
|
|
287
|
-
const uv = record$
|
|
288
|
-
return record$
|
|
289
|
+
const uv = record$6(tool) ? tool["uv"] : void 0;
|
|
290
|
+
return record$6(uv) ? uv["workspace"] : void 0;
|
|
289
291
|
}
|
|
290
292
|
default: throw new Error("unknown workspace ecosystem", { cause: ecosystem });
|
|
291
293
|
}
|
|
@@ -305,7 +307,7 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
305
307
|
const document = (0, yaml.parseDocument)(text);
|
|
306
308
|
if (document.errors.length > 0) throw new Error(`${workspaceFile}:1: invalid pnpm workspace YAML`);
|
|
307
309
|
const parsed = document.toJS({ maxAliasCount: 100 });
|
|
308
|
-
if (!record$
|
|
310
|
+
if (!record$6(parsed)) throw new Error(`${workspaceFile}:1: pnpm workspace must contain an object`);
|
|
309
311
|
const patterns = parsed["packages"] ?? [];
|
|
310
312
|
if (!strings(patterns)) throw new Error(`${workspaceFile}:1: pnpm packages must be a string array`);
|
|
311
313
|
workspaces.set(directory, patterns.map((pattern) => pattern.startsWith("!") ? `!${node_path.default.posix.normalize(pattern.slice(1))}` : node_path.default.posix.normalize(pattern)));
|
|
@@ -317,13 +319,13 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
317
319
|
}
|
|
318
320
|
try {
|
|
319
321
|
const parsed = ecosystem === "npm" ? JSON.parse(files[file] ?? "") : (0, smol_toml.parse)(files[file] ?? "");
|
|
320
|
-
if (!record$
|
|
322
|
+
if (!record$6(parsed)) continue;
|
|
321
323
|
if (ecosystem === "npm" && typeof parsed["packageManager"] === "string" && parsed["packageManager"].startsWith("pnpm@")) continue;
|
|
322
324
|
const value = workspaceTable(parsed, ecosystem);
|
|
323
|
-
const patterns = record$
|
|
324
|
-
const members = ecosystem === "cargo" && record$
|
|
325
|
+
const patterns = record$6(value) ? value[ecosystem === "npm" ? "packages" : "members"] : value;
|
|
326
|
+
const members = ecosystem === "cargo" && record$6(value) && patterns === void 0 ? [] : patterns;
|
|
325
327
|
if (strings(members)) {
|
|
326
|
-
const excluded = ecosystem !== "npm" && record$
|
|
328
|
+
const excluded = ecosystem !== "npm" && record$6(value) && strings(value["exclude"]) ? value["exclude"].map((pattern) => `!${pattern}`) : [];
|
|
327
329
|
workspaces.set(node_path.default.posix.dirname(file), [...members, ...excluded]);
|
|
328
330
|
}
|
|
329
331
|
} catch {}
|
|
@@ -374,11 +376,11 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
374
376
|
for (const directory of memberDirectories) try {
|
|
375
377
|
const parsed = (0, smol_toml.parse)(files[node_path.default.posix.join(directory, "Cargo.toml")] ?? "");
|
|
376
378
|
const workspace = (0, smol_toml.parse)(files[node_path.default.posix.join(root, "Cargo.toml")] ?? "")["workspace"];
|
|
377
|
-
const inherited = record$
|
|
379
|
+
const inherited = record$6(workspace) ? workspace["dependencies"] : void 0;
|
|
378
380
|
const dependencyOwners = [parsed];
|
|
379
381
|
const targets = parsed["target"];
|
|
380
|
-
if (record$
|
|
381
|
-
for (const target of Object.values(targets)) if (record$
|
|
382
|
+
if (record$6(targets)) {
|
|
383
|
+
for (const target of Object.values(targets)) if (record$6(target)) dependencyOwners.push(target);
|
|
382
384
|
}
|
|
383
385
|
for (const owner of dependencyOwners) for (const key of [
|
|
384
386
|
"dependencies",
|
|
@@ -386,12 +388,12 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
386
388
|
"build-dependencies"
|
|
387
389
|
]) {
|
|
388
390
|
const dependencies = owner[key];
|
|
389
|
-
if (!record$
|
|
391
|
+
if (!record$6(dependencies)) continue;
|
|
390
392
|
for (const [name, dependency] of Object.entries(dependencies)) {
|
|
391
|
-
if (!record$
|
|
393
|
+
if (!record$6(dependency)) continue;
|
|
392
394
|
const isInherited = dependency["workspace"] === true;
|
|
393
|
-
const resolved = isInherited && record$
|
|
394
|
-
if (!record$
|
|
395
|
+
const resolved = isInherited && record$6(inherited) ? inherited[name] : dependency;
|
|
396
|
+
if (!record$6(resolved) || typeof resolved["path"] !== "string") continue;
|
|
395
397
|
if (node_path.default.posix.isAbsolute(resolved["path"]) || /\\|\$|^[A-Za-z]:/.test(resolved["path"])) continue;
|
|
396
398
|
const target = node_path.default.posix.normalize(node_path.default.posix.join(isInherited ? root : directory, resolved["path"]));
|
|
397
399
|
const relative = node_path.default.posix.relative(root, target);
|
|
@@ -419,7 +421,7 @@ const javascriptEcosystem = (directory, files) => {
|
|
|
419
421
|
if (files[node_path.default.posix.join(root, "bun.lock")] !== void 0) return "bun";
|
|
420
422
|
try {
|
|
421
423
|
const manifest = JSON.parse(files[node_path.default.posix.join(root, "package.json")] ?? "{}");
|
|
422
|
-
if (record$
|
|
424
|
+
if (record$6(manifest) && typeof manifest["packageManager"] === "string" && manifest["packageManager"].startsWith("bun@")) return "bun";
|
|
423
425
|
} catch {}
|
|
424
426
|
return "npm";
|
|
425
427
|
};
|
|
@@ -431,7 +433,7 @@ const pythonEcosystem = (file, files) => {
|
|
|
431
433
|
const pyproject = files[node_path.default.posix.join(root, "pyproject.toml")];
|
|
432
434
|
if (pyproject !== void 0) try {
|
|
433
435
|
const tool = (0, smol_toml.parse)(pyproject)["tool"];
|
|
434
|
-
if (record$
|
|
436
|
+
if (record$6(tool) && record$6(tool["uv"])) return "uv";
|
|
435
437
|
} catch {}
|
|
436
438
|
return "pip";
|
|
437
439
|
};
|
|
@@ -502,7 +504,7 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
502
504
|
add("cannot resolve Dependabot YAML aliases");
|
|
503
505
|
return diagnostics;
|
|
504
506
|
}
|
|
505
|
-
if (!record$
|
|
507
|
+
if (!record$6(parsed) || parsed["version"] !== 2 || !Array.isArray(parsed["updates"])) {
|
|
506
508
|
add("Dependabot must declare version: 2 and an updates array");
|
|
507
509
|
return diagnostics;
|
|
508
510
|
}
|
|
@@ -515,7 +517,7 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
515
517
|
});
|
|
516
518
|
if (legacyDocument.errors.length === 0) legacy = legacyDocument.toJS({ maxAliasCount: 100 });
|
|
517
519
|
} catch {}
|
|
518
|
-
const legacyUpdates = record$
|
|
520
|
+
const legacyUpdates = record$6(legacy) && Array.isArray(legacy["updates"]) ? legacy["updates"] : [];
|
|
519
521
|
const rootClaims = /* @__PURE__ */ new Map();
|
|
520
522
|
const directoryClaims = /* @__PURE__ */ new Map();
|
|
521
523
|
for (const [index, update] of parsed["updates"].entries()) {
|
|
@@ -529,7 +531,7 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
529
531
|
return offset === void 0 ? 1 : lineCounter.linePos(offset).line;
|
|
530
532
|
};
|
|
531
533
|
const report = (key, message) => add(message, lineOf(key));
|
|
532
|
-
if (!record$
|
|
534
|
+
if (!record$6(update) || typeof update["package-ecosystem"] !== "string") {
|
|
533
535
|
report("package-ecosystem", "each Dependabot update must declare a package ecosystem");
|
|
534
536
|
continue;
|
|
535
537
|
}
|
|
@@ -563,21 +565,21 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
563
565
|
covered.set(ecosystem, matches);
|
|
564
566
|
const schedule = update["schedule"];
|
|
565
567
|
const legacyUpdate = legacyUpdates.at(index);
|
|
566
|
-
const legacySchedule = record$
|
|
567
|
-
if (record$
|
|
568
|
-
if (!record$
|
|
568
|
+
const legacySchedule = record$6(legacyUpdate) ? legacyUpdate["schedule"] : void 0;
|
|
569
|
+
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`);
|
|
570
|
+
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`);
|
|
569
571
|
const cooldown = update["cooldown"];
|
|
570
|
-
if (!record$
|
|
572
|
+
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}`);
|
|
571
573
|
const groups = update["groups"];
|
|
572
|
-
if (!record$
|
|
574
|
+
if (!record$6(groups) || Object.keys(groups).length !== Object.keys(policy.groups).length || Object.entries(policy.groups).some(([name, expected]) => {
|
|
573
575
|
const group = groups[name];
|
|
574
|
-
return !record$
|
|
576
|
+
return !record$6(group) || Object.keys(group).length !== 2 || !sameStrings(group["patterns"], expected.patterns) || !sameStrings(group["update-types"], expected.updateTypes);
|
|
575
577
|
})) report("groups", `${ecosystem}: groups must match the shared policy`);
|
|
576
578
|
const ignored = ignoredFor(ecosystem, policy);
|
|
577
579
|
const ignore = update["ignore"];
|
|
578
580
|
if (ignore !== void 0 && (!Array.isArray(ignore) || ignore.length === 0)) report("ignore", `${ecosystem}: omit ignore or provide a nonempty array`);
|
|
579
|
-
if (Array.isArray(ignore) && ignore.some((entry) => !record$
|
|
580
|
-
for (const name of ignored) if (!Array.isArray(ignore) || !ignore.some((entry) => record$
|
|
581
|
+
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`);
|
|
582
|
+
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}`);
|
|
581
583
|
}
|
|
582
584
|
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}`);
|
|
583
585
|
return diagnostics;
|
|
@@ -589,7 +591,7 @@ const locatorValidators = {
|
|
|
589
591
|
"bun-source": /^(?:jobs\.[\w-]+|runs)\.steps\.[\w-]+$/
|
|
590
592
|
};
|
|
591
593
|
const knownKind = (value) => typeof value === "string" && Object.hasOwn(locatorValidators, value);
|
|
592
|
-
const record$
|
|
594
|
+
const record$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
593
595
|
/** One reviewed declaration identifies one unresolved selector, never a rule. */
|
|
594
596
|
const parseDynamicSelectors = (input) => {
|
|
595
597
|
if (input === void 0) return [];
|
|
@@ -597,7 +599,7 @@ const parseDynamicSelectors = (input) => {
|
|
|
597
599
|
const result = [];
|
|
598
600
|
const keys = /* @__PURE__ */ new Set();
|
|
599
601
|
for (const entry of input) {
|
|
600
|
-
if (!record$
|
|
602
|
+
if (!record$5(entry) || Object.keys(entry).some((key) => ![
|
|
601
603
|
"path",
|
|
602
604
|
"kind",
|
|
603
605
|
"reason",
|
|
@@ -633,7 +635,7 @@ const parseDynamicSelectors = (input) => {
|
|
|
633
635
|
};
|
|
634
636
|
//#endregion
|
|
635
637
|
//#region src/toolchain-engine-floors.ts
|
|
636
|
-
const record$
|
|
638
|
+
const record$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
637
639
|
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 !== "..");
|
|
638
640
|
const parseEngineFloors = (input) => {
|
|
639
641
|
if (input === void 0) return [];
|
|
@@ -641,7 +643,7 @@ const parseEngineFloors = (input) => {
|
|
|
641
643
|
const result = [];
|
|
642
644
|
const jobs = /* @__PURE__ */ new Set();
|
|
643
645
|
for (const entry of input) {
|
|
644
|
-
if (!record$
|
|
646
|
+
if (!record$4(entry) || Object.keys(entry).some((key) => ![
|
|
645
647
|
"package",
|
|
646
648
|
"workflow",
|
|
647
649
|
"job"
|
|
@@ -662,16 +664,16 @@ const resolveEngineFloor = (entry, files) => {
|
|
|
662
664
|
const workflowText = files[entry.workflow];
|
|
663
665
|
if (manifestText === void 0 || workflowText === void 0) throw new Error("engine floor package and workflow must be tracked");
|
|
664
666
|
const manifest = JSON.parse(manifestText);
|
|
665
|
-
if (!record$
|
|
667
|
+
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");
|
|
666
668
|
const range = manifest["engines"]["node"];
|
|
667
669
|
const minimum = (0, semver.validRange)(range) === null ? null : (0, semver.minVersion)(range);
|
|
668
670
|
if (minimum === null || minimum.prerelease.length !== 0) throw new Error("engine floor engines.node must declare a stable minimum");
|
|
669
671
|
const document = (0, yaml.parseDocument)(workflowText, { merge: true });
|
|
670
672
|
if (document.errors.length !== 0) throw new Error("engine floor workflow must be valid YAML");
|
|
671
673
|
const workflow = document.toJS({ maxAliasCount: 100 });
|
|
672
|
-
if (!record$
|
|
674
|
+
if (!record$4(workflow) || !record$4(workflow["jobs"])) throw new Error("engine floor job must exist in its tracked workflow");
|
|
673
675
|
const job = workflow["jobs"][entry.job];
|
|
674
|
-
if (!record$
|
|
676
|
+
if (!record$4(job)) throw new Error("engine floor job must exist in its tracked workflow");
|
|
675
677
|
if (!Array.isArray(job["steps"]) || Object.hasOwn(job, "uses")) throw new Error("engine floor job must declare steps and cannot reuse a workflow");
|
|
676
678
|
return {
|
|
677
679
|
...entry,
|
|
@@ -691,7 +693,7 @@ const packageRules = [
|
|
|
691
693
|
"typescript-layout",
|
|
692
694
|
"node-engine"
|
|
693
695
|
];
|
|
694
|
-
const record$
|
|
696
|
+
const record$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
695
697
|
const shellSubstitutions = (command) => {
|
|
696
698
|
const frames = [{
|
|
697
699
|
kind: "root",
|
|
@@ -752,43 +754,371 @@ const shellSubstitutions = (command) => {
|
|
|
752
754
|
for (const frame of frames.slice(1)) bodies.push(command.slice(frame.start));
|
|
753
755
|
return bodies;
|
|
754
756
|
};
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
757
|
+
const shellValueOptions = /* @__PURE__ */ new Set([
|
|
758
|
+
"--rcfile",
|
|
759
|
+
"--init-file",
|
|
760
|
+
"-o",
|
|
761
|
+
"+o",
|
|
762
|
+
"-O",
|
|
763
|
+
"+O"
|
|
764
|
+
]);
|
|
765
|
+
const shellExecutable = /^(?:\/(?:[^/]+\/)*)?(?:bash|dash|ksh|zsh|sh)$/;
|
|
766
|
+
const shellTokens = (source) => {
|
|
767
|
+
const tokens = [];
|
|
768
|
+
for (let index = 0; index < source.length;) {
|
|
769
|
+
const character = source.at(index);
|
|
770
|
+
if (character === void 0) break;
|
|
771
|
+
if (character === "\n") {
|
|
772
|
+
tokens.push({
|
|
773
|
+
kind: "boundary",
|
|
774
|
+
value: character,
|
|
775
|
+
index: index++
|
|
776
|
+
});
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
if (/\s/.test(character)) {
|
|
780
|
+
index++;
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
if (character === "#") {
|
|
784
|
+
const end = source.indexOf("\n", index);
|
|
785
|
+
const stop = end < 0 ? source.length : end;
|
|
786
|
+
tokens.push({
|
|
787
|
+
kind: "comment",
|
|
788
|
+
value: source.slice(index, stop),
|
|
789
|
+
index
|
|
790
|
+
});
|
|
791
|
+
index = stop;
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
const redirect = /^(?:\d*)(?:&>>|&>|<<<|<<-|<<|>>|>\||>&|<&|<>|>|<)/.exec(source.slice(index));
|
|
795
|
+
if (redirect !== null) {
|
|
796
|
+
tokens.push({
|
|
797
|
+
kind: "redirect",
|
|
798
|
+
value: redirect[0],
|
|
799
|
+
index
|
|
800
|
+
});
|
|
801
|
+
index += redirect[0].length;
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
const boundary = /^(?:&&|\|\||[();&|])/.exec(source.slice(index));
|
|
805
|
+
if (boundary !== null) {
|
|
806
|
+
tokens.push({
|
|
807
|
+
kind: "boundary",
|
|
808
|
+
value: boundary[0],
|
|
809
|
+
index
|
|
810
|
+
});
|
|
811
|
+
index += boundary[0].length;
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
const start = index;
|
|
815
|
+
const frames = [{
|
|
816
|
+
kind: "word",
|
|
817
|
+
quote: void 0,
|
|
818
|
+
depth: 0
|
|
819
|
+
}];
|
|
820
|
+
while (index < source.length) {
|
|
821
|
+
const frame = frames.at(-1);
|
|
822
|
+
const current = source.at(index);
|
|
823
|
+
if (frame === void 0 || current === void 0) break;
|
|
824
|
+
if (current === "\\" && frame.quote !== "'") {
|
|
825
|
+
index += 2;
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
if (current === "`" && frame.kind === "backticks") {
|
|
829
|
+
frames.pop();
|
|
830
|
+
index++;
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
if (current === frame.quote) {
|
|
834
|
+
frame.quote = void 0;
|
|
835
|
+
index++;
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
if (frame.quote === "'") {
|
|
839
|
+
index++;
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
if (current === "$" && source.at(index + 1) === "(") {
|
|
843
|
+
frames.push({
|
|
844
|
+
kind: "parentheses",
|
|
845
|
+
quote: void 0,
|
|
846
|
+
depth: 1
|
|
847
|
+
});
|
|
848
|
+
index += 2;
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
if (current === "`") {
|
|
852
|
+
frames.push({
|
|
853
|
+
kind: "backticks",
|
|
854
|
+
quote: void 0,
|
|
855
|
+
depth: 0
|
|
856
|
+
});
|
|
857
|
+
index++;
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
if (frame.quote !== void 0) {
|
|
861
|
+
index++;
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
if (current === "'" || current === "\"") {
|
|
865
|
+
frame.quote = current;
|
|
866
|
+
index++;
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
if (frame.kind === "parentheses") {
|
|
870
|
+
if (current === "(") frame.depth++;
|
|
871
|
+
if (current === ")" && --frame.depth === 0) frames.pop();
|
|
872
|
+
index++;
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
if (frame.kind === "word" && /[\s<>;&|()]/.test(current)) break;
|
|
876
|
+
index++;
|
|
877
|
+
}
|
|
878
|
+
const value = source.slice(start, index);
|
|
879
|
+
tokens.push({
|
|
880
|
+
kind: "word",
|
|
881
|
+
value,
|
|
882
|
+
index: start
|
|
883
|
+
});
|
|
762
884
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
885
|
+
let groupDepth = 0;
|
|
886
|
+
let redirectTarget = false;
|
|
887
|
+
const commandWords = [];
|
|
888
|
+
for (const token of tokens) {
|
|
889
|
+
if (token.kind === "comment") continue;
|
|
890
|
+
if (token.kind === "redirect") {
|
|
891
|
+
redirectTarget = true;
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
if (redirectTarget) {
|
|
895
|
+
redirectTarget = false;
|
|
896
|
+
if (token.kind === "word") continue;
|
|
897
|
+
}
|
|
898
|
+
if (token.kind === "boundary") {
|
|
899
|
+
commandWords.length = 0;
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
const functionBody = commandWords.length === 2 && commandWords.at(0) === "function";
|
|
903
|
+
const opensGroup = token.value === "{" && (commandWords.length === 0 || functionBody);
|
|
904
|
+
const closesGroup = token.value === "}" && groupDepth > 0 && commandWords.length === 0;
|
|
905
|
+
if (opensGroup || closesGroup) {
|
|
906
|
+
token.kind = "boundary";
|
|
907
|
+
groupDepth += opensGroup ? 1 : -1;
|
|
908
|
+
commandWords.length = 0;
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
if (commandWords.length === 0 && [
|
|
912
|
+
"if",
|
|
913
|
+
"then",
|
|
914
|
+
"elif",
|
|
915
|
+
"else",
|
|
916
|
+
"do",
|
|
917
|
+
"!",
|
|
918
|
+
"time"
|
|
919
|
+
].includes(token.value)) continue;
|
|
920
|
+
commandWords.push(token.value);
|
|
921
|
+
}
|
|
922
|
+
return tokens;
|
|
923
|
+
};
|
|
924
|
+
const shellArgumentTokens = (tokens) => {
|
|
925
|
+
const arguments_ = [];
|
|
926
|
+
let redirectTarget = false;
|
|
927
|
+
for (const token of tokens) {
|
|
928
|
+
if (token.kind === "comment") continue;
|
|
929
|
+
if (token.kind === "redirect") {
|
|
930
|
+
redirectTarget = true;
|
|
931
|
+
continue;
|
|
932
|
+
}
|
|
933
|
+
if (redirectTarget) {
|
|
934
|
+
redirectTarget = false;
|
|
935
|
+
if (token.kind === "word") continue;
|
|
936
|
+
}
|
|
937
|
+
arguments_.push(token);
|
|
938
|
+
}
|
|
939
|
+
return arguments_;
|
|
940
|
+
};
|
|
941
|
+
const shellReadsProgramStdin = (tokens) => {
|
|
942
|
+
const words = executableShellWords(shellArgumentTokens(tokens).map(({ value }) => value)).map(decodeShellWord);
|
|
943
|
+
if (!shellExecutable.test(words.at(0) ?? "")) return false;
|
|
944
|
+
let stdin = false;
|
|
945
|
+
let script = false;
|
|
946
|
+
for (let index = 1; index < words.length; index++) {
|
|
947
|
+
const option = words.at(index) ?? "";
|
|
948
|
+
if (option === "--" || option === "-") {
|
|
949
|
+
script = index + 1 < words.length;
|
|
950
|
+
break;
|
|
951
|
+
}
|
|
952
|
+
if (!/^[+-]/.test(option)) {
|
|
953
|
+
script = true;
|
|
954
|
+
break;
|
|
955
|
+
}
|
|
956
|
+
if (shellValueOptions.has(option)) {
|
|
957
|
+
index++;
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
960
|
+
if (/^-[^-]*c/.test(option)) return false;
|
|
961
|
+
if (/^-[^-]*s/.test(option)) stdin = true;
|
|
962
|
+
}
|
|
963
|
+
return stdin || !script;
|
|
964
|
+
};
|
|
965
|
+
const shellStdinHeredoc = (tokens) => {
|
|
966
|
+
const descriptors = /* @__PURE__ */ new Map();
|
|
967
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
968
|
+
const token = tokens.at(index);
|
|
969
|
+
if (token?.kind !== "redirect") continue;
|
|
970
|
+
const declaration = /^(\d*)(.+)$/.exec(token.value);
|
|
971
|
+
const operator = declaration?.[2];
|
|
972
|
+
if (operator === void 0) continue;
|
|
973
|
+
const descriptor = String(Number(declaration?.[1] || (operator.startsWith("<") ? "0" : "1")));
|
|
974
|
+
const target = tokens.at(++index);
|
|
975
|
+
if (operator === "<<" || operator === "<<-") {
|
|
976
|
+
descriptors.set(descriptor, token);
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
if (operator === "<&" || operator === ">&") {
|
|
980
|
+
const copy = /^(\d+)(-?)$/.exec(decodeShellWord(target?.value ?? ""));
|
|
981
|
+
const sourceDescriptor = copy?.[1] === void 0 ? void 0 : String(Number(copy[1]));
|
|
982
|
+
const source = sourceDescriptor === void 0 ? void 0 : descriptors.get(sourceDescriptor);
|
|
983
|
+
if (source === void 0) descriptors.delete(descriptor);
|
|
984
|
+
else descriptors.set(descriptor, source);
|
|
985
|
+
if (copy?.[2] === "-" && sourceDescriptor !== void 0) descriptors.delete(sourceDescriptor);
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
descriptors.delete(descriptor);
|
|
989
|
+
if (operator === "&>" || operator === "&>>") descriptors.delete("2");
|
|
990
|
+
}
|
|
991
|
+
return descriptors.get("0");
|
|
992
|
+
};
|
|
993
|
+
const shellExecutableSource = (command) => {
|
|
994
|
+
const pending = [];
|
|
995
|
+
return command.split(/\r?\n/).map((line) => {
|
|
996
|
+
const here = pending.at(0);
|
|
997
|
+
if (here !== void 0) {
|
|
998
|
+
if ((here.stripTabs ? line.replace(/^\t+/, "") : line) === here.delimiter) {
|
|
999
|
+
pending.shift();
|
|
1000
|
+
return "";
|
|
1001
|
+
}
|
|
1002
|
+
if (here.execute) return line;
|
|
1003
|
+
if (here.literal) return "";
|
|
1004
|
+
return shellSubstitutions(line).join("\n");
|
|
1005
|
+
}
|
|
1006
|
+
const words = shellTokens(line);
|
|
1007
|
+
let commandStart = 0;
|
|
1008
|
+
for (let index = 0; index < words.length; index++) {
|
|
1009
|
+
const match = words.at(index);
|
|
1010
|
+
const word = match?.value;
|
|
1011
|
+
if (word === void 0) continue;
|
|
1012
|
+
if (match?.kind === "comment") return line.slice(0, match.index);
|
|
1013
|
+
if (match?.kind === "boundary") {
|
|
1014
|
+
commandStart = index + 1;
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
1017
|
+
const declaration = match?.kind === "redirect" ? /^(?:\d*)<<(-?)(.*)$/.exec(word) : null;
|
|
1018
|
+
let delimiter = declaration?.[2];
|
|
1019
|
+
if (delimiter === "") delimiter = words.at(++index)?.value;
|
|
1020
|
+
if (declaration !== null && delimiter !== void 0 && !delimiter.startsWith("<")) {
|
|
1021
|
+
const commandEnd = words.findIndex((entry, position) => position > index && (entry.kind === "boundary" || entry.kind === "comment"));
|
|
1022
|
+
const invocation = words.slice(commandStart, commandEnd < 0 ? void 0 : commandEnd);
|
|
1023
|
+
pending.push({
|
|
1024
|
+
delimiter: decodeShellWord(delimiter),
|
|
1025
|
+
stripTabs: declaration[1] === "-",
|
|
1026
|
+
literal: /['"\\]/.test(delimiter),
|
|
1027
|
+
execute: shellReadsProgramStdin(invocation) && shellStdinHeredoc(invocation) === match
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return line;
|
|
1032
|
+
}).join("\n");
|
|
1033
|
+
};
|
|
1034
|
+
const executableShellWords = (words) => {
|
|
1035
|
+
let start = 0;
|
|
1036
|
+
const skipAssignments = () => {
|
|
1037
|
+
while (/^[A-Za-z_][A-Za-z\d_]*=/.test(decodeShellWord(words.at(start) ?? ""))) start += 1;
|
|
1038
|
+
};
|
|
1039
|
+
skipAssignments();
|
|
1040
|
+
while (/^(?:\/usr\/bin\/|\/bin\/)?env$/.test(decodeShellWord(words.at(start) ?? ""))) {
|
|
1041
|
+
start += 1;
|
|
1042
|
+
while (start < words.length) {
|
|
1043
|
+
const option = decodeShellWord(words.at(start) ?? "");
|
|
1044
|
+
if (option === "--") {
|
|
1045
|
+
start += 1;
|
|
1046
|
+
break;
|
|
1047
|
+
}
|
|
1048
|
+
if ([
|
|
1049
|
+
"-u",
|
|
1050
|
+
"--unset",
|
|
1051
|
+
"-C",
|
|
1052
|
+
"--chdir"
|
|
1053
|
+
].includes(option)) {
|
|
1054
|
+
start += 2;
|
|
1055
|
+
continue;
|
|
1056
|
+
}
|
|
1057
|
+
if (option === "-S" || option === "--split-string" || option.startsWith("--split-string=") || option.startsWith("-S") && option.length > 2) {
|
|
1058
|
+
let source;
|
|
1059
|
+
if (option === "-S" || option === "--split-string") {
|
|
1060
|
+
source = decodeShellWord(words.at(start + 1) ?? "");
|
|
778
1061
|
start += 2;
|
|
779
|
-
|
|
1062
|
+
} else {
|
|
1063
|
+
source = option.startsWith("-S") ? option.slice(2) : option.slice(15);
|
|
1064
|
+
start++;
|
|
780
1065
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
1066
|
+
return executableShellWords([
|
|
1067
|
+
"env",
|
|
1068
|
+
...shellTokens(source).map(({ value }) => value),
|
|
1069
|
+
...words.slice(start)
|
|
1070
|
+
]);
|
|
786
1071
|
}
|
|
787
|
-
|
|
1072
|
+
if (option === "-" || option === "-i" || option === "--ignore-environment" || /^--(?:unset|chdir)=.+|^-[uC].+/.test(option)) {
|
|
1073
|
+
start += 1;
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
break;
|
|
788
1077
|
}
|
|
789
|
-
|
|
790
|
-
}
|
|
1078
|
+
skipAssignments();
|
|
1079
|
+
}
|
|
1080
|
+
return words.slice(start);
|
|
1081
|
+
};
|
|
1082
|
+
const shellCommandBody = (rawWords) => {
|
|
1083
|
+
const words = executableShellWords(rawWords.filter((word) => word !== "(" && word !== ")")).map(decodeShellWord);
|
|
1084
|
+
if (!shellExecutable.test(words.at(0) ?? "")) return void 0;
|
|
1085
|
+
for (let index = 1; index < words.length; index++) {
|
|
1086
|
+
const option = words.at(index);
|
|
1087
|
+
if (option === void 0 || option === "--" || !/^[+-]/.test(option)) return void 0;
|
|
1088
|
+
if (shellValueOptions.has(option)) {
|
|
1089
|
+
index++;
|
|
1090
|
+
continue;
|
|
1091
|
+
}
|
|
1092
|
+
if (/^-[^-]*c/.test(option)) return words.at(index + 1);
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
/** Shared executable shell words preserve assignments for runtime selectors. */
|
|
1096
|
+
const shellCommandSegments = (command, grouping = "boundaries") => {
|
|
1097
|
+
const segments = [[]];
|
|
1098
|
+
const executableSource = shellExecutableSource(command);
|
|
1099
|
+
const sources = /* @__PURE__ */ new Set([executableSource, ...shellSubstitutions(executableSource)]);
|
|
1100
|
+
for (const source of sources) {
|
|
1101
|
+
const firstSegment = segments.length;
|
|
1102
|
+
segments.push([]);
|
|
1103
|
+
for (const { kind, value: word } of shellArgumentTokens(shellTokens(source))) if (kind === "boundary") if ([
|
|
1104
|
+
"(",
|
|
1105
|
+
")",
|
|
1106
|
+
"{",
|
|
1107
|
+
"}"
|
|
1108
|
+
].includes(word) && grouping === "tokens") segments.at(-1)?.push(word);
|
|
1109
|
+
else segments.push([]);
|
|
1110
|
+
else segments.at(-1)?.push(word);
|
|
1111
|
+
for (const words of segments.slice(firstSegment)) {
|
|
1112
|
+
const body = shellCommandBody(words);
|
|
1113
|
+
if (body === void 0) continue;
|
|
1114
|
+
const executableBody = shellExecutableSource(body);
|
|
1115
|
+
sources.add(executableBody);
|
|
1116
|
+
for (const substitution of shellSubstitutions(executableBody)) sources.add(substitution);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
return segments;
|
|
791
1120
|
};
|
|
1121
|
+
const compilerCommandSegments = (command) => shellCommandSegments(command, "tokens").map(executableShellWords);
|
|
792
1122
|
const launcherValueOptions = new Map(Object.entries({
|
|
793
1123
|
npx: [
|
|
794
1124
|
"-p",
|
|
@@ -922,10 +1252,10 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
922
1252
|
const document = pnpm ? (0, yaml.parseDocument)(text, { lineCounter }) : void 0;
|
|
923
1253
|
if (document !== void 0 && document.errors.length > 0) throw new Error("invalid YAML");
|
|
924
1254
|
const json = document === void 0 ? JSON.parse(text) : document.toJS();
|
|
925
|
-
if (!record$
|
|
1255
|
+
if (!record$3(json)) throw new Error("manifest must be an object");
|
|
926
1256
|
if (pnpm) {
|
|
927
|
-
if (json["catalog"] !== void 0 && !record$
|
|
928
|
-
if (json["catalogs"] !== void 0 && (!record$
|
|
1257
|
+
if (json["catalog"] !== void 0 && !record$3(json["catalog"])) throw new Error("catalog must be a mapping");
|
|
1258
|
+
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");
|
|
929
1259
|
if (json["packages"] !== void 0 && (!Array.isArray(json["packages"]) || !json["packages"].every((pattern) => typeof pattern === "string"))) throw new Error("workspace packages must be patterns");
|
|
930
1260
|
}
|
|
931
1261
|
manifests.set(file, json);
|
|
@@ -939,13 +1269,13 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
939
1269
|
catalog: json["overrides"],
|
|
940
1270
|
prefix: ["overrides"]
|
|
941
1271
|
},
|
|
942
|
-
...record$
|
|
1272
|
+
...record$3(json["catalogs"]) ? Object.entries(json["catalogs"]).map(([name, catalog]) => ({
|
|
943
1273
|
catalog,
|
|
944
1274
|
prefix: ["catalogs", name]
|
|
945
1275
|
})) : []
|
|
946
1276
|
];
|
|
947
1277
|
for (const { catalog, prefix } of catalogEntries) {
|
|
948
|
-
if (!record$
|
|
1278
|
+
if (!record$3(catalog)) continue;
|
|
949
1279
|
for (const [name, value] of Object.entries(catalog)) {
|
|
950
1280
|
const node = document.getIn([...prefix, name], true);
|
|
951
1281
|
if (!(0, yaml.isNode)(node) || node.range === void 0 || node.range === null) continue;
|
|
@@ -964,7 +1294,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
964
1294
|
});
|
|
965
1295
|
}
|
|
966
1296
|
}
|
|
967
|
-
const catalogSources = (json) => [json, ...record$
|
|
1297
|
+
const catalogSources = (json) => [json, ...record$3(json["workspaces"]) ? [json["workspaces"]] : []];
|
|
968
1298
|
const pnpmWorkspaceFor = (file) => {
|
|
969
1299
|
let directory = node_path.default.posix.dirname(file);
|
|
970
1300
|
while (true) {
|
|
@@ -987,8 +1317,8 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
987
1317
|
patterns: workspace["packages"],
|
|
988
1318
|
rootMembership: "implicit"
|
|
989
1319
|
})) return void 0;
|
|
990
|
-
if (name === "" || name === "default") return workspace["catalog"] ?? (record$
|
|
991
|
-
return record$
|
|
1320
|
+
if (name === "" || name === "default") return workspace["catalog"] ?? (record$3(workspace["catalogs"]) ? workspace["catalogs"]["default"] : void 0);
|
|
1321
|
+
return record$3(workspace["catalogs"]) ? workspace["catalogs"][name] : void 0;
|
|
992
1322
|
}
|
|
993
1323
|
let directory = node_path.default.posix.dirname(file);
|
|
994
1324
|
while (true) {
|
|
@@ -996,7 +1326,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
996
1326
|
if (manifest !== void 0) {
|
|
997
1327
|
for (const source of catalogSources(manifest)) {
|
|
998
1328
|
if (name === "" && source["catalog"] !== void 0) return source["catalog"];
|
|
999
|
-
if (record$
|
|
1329
|
+
if (record$3(source["catalogs"]) && source["catalogs"][name] !== void 0) return source["catalogs"][name];
|
|
1000
1330
|
}
|
|
1001
1331
|
if (manifest["workspaces"] !== void 0) return void 0;
|
|
1002
1332
|
}
|
|
@@ -1010,7 +1340,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1010
1340
|
if (visited.has(value)) return void 0;
|
|
1011
1341
|
visited.add(value);
|
|
1012
1342
|
const catalog = catalogFor(file, value.slice(8));
|
|
1013
|
-
value = record$
|
|
1343
|
+
value = record$3(catalog) ? catalog[name] : void 0;
|
|
1014
1344
|
}
|
|
1015
1345
|
return value;
|
|
1016
1346
|
};
|
|
@@ -1026,7 +1356,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1026
1356
|
const workspaces = manifests.get(node_path.default.posix.join(directory, "package.json"))?.["workspaces"];
|
|
1027
1357
|
if (workspaces !== void 0) return {
|
|
1028
1358
|
directory,
|
|
1029
|
-
patterns: record$
|
|
1359
|
+
patterns: record$3(workspaces) ? workspaces["packages"] : workspaces,
|
|
1030
1360
|
source: "json"
|
|
1031
1361
|
};
|
|
1032
1362
|
if (directory === ".") return void 0;
|
|
@@ -1082,7 +1412,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1082
1412
|
"peerDependencies"
|
|
1083
1413
|
]) {
|
|
1084
1414
|
const dependencies = json[section];
|
|
1085
|
-
if (!record$
|
|
1415
|
+
if (!record$3(dependencies)) continue;
|
|
1086
1416
|
for (const [name, value] of Object.entries(dependencies)) {
|
|
1087
1417
|
if (!tsSpecifiers.has(name)) continue;
|
|
1088
1418
|
const resolved = resolve(file, name, value);
|
|
@@ -1144,7 +1474,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1144
1474
|
message: `packageManager Bun version must be ${policy.bun}, found ${JSON.stringify(manager)}`
|
|
1145
1475
|
});
|
|
1146
1476
|
const checkEntries = (entries, location) => {
|
|
1147
|
-
if (!record$
|
|
1477
|
+
if (!record$3(entries)) return;
|
|
1148
1478
|
for (const [name, raw] of Object.entries(entries)) {
|
|
1149
1479
|
const value = resolve(file, name, raw);
|
|
1150
1480
|
const tsAllowed = tsSpecifiers.get(name);
|
|
@@ -1181,12 +1511,12 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1181
1511
|
"devDependencies",
|
|
1182
1512
|
"optionalDependencies"
|
|
1183
1513
|
]) checkEntries(json[key]);
|
|
1184
|
-
if (record$
|
|
1514
|
+
if (record$3(json["peerDependencies"])) {
|
|
1185
1515
|
for (const [name, value] of Object.entries(json["peerDependencies"])) if (tsSpecifiers.has(name) && !peerRange(resolve(file, name, value))) checkEntries({ [name]: value });
|
|
1186
1516
|
}
|
|
1187
1517
|
for (const source of catalogSources(json)) {
|
|
1188
1518
|
checkEntries(source["catalog"]);
|
|
1189
|
-
if (!record$
|
|
1519
|
+
if (!record$3(source["catalogs"])) continue;
|
|
1190
1520
|
for (const catalog of Object.values(source["catalogs"])) checkEntries(catalog);
|
|
1191
1521
|
}
|
|
1192
1522
|
const ownedNames = /* @__PURE__ */ new Set([...pins.keys(), ...tsSpecifiers.keys()]);
|
|
@@ -1198,7 +1528,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1198
1528
|
"optionalDependencies"
|
|
1199
1529
|
]) {
|
|
1200
1530
|
const entries = resolutionRoot[section];
|
|
1201
|
-
if (!record$
|
|
1531
|
+
if (!record$3(entries)) continue;
|
|
1202
1532
|
for (const [name, value] of Object.entries(entries)) resolutionDependencies[name] = resolve(file, name, value);
|
|
1203
1533
|
}
|
|
1204
1534
|
const affectedSpecifiers = /* @__PURE__ */ new Map();
|
|
@@ -1218,7 +1548,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1218
1548
|
"optionalDependencies"
|
|
1219
1549
|
]) {
|
|
1220
1550
|
const entries = member[section];
|
|
1221
|
-
if (!record$
|
|
1551
|
+
if (!record$3(entries)) continue;
|
|
1222
1552
|
for (const [name, value] of Object.entries(entries)) {
|
|
1223
1553
|
if (!tsSpecifiers.has(name)) continue;
|
|
1224
1554
|
const values = affectedSpecifiers.get(name) ?? /* @__PURE__ */ new Set();
|
|
@@ -1228,7 +1558,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1228
1558
|
}
|
|
1229
1559
|
}
|
|
1230
1560
|
const checkResolutionMap = (entries, parent) => {
|
|
1231
|
-
if (!record$
|
|
1561
|
+
if (!record$3(entries)) return;
|
|
1232
1562
|
for (const [selector, replacement] of Object.entries(entries)) {
|
|
1233
1563
|
const target = selector === "." ? parent : selector;
|
|
1234
1564
|
if (target === void 0) continue;
|
|
@@ -1251,7 +1581,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1251
1581
|
});
|
|
1252
1582
|
continue;
|
|
1253
1583
|
}
|
|
1254
|
-
if (record$
|
|
1584
|
+
if (record$3(replacement)) {
|
|
1255
1585
|
checkResolutionMap(replacement, target);
|
|
1256
1586
|
continue;
|
|
1257
1587
|
}
|
|
@@ -1285,7 +1615,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1285
1615
|
};
|
|
1286
1616
|
checkResolutionMap(json["overrides"]);
|
|
1287
1617
|
checkResolutionMap(json["resolutions"]);
|
|
1288
|
-
if (record$
|
|
1618
|
+
if (record$3(json["pnpm"])) checkResolutionMap(json["pnpm"]["overrides"]);
|
|
1289
1619
|
const localEntries = toolchainEntries(file, json);
|
|
1290
1620
|
const usesCompiler = policy.typescriptInstallLayouts.some((layout) => localEntries.some(({ name }) => name === layout.compilerPackage));
|
|
1291
1621
|
let selectedLayout = layoutFor(specifierMap(localEntries));
|
|
@@ -1309,7 +1639,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1309
1639
|
key: "dependencies",
|
|
1310
1640
|
message: "TypeScript compiler and compatibility packages must match one complete declared install layout"
|
|
1311
1641
|
});
|
|
1312
|
-
const command = record$
|
|
1642
|
+
const command = record$3(json["scripts"]) ? json["scripts"]["typecheck"] : void 0;
|
|
1313
1643
|
if (selectedLayout !== void 0 && typeof command === "string") {
|
|
1314
1644
|
const normalizedExpected = selectedLayout.typecheckCommand.replace(/\s+/g, " ").trim();
|
|
1315
1645
|
for (const words of compilerCommandSegments(command)) {
|
|
@@ -1327,7 +1657,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1327
1657
|
}
|
|
1328
1658
|
}
|
|
1329
1659
|
}
|
|
1330
|
-
if (record$
|
|
1660
|
+
if (record$3(json["peerDependencies"])) {
|
|
1331
1661
|
const peerLayout = selectedLayout ?? workspaceLayoutFor(file)?.layout;
|
|
1332
1662
|
for (const [name, raw] of Object.entries(json["peerDependencies"])) {
|
|
1333
1663
|
if (!tsSpecifiers.has(name)) continue;
|
|
@@ -1351,7 +1681,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1351
1681
|
});
|
|
1352
1682
|
}
|
|
1353
1683
|
}
|
|
1354
|
-
if (record$
|
|
1684
|
+
if (record$3(json["engines"]) && json["engines"]["node"] !== void 0 && (typeof json["engines"]["node"] !== "string" || !require_toolchain_inputs.nodeSupportRangeMatches(policy.node, json["engines"]["node"]))) add({
|
|
1355
1685
|
file,
|
|
1356
1686
|
rule: "node-engine",
|
|
1357
1687
|
key: "node",
|
|
@@ -1362,6 +1692,232 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1362
1692
|
return diagnostics;
|
|
1363
1693
|
};
|
|
1364
1694
|
//#endregion
|
|
1695
|
+
//#region src/toolchain-rust.ts
|
|
1696
|
+
const record$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1697
|
+
/** Exceptions authorize one immutable release, never a channel or an entire rule. */
|
|
1698
|
+
const parseRustExceptions = (input, today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)) => {
|
|
1699
|
+
if (input === void 0) return [];
|
|
1700
|
+
if (!Array.isArray(input)) throw new Error("rustExceptions must be an array");
|
|
1701
|
+
const pins = /* @__PURE__ */ new Set();
|
|
1702
|
+
return input.map((entry) => {
|
|
1703
|
+
if (!record$2(entry) || Object.keys(entry).length !== 4 || Object.keys(entry).some((key) => ![
|
|
1704
|
+
"pin",
|
|
1705
|
+
"reason",
|
|
1706
|
+
"tracking",
|
|
1707
|
+
"expires"
|
|
1708
|
+
].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");
|
|
1709
|
+
pins.add(entry["pin"]);
|
|
1710
|
+
return {
|
|
1711
|
+
pin: entry["pin"],
|
|
1712
|
+
reason: entry["reason"],
|
|
1713
|
+
tracking: entry["tracking"],
|
|
1714
|
+
expires: entry["expires"]
|
|
1715
|
+
};
|
|
1716
|
+
});
|
|
1717
|
+
};
|
|
1718
|
+
/** Only executable fields are inspected: unrelated action inputs are not Rust selectors. */
|
|
1719
|
+
const checkRustReferences = (options) => {
|
|
1720
|
+
const { file, rust, exceptions } = options;
|
|
1721
|
+
const text = options.type === "file" ? options.text : "";
|
|
1722
|
+
const diagnostics = [];
|
|
1723
|
+
const select = (value, label, line = 1) => {
|
|
1724
|
+
if (value === rust || exceptions.some(({ pin }) => pin === value)) return;
|
|
1725
|
+
diagnostics.push({
|
|
1726
|
+
rule: "rust-version",
|
|
1727
|
+
path: file,
|
|
1728
|
+
line,
|
|
1729
|
+
message: `${label} must be Rust ${rust}, found ${String(value)}`
|
|
1730
|
+
});
|
|
1731
|
+
};
|
|
1732
|
+
const shell = (source, firstLine = 1) => {
|
|
1733
|
+
const commands = shellCommandSegments(source).map((words) => words.map((word) => ({
|
|
1734
|
+
value: decodeShellWord(word),
|
|
1735
|
+
line: firstLine
|
|
1736
|
+
})));
|
|
1737
|
+
for (const command of commands) {
|
|
1738
|
+
let position = 0;
|
|
1739
|
+
const prefixes = /* @__PURE__ */ new Set([
|
|
1740
|
+
"if",
|
|
1741
|
+
"then",
|
|
1742
|
+
"do",
|
|
1743
|
+
"elif",
|
|
1744
|
+
"!",
|
|
1745
|
+
"env",
|
|
1746
|
+
"sudo",
|
|
1747
|
+
"command",
|
|
1748
|
+
"exec",
|
|
1749
|
+
"time",
|
|
1750
|
+
"export",
|
|
1751
|
+
"readonly",
|
|
1752
|
+
"/usr/bin/env",
|
|
1753
|
+
"/bin/env"
|
|
1754
|
+
]);
|
|
1755
|
+
while (position < command.length) {
|
|
1756
|
+
const value = command.at(position)?.value;
|
|
1757
|
+
if (value?.startsWith("RUSTUP_TOOLCHAIN=")) select(value.slice(17), "RUSTUP_TOOLCHAIN", firstLine);
|
|
1758
|
+
if (value === "-u" || value === "--unset") {
|
|
1759
|
+
position += 2;
|
|
1760
|
+
continue;
|
|
1761
|
+
}
|
|
1762
|
+
if (value?.startsWith("--unset=") || value === "-i" || value === "--ignore-environment") {
|
|
1763
|
+
position++;
|
|
1764
|
+
continue;
|
|
1765
|
+
}
|
|
1766
|
+
if (value !== void 0 && (prefixes.has(value) || /^[A-Za-z_][A-Za-z_\d]*=/.test(value))) position++;
|
|
1767
|
+
else break;
|
|
1768
|
+
}
|
|
1769
|
+
const executable = command.at(position)?.value;
|
|
1770
|
+
const arguments_ = command.slice(position + 1);
|
|
1771
|
+
if (executable === "cargo" && arguments_.at(0)?.value.startsWith("+")) {
|
|
1772
|
+
const selector = arguments_.at(0);
|
|
1773
|
+
select(selector?.value.slice(1), "cargo toolchain", selector?.line);
|
|
1774
|
+
}
|
|
1775
|
+
if (executable !== "rustup") continue;
|
|
1776
|
+
let subcommandPosition = 0;
|
|
1777
|
+
while (subcommandPosition < arguments_.length) {
|
|
1778
|
+
const token = arguments_.at(subcommandPosition);
|
|
1779
|
+
if (token === void 0) break;
|
|
1780
|
+
if (token.value === "--") {
|
|
1781
|
+
subcommandPosition++;
|
|
1782
|
+
break;
|
|
1783
|
+
}
|
|
1784
|
+
if (token.value.startsWith("+")) select(token.value.slice(1), "rustup toolchain", token.line);
|
|
1785
|
+
else if (!token.value.startsWith("-")) break;
|
|
1786
|
+
subcommandPosition++;
|
|
1787
|
+
}
|
|
1788
|
+
const invocation = arguments_.slice(subcommandPosition);
|
|
1789
|
+
const subcommand = invocation.at(0)?.value;
|
|
1790
|
+
if (subcommand === "target" || subcommand === "component") {
|
|
1791
|
+
for (let index = 1; index < invocation.length; index++) {
|
|
1792
|
+
const token = invocation.at(index);
|
|
1793
|
+
if (token?.value === "--toolchain") select(invocation.at(++index)?.value, "rustup --toolchain", token.line);
|
|
1794
|
+
else if (token?.value.startsWith("--toolchain=")) select(token.value.slice(12), "rustup --toolchain", token.line);
|
|
1795
|
+
}
|
|
1796
|
+
continue;
|
|
1797
|
+
}
|
|
1798
|
+
let selectors;
|
|
1799
|
+
let requiresSelector = true;
|
|
1800
|
+
let firstSelectorOnly = false;
|
|
1801
|
+
if (subcommand === "default" || subcommand === "install" || subcommand === "add" || subcommand === "update") {
|
|
1802
|
+
selectors = invocation.slice(1);
|
|
1803
|
+
requiresSelector = subcommand !== "update";
|
|
1804
|
+
} else if (subcommand === "toolchain" && [
|
|
1805
|
+
"install",
|
|
1806
|
+
"add",
|
|
1807
|
+
"update"
|
|
1808
|
+
].includes(invocation.at(1)?.value ?? "")) {
|
|
1809
|
+
selectors = invocation.slice(2);
|
|
1810
|
+
requiresSelector = invocation.at(1)?.value !== "update";
|
|
1811
|
+
} else if (subcommand === "run") {
|
|
1812
|
+
selectors = invocation.slice(1);
|
|
1813
|
+
firstSelectorOnly = true;
|
|
1814
|
+
} else if (subcommand === "override" && invocation.at(1)?.value === "set") {
|
|
1815
|
+
selectors = invocation.slice(2);
|
|
1816
|
+
firstSelectorOnly = true;
|
|
1817
|
+
} else continue;
|
|
1818
|
+
let selected = 0;
|
|
1819
|
+
const optionArguments = /* @__PURE__ */ new Set([
|
|
1820
|
+
"--path",
|
|
1821
|
+
"--profile",
|
|
1822
|
+
"--component",
|
|
1823
|
+
"-c",
|
|
1824
|
+
"--target",
|
|
1825
|
+
"-t"
|
|
1826
|
+
]);
|
|
1827
|
+
for (let index = 0; index < selectors.length; index++) {
|
|
1828
|
+
const token = selectors.at(index);
|
|
1829
|
+
if (token === void 0) continue;
|
|
1830
|
+
if (optionArguments.has(token.value)) {
|
|
1831
|
+
index++;
|
|
1832
|
+
continue;
|
|
1833
|
+
}
|
|
1834
|
+
if (token.value.startsWith("-")) continue;
|
|
1835
|
+
selected++;
|
|
1836
|
+
select(token.value, "rustup toolchain", token.line);
|
|
1837
|
+
if (firstSelectorOnly) break;
|
|
1838
|
+
}
|
|
1839
|
+
if (selected === 0 && requiresSelector) select(void 0, "rustup toolchain", firstLine);
|
|
1840
|
+
}
|
|
1841
|
+
};
|
|
1842
|
+
if (options.type === "environment") {
|
|
1843
|
+
for (const { key, value } of options.assignments) if (key === "RUSTUP_TOOLCHAIN") select(value, key, options.line);
|
|
1844
|
+
return diagnostics;
|
|
1845
|
+
}
|
|
1846
|
+
if (options.type === "command") {
|
|
1847
|
+
shell(options.source, options.line);
|
|
1848
|
+
return diagnostics;
|
|
1849
|
+
}
|
|
1850
|
+
const lineOf = (token) => {
|
|
1851
|
+
const index = text.split(/\r?\n/).findIndex((line) => line.includes(token));
|
|
1852
|
+
return index < 0 ? 1 : index + 1;
|
|
1853
|
+
};
|
|
1854
|
+
if (require_toolchain_inputs.isShellScript(file, text)) shell(text);
|
|
1855
|
+
if (/(?:^|\/)package\.json$/.test(file)) try {
|
|
1856
|
+
const object = JSON.parse(text);
|
|
1857
|
+
if (record$2(object) && record$2(object["scripts"])) {
|
|
1858
|
+
for (const [name, script] of Object.entries(object["scripts"])) if (typeof script === "string") shell(script, lineOf(`"${name}"`));
|
|
1859
|
+
}
|
|
1860
|
+
} catch {}
|
|
1861
|
+
if (/(?:^|\/)ci-service-images\.json$/.test(file)) {
|
|
1862
|
+
const images = (value) => {
|
|
1863
|
+
if (typeof value === "string" && /^rust(?=:|@|$)/.test(canonicalDockerRuntime(value))) {
|
|
1864
|
+
const tag = canonicalDockerRuntime(value).replace(/^rust:?/, "").split("@").at(0);
|
|
1865
|
+
const version = tag === void 0 ? void 0 : rustImageVersion(tag);
|
|
1866
|
+
select(version, "Rust service image", lineOf(value));
|
|
1867
|
+
} else if (Array.isArray(value)) value.forEach(images);
|
|
1868
|
+
else if (record$2(value)) Object.values(value).forEach(images);
|
|
1869
|
+
};
|
|
1870
|
+
try {
|
|
1871
|
+
images(JSON.parse(text));
|
|
1872
|
+
} catch {
|
|
1873
|
+
diagnostics.push({
|
|
1874
|
+
rule: "rust-version",
|
|
1875
|
+
path: file,
|
|
1876
|
+
line: 1,
|
|
1877
|
+
message: "invalid service image JSON"
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
if (require_toolchain_inputs.githubAutomationFileKind(file) === void 0) return diagnostics;
|
|
1882
|
+
const document = (0, yaml.parseDocument)(text);
|
|
1883
|
+
if (document.errors.length > 0) return diagnostics;
|
|
1884
|
+
try {
|
|
1885
|
+
const visit = (value) => {
|
|
1886
|
+
if (Array.isArray(value)) {
|
|
1887
|
+
value.forEach(visit);
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
if (!record$2(value)) return;
|
|
1891
|
+
if (record$2(value["env"]) && value["env"]["RUSTUP_TOOLCHAIN"] !== void 0) select(value["env"]["RUSTUP_TOOLCHAIN"], "RUSTUP_TOOLCHAIN", lineOf("RUSTUP_TOOLCHAIN"));
|
|
1892
|
+
if (typeof value["run"] === "string") shell(value["run"], lineOf("run:"));
|
|
1893
|
+
const uses = value["uses"];
|
|
1894
|
+
if (typeof uses === "string") {
|
|
1895
|
+
const action = uses.split("@").at(0)?.toLowerCase();
|
|
1896
|
+
const inputs = record$2(value["with"]) ? value["with"] : {};
|
|
1897
|
+
if (action === "dtolnay/rust-toolchain" || action === "actions-rust-lang/setup-rust-toolchain") {
|
|
1898
|
+
const entries = Object.entries(inputs).filter(([key]) => key.toLowerCase() === "toolchain");
|
|
1899
|
+
if (entries.length !== 1) select(void 0, `${action} requires an explicit exact toolchain input`, lineOf(uses));
|
|
1900
|
+
else {
|
|
1901
|
+
const selected = entries.at(0)?.at(1);
|
|
1902
|
+
const selectors = action === "actions-rust-lang/setup-rust-toolchain" && typeof selected === "string" ? selected.split(",").map((pin) => pin.trim()) : [selected];
|
|
1903
|
+
for (const selector of selectors) select(selector, `${action} toolchain`, lineOf("toolchain:"));
|
|
1904
|
+
}
|
|
1905
|
+
} else if (/(?:^|\/)\.github\/workflows\/[^@]+\.ya?ml(?:@|$)/.test(uses)) {
|
|
1906
|
+
for (const [key, selected] of Object.entries(inputs)) if (["toolchain", "rust-toolchain"].includes(key.toLowerCase())) select(selected, `reusable workflow ${key}`, lineOf(`${key}:`));
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1910
|
+
if (key === "workflow_call" && record$2(child) && record$2(child["inputs"])) {
|
|
1911
|
+
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:"));
|
|
1912
|
+
}
|
|
1913
|
+
visit(child);
|
|
1914
|
+
}
|
|
1915
|
+
};
|
|
1916
|
+
visit(document.toJS({ maxAliasCount: 100 }));
|
|
1917
|
+
} catch {}
|
|
1918
|
+
return diagnostics;
|
|
1919
|
+
};
|
|
1920
|
+
//#endregion
|
|
1365
1921
|
//#region src/toolchain-runtime.ts
|
|
1366
1922
|
const runtimeRules = [
|
|
1367
1923
|
"bun-pins",
|
|
@@ -1374,6 +1930,69 @@ const runtimeRules = [
|
|
|
1374
1930
|
"action-pins"
|
|
1375
1931
|
];
|
|
1376
1932
|
const record$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1933
|
+
const dockerEnvironmentAssignments = ({ source, escape }) => {
|
|
1934
|
+
const words = [];
|
|
1935
|
+
let word = "";
|
|
1936
|
+
let quote;
|
|
1937
|
+
for (let index = 0; index < source.length; index++) {
|
|
1938
|
+
const character = source.at(index) ?? "";
|
|
1939
|
+
if (character === escape && quote !== "'") {
|
|
1940
|
+
word += character + (source.at(++index) ?? "");
|
|
1941
|
+
continue;
|
|
1942
|
+
}
|
|
1943
|
+
if (character === quote) quote = void 0;
|
|
1944
|
+
else if (quote === void 0 && (character === "'" || character === "\"")) quote = character;
|
|
1945
|
+
else if (quote === void 0 && /\s/.test(character)) {
|
|
1946
|
+
if (word !== "") words.push(word);
|
|
1947
|
+
word = "";
|
|
1948
|
+
continue;
|
|
1949
|
+
}
|
|
1950
|
+
word += character;
|
|
1951
|
+
}
|
|
1952
|
+
if (quote !== void 0) return void 0;
|
|
1953
|
+
if (word !== "") words.push(word);
|
|
1954
|
+
const decode = (raw) => {
|
|
1955
|
+
let value = "";
|
|
1956
|
+
let quoted;
|
|
1957
|
+
for (let index = 0; index < raw.length; index++) {
|
|
1958
|
+
const character = raw.at(index) ?? "";
|
|
1959
|
+
if (character === quoted) {
|
|
1960
|
+
quoted = void 0;
|
|
1961
|
+
continue;
|
|
1962
|
+
}
|
|
1963
|
+
if (quoted === void 0 && (character === "'" || character === "\"")) {
|
|
1964
|
+
quoted = character;
|
|
1965
|
+
continue;
|
|
1966
|
+
}
|
|
1967
|
+
const next = raw.at(index + 1);
|
|
1968
|
+
if (character === escape && quoted !== "'" && next !== void 0 && (quoted === void 0 || next === "\"" || next === "$" || next === escape)) {
|
|
1969
|
+
value += next;
|
|
1970
|
+
index++;
|
|
1971
|
+
} else value += character;
|
|
1972
|
+
}
|
|
1973
|
+
return value;
|
|
1974
|
+
};
|
|
1975
|
+
const first = words.at(0);
|
|
1976
|
+
if (first === void 0) return void 0;
|
|
1977
|
+
if (!first.includes("=")) {
|
|
1978
|
+
const legacy = /^(\S+)\s+([\s\S]*)$/.exec(source);
|
|
1979
|
+
if (legacy === null) return void 0;
|
|
1980
|
+
return [{
|
|
1981
|
+
key: decode(legacy[1] ?? ""),
|
|
1982
|
+
value: decode(legacy[2] ?? "")
|
|
1983
|
+
}];
|
|
1984
|
+
}
|
|
1985
|
+
const assignments = [];
|
|
1986
|
+
for (const raw of words) {
|
|
1987
|
+
const separator = raw.indexOf("=");
|
|
1988
|
+
if (separator < 1) return void 0;
|
|
1989
|
+
assignments.push({
|
|
1990
|
+
key: decode(raw.slice(0, separator)),
|
|
1991
|
+
value: decode(raw.slice(separator + 1))
|
|
1992
|
+
});
|
|
1993
|
+
}
|
|
1994
|
+
return assignments;
|
|
1995
|
+
};
|
|
1377
1996
|
const pythonSelectorMatches = (value, expected) => value === expected || typeof value === "string" && /^\d+\.\d+$/.test(expected) && value.startsWith(`${expected}.`) && /^\d+\.\d+\.\d+$/.test(value);
|
|
1378
1997
|
const compareRelease = (left, right) => {
|
|
1379
1998
|
for (let index = 0; index < Math.max(left.length, right.length); index++) {
|
|
@@ -1443,8 +2062,14 @@ const pythonRequirementMatches = (requirement, version) => {
|
|
|
1443
2062
|
});
|
|
1444
2063
|
};
|
|
1445
2064
|
/** Check runtime declarations only in the files that declare or configure them. */
|
|
1446
|
-
const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, repository, onDelegated, dynamicSelectors = [], onDynamicSelector, engineFloors = [], onEngineFloor }) => {
|
|
1447
|
-
const diagnostics =
|
|
2065
|
+
const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, repository, onDelegated, dynamicSelectors = [], onDynamicSelector, rustExceptions = [], engineFloors = [], onEngineFloor }) => {
|
|
2066
|
+
const diagnostics = checkRustReferences({
|
|
2067
|
+
type: "file",
|
|
2068
|
+
file,
|
|
2069
|
+
text,
|
|
2070
|
+
rust: policy.rust,
|
|
2071
|
+
exceptions: rustExceptions
|
|
2072
|
+
});
|
|
1448
2073
|
const name = node_path.default.posix.basename(file);
|
|
1449
2074
|
const lines = text.split(/\r?\n/);
|
|
1450
2075
|
const add = ({ rule, line, message }) => {
|
|
@@ -1467,7 +2092,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1467
2092
|
};
|
|
1468
2093
|
const lineOf = (key) => Math.max(1, lines.findIndex((line) => line.includes(key)) + 1);
|
|
1469
2094
|
const pin = ({ rule, value, expected, label, line = 1, selector }) => {
|
|
1470
|
-
let matches = value === expected;
|
|
2095
|
+
let matches = value === expected || rule === "rust-version" && rustExceptions.some((entry) => entry.pin === value);
|
|
1471
2096
|
if (selector === "node") matches = require_toolchain_inputs.nodeSelectorMatches(value, expected);
|
|
1472
2097
|
else if (label.includes("python")) matches = pythonSelectorMatches(value, expected);
|
|
1473
2098
|
if (!matches) add({
|
|
@@ -1537,12 +2162,10 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1537
2162
|
const parsed = toml("rust-version");
|
|
1538
2163
|
if (parsed !== void 0) {
|
|
1539
2164
|
const toolchain = parsed["toolchain"];
|
|
1540
|
-
const components = record$1(toolchain) ? toolchain["components"] : void 0;
|
|
1541
|
-
const expected = Array.isArray(components) && components.includes("rustc-dev") ? policy.rustCompilerDevelopment : policy.rust;
|
|
1542
2165
|
pin({
|
|
1543
2166
|
rule: "rust-version",
|
|
1544
2167
|
value: record$1(toolchain) ? toolchain["channel"] : void 0,
|
|
1545
|
-
expected,
|
|
2168
|
+
expected: policy.rust,
|
|
1546
2169
|
label: "Rust channel",
|
|
1547
2170
|
line: lineOf("channel")
|
|
1548
2171
|
});
|
|
@@ -1643,7 +2266,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1643
2266
|
if (record$1(tools)) for (const [tool, expected] of Object.entries(versions)) {
|
|
1644
2267
|
const configured = tools[tool];
|
|
1645
2268
|
if (configured !== void 0) pin({
|
|
1646
|
-
rule: "runtime-manager",
|
|
2269
|
+
rule: tool === "rust" || tool === "core:rust" ? "rust-version" : "runtime-manager",
|
|
1647
2270
|
value: record$1(configured) ? configured["version"] : configured,
|
|
1648
2271
|
expected,
|
|
1649
2272
|
selector: nodeTools.has(tool) ? "node" : void 0,
|
|
@@ -1658,7 +2281,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1658
2281
|
if (tool === void 0 || !(tool in versions)) return;
|
|
1659
2282
|
const expected = Object.entries(versions).find(([key]) => key === tool)?.[1];
|
|
1660
2283
|
if (expected !== void 0) pin({
|
|
1661
|
-
rule: "runtime-manager",
|
|
2284
|
+
rule: tool === "rust" || tool === "core:rust" ? "rust-version" : "runtime-manager",
|
|
1662
2285
|
value: fields.slice(1).join(" "),
|
|
1663
2286
|
expected,
|
|
1664
2287
|
selector: nodeTools.has(tool) ? "node" : void 0,
|
|
@@ -1668,11 +2291,12 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1668
2291
|
});
|
|
1669
2292
|
const checkRuntimeImage = ({ image, line, label, at }) => {
|
|
1670
2293
|
const unknownRuntime = () => {
|
|
1671
|
-
|
|
2294
|
+
const rustImage = typeof image === "string" && /^rust(?=:|@|$)/i.test(canonicalDockerRuntime(image));
|
|
2295
|
+
if (!rustImage && dynamic(image) && authorizeDynamic("image", line, at)) return;
|
|
1672
2296
|
add({
|
|
1673
|
-
rule: "runtime-docker",
|
|
2297
|
+
rule: rustImage ? "rust-version" : "runtime-docker",
|
|
1674
2298
|
line,
|
|
1675
|
-
message: `cannot determine ${label} runtime; use a static image or a scoped dynamicSelectors declaration`
|
|
2299
|
+
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`
|
|
1676
2300
|
});
|
|
1677
2301
|
};
|
|
1678
2302
|
if (typeof image !== "string") {
|
|
@@ -1684,7 +2308,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1684
2308
|
const digestSeparator = structuralImage.indexOf("@");
|
|
1685
2309
|
const reference = digestSeparator < 0 ? canonical : canonical.slice(0, digestSeparator);
|
|
1686
2310
|
const structuralReference = digestSeparator < 0 ? structuralImage : structuralImage.slice(0, digestSeparator);
|
|
1687
|
-
const managedFamily = /^(node|python|oven\/bun)(?=:|$)/i.exec(structuralReference)?.[1];
|
|
2311
|
+
const managedFamily = /^(node|python|rust|oven\/bun)(?=:|$)/i.exec(structuralReference)?.[1];
|
|
1688
2312
|
const tagSeparator = managedFamily === void 0 ? structuralReference.lastIndexOf(":") : structuralReference.indexOf(":", managedFamily.length);
|
|
1689
2313
|
const hasTag = managedFamily === void 0 ? tagSeparator > structuralReference.lastIndexOf("/") : tagSeparator !== -1;
|
|
1690
2314
|
const imageRepository = hasTag ? reference.slice(0, tagSeparator) : reference;
|
|
@@ -1692,8 +2316,11 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1692
2316
|
unknownRuntime();
|
|
1693
2317
|
return;
|
|
1694
2318
|
}
|
|
1695
|
-
if (!/^(node|python|oven\/bun)$/i.test(imageRepository)) return;
|
|
2319
|
+
if (!/^(node|python|rust|oven\/bun)$/i.test(imageRepository)) return;
|
|
1696
2320
|
const tool = imageRepository.toLowerCase();
|
|
2321
|
+
let imageRule = "runtime-docker";
|
|
2322
|
+
if (tool === "rust") imageRule = "rust-version";
|
|
2323
|
+
else if (tool === "oven/bun") imageRule = "bun-pins";
|
|
1697
2324
|
if (image.includes("$")) {
|
|
1698
2325
|
unknownRuntime();
|
|
1699
2326
|
return;
|
|
@@ -1701,7 +2328,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1701
2328
|
const digest = digestSeparator < 0 ? void 0 : canonical.slice(digestSeparator + 1);
|
|
1702
2329
|
if (digest !== void 0 && !/^sha256:[a-fA-F\d]{64}$/.test(digest)) {
|
|
1703
2330
|
add({
|
|
1704
|
-
rule:
|
|
2331
|
+
rule: imageRule,
|
|
1705
2332
|
line,
|
|
1706
2333
|
message: `${label} ${tool} digest must be sha256 followed by 64 hexadecimal characters`
|
|
1707
2334
|
});
|
|
@@ -1709,14 +2336,15 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1709
2336
|
}
|
|
1710
2337
|
const tag = hasTag ? reference.slice(tagSeparator + 1) : void 0;
|
|
1711
2338
|
const variantSuffix = tool === "oven/bun" ? /-(?:alpine|slim|debian)$/ : /-(?:alpine(?:\d+(?:\.\d+)*)?|(?:bookworm|bullseye|trixie|buster)(?:-slim)?|slim)$/;
|
|
1712
|
-
const version = tag?.replace(variantSuffix, "");
|
|
2339
|
+
const version = tool === "rust" && tag !== void 0 ? rustImageVersion(tag) : tag?.replace(variantSuffix, "");
|
|
1713
2340
|
let expected = policy.bun;
|
|
1714
2341
|
if (tool === "node") expected = policy.node;
|
|
1715
2342
|
else if (tool === "python") expected = policy.python;
|
|
2343
|
+
else if (tool === "rust") expected = policy.rust;
|
|
1716
2344
|
if (digest !== void 0) {
|
|
1717
2345
|
if (version === void 0 || !/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/.test(version)) {
|
|
1718
2346
|
add({
|
|
1719
|
-
rule:
|
|
2347
|
+
rule: imageRule,
|
|
1720
2348
|
line,
|
|
1721
2349
|
message: `${label} ${tool} digest requires an exact stable patch tag matching ${expected}`
|
|
1722
2350
|
});
|
|
@@ -1734,7 +2362,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1734
2362
|
}
|
|
1735
2363
|
}
|
|
1736
2364
|
pin({
|
|
1737
|
-
rule:
|
|
2365
|
+
rule: imageRule,
|
|
1738
2366
|
value: version,
|
|
1739
2367
|
expected,
|
|
1740
2368
|
selector: tool === "node" ? "node" : void 0,
|
|
@@ -1751,6 +2379,17 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1751
2379
|
const heredocs = [];
|
|
1752
2380
|
let instruction = "";
|
|
1753
2381
|
let startLine = 1;
|
|
2382
|
+
let pendingRustRun;
|
|
2383
|
+
const checkRustCommand = (source, line) => {
|
|
2384
|
+
diagnostics.push(...checkRustReferences({
|
|
2385
|
+
type: "command",
|
|
2386
|
+
file,
|
|
2387
|
+
source,
|
|
2388
|
+
line,
|
|
2389
|
+
rust: policy.rust,
|
|
2390
|
+
exceptions: rustExceptions
|
|
2391
|
+
}));
|
|
2392
|
+
};
|
|
1754
2393
|
const expand = (value) => {
|
|
1755
2394
|
for (let count = 0; count <= variables.size; count++) {
|
|
1756
2395
|
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);
|
|
@@ -1837,7 +2476,49 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1837
2476
|
if (key !== void 0 && value !== void 0) variables.set(key, expand(value.replace(/^(["'])(.*)\1$/, "$2")));
|
|
1838
2477
|
return;
|
|
1839
2478
|
}
|
|
1840
|
-
|
|
2479
|
+
const run = /^\s*RUN\s+(.+)$/i.exec(content);
|
|
2480
|
+
if (run !== null) {
|
|
2481
|
+
let source = run[1] ?? "";
|
|
2482
|
+
source = source.replace(/^(?:--\S+\s+)*/, "");
|
|
2483
|
+
if (source.startsWith("[")) try {
|
|
2484
|
+
const words = JSON.parse(source);
|
|
2485
|
+
if (Array.isArray(words) && words.every((word) => typeof word === "string")) source = words.map((word) => `'${word.replaceAll("'", "'\\''")}'`).join(" ");
|
|
2486
|
+
} catch {}
|
|
2487
|
+
const implicit = /^(<<-?(?:'[^']+'|"[^"]+"|\S+))(?:\s+(.+))?$/.exec(source);
|
|
2488
|
+
if (implicit !== null && implicit[2] === void 0) source = `sh ${source}`;
|
|
2489
|
+
collectHeredocs(content, line);
|
|
2490
|
+
if (heredocs.length > 0) pendingRustRun = {
|
|
2491
|
+
source,
|
|
2492
|
+
line
|
|
2493
|
+
};
|
|
2494
|
+
else checkRustCommand(source, line);
|
|
2495
|
+
return;
|
|
2496
|
+
}
|
|
2497
|
+
const environment = /^\s*ENV\s+(.+)$/i.exec(content);
|
|
2498
|
+
if (environment !== null) {
|
|
2499
|
+
const assignments = dockerEnvironmentAssignments({
|
|
2500
|
+
source: environment[1] ?? "",
|
|
2501
|
+
escape
|
|
2502
|
+
});
|
|
2503
|
+
if (assignments === void 0) {
|
|
2504
|
+
add({
|
|
2505
|
+
rule: "runtime-docker",
|
|
2506
|
+
line,
|
|
2507
|
+
message: "invalid Docker ENV assignments"
|
|
2508
|
+
});
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2511
|
+
diagnostics.push(...checkRustReferences({
|
|
2512
|
+
type: "environment",
|
|
2513
|
+
file,
|
|
2514
|
+
assignments,
|
|
2515
|
+
line,
|
|
2516
|
+
rust: policy.rust,
|
|
2517
|
+
exceptions: rustExceptions
|
|
2518
|
+
}));
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2521
|
+
if (/^\s*COPY\s+/i.test(content)) {
|
|
1841
2522
|
collectHeredocs(content, line);
|
|
1842
2523
|
return;
|
|
1843
2524
|
}
|
|
@@ -1858,7 +2539,12 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1858
2539
|
lines.forEach((line, index) => {
|
|
1859
2540
|
const heredoc = heredocs.at(0);
|
|
1860
2541
|
if (heredoc !== void 0) {
|
|
2542
|
+
if (pendingRustRun !== void 0) pendingRustRun.source += `\n${line}`;
|
|
1861
2543
|
if ((heredoc.stripTabs ? line.replace(/^\t+/, "") : line) === heredoc.delimiter) heredocs.shift();
|
|
2544
|
+
if (heredocs.length === 0 && pendingRustRun !== void 0) {
|
|
2545
|
+
checkRustCommand(pendingRustRun.source, pendingRustRun.line);
|
|
2546
|
+
pendingRustRun = void 0;
|
|
2547
|
+
}
|
|
1862
2548
|
return;
|
|
1863
2549
|
}
|
|
1864
2550
|
if (line.trimStart().startsWith("#")) return;
|
|
@@ -2533,18 +3219,20 @@ const record = (value) => typeof value === "object" && value !== null && !Array.
|
|
|
2533
3219
|
const array = (value) => Array.isArray(value);
|
|
2534
3220
|
/** Repository-owned declarations are explicit; malformed decisions fail closed. */
|
|
2535
3221
|
const parseToolchainConfiguration = (input) => {
|
|
2536
|
-
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
|
|
3222
|
+
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");
|
|
2537
3223
|
const disabled = /* @__PURE__ */ new Set();
|
|
2538
3224
|
const optOuts = input["optOuts"] ?? [];
|
|
2539
3225
|
if (!array(optOuts)) throw new Error("optOuts must be an array");
|
|
2540
3226
|
for (const entry of optOuts) {
|
|
2541
3227
|
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");
|
|
3228
|
+
if (entry["rule"] === "rust-version") throw new Error("rust-version requires expiring exact-pin rustExceptions, not an opt-out");
|
|
2542
3229
|
if (entry["rule"] === "node-engine") throw new Error("node-engine cannot be opted out; engines.node must support the shared Node series");
|
|
2543
3230
|
if (entry["rule"] === "cloud-setup-drift") throw new Error("cloud-setup-drift cannot be opted out; generated setup must match shared policy");
|
|
2544
3231
|
disabled.add(entry["rule"]);
|
|
2545
3232
|
}
|
|
2546
3233
|
return {
|
|
2547
3234
|
disabled,
|
|
3235
|
+
rustExceptions: parseRustExceptions(input["rustExceptions"]),
|
|
2548
3236
|
cloud: require_toolchain_inputs.parseCloudSetup(input["cloud"]),
|
|
2549
3237
|
engineFloors: parseEngineFloors(input["engineFloors"]),
|
|
2550
3238
|
dynamicSelectors: parseDynamicSelectors(input["dynamicSelectors"]),
|
|
@@ -2557,27 +3245,47 @@ const readToolchainInputs = (root) => {
|
|
|
2557
3245
|
const tracked = (0, node_child_process.execFileSync)("git", [
|
|
2558
3246
|
"ls-files",
|
|
2559
3247
|
"-z",
|
|
2560
|
-
"--cached"
|
|
3248
|
+
"--cached",
|
|
3249
|
+
"--stage"
|
|
2561
3250
|
], {
|
|
2562
3251
|
cwd: root,
|
|
2563
3252
|
encoding: "utf8"
|
|
2564
|
-
}).split("\0").filter((
|
|
3253
|
+
}).split("\0").filter((entry) => entry !== "" && !entry.startsWith("160000 ")).map((entry) => entry.slice(entry.indexOf(" ") + 1));
|
|
2565
3254
|
const files = {};
|
|
2566
3255
|
const trackedFiles = new Set(tracked);
|
|
2567
3256
|
const resolvedRoot = (0, node_fs.realpathSync)(root);
|
|
2568
3257
|
for (const file of tracked) {
|
|
2569
3258
|
const kind = require_toolchain_inputs.toolchainInputKind(file);
|
|
2570
|
-
|
|
3259
|
+
const shebangCandidate = kind === void 0 && node_path.default.posix.extname(file) === "" && !file.split("/").some((part) => part === "vendor" || part === "node_modules");
|
|
3260
|
+
if (kind === void 0 && !shebangCandidate) continue;
|
|
2571
3261
|
if (kind === "presence") {
|
|
2572
3262
|
files[file] = "";
|
|
2573
3263
|
continue;
|
|
2574
3264
|
}
|
|
2575
3265
|
try {
|
|
2576
|
-
const
|
|
3266
|
+
const inputPath = node_path.default.join(resolvedRoot, file);
|
|
3267
|
+
if (shebangCandidate) {
|
|
3268
|
+
const input = (0, node_fs.lstatSync)(inputPath);
|
|
3269
|
+
if (!input.isFile() && !input.isSymbolicLink()) continue;
|
|
3270
|
+
}
|
|
3271
|
+
const resolvedFile = (0, node_fs.realpathSync)(inputPath);
|
|
2577
3272
|
const relative = node_path.default.relative(resolvedRoot, resolvedFile);
|
|
2578
3273
|
if (node_path.default.isAbsolute(relative) || relative.split(node_path.default.sep).at(0) === ".." || !trackedFiles.has(relative.split(node_path.default.sep).join("/"))) throw new Error("Configuration target is outside the tracked tree");
|
|
3274
|
+
if (shebangCandidate) {
|
|
3275
|
+
if (!(0, node_fs.lstatSync)(resolvedFile).isFile()) continue;
|
|
3276
|
+
const descriptor = (0, node_fs.openSync)(resolvedFile, "r");
|
|
3277
|
+
const prefix = Buffer.alloc(512);
|
|
3278
|
+
let count;
|
|
3279
|
+
try {
|
|
3280
|
+
count = (0, node_fs.readSync)(descriptor, prefix, 0, prefix.length, 0);
|
|
3281
|
+
} finally {
|
|
3282
|
+
(0, node_fs.closeSync)(descriptor);
|
|
3283
|
+
}
|
|
3284
|
+
if (require_toolchain_inputs.toolchainInputKind(file, prefix.toString("utf8", 0, count)) === void 0) continue;
|
|
3285
|
+
}
|
|
2579
3286
|
files[file] = (0, node_fs.readFileSync)(resolvedFile, "utf8");
|
|
2580
3287
|
} catch {
|
|
3288
|
+
if (shebangCandidate) continue;
|
|
2581
3289
|
diagnostics.push({
|
|
2582
3290
|
rule: "configuration",
|
|
2583
3291
|
path: file,
|
|
@@ -2678,6 +3386,7 @@ const checkToolchain = ({ root, policy, onDelegated }) => {
|
|
|
2678
3386
|
}
|
|
2679
3387
|
},
|
|
2680
3388
|
trackedFiles,
|
|
3389
|
+
rustExceptions: configuration.rustExceptions,
|
|
2681
3390
|
dynamicSelectors: dynamicSelectors.filter((entry) => entry.path === file),
|
|
2682
3391
|
onDynamicSelector: (entry) => selectorMatches.set(entry, (selectorMatches.get(entry) ?? 0) + 1),
|
|
2683
3392
|
repository,
|