fix(core): pin typescript in preset dependencies so npm cannot hoist typescript 7 - #36497
Merged
Conversation
…typescript 7 `@phenomnomnominal/tsquery` declares `typescript: >3.0.0` as a peer dependency. npm auto-installs peers, so with no `typescript` in the new workspace's package.json it resolves that peer to the latest major — now 7.x — and hoists it to the workspace root. TypeScript 7's entry point only exports `version` and `versionMajorMinor`; the compiler API moved to `typescript/unstable/*`. The preset generator then fails with `ts.readConfigFile is not a function`. Whether this bites depends on hoisting order. `@nx/next` and `@nx/react` depend on tsquery directly, so its permissive peer wins the root slot over `@nx/eslint`'s concrete `~6.0.3`. In the node preset tsquery sits one level deeper and `@nx/eslint` wins instead, which is why only some presets break. Pinning `typescript` alongside the other preset dependencies puts a concrete range in package.json before the first install, so npm resolves the peer to it. angular, nest and web-components already did this. This adds nothing new to the final workspace — `@nx/js:init` already installs the same version — it just lands before the install rather than after. The apps and npm presets are excluded and split out of the branch they shared with ts and ts-standalone: neither runs a preset generator, so `@nx/js:init` never runs and typescript would be net-new there. ts-standalone is the only preset that prompts for JS vs TS, so it mirrors `@nx/js:init` and skips the pin when `js` is set.
✅ Deploy Preview for nx-dev ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for nx-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Contributor
|
View your CI Pipeline Execution ↗ for commit 3531a60
☁️ Nx Cloud last updated this comment at |
AgentEnder
approved these changes
Jul 28, 2026
FrozenPandaz
marked this pull request as ready for review
July 28, 2026 22:26
rapaglaz
pushed a commit
to rapaglaz/nx
that referenced
this pull request
Jul 29, 2026
…typescript 7 (nrwl#36497) ## Current Behavior `create-nx-workspace` fails for most framework presets: ``` ✔ Installing dependencies with npm ✖ Creating your workspace in test NX Failed to create workspace Failed to create a workspace: NX ts.readConfigFile is not a function ``` TypeScript 7 is now `latest` on npm. Its main entry point exports only `version` and `versionMajorMinor` — the compiler API moved to `typescript/unstable/*` and is not a drop-in replacement (no `readConfigFile`, `parseJsonConfigFileContent`, `resolveModuleName`, `createProgram`, or `createCompilerHost` anywhere in those subpaths, including today's `7.1.0-dev` nightly). `@phenomnomnominal/tsquery` declares `typescript: >3.0.0` as a peer dependency. npm auto-installs peers, so when the new workspace's `package.json` has no `typescript` entry, npm resolves that peer to the newest major — 7.x — and hoists it to the workspace root. The preset generator then reaches `getNeededCompilerOptionOverrides` and calls `ts.readConfigFile` on a module that no longer has it. Whether this bites depends on npm hoisting order, which is why only some presets break: ``` next: @nx/next → tsquery → typescript@7.0.2 ← depth 1, wins the root slot @nx/next → @nx/eslint → typescript@6.0.3 ← depth 2, gets nested node: @nx/node → @nx/eslint → typescript@6.0.3 ← wins the root slot @nx/node → @nx/jest → tsquery ← deduped to 6.0.3 ``` `angular`, `nest` and `web-components` already pinned `typescript` in their preset dependencies and were unaffected. Verified on `create-nx-workspace@23.1.0` with npm: | preset | result | | --- | --- | | `angular-monorepo` (pinned) | exit 0, root `typescript` 6.0.3 | | `next` (unpinned) | exit 1, `ts.readConfigFile is not a function` | ## Expected Behavior `getPresetDependencies` pins `typescript` for the remaining presets that scaffold a TypeScript project: express, next (+ standalone), vue, nuxt, react (+ standalone), react-native, expo, node, ts and ts-standalone. The pin lands in `package.json` before the first install, so npm resolves tsquery's peer against it instead of against `latest`. This adds nothing to the final workspace. `@nx/js:init` already installs the same `~6.0.3`; the pin only changes *when* it is written — before the install rather than after — which is what npm needs in order to resolve the peer correctly. `apps` and `npm` are split out of the case they shared with the TS presets and deliberately left unpinned: neither runs a preset generator (`preset.ts` returns immediately for `apps`, and `new.ts` skips `generatePreset` for `npm`), so `@nx/js:init` never runs and `typescript` would be net-new there. Neither pulls tsquery, so neither is exposed. `ts-standalone` is the only preset that forwards `js` to its generator (`preset.ts:324`) and the only one that prompts for JS vs TS, so its pin mirrors `@nx/js:init` and is skipped when `js` is set. ### Notes for reviewers - This makes workspace creation deterministic; it does not add TypeScript 7 support. A workspace that installs TypeScript 7 deliberately still hits the same `TypeError` from the project graph via the bare `require('typescript')` in `packages/nx/src/plugins/js/utils/typescript.ts`. That is nrwl#36306 and is out of scope here. - Tests: 3 existing assertions in `new.spec.ts` (react/vue/nuxt) updated to include `typescript`, matching the existing angular assertion. 3 new tests cover the split — `apps`/`npm` stay TypeScript-free, `ts` gets the pin, and `ts-standalone --js` does not. ## Related Issue(s) Related to nrwl#36306 (Nx does not yet support the TypeScript 7 API). That issue is **not** fixed by this PR and should stay open. Fixes N/A <!-- polygraph-session-start --> --- [View session information ↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/Pin-typescript-in-create-nx-workspace-preset-dependencies-84db677c) <!-- polygraph-session-end -->
FrozenPandaz
added a commit
that referenced
this pull request
Jul 29, 2026
…typescript 7 (#36497) ## Current Behavior `create-nx-workspace` fails for most framework presets: ``` ✔ Installing dependencies with npm ✖ Creating your workspace in test NX Failed to create workspace Failed to create a workspace: NX ts.readConfigFile is not a function ``` TypeScript 7 is now `latest` on npm. Its main entry point exports only `version` and `versionMajorMinor` — the compiler API moved to `typescript/unstable/*` and is not a drop-in replacement (no `readConfigFile`, `parseJsonConfigFileContent`, `resolveModuleName`, `createProgram`, or `createCompilerHost` anywhere in those subpaths, including today's `7.1.0-dev` nightly). `@phenomnomnominal/tsquery` declares `typescript: >3.0.0` as a peer dependency. npm auto-installs peers, so when the new workspace's `package.json` has no `typescript` entry, npm resolves that peer to the newest major — 7.x — and hoists it to the workspace root. The preset generator then reaches `getNeededCompilerOptionOverrides` and calls `ts.readConfigFile` on a module that no longer has it. Whether this bites depends on npm hoisting order, which is why only some presets break: ``` next: @nx/next → tsquery → typescript@7.0.2 ← depth 1, wins the root slot @nx/next → @nx/eslint → typescript@6.0.3 ← depth 2, gets nested node: @nx/node → @nx/eslint → typescript@6.0.3 ← wins the root slot @nx/node → @nx/jest → tsquery ← deduped to 6.0.3 ``` `angular`, `nest` and `web-components` already pinned `typescript` in their preset dependencies and were unaffected. Verified on `create-nx-workspace@23.1.0` with npm: | preset | result | | --- | --- | | `angular-monorepo` (pinned) | exit 0, root `typescript` 6.0.3 | | `next` (unpinned) | exit 1, `ts.readConfigFile is not a function` | ## Expected Behavior `getPresetDependencies` pins `typescript` for the remaining presets that scaffold a TypeScript project: express, next (+ standalone), vue, nuxt, react (+ standalone), react-native, expo, node, ts and ts-standalone. The pin lands in `package.json` before the first install, so npm resolves tsquery's peer against it instead of against `latest`. This adds nothing to the final workspace. `@nx/js:init` already installs the same `~6.0.3`; the pin only changes *when* it is written — before the install rather than after — which is what npm needs in order to resolve the peer correctly. `apps` and `npm` are split out of the case they shared with the TS presets and deliberately left unpinned: neither runs a preset generator (`preset.ts` returns immediately for `apps`, and `new.ts` skips `generatePreset` for `npm`), so `@nx/js:init` never runs and `typescript` would be net-new there. Neither pulls tsquery, so neither is exposed. `ts-standalone` is the only preset that forwards `js` to its generator (`preset.ts:324`) and the only one that prompts for JS vs TS, so its pin mirrors `@nx/js:init` and is skipped when `js` is set. ### Notes for reviewers - This makes workspace creation deterministic; it does not add TypeScript 7 support. A workspace that installs TypeScript 7 deliberately still hits the same `TypeError` from the project graph via the bare `require('typescript')` in `packages/nx/src/plugins/js/utils/typescript.ts`. That is #36306 and is out of scope here. - Tests: 3 existing assertions in `new.spec.ts` (react/vue/nuxt) updated to include `typescript`, matching the existing angular assertion. 3 new tests cover the split — `apps`/`npm` stay TypeScript-free, `ts` gets the pin, and `ts-standalone --js` does not. ## Related Issue(s) Related to #36306 (Nx does not yet support the TypeScript 7 API). That issue is **not** fixed by this PR and should stay open. Fixes N/A <!-- polygraph-session-start --> --- [View session information ↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/Pin-typescript-in-create-nx-workspace-preset-dependencies-84db677c) <!-- polygraph-session-end --> (cherry picked from commit 0abf7a4)
polygraph-snapshot-app Bot
pushed a commit
that referenced
this pull request
Aug 11, 2026
…typescript 7 (#36497) ## Current Behavior `create-nx-workspace` fails for most framework presets: ``` ✔ Installing dependencies with npm ✖ Creating your workspace in test NX Failed to create workspace Failed to create a workspace: NX ts.readConfigFile is not a function ``` TypeScript 7 is now `latest` on npm. Its main entry point exports only `version` and `versionMajorMinor` — the compiler API moved to `typescript/unstable/*` and is not a drop-in replacement (no `readConfigFile`, `parseJsonConfigFileContent`, `resolveModuleName`, `createProgram`, or `createCompilerHost` anywhere in those subpaths, including today's `7.1.0-dev` nightly). `@phenomnomnominal/tsquery` declares `typescript: >3.0.0` as a peer dependency. npm auto-installs peers, so when the new workspace's `package.json` has no `typescript` entry, npm resolves that peer to the newest major — 7.x — and hoists it to the workspace root. The preset generator then reaches `getNeededCompilerOptionOverrides` and calls `ts.readConfigFile` on a module that no longer has it. Whether this bites depends on npm hoisting order, which is why only some presets break: ``` next: @nx/next → tsquery → typescript@7.0.2 ← depth 1, wins the root slot @nx/next → @nx/eslint → typescript@6.0.3 ← depth 2, gets nested node: @nx/node → @nx/eslint → typescript@6.0.3 ← wins the root slot @nx/node → @nx/jest → tsquery ← deduped to 6.0.3 ``` `angular`, `nest` and `web-components` already pinned `typescript` in their preset dependencies and were unaffected. Verified on `create-nx-workspace@23.1.0` with npm: | preset | result | | --- | --- | | `angular-monorepo` (pinned) | exit 0, root `typescript` 6.0.3 | | `next` (unpinned) | exit 1, `ts.readConfigFile is not a function` | ## Expected Behavior `getPresetDependencies` pins `typescript` for the remaining presets that scaffold a TypeScript project: express, next (+ standalone), vue, nuxt, react (+ standalone), react-native, expo, node, ts and ts-standalone. The pin lands in `package.json` before the first install, so npm resolves tsquery's peer against it instead of against `latest`. This adds nothing to the final workspace. `@nx/js:init` already installs the same `~6.0.3`; the pin only changes *when* it is written — before the install rather than after — which is what npm needs in order to resolve the peer correctly. `apps` and `npm` are split out of the case they shared with the TS presets and deliberately left unpinned: neither runs a preset generator (`preset.ts` returns immediately for `apps`, and `new.ts` skips `generatePreset` for `npm`), so `@nx/js:init` never runs and `typescript` would be net-new there. Neither pulls tsquery, so neither is exposed. `ts-standalone` is the only preset that forwards `js` to its generator (`preset.ts:324`) and the only one that prompts for JS vs TS, so its pin mirrors `@nx/js:init` and is skipped when `js` is set. ### Notes for reviewers - This makes workspace creation deterministic; it does not add TypeScript 7 support. A workspace that installs TypeScript 7 deliberately still hits the same `TypeError` from the project graph via the bare `require('typescript')` in `packages/nx/src/plugins/js/utils/typescript.ts`. That is #36306 and is out of scope here. - Tests: 3 existing assertions in `new.spec.ts` (react/vue/nuxt) updated to include `typescript`, matching the existing angular assertion. 3 new tests cover the split — `apps`/`npm` stay TypeScript-free, `ts` gets the pin, and `ts-standalone --js` does not. ## Related Issue(s) Related to #36306 (Nx does not yet support the TypeScript 7 API). That issue is **not** fixed by this PR and should stay open. Fixes N/A <!-- polygraph-session-start --> --- [View session information ↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/Pin-typescript-in-create-nx-workspace-preset-dependencies-84db677c) <!-- polygraph-session-end -->
polygraph-snapshot-app Bot
pushed a commit
that referenced
this pull request
Aug 11, 2026
## Current Behavior `apps`/`npm` preset workspaces pin no root typescript, so npm resolves tsquery's open `typescript: >3.0.0` peer to 7.x. TS 7's CJS entry drops the compiler API - tsquery crashes at load and `readTsConfig` sees `ts.sys` undefined. Breaks e2e-vite, e2e-web, e2e-rspack on npm. e2e-expo: Jest's NODE_ENV=test leaves @expo/cli's fusebox debugger shell on, and dev-middleware 0.85+ throws "DefaultToolLauncher must be mocked or overridden in tests". Behind it, `@nx/expo:serve` waits for a `Bundled` line that only arrives once something requests the page, deadlocking @nx/cypress. ## Expected Behavior `apps`/`npm` presets pin typescript, like every other preset (#36497). Expo e2e sets EXPO_UNSTABLE_HEADLESS=1 through a shared `setupExpoEnv` helper. `@nx/expo:serve` resolves once the packager accepts connections. ## Related Issue(s) Fixes NXC-4612
jaysoo
added a commit
that referenced
this pull request
Aug 11, 2026
## Current Behavior `apps`/`npm` preset workspaces pin no root typescript, so npm resolves tsquery's open `typescript: >3.0.0` peer to 7.x. TS 7's CJS entry drops the compiler API - tsquery crashes at load and `readTsConfig` sees `ts.sys` undefined. Breaks e2e-vite, e2e-web, e2e-rspack on npm. e2e-expo: Jest's NODE_ENV=test leaves @expo/cli's fusebox debugger shell on, and dev-middleware 0.85+ throws "DefaultToolLauncher must be mocked or overridden in tests". Behind it, `@nx/expo:serve` waits for a `Bundled` line that only arrives once something requests the page, deadlocking @nx/cypress. ## Expected Behavior `apps`/`npm` presets pin typescript, like every other preset (#36497). Expo e2e sets EXPO_UNSTABLE_HEADLESS=1 through a shared `setupExpoEnv` helper. `@nx/expo:serve` resolves once the packager accepts connections. ## Related Issue(s) Fixes NXC-4612
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.
Current Behavior
create-nx-workspacefails for most framework presets:TypeScript 7 is now
lateston npm. Its main entry point exports onlyversionandversionMajorMinor— the compiler API moved totypescript/unstable/*and is not a drop-in replacement (noreadConfigFile,parseJsonConfigFileContent,resolveModuleName,createProgram, orcreateCompilerHostanywhere in those subpaths, including today's7.1.0-devnightly).@phenomnomnominal/tsquerydeclarestypescript: >3.0.0as a peer dependency. npm auto-installs peers, so when the new workspace'spackage.jsonhas notypescriptentry, npm resolves that peer to the newest major — 7.x — and hoists it to the workspace root. The preset generator then reachesgetNeededCompilerOptionOverridesand callsts.readConfigFileon a module that no longer has it.Whether this bites depends on npm hoisting order, which is why only some presets break:
angular,nestandweb-componentsalready pinnedtypescriptin their preset dependencies and were unaffected. Verified oncreate-nx-workspace@23.1.0with npm:angular-monorepo(pinned)typescript6.0.3next(unpinned)ts.readConfigFile is not a functionExpected Behavior
getPresetDependenciespinstypescriptfor the remaining presets that scaffold a TypeScript project: express, next (+ standalone), vue, nuxt, react (+ standalone), react-native, expo, node, ts and ts-standalone. The pin lands inpackage.jsonbefore the first install, so npm resolves tsquery's peer against it instead of againstlatest.This adds nothing to the final workspace.
@nx/js:initalready installs the same~6.0.3; the pin only changes when it is written — before the install rather than after — which is what npm needs in order to resolve the peer correctly.appsandnpmare split out of the case they shared with the TS presets and deliberately left unpinned: neither runs a preset generator (preset.tsreturns immediately forapps, andnew.tsskipsgeneratePresetfornpm), so@nx/js:initnever runs andtypescriptwould be net-new there. Neither pulls tsquery, so neither is exposed.ts-standaloneis the only preset that forwardsjsto its generator (preset.ts:324) and the only one that prompts for JS vs TS, so its pin mirrors@nx/js:initand is skipped whenjsis set.Notes for reviewers
TypeErrorfrom the project graph via the barerequire('typescript')inpackages/nx/src/plugins/js/utils/typescript.ts. That is Nx breaks with TypeScript 7: programmatic API completely restructured #36306 and is out of scope here.new.spec.ts(react/vue/nuxt) updated to includetypescript, matching the existing angular assertion. 3 new tests cover the split —apps/npmstay TypeScript-free,tsgets the pin, andts-standalone --jsdoes not.Related Issue(s)
Related to #36306 (Nx does not yet support the TypeScript 7 API). That issue is not fixed by this PR and should stay open.
Fixes N/A
View session information ↗