@stll/oxlint-config 1.9.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -63
- package/dist/cloud-setup-cli.cjs +1 -2
- package/dist/cloud-setup-cli.cjs.map +1 -1
- package/dist/cloud-setup-cli.mjs +1 -2
- package/dist/cloud-setup-cli.mjs.map +1 -1
- package/dist/consumer-node-support.cjs +2 -2
- package/dist/consumer-node-support.mjs +1 -1
- package/dist/dependabot.d.cts +8 -19
- package/dist/dependabot.d.mts +8 -19
- package/dist/publish-artifact-syntax.cjs +1 -1
- package/dist/publish-artifact-syntax.mjs +1 -1
- package/dist/publish-contract-cli.cjs +1 -1
- package/dist/publish-contract-cli.mjs +1 -1
- package/dist/quarantine-cli.cjs +2 -1
- package/dist/quarantine-cli.cjs.map +1 -1
- package/dist/quarantine-cli.mjs +3 -2
- package/dist/quarantine-cli.mjs.map +1 -1
- package/dist/quarantine-policy.d.cts +25 -2
- package/dist/quarantine-policy.d.mts +25 -2
- package/dist/quarantine-policy2.cjs +177 -5
- package/dist/quarantine-policy2.cjs.map +1 -1
- package/dist/quarantine-policy2.mjs +166 -6
- package/dist/quarantine-policy2.mjs.map +1 -1
- package/dist/rust-policy-cli.cjs +481 -0
- package/dist/rust-policy-cli.cjs.map +1 -0
- package/dist/rust-policy-cli.mjs +479 -0
- package/dist/rust-policy-cli.mjs.map +1 -0
- package/dist/toolchain-changed-cli.cjs +1477 -2
- package/dist/toolchain-changed-cli.cjs.map +1 -1
- package/dist/toolchain-changed-cli.mjs +1474 -1
- package/dist/toolchain-changed-cli.mjs.map +1 -1
- package/dist/toolchain-check-cli.cjs +2 -0
- package/dist/toolchain-check-cli.cjs.map +1 -1
- package/dist/toolchain-check-cli.mjs +2 -0
- package/dist/toolchain-check-cli.mjs.map +1 -1
- package/dist/toolchain-dependabot.cjs +1 -1
- package/dist/toolchain-dependabot.mjs +2 -2
- package/dist/toolchain-dependabot.mjs.map +1 -1
- package/dist/toolchain-guard.cjs +353 -244
- package/dist/toolchain-guard.cjs.map +1 -1
- package/dist/toolchain-guard.mjs +348 -245
- package/dist/toolchain-guard.mjs.map +1 -1
- package/dist/toolchain-inputs.cjs +108 -56
- package/dist/toolchain-inputs.cjs.map +1 -1
- package/dist/toolchain-inputs.mjs +102 -56
- package/dist/toolchain-inputs.mjs.map +1 -1
- package/dist/toolchain-schema.cjs +8 -46
- package/dist/toolchain-schema.cjs.map +1 -1
- package/dist/toolchain-schema.mjs +8 -46
- package/dist/toolchain-schema.mjs.map +1 -1
- package/dist/typecheck-probe-cli.cjs +40 -2
- package/dist/typecheck-probe-cli.cjs.map +1 -1
- package/dist/typecheck-probe-cli.mjs +39 -1
- package/dist/typecheck-probe-cli.mjs.map +1 -1
- package/package.json +4 -4
- package/toolchain.json +13 -27
- package/dist/toolchain-bun.cjs +0 -34
- package/dist/toolchain-bun.cjs.map +0 -1
- package/dist/toolchain-bun.mjs +0 -23
- package/dist/toolchain-bun.mjs.map +0 -1
- package/dist/toolchain-changed.cjs +0 -1484
- package/dist/toolchain-changed.cjs.map +0 -1
- package/dist/toolchain-changed.mjs +0 -1477
- package/dist/toolchain-changed.mjs.map +0 -1
- package/dist/typecheck-parity-cli.cjs +0 -71
- package/dist/typecheck-parity-cli.cjs.map +0 -1
- package/dist/typecheck-parity-cli.mjs +0 -71
- package/dist/typecheck-parity-cli.mjs.map +0 -1
- package/dist/typecheck-parity.cjs +0 -908
- package/dist/typecheck-parity.cjs.map +0 -1
- package/dist/typecheck-parity.mjs +0 -897
- package/dist/typecheck-parity.mjs.map +0 -1
- /package/dist/{typecheck-parity-cli.d.cts → rust-policy-cli.d.cts} +0 -0
- /package/dist/{typecheck-parity-cli.d.mts → rust-policy-cli.d.mts} +0 -0
package/dist/toolchain-guard.mjs
CHANGED
|
@@ -1,21 +1,37 @@
|
|
|
1
|
-
import { t as checkRepositoryQuarantine } from "./quarantine-policy2.mjs";
|
|
1
|
+
import { l as overdueVerdict, t as checkRepositoryQuarantine } from "./quarantine-policy2.mjs";
|
|
2
2
|
import { C as cloudSetupPath, D as nodeSupportRangeMatches, E as nodeSelectorMatches, O as parseCloudSetup, _ as cloudAttributionFix, b as hasCloudAttribution, f as toolchainInputKind, g as workspaceContains, h as isKubernetesDefinitionPath, i as isDockerDefinitionPath, l as isShellScript, m as isComposeDefinitionPath, n as githubAutomationFileKind, p as containerDocumentImages, s as isMiseConfigPath, v as cloudClaudeSettingsPath, w as generateCloudSetup, x as hasCloudSessionStartHook, y as cloudSessionStartFix } from "./toolchain-inputs.mjs";
|
|
3
3
|
import { t as readGitTrackedOutput } from "./git-tracked-output.mjs";
|
|
4
|
-
import { t as repositoryBun } from "./toolchain-bun.mjs";
|
|
5
4
|
import { a as assertConsumerFixtureSelection, g as parseConsumerFixtures, h as discoverConsumerPackages, m as discoverConsumerManifests, r as assertConsumerFixtureKind, t as assertConsumerNodeSupport, u as consumerPackageClosure } from "./consumer-node-support.mjs";
|
|
6
5
|
import { d as rustImageVersion, i as dependabotRules, n as declaredIgnoreConflicts, o as parseDependabotIgnores, t as checkDependabot, u as canonicalDockerRuntime } from "./toolchain-dependabot.mjs";
|
|
7
6
|
import { n as checkFormatter, r as formatterRule } from "./toolchain-schema.mjs";
|
|
8
7
|
import { createRequire } from "node:module";
|
|
9
8
|
import path, { extname } from "node:path";
|
|
10
|
-
import { closeSync, lstatSync, openSync, readFileSync, readSync, realpathSync } from "node:fs";
|
|
11
9
|
import { parse } from "smol-toml";
|
|
10
|
+
import { closeSync, lstatSync, openSync, readFileSync, readSync, realpathSync } from "node:fs";
|
|
11
|
+
import picomatch from "picomatch";
|
|
12
12
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
13
|
-
import { minVersion, satisfies, valid, validRange } from "semver";
|
|
13
|
+
import { gte, minVersion, satisfies, valid, validRange } from "semver";
|
|
14
14
|
import { LineCounter, isAlias, isMap, isNode, isScalar, isSeq, parseAllDocuments, parseDocument } from "yaml";
|
|
15
|
-
import
|
|
15
|
+
import { pathToFileURL } from "node:url";
|
|
16
|
+
//#region src/toolchain-bun.ts
|
|
17
|
+
const STABLE_RELEASE = /^\d+\.\d+\.\d+$/u;
|
|
18
|
+
/**
|
|
19
|
+
* The shared policy names the minimum Bun. A repository pins one exact
|
|
20
|
+
* release at or above it in its root packageManager, and every other Bun
|
|
21
|
+
* reference in the repository must equal that release.
|
|
22
|
+
*/
|
|
23
|
+
const repositoryBun = (floor, manifest) => {
|
|
24
|
+
let packageManager;
|
|
25
|
+
try {
|
|
26
|
+
const parsed = JSON.parse(manifest ?? "");
|
|
27
|
+
if (typeof parsed === "object" && parsed !== null) packageManager = Reflect.get(parsed, "packageManager");
|
|
28
|
+
} catch {}
|
|
29
|
+
const version = typeof packageManager === "string" ? /^bun@(.+)$/.exec(packageManager)?.[1] : void 0;
|
|
30
|
+
return version !== void 0 && STABLE_RELEASE.test(version) && valid(version) === version && gte(version, floor) ? version : floor;
|
|
31
|
+
};
|
|
16
32
|
//#endregion
|
|
17
33
|
//#region src/toolchain-consumer-checks.ts
|
|
18
|
-
const consumerRunnerVersion = "1.
|
|
34
|
+
const consumerRunnerVersion = "2.1.0";
|
|
19
35
|
const consumerCheckWorkflow = "stella/.github/.github/workflows/package-consumer-compat.yml";
|
|
20
36
|
const record$10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21
37
|
const repositoryDirectory = (value) => typeof value === "string" && value !== "" && !value.includes("\\") && !value.includes("${{") && !value.split("").some((character) => {
|
|
@@ -350,7 +366,71 @@ const packageRules = [
|
|
|
350
366
|
"typescript-layout",
|
|
351
367
|
"node-engine"
|
|
352
368
|
];
|
|
369
|
+
/** Compiler packages `bun check` replaces; an install needs a repository allowlist entry. */
|
|
370
|
+
const typescriptPackages = /* @__PURE__ */ new Set(["typescript", "@typescript/native"]);
|
|
371
|
+
const typecheckCommand = "bun check";
|
|
372
|
+
const typescriptGuidance = "type check with `bun check --no-pretty --all --project=<tsconfig>`, use tsrs for programs and type checkers, and oxc-parser for syntax-only reads";
|
|
373
|
+
const installSections = [
|
|
374
|
+
"dependencies",
|
|
375
|
+
"devDependencies",
|
|
376
|
+
"optionalDependencies"
|
|
377
|
+
];
|
|
378
|
+
const allowlistFile = "stll-toolchain.json";
|
|
353
379
|
const record$7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
380
|
+
/** `npm:typescript@7.0.2` installs a compiler under another name; returns its version. */
|
|
381
|
+
const typescriptAliasVersion = (spec) => {
|
|
382
|
+
if (typeof spec !== "string") return void 0;
|
|
383
|
+
const match = /^npm:(?:typescript|@typescript\/native)(?:@(.*))?$/.exec(spec);
|
|
384
|
+
return match === null ? void 0 : match[1] ?? "";
|
|
385
|
+
};
|
|
386
|
+
const exactTypescriptSpec = (spec) => {
|
|
387
|
+
if (typeof spec !== "string") return false;
|
|
388
|
+
const version = typescriptAliasVersion(spec) ?? spec;
|
|
389
|
+
return valid(version) === version;
|
|
390
|
+
};
|
|
391
|
+
const isTypescriptInstall = (name, value) => typescriptPackages.has(name) || typescriptAliasVersion(value) !== void 0;
|
|
392
|
+
const allowlistKeys = [
|
|
393
|
+
"workspace",
|
|
394
|
+
"name",
|
|
395
|
+
"spec",
|
|
396
|
+
"dependency",
|
|
397
|
+
"reason"
|
|
398
|
+
];
|
|
399
|
+
const allowlistFormat = "{\"workspace\": \"<package directory or .>\", \"name\": \"typescript\", \"spec\": \"<exact version>\", \"dependency\": \"<package that needs it>\", \"reason\": \"<why>\"}";
|
|
400
|
+
const workspaceManifest = (workspace) => workspace === "." ? "package.json" : `${workspace}/package.json`;
|
|
401
|
+
/** Each repository declares its own TypeScript installs; there is no shared list. */
|
|
402
|
+
const parseTypescriptAllowlist = (input) => {
|
|
403
|
+
if (input === void 0) return [];
|
|
404
|
+
if (!Array.isArray(input)) throw new Error(`typescriptAllowlist must be an array of entries like ${allowlistFormat}`);
|
|
405
|
+
const seen = /* @__PURE__ */ new Set();
|
|
406
|
+
return input.map((entry) => {
|
|
407
|
+
const shown = JSON.stringify(entry);
|
|
408
|
+
const text = (key) => {
|
|
409
|
+
const value = record$7(entry) ? entry[key] : void 0;
|
|
410
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
411
|
+
};
|
|
412
|
+
const workspace = text("workspace");
|
|
413
|
+
const name = text("name");
|
|
414
|
+
const spec = text("spec");
|
|
415
|
+
const dependency = text("dependency");
|
|
416
|
+
const reason = text("reason");
|
|
417
|
+
if (!record$7(entry) || Object.keys(entry).length !== allowlistKeys.length || workspace === void 0 || name === void 0 || spec === void 0 || dependency === void 0 || reason === void 0) throw new Error(`typescriptAllowlist entry ${shown} must have exactly workspace, name, spec, dependency and reason as nonempty strings: ${allowlistFormat}`);
|
|
418
|
+
if (workspace !== "." && (path.posix.normalize(workspace) !== workspace || workspace.startsWith("/") || workspace.endsWith("/") || workspace === ".." || workspace.startsWith("../"))) throw new Error(`typescriptAllowlist entry ${shown}: workspace must be "." for the root or a normalized relative package directory such as "apps/web"`);
|
|
419
|
+
if (!exactTypescriptSpec(spec)) throw new Error(`typescriptAllowlist entry ${shown}: spec must be the exact spec the manifest resolves to, such as "6.0.3" or "npm:typescript@6.0.3", not a range or catalog reference`);
|
|
420
|
+
if (!isTypescriptInstall(name, spec)) throw new Error(`typescriptAllowlist entry ${shown}: name must be typescript or @typescript/native, or an alias whose spec is npm:typescript@<version> or npm:@typescript/native@<version>`);
|
|
421
|
+
if (typescriptPackages.has(dependency) || dependency === name) throw new Error(`typescriptAllowlist entry ${shown}: dependency must name the package in that workspace that needs ${name}, not a TypeScript package`);
|
|
422
|
+
const key = `${workspace}\0${name}`;
|
|
423
|
+
if (seen.has(key)) throw new Error(`typescriptAllowlist declares ${name} for ${workspace} more than once; keep one entry`);
|
|
424
|
+
seen.add(key);
|
|
425
|
+
return {
|
|
426
|
+
workspace,
|
|
427
|
+
name,
|
|
428
|
+
spec,
|
|
429
|
+
dependency,
|
|
430
|
+
reason
|
|
431
|
+
};
|
|
432
|
+
});
|
|
433
|
+
};
|
|
354
434
|
const shellSubstitutions = (command) => {
|
|
355
435
|
const frames = [{
|
|
356
436
|
kind: "root",
|
|
@@ -876,7 +956,7 @@ const invokesCompiler = (words) => {
|
|
|
876
956
|
return tokens.slice(start).some((word, index) => compiler(word) || word === "bun" && tokens.at(skipOptions(start + index + 1, "bun")) === "check");
|
|
877
957
|
};
|
|
878
958
|
/** Validate manifests and catalog resolutions from a tracked-file snapshot. */
|
|
879
|
-
const checkPackageFiles = ({ files, policy }) => {
|
|
959
|
+
const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
880
960
|
const sources = new Map(Object.entries(files));
|
|
881
961
|
const diagnostics = [];
|
|
882
962
|
const pins = new Map(Object.entries({
|
|
@@ -992,16 +1072,29 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
992
1072
|
directory = path.posix.dirname(directory);
|
|
993
1073
|
}
|
|
994
1074
|
};
|
|
995
|
-
const
|
|
1075
|
+
const resolveThrough = (file, name, value) => {
|
|
996
1076
|
const visited = /* @__PURE__ */ new Set();
|
|
1077
|
+
const catalogs = [];
|
|
997
1078
|
while (typeof value === "string" && value.startsWith("catalog:")) {
|
|
998
|
-
if (visited.has(value)) return
|
|
1079
|
+
if (visited.has(value)) return {
|
|
1080
|
+
value: void 0,
|
|
1081
|
+
catalogs
|
|
1082
|
+
};
|
|
999
1083
|
visited.add(value);
|
|
1000
1084
|
const catalog = catalogFor(file, value.slice(8));
|
|
1001
|
-
|
|
1085
|
+
if (!record$7(catalog)) return {
|
|
1086
|
+
value: void 0,
|
|
1087
|
+
catalogs
|
|
1088
|
+
};
|
|
1089
|
+
catalogs.push(catalog);
|
|
1090
|
+
value = catalog[name];
|
|
1002
1091
|
}
|
|
1003
|
-
return
|
|
1092
|
+
return {
|
|
1093
|
+
value,
|
|
1094
|
+
catalogs
|
|
1095
|
+
};
|
|
1004
1096
|
};
|
|
1097
|
+
const resolve = (file, name, value) => resolveThrough(file, name, value).value;
|
|
1005
1098
|
const workspaceOwnerFor = (file) => {
|
|
1006
1099
|
const pnpm = pnpmWorkspaceFor(file);
|
|
1007
1100
|
if (pnpm !== void 0) return {
|
|
@@ -1040,88 +1133,49 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1040
1133
|
}));
|
|
1041
1134
|
return candidates.length === 1 && candidates.every(([, json]) => json["version"] === pin) ? "matched" : "mismatch";
|
|
1042
1135
|
};
|
|
1043
|
-
const
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
if (
|
|
1049
|
-
const
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
const compatibility = specifiers.get(layout.compatibilityPackage);
|
|
1059
|
-
return compatibility?.size === 1 && compatibility.has(layout.compatibilitySpecifier);
|
|
1060
|
-
}
|
|
1061
|
-
return policy.typescriptInstallLayouts.every((other) => other.compilerPackage === layout.compilerPackage || !specifiers.has(other.compilerPackage));
|
|
1062
|
-
});
|
|
1063
|
-
const peerRange = (value) => typeof value === "string" && valid(value) === null && validRange(value) !== null;
|
|
1064
|
-
const toolchainEntries = (file, json) => {
|
|
1065
|
-
const entries = [];
|
|
1066
|
-
for (const section of [
|
|
1067
|
-
"dependencies",
|
|
1068
|
-
"devDependencies",
|
|
1069
|
-
"optionalDependencies",
|
|
1070
|
-
"peerDependencies"
|
|
1071
|
-
]) {
|
|
1072
|
-
const dependencies = json[section];
|
|
1073
|
-
if (!record$7(dependencies)) continue;
|
|
1074
|
-
for (const [name, value] of Object.entries(dependencies)) {
|
|
1075
|
-
if (!tsSpecifiers.has(name)) continue;
|
|
1076
|
-
const resolved = resolve(file, name, value);
|
|
1077
|
-
if (section === "peerDependencies" && peerRange(resolved)) continue;
|
|
1078
|
-
entries.push({
|
|
1079
|
-
section,
|
|
1080
|
-
name,
|
|
1081
|
-
value: resolved
|
|
1082
|
-
});
|
|
1136
|
+
const allowed = new Map(typescriptAllowlist.map((entry) => [`${workspaceManifest(entry.workspace)}\0${entry.name}`, entry]));
|
|
1137
|
+
const allowedCatalogEntries = /* @__PURE__ */ new Map();
|
|
1138
|
+
for (const entry of typescriptAllowlist) {
|
|
1139
|
+
const file = workspaceManifest(entry.workspace);
|
|
1140
|
+
const json = manifests.get(file);
|
|
1141
|
+
if (json === void 0) continue;
|
|
1142
|
+
for (const section of installSections) {
|
|
1143
|
+
const entries = json[section];
|
|
1144
|
+
if (!record$7(entries) || !Object.hasOwn(entries, entry.name)) continue;
|
|
1145
|
+
const { value, catalogs } = resolveThrough(file, entry.name, entries[entry.name]);
|
|
1146
|
+
if (value !== entry.spec) continue;
|
|
1147
|
+
for (const catalog of catalogs) {
|
|
1148
|
+
const names = allowedCatalogEntries.get(catalog) ?? /* @__PURE__ */ new Set();
|
|
1149
|
+
names.add(entry.name);
|
|
1150
|
+
allowedCatalogEntries.set(catalog, names);
|
|
1083
1151
|
}
|
|
1084
1152
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
for (const { name, value } of entries) {
|
|
1090
|
-
const values = specifiers.get(name) ?? /* @__PURE__ */ new Set();
|
|
1091
|
-
values.add(value);
|
|
1092
|
-
specifiers.set(name, values);
|
|
1093
|
-
}
|
|
1094
|
-
return specifiers;
|
|
1153
|
+
}
|
|
1154
|
+
const shownSpec = (raw, value) => {
|
|
1155
|
+
if (typeof value !== "string") return `${String(raw)} (unresolved)`;
|
|
1156
|
+
return raw === value ? value : `${value} (from ${String(raw)})`;
|
|
1095
1157
|
};
|
|
1096
|
-
const
|
|
1097
|
-
const
|
|
1098
|
-
const
|
|
1099
|
-
if (
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
file: candidate,
|
|
1114
|
-
rootMembership: "implicit"
|
|
1115
|
-
})) continue;
|
|
1116
|
-
entries.push(...toolchainEntries(candidate, member).filter(({ section }) => section === "devDependencies"));
|
|
1158
|
+
const typescriptMessage = (name, value) => `${name} ${String(value)} is not needed: ${typescriptGuidance}`;
|
|
1159
|
+
const typescriptEntryMessage = ({ file, entries, name, raw, value, scope }) => {
|
|
1160
|
+
const shown = shownSpec(raw, value);
|
|
1161
|
+
if (scope === "catalog") return allowedCatalogEntries.get(entries)?.has(name) === true ? void 0 : `${name} ${shown} in a catalog of ${file} serves no allowed install: ${typescriptGuidance}; a catalog may hold ${name} only at the spec of a workspace entry in typescriptAllowlist in ${allowlistFile}`;
|
|
1162
|
+
if (scope === "resolution") return `${name} must not be overridden or resolved (${shown}): ${typescriptGuidance}; declare it in the workspace whose dependency needs it and add a typescriptAllowlist entry in ${allowlistFile}`;
|
|
1163
|
+
if (scope === "peer") return `${name} peer ${shown} is not a version range: ${typescriptGuidance}`;
|
|
1164
|
+
const entry = path.posix.basename(file) === "package.json" ? allowed.get(`${file}\0${name}`) : void 0;
|
|
1165
|
+
if (entry === void 0) {
|
|
1166
|
+
const workspace = path.posix.dirname(file);
|
|
1167
|
+
const suggestion = JSON.stringify({
|
|
1168
|
+
workspace,
|
|
1169
|
+
name,
|
|
1170
|
+
spec: exactTypescriptSpec(value) ? value : "<exact version>",
|
|
1171
|
+
dependency: "<package that needs it>",
|
|
1172
|
+
reason: "<why it needs this TypeScript>"
|
|
1173
|
+
});
|
|
1174
|
+
return `${name} ${shown} in ${file} is not allowed: ${typescriptGuidance}; if a dependency of this workspace needs it, add ${suggestion} to typescriptAllowlist in ${allowlistFile}`;
|
|
1117
1175
|
}
|
|
1118
|
-
|
|
1119
|
-
layout: layoutFor(specifierMap(entries)),
|
|
1120
|
-
directory: owner.directory
|
|
1121
|
-
};
|
|
1122
|
-
workspaceLayouts.set(key, result);
|
|
1123
|
-
return result;
|
|
1176
|
+
return value === entry.spec ? void 0 : `${name} in ${file} resolves to ${shown}, but typescriptAllowlist in ${allowlistFile} allows ${entry.spec} for ${entry.dependency}; declare ${entry.spec} or update the entry's spec`;
|
|
1124
1177
|
};
|
|
1178
|
+
const peerRange = (value) => typeof value === "string" && validRange(value) !== null;
|
|
1125
1179
|
for (const [file, json] of manifests) {
|
|
1126
1180
|
const manager = json["packageManager"];
|
|
1127
1181
|
if (manager !== void 0 && manager !== `bun@${policy.bun}`) add({
|
|
@@ -1131,18 +1185,25 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1131
1185
|
value: manager,
|
|
1132
1186
|
message: `packageManager Bun version must be ${policy.bun} (one exact release at or above the shared minimum, the same in every manifest), found ${JSON.stringify(manager)}`
|
|
1133
1187
|
});
|
|
1134
|
-
const checkEntries = (entries, location) => {
|
|
1188
|
+
const checkEntries = (entries, scope, location) => {
|
|
1135
1189
|
if (!record$7(entries)) return;
|
|
1136
1190
|
for (const [name, raw] of Object.entries(entries)) {
|
|
1137
1191
|
const value = resolve(file, name, raw);
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1192
|
+
if (isTypescriptInstall(name, value)) {
|
|
1193
|
+
const message = typescriptEntryMessage({
|
|
1194
|
+
file,
|
|
1195
|
+
entries,
|
|
1196
|
+
name,
|
|
1197
|
+
raw,
|
|
1198
|
+
value,
|
|
1199
|
+
scope
|
|
1200
|
+
});
|
|
1201
|
+
if (message !== void 0) add({
|
|
1141
1202
|
file,
|
|
1142
1203
|
rule: "typescript-layout",
|
|
1143
1204
|
key: location?.key ?? name,
|
|
1144
1205
|
value: location === void 0 ? raw : location.value,
|
|
1145
|
-
message
|
|
1206
|
+
message
|
|
1146
1207
|
});
|
|
1147
1208
|
continue;
|
|
1148
1209
|
}
|
|
@@ -1164,20 +1225,17 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1164
1225
|
});
|
|
1165
1226
|
}
|
|
1166
1227
|
};
|
|
1167
|
-
for (const key of [
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
"
|
|
1171
|
-
]) checkEntries(json[key]);
|
|
1172
|
-
if (record$7(json["peerDependencies"])) {
|
|
1173
|
-
for (const [name, value] of Object.entries(json["peerDependencies"])) if (tsSpecifiers.has(name) && !peerRange(resolve(file, name, value))) checkEntries({ [name]: value });
|
|
1228
|
+
for (const key of installSections) checkEntries(json[key], "install");
|
|
1229
|
+
if (record$7(json["peerDependencies"])) for (const [name, value] of Object.entries(json["peerDependencies"])) {
|
|
1230
|
+
const resolved = resolve(file, name, value);
|
|
1231
|
+
if (isTypescriptInstall(name, resolved) && !peerRange(resolved)) checkEntries({ [name]: value }, "peer");
|
|
1174
1232
|
}
|
|
1175
1233
|
for (const source of catalogSources(json)) {
|
|
1176
|
-
checkEntries(source["catalog"]);
|
|
1234
|
+
checkEntries(source["catalog"], "catalog");
|
|
1177
1235
|
if (!record$7(source["catalogs"])) continue;
|
|
1178
|
-
for (const catalog of Object.values(source["catalogs"])) checkEntries(catalog);
|
|
1236
|
+
for (const catalog of Object.values(source["catalogs"])) checkEntries(catalog, "catalog");
|
|
1179
1237
|
}
|
|
1180
|
-
const ownedNames = /* @__PURE__ */ new Set([...pins.keys(), ...
|
|
1238
|
+
const ownedNames = /* @__PURE__ */ new Set([...pins.keys(), ...typescriptPackages]);
|
|
1181
1239
|
const resolutionDependencies = {};
|
|
1182
1240
|
const resolutionRoot = manifests.get(path.posix.join(path.posix.dirname(file), "package.json"));
|
|
1183
1241
|
if (resolutionRoot !== void 0) for (const section of [
|
|
@@ -1189,32 +1247,6 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1189
1247
|
if (!record$7(entries)) continue;
|
|
1190
1248
|
for (const [name, value] of Object.entries(entries)) resolutionDependencies[name] = resolve(file, name, value);
|
|
1191
1249
|
}
|
|
1192
|
-
const affectedSpecifiers = /* @__PURE__ */ new Map();
|
|
1193
|
-
const owner = workspaceOwnerFor(file);
|
|
1194
|
-
for (const [candidate, member] of manifests) {
|
|
1195
|
-
if (path.posix.basename(candidate) !== "package.json") continue;
|
|
1196
|
-
const memberOwner = workspaceOwnerFor(candidate);
|
|
1197
|
-
if (!(candidate === path.posix.join(path.posix.dirname(file), "package.json")) && !(owner !== void 0 && owner.directory === path.posix.dirname(file) && memberOwner?.directory === owner.directory && memberOwner.source === owner.source && workspaceContains({
|
|
1198
|
-
directory: owner.directory,
|
|
1199
|
-
file: candidate,
|
|
1200
|
-
patterns: owner.patterns,
|
|
1201
|
-
rootMembership: "implicit"
|
|
1202
|
-
}))) continue;
|
|
1203
|
-
for (const section of [
|
|
1204
|
-
"dependencies",
|
|
1205
|
-
"devDependencies",
|
|
1206
|
-
"optionalDependencies"
|
|
1207
|
-
]) {
|
|
1208
|
-
const entries = member[section];
|
|
1209
|
-
if (!record$7(entries)) continue;
|
|
1210
|
-
for (const [name, value] of Object.entries(entries)) {
|
|
1211
|
-
if (!tsSpecifiers.has(name)) continue;
|
|
1212
|
-
const values = affectedSpecifiers.get(name) ?? /* @__PURE__ */ new Set();
|
|
1213
|
-
values.add(resolve(candidate, name, value));
|
|
1214
|
-
affectedSpecifiers.set(name, values);
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
1250
|
const checkResolutionMap = (entries, parent) => {
|
|
1219
1251
|
if (!record$7(entries)) return;
|
|
1220
1252
|
for (const [selector, replacement] of Object.entries(entries)) {
|
|
@@ -1222,11 +1254,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1222
1254
|
if (target === void 0) continue;
|
|
1223
1255
|
let pattern = target.split(/>\s*(?=[@A-Za-z_*]|$)/).at(-1)?.trim() ?? "";
|
|
1224
1256
|
const version = pattern.lastIndexOf("@");
|
|
1225
|
-
|
|
1226
|
-
if (version > pattern.lastIndexOf("/")) {
|
|
1227
|
-
qualifier = pattern.slice(version + 1).replace(/^npm:/, "");
|
|
1228
|
-
pattern = pattern.slice(0, version);
|
|
1229
|
-
}
|
|
1257
|
+
if (version > pattern.lastIndexOf("/")) pattern = pattern.slice(0, version);
|
|
1230
1258
|
const parts = pattern.split("/");
|
|
1231
1259
|
pattern = parts.at(-2)?.startsWith("@") ? parts.slice(-2).join("/") : parts.at(-1) ?? "";
|
|
1232
1260
|
if (pattern === "") {
|
|
@@ -1243,102 +1271,60 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1243
1271
|
checkResolutionMap(replacement, target);
|
|
1244
1272
|
continue;
|
|
1245
1273
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
const reference = replacement.slice(1);
|
|
1251
|
-
value = resolutionDependencies[reference];
|
|
1252
|
-
}
|
|
1253
|
-
const resolved = resolve(file, name, value);
|
|
1254
|
-
checkEntries({ [name]: value }, {
|
|
1255
|
-
key: selector,
|
|
1256
|
-
value: replacement
|
|
1257
|
-
});
|
|
1258
|
-
if (tsSpecifiers.has(name) && [...affectedSpecifiers.get(name) ?? []].some((specifier) => {
|
|
1259
|
-
if (resolved === specifier) return false;
|
|
1260
|
-
if (qualifier === void 0 || validRange(qualifier) === null) return true;
|
|
1261
|
-
if (typeof specifier !== "string") return false;
|
|
1262
|
-
const versionSpecifier = specifier.startsWith("npm:") ? specifier.slice(specifier.lastIndexOf("@") + 1) : specifier;
|
|
1263
|
-
const release = valid(versionSpecifier);
|
|
1264
|
-
return release !== null && satisfies(release, qualifier);
|
|
1265
|
-
}) && typeof resolved === "string" && tsSpecifiers.get(name)?.has(resolved)) add({
|
|
1266
|
-
file,
|
|
1267
|
-
rule: "typescript-layout",
|
|
1268
|
-
key: selector,
|
|
1269
|
-
value: replacement,
|
|
1270
|
-
message: `${name} resolution must preserve its declared TypeScript install specifier`
|
|
1271
|
-
});
|
|
1274
|
+
let value = replacement;
|
|
1275
|
+
if (typeof replacement === "string" && replacement.startsWith("$")) {
|
|
1276
|
+
const reference = replacement.slice(1);
|
|
1277
|
+
value = resolutionDependencies[reference];
|
|
1272
1278
|
}
|
|
1279
|
+
const location = {
|
|
1280
|
+
key: selector,
|
|
1281
|
+
value: replacement
|
|
1282
|
+
};
|
|
1283
|
+
const targets = [...ownedNames].filter((name) => picomatch(pattern)(name));
|
|
1284
|
+
for (const name of targets) checkEntries({ [name]: value }, "resolution", location);
|
|
1285
|
+
if (typescriptAliasVersion(value) !== void 0 && !targets.some((name) => isTypescriptInstall(name, value))) checkEntries({ [pattern]: value }, "resolution", location);
|
|
1273
1286
|
}
|
|
1274
1287
|
};
|
|
1275
1288
|
checkResolutionMap(json["overrides"]);
|
|
1276
1289
|
checkResolutionMap(json["resolutions"]);
|
|
1277
1290
|
if (record$7(json["pnpm"])) checkResolutionMap(json["pnpm"]["overrides"]);
|
|
1278
|
-
const
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
if (
|
|
1283
|
-
|
|
1284
|
-
const workspace = workspaceLayoutFor(file);
|
|
1285
|
-
const layout = workspace?.layout;
|
|
1286
|
-
if (workspace !== void 0 && layout !== void 0) {
|
|
1287
|
-
const expected = /* @__PURE__ */ new Map([[layout.compilerPackage, layout.compilerSpecifier]]);
|
|
1288
|
-
if (layout.type === "split-compatibility") expected.set(layout.compatibilityPackage, layout.compatibilitySpecifier);
|
|
1289
|
-
expected.set(policy.typescript6Compatibility.packageAlias, `npm:typescript@${policy.typescript6Compatibility.version}`);
|
|
1290
|
-
const developmentOnly = localEntries.every(({ section, name, value }) => section === "devDependencies" && expected.get(name) === value);
|
|
1291
|
-
const runtimeCompatibilityOnly = layout.type === "split-compatibility" && path.posix.dirname(file) !== workspace.directory && localEntries.every(({ section, name, value }) => (section === "dependencies" || section === "devDependencies" || section === "peerDependencies") && name === layout.compatibilityPackage && value === layout.compatibilitySpecifier);
|
|
1292
|
-
if (developmentOnly || runtimeCompatibilityOnly) selectedLayout = layout;
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
if (selectedLayout === void 0) add({
|
|
1291
|
+
const command = record$7(json["scripts"]) ? json["scripts"]["typecheck"] : void 0;
|
|
1292
|
+
if (typeof command === "string") for (const words of compilerCommandSegments(command)) {
|
|
1293
|
+
if (!invokesCompiler(words)) continue;
|
|
1294
|
+
const normalized = words.join(" ");
|
|
1295
|
+
if (!words.some((word) => word === "(" || word === ")") && shellSubstitutions(normalized).length === 0 && (normalized === typecheckCommand || normalized.startsWith(`${typecheckCommand} `))) continue;
|
|
1296
|
+
add({
|
|
1296
1297
|
file,
|
|
1297
1298
|
rule: "typescript-layout",
|
|
1298
|
-
key: "
|
|
1299
|
-
|
|
1299
|
+
key: "typecheck",
|
|
1300
|
+
value: command,
|
|
1301
|
+
message: `typecheck must run \`${typecheckCommand} --no-pretty --all --project=<tsconfig>\`, not a TypeScript compiler package`
|
|
1300
1302
|
});
|
|
1301
|
-
|
|
1302
|
-
if (selectedLayout !== void 0 && typeof command === "string") {
|
|
1303
|
-
const normalizedExpected = selectedLayout.typecheckCommand.replace(/\s+/g, " ").trim();
|
|
1304
|
-
for (const words of compilerCommandSegments(command)) {
|
|
1305
|
-
if (!invokesCompiler(words)) continue;
|
|
1306
|
-
const normalized = words.join(" ");
|
|
1307
|
-
if (!words.some((word) => word === "(" || word === ")") && shellSubstitutions(normalized).length === 0 && (normalized === normalizedExpected || normalized.startsWith(`${normalizedExpected} `))) continue;
|
|
1308
|
-
add({
|
|
1309
|
-
file,
|
|
1310
|
-
rule: "typescript-layout",
|
|
1311
|
-
key: "typecheck",
|
|
1312
|
-
value: command,
|
|
1313
|
-
message: `typecheck compiler invocation must use ${selectedLayout.typecheckCommand}`
|
|
1314
|
-
});
|
|
1315
|
-
break;
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1303
|
+
break;
|
|
1318
1304
|
}
|
|
1319
|
-
if (record$7(json["peerDependencies"])) {
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
const releases = /* @__PURE__ */ new Set();
|
|
1326
|
-
for (const layout of policy.typescriptInstallLayouts) {
|
|
1327
|
-
if (layout.compilerPackage !== name && !(layout.type === "split-compatibility" && layout.compatibilityPackage === name)) continue;
|
|
1328
|
-
const specifier = layout.compilerSpecifier;
|
|
1329
|
-
releases.add(specifier.startsWith("npm:") ? specifier.slice(specifier.lastIndexOf("@") + 1) : specifier);
|
|
1330
|
-
}
|
|
1331
|
-
if (name === policy.typescript6Compatibility.packageAlias) releases.add(policy.typescript6Compatibility.version);
|
|
1332
|
-
if (peerLayout?.type === "split-compatibility" && name === peerLayout.compatibilityPackage) releases.add(peerLayout.compatibilitySpecifier);
|
|
1333
|
-
if ([...releases].every((release) => satisfies(release, range))) continue;
|
|
1305
|
+
if (record$7(json["peerDependencies"])) for (const [name, raw] of Object.entries(json["peerDependencies"])) {
|
|
1306
|
+
if (!typescriptPackages.has(name)) continue;
|
|
1307
|
+
const range = resolve(file, name, raw);
|
|
1308
|
+
if (!peerRange(range)) continue;
|
|
1309
|
+
const release = name === "typescript" ? policy.typescript : void 0;
|
|
1310
|
+
if (release === void 0) {
|
|
1334
1311
|
add({
|
|
1335
1312
|
file,
|
|
1336
1313
|
rule: "typescript-layout",
|
|
1337
1314
|
key: name,
|
|
1338
1315
|
value: raw,
|
|
1339
|
-
message:
|
|
1316
|
+
message: typescriptMessage(name, range)
|
|
1340
1317
|
});
|
|
1318
|
+
continue;
|
|
1341
1319
|
}
|
|
1320
|
+
if (satisfies(release, range)) continue;
|
|
1321
|
+
add({
|
|
1322
|
+
file,
|
|
1323
|
+
rule: "typescript-layout",
|
|
1324
|
+
key: name,
|
|
1325
|
+
value: raw,
|
|
1326
|
+
message: `${name} peer support range must include ${release}, found ${range}`
|
|
1327
|
+
});
|
|
1342
1328
|
}
|
|
1343
1329
|
if (record$7(json["engines"]) && json["engines"]["node"] !== void 0 && (typeof json["engines"]["node"] !== "string" || !nodeSupportRangeMatches(policy.node, json["engines"]["node"]))) add({
|
|
1344
1330
|
file,
|
|
@@ -1348,6 +1334,35 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1348
1334
|
message: `engines.node must support ${policy.node}, found ${JSON.stringify(json["engines"]["node"])}`
|
|
1349
1335
|
});
|
|
1350
1336
|
}
|
|
1337
|
+
const staleness = (entry) => {
|
|
1338
|
+
const file = workspaceManifest(entry.workspace);
|
|
1339
|
+
const json = manifests.get(file);
|
|
1340
|
+
if (json === void 0) return `${file} is missing or invalid; remove the entry`;
|
|
1341
|
+
const declares = (name) => installSections.some((section) => {
|
|
1342
|
+
const entries = json[section];
|
|
1343
|
+
return record$7(entries) && Object.hasOwn(entries, name);
|
|
1344
|
+
});
|
|
1345
|
+
if (!declares(entry.name)) return `${file} no longer declares ${entry.name} in dependencies, devDependencies or optionalDependencies; remove the entry`;
|
|
1346
|
+
if (!declares(entry.dependency)) return `${file} no longer depends on ${entry.dependency}; remove the entry and the ${entry.name} declaration, or name the dependency that still needs it`;
|
|
1347
|
+
};
|
|
1348
|
+
for (const entry of typescriptAllowlist) {
|
|
1349
|
+
const stale = staleness(entry);
|
|
1350
|
+
if (stale === void 0) continue;
|
|
1351
|
+
const message = `typescriptAllowlist entry ${entry.name} ${entry.spec} for ${entry.workspace} is stale: ${stale}`;
|
|
1352
|
+
if (sources.has(allowlistFile)) add({
|
|
1353
|
+
file: allowlistFile,
|
|
1354
|
+
rule: "typescript-layout",
|
|
1355
|
+
key: "workspace",
|
|
1356
|
+
value: entry.workspace,
|
|
1357
|
+
message
|
|
1358
|
+
});
|
|
1359
|
+
else diagnostics.push({
|
|
1360
|
+
rule: "typescript-layout",
|
|
1361
|
+
path: allowlistFile,
|
|
1362
|
+
line: 1,
|
|
1363
|
+
message
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1351
1366
|
return diagnostics;
|
|
1352
1367
|
};
|
|
1353
1368
|
const extensionKinds = new Map(Object.entries({
|
|
@@ -1737,6 +1752,64 @@ const readCommand = ({ root, executable, args, runtime }) => {
|
|
|
1737
1752
|
if (result.status !== 0) throw new Error(`configuration discovery failed: ${(result.stderr + result.stdout).trim()}`);
|
|
1738
1753
|
return result.stdout;
|
|
1739
1754
|
};
|
|
1755
|
+
const projectConfigScript = `
|
|
1756
|
+
const [moduleUrl, root, config] = process.argv.slice(1);
|
|
1757
|
+
const { API } = await import(moduleUrl);
|
|
1758
|
+
const api = new API({ cwd: root });
|
|
1759
|
+
try {
|
|
1760
|
+
const parsed = await api.parseConfigFile(config);
|
|
1761
|
+
// An empty files list (TS18002) or no inputs (TS18003) leaves a project
|
|
1762
|
+
// without roots; only unreadable configuration fails discovery.
|
|
1763
|
+
const errors = parsed.errors.filter((error) => error.code !== 18002 && error.code !== 18003);
|
|
1764
|
+
if (errors.length > 0) {
|
|
1765
|
+
process.stderr.write(errors.map((error) => "TS" + error.code + ": " + (error.text ?? error.messageText ?? "")).join("\\n"));
|
|
1766
|
+
process.exitCode = 1;
|
|
1767
|
+
} else
|
|
1768
|
+
process.stdout.write(JSON.stringify({
|
|
1769
|
+
files: parsed.fileNames,
|
|
1770
|
+
references: (parsed.projectReferences ?? []).map((reference) => reference.path),
|
|
1771
|
+
allowJs: parsed.options.allowJs === true,
|
|
1772
|
+
checkJs: parsed.options.checkJs === true,
|
|
1773
|
+
}));
|
|
1774
|
+
} finally {
|
|
1775
|
+
await api.close();
|
|
1776
|
+
}
|
|
1777
|
+
`;
|
|
1778
|
+
const resolveProjectApi = (root) => {
|
|
1779
|
+
for (const base of [import.meta.url, path.join(root, "package.json")]) try {
|
|
1780
|
+
return pathToFileURL(createRequire(base).resolve("tsrs/unstable/async")).href;
|
|
1781
|
+
} catch (error) {
|
|
1782
|
+
const code = record$4(error) ? error["code"] : void 0;
|
|
1783
|
+
if (code !== "MODULE_NOT_FOUND" && code !== "ERR_MODULE_NOT_FOUND") throw error;
|
|
1784
|
+
}
|
|
1785
|
+
throw new Error("configuration discovery needs tsrs: reinstall @stll/oxlint-config, which depends on it");
|
|
1786
|
+
};
|
|
1787
|
+
const readProjectConfig = (root, config) => {
|
|
1788
|
+
const api = resolveProjectApi(root);
|
|
1789
|
+
const result = spawnSync("node", [
|
|
1790
|
+
"--input-type=module",
|
|
1791
|
+
"-e",
|
|
1792
|
+
projectConfigScript,
|
|
1793
|
+
api,
|
|
1794
|
+
root,
|
|
1795
|
+
path.resolve(root, config)
|
|
1796
|
+
], {
|
|
1797
|
+
cwd: root,
|
|
1798
|
+
encoding: "utf8",
|
|
1799
|
+
timeout: 3e4,
|
|
1800
|
+
maxBuffer: 33554432
|
|
1801
|
+
});
|
|
1802
|
+
if (result.error !== void 0) throw result.error;
|
|
1803
|
+
if (result.status !== 0) throw new Error(`configuration discovery failed: ${(result.stderr + result.stdout).trim()}`);
|
|
1804
|
+
const parsed = JSON.parse(result.stdout);
|
|
1805
|
+
if (!record$4(parsed) || !stringList(parsed["files"]) || !stringList(parsed["references"]) || typeof parsed["allowJs"] !== "boolean" || typeof parsed["checkJs"] !== "boolean") throw new Error("compiler configuration must list files and references");
|
|
1806
|
+
return {
|
|
1807
|
+
files: parsed["files"],
|
|
1808
|
+
references: parsed["references"],
|
|
1809
|
+
allowJs: parsed["allowJs"],
|
|
1810
|
+
checkJs: parsed["checkJs"]
|
|
1811
|
+
};
|
|
1812
|
+
};
|
|
1740
1813
|
const relativePath = (root, file) => path.relative(root, path.resolve(root, file)).split(path.sep).join("/");
|
|
1741
1814
|
const lifecycleFile = (root, file, tracked) => {
|
|
1742
1815
|
try {
|
|
@@ -1835,7 +1908,6 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
1835
1908
|
try {
|
|
1836
1909
|
const require = createRequire(path.join(root, "package.json"));
|
|
1837
1910
|
const oxlint = path.join(path.dirname(require.resolve("oxlint/package.json")), "bin/oxlint");
|
|
1838
|
-
const compiler = path.join(path.dirname(require.resolve("typescript/package.json")), "bin/tsc");
|
|
1839
1911
|
const commands = inventory.commands.map((command) => ({
|
|
1840
1912
|
cwd: root,
|
|
1841
1913
|
...command
|
|
@@ -1897,32 +1969,16 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
1897
1969
|
references: []
|
|
1898
1970
|
};
|
|
1899
1971
|
try {
|
|
1900
|
-
const project =
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
args: [
|
|
1904
|
-
"--showConfig",
|
|
1905
|
-
"--project",
|
|
1906
|
-
config
|
|
1907
|
-
],
|
|
1908
|
-
runtime: "node"
|
|
1909
|
-
}));
|
|
1910
|
-
if (!record$4(project)) throw new Error("compiler configuration must be an object");
|
|
1911
|
-
const references = project["references"] ?? [];
|
|
1912
|
-
if (!Array.isArray(references)) throw new Error("compiler configuration references must be an array");
|
|
1913
|
-
for (const reference of references) {
|
|
1914
|
-
if (!record$4(reference) || typeof reference["path"] !== "string") throw new Error("compiler project reference requires a path");
|
|
1915
|
-
const target = relativePath(root, path.resolve(root, path.dirname(config), reference["path"]));
|
|
1972
|
+
const project = readProjectConfig(root, config);
|
|
1973
|
+
for (const reference of project.references) {
|
|
1974
|
+
const target = relativePath(root, reference);
|
|
1916
1975
|
const referencedConfig = target.endsWith(".json") ? target : path.posix.join(target, "tsconfig.json");
|
|
1917
1976
|
if (!trackedFiles.has(referencedConfig)) throw new Error(`project reference is not tracked: ${referencedConfig}`);
|
|
1918
1977
|
result.references.push(referencedConfig);
|
|
1919
1978
|
}
|
|
1920
|
-
const
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
for (const file of sources) {
|
|
1924
|
-
if (scriptSourceKind(file) === "javascript" && (!record$4(options) || options["allowJs"] !== true && options["checkJs"] !== true)) continue;
|
|
1925
|
-
result.files.add(relativePath(root, path.resolve(root, path.dirname(config), file)));
|
|
1979
|
+
for (const file of project.files) {
|
|
1980
|
+
if (scriptSourceKind(file) === "javascript" && !project.allowJs && !project.checkJs) continue;
|
|
1981
|
+
result.files.add(relativePath(root, file));
|
|
1926
1982
|
}
|
|
1927
1983
|
} catch (error) {
|
|
1928
1984
|
result.files.clear();
|
|
@@ -1970,8 +2026,11 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
1970
2026
|
//#endregion
|
|
1971
2027
|
//#region src/toolchain-rust.ts
|
|
1972
2028
|
const record$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1973
|
-
/**
|
|
1974
|
-
|
|
2029
|
+
/**
|
|
2030
|
+
* Exceptions authorize one immutable release, never a channel or an entire
|
|
2031
|
+
* rule. Expiry is scoped separately by checkRustExceptionExpiry.
|
|
2032
|
+
*/
|
|
2033
|
+
const parseRustExceptions = (input) => {
|
|
1975
2034
|
if (input === void 0) return [];
|
|
1976
2035
|
if (!Array.isArray(input)) throw new Error("rustExceptions must be an array");
|
|
1977
2036
|
const pins = /* @__PURE__ */ new Set();
|
|
@@ -1981,7 +2040,7 @@ const parseRustExceptions = (input, today = (/* @__PURE__ */ new Date()).toISOSt
|
|
|
1981
2040
|
"reason",
|
|
1982
2041
|
"tracking",
|
|
1983
2042
|
"expires"
|
|
1984
|
-
].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"] ||
|
|
2043
|
+
].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"] || pins.has(entry["pin"])) throw new Error("each Rust exception requires a unique exact stable pin, reason, GitHub issue and valid expiry date");
|
|
1985
2044
|
pins.add(entry["pin"]);
|
|
1986
2045
|
return {
|
|
1987
2046
|
pin: entry["pin"],
|
|
@@ -1991,6 +2050,30 @@ const parseRustExceptions = (input, today = (/* @__PURE__ */ new Date()).toISOSt
|
|
|
1991
2050
|
};
|
|
1992
2051
|
});
|
|
1993
2052
|
};
|
|
2053
|
+
/** Whether the base stll-toolchain.json declares this exception with the same expiry. */
|
|
2054
|
+
const declaresException = (baseText, { pin, expires }) => {
|
|
2055
|
+
const base = JSON.parse(baseText);
|
|
2056
|
+
const exceptions = record$3(base) ? base["rustExceptions"] : void 0;
|
|
2057
|
+
return Array.isArray(exceptions) && exceptions.some((entry) => record$3(entry) && entry["pin"] === pin && entry["expires"] === expires);
|
|
2058
|
+
};
|
|
2059
|
+
/** An exception expires on its date; the scope decides whether that fails this run. */
|
|
2060
|
+
const checkRustExceptionExpiry = ({ exceptions, now = /* @__PURE__ */ new Date(), scope }) => {
|
|
2061
|
+
const today = now.toISOString().slice(0, 10);
|
|
2062
|
+
const errors = [];
|
|
2063
|
+
const warnings = [];
|
|
2064
|
+
for (const exception of exceptions) {
|
|
2065
|
+
if (exception.expires > today) continue;
|
|
2066
|
+
const overdue = `stll-toolchain.json Rust exception ${exception.pin} expired on ${exception.expires}`;
|
|
2067
|
+
const verdict = overdueVerdict(scope, "stll-toolchain.json", (base) => declaresException(base, exception));
|
|
2068
|
+
if (!verdict.fail) warnings.push(`${overdue}; remove it or move to the shared Rust pin (default-branch and scheduled runs fail, as do changes that add or edit it)`);
|
|
2069
|
+
else if (verdict.unavailable === void 0) errors.push(`${overdue}; remove it or move to the shared Rust pin`);
|
|
2070
|
+
else errors.push(`${overdue}; cannot tell whether this change adds it: ${verdict.unavailable}`);
|
|
2071
|
+
}
|
|
2072
|
+
return {
|
|
2073
|
+
errors,
|
|
2074
|
+
warnings
|
|
2075
|
+
};
|
|
2076
|
+
};
|
|
1994
2077
|
/** Only executable fields are inspected: unrelated action inputs are not Rust selectors. */
|
|
1995
2078
|
const checkRustReferences = (options) => {
|
|
1996
2079
|
const { file, rust, exceptions } = options;
|
|
@@ -3893,7 +3976,7 @@ const record = (value) => typeof value === "object" && value !== null && !Array.
|
|
|
3893
3976
|
const array = (value) => Array.isArray(value);
|
|
3894
3977
|
/** Repository-owned declarations are explicit; malformed decisions fail closed. */
|
|
3895
3978
|
const parseToolchainConfiguration = (input) => {
|
|
3896
|
-
if (!record(input) || Object.keys(input).some((key) => key !== "optOuts" && key !== "cloud" && key !== "engineFloors" && key !== "dynamicSelectors" && key !== "consumerChecks" && key !== "rustExceptions" && key !== "dependabotIgnores") || input["optOuts"] !== void 0 && !array(input["optOuts"])) throw new Error("stll-toolchain.json accepts only optOuts, cloud, engineFloors, dynamicSelectors, consumerChecks, rustExceptions and
|
|
3979
|
+
if (!record(input) || Object.keys(input).some((key) => key !== "optOuts" && key !== "cloud" && key !== "engineFloors" && key !== "dynamicSelectors" && key !== "consumerChecks" && key !== "rustExceptions" && key !== "dependabotIgnores" && key !== "typescriptAllowlist") || input["optOuts"] !== void 0 && !array(input["optOuts"])) throw new Error("stll-toolchain.json accepts only optOuts, cloud, engineFloors, dynamicSelectors, consumerChecks, rustExceptions, dependabotIgnores and typescriptAllowlist declarations");
|
|
3897
3980
|
const disabled = /* @__PURE__ */ new Set();
|
|
3898
3981
|
const optOuts = input["optOuts"] ?? [];
|
|
3899
3982
|
if (!array(optOuts)) throw new Error("optOuts must be an array");
|
|
@@ -3914,7 +3997,8 @@ const parseToolchainConfiguration = (input) => {
|
|
|
3914
3997
|
engineFloors: parseEngineFloors(input["engineFloors"]),
|
|
3915
3998
|
dynamicSelectors: parseDynamicSelectors(input["dynamicSelectors"]),
|
|
3916
3999
|
consumerChecks: parseConsumerChecks(input["consumerChecks"]),
|
|
3917
|
-
dependabotIgnores: parseDependabotIgnores(input["dependabotIgnores"])
|
|
4000
|
+
dependabotIgnores: parseDependabotIgnores(input["dependabotIgnores"]),
|
|
4001
|
+
typescriptAllowlist: parseTypescriptAllowlist(input["typescriptAllowlist"])
|
|
3918
4002
|
};
|
|
3919
4003
|
};
|
|
3920
4004
|
/** Dependency graphs stay opaque unless a caller explicitly needs registry identities. */
|
|
@@ -3972,7 +4056,7 @@ const readToolchainInputs = (root, { bunLock = "presence" } = {}) => {
|
|
|
3972
4056
|
diagnostics
|
|
3973
4057
|
};
|
|
3974
4058
|
};
|
|
3975
|
-
const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, now }) => {
|
|
4059
|
+
const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, now, expiryScope = { kind: "enforce" } }) => {
|
|
3976
4060
|
const { files, trackedFiles, diagnostics } = readToolchainInputs(root, { bunLock: "content" });
|
|
3977
4061
|
const policy = {
|
|
3978
4062
|
...sharedPolicy,
|
|
@@ -4001,6 +4085,23 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4001
4085
|
if (files["stll-toolchain.json"] !== void 0) try {
|
|
4002
4086
|
configuration = parseToolchainConfiguration(JSON.parse(files["stll-toolchain.json"]));
|
|
4003
4087
|
dynamicSelectors = configuration.dynamicSelectors;
|
|
4088
|
+
const expiry = checkRustExceptionExpiry({
|
|
4089
|
+
exceptions: configuration.rustExceptions,
|
|
4090
|
+
now,
|
|
4091
|
+
scope: expiryScope
|
|
4092
|
+
});
|
|
4093
|
+
for (const message of expiry.errors) diagnostics.push({
|
|
4094
|
+
rule: "configuration",
|
|
4095
|
+
path: "stll-toolchain.json",
|
|
4096
|
+
line: 1,
|
|
4097
|
+
message
|
|
4098
|
+
});
|
|
4099
|
+
for (const message of expiry.warnings) onWarning?.({
|
|
4100
|
+
rule: "configuration",
|
|
4101
|
+
path: "stll-toolchain.json",
|
|
4102
|
+
line: 1,
|
|
4103
|
+
message
|
|
4104
|
+
});
|
|
4004
4105
|
for (const entry of configuration.engineFloors) engineFloors.push(resolveEngineFloor(entry, files));
|
|
4005
4106
|
} catch (error) {
|
|
4006
4107
|
diagnostics.push({
|
|
@@ -4065,7 +4166,8 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4065
4166
|
}));
|
|
4066
4167
|
diagnostics.push(...checkPackageFiles({
|
|
4067
4168
|
files,
|
|
4068
|
-
policy
|
|
4169
|
+
policy,
|
|
4170
|
+
typescriptAllowlist: configuration.typescriptAllowlist
|
|
4069
4171
|
}));
|
|
4070
4172
|
const usesBun = Object.entries(files).some(([file, text]) => {
|
|
4071
4173
|
if (path.posix.basename(file) === "bun.lock") return true;
|
|
@@ -4087,7 +4189,8 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4087
4189
|
const result = checkRepositoryQuarantine({
|
|
4088
4190
|
files,
|
|
4089
4191
|
now,
|
|
4090
|
-
npmrcPresent: trackedFiles.has(".npmrc")
|
|
4192
|
+
npmrcPresent: trackedFiles.has(".npmrc"),
|
|
4193
|
+
expiry: expiryScope
|
|
4091
4194
|
});
|
|
4092
4195
|
for (const message of result.errors) diagnostics.push({
|
|
4093
4196
|
rule: "quarantine-policy",
|
|
@@ -4173,6 +4276,6 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4173
4276
|
return diagnostics.filter((diagnostic) => !configuration.disabled.has(diagnostic.rule));
|
|
4174
4277
|
};
|
|
4175
4278
|
//#endregion
|
|
4176
|
-
export { parseToolchainConfiguration as n, readToolchainInputs as r, checkToolchain as t };
|
|
4279
|
+
export { repositoryBun as i, parseToolchainConfiguration as n, readToolchainInputs as r, checkToolchain as t };
|
|
4177
4280
|
|
|
4178
4281
|
//# sourceMappingURL=toolchain-guard.mjs.map
|