Investigated: 2026-07-13 · Re-checked: 2026-08-06, 2026-09-14 (still blocked — see below)
Decision: Stay on TypeScript 6.0.3. Revisit when typescript-eslint supports TS 7.x.
Blocker: typescript-eslint (all packages) — its peer range explicitly excludes TypeScript 7.
TypeScript 7.0.2 is real, released, and is the latest tag on npm. Our code is fully ready for it:
it typechecks with zero errors and roughly 6x faster than TS 6. Nothing in our source needs to change.
We are blocked purely on tooling. TypeScript 7 is the Go-native compiler rewrite, and that rewrite
removed the classic in-process JavaScript compiler API. typescript-eslint is built entirely on that API.
Installing TS 7 today does not degrade our lint — it crashes it, taking npm run lint (and therefore
prebuild) down with it.
TypeScript 7 is not an incremental release. It is the native (Go) port of the compiler — the project
previously previewed as "Corsa" / @typescript/native-preview (the tsgo binary).
Consequences visible in the published package:
- The
typescriptnpm package is now a ~360 KB JS shim (lib/getExePath.js) that shells out to a platform-specific native Go binary, delivered viaoptionalDependencies(@typescript/typescript-linux-x64,-darwin-arm64,-win32-x64, …). - The compiler no longer runs in your Node process. It runs as a separate Go process, reachable over
JSON-RPC (the package vendors
vscode-jsonrpc). - The API surface is entirely new and lives under
./unstable/*export subpaths — and is, per its own naming, explicitly unstable.
From typescript@7.0.2's package.json:
Importing typescript no longer gives you a compiler. It gives you a version number.
$ npm view typescript dist-tags
{
beta: '6.0.0-beta',
rc: '7.0.1-rc',
latest: '7.0.2', <-- current
next: '7.1.0-dev.20260713.1'
}
Installed typescript@7.0.2 in a scratch dir and imported it:
import * as ts from 'typescript';
console.log(Object.keys(ts));
console.log(typeof ts.createProgram, typeof ts.SyntaxKind, typeof ts.createSourceFile);Result:
exports: [ 'default', 'version', 'versionMajorMinor' ]
ts.createProgram = undefined
ts.SyntaxKind = undefined
ts.createSourceFile = undefined
This is the blocker in one output block. Any tool doing import ts from 'typescript' and calling
ts.createProgram(...) / reading ts.SyntaxKind gets undefined and throws. It is a hard runtime crash,
not a compatibility warning.
$ npm view typescript-eslint dist-tags
{ latest: '8.63.0', canary: '8.63.1-alpha.17' }
$ npm view typescript-eslint@latest peerDependencies
{
eslint: "^8.57.0 || ^9.0.0 || ^10.0.0",
typescript: ">=4.8.4 <6.1.0" <-- upper bound excludes 7.x outright
}
Every package in the family carries that same <6.1.0 ceiling:
| Package | typescript peer range |
|---|---|
typescript-eslint |
>=4.8.4 <6.1.0 |
@typescript-eslint/eslint-plugin |
>=4.8.4 <6.1.0 |
@typescript-eslint/parser |
>=4.8.4 <6.1.0 |
@typescript-eslint/typescript-estree |
>=4.8.4 <6.1.0 |
@typescript-eslint/project-service |
>=4.8.4 <6.1.0 |
@typescript-eslint/tsconfig-utils |
>=4.8.4 <6.1.0 |
@typescript-eslint/type-utils |
>=4.8.4 <6.1.0 |
@typescript-eslint/utils |
>=4.8.4 <6.1.0 |
ts-api-utils |
>=4.8.4 (loose range, but also uses the classic API — would break too) |
Scanned every installed package for a typescript peer dependency. The only hits are the
typescript-eslint family plus ts-api-utils (which is typescript-eslint's own dependency).
Nothing else in the tree touches the TypeScript JS API:
- Vite / Vitest — transpile via esbuild, never load the TS compiler.
- Electron Forge / plugin-vite — unaffected.
- babel-plugin-react-compiler — runs on Babel, not the TS API.
compiler-coverage.mjs— drives the React Compiler, not the TS API.
Also confirmed: no file in src/ or at the repo root imports typescript directly. Our own code never
touches the compiler API.
Ran both compilers against our real, unmodified tsconfig.json:
| Compiler | Result | Wall time |
|---|---|---|
| TS 6.0.3 (current) | clean, 0 errors | 3.79s |
| TS 7.0.2 (native) | clean, 0 errors | 0.63s |
Zero errors on TS 7, ~6x faster. No source changes, no tsconfig.json changes required. The upgrade is
sitting there ready the moment the linter can come along.
package.json wires the typecheck and the lint together, and gates the build on both:
"prebuild": "npm run lint",
"lint": "tsc --noEmit && eslint .",If eslint . crashes, we don't just lose style nits. We lose:
- Type-aware correctness rules (require a TS Program via
projectService: trueineslint.config.mjs):@typescript-eslint/no-floating-promises,no-misused-promises,return-await,switch-exhaustiveness-check,no-deprecated, and the fourno-unsafe-*rules (no-unsafe-assignment/-call/-member-access/-return) that catchanyleaking out of third-party libraries. - The React Compiler bailout guards —
react-hooks/todoandreact-hooks/syntax, plus the rest of theeslint-plugin-react-hooks@7set.
That second one is the real reason this is non-negotiable. Per AGENTS.md, this codebase removed all manual
useCallback/useMemo in favor of the React Compiler. The compiler bails out silently on constructs it
can't handle (try/finally, ref writes during render, mutating module globals, this), leaving a component
fully de-memoized with no error and no warning. Those ESLint rules are what turn a silent perf regression
into a failed lint. Running without them means flying blind on exactly the failure mode we can't otherwise see.
Trading a 3-second typecheck speedup for that is a bad deal.
| Option | Verdict |
|---|---|
Full TS 7 now — replace typescript@6 outright |
❌ Rejected. eslint . crashes; loses type-aware rules + React Compiler bailout guards. |
Side-by-side — keep typescript@6 as the resolved typescript (so typescript-eslint works), add TS 7 under an npm alias (e.g. "typescript7": "npm:typescript@7.0.2") and point the typecheck step at its binary by explicit path |
tsc bin, so node_modules/.bin/tsc would collide; the alias must be invoked by full path. |
| Wait for typescript-eslint | ✅ Chosen. Stay on 6.0.3, upgrade in one clean step later. |
Ran the same check again:
$ npm view typescript-eslint@latest peerDependencies
{ eslint: '^8.57.0 || ^9.0.0 || ^10.0.0', typescript: '>=4.8.4 <6.1.0' }
typescript-eslint had moved 8.63.0 → 8.66.0 in the intervening weeks, but the <6.1.0 ceiling is
byte-for-byte unchanged (checked @latest and @canary — same range on both). Our installed tree is
still typescript@6.0.3 / typescript-eslint@8.64.0.
Dug into why it hasn't moved, since a plain version bump felt like it should have picked this up by now:
- TypeScript 7.0 shipped with no programmatic/JS API at all. Per Microsoft's own announcement:
"TypeScript 7.0 is here, it does not ship with an API." A stable API is targeted for 7.1
(~3-4 months out on their usual cadence).
typescript-eslinthas nothing to bind against yet — this isn't a case of the maintainers being slow to bump a peer range, there's no API to consume. typescript-eslint's own tracking issue for tsgo/TS7 support, #10940, is open but labeled "blocked by external API." Maintainers also flag a second, independent obstacle: ESLint's parser interface is synchronous, but tsgo will be consumed via WASM/native bindings (async by nature) — they describe this as "many months away," not a near-term fix.- A direct ask for TS 7.0.2 support, #12518, was closed as "not planned."
- Microsoft has published
@typescript/typescript6— a compat package that re-exports the 6.0 API (with atsc6binary to avoid the bin collision) — as the officially sanctioned stopgap for exactly this transition. This is the same shape as the "Side-by-side" option we already rejected below; it isn't a new workaround to consider, just confirmation that the split-compiler approach is Microsoft's intended bridge, not a hack we invented. We're still opting to wait rather than run it.
Net effect on the decision: unchanged. If anything the wait is now better justified — this doesn't resolve until TS 7.1 ships a stable API and typescript-eslint builds support against it and the ESLint async-parser problem is solved. None of those have a committed date yet.
npm registry:
$ npm view typescript dist-tags
{ latest: '7.0.2', rc: '7.0.1-rc', beta: '6.0.0-beta', next: '7.1.0-dev.20260913.1', ... }
$ npm view typescript-eslint dist-tags
{ latest: '8.70.0', canary: '8.70.1-alpha.0' }
$ npm view typescript-eslint@latest peerDependencies # same on @canary
{ eslint: '^8.57.0 || ^9.0.0 || ^10.0.0', typescript: '>=4.8.4 <6.1.0' }
typescript-eslintmoved8.66.0 → 8.70.0; the<6.1.0ceiling is still unchanged on bothlatestandcanary.- TS 7.1 has not shipped — no beta or RC tag yet, only nightly
7.1.0-devbuilds. Microsoft still targets "autumn 2026". The7.1milestone onmicrosoft/typescript-goshows ~95% complete (1 open / 21 closed) with no due date. That repo was archived on 2026-09-01 (work consolidated back into the main TypeScript repo), so that milestone is no longer a useful progress signal going forward. - The nightly
typescript@nextstill exposes its API only under./unstable/*subpaths; the main entry is still just./lib/version.cjs. No stable API yet. typescript-eslintissue #10940 is still open; nothing announced about a TS 7 timeline.@typescript/typescript6is at6.0.2.
New finding: we already have two TypeScript versions installed — a second obstacle to "one TS
everywhere." npm ls typescript shows:
typescript@6.0.3 <- ours (root devDependency)
typescript@5.4.5 <- nested, NOT ours:
@electron-forge/cli@7.11.2
└ @electron-forge/core@7.11.2
└ @electron-forge/template-webpack-typescript@7.11.2 (typescript: "~5.4.5")
Forge 7's core hard-depends on its project-scaffolding templates, and the webpack TS template pins its
own TypeScript. It's only used by electron-forge init, never by our build, so it's harmless — but it means
a TS 7 upgrade on Forge 7 would still leave a 5.4.5 copy in node_modules. Forge 8 (currently
8.0.0-alpha.10; latest is still 7.11.2) removes the templates from @electron-forge/core's
dependencies, which would eliminate it. So the true "single TS version" upgrade also needs Forge 8 stable
(or an npm overrides entry — not recommended, since it would force a version the template wasn't built for).
Net effect on the decision: unchanged. Still waiting on TS 7.1 (stable API) → typescript-eslint support, and now also noting Forge 8 as a prerequisite for a genuinely single-version tree.
Re-check periodically:
npm view typescript-eslint@latest peerDependencies
npm view typescript dist-tags # has 7.1 shipped?
npm view @electron-forge/cli dist-tags # is Forge 8 'latest'? (drops nested typescript@5.4.5)
npm ls typescript # how many TS copies are installed?The upgrade is unblocked when that typescript range admits 7.x (i.e. the <6.1.0 ceiling is lifted).
For a truly single-version tree, Forge 8 must also be stable (see the 2026-09-14 entry).
At that point the upgrade should be close to trivial, because we already proved the code compiles clean:
npm install -D typescript@^7 typescript-eslint@<whatever version lands>- Bump the other
@typescript-eslint/*devDeps to match. npm run lint— expecttsc --noEmitclean (already verified) andeslint .to work again.npm testand anpm run packagesmoke test.
No tsconfig.json changes are anticipated — TS 7.0.2 accepted our existing config as-is.
Installing TS 7 in one project does not affect any other project. There is no machine-wide TypeScript
install; it is a per-project devDependency resolved out of that project's own node_modules/typescript.
Every other repo stays on whatever version its own package.json pins. Nothing breaks on its own, and there is
no forced migration.
When upgrading those projects, the triage is the same one that decided this project — and it is not about your source code. It's about whether anything in the project consumes TypeScript's JS API.
Quick test in any repo:
npm ls typescript
grep -rn "from 'typescript'\|require('typescript')" --include=*.ts --include=*.js --include=*.mjs . | grep -v node_modules…and check for any of these usual suspects:
typescript-eslint/@typescript-eslint/*← the big onets-node,ts-jest,ts-patch,tsup, TypeDoc- anything else that builds a TS Program
Rule of thumb:
- Projects with none of the above — a plain library, or a Vite/esbuild/SWC app whose lint is not type-aware — can typically go straight to TS 7 and just enjoy the speedup.
- Projects with type-aware ESLint (like this one) hit the identical wall and should wait.