@stll/oxlint-config 0.15.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -1
- package/dist/cloud-setup-cli.mjs +1 -1
- package/dist/toolchain-changed.cjs +5 -22
- package/dist/toolchain-changed.cjs.map +1 -1
- package/dist/toolchain-changed.mjs +6 -23
- package/dist/toolchain-changed.mjs.map +1 -1
- package/dist/toolchain-guard.cjs +837 -121
- package/dist/toolchain-guard.cjs.map +1 -1
- package/dist/toolchain-guard.mjs +839 -123
- package/dist/toolchain-guard.mjs.map +1 -1
- package/dist/toolchain-inputs.cjs +10 -2
- package/dist/toolchain-inputs.cjs.map +1 -1
- package/dist/toolchain-inputs.mjs +5 -3
- package/dist/toolchain-inputs.mjs.map +1 -1
- package/dist/toolchain-schema.cjs +0 -3
- package/dist/toolchain-schema.cjs.map +1 -1
- package/dist/toolchain-schema.mjs +1 -4
- package/dist/toolchain-schema.mjs.map +1 -1
- package/dist/typecheck-parity.cjs +1 -1
- package/dist/typecheck-parity.cjs.map +1 -1
- package/dist/typecheck-parity.mjs +1 -1
- package/dist/typecheck-parity.mjs.map +1 -1
- package/package.json +1 -1
- package/toolchain.json +1 -2
package/dist/toolchain-guard.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.1";
|
|
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,16 +259,24 @@ 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;
|
|
269
271
|
const sortedExpected = [...expected].sort();
|
|
270
272
|
return [...value].sort().every((entry, index) => entry === sortedExpected[index]);
|
|
271
273
|
};
|
|
274
|
+
const pinnedVersionUpdateTypes = [
|
|
275
|
+
"version-update:semver-major",
|
|
276
|
+
"version-update:semver-minor",
|
|
277
|
+
"version-update:semver-patch"
|
|
278
|
+
];
|
|
279
|
+
const versionOnlyIgnore = (entry) => record$6(entry) && Object.keys(entry).length === 2 && typeof entry["dependency-name"] === "string" && sameStrings(entry["update-types"], pinnedVersionUpdateTypes);
|
|
272
280
|
const directoryOf = (file) => {
|
|
273
281
|
const directory = node_path.default.posix.dirname(file);
|
|
274
282
|
return directory === "." ? "/" : `/${directory}`;
|
|
@@ -284,8 +292,8 @@ const workspaceTable = (parsed, ecosystem) => {
|
|
|
284
292
|
case "cargo": return parsed["workspace"];
|
|
285
293
|
case "uv": {
|
|
286
294
|
const tool = parsed["tool"];
|
|
287
|
-
const uv = record$
|
|
288
|
-
return record$
|
|
295
|
+
const uv = record$6(tool) ? tool["uv"] : void 0;
|
|
296
|
+
return record$6(uv) ? uv["workspace"] : void 0;
|
|
289
297
|
}
|
|
290
298
|
default: throw new Error("unknown workspace ecosystem", { cause: ecosystem });
|
|
291
299
|
}
|
|
@@ -305,7 +313,7 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
305
313
|
const document = (0, yaml.parseDocument)(text);
|
|
306
314
|
if (document.errors.length > 0) throw new Error(`${workspaceFile}:1: invalid pnpm workspace YAML`);
|
|
307
315
|
const parsed = document.toJS({ maxAliasCount: 100 });
|
|
308
|
-
if (!record$
|
|
316
|
+
if (!record$6(parsed)) throw new Error(`${workspaceFile}:1: pnpm workspace must contain an object`);
|
|
309
317
|
const patterns = parsed["packages"] ?? [];
|
|
310
318
|
if (!strings(patterns)) throw new Error(`${workspaceFile}:1: pnpm packages must be a string array`);
|
|
311
319
|
workspaces.set(directory, patterns.map((pattern) => pattern.startsWith("!") ? `!${node_path.default.posix.normalize(pattern.slice(1))}` : node_path.default.posix.normalize(pattern)));
|
|
@@ -317,13 +325,13 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
317
325
|
}
|
|
318
326
|
try {
|
|
319
327
|
const parsed = ecosystem === "npm" ? JSON.parse(files[file] ?? "") : (0, smol_toml.parse)(files[file] ?? "");
|
|
320
|
-
if (!record$
|
|
328
|
+
if (!record$6(parsed)) continue;
|
|
321
329
|
if (ecosystem === "npm" && typeof parsed["packageManager"] === "string" && parsed["packageManager"].startsWith("pnpm@")) continue;
|
|
322
330
|
const value = workspaceTable(parsed, ecosystem);
|
|
323
|
-
const patterns = record$
|
|
324
|
-
const members = ecosystem === "cargo" && record$
|
|
331
|
+
const patterns = record$6(value) ? value[ecosystem === "npm" ? "packages" : "members"] : value;
|
|
332
|
+
const members = ecosystem === "cargo" && record$6(value) && patterns === void 0 ? [] : patterns;
|
|
325
333
|
if (strings(members)) {
|
|
326
|
-
const excluded = ecosystem !== "npm" && record$
|
|
334
|
+
const excluded = ecosystem !== "npm" && record$6(value) && strings(value["exclude"]) ? value["exclude"].map((pattern) => `!${pattern}`) : [];
|
|
327
335
|
workspaces.set(node_path.default.posix.dirname(file), [...members, ...excluded]);
|
|
328
336
|
}
|
|
329
337
|
} catch {}
|
|
@@ -374,11 +382,11 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
374
382
|
for (const directory of memberDirectories) try {
|
|
375
383
|
const parsed = (0, smol_toml.parse)(files[node_path.default.posix.join(directory, "Cargo.toml")] ?? "");
|
|
376
384
|
const workspace = (0, smol_toml.parse)(files[node_path.default.posix.join(root, "Cargo.toml")] ?? "")["workspace"];
|
|
377
|
-
const inherited = record$
|
|
385
|
+
const inherited = record$6(workspace) ? workspace["dependencies"] : void 0;
|
|
378
386
|
const dependencyOwners = [parsed];
|
|
379
387
|
const targets = parsed["target"];
|
|
380
|
-
if (record$
|
|
381
|
-
for (const target of Object.values(targets)) if (record$
|
|
388
|
+
if (record$6(targets)) {
|
|
389
|
+
for (const target of Object.values(targets)) if (record$6(target)) dependencyOwners.push(target);
|
|
382
390
|
}
|
|
383
391
|
for (const owner of dependencyOwners) for (const key of [
|
|
384
392
|
"dependencies",
|
|
@@ -386,12 +394,12 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
386
394
|
"build-dependencies"
|
|
387
395
|
]) {
|
|
388
396
|
const dependencies = owner[key];
|
|
389
|
-
if (!record$
|
|
397
|
+
if (!record$6(dependencies)) continue;
|
|
390
398
|
for (const [name, dependency] of Object.entries(dependencies)) {
|
|
391
|
-
if (!record$
|
|
399
|
+
if (!record$6(dependency)) continue;
|
|
392
400
|
const isInherited = dependency["workspace"] === true;
|
|
393
|
-
const resolved = isInherited && record$
|
|
394
|
-
if (!record$
|
|
401
|
+
const resolved = isInherited && record$6(inherited) ? inherited[name] : dependency;
|
|
402
|
+
if (!record$6(resolved) || typeof resolved["path"] !== "string") continue;
|
|
395
403
|
if (node_path.default.posix.isAbsolute(resolved["path"]) || /\\|\$|^[A-Za-z]:/.test(resolved["path"])) continue;
|
|
396
404
|
const target = node_path.default.posix.normalize(node_path.default.posix.join(isInherited ? root : directory, resolved["path"]));
|
|
397
405
|
const relative = node_path.default.posix.relative(root, target);
|
|
@@ -419,7 +427,7 @@ const javascriptEcosystem = (directory, files) => {
|
|
|
419
427
|
if (files[node_path.default.posix.join(root, "bun.lock")] !== void 0) return "bun";
|
|
420
428
|
try {
|
|
421
429
|
const manifest = JSON.parse(files[node_path.default.posix.join(root, "package.json")] ?? "{}");
|
|
422
|
-
if (record$
|
|
430
|
+
if (record$6(manifest) && typeof manifest["packageManager"] === "string" && manifest["packageManager"].startsWith("bun@")) return "bun";
|
|
423
431
|
} catch {}
|
|
424
432
|
return "npm";
|
|
425
433
|
};
|
|
@@ -431,7 +439,7 @@ const pythonEcosystem = (file, files) => {
|
|
|
431
439
|
const pyproject = files[node_path.default.posix.join(root, "pyproject.toml")];
|
|
432
440
|
if (pyproject !== void 0) try {
|
|
433
441
|
const tool = (0, smol_toml.parse)(pyproject)["tool"];
|
|
434
|
-
if (record$
|
|
442
|
+
if (record$6(tool) && record$6(tool["uv"])) return "uv";
|
|
435
443
|
} catch {}
|
|
436
444
|
return "pip";
|
|
437
445
|
};
|
|
@@ -502,7 +510,7 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
502
510
|
add("cannot resolve Dependabot YAML aliases");
|
|
503
511
|
return diagnostics;
|
|
504
512
|
}
|
|
505
|
-
if (!record$
|
|
513
|
+
if (!record$6(parsed) || parsed["version"] !== 2 || !Array.isArray(parsed["updates"])) {
|
|
506
514
|
add("Dependabot must declare version: 2 and an updates array");
|
|
507
515
|
return diagnostics;
|
|
508
516
|
}
|
|
@@ -515,7 +523,7 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
515
523
|
});
|
|
516
524
|
if (legacyDocument.errors.length === 0) legacy = legacyDocument.toJS({ maxAliasCount: 100 });
|
|
517
525
|
} catch {}
|
|
518
|
-
const legacyUpdates = record$
|
|
526
|
+
const legacyUpdates = record$6(legacy) && Array.isArray(legacy["updates"]) ? legacy["updates"] : [];
|
|
519
527
|
const rootClaims = /* @__PURE__ */ new Map();
|
|
520
528
|
const directoryClaims = /* @__PURE__ */ new Map();
|
|
521
529
|
for (const [index, update] of parsed["updates"].entries()) {
|
|
@@ -529,7 +537,7 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
529
537
|
return offset === void 0 ? 1 : lineCounter.linePos(offset).line;
|
|
530
538
|
};
|
|
531
539
|
const report = (key, message) => add(message, lineOf(key));
|
|
532
|
-
if (!record$
|
|
540
|
+
if (!record$6(update) || typeof update["package-ecosystem"] !== "string") {
|
|
533
541
|
report("package-ecosystem", "each Dependabot update must declare a package ecosystem");
|
|
534
542
|
continue;
|
|
535
543
|
}
|
|
@@ -563,21 +571,22 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
563
571
|
covered.set(ecosystem, matches);
|
|
564
572
|
const schedule = update["schedule"];
|
|
565
573
|
const legacyUpdate = legacyUpdates.at(index);
|
|
566
|
-
const legacySchedule = record$
|
|
567
|
-
if (record$
|
|
568
|
-
if (!record$
|
|
574
|
+
const legacySchedule = record$6(legacyUpdate) ? legacyUpdate["schedule"] : void 0;
|
|
575
|
+
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`);
|
|
576
|
+
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
577
|
const cooldown = update["cooldown"];
|
|
570
|
-
if (!record$
|
|
578
|
+
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
579
|
const groups = update["groups"];
|
|
572
|
-
if (!record$
|
|
580
|
+
if (!record$6(groups) || Object.keys(groups).length !== Object.keys(policy.groups).length || Object.entries(policy.groups).some(([name, expected]) => {
|
|
573
581
|
const group = groups[name];
|
|
574
|
-
return !record$
|
|
582
|
+
return !record$6(group) || Object.keys(group).length !== 2 || !sameStrings(group["patterns"], expected.patterns) || !sameStrings(group["update-types"], expected.updateTypes);
|
|
575
583
|
})) report("groups", `${ecosystem}: groups must match the shared policy`);
|
|
576
584
|
const ignored = ignoredFor(ecosystem, policy);
|
|
577
585
|
const ignore = update["ignore"];
|
|
578
586
|
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
|
-
|
|
587
|
+
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`);
|
|
588
|
+
if (Array.isArray(ignore) && ignore.some((entry) => !versionOnlyIgnore(entry))) report("ignore", `${ecosystem}: each shared-pin ignore must contain only dependency-name and update-types for semver-major, semver-minor, and semver-patch version updates; security updates must remain enabled`);
|
|
589
|
+
for (const name of ignored) if (!Array.isArray(ignore) || !ignore.some((entry) => record$6(entry) && entry["dependency-name"] === name && versionOnlyIgnore(entry))) report("ignore", `${ecosystem}: ignore all version updates for shared pin ${name}`);
|
|
581
590
|
}
|
|
582
591
|
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
592
|
return diagnostics;
|
|
@@ -589,7 +598,7 @@ const locatorValidators = {
|
|
|
589
598
|
"bun-source": /^(?:jobs\.[\w-]+|runs)\.steps\.[\w-]+$/
|
|
590
599
|
};
|
|
591
600
|
const knownKind = (value) => typeof value === "string" && Object.hasOwn(locatorValidators, value);
|
|
592
|
-
const record$
|
|
601
|
+
const record$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
593
602
|
/** One reviewed declaration identifies one unresolved selector, never a rule. */
|
|
594
603
|
const parseDynamicSelectors = (input) => {
|
|
595
604
|
if (input === void 0) return [];
|
|
@@ -597,7 +606,7 @@ const parseDynamicSelectors = (input) => {
|
|
|
597
606
|
const result = [];
|
|
598
607
|
const keys = /* @__PURE__ */ new Set();
|
|
599
608
|
for (const entry of input) {
|
|
600
|
-
if (!record$
|
|
609
|
+
if (!record$5(entry) || Object.keys(entry).some((key) => ![
|
|
601
610
|
"path",
|
|
602
611
|
"kind",
|
|
603
612
|
"reason",
|
|
@@ -633,7 +642,7 @@ const parseDynamicSelectors = (input) => {
|
|
|
633
642
|
};
|
|
634
643
|
//#endregion
|
|
635
644
|
//#region src/toolchain-engine-floors.ts
|
|
636
|
-
const record$
|
|
645
|
+
const record$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
637
646
|
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
647
|
const parseEngineFloors = (input) => {
|
|
639
648
|
if (input === void 0) return [];
|
|
@@ -641,7 +650,7 @@ const parseEngineFloors = (input) => {
|
|
|
641
650
|
const result = [];
|
|
642
651
|
const jobs = /* @__PURE__ */ new Set();
|
|
643
652
|
for (const entry of input) {
|
|
644
|
-
if (!record$
|
|
653
|
+
if (!record$4(entry) || Object.keys(entry).some((key) => ![
|
|
645
654
|
"package",
|
|
646
655
|
"workflow",
|
|
647
656
|
"job"
|
|
@@ -662,16 +671,16 @@ const resolveEngineFloor = (entry, files) => {
|
|
|
662
671
|
const workflowText = files[entry.workflow];
|
|
663
672
|
if (manifestText === void 0 || workflowText === void 0) throw new Error("engine floor package and workflow must be tracked");
|
|
664
673
|
const manifest = JSON.parse(manifestText);
|
|
665
|
-
if (!record$
|
|
674
|
+
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
675
|
const range = manifest["engines"]["node"];
|
|
667
676
|
const minimum = (0, semver.validRange)(range) === null ? null : (0, semver.minVersion)(range);
|
|
668
677
|
if (minimum === null || minimum.prerelease.length !== 0) throw new Error("engine floor engines.node must declare a stable minimum");
|
|
669
678
|
const document = (0, yaml.parseDocument)(workflowText, { merge: true });
|
|
670
679
|
if (document.errors.length !== 0) throw new Error("engine floor workflow must be valid YAML");
|
|
671
680
|
const workflow = document.toJS({ maxAliasCount: 100 });
|
|
672
|
-
if (!record$
|
|
681
|
+
if (!record$4(workflow) || !record$4(workflow["jobs"])) throw new Error("engine floor job must exist in its tracked workflow");
|
|
673
682
|
const job = workflow["jobs"][entry.job];
|
|
674
|
-
if (!record$
|
|
683
|
+
if (!record$4(job)) throw new Error("engine floor job must exist in its tracked workflow");
|
|
675
684
|
if (!Array.isArray(job["steps"]) || Object.hasOwn(job, "uses")) throw new Error("engine floor job must declare steps and cannot reuse a workflow");
|
|
676
685
|
return {
|
|
677
686
|
...entry,
|
|
@@ -691,7 +700,7 @@ const packageRules = [
|
|
|
691
700
|
"typescript-layout",
|
|
692
701
|
"node-engine"
|
|
693
702
|
];
|
|
694
|
-
const record$
|
|
703
|
+
const record$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
695
704
|
const shellSubstitutions = (command) => {
|
|
696
705
|
const frames = [{
|
|
697
706
|
kind: "root",
|
|
@@ -752,43 +761,371 @@ const shellSubstitutions = (command) => {
|
|
|
752
761
|
for (const frame of frames.slice(1)) bodies.push(command.slice(frame.start));
|
|
753
762
|
return bodies;
|
|
754
763
|
};
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
764
|
+
const shellValueOptions = /* @__PURE__ */ new Set([
|
|
765
|
+
"--rcfile",
|
|
766
|
+
"--init-file",
|
|
767
|
+
"-o",
|
|
768
|
+
"+o",
|
|
769
|
+
"-O",
|
|
770
|
+
"+O"
|
|
771
|
+
]);
|
|
772
|
+
const shellExecutable = /^(?:\/(?:[^/]+\/)*)?(?:bash|dash|ksh|zsh|sh)$/;
|
|
773
|
+
const shellTokens = (source) => {
|
|
774
|
+
const tokens = [];
|
|
775
|
+
for (let index = 0; index < source.length;) {
|
|
776
|
+
const character = source.at(index);
|
|
777
|
+
if (character === void 0) break;
|
|
778
|
+
if (character === "\n") {
|
|
779
|
+
tokens.push({
|
|
780
|
+
kind: "boundary",
|
|
781
|
+
value: character,
|
|
782
|
+
index: index++
|
|
783
|
+
});
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
786
|
+
if (/\s/.test(character)) {
|
|
787
|
+
index++;
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
if (character === "#") {
|
|
791
|
+
const end = source.indexOf("\n", index);
|
|
792
|
+
const stop = end < 0 ? source.length : end;
|
|
793
|
+
tokens.push({
|
|
794
|
+
kind: "comment",
|
|
795
|
+
value: source.slice(index, stop),
|
|
796
|
+
index
|
|
797
|
+
});
|
|
798
|
+
index = stop;
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
const redirect = /^(?:\d*)(?:&>>|&>|<<<|<<-|<<|>>|>\||>&|<&|<>|>|<)/.exec(source.slice(index));
|
|
802
|
+
if (redirect !== null) {
|
|
803
|
+
tokens.push({
|
|
804
|
+
kind: "redirect",
|
|
805
|
+
value: redirect[0],
|
|
806
|
+
index
|
|
807
|
+
});
|
|
808
|
+
index += redirect[0].length;
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
const boundary = /^(?:&&|\|\||[();&|])/.exec(source.slice(index));
|
|
812
|
+
if (boundary !== null) {
|
|
813
|
+
tokens.push({
|
|
814
|
+
kind: "boundary",
|
|
815
|
+
value: boundary[0],
|
|
816
|
+
index
|
|
817
|
+
});
|
|
818
|
+
index += boundary[0].length;
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
const start = index;
|
|
822
|
+
const frames = [{
|
|
823
|
+
kind: "word",
|
|
824
|
+
quote: void 0,
|
|
825
|
+
depth: 0
|
|
826
|
+
}];
|
|
827
|
+
while (index < source.length) {
|
|
828
|
+
const frame = frames.at(-1);
|
|
829
|
+
const current = source.at(index);
|
|
830
|
+
if (frame === void 0 || current === void 0) break;
|
|
831
|
+
if (current === "\\" && frame.quote !== "'") {
|
|
832
|
+
index += 2;
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
if (current === "`" && frame.kind === "backticks") {
|
|
836
|
+
frames.pop();
|
|
837
|
+
index++;
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
if (current === frame.quote) {
|
|
841
|
+
frame.quote = void 0;
|
|
842
|
+
index++;
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
if (frame.quote === "'") {
|
|
846
|
+
index++;
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
if (current === "$" && source.at(index + 1) === "(") {
|
|
850
|
+
frames.push({
|
|
851
|
+
kind: "parentheses",
|
|
852
|
+
quote: void 0,
|
|
853
|
+
depth: 1
|
|
854
|
+
});
|
|
855
|
+
index += 2;
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
if (current === "`") {
|
|
859
|
+
frames.push({
|
|
860
|
+
kind: "backticks",
|
|
861
|
+
quote: void 0,
|
|
862
|
+
depth: 0
|
|
863
|
+
});
|
|
864
|
+
index++;
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
if (frame.quote !== void 0) {
|
|
868
|
+
index++;
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
if (current === "'" || current === "\"") {
|
|
872
|
+
frame.quote = current;
|
|
873
|
+
index++;
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
if (frame.kind === "parentheses") {
|
|
877
|
+
if (current === "(") frame.depth++;
|
|
878
|
+
if (current === ")" && --frame.depth === 0) frames.pop();
|
|
879
|
+
index++;
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
if (frame.kind === "word" && /[\s<>;&|()]/.test(current)) break;
|
|
883
|
+
index++;
|
|
884
|
+
}
|
|
885
|
+
const value = source.slice(start, index);
|
|
886
|
+
tokens.push({
|
|
887
|
+
kind: "word",
|
|
888
|
+
value,
|
|
889
|
+
index: start
|
|
890
|
+
});
|
|
762
891
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
892
|
+
let groupDepth = 0;
|
|
893
|
+
let redirectTarget = false;
|
|
894
|
+
const commandWords = [];
|
|
895
|
+
for (const token of tokens) {
|
|
896
|
+
if (token.kind === "comment") continue;
|
|
897
|
+
if (token.kind === "redirect") {
|
|
898
|
+
redirectTarget = true;
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
901
|
+
if (redirectTarget) {
|
|
902
|
+
redirectTarget = false;
|
|
903
|
+
if (token.kind === "word") continue;
|
|
904
|
+
}
|
|
905
|
+
if (token.kind === "boundary") {
|
|
906
|
+
commandWords.length = 0;
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
const functionBody = commandWords.length === 2 && commandWords.at(0) === "function";
|
|
910
|
+
const opensGroup = token.value === "{" && (commandWords.length === 0 || functionBody);
|
|
911
|
+
const closesGroup = token.value === "}" && groupDepth > 0 && commandWords.length === 0;
|
|
912
|
+
if (opensGroup || closesGroup) {
|
|
913
|
+
token.kind = "boundary";
|
|
914
|
+
groupDepth += opensGroup ? 1 : -1;
|
|
915
|
+
commandWords.length = 0;
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
if (commandWords.length === 0 && [
|
|
919
|
+
"if",
|
|
920
|
+
"then",
|
|
921
|
+
"elif",
|
|
922
|
+
"else",
|
|
923
|
+
"do",
|
|
924
|
+
"!",
|
|
925
|
+
"time"
|
|
926
|
+
].includes(token.value)) continue;
|
|
927
|
+
commandWords.push(token.value);
|
|
928
|
+
}
|
|
929
|
+
return tokens;
|
|
930
|
+
};
|
|
931
|
+
const shellArgumentTokens = (tokens) => {
|
|
932
|
+
const arguments_ = [];
|
|
933
|
+
let redirectTarget = false;
|
|
934
|
+
for (const token of tokens) {
|
|
935
|
+
if (token.kind === "comment") continue;
|
|
936
|
+
if (token.kind === "redirect") {
|
|
937
|
+
redirectTarget = true;
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
if (redirectTarget) {
|
|
941
|
+
redirectTarget = false;
|
|
942
|
+
if (token.kind === "word") continue;
|
|
943
|
+
}
|
|
944
|
+
arguments_.push(token);
|
|
945
|
+
}
|
|
946
|
+
return arguments_;
|
|
947
|
+
};
|
|
948
|
+
const shellReadsProgramStdin = (tokens) => {
|
|
949
|
+
const words = executableShellWords(shellArgumentTokens(tokens).map(({ value }) => value)).map(decodeShellWord);
|
|
950
|
+
if (!shellExecutable.test(words.at(0) ?? "")) return false;
|
|
951
|
+
let stdin = false;
|
|
952
|
+
let script = false;
|
|
953
|
+
for (let index = 1; index < words.length; index++) {
|
|
954
|
+
const option = words.at(index) ?? "";
|
|
955
|
+
if (option === "--" || option === "-") {
|
|
956
|
+
script = index + 1 < words.length;
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
if (!/^[+-]/.test(option)) {
|
|
960
|
+
script = true;
|
|
961
|
+
break;
|
|
962
|
+
}
|
|
963
|
+
if (shellValueOptions.has(option)) {
|
|
964
|
+
index++;
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
if (/^-[^-]*c/.test(option)) return false;
|
|
968
|
+
if (/^-[^-]*s/.test(option)) stdin = true;
|
|
969
|
+
}
|
|
970
|
+
return stdin || !script;
|
|
971
|
+
};
|
|
972
|
+
const shellStdinHeredoc = (tokens) => {
|
|
973
|
+
const descriptors = /* @__PURE__ */ new Map();
|
|
974
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
975
|
+
const token = tokens.at(index);
|
|
976
|
+
if (token?.kind !== "redirect") continue;
|
|
977
|
+
const declaration = /^(\d*)(.+)$/.exec(token.value);
|
|
978
|
+
const operator = declaration?.[2];
|
|
979
|
+
if (operator === void 0) continue;
|
|
980
|
+
const descriptor = String(Number(declaration?.[1] || (operator.startsWith("<") ? "0" : "1")));
|
|
981
|
+
const target = tokens.at(++index);
|
|
982
|
+
if (operator === "<<" || operator === "<<-") {
|
|
983
|
+
descriptors.set(descriptor, token);
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
if (operator === "<&" || operator === ">&") {
|
|
987
|
+
const copy = /^(\d+)(-?)$/.exec(decodeShellWord(target?.value ?? ""));
|
|
988
|
+
const sourceDescriptor = copy?.[1] === void 0 ? void 0 : String(Number(copy[1]));
|
|
989
|
+
const source = sourceDescriptor === void 0 ? void 0 : descriptors.get(sourceDescriptor);
|
|
990
|
+
if (source === void 0) descriptors.delete(descriptor);
|
|
991
|
+
else descriptors.set(descriptor, source);
|
|
992
|
+
if (copy?.[2] === "-" && sourceDescriptor !== void 0) descriptors.delete(sourceDescriptor);
|
|
993
|
+
continue;
|
|
994
|
+
}
|
|
995
|
+
descriptors.delete(descriptor);
|
|
996
|
+
if (operator === "&>" || operator === "&>>") descriptors.delete("2");
|
|
997
|
+
}
|
|
998
|
+
return descriptors.get("0");
|
|
999
|
+
};
|
|
1000
|
+
const shellExecutableSource = (command) => {
|
|
1001
|
+
const pending = [];
|
|
1002
|
+
return command.split(/\r?\n/).map((line) => {
|
|
1003
|
+
const here = pending.at(0);
|
|
1004
|
+
if (here !== void 0) {
|
|
1005
|
+
if ((here.stripTabs ? line.replace(/^\t+/, "") : line) === here.delimiter) {
|
|
1006
|
+
pending.shift();
|
|
1007
|
+
return "";
|
|
1008
|
+
}
|
|
1009
|
+
if (here.execute) return line;
|
|
1010
|
+
if (here.literal) return "";
|
|
1011
|
+
return shellSubstitutions(line).join("\n");
|
|
1012
|
+
}
|
|
1013
|
+
const words = shellTokens(line);
|
|
1014
|
+
let commandStart = 0;
|
|
1015
|
+
for (let index = 0; index < words.length; index++) {
|
|
1016
|
+
const match = words.at(index);
|
|
1017
|
+
const word = match?.value;
|
|
1018
|
+
if (word === void 0) continue;
|
|
1019
|
+
if (match?.kind === "comment") return line.slice(0, match.index);
|
|
1020
|
+
if (match?.kind === "boundary") {
|
|
1021
|
+
commandStart = index + 1;
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
const declaration = match?.kind === "redirect" ? /^(?:\d*)<<(-?)(.*)$/.exec(word) : null;
|
|
1025
|
+
let delimiter = declaration?.[2];
|
|
1026
|
+
if (delimiter === "") delimiter = words.at(++index)?.value;
|
|
1027
|
+
if (declaration !== null && delimiter !== void 0 && !delimiter.startsWith("<")) {
|
|
1028
|
+
const commandEnd = words.findIndex((entry, position) => position > index && (entry.kind === "boundary" || entry.kind === "comment"));
|
|
1029
|
+
const invocation = words.slice(commandStart, commandEnd < 0 ? void 0 : commandEnd);
|
|
1030
|
+
pending.push({
|
|
1031
|
+
delimiter: decodeShellWord(delimiter),
|
|
1032
|
+
stripTabs: declaration[1] === "-",
|
|
1033
|
+
literal: /['"\\]/.test(delimiter),
|
|
1034
|
+
execute: shellReadsProgramStdin(invocation) && shellStdinHeredoc(invocation) === match
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
return line;
|
|
1039
|
+
}).join("\n");
|
|
1040
|
+
};
|
|
1041
|
+
const executableShellWords = (words) => {
|
|
1042
|
+
let start = 0;
|
|
1043
|
+
const skipAssignments = () => {
|
|
1044
|
+
while (/^[A-Za-z_][A-Za-z\d_]*=/.test(decodeShellWord(words.at(start) ?? ""))) start += 1;
|
|
1045
|
+
};
|
|
1046
|
+
skipAssignments();
|
|
1047
|
+
while (/^(?:\/usr\/bin\/|\/bin\/)?env$/.test(decodeShellWord(words.at(start) ?? ""))) {
|
|
1048
|
+
start += 1;
|
|
1049
|
+
while (start < words.length) {
|
|
1050
|
+
const option = decodeShellWord(words.at(start) ?? "");
|
|
1051
|
+
if (option === "--") {
|
|
1052
|
+
start += 1;
|
|
1053
|
+
break;
|
|
1054
|
+
}
|
|
1055
|
+
if ([
|
|
1056
|
+
"-u",
|
|
1057
|
+
"--unset",
|
|
1058
|
+
"-C",
|
|
1059
|
+
"--chdir"
|
|
1060
|
+
].includes(option)) {
|
|
1061
|
+
start += 2;
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1064
|
+
if (option === "-S" || option === "--split-string" || option.startsWith("--split-string=") || option.startsWith("-S") && option.length > 2) {
|
|
1065
|
+
let source;
|
|
1066
|
+
if (option === "-S" || option === "--split-string") {
|
|
1067
|
+
source = decodeShellWord(words.at(start + 1) ?? "");
|
|
778
1068
|
start += 2;
|
|
779
|
-
|
|
1069
|
+
} else {
|
|
1070
|
+
source = option.startsWith("-S") ? option.slice(2) : option.slice(15);
|
|
1071
|
+
start++;
|
|
780
1072
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
1073
|
+
return executableShellWords([
|
|
1074
|
+
"env",
|
|
1075
|
+
...shellTokens(source).map(({ value }) => value),
|
|
1076
|
+
...words.slice(start)
|
|
1077
|
+
]);
|
|
786
1078
|
}
|
|
787
|
-
|
|
1079
|
+
if (option === "-" || option === "-i" || option === "--ignore-environment" || /^--(?:unset|chdir)=.+|^-[uC].+/.test(option)) {
|
|
1080
|
+
start += 1;
|
|
1081
|
+
continue;
|
|
1082
|
+
}
|
|
1083
|
+
break;
|
|
788
1084
|
}
|
|
789
|
-
|
|
790
|
-
}
|
|
1085
|
+
skipAssignments();
|
|
1086
|
+
}
|
|
1087
|
+
return words.slice(start);
|
|
791
1088
|
};
|
|
1089
|
+
const shellCommandBody = (rawWords) => {
|
|
1090
|
+
const words = executableShellWords(rawWords.filter((word) => word !== "(" && word !== ")")).map(decodeShellWord);
|
|
1091
|
+
if (!shellExecutable.test(words.at(0) ?? "")) return void 0;
|
|
1092
|
+
for (let index = 1; index < words.length; index++) {
|
|
1093
|
+
const option = words.at(index);
|
|
1094
|
+
if (option === void 0 || option === "--" || !/^[+-]/.test(option)) return void 0;
|
|
1095
|
+
if (shellValueOptions.has(option)) {
|
|
1096
|
+
index++;
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
if (/^-[^-]*c/.test(option)) return words.at(index + 1);
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
/** Shared executable shell words preserve assignments for runtime selectors. */
|
|
1103
|
+
const shellCommandSegments = (command, grouping = "boundaries") => {
|
|
1104
|
+
const segments = [[]];
|
|
1105
|
+
const executableSource = shellExecutableSource(command);
|
|
1106
|
+
const sources = /* @__PURE__ */ new Set([executableSource, ...shellSubstitutions(executableSource)]);
|
|
1107
|
+
for (const source of sources) {
|
|
1108
|
+
const firstSegment = segments.length;
|
|
1109
|
+
segments.push([]);
|
|
1110
|
+
for (const { kind, value: word } of shellArgumentTokens(shellTokens(source))) if (kind === "boundary") if ([
|
|
1111
|
+
"(",
|
|
1112
|
+
")",
|
|
1113
|
+
"{",
|
|
1114
|
+
"}"
|
|
1115
|
+
].includes(word) && grouping === "tokens") segments.at(-1)?.push(word);
|
|
1116
|
+
else segments.push([]);
|
|
1117
|
+
else segments.at(-1)?.push(word);
|
|
1118
|
+
for (const words of segments.slice(firstSegment)) {
|
|
1119
|
+
const body = shellCommandBody(words);
|
|
1120
|
+
if (body === void 0) continue;
|
|
1121
|
+
const executableBody = shellExecutableSource(body);
|
|
1122
|
+
sources.add(executableBody);
|
|
1123
|
+
for (const substitution of shellSubstitutions(executableBody)) sources.add(substitution);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return segments;
|
|
1127
|
+
};
|
|
1128
|
+
const compilerCommandSegments = (command) => shellCommandSegments(command, "tokens").map(executableShellWords);
|
|
792
1129
|
const launcherValueOptions = new Map(Object.entries({
|
|
793
1130
|
npx: [
|
|
794
1131
|
"-p",
|
|
@@ -922,10 +1259,10 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
922
1259
|
const document = pnpm ? (0, yaml.parseDocument)(text, { lineCounter }) : void 0;
|
|
923
1260
|
if (document !== void 0 && document.errors.length > 0) throw new Error("invalid YAML");
|
|
924
1261
|
const json = document === void 0 ? JSON.parse(text) : document.toJS();
|
|
925
|
-
if (!record$
|
|
1262
|
+
if (!record$3(json)) throw new Error("manifest must be an object");
|
|
926
1263
|
if (pnpm) {
|
|
927
|
-
if (json["catalog"] !== void 0 && !record$
|
|
928
|
-
if (json["catalogs"] !== void 0 && (!record$
|
|
1264
|
+
if (json["catalog"] !== void 0 && !record$3(json["catalog"])) throw new Error("catalog must be a mapping");
|
|
1265
|
+
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
1266
|
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
1267
|
}
|
|
931
1268
|
manifests.set(file, json);
|
|
@@ -939,13 +1276,13 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
939
1276
|
catalog: json["overrides"],
|
|
940
1277
|
prefix: ["overrides"]
|
|
941
1278
|
},
|
|
942
|
-
...record$
|
|
1279
|
+
...record$3(json["catalogs"]) ? Object.entries(json["catalogs"]).map(([name, catalog]) => ({
|
|
943
1280
|
catalog,
|
|
944
1281
|
prefix: ["catalogs", name]
|
|
945
1282
|
})) : []
|
|
946
1283
|
];
|
|
947
1284
|
for (const { catalog, prefix } of catalogEntries) {
|
|
948
|
-
if (!record$
|
|
1285
|
+
if (!record$3(catalog)) continue;
|
|
949
1286
|
for (const [name, value] of Object.entries(catalog)) {
|
|
950
1287
|
const node = document.getIn([...prefix, name], true);
|
|
951
1288
|
if (!(0, yaml.isNode)(node) || node.range === void 0 || node.range === null) continue;
|
|
@@ -964,7 +1301,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
964
1301
|
});
|
|
965
1302
|
}
|
|
966
1303
|
}
|
|
967
|
-
const catalogSources = (json) => [json, ...record$
|
|
1304
|
+
const catalogSources = (json) => [json, ...record$3(json["workspaces"]) ? [json["workspaces"]] : []];
|
|
968
1305
|
const pnpmWorkspaceFor = (file) => {
|
|
969
1306
|
let directory = node_path.default.posix.dirname(file);
|
|
970
1307
|
while (true) {
|
|
@@ -987,8 +1324,8 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
987
1324
|
patterns: workspace["packages"],
|
|
988
1325
|
rootMembership: "implicit"
|
|
989
1326
|
})) return void 0;
|
|
990
|
-
if (name === "" || name === "default") return workspace["catalog"] ?? (record$
|
|
991
|
-
return record$
|
|
1327
|
+
if (name === "" || name === "default") return workspace["catalog"] ?? (record$3(workspace["catalogs"]) ? workspace["catalogs"]["default"] : void 0);
|
|
1328
|
+
return record$3(workspace["catalogs"]) ? workspace["catalogs"][name] : void 0;
|
|
992
1329
|
}
|
|
993
1330
|
let directory = node_path.default.posix.dirname(file);
|
|
994
1331
|
while (true) {
|
|
@@ -996,7 +1333,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
996
1333
|
if (manifest !== void 0) {
|
|
997
1334
|
for (const source of catalogSources(manifest)) {
|
|
998
1335
|
if (name === "" && source["catalog"] !== void 0) return source["catalog"];
|
|
999
|
-
if (record$
|
|
1336
|
+
if (record$3(source["catalogs"]) && source["catalogs"][name] !== void 0) return source["catalogs"][name];
|
|
1000
1337
|
}
|
|
1001
1338
|
if (manifest["workspaces"] !== void 0) return void 0;
|
|
1002
1339
|
}
|
|
@@ -1010,7 +1347,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1010
1347
|
if (visited.has(value)) return void 0;
|
|
1011
1348
|
visited.add(value);
|
|
1012
1349
|
const catalog = catalogFor(file, value.slice(8));
|
|
1013
|
-
value = record$
|
|
1350
|
+
value = record$3(catalog) ? catalog[name] : void 0;
|
|
1014
1351
|
}
|
|
1015
1352
|
return value;
|
|
1016
1353
|
};
|
|
@@ -1026,7 +1363,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1026
1363
|
const workspaces = manifests.get(node_path.default.posix.join(directory, "package.json"))?.["workspaces"];
|
|
1027
1364
|
if (workspaces !== void 0) return {
|
|
1028
1365
|
directory,
|
|
1029
|
-
patterns: record$
|
|
1366
|
+
patterns: record$3(workspaces) ? workspaces["packages"] : workspaces,
|
|
1030
1367
|
source: "json"
|
|
1031
1368
|
};
|
|
1032
1369
|
if (directory === ".") return void 0;
|
|
@@ -1082,7 +1419,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1082
1419
|
"peerDependencies"
|
|
1083
1420
|
]) {
|
|
1084
1421
|
const dependencies = json[section];
|
|
1085
|
-
if (!record$
|
|
1422
|
+
if (!record$3(dependencies)) continue;
|
|
1086
1423
|
for (const [name, value] of Object.entries(dependencies)) {
|
|
1087
1424
|
if (!tsSpecifiers.has(name)) continue;
|
|
1088
1425
|
const resolved = resolve(file, name, value);
|
|
@@ -1144,7 +1481,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1144
1481
|
message: `packageManager Bun version must be ${policy.bun}, found ${JSON.stringify(manager)}`
|
|
1145
1482
|
});
|
|
1146
1483
|
const checkEntries = (entries, location) => {
|
|
1147
|
-
if (!record$
|
|
1484
|
+
if (!record$3(entries)) return;
|
|
1148
1485
|
for (const [name, raw] of Object.entries(entries)) {
|
|
1149
1486
|
const value = resolve(file, name, raw);
|
|
1150
1487
|
const tsAllowed = tsSpecifiers.get(name);
|
|
@@ -1181,12 +1518,12 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1181
1518
|
"devDependencies",
|
|
1182
1519
|
"optionalDependencies"
|
|
1183
1520
|
]) checkEntries(json[key]);
|
|
1184
|
-
if (record$
|
|
1521
|
+
if (record$3(json["peerDependencies"])) {
|
|
1185
1522
|
for (const [name, value] of Object.entries(json["peerDependencies"])) if (tsSpecifiers.has(name) && !peerRange(resolve(file, name, value))) checkEntries({ [name]: value });
|
|
1186
1523
|
}
|
|
1187
1524
|
for (const source of catalogSources(json)) {
|
|
1188
1525
|
checkEntries(source["catalog"]);
|
|
1189
|
-
if (!record$
|
|
1526
|
+
if (!record$3(source["catalogs"])) continue;
|
|
1190
1527
|
for (const catalog of Object.values(source["catalogs"])) checkEntries(catalog);
|
|
1191
1528
|
}
|
|
1192
1529
|
const ownedNames = /* @__PURE__ */ new Set([...pins.keys(), ...tsSpecifiers.keys()]);
|
|
@@ -1198,7 +1535,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1198
1535
|
"optionalDependencies"
|
|
1199
1536
|
]) {
|
|
1200
1537
|
const entries = resolutionRoot[section];
|
|
1201
|
-
if (!record$
|
|
1538
|
+
if (!record$3(entries)) continue;
|
|
1202
1539
|
for (const [name, value] of Object.entries(entries)) resolutionDependencies[name] = resolve(file, name, value);
|
|
1203
1540
|
}
|
|
1204
1541
|
const affectedSpecifiers = /* @__PURE__ */ new Map();
|
|
@@ -1218,7 +1555,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1218
1555
|
"optionalDependencies"
|
|
1219
1556
|
]) {
|
|
1220
1557
|
const entries = member[section];
|
|
1221
|
-
if (!record$
|
|
1558
|
+
if (!record$3(entries)) continue;
|
|
1222
1559
|
for (const [name, value] of Object.entries(entries)) {
|
|
1223
1560
|
if (!tsSpecifiers.has(name)) continue;
|
|
1224
1561
|
const values = affectedSpecifiers.get(name) ?? /* @__PURE__ */ new Set();
|
|
@@ -1228,7 +1565,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1228
1565
|
}
|
|
1229
1566
|
}
|
|
1230
1567
|
const checkResolutionMap = (entries, parent) => {
|
|
1231
|
-
if (!record$
|
|
1568
|
+
if (!record$3(entries)) return;
|
|
1232
1569
|
for (const [selector, replacement] of Object.entries(entries)) {
|
|
1233
1570
|
const target = selector === "." ? parent : selector;
|
|
1234
1571
|
if (target === void 0) continue;
|
|
@@ -1251,7 +1588,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1251
1588
|
});
|
|
1252
1589
|
continue;
|
|
1253
1590
|
}
|
|
1254
|
-
if (record$
|
|
1591
|
+
if (record$3(replacement)) {
|
|
1255
1592
|
checkResolutionMap(replacement, target);
|
|
1256
1593
|
continue;
|
|
1257
1594
|
}
|
|
@@ -1285,7 +1622,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1285
1622
|
};
|
|
1286
1623
|
checkResolutionMap(json["overrides"]);
|
|
1287
1624
|
checkResolutionMap(json["resolutions"]);
|
|
1288
|
-
if (record$
|
|
1625
|
+
if (record$3(json["pnpm"])) checkResolutionMap(json["pnpm"]["overrides"]);
|
|
1289
1626
|
const localEntries = toolchainEntries(file, json);
|
|
1290
1627
|
const usesCompiler = policy.typescriptInstallLayouts.some((layout) => localEntries.some(({ name }) => name === layout.compilerPackage));
|
|
1291
1628
|
let selectedLayout = layoutFor(specifierMap(localEntries));
|
|
@@ -1309,7 +1646,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1309
1646
|
key: "dependencies",
|
|
1310
1647
|
message: "TypeScript compiler and compatibility packages must match one complete declared install layout"
|
|
1311
1648
|
});
|
|
1312
|
-
const command = record$
|
|
1649
|
+
const command = record$3(json["scripts"]) ? json["scripts"]["typecheck"] : void 0;
|
|
1313
1650
|
if (selectedLayout !== void 0 && typeof command === "string") {
|
|
1314
1651
|
const normalizedExpected = selectedLayout.typecheckCommand.replace(/\s+/g, " ").trim();
|
|
1315
1652
|
for (const words of compilerCommandSegments(command)) {
|
|
@@ -1327,7 +1664,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1327
1664
|
}
|
|
1328
1665
|
}
|
|
1329
1666
|
}
|
|
1330
|
-
if (record$
|
|
1667
|
+
if (record$3(json["peerDependencies"])) {
|
|
1331
1668
|
const peerLayout = selectedLayout ?? workspaceLayoutFor(file)?.layout;
|
|
1332
1669
|
for (const [name, raw] of Object.entries(json["peerDependencies"])) {
|
|
1333
1670
|
if (!tsSpecifiers.has(name)) continue;
|
|
@@ -1351,7 +1688,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1351
1688
|
});
|
|
1352
1689
|
}
|
|
1353
1690
|
}
|
|
1354
|
-
if (record$
|
|
1691
|
+
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
1692
|
file,
|
|
1356
1693
|
rule: "node-engine",
|
|
1357
1694
|
key: "node",
|
|
@@ -1362,6 +1699,232 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1362
1699
|
return diagnostics;
|
|
1363
1700
|
};
|
|
1364
1701
|
//#endregion
|
|
1702
|
+
//#region src/toolchain-rust.ts
|
|
1703
|
+
const record$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1704
|
+
/** Exceptions authorize one immutable release, never a channel or an entire rule. */
|
|
1705
|
+
const parseRustExceptions = (input, today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)) => {
|
|
1706
|
+
if (input === void 0) return [];
|
|
1707
|
+
if (!Array.isArray(input)) throw new Error("rustExceptions must be an array");
|
|
1708
|
+
const pins = /* @__PURE__ */ new Set();
|
|
1709
|
+
return input.map((entry) => {
|
|
1710
|
+
if (!record$2(entry) || Object.keys(entry).length !== 4 || Object.keys(entry).some((key) => ![
|
|
1711
|
+
"pin",
|
|
1712
|
+
"reason",
|
|
1713
|
+
"tracking",
|
|
1714
|
+
"expires"
|
|
1715
|
+
].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");
|
|
1716
|
+
pins.add(entry["pin"]);
|
|
1717
|
+
return {
|
|
1718
|
+
pin: entry["pin"],
|
|
1719
|
+
reason: entry["reason"],
|
|
1720
|
+
tracking: entry["tracking"],
|
|
1721
|
+
expires: entry["expires"]
|
|
1722
|
+
};
|
|
1723
|
+
});
|
|
1724
|
+
};
|
|
1725
|
+
/** Only executable fields are inspected: unrelated action inputs are not Rust selectors. */
|
|
1726
|
+
const checkRustReferences = (options) => {
|
|
1727
|
+
const { file, rust, exceptions } = options;
|
|
1728
|
+
const text = options.type === "file" ? options.text : "";
|
|
1729
|
+
const diagnostics = [];
|
|
1730
|
+
const select = (value, label, line = 1) => {
|
|
1731
|
+
if (value === rust || exceptions.some(({ pin }) => pin === value)) return;
|
|
1732
|
+
diagnostics.push({
|
|
1733
|
+
rule: "rust-version",
|
|
1734
|
+
path: file,
|
|
1735
|
+
line,
|
|
1736
|
+
message: `${label} must be Rust ${rust}, found ${String(value)}`
|
|
1737
|
+
});
|
|
1738
|
+
};
|
|
1739
|
+
const shell = (source, firstLine = 1) => {
|
|
1740
|
+
const commands = shellCommandSegments(source).map((words) => words.map((word) => ({
|
|
1741
|
+
value: decodeShellWord(word),
|
|
1742
|
+
line: firstLine
|
|
1743
|
+
})));
|
|
1744
|
+
for (const command of commands) {
|
|
1745
|
+
let position = 0;
|
|
1746
|
+
const prefixes = /* @__PURE__ */ new Set([
|
|
1747
|
+
"if",
|
|
1748
|
+
"then",
|
|
1749
|
+
"do",
|
|
1750
|
+
"elif",
|
|
1751
|
+
"!",
|
|
1752
|
+
"env",
|
|
1753
|
+
"sudo",
|
|
1754
|
+
"command",
|
|
1755
|
+
"exec",
|
|
1756
|
+
"time",
|
|
1757
|
+
"export",
|
|
1758
|
+
"readonly",
|
|
1759
|
+
"/usr/bin/env",
|
|
1760
|
+
"/bin/env"
|
|
1761
|
+
]);
|
|
1762
|
+
while (position < command.length) {
|
|
1763
|
+
const value = command.at(position)?.value;
|
|
1764
|
+
if (value?.startsWith("RUSTUP_TOOLCHAIN=")) select(value.slice(17), "RUSTUP_TOOLCHAIN", firstLine);
|
|
1765
|
+
if (value === "-u" || value === "--unset") {
|
|
1766
|
+
position += 2;
|
|
1767
|
+
continue;
|
|
1768
|
+
}
|
|
1769
|
+
if (value?.startsWith("--unset=") || value === "-i" || value === "--ignore-environment") {
|
|
1770
|
+
position++;
|
|
1771
|
+
continue;
|
|
1772
|
+
}
|
|
1773
|
+
if (value !== void 0 && (prefixes.has(value) || /^[A-Za-z_][A-Za-z_\d]*=/.test(value))) position++;
|
|
1774
|
+
else break;
|
|
1775
|
+
}
|
|
1776
|
+
const executable = command.at(position)?.value;
|
|
1777
|
+
const arguments_ = command.slice(position + 1);
|
|
1778
|
+
if (executable === "cargo" && arguments_.at(0)?.value.startsWith("+")) {
|
|
1779
|
+
const selector = arguments_.at(0);
|
|
1780
|
+
select(selector?.value.slice(1), "cargo toolchain", selector?.line);
|
|
1781
|
+
}
|
|
1782
|
+
if (executable !== "rustup") continue;
|
|
1783
|
+
let subcommandPosition = 0;
|
|
1784
|
+
while (subcommandPosition < arguments_.length) {
|
|
1785
|
+
const token = arguments_.at(subcommandPosition);
|
|
1786
|
+
if (token === void 0) break;
|
|
1787
|
+
if (token.value === "--") {
|
|
1788
|
+
subcommandPosition++;
|
|
1789
|
+
break;
|
|
1790
|
+
}
|
|
1791
|
+
if (token.value.startsWith("+")) select(token.value.slice(1), "rustup toolchain", token.line);
|
|
1792
|
+
else if (!token.value.startsWith("-")) break;
|
|
1793
|
+
subcommandPosition++;
|
|
1794
|
+
}
|
|
1795
|
+
const invocation = arguments_.slice(subcommandPosition);
|
|
1796
|
+
const subcommand = invocation.at(0)?.value;
|
|
1797
|
+
if (subcommand === "target" || subcommand === "component") {
|
|
1798
|
+
for (let index = 1; index < invocation.length; index++) {
|
|
1799
|
+
const token = invocation.at(index);
|
|
1800
|
+
if (token?.value === "--toolchain") select(invocation.at(++index)?.value, "rustup --toolchain", token.line);
|
|
1801
|
+
else if (token?.value.startsWith("--toolchain=")) select(token.value.slice(12), "rustup --toolchain", token.line);
|
|
1802
|
+
}
|
|
1803
|
+
continue;
|
|
1804
|
+
}
|
|
1805
|
+
let selectors;
|
|
1806
|
+
let requiresSelector = true;
|
|
1807
|
+
let firstSelectorOnly = false;
|
|
1808
|
+
if (subcommand === "default" || subcommand === "install" || subcommand === "add" || subcommand === "update") {
|
|
1809
|
+
selectors = invocation.slice(1);
|
|
1810
|
+
requiresSelector = subcommand !== "update";
|
|
1811
|
+
} else if (subcommand === "toolchain" && [
|
|
1812
|
+
"install",
|
|
1813
|
+
"add",
|
|
1814
|
+
"update"
|
|
1815
|
+
].includes(invocation.at(1)?.value ?? "")) {
|
|
1816
|
+
selectors = invocation.slice(2);
|
|
1817
|
+
requiresSelector = invocation.at(1)?.value !== "update";
|
|
1818
|
+
} else if (subcommand === "run") {
|
|
1819
|
+
selectors = invocation.slice(1);
|
|
1820
|
+
firstSelectorOnly = true;
|
|
1821
|
+
} else if (subcommand === "override" && invocation.at(1)?.value === "set") {
|
|
1822
|
+
selectors = invocation.slice(2);
|
|
1823
|
+
firstSelectorOnly = true;
|
|
1824
|
+
} else continue;
|
|
1825
|
+
let selected = 0;
|
|
1826
|
+
const optionArguments = /* @__PURE__ */ new Set([
|
|
1827
|
+
"--path",
|
|
1828
|
+
"--profile",
|
|
1829
|
+
"--component",
|
|
1830
|
+
"-c",
|
|
1831
|
+
"--target",
|
|
1832
|
+
"-t"
|
|
1833
|
+
]);
|
|
1834
|
+
for (let index = 0; index < selectors.length; index++) {
|
|
1835
|
+
const token = selectors.at(index);
|
|
1836
|
+
if (token === void 0) continue;
|
|
1837
|
+
if (optionArguments.has(token.value)) {
|
|
1838
|
+
index++;
|
|
1839
|
+
continue;
|
|
1840
|
+
}
|
|
1841
|
+
if (token.value.startsWith("-")) continue;
|
|
1842
|
+
selected++;
|
|
1843
|
+
select(token.value, "rustup toolchain", token.line);
|
|
1844
|
+
if (firstSelectorOnly) break;
|
|
1845
|
+
}
|
|
1846
|
+
if (selected === 0 && requiresSelector) select(void 0, "rustup toolchain", firstLine);
|
|
1847
|
+
}
|
|
1848
|
+
};
|
|
1849
|
+
if (options.type === "environment") {
|
|
1850
|
+
for (const { key, value } of options.assignments) if (key === "RUSTUP_TOOLCHAIN") select(value, key, options.line);
|
|
1851
|
+
return diagnostics;
|
|
1852
|
+
}
|
|
1853
|
+
if (options.type === "command") {
|
|
1854
|
+
shell(options.source, options.line);
|
|
1855
|
+
return diagnostics;
|
|
1856
|
+
}
|
|
1857
|
+
const lineOf = (token) => {
|
|
1858
|
+
const index = text.split(/\r?\n/).findIndex((line) => line.includes(token));
|
|
1859
|
+
return index < 0 ? 1 : index + 1;
|
|
1860
|
+
};
|
|
1861
|
+
if (require_toolchain_inputs.isShellScript(file, text)) shell(text);
|
|
1862
|
+
if (/(?:^|\/)package\.json$/.test(file)) try {
|
|
1863
|
+
const object = JSON.parse(text);
|
|
1864
|
+
if (record$2(object) && record$2(object["scripts"])) {
|
|
1865
|
+
for (const [name, script] of Object.entries(object["scripts"])) if (typeof script === "string") shell(script, lineOf(`"${name}"`));
|
|
1866
|
+
}
|
|
1867
|
+
} catch {}
|
|
1868
|
+
if (/(?:^|\/)ci-service-images\.json$/.test(file)) {
|
|
1869
|
+
const images = (value) => {
|
|
1870
|
+
if (typeof value === "string" && /^rust(?=:|@|$)/.test(canonicalDockerRuntime(value))) {
|
|
1871
|
+
const tag = canonicalDockerRuntime(value).replace(/^rust:?/, "").split("@").at(0);
|
|
1872
|
+
const version = tag === void 0 ? void 0 : rustImageVersion(tag);
|
|
1873
|
+
select(version, "Rust service image", lineOf(value));
|
|
1874
|
+
} else if (Array.isArray(value)) value.forEach(images);
|
|
1875
|
+
else if (record$2(value)) Object.values(value).forEach(images);
|
|
1876
|
+
};
|
|
1877
|
+
try {
|
|
1878
|
+
images(JSON.parse(text));
|
|
1879
|
+
} catch {
|
|
1880
|
+
diagnostics.push({
|
|
1881
|
+
rule: "rust-version",
|
|
1882
|
+
path: file,
|
|
1883
|
+
line: 1,
|
|
1884
|
+
message: "invalid service image JSON"
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
if (require_toolchain_inputs.githubAutomationFileKind(file) === void 0) return diagnostics;
|
|
1889
|
+
const document = (0, yaml.parseDocument)(text);
|
|
1890
|
+
if (document.errors.length > 0) return diagnostics;
|
|
1891
|
+
try {
|
|
1892
|
+
const visit = (value) => {
|
|
1893
|
+
if (Array.isArray(value)) {
|
|
1894
|
+
value.forEach(visit);
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1897
|
+
if (!record$2(value)) return;
|
|
1898
|
+
if (record$2(value["env"]) && value["env"]["RUSTUP_TOOLCHAIN"] !== void 0) select(value["env"]["RUSTUP_TOOLCHAIN"], "RUSTUP_TOOLCHAIN", lineOf("RUSTUP_TOOLCHAIN"));
|
|
1899
|
+
if (typeof value["run"] === "string") shell(value["run"], lineOf("run:"));
|
|
1900
|
+
const uses = value["uses"];
|
|
1901
|
+
if (typeof uses === "string") {
|
|
1902
|
+
const action = uses.split("@").at(0)?.toLowerCase();
|
|
1903
|
+
const inputs = record$2(value["with"]) ? value["with"] : {};
|
|
1904
|
+
if (action === "dtolnay/rust-toolchain" || action === "actions-rust-lang/setup-rust-toolchain") {
|
|
1905
|
+
const entries = Object.entries(inputs).filter(([key]) => key.toLowerCase() === "toolchain");
|
|
1906
|
+
if (entries.length !== 1) select(void 0, `${action} requires an explicit exact toolchain input`, lineOf(uses));
|
|
1907
|
+
else {
|
|
1908
|
+
const selected = entries.at(0)?.at(1);
|
|
1909
|
+
const selectors = action === "actions-rust-lang/setup-rust-toolchain" && typeof selected === "string" ? selected.split(",").map((pin) => pin.trim()) : [selected];
|
|
1910
|
+
for (const selector of selectors) select(selector, `${action} toolchain`, lineOf("toolchain:"));
|
|
1911
|
+
}
|
|
1912
|
+
} else if (/(?:^|\/)\.github\/workflows\/[^@]+\.ya?ml(?:@|$)/.test(uses)) {
|
|
1913
|
+
for (const [key, selected] of Object.entries(inputs)) if (["toolchain", "rust-toolchain"].includes(key.toLowerCase())) select(selected, `reusable workflow ${key}`, lineOf(`${key}:`));
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1917
|
+
if (key === "workflow_call" && record$2(child) && record$2(child["inputs"])) {
|
|
1918
|
+
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:"));
|
|
1919
|
+
}
|
|
1920
|
+
visit(child);
|
|
1921
|
+
}
|
|
1922
|
+
};
|
|
1923
|
+
visit(document.toJS({ maxAliasCount: 100 }));
|
|
1924
|
+
} catch {}
|
|
1925
|
+
return diagnostics;
|
|
1926
|
+
};
|
|
1927
|
+
//#endregion
|
|
1365
1928
|
//#region src/toolchain-runtime.ts
|
|
1366
1929
|
const runtimeRules = [
|
|
1367
1930
|
"bun-pins",
|
|
@@ -1374,6 +1937,69 @@ const runtimeRules = [
|
|
|
1374
1937
|
"action-pins"
|
|
1375
1938
|
];
|
|
1376
1939
|
const record$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1940
|
+
const dockerEnvironmentAssignments = ({ source, escape }) => {
|
|
1941
|
+
const words = [];
|
|
1942
|
+
let word = "";
|
|
1943
|
+
let quote;
|
|
1944
|
+
for (let index = 0; index < source.length; index++) {
|
|
1945
|
+
const character = source.at(index) ?? "";
|
|
1946
|
+
if (character === escape && quote !== "'") {
|
|
1947
|
+
word += character + (source.at(++index) ?? "");
|
|
1948
|
+
continue;
|
|
1949
|
+
}
|
|
1950
|
+
if (character === quote) quote = void 0;
|
|
1951
|
+
else if (quote === void 0 && (character === "'" || character === "\"")) quote = character;
|
|
1952
|
+
else if (quote === void 0 && /\s/.test(character)) {
|
|
1953
|
+
if (word !== "") words.push(word);
|
|
1954
|
+
word = "";
|
|
1955
|
+
continue;
|
|
1956
|
+
}
|
|
1957
|
+
word += character;
|
|
1958
|
+
}
|
|
1959
|
+
if (quote !== void 0) return void 0;
|
|
1960
|
+
if (word !== "") words.push(word);
|
|
1961
|
+
const decode = (raw) => {
|
|
1962
|
+
let value = "";
|
|
1963
|
+
let quoted;
|
|
1964
|
+
for (let index = 0; index < raw.length; index++) {
|
|
1965
|
+
const character = raw.at(index) ?? "";
|
|
1966
|
+
if (character === quoted) {
|
|
1967
|
+
quoted = void 0;
|
|
1968
|
+
continue;
|
|
1969
|
+
}
|
|
1970
|
+
if (quoted === void 0 && (character === "'" || character === "\"")) {
|
|
1971
|
+
quoted = character;
|
|
1972
|
+
continue;
|
|
1973
|
+
}
|
|
1974
|
+
const next = raw.at(index + 1);
|
|
1975
|
+
if (character === escape && quoted !== "'" && next !== void 0 && (quoted === void 0 || next === "\"" || next === "$" || next === escape)) {
|
|
1976
|
+
value += next;
|
|
1977
|
+
index++;
|
|
1978
|
+
} else value += character;
|
|
1979
|
+
}
|
|
1980
|
+
return value;
|
|
1981
|
+
};
|
|
1982
|
+
const first = words.at(0);
|
|
1983
|
+
if (first === void 0) return void 0;
|
|
1984
|
+
if (!first.includes("=")) {
|
|
1985
|
+
const legacy = /^(\S+)\s+([\s\S]*)$/.exec(source);
|
|
1986
|
+
if (legacy === null) return void 0;
|
|
1987
|
+
return [{
|
|
1988
|
+
key: decode(legacy[1] ?? ""),
|
|
1989
|
+
value: decode(legacy[2] ?? "")
|
|
1990
|
+
}];
|
|
1991
|
+
}
|
|
1992
|
+
const assignments = [];
|
|
1993
|
+
for (const raw of words) {
|
|
1994
|
+
const separator = raw.indexOf("=");
|
|
1995
|
+
if (separator < 1) return void 0;
|
|
1996
|
+
assignments.push({
|
|
1997
|
+
key: decode(raw.slice(0, separator)),
|
|
1998
|
+
value: decode(raw.slice(separator + 1))
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
2001
|
+
return assignments;
|
|
2002
|
+
};
|
|
1377
2003
|
const pythonSelectorMatches = (value, expected) => value === expected || typeof value === "string" && /^\d+\.\d+$/.test(expected) && value.startsWith(`${expected}.`) && /^\d+\.\d+\.\d+$/.test(value);
|
|
1378
2004
|
const compareRelease = (left, right) => {
|
|
1379
2005
|
for (let index = 0; index < Math.max(left.length, right.length); index++) {
|
|
@@ -1443,8 +2069,14 @@ const pythonRequirementMatches = (requirement, version) => {
|
|
|
1443
2069
|
});
|
|
1444
2070
|
};
|
|
1445
2071
|
/** 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 =
|
|
2072
|
+
const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, repository, onDelegated, dynamicSelectors = [], onDynamicSelector, rustExceptions = [], engineFloors = [], onEngineFloor }) => {
|
|
2073
|
+
const diagnostics = checkRustReferences({
|
|
2074
|
+
type: "file",
|
|
2075
|
+
file,
|
|
2076
|
+
text,
|
|
2077
|
+
rust: policy.rust,
|
|
2078
|
+
exceptions: rustExceptions
|
|
2079
|
+
});
|
|
1448
2080
|
const name = node_path.default.posix.basename(file);
|
|
1449
2081
|
const lines = text.split(/\r?\n/);
|
|
1450
2082
|
const add = ({ rule, line, message }) => {
|
|
@@ -1467,7 +2099,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1467
2099
|
};
|
|
1468
2100
|
const lineOf = (key) => Math.max(1, lines.findIndex((line) => line.includes(key)) + 1);
|
|
1469
2101
|
const pin = ({ rule, value, expected, label, line = 1, selector }) => {
|
|
1470
|
-
let matches = value === expected;
|
|
2102
|
+
let matches = value === expected || rule === "rust-version" && rustExceptions.some((entry) => entry.pin === value);
|
|
1471
2103
|
if (selector === "node") matches = require_toolchain_inputs.nodeSelectorMatches(value, expected);
|
|
1472
2104
|
else if (label.includes("python")) matches = pythonSelectorMatches(value, expected);
|
|
1473
2105
|
if (!matches) add({
|
|
@@ -1537,12 +2169,10 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1537
2169
|
const parsed = toml("rust-version");
|
|
1538
2170
|
if (parsed !== void 0) {
|
|
1539
2171
|
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
2172
|
pin({
|
|
1543
2173
|
rule: "rust-version",
|
|
1544
2174
|
value: record$1(toolchain) ? toolchain["channel"] : void 0,
|
|
1545
|
-
expected,
|
|
2175
|
+
expected: policy.rust,
|
|
1546
2176
|
label: "Rust channel",
|
|
1547
2177
|
line: lineOf("channel")
|
|
1548
2178
|
});
|
|
@@ -1643,7 +2273,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1643
2273
|
if (record$1(tools)) for (const [tool, expected] of Object.entries(versions)) {
|
|
1644
2274
|
const configured = tools[tool];
|
|
1645
2275
|
if (configured !== void 0) pin({
|
|
1646
|
-
rule: "runtime-manager",
|
|
2276
|
+
rule: tool === "rust" || tool === "core:rust" ? "rust-version" : "runtime-manager",
|
|
1647
2277
|
value: record$1(configured) ? configured["version"] : configured,
|
|
1648
2278
|
expected,
|
|
1649
2279
|
selector: nodeTools.has(tool) ? "node" : void 0,
|
|
@@ -1658,7 +2288,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1658
2288
|
if (tool === void 0 || !(tool in versions)) return;
|
|
1659
2289
|
const expected = Object.entries(versions).find(([key]) => key === tool)?.[1];
|
|
1660
2290
|
if (expected !== void 0) pin({
|
|
1661
|
-
rule: "runtime-manager",
|
|
2291
|
+
rule: tool === "rust" || tool === "core:rust" ? "rust-version" : "runtime-manager",
|
|
1662
2292
|
value: fields.slice(1).join(" "),
|
|
1663
2293
|
expected,
|
|
1664
2294
|
selector: nodeTools.has(tool) ? "node" : void 0,
|
|
@@ -1668,11 +2298,12 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1668
2298
|
});
|
|
1669
2299
|
const checkRuntimeImage = ({ image, line, label, at }) => {
|
|
1670
2300
|
const unknownRuntime = () => {
|
|
1671
|
-
|
|
2301
|
+
const rustImage = typeof image === "string" && /^rust(?=:|@|$)/i.test(canonicalDockerRuntime(image));
|
|
2302
|
+
if (!rustImage && dynamic(image) && authorizeDynamic("image", line, at)) return;
|
|
1672
2303
|
add({
|
|
1673
|
-
rule: "runtime-docker",
|
|
2304
|
+
rule: rustImage ? "rust-version" : "runtime-docker",
|
|
1674
2305
|
line,
|
|
1675
|
-
message: `cannot determine ${label} runtime; use a static image or a scoped dynamicSelectors declaration`
|
|
2306
|
+
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
2307
|
});
|
|
1677
2308
|
};
|
|
1678
2309
|
if (typeof image !== "string") {
|
|
@@ -1684,7 +2315,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1684
2315
|
const digestSeparator = structuralImage.indexOf("@");
|
|
1685
2316
|
const reference = digestSeparator < 0 ? canonical : canonical.slice(0, digestSeparator);
|
|
1686
2317
|
const structuralReference = digestSeparator < 0 ? structuralImage : structuralImage.slice(0, digestSeparator);
|
|
1687
|
-
const managedFamily = /^(node|python|oven\/bun)(?=:|$)/i.exec(structuralReference)?.[1];
|
|
2318
|
+
const managedFamily = /^(node|python|rust|oven\/bun)(?=:|$)/i.exec(structuralReference)?.[1];
|
|
1688
2319
|
const tagSeparator = managedFamily === void 0 ? structuralReference.lastIndexOf(":") : structuralReference.indexOf(":", managedFamily.length);
|
|
1689
2320
|
const hasTag = managedFamily === void 0 ? tagSeparator > structuralReference.lastIndexOf("/") : tagSeparator !== -1;
|
|
1690
2321
|
const imageRepository = hasTag ? reference.slice(0, tagSeparator) : reference;
|
|
@@ -1692,8 +2323,11 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1692
2323
|
unknownRuntime();
|
|
1693
2324
|
return;
|
|
1694
2325
|
}
|
|
1695
|
-
if (!/^(node|python|oven\/bun)$/i.test(imageRepository)) return;
|
|
2326
|
+
if (!/^(node|python|rust|oven\/bun)$/i.test(imageRepository)) return;
|
|
1696
2327
|
const tool = imageRepository.toLowerCase();
|
|
2328
|
+
let imageRule = "runtime-docker";
|
|
2329
|
+
if (tool === "rust") imageRule = "rust-version";
|
|
2330
|
+
else if (tool === "oven/bun") imageRule = "bun-pins";
|
|
1697
2331
|
if (image.includes("$")) {
|
|
1698
2332
|
unknownRuntime();
|
|
1699
2333
|
return;
|
|
@@ -1701,7 +2335,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1701
2335
|
const digest = digestSeparator < 0 ? void 0 : canonical.slice(digestSeparator + 1);
|
|
1702
2336
|
if (digest !== void 0 && !/^sha256:[a-fA-F\d]{64}$/.test(digest)) {
|
|
1703
2337
|
add({
|
|
1704
|
-
rule:
|
|
2338
|
+
rule: imageRule,
|
|
1705
2339
|
line,
|
|
1706
2340
|
message: `${label} ${tool} digest must be sha256 followed by 64 hexadecimal characters`
|
|
1707
2341
|
});
|
|
@@ -1709,14 +2343,15 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1709
2343
|
}
|
|
1710
2344
|
const tag = hasTag ? reference.slice(tagSeparator + 1) : void 0;
|
|
1711
2345
|
const variantSuffix = tool === "oven/bun" ? /-(?:alpine|slim|debian)$/ : /-(?:alpine(?:\d+(?:\.\d+)*)?|(?:bookworm|bullseye|trixie|buster)(?:-slim)?|slim)$/;
|
|
1712
|
-
const version = tag?.replace(variantSuffix, "");
|
|
2346
|
+
const version = tool === "rust" && tag !== void 0 ? rustImageVersion(tag) : tag?.replace(variantSuffix, "");
|
|
1713
2347
|
let expected = policy.bun;
|
|
1714
2348
|
if (tool === "node") expected = policy.node;
|
|
1715
2349
|
else if (tool === "python") expected = policy.python;
|
|
2350
|
+
else if (tool === "rust") expected = policy.rust;
|
|
1716
2351
|
if (digest !== void 0) {
|
|
1717
2352
|
if (version === void 0 || !/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/.test(version)) {
|
|
1718
2353
|
add({
|
|
1719
|
-
rule:
|
|
2354
|
+
rule: imageRule,
|
|
1720
2355
|
line,
|
|
1721
2356
|
message: `${label} ${tool} digest requires an exact stable patch tag matching ${expected}`
|
|
1722
2357
|
});
|
|
@@ -1734,7 +2369,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1734
2369
|
}
|
|
1735
2370
|
}
|
|
1736
2371
|
pin({
|
|
1737
|
-
rule:
|
|
2372
|
+
rule: imageRule,
|
|
1738
2373
|
value: version,
|
|
1739
2374
|
expected,
|
|
1740
2375
|
selector: tool === "node" ? "node" : void 0,
|
|
@@ -1751,6 +2386,17 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1751
2386
|
const heredocs = [];
|
|
1752
2387
|
let instruction = "";
|
|
1753
2388
|
let startLine = 1;
|
|
2389
|
+
let pendingRustRun;
|
|
2390
|
+
const checkRustCommand = (source, line) => {
|
|
2391
|
+
diagnostics.push(...checkRustReferences({
|
|
2392
|
+
type: "command",
|
|
2393
|
+
file,
|
|
2394
|
+
source,
|
|
2395
|
+
line,
|
|
2396
|
+
rust: policy.rust,
|
|
2397
|
+
exceptions: rustExceptions
|
|
2398
|
+
}));
|
|
2399
|
+
};
|
|
1754
2400
|
const expand = (value) => {
|
|
1755
2401
|
for (let count = 0; count <= variables.size; count++) {
|
|
1756
2402
|
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 +2483,49 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1837
2483
|
if (key !== void 0 && value !== void 0) variables.set(key, expand(value.replace(/^(["'])(.*)\1$/, "$2")));
|
|
1838
2484
|
return;
|
|
1839
2485
|
}
|
|
1840
|
-
|
|
2486
|
+
const run = /^\s*RUN\s+(.+)$/i.exec(content);
|
|
2487
|
+
if (run !== null) {
|
|
2488
|
+
let source = run[1] ?? "";
|
|
2489
|
+
source = source.replace(/^(?:--\S+\s+)*/, "");
|
|
2490
|
+
if (source.startsWith("[")) try {
|
|
2491
|
+
const words = JSON.parse(source);
|
|
2492
|
+
if (Array.isArray(words) && words.every((word) => typeof word === "string")) source = words.map((word) => `'${word.replaceAll("'", "'\\''")}'`).join(" ");
|
|
2493
|
+
} catch {}
|
|
2494
|
+
const implicit = /^(<<-?(?:'[^']+'|"[^"]+"|\S+))(?:\s+(.+))?$/.exec(source);
|
|
2495
|
+
if (implicit !== null && implicit[2] === void 0) source = `sh ${source}`;
|
|
2496
|
+
collectHeredocs(content, line);
|
|
2497
|
+
if (heredocs.length > 0) pendingRustRun = {
|
|
2498
|
+
source,
|
|
2499
|
+
line
|
|
2500
|
+
};
|
|
2501
|
+
else checkRustCommand(source, line);
|
|
2502
|
+
return;
|
|
2503
|
+
}
|
|
2504
|
+
const environment = /^\s*ENV\s+(.+)$/i.exec(content);
|
|
2505
|
+
if (environment !== null) {
|
|
2506
|
+
const assignments = dockerEnvironmentAssignments({
|
|
2507
|
+
source: environment[1] ?? "",
|
|
2508
|
+
escape
|
|
2509
|
+
});
|
|
2510
|
+
if (assignments === void 0) {
|
|
2511
|
+
add({
|
|
2512
|
+
rule: "runtime-docker",
|
|
2513
|
+
line,
|
|
2514
|
+
message: "invalid Docker ENV assignments"
|
|
2515
|
+
});
|
|
2516
|
+
return;
|
|
2517
|
+
}
|
|
2518
|
+
diagnostics.push(...checkRustReferences({
|
|
2519
|
+
type: "environment",
|
|
2520
|
+
file,
|
|
2521
|
+
assignments,
|
|
2522
|
+
line,
|
|
2523
|
+
rust: policy.rust,
|
|
2524
|
+
exceptions: rustExceptions
|
|
2525
|
+
}));
|
|
2526
|
+
return;
|
|
2527
|
+
}
|
|
2528
|
+
if (/^\s*COPY\s+/i.test(content)) {
|
|
1841
2529
|
collectHeredocs(content, line);
|
|
1842
2530
|
return;
|
|
1843
2531
|
}
|
|
@@ -1858,7 +2546,12 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
1858
2546
|
lines.forEach((line, index) => {
|
|
1859
2547
|
const heredoc = heredocs.at(0);
|
|
1860
2548
|
if (heredoc !== void 0) {
|
|
2549
|
+
if (pendingRustRun !== void 0) pendingRustRun.source += `\n${line}`;
|
|
1861
2550
|
if ((heredoc.stripTabs ? line.replace(/^\t+/, "") : line) === heredoc.delimiter) heredocs.shift();
|
|
2551
|
+
if (heredocs.length === 0 && pendingRustRun !== void 0) {
|
|
2552
|
+
checkRustCommand(pendingRustRun.source, pendingRustRun.line);
|
|
2553
|
+
pendingRustRun = void 0;
|
|
2554
|
+
}
|
|
1862
2555
|
return;
|
|
1863
2556
|
}
|
|
1864
2557
|
if (line.trimStart().startsWith("#")) return;
|
|
@@ -2533,18 +3226,20 @@ const record = (value) => typeof value === "object" && value !== null && !Array.
|
|
|
2533
3226
|
const array = (value) => Array.isArray(value);
|
|
2534
3227
|
/** Repository-owned declarations are explicit; malformed decisions fail closed. */
|
|
2535
3228
|
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
|
|
3229
|
+
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
3230
|
const disabled = /* @__PURE__ */ new Set();
|
|
2538
3231
|
const optOuts = input["optOuts"] ?? [];
|
|
2539
3232
|
if (!array(optOuts)) throw new Error("optOuts must be an array");
|
|
2540
3233
|
for (const entry of optOuts) {
|
|
2541
3234
|
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");
|
|
3235
|
+
if (entry["rule"] === "rust-version") throw new Error("rust-version requires expiring exact-pin rustExceptions, not an opt-out");
|
|
2542
3236
|
if (entry["rule"] === "node-engine") throw new Error("node-engine cannot be opted out; engines.node must support the shared Node series");
|
|
2543
3237
|
if (entry["rule"] === "cloud-setup-drift") throw new Error("cloud-setup-drift cannot be opted out; generated setup must match shared policy");
|
|
2544
3238
|
disabled.add(entry["rule"]);
|
|
2545
3239
|
}
|
|
2546
3240
|
return {
|
|
2547
3241
|
disabled,
|
|
3242
|
+
rustExceptions: parseRustExceptions(input["rustExceptions"]),
|
|
2548
3243
|
cloud: require_toolchain_inputs.parseCloudSetup(input["cloud"]),
|
|
2549
3244
|
engineFloors: parseEngineFloors(input["engineFloors"]),
|
|
2550
3245
|
dynamicSelectors: parseDynamicSelectors(input["dynamicSelectors"]),
|
|
@@ -2557,27 +3252,47 @@ const readToolchainInputs = (root) => {
|
|
|
2557
3252
|
const tracked = (0, node_child_process.execFileSync)("git", [
|
|
2558
3253
|
"ls-files",
|
|
2559
3254
|
"-z",
|
|
2560
|
-
"--cached"
|
|
3255
|
+
"--cached",
|
|
3256
|
+
"--stage"
|
|
2561
3257
|
], {
|
|
2562
3258
|
cwd: root,
|
|
2563
3259
|
encoding: "utf8"
|
|
2564
|
-
}).split("\0").filter((
|
|
3260
|
+
}).split("\0").filter((entry) => entry !== "" && !entry.startsWith("160000 ")).map((entry) => entry.slice(entry.indexOf(" ") + 1));
|
|
2565
3261
|
const files = {};
|
|
2566
3262
|
const trackedFiles = new Set(tracked);
|
|
2567
3263
|
const resolvedRoot = (0, node_fs.realpathSync)(root);
|
|
2568
3264
|
for (const file of tracked) {
|
|
2569
3265
|
const kind = require_toolchain_inputs.toolchainInputKind(file);
|
|
2570
|
-
|
|
3266
|
+
const shebangCandidate = kind === void 0 && node_path.default.posix.extname(file) === "" && !file.split("/").some((part) => part === "vendor" || part === "node_modules");
|
|
3267
|
+
if (kind === void 0 && !shebangCandidate) continue;
|
|
2571
3268
|
if (kind === "presence") {
|
|
2572
3269
|
files[file] = "";
|
|
2573
3270
|
continue;
|
|
2574
3271
|
}
|
|
2575
3272
|
try {
|
|
2576
|
-
const
|
|
3273
|
+
const inputPath = node_path.default.join(resolvedRoot, file);
|
|
3274
|
+
if (shebangCandidate) {
|
|
3275
|
+
const input = (0, node_fs.lstatSync)(inputPath);
|
|
3276
|
+
if (!input.isFile() && !input.isSymbolicLink()) continue;
|
|
3277
|
+
}
|
|
3278
|
+
const resolvedFile = (0, node_fs.realpathSync)(inputPath);
|
|
2577
3279
|
const relative = node_path.default.relative(resolvedRoot, resolvedFile);
|
|
2578
3280
|
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");
|
|
3281
|
+
if (shebangCandidate) {
|
|
3282
|
+
if (!(0, node_fs.lstatSync)(resolvedFile).isFile()) continue;
|
|
3283
|
+
const descriptor = (0, node_fs.openSync)(resolvedFile, "r");
|
|
3284
|
+
const prefix = Buffer.alloc(512);
|
|
3285
|
+
let count;
|
|
3286
|
+
try {
|
|
3287
|
+
count = (0, node_fs.readSync)(descriptor, prefix, 0, prefix.length, 0);
|
|
3288
|
+
} finally {
|
|
3289
|
+
(0, node_fs.closeSync)(descriptor);
|
|
3290
|
+
}
|
|
3291
|
+
if (require_toolchain_inputs.toolchainInputKind(file, prefix.toString("utf8", 0, count)) === void 0) continue;
|
|
3292
|
+
}
|
|
2579
3293
|
files[file] = (0, node_fs.readFileSync)(resolvedFile, "utf8");
|
|
2580
3294
|
} catch {
|
|
3295
|
+
if (shebangCandidate) continue;
|
|
2581
3296
|
diagnostics.push({
|
|
2582
3297
|
rule: "configuration",
|
|
2583
3298
|
path: file,
|
|
@@ -2678,6 +3393,7 @@ const checkToolchain = ({ root, policy, onDelegated }) => {
|
|
|
2678
3393
|
}
|
|
2679
3394
|
},
|
|
2680
3395
|
trackedFiles,
|
|
3396
|
+
rustExceptions: configuration.rustExceptions,
|
|
2681
3397
|
dynamicSelectors: dynamicSelectors.filter((entry) => entry.path === file),
|
|
2682
3398
|
onDynamicSelector: (entry) => selectorMatches.set(entry, (selectorMatches.get(entry) ?? 0) + 1),
|
|
2683
3399
|
repository,
|