Migrate an AI monorepo to TypeScript 6 by pinning one 6.0 patch, inventorying each package’s runtime and globals, making types, rootDir, module, moduleResolution, and target explicit, removing 6.0 deprecations, and upgrading packages from leaves to applications. Run stableTypeOrdering only as a TypeScript 7 compatibility diagnostic and keep runtime tests separate from type checks.
As of July 18, 2026, TypeScript 7.0 is already stable. TypeScript 6 remains useful as a bridge for tools that still require the JavaScript compiler API or for organizations staging the native-compiler transition. Microsoft’s TypeScript 6 announcement calls it the last release based on the JavaScript codebase and the bridge between 5.9 and 7.0; the later TypeScript 7 announcement documents side-by-side compatibility options.
Inventory runtime boundaries before changing the compiler
AI monorepos often combine incompatible environments:
- Node orchestration and provider SDKs;
- browser chat UI;
- edge or worker request gateways;
- tool sandboxes;
- generated JSON-schema and RPC bindings;
- test runners with global functions;
- shared packages that should contain no runtime globals.
Build a table for every tsconfig.json: package, runtime, module loader, bundler, emitted artifacts, global type packages, consumers, and current compiler errors. Do not let one root config inject DOM, Node, Bun, and test globals into every package. That masks portability bugs and creates declaration conflicts.
Freeze the pre-migration baseline: clean install, typecheck, declaration build, runtime tests, bundle build, and generated-code diff. Save tsc --extendedDiagnostics output for representative projects. TypeScript’s official performance wiki documents project references, explicit types, and diagnostic tools.
Pin TypeScript 6 intentionally
The unqualified typescript package now follows the 7.x line. Pin the reviewed 6.0 patch in the workspace root and enforce a single resolution. This article was prepared against the official typescript@6.0.3 release; verify the registry and release notes before choosing a later patch.
{
"devDependencies": {
"typescript": "6.0.3"
}
}
Commit the lockfile. Make CI print tsc --version so a package-local install cannot silently select another compiler. Compiler API consumers, linters, framework plugins, transformers, and declaration tools need their own compatibility check; successful application typechecking does not prove the tooling stack is supported.
Account for TypeScript 6’s new defaults
The official TypeScript 6 release notes list defaults that can change previously implicit projects:
strictdefaults totrue;moduledefaults toesnext;targetdefaults to the current-year target, ES2025 in 6.0;noUncheckedSideEffectImportsdefaults totrue;libReplacementdefaults tofalse;rootDirdefaults to thetsconfig.jsondirectory;typesdefaults to an empty list instead of discovering every@typespackage.
Do not depend on floating defaults. State the environment explicitly:
{
"compilerOptions": {
"strict": true,
"target": "ES2024",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"rootDir": "./src",
"types": ["node"],
"noUncheckedSideEffectImports": true,
"noEmit": true
},
"include": ["src/**/*.ts"]
}
This is an example for a Node package whose runtime supports the selected target. A browser package might use module: "ESNext", moduleResolution: "Bundler", DOM libraries, and no Node globals. A test config should add only its runner’s types. Match actual runtime and bundler behavior; do not paste one config everywhere.
The types: [] default is especially visible in flattened workspaces. Add node, bun, or test types only where globals are intentionally used. Imported package types still resolve through imports; types controls global declarations.
Remove deprecations instead of silencing them
TypeScript 6 deprecates configuration and syntax that TypeScript 7 does not support. Important cases from the official notes include:
target: "es5"anddownlevelIteration;moduleResolution: "node"/"node10"and"classic";- AMD, UMD, and SystemJS module output;
baseUrlas a module-resolution root;esModuleInterop: falseandallowSyntheticDefaultImports: false;alwaysStrict: falseandoutFile;- legacy
module Foo {}namespace syntax; - import
assertsyntax instead of import attributes withwith; no-default-libreference directives.
Choose NodeNext for code executed directly by modern Node, or Bundler for code whose bundler defines resolution. TypeScript 6 permits moduleResolution: "Bundler" with additional module choices, but test emitted or bundled code in the real runtime.
If baseUrl only supported path mappings, remove it and put the prefix directly in paths. Then verify that the bundler, test runner, and runtime use the same alias rules; TypeScript path mapping does not rewrite runtime imports.
ignoreDeprecations: "6.0" can temporarily expose the next failure, but it is not migration completion. Track every suppression with an owner and remove it before the TypeScript 7 lane becomes required.
Upgrade the project graph from leaves upward
Create a root solution config with no source files and references to buildable packages:
{
"files": [],
"references": [
{ "path": "./packages/protocol" },
{ "path": "./packages/tools" },
{ "path": "./apps/agent-api" },
{ "path": "./apps/web" }
]
}
Start with shared protocol and schema packages, then tools, orchestration, and applications. Each referenced library that emits declarations should use composite and declaration settings appropriate to its build. Give exported functions explicit return types and keep public types named; this improves declaration stability and makes generated agent/tool boundaries reviewable.
Do not include unrelated examples/**/*.ts in a root application config unless their dependencies are installed in that workspace. Give examples their own package and typecheck command. That prevents clean CI from seeing local-only transitive dependencies.
Treat generated AI interfaces as external input
Model tool schemas, provider payloads, code-generated clients, and evaluation fixtures can produce enormous unions or deep conditional types. Separate generation from checking:
- Pin generator and source-schema versions.
- Generate into a deterministic directory.
- Diff generated output in CI.
- Validate payloads at runtime; TypeScript types do not validate JSON.
- Export smaller named interfaces instead of intersecting giant inferred types.
- Keep generated files out of unrelated project includes.
If typechecking regresses, use --extendedDiagnostics and --generateTrace before weakening types. Look for repeated anonymous intersections, huge unions, recursive conditional types, and declaration emit inferred across package boundaries.
Use stable type ordering as a diagnostic lane
TypeScript 6’s --stableTypeOrdering makes its internal ordering closer to TypeScript 7. The release notes say it can slow typechecking by up to 25% depending on the codebase and is intended to diagnose transition differences, not as a permanent feature.
Run it in a separate CI job:
normal TypeScript 6 check
TypeScript 6 check with stableTypeOrdering
TypeScript 7 compatibility check where tooling permits
When ordering exposes inference differences, add a justified type argument, variable annotation, or exported return type. Do not reorder source declarations until the error disappears without understanding why.
Verify more than a green typecheck
For every package wave, run:
- compiler and declaration checks;
- unit and integration tests;
- ESM/CJS import smoke tests in the actual runtime;
- production bundler builds;
- Server/Client boundary checks for web packages;
- generated-schema compatibility tests;
- provider/tool response runtime validation;
- cold and incremental compiler diagnostics.
Compare emitted declarations and package exports. A new module default can leave tsc green while consumers fail at runtime. skipLibCheck may reduce third-party declaration work, but it does not make an incompatible runtime package safe.
Roll out with reversible config commits
Keep dependency pin, base-config changes, package migrations, and deprecation cleanup in separable commits. Canary developer editor performance and CI workers, then expand. Rollback means restoring the compiler, lockfile, and config together; leaving TS6-only defaults or generated declarations behind can create a mixed state.
For teams moving directly to TypeScript 7, use Microsoft’s documented compatibility package or aliases only when compiler-API tooling needs TypeScript 6. Pin both binaries and make each CI command unambiguous.