TypeScript 7.0 / tsgo readiness scanner. Tells you exactly what in your repo will break when you move to the native Go compiler — before it breaks your build — from
package.jsonandtsconfig.jsonalone.
TypeScript 7.0 shipped GA on 2026-07-08: the native Go rewrite ("tsgo"), ~10× faster. Two things bite on upgrade:
- No programmatic Compiler API (deferred to 7.1). Every tool that embeds it —
Vue/Volar,
vue-tsc, Astro, Svelte, MDX, Angular template checking,ts-node,ts-morph,typescript-eslint,ts-jest,typedoc, … — cannot run on 7.0 yet. - Removed
tsconfig.jsonoptions.baseUrl,target: es5, legacymodule/moduleResolution,esModuleInterop: falseand more are now hard errors, andstrictis on by default.
ts7-compat-guard scans both files and reports what breaks, why, and how to fix
it — as a fail-the-build GitHub Action, an npx CLI, or SARIF for
code scanning.
Since v3 the database is a dated readiness ledger, not a name blacklist:
each entry can carry ts7Ready (the release range that actually supports TS7),
so a repo whose tools have caught up gets a green notice instead of a
stale conflict. v3 also makes the officially documented escape hatch —
@typescript/typescript6,
the TS6 API shim — first-class: both documented layouts are detected, and
Compiler-API conflicts are downgraded to warnings when the shim is present
(removed tsconfig options are not downgraded — the shim restores the API,
not the config options).
Accuracy is the point. It reads
package.json,tsconfig.jsonand installednode_modules/*/package.jsonversions only — it never parses your source, so it never cries wolf. Things that TS7 changes but can't be proven to break your code (decorator metadata, strict-by-default) are reported as advisories, clearly separated from hard conflicts, and never fail your build. A normal scan is fully offline; the only network command is the opt-indb --check.
Sources: Announcing TypeScript 7.0 (Microsoft devblog) · TypeScript-Go decorators discussion #741
| Pillar | Source | Severity |
|---|---|---|
| Compiler-API dependencies — 25 packages that embed the removed programmatic API | package.json + installed versions |
conflict on TS7 · warning on TS6/shim/partial · notice when the installed version satisfies ts7Ready |
Installed-tree peer scan (v3.1) — every installed package whose bounded peerDependencies.typescript range excludes the target TS (default 7.0.2). Catches transitive deps and packages the ledger has never heard of |
node_modules/**/package.json (scoped, nested, pnpm .pnpm store) |
warning (never fails) · conflict with --strict-peers |
TS6 API shim — @typescript/typescript6, both documented layouts |
package.json |
advisory line; downgrades Compiler-API conflicts to warning |
Removed tsconfig options — 17 options + references.prepend, with exact line numbers |
tsconfig.json |
conflict on TS7 · warning on TS6 (never downgraded by the shim) |
Behavioural advisories — strict default, emitDecoratorMetadata, ignoreDeprecations |
tsconfig.json (+ dep context) |
advisory (never fails) |
Only conflict-severity findings (something that will break under TS7) fail a
--mode fail run. Warnings ("will break when you upgrade") and advisories never do.
# scan ./package.json + ./tsconfig.json
npx ts7-compat-guard
# scan a specific directory, machine-readable output
npx ts7-compat-guard --dir ./apps/web --json
# whole monorepo (every package.json + tsconfig under --dir)
npx ts7-compat-guard --recursive
# emit SARIF for GitHub code scanning
npx ts7-compat-guard --sarif-file ts7-compat.sarif
# dependencies only (skip tsconfig analysis)
npx ts7-compat-guard --no-tsconfig
# maintainer/curious: propose readiness-db updates from the npm registry
# (opt-in, the ONLY command that touches the network; writes nothing)
npx ts7-compat-guard db --check=== TypeScript 7.0 / tsgo Readiness ===
typescript ^7.0.2 → TypeScript 7.0 detected (via devDependencies)
[dependencies]
CONFLICT: typescript-eslint — reads types via the TypeScript Compiler API, which TS 7.0 does not export until 7.1
Fix: Run typescript-eslint against @typescript/typescript6 side-by-side, or pin typescript to ^6.x
[tsconfig.json]
CONFLICT: baseUrl — baseUrl removed (tsconfig.json:6)
Reason: `baseUrl` is removed in TypeScript 7.0; path mapping is now resolved relative to the tsconfig.json location.
Fix: Delete `baseUrl` and rewrite `paths` entries relative to the config file (e.g. `"@/*": ["./src/*"]`).
[advisories] (behavioural risks — do not fail the build)
ADVISORY: emitDecoratorMetadata support on tsgo is unconfirmed (tsconfig.json:9)
You rely on `emitDecoratorMetadata` … Detected in your dependencies: @nestjs/core.
Fix: Verify your DI/ORM works against the native compiler before upgrading; keep typescript on 6.x for that build.
2 conflict(s) · 1 advisory(ies) — type-checking/builds will break under TypeScript 7.0.
A repo that followed the 7.0 announcement
and aliased typescript to the TS6 shim while installing TS7 under
@typescript/native — v2 misread this as "plain TypeScript 6"; v3 reports it
correctly and exits 0:
=== TypeScript 7.0 / tsgo Readiness ===
typescript npm:@typescript/typescript6@^6.0.2 → TS6 API shim (@typescript/typescript6) — Compiler-API consumers resolve the TypeScript 6 API (via devDependencies)
TypeScript 7.0 detected via "@typescript/native": npm:typescript@^7.0.2
✓ TS6 API shim present (@typescript/typescript6, aliased) — Compiler-API conflicts downgraded to warnings
see https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/
[dependencies]
WARNING: ts-morph — Built entirely on the TypeScript Compiler API (downgraded: TS6 API shim present)
Fix: Wait for ts-morph TypeScript 7.1 support or pin typescript to ^6.x
1 warning(s) — the TS6 API shim keeps Compiler-API tools working; nothing is build-breaking.
And a repo whose tooling has already caught up (a db entry with ts7Ready
satisfied by the installed version) gets a notice, not a conflict:
[dependencies]
NOTICE: typescript-eslint 8.70.0 — TS7 supported since 8.70.0 (source: https://github.com/typescript-eslint/typescript-eslint/releases, checked 2026-07-29)
1 notice(s) — all flagged dependencies have TypeScript 7 support.
A repo on typescript ^6.2.0 with @typescript-eslint/parser 8.67.0 and
svelte-check 4.7.5 installed — neither declared directly (both pulled in
transitively). v3.0 reported zero dependency findings here, because the curated
pillar only ever saw the manifest's direct dependencies. v3.1 reads the
installed tree:
=== TypeScript 7.0 / tsgo Readiness ===
typescript ^6.2.0 → TypeScript 6.x (pre-7.0) (via devDependencies)
[installed tree] (target: typescript 7.0.2)
WARNING: @typescript-eslint/parser 8.67.0 — declares peerDependencies.typescript ">=4.8.4 <6.1.0", which excludes 7.0.2
WARNING: svelte-check 4.7.5 — declares peerDependencies.typescript "^5.0.0 || ^6.0.0", which excludes 7.0.2
2 warning(s) — these will not resolve against TypeScript 7.
Exit 0 — these are warnings, because a bounded peer range excluding 7.x
proves an install-time peer conflict, not a runtime crash (pnpm, yarn and
npm --legacy-peer-deps install straight through it). Add --strict-peers to
make them conflicts and exit 1.
| Flag | Default | Meaning |
|---|---|---|
--dir <path> |
. (cwd) |
Directory containing package.json / tsconfig.json |
--recursive, -r |
off | Scan every package under --dir (skips node_modules, build output, dotfolders) |
--json |
off | Emit a JSON report |
--sarif |
off | Emit SARIF 2.1.0 to stdout |
--sarif-file <p> |
Write SARIF 2.1.0 to a file | |
--mode fail|warn |
fail |
fail → exit 1 on a build-breaking conflict; warn → always exit 0 |
--ignore <list> |
Comma-separated package names to exclude from conflicts | |
--db <path> |
JSON of extra { "pkg": { "reason", "fix" } } entries to merge |
|
--target-ts <v> |
7.0.2 |
Exact TypeScript version the installed-tree peer scan tests ranges against |
--strict-peers |
off | Promote installed-tree peer findings from warning to conflict (they then fail --mode fail) |
--no-peers |
Skip the installed-tree peer scan | |
--no-tsconfig |
Skip tsconfig.json analysis (dependencies only) | |
--no-config |
Do not read .ts7guardrc.json |
|
-h, --help / -v, --version |
Help / version |
| Code | When |
|---|---|
0 |
No build-breaking conflicts (or --mode warn). Warnings, notices, advisories & installed-tree peer findings (without --strict-peers) do not fail. |
1 |
A Compiler-API dependency (not TS7-ready, no shim) or a removed tsconfig option, while on TypeScript 7.0, or an installed-tree peer finding under --strict-peers (--mode fail) |
2 |
Usage / runtime error (e.g. no package.json, invalid --target-ts) |
Breaking change in v3: a repo whose flagged dependencies satisfy their
ts7Readyrange, or which has the@typescript/typescript6shim installed, now exits 0 where v2 exited 1. Removed tsconfig options still exit 1.
TypeScript 7.0 ships without a programmatic API (deferred to 7.1). The
announcement
documents an official escape hatch — @typescript/typescript6, which re-exports
the TypeScript 6 API (and a tsc6 binary) so Compiler-API tools keep working.
v3 recognises both documented layouts:
With either layout present, the scan prints TS6 API shim present,
downgrades every Compiler-API dependency conflict to a warning, and exits 0.
Removed-tsconfig-option conflicts are not downgraded — the shim restores
the API, not the removed options. In layout B the guard resolves the alias
targets: it reports TypeScript 7.0 as installed (via the npm:typescript@^7
alias, whatever the key is named) and the typescript key as the TS6 API half —
v2 misread this layout as plain TypeScript 6.
The curated ledger is precise but finite: 25 names, direct dependencies only.
The installed-tree scan is the generic second pillar. It enumerates every
node_modules/<pkg>/package.json — scoped packages, nested node_modules
(depth-bounded), and pnpm's .pnpm store (symlink-loop-safe; every package
directory is realpath'd once, so pnpm's symlinked layout is never reported
twice) — reads peerDependencies.typescript, and reports each package whose
bounded range excludes the target version (--target-ts, default 7.0.2).
The bounded-range doctrine from db --check applies per range:
>=4.8.4 <6.1.0(bounded, excludes 7.0.2) → finding. Real:@typescript-eslint/parser@8.67.0, verified 2026-08-11.^5.0.0 || ^6.0.0(bounded compound, excludes 7.0.2) → finding. Real:svelte-check@4.7.5, verified 2026-08-11.*,>=4.8.4(unbounded) → silent, always. An unbounded range admits 7.x trivially and is never evidence of anything.peerDependenciesMeta.typescript.optional: true→ the finding says "optional peer — lower confidence"; an optional peer does not always block an install.
typescript itself and @typescript/typescript6 are excluded. Malformed
manifests are skipped and counted (peerScan.skipped in --json). A repo
with no node_modules at all reports
not run — no node_modules found; run npm install for full coverage —
never an empty pass. A package the curated ledger already reported (conflict,
notice, or ignored) is not repeated; a ledger package that is only installed
transitively is exactly what this pillar exists to catch.
A rule written from a spec is a hypothesis, so this pillar was measured before
shipping (2026-08-11): 17 real public TypeScript repos were cloned and
npm-installed into a scratch directory and scanned — 15 installs succeeded
(zod and typeorm did not: workspace: protocol / install failure). The
corpus: hono, ky, got, execa, chalk, fastify, axios, node-fetch, winston,
class-validator, immer, zustand, nest, class-transformer, undici.
| Metric | Value |
|---|---|
| Repos hit | 9 / 15 (60%) |
| Packages flagged / inspected | 79 / 12,263 (0.64%) |
| …of packages declaring a typescript peer at all | 79 / 134 (59%) |
Unique flagged name@version |
26 |
| Manifests skipped (malformed) | 0 |
Every sampled hit was hand-verified against the package's actual published
registry manifest — 8 of 8 matched the scanned range exactly. The findings are
dominated by the @typescript-eslint/* family (>=4.8.4 <6.1.0) plus
@hono/eslint-config (^5.0.0 || ^6.0.0) — including
@typescript-eslint/project-service, tsconfig-utils, eslint-plugin and
utils, none of which are in the 25-name curated ledger and all of which
arrive transitively. The 0.64% package-level rate is why this ships as a
default-on warning: it is a real signal, not a tax — and the 60% repo-level
rate is not noise, it is the honest state of the ecosystem (any repo linting
through typescript-eslint today genuinely will not resolve against TS 7).
npx ts7-compat-guard db --check # against registry.npmjs.org (no auth)
npx ts7-compat-guard db --check --json # machine-readable proposed patchFor every db package it fetches the npm registry document, walks the versions
map oldest-to-newest, reads each version's peerDependencies.typescript, and
finds the earliest stable release at which a bounded range widens to
admit TypeScript 7.x. It prints a proposed db.json patch plus a diff against
the committed values — and writes nothing; entries are applied by hand after
checking the release notes.
The bounded-range rule is the whole point: an unbounded peer range is never
evidence of support. Measured 2026-07-29, 7 of the 25 covered packages declare
ranges that trivially admit 7.0.2 while being known-broken (ts-loader *,
ts-node >=2.7, tsup >=4.5.0 — our own db entry documents tsup crashing
on 7.0 — @rollup/plugin-typescript, rollup-plugin-typescript2,
fork-ts-checker-webpack-plugin, vue-tsc). Those report
unknown — manual check, never supported. 404s, network failures and
missing peer ranges also report unknown instead of crashing.
db --check never runs during a normal scan or inside the Action. If the
bundled ledger's generatedAt is older than 60 days, a scan prints a
non-failing staleness note suggesting a refresh.
Removed options detected (each reported with its exact line, conflict on TS7 /
warning on TS6):
target: es5/es3 · downlevelIteration · module: amd/umd/system/none ·
moduleResolution: node/node10/classic · baseUrl · esModuleInterop: false ·
allowSyntheticDefaultImports: false · alwaysStrict: false · out ·
importsNotUsedAsValues · preserveValueImports · keyofStringsOnly ·
noImplicitUseStrict · noStrictGenericChecks · charset · references[].prepend
JSONC (comments + trailing commas) is parsed correctly, and relative extends
chains are followed so inherited options are still caught.
strictis now default-on — if your tsconfig doesn't set it, the upgrade turns on every strict check at once.emitDecoratorMetadata— the native compiler's design-time metadata emit is unresolved upstream; if you use reflect-metadata DI (NestJS/TypeORM/Angular/class-transformer), verify before upgrading.ignoreDeprecations— the 6.x escape hatch stops working; the options it hid are now removed outright.
# .github/workflows/ts7-guard.yml
name: TS7 Readiness
on: [pull_request, push]
jobs:
ts7-compat:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: Booyaka101/ts7-compat-guard@v3
with:
package-dir: . # default: .
mode: fail # default: fail ("warn" to annotate without failing)
recursive: 'false' # 'true' for monorepos
# sarif-file: ts7-compat.sarif # upload with github/codeql-action/upload-sarifInputs: package-dir, mode, recursive, ignore, sarif-file, config,
target-ts, strict-peers, peers.
Outputs: ts7, conflict-count, tsconfig-count, advisory-count,
notice-count, peer-count, shim-detected, status, json.
Conflicts surface as GitHub error annotations — tsconfig ones point at the exact
tsconfig.json line — with warnings when you're still on TS 6 and non-failing notices
for advisories, plus a job-summary line.
- Read
package.json; resolve the effectivetypescriptversion — a top-leveloverrides/resolutions/pnpm.overridespin wins over a declared dependency — and flag TS7 when its floor is>= 7.0.0.npm:alias targets are resolved too: any dependency aliased tonpm:typescript@^7means TS7 is installed, andtypescriptaliased tonpm:@typescript/typescript6@…means the API half is 6.x. - Cross-reference every other dependency against the readiness ledger (
src/db.json). For each match, resolve the effective version: the installednode_modules/<pkg>/package.jsonversion when present (per package dir, falling back to the repo root in monorepos), else the minimum of the declared range. - If that version satisfies the entry's
ts7Readyrange → notice (never fails). Ifts7Statusis"partial"→ warning with the source URL. Otherwise the classic rule: conflict on TS7 (downgraded to warning when the TS6 shim is present), warning on TS6. - Scan the installed tree (v3.1): every
node_modules/**/package.jsonwhose boundedpeerDependencies.typescriptrange excludes the target version (default 7.0.2) → warning (conflictwith--strict-peers). Unbounded ranges are never evidence. Findings the curated pillar already made are not repeated. - Read
tsconfig.json(JSONC + relativeextends); flag removed options and derive advisories. Removed options are never downgraded by the shim.
Non-semver typescript specs (latest, *, git/file URLs) are treated conservatively
as not TS7 to avoid false alarms. Prerelease installed versions are compared with
includePrerelease. Empty or malformed installed manifests fall back to the declared
range.
25 packages, each entry carrying reason, fix, ts7Status
(none | partial | supported), an optional ts7Ready range, a source URL
and a checkedAt date. As of 2026-07-29 not one of them ships a bounded
typescript peer range that admits 7.x (typescript-eslint 8.65.0 added a
"TS 7 detected" warning while staying pinned >=4.8.4 <6.1.0), so every entry
truthfully reads ts7Status: "none" — no invented version numbers. When the
ecosystem catches up, db --check proposes the exact release, and repos on that
release start seeing green notices instead of conflicts.
| Package | TS7 status (checked 2026-07-29) |
|---|---|
@vue/language-tools, volar, @volar/typescript, vue-tsc |
none |
@astrojs/language-server, @astrojs/check |
none |
svelte-language-server, svelte-check |
none |
@angular/compiler-cli |
none |
@mdx-js/mdx |
none |
ts-node, ts-morph |
none |
typescript-eslint, @typescript-eslint/parser, @typescript-eslint/typescript-estree |
none (8.65.0 warns on TS7; peer range still excludes it) |
ts-loader, fork-ts-checker-webpack-plugin |
none |
rollup-plugin-typescript2, @rollup/plugin-typescript |
none |
ts-jest |
none |
@microsoft/api-extractor, typedoc, dts-bundle-generator, tsd |
none |
tsup |
none (declaration step crashes on 7.0) |
Extend via src/db.json, --db, or .ts7guardrc.json — custom entries may
carry the same ts7Ready / ts7Status / source / checkedAt fields.
{
"ignore": ["ts-node"],
"mode": "warn",
"db": { "my-internal-tool": { "reason": "wraps ts.createProgram", "fix": "pin typescript to ^6.x" } }
}npm install
npm run build # bundle src/action.js -> dist/action.js (esbuild; inlines semver + db.json)
npm test # 183 checks: core, tsconfig engine, readiness/shim/alias, installed-tree peer scan, db --check, report, SARIF, CLI (in-process + spawned), Action, bundled distThe Action runs from the committed self-contained bundle dist/action.js, so
re-run npm run build and commit dist/ before tagging a release (CI enforces this).
MIT