mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-15 03:12:36 +03:00
TypeScript 6.x raises TS5101 on `open-sse/tsconfig.json`: `baseUrl` is deprecated and stops functioning in TypeScript 7.0. It was paired with `ignoreDeprecations: "5.0"`, which no longer silences it under TS 6 (the compiler now demands "6.0"). Remove `baseUrl: ".."` and rewrite the `paths` mappings relative to the tsconfig's own directory, which is how TypeScript resolves them with no baseUrl set: "@/*" ./src/* -> ../src/* "@omniroute/open-sse" ./open-sse -> ../open-sse "@omniroute/open-sse/*" ./open-sse/* -> ../open-sse/* `ignoreDeprecations` goes with it — baseUrl was the only deprecated option it was suppressing. Verified by diffing the full tsc error set against the previous config (the base run used `ignoreDeprecations: "6.0"` so compilation proceeds past the config error, which otherwise aborts type-checking and masks everything): zero new errors, 28 fewer. All 28 were in `electron/*.js`, which `baseUrl: ".."` had been dragging into the open-sse program via root-relative resolution. Scoping the program back to open-sse also moves `check:type-coverage` from 92.17% to 94.01%; the ratchet direction is up so the gate passes, and the baseline is deliberately left alone because the gain is a measurement-scope change rather than new typing work. The guard test asserts no tsconfig reintroduces `baseUrl` or `ignoreDeprecations`, and that every `paths` target still resolves to a real directory — the second half is the part that matters, since dropping baseUrl silently changes what those mappings point at.
149 lines
5.2 KiB
TypeScript
149 lines
5.2 KiB
TypeScript
/**
|
||
* TS 7.0 readiness guard for tsconfig files.
|
||
*
|
||
* TypeScript 6.x raises `TS5101: Option 'baseUrl' is deprecated and will stop
|
||
* functioning in TypeScript 7.0` for any tsconfig that still declares
|
||
* `compilerOptions.baseUrl`. The only offender was `open-sse/tsconfig.json`,
|
||
* which paired it with `ignoreDeprecations: "5.0"` to silence the warning.
|
||
*
|
||
* Dropping `baseUrl` changes how `paths` are resolved: without it, every path
|
||
* mapping is resolved relative to the directory containing the tsconfig rather
|
||
* than relative to `baseUrl`. So asserting "no baseUrl" alone is not enough —
|
||
* the mappings have to keep pointing at real directories, or `@/*` and
|
||
* `@omniroute/open-sse/*` silently stop resolving. Both halves are asserted here.
|
||
*/
|
||
import test from "node:test";
|
||
import assert from "node:assert/strict";
|
||
import fs from "node:fs";
|
||
import path from "node:path";
|
||
import { fileURLToPath } from "node:url";
|
||
import ts from "typescript";
|
||
|
||
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||
|
||
/** Directories that hold generated/vendored copies of tsconfig files. */
|
||
const SKIP_DIRS = new Set([
|
||
"node_modules",
|
||
".build",
|
||
".next",
|
||
".next-playwright",
|
||
".claude",
|
||
".git",
|
||
"dist",
|
||
"dist-electron",
|
||
"coverage",
|
||
".source",
|
||
".tmp",
|
||
"_tasks",
|
||
"_ideia",
|
||
"_mono_repo",
|
||
"_references",
|
||
]);
|
||
|
||
function collectTsconfigs(dir: string, found: string[] = []): string[] {
|
||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||
if (entry.isDirectory()) {
|
||
if (SKIP_DIRS.has(entry.name)) continue;
|
||
collectTsconfigs(path.join(dir, entry.name), found);
|
||
} else if (/^tsconfig(\..+)?\.json$/.test(entry.name)) {
|
||
found.push(path.join(dir, entry.name));
|
||
}
|
||
}
|
||
return found;
|
||
}
|
||
|
||
/**
|
||
* tsconfig is JSONC (comments + trailing commas allowed), and glob values like
|
||
* `"**/*.ts"` contain `/*` — so a hand-rolled comment stripper corrupts them.
|
||
* Use TypeScript's own parser. `extends` is deliberately NOT resolved: each file
|
||
* is asserted on what it literally declares.
|
||
*/
|
||
function readTsconfig(file: string): { compilerOptions?: Record<string, unknown> } {
|
||
const { config, error } = ts.readConfigFile(file, (p) => fs.readFileSync(p, "utf8"));
|
||
assert.equal(
|
||
error,
|
||
undefined,
|
||
`${path.relative(REPO_ROOT, file)} is not parseable: ${
|
||
error && ts.flattenDiagnosticMessageText(error.messageText, " ")
|
||
}`
|
||
);
|
||
return config as { compilerOptions?: Record<string, unknown> };
|
||
}
|
||
|
||
const TSCONFIGS = collectTsconfigs(REPO_ROOT);
|
||
|
||
test("repo actually has tsconfig files to check", () => {
|
||
assert.ok(TSCONFIGS.length > 0, "no tsconfig files discovered — the walker is broken");
|
||
});
|
||
|
||
test("no tsconfig declares the TS 7.0-removed 'baseUrl' option", () => {
|
||
const offenders = TSCONFIGS.filter(
|
||
(file) => readTsconfig(file).compilerOptions?.baseUrl !== undefined
|
||
).map((file) => path.relative(REPO_ROOT, file));
|
||
|
||
assert.deepEqual(
|
||
offenders,
|
||
[],
|
||
`'baseUrl' is removed in TypeScript 7.0 (TS5101). Drop it and rewrite 'paths' ` +
|
||
`relative to the tsconfig's own directory. Offenders: ${offenders.join(", ")}`
|
||
);
|
||
});
|
||
|
||
test("no tsconfig needs 'ignoreDeprecations' to silence removed options", () => {
|
||
const offenders = TSCONFIGS.filter(
|
||
(file) => readTsconfig(file).compilerOptions?.ignoreDeprecations !== undefined
|
||
).map((file) => path.relative(REPO_ROOT, file));
|
||
|
||
assert.deepEqual(
|
||
offenders,
|
||
[],
|
||
`'ignoreDeprecations' only suppresses the symptom — remove the deprecated ` +
|
||
`option itself. Offenders: ${offenders.join(", ")}`
|
||
);
|
||
});
|
||
|
||
test("every tsconfig 'paths' mapping resolves to a real file or directory", () => {
|
||
const broken: string[] = [];
|
||
|
||
for (const file of TSCONFIGS) {
|
||
const compilerOptions = readTsconfig(file).compilerOptions;
|
||
const paths = compilerOptions?.paths as Record<string, string[]> | undefined;
|
||
if (!paths) continue;
|
||
|
||
// Without baseUrl, path mappings resolve relative to the tsconfig's directory.
|
||
const resolveRoot = path.dirname(file);
|
||
|
||
for (const [alias, targets] of Object.entries(paths)) {
|
||
for (const target of targets) {
|
||
// Strip the trailing wildcard segment: "../src/*" -> "../src"
|
||
const concrete = target.replace(/\/?\*+$/, "");
|
||
const absolute = path.resolve(resolveRoot, concrete);
|
||
if (!fs.existsSync(absolute)) {
|
||
broken.push(`${path.relative(REPO_ROOT, file)}: "${alias}" -> "${target}"`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
assert.deepEqual(
|
||
broken,
|
||
[],
|
||
`path alias targets that do not exist on disk:\n ${broken.join("\n ")}`
|
||
);
|
||
});
|
||
|
||
test("open-sse aliases point at the repo-root src/ and open-sse/ directories", () => {
|
||
const file = path.join(REPO_ROOT, "open-sse/tsconfig.json");
|
||
const paths = readTsconfig(file).compilerOptions?.paths as Record<string, string[]>;
|
||
|
||
assert.ok(paths, "open-sse/tsconfig.json must keep its path aliases");
|
||
|
||
const resolveRoot = path.dirname(file);
|
||
const resolveAlias = (alias: string) =>
|
||
path.resolve(resolveRoot, paths[alias][0].replace(/\/?\*+$/, ""));
|
||
|
||
assert.equal(resolveAlias("@/*"), path.join(REPO_ROOT, "src"));
|
||
assert.equal(resolveAlias("@omniroute/open-sse"), path.join(REPO_ROOT, "open-sse"));
|
||
assert.equal(resolveAlias("@omniroute/open-sse/*"), path.join(REPO_ROOT, "open-sse"));
|
||
});
|