@stll/oxlint-config 3.1.0 → 3.2.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 +27 -1
- package/dist/consumer-compat-cli.cjs +12 -6
- package/dist/consumer-compat-cli.cjs.map +1 -1
- package/dist/consumer-compat-cli.mjs +12 -6
- package/dist/consumer-compat-cli.mjs.map +1 -1
- package/dist/toolchain-guard.cjs +97 -6
- package/dist/toolchain-guard.cjs.map +1 -1
- package/dist/toolchain-guard.mjs +97 -6
- package/dist/toolchain-guard.mjs.map +1 -1
- package/package.json +1 -1
- package/toolchain.json +1 -1
package/dist/toolchain-guard.mjs
CHANGED
|
@@ -13,6 +13,8 @@ import { execFileSync, spawnSync } from "node:child_process";
|
|
|
13
13
|
import { gte, minVersion, satisfies, valid, validRange } from "semver";
|
|
14
14
|
import { LineCounter, isAlias, isMap, isNode, isScalar, isSeq, parseAllDocuments, parseDocument } from "yaml";
|
|
15
15
|
import { pathToFileURL } from "node:url";
|
|
16
|
+
import { parse as parse$3 } from "acorn";
|
|
17
|
+
import { transformSync } from "esbuild";
|
|
16
18
|
//#region src/toolchain-bun.ts
|
|
17
19
|
const STABLE_RELEASE = /^\d+\.\d+\.\d+$/u;
|
|
18
20
|
/**
|
|
@@ -31,7 +33,7 @@ const repositoryBun = (floor, manifest) => {
|
|
|
31
33
|
};
|
|
32
34
|
//#endregion
|
|
33
35
|
//#region src/toolchain-consumer-checks.ts
|
|
34
|
-
const consumerRunnerVersion = "3.
|
|
36
|
+
const consumerRunnerVersion = "3.2.0";
|
|
35
37
|
const consumerCheckWorkflow = "stella/.github/.github/workflows/package-consumer-compat.yml";
|
|
36
38
|
const record$11 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37
39
|
const repositoryDirectory = (value) => typeof value === "string" && value !== "" && !value.includes("\\") && !value.includes("${{") && !value.split("").some((character) => {
|
|
@@ -359,6 +361,38 @@ const engineFloorSelectorMatches = (value, floor) => {
|
|
|
359
361
|
return Number(value.split(".").at(0)) === floor.major && satisfies(value, floor.range);
|
|
360
362
|
};
|
|
361
363
|
//#endregion
|
|
364
|
+
//#region src/toolchain-typescript-source-api.ts
|
|
365
|
+
const readSourceApiInput = ({ root, source, tracked }) => {
|
|
366
|
+
if (!tracked.has(source)) return void 0;
|
|
367
|
+
const resolvedRoot = realpathSync(root);
|
|
368
|
+
const input = path.resolve(resolvedRoot, source);
|
|
369
|
+
try {
|
|
370
|
+
if (realpathSync(input) !== input || !lstatSync(input).isFile()) return void 0;
|
|
371
|
+
return readFileSync(input, "utf8");
|
|
372
|
+
} catch {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
const sourceApiImportProblem = ({ source, content, name }) => {
|
|
377
|
+
if (content === void 0) return "must be a tracked, readable regular source file without symlinks";
|
|
378
|
+
try {
|
|
379
|
+
const { code } = transformSync(content, {
|
|
380
|
+
sourcefile: source,
|
|
381
|
+
loader: source.endsWith("x") ? "tsx" : "ts",
|
|
382
|
+
format: "esm",
|
|
383
|
+
tsconfigRaw: { compilerOptions: { verbatimModuleSyntax: true } },
|
|
384
|
+
logLevel: "silent"
|
|
385
|
+
});
|
|
386
|
+
if (parse$3(code, {
|
|
387
|
+
ecmaVersion: "latest",
|
|
388
|
+
sourceType: "module"
|
|
389
|
+
}).body.some((node) => node.type === "ImportDeclaration" && node.source.value === name && node.specifiers.length > 0)) return void 0;
|
|
390
|
+
return `no longer has a static value import from ${name}; remove the declaration or name its current importer`;
|
|
391
|
+
} catch {
|
|
392
|
+
return "cannot parse the declared compiler API importer";
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
//#endregion
|
|
362
396
|
//#region src/toolchain-packages.ts
|
|
363
397
|
const packageRules = [
|
|
364
398
|
"bun-pins",
|
|
@@ -413,15 +447,37 @@ const parseTypescriptAllowlist = (input) => {
|
|
|
413
447
|
const name = text("name");
|
|
414
448
|
const spec = text("spec");
|
|
415
449
|
const dependency = text("dependency");
|
|
450
|
+
const sourceApi = record$8(entry) && entry["kind"] === "source-api";
|
|
451
|
+
const keys = sourceApi ? [
|
|
452
|
+
"workspace",
|
|
453
|
+
"name",
|
|
454
|
+
"spec",
|
|
455
|
+
"reason",
|
|
456
|
+
"kind",
|
|
457
|
+
"sources"
|
|
458
|
+
] : [...allowlistKeys, ...record$8(entry) && entry["kind"] === "dependency" ? ["kind"] : []];
|
|
416
459
|
const reason = text("reason");
|
|
417
|
-
if (!record$8(entry) || Object.keys(entry).length !==
|
|
460
|
+
if (!record$8(entry) || Object.keys(entry).length !== keys.length || Object.keys(entry).some((key) => !keys.includes(key)) || workspace === void 0 || name === void 0 || spec === void 0 || !sourceApi && 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
461
|
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
462
|
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
463
|
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`);
|
|
464
|
+
if (dependency !== void 0 && (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
465
|
const key = `${workspace}\0${name}`;
|
|
423
466
|
if (seen.has(key)) throw new Error(`typescriptAllowlist declares ${name} for ${workspace} more than once; keep one entry`);
|
|
424
467
|
seen.add(key);
|
|
468
|
+
if (sourceApi) {
|
|
469
|
+
const sources = entry["sources"];
|
|
470
|
+
if (!Array.isArray(sources) || sources.length === 0 || !sources.every((source) => typeof source === "string" && path.posix.normalize(source) === source && !source.startsWith("/") && source !== ".." && !source.startsWith("../") && !source.includes("\\") && !/[?*{}[\]]/.test(source) && /\.[cm]?[jt]sx?$/.test(source) && !/\.d\.[cm]?ts$/.test(source) && !source.split("/").some((part) => ["node_modules", "vendor"].includes(part))) || new Set(sources).size !== sources.length) throw new Error(`typescriptAllowlist source-api entry ${shown} requires unique normalized source file paths`);
|
|
471
|
+
return {
|
|
472
|
+
kind: "source-api",
|
|
473
|
+
workspace,
|
|
474
|
+
name,
|
|
475
|
+
spec,
|
|
476
|
+
reason,
|
|
477
|
+
sources
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
if (dependency === void 0) throw new Error("Missing TypeScript dependency owner");
|
|
425
481
|
return {
|
|
426
482
|
workspace,
|
|
427
483
|
name,
|
|
@@ -1173,7 +1229,7 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1173
1229
|
});
|
|
1174
1230
|
return `${name} ${shown} in ${file} is not allowed: ${typescriptGuidance}; if a dependency of this workspace needs it, add ${suggestion} to typescriptAllowlist in ${allowlistFile}`;
|
|
1175
1231
|
}
|
|
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`;
|
|
1232
|
+
return value === entry.spec ? void 0 : `${name} in ${file} resolves to ${shown}, but typescriptAllowlist in ${allowlistFile} allows ${entry.spec} for ${entry.kind === "source-api" ? "source API imports" : entry.dependency}; declare ${entry.spec} or update the entry's spec`;
|
|
1177
1233
|
};
|
|
1178
1234
|
const peerRange = (value) => typeof value === "string" && validRange(value) !== null;
|
|
1179
1235
|
for (const [file, json] of manifests) {
|
|
@@ -1343,6 +1399,20 @@ const checkPackageFiles = ({ files, policy, typescriptAllowlist }) => {
|
|
|
1343
1399
|
return record$8(entries) && Object.hasOwn(entries, name);
|
|
1344
1400
|
});
|
|
1345
1401
|
if (!declares(entry.name)) return `${file} no longer declares ${entry.name} in dependencies, devDependencies or optionalDependencies; remove the entry`;
|
|
1402
|
+
if (entry.kind === "source-api") {
|
|
1403
|
+
for (const source of entry.sources) {
|
|
1404
|
+
let owner = path.posix.dirname(source);
|
|
1405
|
+
while (owner !== "." && !manifests.has(`${owner}/package.json`)) owner = path.posix.dirname(owner);
|
|
1406
|
+
if (owner !== entry.workspace) return `${source} belongs to workspace ${owner}, not ${entry.workspace}`;
|
|
1407
|
+
const problem = sourceApiImportProblem({
|
|
1408
|
+
source,
|
|
1409
|
+
content: files[source],
|
|
1410
|
+
name: entry.name
|
|
1411
|
+
});
|
|
1412
|
+
if (problem !== void 0) return `${source}: ${problem}`;
|
|
1413
|
+
}
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1346
1416
|
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
1417
|
};
|
|
1348
1418
|
for (const entry of typescriptAllowlist) {
|
|
@@ -1415,11 +1485,15 @@ const yamlSettings = (file, manager) => {
|
|
|
1415
1485
|
if (!record$7(settings)) throw new Error(`${manager} lifecycle configuration must be a mapping`);
|
|
1416
1486
|
return settings;
|
|
1417
1487
|
};
|
|
1488
|
+
const pnpmLifecycleVersions = ["12.9.1", "12.15.0"];
|
|
1418
1489
|
const pnpmHooksEnabled = (manifest, context) => {
|
|
1419
1490
|
const declared = declaredManager(context.packageManager ?? manifest["packageManager"]);
|
|
1420
1491
|
const dependency = [manifest["dependencies"], manifest["devDependencies"]].flatMap((field) => record$7(field) ? [field["pnpm"]] : []).find((value) => typeof value === "string");
|
|
1421
|
-
|
|
1422
|
-
|
|
1492
|
+
const version = declared?.at(1) === "pnpm" ? declared.at(2) : dependency;
|
|
1493
|
+
if (!pnpmLifecycleVersions.some((supported) => version === supported)) throw new Error(`pnpm lifecycle discovery requires declared pnpm@${pnpmLifecycleVersions.join(" or pnpm@")}`);
|
|
1494
|
+
const settings = yamlSettings(context.pnpmWorkspace, "pnpm");
|
|
1495
|
+
if ("include" in settings) throw new Error("Delegated pnpm settings require explicit lifecycle discovery support");
|
|
1496
|
+
const enabled = settings["enablePrePostScripts"];
|
|
1423
1497
|
if (enabled === void 0) return true;
|
|
1424
1498
|
if (typeof enabled !== "boolean") throw new Error("pnpm enablePrePostScripts must be a literal boolean");
|
|
1425
1499
|
return enabled;
|
|
@@ -4184,6 +4258,23 @@ const checkToolchain = ({ root, policy: sharedPolicy, onDelegated, onWarning, no
|
|
|
4184
4258
|
message: error instanceof Error ? error.message : String(error)
|
|
4185
4259
|
});
|
|
4186
4260
|
}
|
|
4261
|
+
for (const entry of configuration.typescriptAllowlist) {
|
|
4262
|
+
if (entry.kind !== "source-api") continue;
|
|
4263
|
+
for (const source of entry.sources) {
|
|
4264
|
+
const content = readSourceApiInput({
|
|
4265
|
+
root,
|
|
4266
|
+
source,
|
|
4267
|
+
tracked: trackedFiles
|
|
4268
|
+
});
|
|
4269
|
+
if (content !== void 0) files[source] = content;
|
|
4270
|
+
else diagnostics.push({
|
|
4271
|
+
rule: "configuration",
|
|
4272
|
+
path: source,
|
|
4273
|
+
line: 1,
|
|
4274
|
+
message: "TypeScript source API evidence must be tracked, readable, regular and free of symlinks"
|
|
4275
|
+
});
|
|
4276
|
+
}
|
|
4277
|
+
}
|
|
4187
4278
|
const cloud = configuration.cloud;
|
|
4188
4279
|
const script = files[cloudSetupPath];
|
|
4189
4280
|
if (cloud === void 0 && trackedFiles.has(".agents/cloud-setup.sh")) diagnostics.push({
|