chore(deps): TypeScript major version migration (pathfinder) - #1037
Closed
ByronDWall wants to merge 2 commits into
Closed
chore(deps): TypeScript major version migration (pathfinder)#1037ByronDWall wants to merge 2 commits into
ByronDWall wants to merge 2 commits into
Conversation
- Bump typescript 5.9.3 -> 7.0.2 (latest major, Project Corsa native compiler) - tsconfig.json: add explicit `types: ["jest", "node"]` since TS 6.0+ defaults `types` to [] instead of auto-discovering @types/* packages; our .spec.ts files rely on Jest's ambient globals. - generators/package.json, standalone/package.json: pin a direct typescript@6.0.3 devDependency so @preconstruct/cli's declaration-bundling step (which calls the classic ts.sys/ts.createProgram Program API) keeps working. TS 7's npm package only exports version metadata now (the classic JS "Strada" API was removed as part of the Go-native rewrite). - pnpm-workspace.yaml: add packageExtensions forcing typescript-eslint v5's optional typescript peer to resolve 6.0.3 for the same Strada-API reason (its type-aware lint rules read ts.TypeFlags, which no longer exists on TS 7's public surface).
Under TypeScript 7, calling buildGraphql() with no type argument on a value returned by the untyped-generic compatibility builder (CustomLineItemDraft.random(), whose type parameter defaults to TCustomLineItemDraftGraphql | TCustomLineItemDraftRest) now resolves the method's own default type parameter to that same union instead of narrowing it, so the Rest variant's object shape leaked into a context that expects the GraphQL variant only. TS 5.9 inferred/defaulted this without complaint; TS 7 does not. Fixed by supplying the same explicit generic argument other spec files in this repo already use for this compatibility-builder pattern.
|
Contributor
Author
|
Closing — decided not to pursue the TypeScript v7 migration right now given the tooling-compat workaround required (preconstruct/typescript-eslint still depend on the removed Strada API). Recipe stays here for reference if we revisit later. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
MIGRATION RECIPE (for fan-out to other repos)
This section is written to be copied verbatim into the other 8 repos
(
audit-log,identity,merchant-center-application-kit,merchant-center-frontend,merchant-center-prices,merchant-center-services,nimbus,ui-kit).What changed
typescript5.9.37.0.2TypeScript's real current latest major on npm is 7 (
7.0.2, released2026-07-08 — "Project Corsa", a full port of the compiler/language service to
Go). TypeScript 6 (
6.0.3) existed as an intermediate major and was the lastrelease built on the classic JS compiler; read its release notes too, because
every 6.0 breaking change also applies to 7.0 (7.0 is a superset).
References read:
tsconfig.json changes
Added one compiler option, no removals were needed for this repo's existing
config (it already used
moduleResolution: "Bundler",module: "ESNext",target: "ESNext", so none of the hard-removed options —target: es5,moduleResolution: classic,module: amd/umd/systemjs/none,baseUrl,downlevelIteration— were in play):{ "compilerOptions": { // TypeScript 6.0+ defaults `types` to `[]` instead of auto-discovering // every package under node_modules/@types. Spec files that use Jest's // ambient globals (describe/it/expect) without importing them from // '@jest/globals' will fail to compile unless you list them explicitly. "types": ["jest", "node"] } }Watch for in other repos: any repo relying on ambient
@types/*globalaugmentation (jest, mocha, node, cypress, testing-library matchers, etc.)
without an explicit
typesarray needs this same fix. Grep fordescribe(/it(/expect(used without a corresponding import, and for anyother ambient global usage (e.g.
process,Buffer) that depended on@types/nodebeing auto-included.If your repo does use any of the hard-removed/changed options, here's the
mapping (from the TS 6.0/7.0 release notes):
target: "es5"→ bump toes2015+ (es5is a hard error now)downlevelIteration→ remove (no longer has any effect below es5, which nolonger exists)
moduleResolution: "node"/"node10"→ migrate to"nodenext"or"bundler"moduleResolution: "classic"→ hard error, must move to"bundler"or"node16"/"nodenext"module: "amd" | "umd" | "systemjs" | "none"→ hard error, migrate to"esnext"/"commonjs"/bundler-appropriate valuebaseUrl(used as a module-resolution root) → hard error; switch toexplicit
pathswith relative globs, or a bundler/tsconfig-paths-styleresolution
esModuleInterop: false/allowSyntheticDefaultImports: false/alwaysStrict: false→ can no longer be set tofalse; delete the line(safer interop / strict mode is now unconditional)
outFile→ removed entirelymodule Foo { ... }namespace syntax → error; rename tonamespace Foo { ... }rootDirnow defaults to.(the tsconfig's directory) instead of beinginferred from your source layout — if your repo's
tsconfig.jsonlivesoutside
src/, add an explicit"rootDir": "./src"(or equivalent) toavoid unexpected emit-path changes (this only matters if you emit; a
noEmit/--noEmittypecheck-only repo like this one is unaffected)Error categories found and fix patterns
Running
tsc --noEmitright after the bump (before any tsconfig changes)produced ~8,442
TS2304/2593errors + 4TS2694+ 1TS2345across therepo. All but one category collapsed to a single tsconfig fix; only one file
needed an actual source-code change.
1.
TS2304/TS2593"Cannot find name 'describe'/'it'/'expect'" (~8,442occurrences, ~530 files)
Cause:
typesdefaults to[]in TS 6.0+ (see tsconfig section above).Fix: add
"types": ["jest", "node"]totsconfig.json. One-line fix,resolved the entire category at once — no per-file changes needed.
2.
TS2345type mismatch on a compatibility-builder call (1 occurrence)This is a genuine generic-default inference difference between TS 5.9 and
TS 7, not a TS 6/7-documented breaking change — it slipped through because
one spec file omitted the explicit type argument that ~40 other, nearly
identical spec files in this repo already pass. Watch for this pattern in
other repos: any call site that relies on an unspecified generic type
argument defaulting through two levels of generics (an untyped factory call
feeding an untyped
build*()call) is worth grepping for and double-checkingunder TS 7, even if
tscdoesn't flag it — in our case it did, but thefailure mode if it silently type-checked wrong would be a false green.
3. Runtime tool breakage:
@preconstruct/cli's declaration bundler(build-time, not a
tscerror)pnpm buildfailed with:Root cause (the single most important gotcha for fan-out repos):
TypeScript 7.0 is a full Go port ("Project Corsa"). The
typescriptnpmpackage's public API surface is no longer the classic JS "Strada"
Program/LanguageService API —
require('typescript')on 7.0.2 resolves tolib/version.cjs, which exports only version metadata. There is nots.sys,ts.createProgram,ts.findConfigFile,ts.TypeFlags, etc. Anytool that calls into that classic API directly (declaration bundlers, custom
build scripts, ts-jest,
rollup-plugin-typescript2-style plugins,typescript-eslintfor type-aware rules,ts-morph, etc.) will crash orsilently misbehave the moment
typescriptresolves to 7.x for that tool,even though
tscitself works fine.Fix pattern — pin a classic-API-compatible TypeScript (6.0.3, the last
JS-based release) as an explicit dependency scoped to just the
package/tool that needs it, while keeping 7.0.2 as the repo's primary
devDependency for
tsc/editors/CI typecheck:For a workspace package whose own bundler resolves
typescriptfromthat package's own directory (verify via
require.resolve('typescript', { paths: [packageDir] })— preconstructdoes this via
resolveFrom(packageDir, 'typescript')), just add:pnpm's per-package node_modules isolation means that package now resolves
its own 6.0.3 instead of falling back up to the root's 7.0.2.
For a transitive tool with an optional peer dependency on
typescript(e.g.
typescript-eslintv5, which declarestypescript: '*'as anoptional peer) — a plain
pnpm.overrides/pnpm-workspace.yaml overridesentry with
'pkg>typescript': '6.0.3'syntax does not work, becauseoptional peers are resolved from whatever instance is nearest in the graph,
not from
overrides. UsepackageExtensionsinstead, which rewrites thepackage's own manifest to declare it as a hard dependency:
After this,
require.resolve('typescript', { paths: [thatPackageDir] })returns 6.0.3's
lib/typescript.js(the classic entrypoint) instead of7.0.2's
lib/version.cjs. Confirm with a quicknode -eresolve checkbefore assuming the fix landed — pnpm silently keeps the old lockfile
resolution ("Already up to date") until you delete
node_modulesandreinstall, or otherwise force a re-resolution.
This exact symptom hit ESLint type-aware rules here:
TypeError: Cannot read properties of undefined (reading 'Any')from@typescript-eslint/type-utilsreadingts.TypeFlags.Any— same rootcause, same fix.
If you're on a newer
typescript-eslint(v8/v9+) in your repo, checkfirst whether it has already added TS 7 support before reaching for this
workaround — v5 (used here, via
@commercetools-frontend/eslint-config-mc-app@24.12.0) predates TS 7 by awide margin and has no chance of supporting it natively.
Time taken
Roughly 1.5-2 hours of agent wall-clock time end-to-end (version research,
bump, iterative fixing, three tool-compatibility investigations, and
verification runs), most of it in the
@preconstruct/cli/typescript-eslintStrada-API investigation rather than actual
tsctype errors.Gotchas for fan-out agents
1".
npm view typescript versions --jsonat the time of this migrationshowed 7.0.2 as
latest, with 6.0.3 as a real, separate intermediatemajor worth reading release notes for even though we bumped straight to 7.
tsc --noEmitpassing clean is not sufficient to call the migrationdone. Build tooling, ESLint, and any script that does
require('typescript')directly can break even when the compiler itselfis happy, because TS 7 removed the classic JS API. Always run the actual
build and the actual CI lint/test command (not just
pnpm test) beforedeclaring success — this repo's
pnpm buildandpnpm lintboth failedsilently-differently from
tscand needed separate fixes.moduleResolution/module/targetthis repo alreadyuses before assuming you'll hit the hard-removed options. This repo was
already on
Bundler/ESNext/ESNext, so none of the hard breaks appliedhere — other repos on older configs (
node,commonjs,es2020, etc.)will likely hit several of the removed-option errors listed above.
ignoreDeprecationswon't save you for the hard-removed options — itonly covers the deprecated-but-still-working set (
moduleResolution: node,esModuleInterop: false, etc.), not the ones that are now hardcompiler errors (
target: es5,moduleResolution: classic,module: amd/umd/systemjs/none,baseUrlas a resolution root,outFile).ts-jest,ts-node,ts-morph, or a webpack/rollup/viteTypeScript plugin should expect the Strada-API-removal issue and budget
time for it — it was the majority of the effort here despite affecting
only tooling, not source code.
Summary of changes
Pathfinder migration of this repo's TypeScript major version, ahead of
fanning the same recipe out to 8 other commercetools frontend repos.
typescript:5.9.3→7.0.2(rootpackage.json)tsconfig.json: added"types": ["jest", "node"](TS 6.0+ default fortypeschanged from auto-discovery to[])generators/package.json,standalone/package.json: added a scopedtypescript@6.0.3devDependency so@preconstruct/cli's declarationbundler (which needs the classic TS Program API, removed from TS 7's
public npm package surface) keeps working
pnpm-workspace.yaml: addedpackageExtensionspinningtypescript-eslintv5's optionaltypescriptpeer to6.0.3for the sameStrada-API reason (type-aware lint rules read
ts.TypeFlags)buildGraphql()call instandalone/src/models/cart/cart/custom-line-item/custom-line-item-draft/builders.spec.tswhere TS 7's generic-default inference surfaced a real (if narrow) type
mismatch that TS 5.9 didn't catch
Verification
pnpm install— cleanpnpm typecheck(tsc --noEmit) — clean, 0 errorspnpm build(preconstruct build) — cleanpnpm jest --projects jest.{eslint,test}.config.js(this repo's actual CIgate, combining lint + tests) — 2820/2820 lint suites + 535/535 test
suites (1691/1691 tests) passing
All four checks were also run from a fully clean
node_modules(not justincrementally) to rule out stale-cache false positives.
Not merging — opening for review per the pathfinder migration process.