From 1930b09c6a812ba6418ef3542696bd413286378e Mon Sep 17 00:00:00 2001 From: backryun Date: Sat, 25 Jul 2026 14:53:01 +0900 Subject: [PATCH] chore(sse): drop deprecated baseUrl from open-sse tsconfig for TS 7.0 (#8473) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- open-sse/tsconfig.json | 8 +- scripts/check/check-type-coverage.mjs | 8 +- tests/unit/tsconfig-ts7-readiness.test.ts | 148 ++++++++++++++++++++++ 3 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 tests/unit/tsconfig-ts7-readiness.test.ts diff --git a/open-sse/tsconfig.json b/open-sse/tsconfig.json index f64585be7e..808d84af25 100644 --- a/open-sse/tsconfig.json +++ b/open-sse/tsconfig.json @@ -13,12 +13,10 @@ "strict": false, "jsx": "react-jsx", "lib": ["dom", "esnext"], - "ignoreDeprecations": "5.0", - "baseUrl": "..", "paths": { - "@/*": ["./src/*"], - "@omniroute/open-sse": ["./open-sse"], - "@omniroute/open-sse/*": ["./open-sse/*"] + "@/*": ["../src/*"], + "@omniroute/open-sse": ["../open-sse"], + "@omniroute/open-sse/*": ["../open-sse/*"] } }, "include": ["**/*.ts", "**/*.js"] diff --git a/scripts/check/check-type-coverage.mjs b/scripts/check/check-type-coverage.mjs index 0051063fd3..076fe2d770 100644 --- a/scripts/check/check-type-coverage.mjs +++ b/scripts/check/check-type-coverage.mjs @@ -11,8 +11,12 @@ // - Rationale: the only tsconfig that covers the full open-sse workspace // (src+open-sse together). `tsconfig.json` excludes open-sse; the // `tsconfig.typecheck-core.json` only lists 26 explicit files (partial). -// open-sse/tsconfig.json sets `baseUrl: ".."` and path aliases so it -// resolves both workspaces correctly and yields a representative global %. +// open-sse/tsconfig.json declares path aliases (`@/*`, `@omniroute/open-sse/*`) +// relative to its own directory, so it resolves both workspaces correctly and +// yields a representative global %. It carried a `baseUrl: ".."` until TS 7 +// readiness removed it; that also stopped `electron/*.js` from being pulled +// into the program via root-relative resolution, which moved the measured % +// up (~92.2% -> ~94.0%). // // Direction: up (% can only improve; ratchet blocks drops once wired into INT). // Eps: 0.05 (float noise tolerance — type-coverage may vary by ~0.01% between runs). diff --git a/tests/unit/tsconfig-ts7-readiness.test.ts b/tests/unit/tsconfig-ts7-readiness.test.ts new file mode 100644 index 0000000000..180cce9a23 --- /dev/null +++ b/tests/unit/tsconfig-ts7-readiness.test.ts @@ -0,0 +1,148 @@ +/** + * 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 } { + 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 }; +} + +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 | 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; + + 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")); +});