Files
OmniRoute/tests/unit/tsconfig-ts7-readiness.test.ts
backryun 1930b09c6a chore(sse): drop deprecated baseUrl from open-sse tsconfig for TS 7.0 (#8473)
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.
2026-07-25 02:53:01 -03:00

149 lines
5.2 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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"));
});