@stll/oxlint-config 1.9.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -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/dependabot.d.cts +0 -19
- package/dist/dependabot.d.mts +0 -19
- package/dist/git-tracked-output.cjs +1 -1
- package/dist/git-tracked-output.mjs +1 -1
- package/dist/publish-artifact-syntax.cjs +1 -1
- package/dist/publish-artifact-syntax.mjs +1 -1
- package/dist/publish-contract-cli.cjs +2 -2
- package/dist/publish-contract-cli.mjs +2 -2
- package/dist/quarantine-cli.cjs +3 -2
- 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/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-guard.cjs +351 -242
- package/dist/toolchain-guard.cjs.map +1 -1
- package/dist/toolchain-guard.mjs +347 -244
- package/dist/toolchain-guard.mjs.map +1 -1
- package/dist/toolchain-inputs.cjs +13 -40
- package/dist/toolchain-inputs.cjs.map +1 -1
- package/dist/toolchain-inputs.mjs +13 -40
- package/dist/toolchain-inputs.mjs.map +1 -1
- package/dist/toolchain-schema.cjs +2 -44
- package/dist/toolchain-schema.cjs.map +1 -1
- package/dist/toolchain-schema.mjs +2 -44
- 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 +3 -4
- package/toolchain.json +2 -28
- 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.d.cts +0 -1
- package/dist/typecheck-parity-cli.d.mts +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/toolchain-guard.cjs
CHANGED
|
@@ -2,23 +2,39 @@ const require_rolldown_runtime = require("./rolldown-runtime.cjs");
|
|
|
2
2
|
const require_quarantine_policy = require("./quarantine-policy2.cjs");
|
|
3
3
|
const require_toolchain_inputs = require("./toolchain-inputs.cjs");
|
|
4
4
|
const require_git_tracked_output = require("./git-tracked-output.cjs");
|
|
5
|
-
const require_toolchain_bun = require("./toolchain-bun.cjs");
|
|
6
5
|
const require_consumer_node_support = require("./consumer-node-support.cjs");
|
|
7
6
|
const require_toolchain_dependabot = require("./toolchain-dependabot.cjs");
|
|
8
7
|
const require_toolchain_schema = require("./toolchain-schema.cjs");
|
|
9
8
|
let node_path = require("node:path");
|
|
10
9
|
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
10
|
+
let node_child_process = require("node:child_process");
|
|
11
11
|
let node_fs = require("node:fs");
|
|
12
12
|
let smol_toml = require("smol-toml");
|
|
13
|
-
let node_child_process = require("node:child_process");
|
|
14
13
|
let semver = require("semver");
|
|
15
14
|
let yaml = require("yaml");
|
|
16
15
|
let picomatch = require("picomatch");
|
|
17
16
|
picomatch = require_rolldown_runtime.__toESM(picomatch, 1);
|
|
18
17
|
let node_module = require("node:module");
|
|
18
|
+
let node_url = require("node:url");
|
|
19
|
+
//#region src/toolchain-bun.ts
|
|
20
|
+
const STABLE_RELEASE = /^\d+\.\d+\.\d+$/u;
|
|
21
|
+
/**
|
|
22
|
+
* The shared policy names the minimum Bun. A repository pins one exact
|
|
23
|
+
* release at or above it in its root packageManager, and every other Bun
|
|
24
|
+
* reference in the repository must equal that release.
|
|
25
|
+
*/
|
|
26
|
+
const repositoryBun = (floor, manifest) => {
|
|
27
|
+
let packageManager;
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(manifest ?? "");
|
|
30
|
+
if (typeof parsed === "object" && parsed !== null) packageManager = Reflect.get(parsed, "packageManager");
|
|
31
|
+
} catch {}
|
|
32
|
+
const version = typeof packageManager === "string" ? /^bun@(.+)$/.exec(packageManager)?.[1] : void 0;
|
|
33
|
+
return version !== void 0 && STABLE_RELEASE.test(version) && (0, semver.valid)(version) === version && (0, semver.gte)(version, floor) ? version : floor;
|
|
34
|
+
};
|
|
19
35
|
//#endregion
|
|
20
36
|
//#region src/toolchain-consumer-checks.ts
|
|
21
|
-
const consumerRunnerVersion = "
|
|
37
|
+
const consumerRunnerVersion = "2.0.0";
|
|
22
38
|
const consumerCheckWorkflow = "stella/.github/.github/workflows/package-consumer-compat.yml";
|
|
23
39
|
const record$10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24
40
|
const repositoryDirectory = (value) => typeof value === "string" && value !== "" && !value.includes("\\") && !value.includes("${{") && !value.split("").some((character) => {
|
|
@@ -353,7 +369,71 @@ const packageRules = [
|
|
|
353
369
|
"typescript-layout",
|
|
354
370
|
"node-engine"
|
|
355
371
|
];
|
|
372
|
+
/** Compiler packages `bun check` replaces; an install needs a repository allowlist entry. */
|
|
373
|
+
const typescriptPackages = /* @__PURE__ */ new Set(["typescript", "@typescript/native"]);
|
|
374
|
+
const typecheckCommand = "bun check";
|
|
375
|
+
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";
|
|
376
|
+
const installSections = [
|
|
377
|
+
"dependencies",
|
|
378
|
+
"devDependencies",
|
|
379
|
+
"optionalDependencies"
|
|
380
|
+
];
|
|
381
|
+
const allowlistFile = "stll-toolchain.json";
|
|
356
382
|
const record$7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
383
|
+
/** `npm:typescript@7.0.2` installs a compiler under another name; returns its version. */
|
|
384
|
+
const typescriptAliasVersion = (spec) => {
|
|
385
|
+
if (typeof spec !== "string") return void 0;
|
|
386
|
+
const match = /^npm:(?:typescript|@typescript\/native)(?:@(.*))?$/.exec(spec);
|
|
387
|
+
return match === null ? void 0 : match[1] ?? "";
|
|
388
|
+
};
|
|
389
|
+
const exactTypescriptSpec = (spec) => {
|
|
390
|
+
if (typeof spec !== "string") return false;
|
|
391
|
+
const version = typescriptAliasVersion(spec) ?? spec;
|
|
392
|
+
return (0, semver.valid)(version) === version;
|
|
393
|
+
};
|
|
394
|
+
const isTypescriptInstall = (name, value) => typescriptPackages.has(name) || typescriptAliasVersion(value) !== void 0;
|
|
395
|
+
const allowlistKeys = [
|
|
396
|
+
"workspace",
|
|
397
|
+
"name",
|
|
398
|
+
"spec",
|
|
399
|
+
"dependency",
|
|
400
|
+
"reason"
|
|
401
|
+
];
|
|
402
|
+
const allowlistFormat = "{\"workspace\": \"<package directory or .>\", \"name\": \"typescript\", \"spec\": \"<exact version>\", \"dependency\": \"<package that needs it>\", \"reason\": \"<why>\"}";
|
|
403
|
+
const workspaceManifest = (workspace) => workspace === "." ? "package.json" : `${workspace}/package.json`;
|
|
404
|
+
/** Each repository declares its own TypeScript installs; there is no shared list. */
|
|
405
|
+
const parseTypescriptAllowlist = (input) => {
|
|
406
|
+
if (input === void 0) return [];
|
|
407
|
+
if (!Array.isArray(input)) throw new Error(`typescriptAllowlist must be an array of entries like ${allowlistFormat}`);
|
|
408
|
+
const seen = /* @__PURE__ */ new Set();
|
|
409
|
+
return input.map((entry) => {
|
|
410
|
+
const shown = JSON.stringify(entry);
|
|
411
|
+
const text = (key) => {
|
|
412
|
+
const value = record$7(entry) ? entry[key] : void 0;
|
|
413
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
414
|
+
};
|
|
415
|
+
const workspace = text("workspace");
|
|
416
|
+
const name = text("name");
|
|
417
|
+
const spec = text("spec");
|
|
418
|
+
const dependency = text("dependency");
|
|
419
|
+
const reason = text("reason");
|
|
420
|
+
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}`);
|
|
421
|
+
if (workspace !== "." && (node_path.default.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"`);
|
|
422
|
+
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`);
|
|
423
|
+
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>`);
|
|
424
|
+
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`);
|
|
425
|
+
const key = `${workspace}\0${name}`;
|
|
426
|
+
if (seen.has(key)) throw new Error(`typescriptAllowlist declares ${name} for ${workspace} more than once; keep one entry`);
|
|
427
|
+
seen.add(key);
|
|
428
|
+
return {
|
|
429
|
+
workspace,
|
|
430
|
+
name,
|
|
431
|
+
spec,
|
|
432
|
+
dependency,
|
|
433
|
+
reason
|
|
434
|
+
};
|
|
435
|
+
});
|
|
436
|
+
};
|
|
357
437
|
const shellSubstitutions = (command) => {
|
|
358
438
|
const frames = [{
|
|
359
439
|
kind: "root",
|
|
@@ -879,7 +959,7 @@ const invokesCompiler = (words) => {
|
|
|
879
959
|
return tokens.slice(start).some((word, index) => compiler(word) || word === "bun" && tokens.at(skipOptions(start + index + 1, "bun")) === "check");
|
|
880
960
|
};
|
|
881
961
|
/** Validate manifests and catalog resolutions from a tracked-file snapshot. */
|
|
882
|
-
const checkPackageFiles = ({ files, policy }) => {
|
|
962
|
+
const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
883
963
|
const sources = new Map(Object.entries(files));
|
|
884
964
|
const diagnostics = [];
|
|
885
965
|
const pins = new Map(Object.entries({
|
|
@@ -995,16 +1075,29 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
995
1075
|
directory = node_path.default.posix.dirname(directory);
|
|
996
1076
|
}
|
|
997
1077
|
};
|
|
998
|
-
const
|
|
1078
|
+
const resolveThrough = (file, name, value) => {
|
|
999
1079
|
const visited = /* @__PURE__ */ new Set();
|
|
1080
|
+
const catalogs = [];
|
|
1000
1081
|
while (typeof value === "string" && value.startsWith("catalog:")) {
|
|
1001
|
-
if (visited.has(value)) return
|
|
1082
|
+
if (visited.has(value)) return {
|
|
1083
|
+
value: void 0,
|
|
1084
|
+
catalogs
|
|
1085
|
+
};
|
|
1002
1086
|
visited.add(value);
|
|
1003
1087
|
const catalog = catalogFor(file, value.slice(8));
|
|
1004
|
-
|
|
1088
|
+
if (!record$7(catalog)) return {
|
|
1089
|
+
value: void 0,
|
|
1090
|
+
catalogs
|
|
1091
|
+
};
|
|
1092
|
+
catalogs.push(catalog);
|
|
1093
|
+
value = catalog[name];
|
|
1005
1094
|
}
|
|
1006
|
-
return
|
|
1095
|
+
return {
|
|
1096
|
+
value,
|
|
1097
|
+
catalogs
|
|
1098
|
+
};
|
|
1007
1099
|
};
|
|
1100
|
+
const resolve = (file, name, value) => resolveThrough(file, name, value).value;
|
|
1008
1101
|
const workspaceOwnerFor = (file) => {
|
|
1009
1102
|
const pnpm = pnpmWorkspaceFor(file);
|
|
1010
1103
|
if (pnpm !== void 0) return {
|
|
@@ -1043,88 +1136,49 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1043
1136
|
}));
|
|
1044
1137
|
return candidates.length === 1 && candidates.every(([, json]) => json["version"] === pin) ? "matched" : "mismatch";
|
|
1045
1138
|
};
|
|
1046
|
-
const
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
if (
|
|
1052
|
-
const
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
const compatibility = specifiers.get(layout.compatibilityPackage);
|
|
1062
|
-
return compatibility?.size === 1 && compatibility.has(layout.compatibilitySpecifier);
|
|
1063
|
-
}
|
|
1064
|
-
return policy.typescriptInstallLayouts.every((other) => other.compilerPackage === layout.compilerPackage || !specifiers.has(other.compilerPackage));
|
|
1065
|
-
});
|
|
1066
|
-
const peerRange = (value) => typeof value === "string" && (0, semver.valid)(value) === null && (0, semver.validRange)(value) !== null;
|
|
1067
|
-
const toolchainEntries = (file, json) => {
|
|
1068
|
-
const entries = [];
|
|
1069
|
-
for (const section of [
|
|
1070
|
-
"dependencies",
|
|
1071
|
-
"devDependencies",
|
|
1072
|
-
"optionalDependencies",
|
|
1073
|
-
"peerDependencies"
|
|
1074
|
-
]) {
|
|
1075
|
-
const dependencies = json[section];
|
|
1076
|
-
if (!record$7(dependencies)) continue;
|
|
1077
|
-
for (const [name, value] of Object.entries(dependencies)) {
|
|
1078
|
-
if (!tsSpecifiers.has(name)) continue;
|
|
1079
|
-
const resolved = resolve(file, name, value);
|
|
1080
|
-
if (section === "peerDependencies" && peerRange(resolved)) continue;
|
|
1081
|
-
entries.push({
|
|
1082
|
-
section,
|
|
1083
|
-
name,
|
|
1084
|
-
value: resolved
|
|
1085
|
-
});
|
|
1139
|
+
const allowed = new Map(typescriptAllowlist.map((entry) => [`${workspaceManifest(entry.workspace)}\0${entry.name}`, entry]));
|
|
1140
|
+
const allowedCatalogEntries = /* @__PURE__ */ new Map();
|
|
1141
|
+
for (const entry of typescriptAllowlist) {
|
|
1142
|
+
const file = workspaceManifest(entry.workspace);
|
|
1143
|
+
const json = manifests.get(file);
|
|
1144
|
+
if (json === void 0) continue;
|
|
1145
|
+
for (const section of installSections) {
|
|
1146
|
+
const entries = json[section];
|
|
1147
|
+
if (!record$7(entries) || !Object.hasOwn(entries, entry.name)) continue;
|
|
1148
|
+
const { value, catalogs } = resolveThrough(file, entry.name, entries[entry.name]);
|
|
1149
|
+
if (value !== entry.spec) continue;
|
|
1150
|
+
for (const catalog of catalogs) {
|
|
1151
|
+
const names = allowedCatalogEntries.get(catalog) ?? /* @__PURE__ */ new Set();
|
|
1152
|
+
names.add(entry.name);
|
|
1153
|
+
allowedCatalogEntries.set(catalog, names);
|
|
1086
1154
|
}
|
|
1087
1155
|
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
for (const { name, value } of entries) {
|
|
1093
|
-
const values = specifiers.get(name) ?? /* @__PURE__ */ new Set();
|
|
1094
|
-
values.add(value);
|
|
1095
|
-
specifiers.set(name, values);
|
|
1096
|
-
}
|
|
1097
|
-
return specifiers;
|
|
1156
|
+
}
|
|
1157
|
+
const shownSpec = (raw, value) => {
|
|
1158
|
+
if (typeof value !== "string") return `${String(raw)} (unresolved)`;
|
|
1159
|
+
return raw === value ? value : `${value} (from ${String(raw)})`;
|
|
1098
1160
|
};
|
|
1099
|
-
const
|
|
1100
|
-
const
|
|
1101
|
-
const
|
|
1102
|
-
if (
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
file: candidate,
|
|
1117
|
-
rootMembership: "implicit"
|
|
1118
|
-
})) continue;
|
|
1119
|
-
entries.push(...toolchainEntries(candidate, member).filter(({ section }) => section === "devDependencies"));
|
|
1161
|
+
const typescriptMessage = (name, value) => `${name} ${String(value)} is not needed: ${typescriptGuidance}`;
|
|
1162
|
+
const typescriptEntryMessage = ({ file, entries, name, raw, value, scope }) => {
|
|
1163
|
+
const shown = shownSpec(raw, value);
|
|
1164
|
+
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}`;
|
|
1165
|
+
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}`;
|
|
1166
|
+
if (scope === "peer") return `${name} peer ${shown} is not a version range: ${typescriptGuidance}`;
|
|
1167
|
+
const entry = node_path.default.posix.basename(file) === "package.json" ? allowed.get(`${file}\0${name}`) : void 0;
|
|
1168
|
+
if (entry === void 0) {
|
|
1169
|
+
const workspace = node_path.default.posix.dirname(file);
|
|
1170
|
+
const suggestion = JSON.stringify({
|
|
1171
|
+
workspace,
|
|
1172
|
+
name,
|
|
1173
|
+
spec: exactTypescriptSpec(value) ? value : "<exact version>",
|
|
1174
|
+
dependency: "<package that needs it>",
|
|
1175
|
+
reason: "<why it needs this TypeScript>"
|
|
1176
|
+
});
|
|
1177
|
+
return `${name} ${shown} in ${file} is not allowed: ${typescriptGuidance}; if a dependency of this workspace needs it, add ${suggestion} to typescriptAllowlist in ${allowlistFile}`;
|
|
1120
1178
|
}
|
|
1121
|
-
|
|
1122
|
-
layout: layoutFor(specifierMap(entries)),
|
|
1123
|
-
directory: owner.directory
|
|
1124
|
-
};
|
|
1125
|
-
workspaceLayouts.set(key, result);
|
|
1126
|
-
return result;
|
|
1179
|
+
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`;
|
|
1127
1180
|
};
|
|
1181
|
+
const peerRange = (value) => typeof value === "string" && (0, semver.validRange)(value) !== null;
|
|
1128
1182
|
for (const [file, json] of manifests) {
|
|
1129
1183
|
const manager = json["packageManager"];
|
|
1130
1184
|
if (manager !== void 0 && manager !== `bun@${policy.bun}`) add({
|
|
@@ -1134,18 +1188,25 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1134
1188
|
value: manager,
|
|
1135
1189
|
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)}`
|
|
1136
1190
|
});
|
|
1137
|
-
const checkEntries = (entries, location) => {
|
|
1191
|
+
const checkEntries = (entries, scope, location) => {
|
|
1138
1192
|
if (!record$7(entries)) return;
|
|
1139
1193
|
for (const [name, raw] of Object.entries(entries)) {
|
|
1140
1194
|
const value = resolve(file, name, raw);
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1195
|
+
if (isTypescriptInstall(name, value)) {
|
|
1196
|
+
const message = typescriptEntryMessage({
|
|
1197
|
+
file,
|
|
1198
|
+
entries,
|
|
1199
|
+
name,
|
|
1200
|
+
raw,
|
|
1201
|
+
value,
|
|
1202
|
+
scope
|
|
1203
|
+
});
|
|
1204
|
+
if (message !== void 0) add({
|
|
1144
1205
|
file,
|
|
1145
1206
|
rule: "typescript-layout",
|
|
1146
1207
|
key: location?.key ?? name,
|
|
1147
1208
|
value: location === void 0 ? raw : location.value,
|
|
1148
|
-
message
|
|
1209
|
+
message
|
|
1149
1210
|
});
|
|
1150
1211
|
continue;
|
|
1151
1212
|
}
|
|
@@ -1167,20 +1228,17 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1167
1228
|
});
|
|
1168
1229
|
}
|
|
1169
1230
|
};
|
|
1170
|
-
for (const key of [
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
"
|
|
1174
|
-
]) checkEntries(json[key]);
|
|
1175
|
-
if (record$7(json["peerDependencies"])) {
|
|
1176
|
-
for (const [name, value] of Object.entries(json["peerDependencies"])) if (tsSpecifiers.has(name) && !peerRange(resolve(file, name, value))) checkEntries({ [name]: value });
|
|
1231
|
+
for (const key of installSections) checkEntries(json[key], "install");
|
|
1232
|
+
if (record$7(json["peerDependencies"])) for (const [name, value] of Object.entries(json["peerDependencies"])) {
|
|
1233
|
+
const resolved = resolve(file, name, value);
|
|
1234
|
+
if (isTypescriptInstall(name, resolved) && !peerRange(resolved)) checkEntries({ [name]: value }, "peer");
|
|
1177
1235
|
}
|
|
1178
1236
|
for (const source of catalogSources(json)) {
|
|
1179
|
-
checkEntries(source["catalog"]);
|
|
1237
|
+
checkEntries(source["catalog"], "catalog");
|
|
1180
1238
|
if (!record$7(source["catalogs"])) continue;
|
|
1181
|
-
for (const catalog of Object.values(source["catalogs"])) checkEntries(catalog);
|
|
1239
|
+
for (const catalog of Object.values(source["catalogs"])) checkEntries(catalog, "catalog");
|
|
1182
1240
|
}
|
|
1183
|
-
const ownedNames = /* @__PURE__ */ new Set([...pins.keys(), ...
|
|
1241
|
+
const ownedNames = /* @__PURE__ */ new Set([...pins.keys(), ...typescriptPackages]);
|
|
1184
1242
|
const resolutionDependencies = {};
|
|
1185
1243
|
const resolutionRoot = manifests.get(node_path.default.posix.join(node_path.default.posix.dirname(file), "package.json"));
|
|
1186
1244
|
if (resolutionRoot !== void 0) for (const section of [
|
|
@@ -1192,32 +1250,6 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1192
1250
|
if (!record$7(entries)) continue;
|
|
1193
1251
|
for (const [name, value] of Object.entries(entries)) resolutionDependencies[name] = resolve(file, name, value);
|
|
1194
1252
|
}
|
|
1195
|
-
const affectedSpecifiers = /* @__PURE__ */ new Map();
|
|
1196
|
-
const owner = workspaceOwnerFor(file);
|
|
1197
|
-
for (const [candidate, member] of manifests) {
|
|
1198
|
-
if (node_path.default.posix.basename(candidate) !== "package.json") continue;
|
|
1199
|
-
const memberOwner = workspaceOwnerFor(candidate);
|
|
1200
|
-
if (!(candidate === node_path.default.posix.join(node_path.default.posix.dirname(file), "package.json")) && !(owner !== void 0 && owner.directory === node_path.default.posix.dirname(file) && memberOwner?.directory === owner.directory && memberOwner.source === owner.source && require_toolchain_inputs.workspaceContains({
|
|
1201
|
-
directory: owner.directory,
|
|
1202
|
-
file: candidate,
|
|
1203
|
-
patterns: owner.patterns,
|
|
1204
|
-
rootMembership: "implicit"
|
|
1205
|
-
}))) continue;
|
|
1206
|
-
for (const section of [
|
|
1207
|
-
"dependencies",
|
|
1208
|
-
"devDependencies",
|
|
1209
|
-
"optionalDependencies"
|
|
1210
|
-
]) {
|
|
1211
|
-
const entries = member[section];
|
|
1212
|
-
if (!record$7(entries)) continue;
|
|
1213
|
-
for (const [name, value] of Object.entries(entries)) {
|
|
1214
|
-
if (!tsSpecifiers.has(name)) continue;
|
|
1215
|
-
const values = affectedSpecifiers.get(name) ?? /* @__PURE__ */ new Set();
|
|
1216
|
-
values.add(resolve(candidate, name, value));
|
|
1217
|
-
affectedSpecifiers.set(name, values);
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
1253
|
const checkResolutionMap = (entries, parent) => {
|
|
1222
1254
|
if (!record$7(entries)) return;
|
|
1223
1255
|
for (const [selector, replacement] of Object.entries(entries)) {
|
|
@@ -1225,11 +1257,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1225
1257
|
if (target === void 0) continue;
|
|
1226
1258
|
let pattern = target.split(/>\s*(?=[@A-Za-z_*]|$)/).at(-1)?.trim() ?? "";
|
|
1227
1259
|
const version = pattern.lastIndexOf("@");
|
|
1228
|
-
|
|
1229
|
-
if (version > pattern.lastIndexOf("/")) {
|
|
1230
|
-
qualifier = pattern.slice(version + 1).replace(/^npm:/, "");
|
|
1231
|
-
pattern = pattern.slice(0, version);
|
|
1232
|
-
}
|
|
1260
|
+
if (version > pattern.lastIndexOf("/")) pattern = pattern.slice(0, version);
|
|
1233
1261
|
const parts = pattern.split("/");
|
|
1234
1262
|
pattern = parts.at(-2)?.startsWith("@") ? parts.slice(-2).join("/") : parts.at(-1) ?? "";
|
|
1235
1263
|
if (pattern === "") {
|
|
@@ -1246,102 +1274,60 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1246
1274
|
checkResolutionMap(replacement, target);
|
|
1247
1275
|
continue;
|
|
1248
1276
|
}
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
const reference = replacement.slice(1);
|
|
1254
|
-
value = resolutionDependencies[reference];
|
|
1255
|
-
}
|
|
1256
|
-
const resolved = resolve(file, name, value);
|
|
1257
|
-
checkEntries({ [name]: value }, {
|
|
1258
|
-
key: selector,
|
|
1259
|
-
value: replacement
|
|
1260
|
-
});
|
|
1261
|
-
if (tsSpecifiers.has(name) && [...affectedSpecifiers.get(name) ?? []].some((specifier) => {
|
|
1262
|
-
if (resolved === specifier) return false;
|
|
1263
|
-
if (qualifier === void 0 || (0, semver.validRange)(qualifier) === null) return true;
|
|
1264
|
-
if (typeof specifier !== "string") return false;
|
|
1265
|
-
const versionSpecifier = specifier.startsWith("npm:") ? specifier.slice(specifier.lastIndexOf("@") + 1) : specifier;
|
|
1266
|
-
const release = (0, semver.valid)(versionSpecifier);
|
|
1267
|
-
return release !== null && (0, semver.satisfies)(release, qualifier);
|
|
1268
|
-
}) && typeof resolved === "string" && tsSpecifiers.get(name)?.has(resolved)) add({
|
|
1269
|
-
file,
|
|
1270
|
-
rule: "typescript-layout",
|
|
1271
|
-
key: selector,
|
|
1272
|
-
value: replacement,
|
|
1273
|
-
message: `${name} resolution must preserve its declared TypeScript install specifier`
|
|
1274
|
-
});
|
|
1277
|
+
let value = replacement;
|
|
1278
|
+
if (typeof replacement === "string" && replacement.startsWith("$")) {
|
|
1279
|
+
const reference = replacement.slice(1);
|
|
1280
|
+
value = resolutionDependencies[reference];
|
|
1275
1281
|
}
|
|
1282
|
+
const location = {
|
|
1283
|
+
key: selector,
|
|
1284
|
+
value: replacement
|
|
1285
|
+
};
|
|
1286
|
+
const targets = [...ownedNames].filter((name) => (0, picomatch.default)(pattern)(name));
|
|
1287
|
+
for (const name of targets) checkEntries({ [name]: value }, "resolution", location);
|
|
1288
|
+
if (typescriptAliasVersion(value) !== void 0 && !targets.some((name) => isTypescriptInstall(name, value))) checkEntries({ [pattern]: value }, "resolution", location);
|
|
1276
1289
|
}
|
|
1277
1290
|
};
|
|
1278
1291
|
checkResolutionMap(json["overrides"]);
|
|
1279
1292
|
checkResolutionMap(json["resolutions"]);
|
|
1280
1293
|
if (record$7(json["pnpm"])) checkResolutionMap(json["pnpm"]["overrides"]);
|
|
1281
|
-
const
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
if (
|
|
1286
|
-
|
|
1287
|
-
const workspace = workspaceLayoutFor(file);
|
|
1288
|
-
const layout = workspace?.layout;
|
|
1289
|
-
if (workspace !== void 0 && layout !== void 0) {
|
|
1290
|
-
const expected = /* @__PURE__ */ new Map([[layout.compilerPackage, layout.compilerSpecifier]]);
|
|
1291
|
-
if (layout.type === "split-compatibility") expected.set(layout.compatibilityPackage, layout.compatibilitySpecifier);
|
|
1292
|
-
expected.set(policy.typescript6Compatibility.packageAlias, `npm:typescript@${policy.typescript6Compatibility.version}`);
|
|
1293
|
-
const developmentOnly = localEntries.every(({ section, name, value }) => section === "devDependencies" && expected.get(name) === value);
|
|
1294
|
-
const runtimeCompatibilityOnly = layout.type === "split-compatibility" && node_path.default.posix.dirname(file) !== workspace.directory && localEntries.every(({ section, name, value }) => (section === "dependencies" || section === "devDependencies" || section === "peerDependencies") && name === layout.compatibilityPackage && value === layout.compatibilitySpecifier);
|
|
1295
|
-
if (developmentOnly || runtimeCompatibilityOnly) selectedLayout = layout;
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
if (selectedLayout === void 0) add({
|
|
1294
|
+
const command = record$7(json["scripts"]) ? json["scripts"]["typecheck"] : void 0;
|
|
1295
|
+
if (typeof command === "string") for (const words of compilerCommandSegments(command)) {
|
|
1296
|
+
if (!invokesCompiler(words)) continue;
|
|
1297
|
+
const normalized = words.join(" ");
|
|
1298
|
+
if (!words.some((word) => word === "(" || word === ")") && shellSubstitutions(normalized).length === 0 && (normalized === typecheckCommand || normalized.startsWith(`${typecheckCommand} `))) continue;
|
|
1299
|
+
add({
|
|
1299
1300
|
file,
|
|
1300
1301
|
rule: "typescript-layout",
|
|
1301
|
-
key: "
|
|
1302
|
-
|
|
1302
|
+
key: "typecheck",
|
|
1303
|
+
value: command,
|
|
1304
|
+
message: `typecheck must run \`${typecheckCommand} --no-pretty --all --project=<tsconfig>\`, not a TypeScript compiler package`
|
|
1303
1305
|
});
|
|
1304
|
-
|
|
1305
|
-
if (selectedLayout !== void 0 && typeof command === "string") {
|
|
1306
|
-
const normalizedExpected = selectedLayout.typecheckCommand.replace(/\s+/g, " ").trim();
|
|
1307
|
-
for (const words of compilerCommandSegments(command)) {
|
|
1308
|
-
if (!invokesCompiler(words)) continue;
|
|
1309
|
-
const normalized = words.join(" ");
|
|
1310
|
-
if (!words.some((word) => word === "(" || word === ")") && shellSubstitutions(normalized).length === 0 && (normalized === normalizedExpected || normalized.startsWith(`${normalizedExpected} `))) continue;
|
|
1311
|
-
add({
|
|
1312
|
-
file,
|
|
1313
|
-
rule: "typescript-layout",
|
|
1314
|
-
key: "typecheck",
|
|
1315
|
-
value: command,
|
|
1316
|
-
message: `typecheck compiler invocation must use ${selectedLayout.typecheckCommand}`
|
|
1317
|
-
});
|
|
1318
|
-
break;
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1306
|
+
break;
|
|
1321
1307
|
}
|
|
1322
|
-
if (record$7(json["peerDependencies"])) {
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
const releases = /* @__PURE__ */ new Set();
|
|
1329
|
-
for (const layout of policy.typescriptInstallLayouts) {
|
|
1330
|
-
if (layout.compilerPackage !== name && !(layout.type === "split-compatibility" && layout.compatibilityPackage === name)) continue;
|
|
1331
|
-
const specifier = layout.compilerSpecifier;
|
|
1332
|
-
releases.add(specifier.startsWith("npm:") ? specifier.slice(specifier.lastIndexOf("@") + 1) : specifier);
|
|
1333
|
-
}
|
|
1334
|
-
if (name === policy.typescript6Compatibility.packageAlias) releases.add(policy.typescript6Compatibility.version);
|
|
1335
|
-
if (peerLayout?.type === "split-compatibility" && name === peerLayout.compatibilityPackage) releases.add(peerLayout.compatibilitySpecifier);
|
|
1336
|
-
if ([...releases].every((release) => (0, semver.satisfies)(release, range))) continue;
|
|
1308
|
+
if (record$7(json["peerDependencies"])) for (const [name, raw] of Object.entries(json["peerDependencies"])) {
|
|
1309
|
+
if (!typescriptPackages.has(name)) continue;
|
|
1310
|
+
const range = resolve(file, name, raw);
|
|
1311
|
+
if (!peerRange(range)) continue;
|
|
1312
|
+
const release = name === "typescript" ? policy.typescript : void 0;
|
|
1313
|
+
if (release === void 0) {
|
|
1337
1314
|
add({
|
|
1338
1315
|
file,
|
|
1339
1316
|
rule: "typescript-layout",
|
|
1340
1317
|
key: name,
|
|
1341
1318
|
value: raw,
|
|
1342
|
-
message:
|
|
1319
|
+
message: typescriptMessage(name, range)
|
|
1343
1320
|
});
|
|
1321
|
+
continue;
|
|
1344
1322
|
}
|
|
1323
|
+
if ((0, semver.satisfies)(release, range)) continue;
|
|
1324
|
+
add({
|
|
1325
|
+
file,
|
|
1326
|
+
rule: "typescript-layout",
|
|
1327
|
+
key: name,
|
|
1328
|
+
value: raw,
|
|
1329
|
+
message: `${name} peer support range must include ${release}, found ${range}`
|
|
1330
|
+
});
|
|
1345
1331
|
}
|
|
1346
1332
|
if (record$7(json["engines"]) && json["engines"]["node"] !== void 0 && (typeof json["engines"]["node"] !== "string" || !require_toolchain_inputs.nodeSupportRangeMatches(policy.node, json["engines"]["node"]))) add({
|
|
1347
1333
|
file,
|
|
@@ -1351,6 +1337,35 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1351
1337
|
message: `engines.node must support ${policy.node}, found ${JSON.stringify(json["engines"]["node"])}`
|
|
1352
1338
|
});
|
|
1353
1339
|
}
|
|
1340
|
+
const staleness = (entry) => {
|
|
1341
|
+
const file = workspaceManifest(entry.workspace);
|
|
1342
|
+
const json = manifests.get(file);
|
|
1343
|
+
if (json === void 0) return `${file} is missing or invalid; remove the entry`;
|
|
1344
|
+
const declares = (name) => installSections.some((section) => {
|
|
1345
|
+
const entries = json[section];
|
|
1346
|
+
return record$7(entries) && Object.hasOwn(entries, name);
|
|
1347
|
+
});
|
|
1348
|
+
if (!declares(entry.name)) return `${file} no longer declares ${entry.name} in dependencies, devDependencies or optionalDependencies; remove the entry`;
|
|
1349
|
+
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`;
|
|
1350
|
+
};
|
|
1351
|
+
for (const entry of typescriptAllowlist) {
|
|
1352
|
+
const stale = staleness(entry);
|
|
1353
|
+
if (stale === void 0) continue;
|
|
1354
|
+
const message = `typescriptAllowlist entry ${entry.name} ${entry.spec} for ${entry.workspace} is stale: ${stale}`;
|
|
1355
|
+
if (sources.has(allowlistFile)) add({
|
|
1356
|
+
file: allowlistFile,
|
|
1357
|
+
rule: "typescript-layout",
|
|
1358
|
+
key: "workspace",
|
|
1359
|
+
value: entry.workspace,
|
|
1360
|
+
message
|
|
1361
|
+
});
|
|
1362
|
+
else diagnostics.push({
|
|
1363
|
+
rule: "typescript-layout",
|
|
1364
|
+
path: allowlistFile,
|
|
1365
|
+
line: 1,
|
|
1366
|
+
message
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1354
1369
|
return diagnostics;
|
|
1355
1370
|
};
|
|
1356
1371
|
const extensionKinds = new Map(Object.entries({
|
|
@@ -1740,6 +1755,64 @@ const readCommand = ({ root, executable, args, runtime }) => {
|
|
|
1740
1755
|
if (result.status !== 0) throw new Error(`configuration discovery failed: ${(result.stderr + result.stdout).trim()}`);
|
|
1741
1756
|
return result.stdout;
|
|
1742
1757
|
};
|
|
1758
|
+
const projectConfigScript = `
|
|
1759
|
+
const [moduleUrl, root, config] = process.argv.slice(1);
|
|
1760
|
+
const { API } = await import(moduleUrl);
|
|
1761
|
+
const api = new API({ cwd: root });
|
|
1762
|
+
try {
|
|
1763
|
+
const parsed = await api.parseConfigFile(config);
|
|
1764
|
+
// An empty files list (TS18002) or no inputs (TS18003) leaves a project
|
|
1765
|
+
// without roots; only unreadable configuration fails discovery.
|
|
1766
|
+
const errors = parsed.errors.filter((error) => error.code !== 18002 && error.code !== 18003);
|
|
1767
|
+
if (errors.length > 0) {
|
|
1768
|
+
process.stderr.write(errors.map((error) => "TS" + error.code + ": " + (error.text ?? error.messageText ?? "")).join("\\n"));
|
|
1769
|
+
process.exitCode = 1;
|
|
1770
|
+
} else
|
|
1771
|
+
process.stdout.write(JSON.stringify({
|
|
1772
|
+
files: parsed.fileNames,
|
|
1773
|
+
references: (parsed.projectReferences ?? []).map((reference) => reference.path),
|
|
1774
|
+
allowJs: parsed.options.allowJs === true,
|
|
1775
|
+
checkJs: parsed.options.checkJs === true,
|
|
1776
|
+
}));
|
|
1777
|
+
} finally {
|
|
1778
|
+
await api.close();
|
|
1779
|
+
}
|
|
1780
|
+
`;
|
|
1781
|
+
const resolveProjectApi = (root) => {
|
|
1782
|
+
for (const base of [require("url").pathToFileURL(__filename).href, node_path.default.join(root, "package.json")]) try {
|
|
1783
|
+
return (0, node_url.pathToFileURL)((0, node_module.createRequire)(base).resolve("tsrs/unstable/async")).href;
|
|
1784
|
+
} catch (error) {
|
|
1785
|
+
const code = record$4(error) ? error["code"] : void 0;
|
|
1786
|
+
if (code !== "MODULE_NOT_FOUND" && code !== "ERR_MODULE_NOT_FOUND") throw error;
|
|
1787
|
+
}
|
|
1788
|
+
throw new Error("configuration discovery needs tsrs: reinstall @stll/oxlint-config, which depends on it");
|
|
1789
|
+
};
|
|
1790
|
+
const readProjectConfig = (root, config) => {
|
|
1791
|
+
const api = resolveProjectApi(root);
|
|
1792
|
+
const result = (0, node_child_process.spawnSync)("node", [
|
|
1793
|
+
"--input-type=module",
|
|
1794
|
+
"-e",
|
|
1795
|
+
projectConfigScript,
|
|
1796
|
+
api,
|
|
1797
|
+
root,
|
|
1798
|
+
node_path.default.resolve(root, config)
|
|
1799
|
+
], {
|
|
1800
|
+
cwd: root,
|
|
1801
|
+
encoding: "utf8",
|
|
1802
|
+
timeout: 3e4,
|
|
1803
|
+
maxBuffer: 33554432
|
|
1804
|
+
});
|
|
1805
|
+
if (result.error !== void 0) throw result.error;
|
|
1806
|
+
if (result.status !== 0) throw new Error(`configuration discovery failed: ${(result.stderr + result.stdout).trim()}`);
|
|
1807
|
+
const parsed = JSON.parse(result.stdout);
|
|
1808
|
+
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");
|
|
1809
|
+
return {
|
|
1810
|
+
files: parsed["files"],
|
|
1811
|
+
references: parsed["references"],
|
|
1812
|
+
allowJs: parsed["allowJs"],
|
|
1813
|
+
checkJs: parsed["checkJs"]
|
|
1814
|
+
};
|
|
1815
|
+
};
|
|
1743
1816
|
const relativePath = (root, file) => node_path.default.relative(root, node_path.default.resolve(root, file)).split(node_path.default.sep).join("/");
|
|
1744
1817
|
const lifecycleFile = (root, file, tracked) => {
|
|
1745
1818
|
try {
|
|
@@ -1838,7 +1911,6 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
1838
1911
|
try {
|
|
1839
1912
|
const require$1 = (0, node_module.createRequire)(node_path.default.join(root, "package.json"));
|
|
1840
1913
|
const oxlint = node_path.default.join(node_path.default.dirname(require$1.resolve("oxlint/package.json")), "bin/oxlint");
|
|
1841
|
-
const compiler = node_path.default.join(node_path.default.dirname(require$1.resolve("typescript/package.json")), "bin/tsc");
|
|
1842
1914
|
const commands = inventory.commands.map((command) => ({
|
|
1843
1915
|
cwd: root,
|
|
1844
1916
|
...command
|
|
@@ -1900,32 +1972,16 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
1900
1972
|
references: []
|
|
1901
1973
|
};
|
|
1902
1974
|
try {
|
|
1903
|
-
const project =
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
args: [
|
|
1907
|
-
"--showConfig",
|
|
1908
|
-
"--project",
|
|
1909
|
-
config
|
|
1910
|
-
],
|
|
1911
|
-
runtime: "node"
|
|
1912
|
-
}));
|
|
1913
|
-
if (!record$4(project)) throw new Error("compiler configuration must be an object");
|
|
1914
|
-
const references = project["references"] ?? [];
|
|
1915
|
-
if (!Array.isArray(references)) throw new Error("compiler configuration references must be an array");
|
|
1916
|
-
for (const reference of references) {
|
|
1917
|
-
if (!record$4(reference) || typeof reference["path"] !== "string") throw new Error("compiler project reference requires a path");
|
|
1918
|
-
const target = relativePath(root, node_path.default.resolve(root, node_path.default.dirname(config), reference["path"]));
|
|
1975
|
+
const project = readProjectConfig(root, config);
|
|
1976
|
+
for (const reference of project.references) {
|
|
1977
|
+
const target = relativePath(root, reference);
|
|
1919
1978
|
const referencedConfig = target.endsWith(".json") ? target : node_path.default.posix.join(target, "tsconfig.json");
|
|
1920
1979
|
if (!trackedFiles.has(referencedConfig)) throw new Error(`project reference is not tracked: ${referencedConfig}`);
|
|
1921
1980
|
result.references.push(referencedConfig);
|
|
1922
1981
|
}
|
|
1923
|
-
const
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
for (const file of sources) {
|
|
1927
|
-
if (scriptSourceKind(file) === "javascript" && (!record$4(options) || options["allowJs"] !== true && options["checkJs"] !== true)) continue;
|
|
1928
|
-
result.files.add(relativePath(root, node_path.default.resolve(root, node_path.default.dirname(config), file)));
|
|
1982
|
+
for (const file of project.files) {
|
|
1983
|
+
if (scriptSourceKind(file) === "javascript" && !project.allowJs && !project.checkJs) continue;
|
|
1984
|
+
result.files.add(relativePath(root, file));
|
|
1929
1985
|
}
|
|
1930
1986
|
} catch (error) {
|
|
1931
1987
|
result.files.clear();
|
|
@@ -1973,8 +2029,11 @@ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
|
|
|
1973
2029
|
//#endregion
|
|
1974
2030
|
//#region src/toolchain-rust.ts
|
|
1975
2031
|
const record$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1976
|
-
/**
|
|
1977
|
-
|
|
2032
|
+
/**
|
|
2033
|
+
* Exceptions authorize one immutable release, never a channel or an entire
|
|
2034
|
+
* rule. Expiry is scoped separately by checkRustExceptionExpiry.
|
|
2035
|
+
*/
|
|
2036
|
+
const parseRustExceptions = (input) => {
|
|
1978
2037
|
if (input === void 0) return [];
|
|
1979
2038
|
if (!Array.isArray(input)) throw new Error("rustExceptions must be an array");
|
|
1980
2039
|
const pins = /* @__PURE__ */ new Set();
|
|
@@ -1984,7 +2043,7 @@ const parseRustExceptions = (input, today = (/* @__PURE__ */ new Date()).toISOSt
|
|
|
1984
2043
|
"reason",
|
|
1985
2044
|
"tracking",
|
|
1986
2045
|
"expires"
|
|
1987
|
-
].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"] ||
|
|
2046
|
+
].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");
|
|
1988
2047
|
pins.add(entry["pin"]);
|
|
1989
2048
|
return {
|
|
1990
2049
|
pin: entry["pin"],
|
|
@@ -1994,6 +2053,30 @@ const parseRustExceptions = (input, today = (/* @__PURE__ */ new Date()).toISOSt
|
|
|
1994
2053
|
};
|
|
1995
2054
|
});
|
|
1996
2055
|
};
|
|
2056
|
+
/** Whether the base stll-toolchain.json declares this exception with the same expiry. */
|
|
2057
|
+
const declaresException = (baseText, { pin, expires }) => {
|
|
2058
|
+
const base = JSON.parse(baseText);
|
|
2059
|
+
const exceptions = record$3(base) ? base["rustExceptions"] : void 0;
|
|
2060
|
+
return Array.isArray(exceptions) && exceptions.some((entry) => record$3(entry) && entry["pin"] === pin && entry["expires"] === expires);
|
|
2061
|
+
};
|
|
2062
|
+
/** An exception expires on its date; the scope decides whether that fails this run. */
|
|
2063
|
+
const checkRustExceptionExpiry = ({ exceptions, now = /* @__PURE__ */ new Date(), scope }) => {
|
|
2064
|
+
const today = now.toISOString().slice(0, 10);
|
|
2065
|
+
const errors = [];
|
|
2066
|
+
const warnings = [];
|
|
2067
|
+
for (const exception of exceptions) {
|
|
2068
|
+
if (exception.expires > today) continue;
|
|
2069
|
+
const overdue = `stll-toolchain.json Rust exception ${exception.pin} expired on ${exception.expires}`;
|
|
2070
|
+
const verdict = require_quarantine_policy.overdueVerdict(scope, "stll-toolchain.json", (base) => declaresException(base, exception));
|
|
2071
|
+
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)`);
|
|
2072
|
+
else if (verdict.unavailable === void 0) errors.push(`${overdue}; remove it or move to the shared Rust pin`);
|
|
2073
|
+
else errors.push(`${overdue}; cannot tell whether this change adds it: ${verdict.unavailable}`);
|
|
2074
|
+
}
|
|
2075
|
+
return {
|
|
2076
|
+
errors,
|
|
2077
|
+
warnings
|
|
2078
|
+
};
|
|
2079
|
+
};
|
|
1997
2080
|
/** Only executable fields are inspected: unrelated action inputs are not Rust selectors. */
|
|
1998
2081
|
const checkRustReferences = (options) => {
|
|
1999
2082
|
const { file, rust, exceptions } = options;
|
|
@@ -3896,7 +3979,7 @@ const record = (value) => typeof value === "object" && value !== null && !Array.
|
|
|
3896
3979
|
const array = (value) => Array.isArray(value);
|
|
3897
3980
|
/** Repository-owned declarations are explicit; malformed decisions fail closed. */
|
|
3898
3981
|
const parseToolchainConfiguration = (input) => {
|
|
3899
|
-
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
|
|
3982
|
+
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");
|
|
3900
3983
|
const disabled = /* @__PURE__ */ new Set();
|
|
3901
3984
|
const optOuts = input["optOuts"] ?? [];
|
|
3902
3985
|
if (!array(optOuts)) throw new Error("optOuts must be an array");
|
|
@@ -3917,7 +4000,8 @@ const parseToolchainConfiguration = (input) => {
|
|
|
3917
4000
|
engineFloors: parseEngineFloors(input["engineFloors"]),
|
|
3918
4001
|
dynamicSelectors: parseDynamicSelectors(input["dynamicSelectors"]),
|
|
3919
4002
|
consumerChecks: parseConsumerChecks(input["consumerChecks"]),
|
|
3920
|
-
dependabotIgnores: require_toolchain_dependabot.parseDependabotIgnores(input["dependabotIgnores"])
|
|
4003
|
+
dependabotIgnores: require_toolchain_dependabot.parseDependabotIgnores(input["dependabotIgnores"]),
|
|
4004
|
+
typescriptAllowlist: parseTypescriptAllowlist(input["typescriptAllowlist"])
|
|
3921
4005
|
};
|
|
3922
4006
|
};
|
|
3923
4007
|
/** Dependency graphs stay opaque unless a caller explicitly needs registry identities. */
|
|
@@ -3975,11 +4059,11 @@ const readToolchainInputs = (root, { bunLock = "presence" } = {}) => {
|
|
|
3975
4059
|
diagnostics
|
|
3976
4060
|
};
|
|
3977
4061
|
};
|
|
3978
|
-
const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, now }) => {
|
|
4062
|
+
const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, now, expiryScope = { kind: "enforce" } }) => {
|
|
3979
4063
|
const { files, trackedFiles, diagnostics } = readToolchainInputs(root, { bunLock: "content" });
|
|
3980
4064
|
const policy = {
|
|
3981
4065
|
...sharedPolicy,
|
|
3982
|
-
bun:
|
|
4066
|
+
bun: repositoryBun(sharedPolicy.bun, files["package.json"])
|
|
3983
4067
|
};
|
|
3984
4068
|
let repository;
|
|
3985
4069
|
try {
|
|
@@ -4004,6 +4088,23 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4004
4088
|
if (files["stll-toolchain.json"] !== void 0) try {
|
|
4005
4089
|
configuration = parseToolchainConfiguration(JSON.parse(files["stll-toolchain.json"]));
|
|
4006
4090
|
dynamicSelectors = configuration.dynamicSelectors;
|
|
4091
|
+
const expiry = checkRustExceptionExpiry({
|
|
4092
|
+
exceptions: configuration.rustExceptions,
|
|
4093
|
+
now,
|
|
4094
|
+
scope: expiryScope
|
|
4095
|
+
});
|
|
4096
|
+
for (const message of expiry.errors) diagnostics.push({
|
|
4097
|
+
rule: "configuration",
|
|
4098
|
+
path: "stll-toolchain.json",
|
|
4099
|
+
line: 1,
|
|
4100
|
+
message
|
|
4101
|
+
});
|
|
4102
|
+
for (const message of expiry.warnings) onWarning?.({
|
|
4103
|
+
rule: "configuration",
|
|
4104
|
+
path: "stll-toolchain.json",
|
|
4105
|
+
line: 1,
|
|
4106
|
+
message
|
|
4107
|
+
});
|
|
4007
4108
|
for (const entry of configuration.engineFloors) engineFloors.push(resolveEngineFloor(entry, files));
|
|
4008
4109
|
} catch (error) {
|
|
4009
4110
|
diagnostics.push({
|
|
@@ -4068,7 +4169,8 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4068
4169
|
}));
|
|
4069
4170
|
diagnostics.push(...checkPackageFiles({
|
|
4070
4171
|
files,
|
|
4071
|
-
policy
|
|
4172
|
+
policy,
|
|
4173
|
+
typescriptAllowlist: configuration.typescriptAllowlist
|
|
4072
4174
|
}));
|
|
4073
4175
|
const usesBun = Object.entries(files).some(([file, text]) => {
|
|
4074
4176
|
if (node_path.default.posix.basename(file) === "bun.lock") return true;
|
|
@@ -4090,7 +4192,8 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4090
4192
|
const result = require_quarantine_policy.checkRepositoryQuarantine({
|
|
4091
4193
|
files,
|
|
4092
4194
|
now,
|
|
4093
|
-
npmrcPresent: trackedFiles.has(".npmrc")
|
|
4195
|
+
npmrcPresent: trackedFiles.has(".npmrc"),
|
|
4196
|
+
expiry: expiryScope
|
|
4094
4197
|
});
|
|
4095
4198
|
for (const message of result.errors) diagnostics.push({
|
|
4096
4199
|
rule: "quarantine-policy",
|
|
@@ -4194,5 +4297,11 @@ Object.defineProperty(exports, "readToolchainInputs", {
|
|
|
4194
4297
|
return readToolchainInputs;
|
|
4195
4298
|
}
|
|
4196
4299
|
});
|
|
4300
|
+
Object.defineProperty(exports, "repositoryBun", {
|
|
4301
|
+
enumerable: true,
|
|
4302
|
+
get: function() {
|
|
4303
|
+
return repositoryBun;
|
|
4304
|
+
}
|
|
4305
|
+
});
|
|
4197
4306
|
|
|
4198
4307
|
//# sourceMappingURL=toolchain-guard.cjs.map
|