A task that depends on a large package carried its entire transitive
external closure as individual External instructions — ~1000 per task
in this repo. Each was hashed and folded separately, and the per-task
instruction list (sorted, deduped, assembled) grew accordingly.
After sort+dedup, collapse all External instructions for a task into a
single ExternalDependencies instruction holding the sorted name list.
The hasher folds the closure once (cached per closure, so tasks sharing
a closure reuse it) instead of dispatching one instruction per member.
In `nx build devkit` this drops individual External instruction
evaluations from 8172 to 0, replaced by 5 collapsed ExternalDependencies
folds. The graph command expands `external-deps:[...]` back into
individual externals for its inputs view.
NOTE: this changes computed task hash values, so it invalidates existing
caches on upgrade. Ship in a major. Snapshots updated accordingly.
## Current Behavior
After a run, Nx prints the cached-task summary but gives no insight into
how the
run's wall-clock time was spent or how to make it faster — no breakdown
of the
critical-path floor versus parallelism contention, and no actionable
guidance.
## Expected Behavior
Every run ends with a concise performance report:
- **Headline stats** — run duration, cache result (hit rate, or `Skipped
(--skip-nx-cache)`), critical path (the dependency floor: how long the
run
would take with unlimited slots), and recoverable time (wall-clock lost
to slot
contention — recoverable with a higher `--parallel` or more machines).
- **Targeted recommendations**, one per lever and ordered
cheapest-action-first:
speed up/split the longest critical-path tasks (listed inline), raise
`--parallel`, distribute with Nx Agents, enable remote cache, or drop
`--skip-nx-cache`. Only the levers that actually apply to the run are
shown.
- **A docs link** — a clickable OSC 8 hyperlink where supported, a plain
auto-linked URL in CI, carrying a `utm=performance-report` tag.
- **Where it renders** — in the Terminal UI, inside the exit-countdown
popup
(pressing `q` mid-run still shows the original exit dialog); otherwise
(non-TUI,
or a single task) it's flushed to the terminal after the run summary.
The report
is delivered exactly once.
## Related Issue(s)
N/A
## Notes
- The analysis (`PerformanceAnalysis`) is a **pure function of the
timings the
lifecycle collects** — no coupling to the orchestrator/scheduler. It
derives the
occupancy timeline, the critical path, and the overhead split entirely
from task
start/end timestamps.
- The report is built once at the end and delivered either through the
native TUI
exit popup or a terminal flush, deduped so it never prints twice.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
The `set-ts-jest-isolated-modules` migration (23.1) sets
`isolatedModules: true` on ts-jest spec configs. This is correct for
almost all projects, but can surface typecheck failures - TypeScript 5.9
deprecated tsconfig options, and isolatedModules incompatibilities -
with no guidance for resolving them.
## Expected Behavior
Adds a follow-up prompt migration `update-23-1-0-verify-typecheck`
(prompt-only, gated on `ts-jest >=29.2.0`) that asks the agent to run
`nx run-many -t typecheck` and points it at the common failures and
their remedies. A companion documentation page covers the details,
including the runtime test break that typecheck cannot catch
(napi/const-enum packages).
Scoped to typecheck only - build and e2e are intentionally out of scope
(too slow to gate a migration on).
## Related Issue(s)
NXC-4591
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/nx-23.1.0-beta.0-Migration-24e91166)
<!-- polygraph-session-end -->
## Current Behavior
The docs top banner was used for the "AI <3 Monorepos" conference
(DOC-521) and auto-expired after June 24. There's currently no banner
promoting the Polygraph Product Hunt launch.
## Expected Behavior
Repurposes the existing time-boxed promo bar for the Polygraph Product
Hunt launch:
- Links the banner to https://www.producthunt.com/products/polygraph
- Copy: **🚀 We're live on Product Hunt** · _Vote or leave a comment
today!_
- `activeUntil` set to `2026-06-26T04:00:00Z` (midnight ET, June 25) —
the banner is build-time gated and auto-hides on the first rebuild after
that
- Removed the now-unused conference `__heart`/`__desc` styles
No new wiring needed — the banner renders site-wide through
`PageFrame.astro` while active.
## Related Issue(s)
N/A — launch-day promo.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/docs-conf-banner-e2599b5b)
<!-- polygraph-session-end -->
<img width="1282" height="227" alt="image"
src="https://github.com/user-attachments/assets/c82da986-2050-48a8-b74d-62e0ac810975"
/>
<img width="548" height="223" alt="image"
src="https://github.com/user-attachments/assets/a3e916ec-fa05-4777-85db-94929326294c"
/>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Current Behavior
The first task run against a freshly started Nx daemon is ~3-5s slower
than subsequent runs. Repro:
```
nx reset --only-daemon && nx show projects # warm the daemon + project graph
nx build js # first build — slow
nx build js # second build — fast
```
The overhead is a full project-graph recompute. On the first build, Nx
restores cached task outputs — including the `nx` package's generated
native bindings, which live in the watched `packages/nx/src/native/`
source tree — with byte-identical content but new inodes. The file
watcher legitimately reports these as changes, and the daemon recomputes
the entire project graph, cold-reloading every plugin worker (~3s of the
overhead), even though no file content actually changed. Subsequent runs
skip the cache restore ("existing outputs match the cache, left as is"),
so no events fire and there is no recompute.
Daemon-side evidence: the slow `HASH_TASKS` batch reports `Handling
time: 3004ms` while the native `hash_plans` inside it completes in ~7ms
— the time is spent blocked on the recompute, not on hashing.
## Expected Behavior
A file rewritten with identical content does not trigger a project-graph
recompute. The daemon recomputes only when something actually changes —
a file's content hash changes, a new path appears, or a file is deleted
— so the first task run against a fresh daemon no longer pays for a
needless cold recompute.
Implementation:
- **native** (`WorkspaceContext::update_files`): returns only the files
whose content actually changed (a new path, or a hash differing from the
existing entry) instead of every updated path. The old hash was already
available in the file map and was previously discarded.
- **daemon** (`scheduleProjectGraphRecomputation`): hashes watched
changes once per watcher batch and gates `kickOffRecompute` on real
changes; the hashes are threaded through `collectedUpdatedFiles` so the
recompute body no longer re-hashes (keeping the content check off the
stale-retry path, which would otherwise see "no change" after the first
pass already updated the context).
This also avoids needless recomputes from `git checkout` back to
identical content, formatters that change nothing, and `touch`.
Verification: new Rust unit test
(`incremental_update_reports_only_real_content_changes`), existing
native watch suite still green, and a direct check that
`incrementalUpdate` returns `{}` for identical bytes and `[changed]` for
a real change.
**Behavior note:** a side effect is that `nx watch` no longer fires on
pure no-op rewrites (content identical).
## Related Issue(s)
N/A
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/Fix-daemon-performance-bug---nrwl-nx-5364e560)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
## What
Corrects the migration added in #36100. The
`change-plugin-version-0-1-23` migration was registered at nx
`23.0.0-rc.5`, but the current release line is `23.1.0-beta`, so it
should run at `23.1.0-beta.4`. The migration folder is corrected
accordingly (`23-0-0` → `23-1-0`).
## Changes
- `packages/gradle/migrations.json` — `version` `23.0.0-rc.5` →
`23.1.0-beta.4`; `factory` / `documentation` paths → `23-1-0`
- Moved `change-plugin-version-0-1-23.ts` / `.md` from
`packages/gradle/src/migrations/23-0-0/` to
`packages/gradle/src/migrations/23-1-0/`
The plugin version (`0.1.23`) is unchanged.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/lively-ferret-e9ee99bb)
<!-- polygraph-session-end -->
## Current Behavior
`nx migrate` resolves package versions and fetches migration metadata by
installing into a throwaway temp directory. Two problems surface in pnpm
workspaces:
1. **Preapproved packages were downgraded.** The per-package migration
cascade extracted versions straight from the migration descriptors
without running them through min-release-age policy resolution. Packages
explicitly preapproved to bypass the cooldown gate (e.g.
`npmPreapprovedPackages` in `.yarnrc.yml`, or pnpm's
`minimumReleaseAgeExclude`) were still downgraded to older "stable"
versions.
2. **The temp workspace manifest was invalid for older pnpm.** When
copying `pnpm-workspace.yaml` into the temp dir, nx deleted the
`packages` field entirely. pnpm `< 10.5` — including the bundled default
pnpm that corepack falls back to inside the temp dir (which carries no
`packageManager` pin) — rejects a workspace manifest whose `packages`
field is missing or empty:
```
ERROR packages field missing or empty
...
NX Failed to fetch migrations for nx@latest
```
This breaks `nx migrate` even when the user's real `pnpm-workspace.yaml`
is perfectly valid.
## Expected Behavior
1. The migration cascade resolves each version through the
min-release-age policy via the new `resolveVersionForCascade()`, so
preapproved packages keep the version their package-manager config
allows instead of being downgraded.
2. The temp `pnpm-workspace.yaml` keeps a non-empty `packages` field
that every supported pnpm accepts. The member globs (which only resolve
in the real workspace) are replaced with a self-reference (`packages:
['.']`) — the temp dir genuinely is a single-package workspace — rather
than dropped. `patchedDependencies` (relative patch paths) is still
dropped.
Both changes ship with regression tests.
## Related Issue(s)
No public issue — surfaced via an internal report of `nx migrate`
failing in a downstream pnpm monorepo (nx 23.0.0-rc.4, pnpm 10.x via
corepack).
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## What
Bumps `dev.nx.gradle.project-graph` from `0.1.22` to `0.1.23` and adds
the standard migration so existing workspaces pick up the new plugin.
This ships the fix from #36099 (track `Copy` / `Sync` and AGP merge task
outputs in dependent task inputs), which lives in the Gradle companion
plugin and only takes effect once the plugin version is published and
consumed.
## Changes
- `packages/gradle/src/utils/versions.ts` — `gradleProjectGraphVersion`
→ `0.1.23`
- `packages/gradle/project-graph/build.gradle.kts` — `version` →
`0.1.23`
-
`packages/gradle/src/migrations/23-0-0/change-plugin-version-0-1-23.ts`
/ `.md` — migration that updates the plugin version in build files and
version catalogs
- `packages/gradle/migrations.json` — migration entry, triggered at nx
`23.0.0-rc.5`
Follows the recurring `nx-gradle-plugin-version-bump` pattern (same
5-file footprint as the previous bump to `0.1.22`).
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/lively-ferret-e9ee99bb)
<!-- polygraph-session-end -->
## Current Behavior
The jest-30 path of `@nx/jest` bumps ts-jest to 29.4.x. On the CommonJS
jest path, ts-jest 29.2+ falls back to `moduleResolution: node10` when
`bundler` is invalid alongside the forced `module: commonjs` (TypeScript
< 6). `node10` ignores package `exports` maps, so workspace libraries
that expose types only via `exports` fail with TS2307 during the ts-jest
type check.
## Expected Behavior
A new migration sets `isolatedModules: true` in `tsconfig.spec.json` for
ts-jest projects on TypeScript < 6 ts-solution workspaces (that do not
already enable it), so ts-jest transpiles per file and the cross-file
type resolution no longer runs. TypeScript >= 6 (where `bundler` is
valid with `commonjs` and resolves `exports`) is unaffected.
## Related Issue(s)
NXC-4591
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/ts-jest-broken-83851e61)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Problem
A consuming Gradle task that `dependsOn` a `Copy`/`Sync` task did not
pick up that task's outputs in its Nx `inputs`. Example:
`processResources` (a `Copy`) feeds `classes`, but `classes` only
declared `{ "dependentTasksOutputFiles": "**/*.class" }`, so changing a
resource did not invalidate the `classes` cache even though
`processResources` is a declared `dependsOn`.
## Root cause
`inferExtensionsFromInputProperties` in `TaskUtils.kt` predicts
dependent-task output extensions from task *type*, and only handled
compile (`AbstractCompile` / Kotlin compile), archive
(`AbstractArchiveTask`), and test tasks. `Copy` / `Sync` tasks
(including `ProcessResources`) were not handled. On a clean build their
output directories are empty, so the file-based output discovery also
finds nothing, and no extension is inferred for them.
## Fix
Predict extensions from a `Copy` / `Sync` task's *source*
(`inputs.files`). The source files exist at graph-construction time,
unlike the (not-yet-produced) outputs. The `dependentTasksOutputFiles`
glob is still matched later, at hash time, once the dependency has run
and its outputs exist, so predicting the extension set from the source
is sufficient and clean-build-safe.
Also matches AGP merge/copy tasks (`MergeResources`,
`MergeSourceSetFolders`, `ProcessApplicationManifest`,
`MergeJavaResourceTask`) through a reflection-based allow-list, since
AGP is not on the plugin's classpath. Missing classes resolve to `null`
and are skipped, so non-Android projects are unaffected.
## Tests
`ProcessTaskUtilsTest`:
- `Copy` dependency, clean build (`.conf` / `.json` source, no
materialized outputs) → consumer gets `**/*.conf` + `**/*.json`.
- `Sync` dependency, same scenario → same result.
- `Jar` dependency still contributes only its `archiveExtension`, not
source extensions.
- Graceful degradation when AGP classes are absent from the classpath.
`./gradlew :gradle-project-graph:test --tests "ProcessTaskUtilsTest"`
passes (39 tests, 0 failures); `ktfmtCheck` is clean.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/lively-ferret-e9ee99bb)
<!-- polygraph-session-end -->
## Current Behavior
`e2e/vite/src/vite-ts-solution.test.ts` ("should generate app and
consume libraries with different bundlers") intermittently fails with
`Command timed out after 300s: build react-app...`.
The single `build <app>` cold-builds the app's six dependency libraries
first (esbuild / rollup / swc / tsc / vite / none) and then the app,
serially, in a freshly created TS-solution workspace. Each individual
build is sub-second, but the serial total (plus first-run tsc
project-reference build, bundler cold starts, and graph computation) can
exceed the default 5-minute `runCLI` timeout under CI load. The command
hit that 300s `runCLI` cap (separate from the test's own jest timeout).
## Expected Behavior
The `build` and `typecheck` invocations get a 10-minute `runCLI`
timeout, and the test's jest timeout is raised to 20 minutes to cover
the seven generators + install + sync + build + typecheck end-to-end. CI
load no longer tips the cold multi-library build over its budget.
## Related Issue(s)
N/A — CI flake fix.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/nrwl-nx---fix-rspack-test-PR-205fdf70)
<!-- polygraph-session-end -->
## Current Behavior
`e2e/release/src/release-publishable-libraries.test.ts` intermittently
fails with `Command timed out after 300s: release --specifier 0.0.3
--yes`, with output frozen at `NX Executing pre-version command`.
`nx release` runs a pre-version command (`<pm> dlx nx run-many -t
build`) that re-resolves and installs `nx` from the local verdaccio
registry on every invocation before building. Under CI/registry load
this can exceed the default 5-minute `runCLI` timeout. Because the tests
share a single git-tag chain (each test bumps to the next version and
tags it), a timeout also **cascades**: the failed test never creates its
`vX` tag, so the next test resolves the wrong "current version" and its
inline snapshot fails too (e.g. the angular test failing only because
the react test timed out).
## Expected Behavior
Every `release` invocation gets a generous 10-minute timeout, so the
pre-version `dlx` install has room to complete under load. This removes
the timeout and, with it, the downstream snapshot cascade.
## Related Issue(s)
N/A — CI flake fix.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/nrwl-nx---fix-rspack-test-PR-205fdf70)
<!-- polygraph-session-end -->
## Current Behavior
The `e2e/maven/src/maven.test.ts` cases "should build Maven project with
dependencies without batch mode" and "should support targetNamePrefix
option" intermittently time out (e.g. `Command timed out after 600s: run
app:install --no-batch`).
`nx run <project>:install --no-batch` fans the build out into one task
per Maven lifecycle phase per module (~87 tasks), each spawning a fresh
`mvn` JVM that re-scans the whole reactor. That serial fan-out alone
runs ~450-550s even with a warm `~/.m2`. The `install` run additionally
passed `verbose: true`, which sets `NX_VERBOSE_LOGGING=true` and makes
every one of the ~87 forks run `mvn -X` (full debug) — a large amount of
extra per-fork log I/O that pushed it over the previous 10-minute
budget.
## Expected Behavior
- The `install` run no longer passes `verbose: true`, so the ~87 forks
don't each run `mvn -X`. The assertions (`BUILD SUCCESS` + jar
existence) don't need verbose output.
- Both `--no-batch` runs get a 15-minute timeout, comfortably above the
inherent serial fan-out floor, so CI load no longer tips them over.
## Related Issue(s)
N/A — CI flake fix.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/nrwl-nx---fix-rspack-test-PR-205fdf70)
<!-- polygraph-session-end -->
## Current Behavior
The `e2e/react/src/react-rspack.test.ts` test "should be able to use
Rspack to build and test apps" generates the app without a `--port`, so
the dev/preview server falls back to the framework default of `4200`.
When e2e tests run in parallel on the same agent, multiple tests end up
fighting over port `4200`. One preview server gets killed (exit code
`137` / SIGKILL) and Playwright fails with `NS_ERROR_CONNECTION_REFUSED`
/ `Connection refused`.
The first test ("should generate app with custom port") hardcoded
`8081`, which carries the same parallel-collision risk.
## Expected Behavior
Both tests reserve a unique port via `reservePort()` (the established
pattern used across the e2e suite, e.g.
`e2e/react/src/react-rsbuild.test.ts`) and pin it on the generate
command, so parallel tests never collide on a shared default port.
- Test 1 now reserves a port instead of hardcoding `8081` — the
custom-port assertion still holds since it checks `port: ${customPort}`.
- Test 2 now reserves a port and passes `--port=${port}` so the preview
server and Playwright use a collision-free port.
## Related Issue(s)
N/A — CI flake fix.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/nrwl-nx---fix-rspack-test-PR-205fdf70)
<!-- polygraph-session-end -->
## Current Behavior
`nx migrate --include=optional` crashes with `Cannot read properties of
undefined (reading 'version')`. The target package sits in its own
required closure, so the Migrator drops its `packageUpdates` entry;
`generateMigrationsJsonAndUpdatePackageJson` then reads `.version` off
it unguarded when building the `writePromptMigrationFiles` argument.
## Expected Behavior
Resolve the target version defensively
(`packageUpdates[walkedTargetPackage]?.version ?? opts.targetVersion`,
the same form already used for completion analytics); optional migrate
completes without crashing.
## Related Issue(s)
Fixes NXC-4590
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/migrate-error-c1c6a147)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`@nx/expo` supports Expo SDK 53–55 (latest/default 55) after the recent
version-lane refactor. It does not yet support **SDK 56**.
Separately, the plugin's Metro and executor wiring predates Expo's SDK
55
`@expo/metro` repackaging, so generated **SDK 55+** apps fail at runtime
even
on the existing lanes:
- `expo start` / web bundling crashes with
`TypeError: Cannot read properties of undefined (reading
'transformFile')` -
`withNxMetro` merges via the standalone `metro-config` and forces
`projectRoot` to the workspace root, which collides with Expo's bundled
`@expo/metro` and breaks the babel transformer's `.babelrc.js`
resolution.
- `nx prebuild` / `start` executors throw `Cannot find module
'@expo/cli/build/bin/cli.js'` because SDK 55+ ships `@expo/cli` with an
`exports` map (`"./*": "./*.js"`), so the hardcoded bin subpath no
longer
resolves.
## Expected Behavior
Adds an Expo **SDK 56** install lane (RN 0.85.3, React 19.2, `@expo/cli`
~56.1.14, `@expo/metro-config` ~56.0.13, `jest-expo` ~56.0.4) as the
default
for new projects, on top of the existing 53–55 lanes, and fixes the SDK
55+
runtime wiring (benefits 55 and 56):
- `withNxMetro` and the Nx resolver prefer `@expo/metro/metro-config` /
`@expo/metro/metro-resolver` (fallback to the standalone packages for
53/54),
and no longer override `projectRoot` to the workspace root on SDK 55+.
- Executors resolve the Expo CLI via the stable `expo/bin/cli` entry
instead of
`@expo/cli/build/bin/cli`.
- New SDK 55+ apps no longer install standalone
`metro-config`/`metro-resolver`
or `@expo/metro-config` directly (the generated `metro.config.js`
extends
`expo/metro-config`); those packages are now optional peer dependencies.
- Adds an AI upgrade-instructions migration for moving workspaces to SDK
56.
Verified by generating a workspace from a locally-published build:
`expo start --web` bundles successfully and `expo-doctor`'s
"`@expo/metro-config` installed directly" check passes.
## Related Issue(s)
Fixes#35714
---------
Co-authored-by: jithin_vijayan <jithinvijayan@vyaparapp.in>
Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
## Current Behavior
To support TypeScript NodeNext-style relative imports under Node's
native type
stripping, Nx registers a small ESM resolution hook that rewrites
`.js`/`.mjs`/`.cjs` specifiers to their `.ts`/`.mts`/`.cts` sources. It
did this
with `module.register()`.
`module.register()` is runtime-deprecated on Node 25.9+ / 26+ (DEP0205),
so
loading a `.ts` config emitted a warning. For example, building a
project with a
TypeScript webpack config:
```
> webpack-cli build
(node:839660) [DEP0205] DeprecationWarning: `module.register()` is deprecated. Use `module.registerHooks()` instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
```
The build still succeeded; the warning was just noise.
## Expected Behavior
Nx now prefers `module.registerHooks()` (added in Node 22.15.0 /
23.5.0), which
runs the resolve hook synchronously in-thread and is not deprecated. No
more
`DEP0205` warning on Node 22.15+ / 24 / 26.
It falls back to `module.register()` only on older supported runtimes
that lack
`registerHooks` — Node `22.12.0`–`22.14.x` (within the current
`^22.12.0`
support floor, and CI-tested at `22.13.0`). On those versions
`module.register()`
is not yet deprecated, so the fallback stays silent.
Implementation notes:
- Added `nodeNextEsmResolveHook`, a synchronous in-thread twin of the
existing
inlined `data:`-module resolver (`NODENEXT_ESM_RESOLVER_SOURCE`). With
`registerHooks`, `nextResolve` throws synchronously rather than
rejecting a
promise, so the hook uses plain try/catch instead of `await`.
- The existing `isTsTranspilerPreloaded()` skip is kept for both paths
so
resolver coverage doesn't vary by Node version.
- The inlined `data:` source is retained for the fallback and marked as
such.
- Added unit tests mirroring all existing resolver cases against the new
synchronous hook.
The third-party ESM loader registration in `forceRegisterEsmLoader`
(`@swc-node/register/esm` / `ts-node/esm`) intentionally still uses
`module.register()`: those are asynchronous worker-thread loaders with
no
synchronous `registerHooks` equivalent, and that path only fires in a
niche
escalation (top-level await + TS syntax native strip can't handle).
## Related Issue(s)
N/A
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The templates gallery renders SaaS and Mobile filter buttons, but no
template uses those categories, so both filters show an empty list.
## Expected Behavior
Only categories that have templates appear as filters. Removes the
unused SaaS and Mobile categories.
## Related Issue(s)
N/A
This PR adds the `/docs/templates` section to docs where we showcase
official templates that are maintained by the Nx team.
Potential follow-up work for CLI is to make these browseable when
running CNW.
Preview:
https://deploy-preview-36062--nx-docs.netlify.app/docs/templates
## Current Behavior
`nx migrate` to 23.1.0 bumps `react` to `^19` for every React-18
workspace. Remix v2 (`@remix-run/react`) peers `react@^18` and does not
support React 19, so Remix apps end up with a split React tree (a forced
18 copy beside the new 19) and hydration crashes (React #418).
## Expected Behavior
The React 19 `packageJsonUpdate` is skipped when `@remix-run/react` is
present, via `incompatibleWith` - matching the existing `@nx/js` and
`@nx/vite` Remix guards. The React 19 AI-instructions migration also
points at the `useRef-required-initial` and `refobject-defaults`
codemods that clear the most common `@types/react` 19 type errors.
## Related Issue(s)
NXC-4573
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/migration-failed-ocean-ec23eb4f)
<!-- polygraph-session-end -->
## Current Behavior
Agentic (AI-assisted) migrations run in two halves: a deterministic
generator, then an AI agent that finishes the work. The generator
formats its own output, but the AI agent edits files directly and its
changes were never formatted.
The agent was actually _blocked_ from formatting by its own scope rules:
the system prompt told it "Do not refactor, reformat, or update
dependencies beyond what the migration prompt directs," and forbade
running nx commands that mutate workspace state (which includes `nx
format:write`).
As a result, after an agentic migration the workspace is left with
unformatted files, and a subsequent `nx format:check` / prepush flags
them. This affects every agentic migration that edits files (for example
the ESLint v9 flat-config migration), not just one prompt.
## Expected Behavior
The agent formats the files it created or modified before writing its
handoff, so the workspace is left consistently formatted.
The fix is in the author-mode scope rules of the agentic migration
system prompt:
- Added a rule directing the agent to format its changed files before
handoff — `nx format:write` when the workspace uses Prettier, otherwise
skip. (`nx format:write` formats the agent's uncommitted changes, which
is exactly the migration's edits at that point.)
- Reworded the blanket "do not reformat" rule to "do not reformat files
you did not change," so it no longer contradicts the new instruction.
- Carved `nx format:write` out of the "do not run mutating nx commands"
prohibition.
This is applied once at the `nx migrate` level, so it covers all agentic
migrations.
## Related Issue(s)
No linked issue — found while running the 23.1 ESLint flat-config
migration on a real workspace.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
Building a publishable library with rollup fails for SCSS, Sass, Less,
and Stylus files since 22.4.0. The files are silently skipped because
the postcss plugin filter only matches
[.css](vscode-file://vscode-app/c:/Program%20Files/Microsoft%20VS%20Code/6928394f91/resources/app/out/vs/code/electron-browser/workbench/workbench.html),
.sss, and .pcss.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Preprocessor files (.scss, .sass, .less, .styl, .stylus) are processed
correctly, as they were before 22.4.0 when the external
[rollup-plugin-postcss](vscode-file://vscode-app/c:/Program%20Files/Microsoft%20VS%20Code/6928394f91/resources/app/out/vs/code/electron-browser/workbench/workbench.html)
package was used.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes#35854
## Current Behavior
When a commit contains a `BREAKING CHANGE:` footer followed by
additional PR-body content (common with squash-merged PR descriptions),
the changelog's "⚠️ Breaking Changes" section captured **everything**
after `BREAKING CHANGE:` until it reached the `Co-authored-by:` /
git-metadata block at the very bottom of the commit. As a result, the
breaking-change entry swallowed unrelated content such as the `##
Related issues` section, the `Fixes #NNNNN` reference, and `<!-- ...
-->` HTML comment markers (e.g. polygraph session blocks).
For example, commit
[`192f6681`](https://github.com/nrwl/nx/commit/192f66811d67ebd551504b314c2e9ed9614c16e1)
(`feat(angular): support angular v22`) rendered its breaking change as
the note **plus** the Related Issues heading, the `Fixes #35910`
reference, and the entire polygraph session comment block.
## Expected Behavior
The breaking-change entry contains only the breaking-change note itself.
For the example above it now renders just:
> Angular v19 is no longer supported.
`extractBreakingChangeExplanation` now:
- **strips HTML comments** (`<!-- ... -->`, including multi-line)
wherever they appear, so a comment in the middle of a note no longer
truncates the text around it; and
- **scans line-by-line** from the `BREAKING CHANGE:` line and stops at
the first structural boundary: a Markdown heading (e.g. `## Related
issues`), a horizontal rule / separator (`---`), a `Co-authored-by:`
trailer, or the git-metadata `"` delimiter.
Multi-line and multi-paragraph breaking changes remain fully supported
(preserving the behavior from #33070). Two regression tests were added —
one reproducing the `192f6681` commit body, and one proving HTML
comments are stripped rather than used to truncate — and all 18
changelog-renderer tests pass.
## Related Issue(s)
No tracked issue — reported via internal review of the changelog output
for commit `192f6681`.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/Nx-changelog-renderer-breaking-changes-parsing-fix-c3d89b8c)
<!-- polygraph-session-end -->
## Current Behavior
The `nx-dev:sitemap` target runs its command through `pnpm`:
```
pnpm next-sitemap --config ./next-sitemap.config.js && node ./scripts/patch-sitemap-index.mjs
```
Because the binary is launched via `pnpm`, pnpm performs its
install-sync check on every run, reading `pnpm-lock.yaml` and the
patches declared in `pnpm-workspace.yaml`
(`patches/@astrojs__starlight.patch`) before `next-sitemap` even starts.
Those files are outside the task's declared inputs, so Nx Cloud
sandboxing flags the task with unexpected reads, which undermines cache
reliability.
## Expected Behavior
The target invokes `next-sitemap` directly. Nx `run-commands` already
prepends `node_modules/.bin` to `PATH`, and `next-sitemap` is installed
at the workspace root, so the binary resolves without the `pnpm`
launcher. The task now reads only its declared inputs — no more
unexpected reads of `pnpm-lock.yaml` or the Starlight patch.
Verified locally with `nx sitemap nx-dev --skip-nx-cache`: both
`next-sitemap` and the `patch-sitemap-index.mjs` step run successfully.
## Related Issue(s)
N/A — internal CI/caching correctness fix (Nx Cloud sandbox violations).
<details>
<summary>Pre-create review (run before PR open)</summary>
### Critical
none
### Important
none
### Suggestions
- `packages/nx/project.json` still uses the `pnpm <bin>` pattern for a
different target (`napi artifacts`). Out of scope here; the same fix
applies if it ever hits the sandbox flag.
_Reviewer confirmed: `next-sitemap` resolves reliably via `run-commands`
PATH handling (ancestor `node_modules/.bin` dirs are appended; verified
against the executor's spec), and dropping `pnpm` changes no
env/node/script-resolution behavior. silent-failure-hunter /
pr-test-analyzer / comment-analyzer skipped as N/A — one-line
build-config change with no logic, error handling, testable units, or
new comments._
</details>
## Current Behavior
`nx migrate <version>` crashes with `TypeError: Invalid comparator:
<specifier>` when any dependency in `package.json` uses a non-semver
specifier — pnpm's `catalog:` / `workspace:` protocols, `npm:` aliases,
or `git` / `file` / `link` refs.
`filterDowngradedUpdates` (in
`packages/nx/src/command-line/migrate/update-filters.ts`) passes the raw
specifier straight to `semver.minVersion()`. For a spec like
`catalog:eslint`, `minVersion` throws, aborting the entire migration
before any `package.json` or `migrations.json` is written. This blocks
`nx migrate` for any repo that uses pnpm catalogs (including this one).
## Expected Behavior
`nx migrate` treats a specifier it cannot parse as a semver range as
"can't narrow" and leaves the user's specifier untouched, so the
migration completes. Genuine semver ranges keep their existing narrowing
/ downgrade-filtering behavior.
The fix wraps the `minVersion()` call in a try/catch: an unparseable
specifier yields a `null` floor, which falls through to the existing
"leave untouched" path. Adds regression tests covering the `catalog:`
repro plus the wider `workspace:` / `npm:` / git / file family.
## Related Issue(s)
Discovered while migrating the nrwl repo set to nx 23.1.0-beta.0. No
existing issue found.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/nx-23.1.0-beta.0-Migration-24e91166)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The workspace builds and typechecks every first-party package with
`tsc`. An earlier attempt to enable the Go-based TypeScript compiler
(tsgo) for just `packages/nx` (#35047) was reverted (#35167) because
mixing compilers caused `.tsbuildinfo` version-mismatch cascades — a
downstream `tsc --build` would see a tsgo-stamped build info and
recompile everything.
## Expected Behavior
The entire `@nx/js/typescript` build + typecheck graph — packages,
graph, tools, `e2e/**`, and `nx-dev/ui-fence` — compiles with `tsgo`.
Now that every package is on `nodenext` (NXC-4538), a single compiler
runs across the whole graph, so there is no cross-compiler
`.tsbuildinfo` cascade.
### Changes
- Install `@typescript/native-preview` and set `compiler: "tsgo"` on
**both** `@nx/js/typescript` plugin entries (the package build/typecheck
entry and the e2e/nx-dev typecheck entry — the latter does `tsc --build`
with references into `packages/*`, so it had to move too).
- `tsconfig.base.json`: switch to `nodenext` module resolution, remove
`baseUrl` (tsgo removed it — `TS5102`), and set `strict: false` to
preserve current tsc behavior (tsgo defaults strict on).
- Align spec/e2e tsconfig `module` to `nodenext` (`TS5110`) and add
`customConditions: ["@nx/nx-source"]` to the spec tsconfigs so test
files resolve `@nx/*` subpaths to workspace **source** — notably
`@nx/devkit/internal-testing-utils`, which is excluded from devkit's
build so no declaration is emitted under nodenext.
- Source fixes surfaced by tsgo: two accidental workspace-root
(`baseUrl`-anchored) imports now use package names; `@nx/expo` `addJest`
gets an explicit `Promise<GeneratorCallback>` return type; `@nx/angular`
webpack-browser casts past the angular/webpack plugin type difference;
`graph/client-e2e` cypress global augmentations and `AUTWindow` casts;
`Task` mocks get the required `cache` field;
`update-repos`/`create-embeddings` config fixes.
## Validation
- `build-base`: **42 projects green** under tsgo.
- `typecheck`: **53 projects, 0 errors** under tsgo (entire
`@nx/js/typescript` graph).
- lint / test / e2e: pending CI.
> Note: tsgo's incremental/cached builds occasionally drop emitted
declarations (observed with devkit's `internal-testing-utils`); a
from-scratch build emits them. Worth watching in CI.
The remaining `tsc` users — `astro-docs` (astro check), `nx-dev`'s
Next.js build, and `@nx/angular`'s ng-packagr (ngc) — do **not** `tsc
--build` the packages, so they don't share `.tsbuildinfo` with the tsgo
graph and coexist safely.
## Related Issue(s)
Implements Linear NXC-4539 (builds on NXC-4538 — all packages on
nodenext). No GitHub issue to close.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
On mobile, the conference banner offsets the header, sidebar pane, and
mobile "On this page" bar down by its height, but the fixed
`starlight-menu-button` (hamburger) toggle is not offset. It stays
anchored near the viewport top, underneath the banner strip - rendered
but unclickable. With no working toggle, the entire mobile sidebar
(including the Reference section) is unreachable.
## Expected Behavior
The mobile menu toggle is offset down by `--conf-banner-h` when the
banner is active, so the hamburger sits in the header and opens the full
tabbed sidebar (Getting Started / Technologies / Knowledge Base /
Reference). The rule is scoped to `.page.has-conf-banner`, so it is a
no-op once the banner expires.
Verified on live nx.dev at mobile width: toggle moves into the header,
becomes the topmost clickable element, and opens the full sidebar.
## Screenshots
<img width="965" height="1521" alt="Screenshot 2026-06-18 at 2 51 44 PM"
src="https://github.com/user-attachments/assets/7ad75414-f2ca-4706-baaf-6c21019b672f"
/>
<img width="965" height="1521" alt="Screenshot 2026-06-18 at 2 51 45 PM"
src="https://github.com/user-attachments/assets/5e78d0bb-d406-4e54-91b3-56ee15a2a04f"
/>
<img width="965" height="1521" alt="Screenshot 2026-06-18 at 2 51 36 PM"
src="https://github.com/user-attachments/assets/e878dbbd-f9a0-4dcd-b23e-a8b72d97c80c"
/>
<img width="965" height="1521" alt="Screenshot 2026-06-18 at 2 51 38 PM"
src="https://github.com/user-attachments/assets/5c7555d0-2f44-41e3-9540-276e0f23ac36"
/>
## Related Issue(s)
Fixes DOC-536
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/docs-sidebar-mobile-5d199be9)
<!-- polygraph-session-end -->
## Current Behavior
When resolving package versions during `nx migrate`, Nx reads the
package manager's minimum-release-age (cooldown) policy so its
registry-based resolution matches what the package manager would
install.
On **pnpm `>=11.1.3`** in **loose mode** — which includes pnpm's
built-in 1-day default that is active even with no user configuration —
an immature pick caused Nx to eagerly write the resolved `name@version`
into `minimumReleaseAgeExclude` in `pnpm-workspace.yaml` during the
resolution step (and log that it had done so). This surprised users
whose `pnpm-workspace.yaml` was modified by `nx migrate` even though
they had never configured a cooldown.
## Expected Behavior
`nx migrate` no longer writes `minimumReleaseAgeExclude` entries to
`pnpm-workspace.yaml` during version resolution.
`nx migrate` resolves versions but does **not** replace the install —
the real `pnpm install` still runs afterward. On pnpm `>=11.1.3` in
loose mode, pnpm itself auto-writes the exclude at install time, so Nx's
eager write was redundant and risked diverging from pnpm's actual pick
(Nx resolves off the registry; pnpm may resolve a different version at
install time).
Resolution now returns the immature version without touching
`pnpm-workspace.yaml`, letting the package manager own that write at the
correct layer. The strict-mode approval prompt (`handleViolation`) is
unchanged: when a user has explicitly enabled a cooldown that blocks the
install, Nx still prompts before writing the exclude.
## Related Issue(s)
<!-- Reported internally; add "Fixes #<issue>" here if there is a
tracking issue. -->
## Current Behavior
The AI agent configuration files (skills, commands, and subagents)
checked into the repo for the various assistants (`.agents`, `.github`,
`.gemini`, `.opencode`) were out of date relative to the current `nx
configure-ai-agents` output.
## Expected Behavior
Regenerate the AI agent configuration across `.agents`, `.github`,
`.gemini`, and `.opencode` by running `nx configure-ai-agents`. This
refreshes the shared skills (`nx-workspace`, `nx-generate`, `nx-import`,
`nx-plugins`, `nx-run-tasks`, `link-workspace-packages`, `monitor-ci`),
adds the `monitor-ci` command/subagent, and aligns the per-assistant
directories with the canonical `.agents` skill layout (`SKILL.md` +
`references/`).
## Related Issue(s)
N/A — tooling/config regeneration.
## Current Behavior
The Nx Agents distributed task execution docs describe task-centric
scheduling, but they do not explicitly explain continuous assignment as
the mechanism that keeps agents supplied with work during a CI run.
## Expected Behavior
The docs explain continuous assignment in the Nx Agents page, including
how it differs from fixed manual distribution and why it improves agent
utilization.
## Related Issue(s)
N/A
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/info-dtes-aa95bbfd)
<!-- polygraph-session-end -->
## Current Behavior
When a `@nx/vitest:test` target sets a `mode` (for example a `ci`
configuration whose `vitest.config.ts` branches on `({ mode }) => ...`),
only some mode-based settings were applied at runtime. `test.reporters`
changed as expected, but `test.outputFile` and `test.coverage.reporter`
fell back to their non-`ci` values, so JUnit output could be written to
stdout instead of the configured file and coverage used the default
reporters.
The executor loaded the config once with the configured mode to read the
reporters, then let Vitest reload the config without forwarding that
mode. Vitest then resolved every other mode-based branch with its
default run mode (`test`), so only the explicitly forwarded `reporters`
honored the configured mode.
## Expected Behavior
All mode-derived Vitest config (reporters, outputFile,
coverage.reporter, and any other mode-based branch) is applied
consistently. The executor resolves the mode once (an explicit `mode`,
otherwise `runMode`, otherwise `test`) and forwards it to Vitest so both
config loads resolve their mode-based branches identically. This mirrors
Vitest's own default where the config mode falls back to the run mode,
so `benchmark` targets keep loading their config with mode `benchmark`.
## Related Issue(s)
Fixes#35196
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/gh-35196-928adbb7)
<!-- polygraph-session-end -->
## Current Behavior
Cloud setup prompts show a plain `https://nx.dev/nx-cloud` link with no
attribution.
## Expected Behavior
The footer link is rendered as an OSC 8 hyperlink. The visible text
stays clean (`https://nx.dev/nx-cloud`) while the click target carries
`utm_source=nx-cli` plus a per-command `utm_medium`
(`create-nx-workspace`, `nx-init`, `nx-migrate`, `nx-connect`).
Terminals without OSC 8 support fall back to the plain link.
## Related Issue(s)
CLOUD-4642
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/cli-utm-99e98561)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current behavior
Angular v22 is not supported.
## Expected behavior
Angular v22 should be supported.
BREAKING CHANGE: Angular v19 is no longer supported.
## Related issues
Fixes#35910
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/angular-v22-3d830e58)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
No `nx migrate` path from Next.js 14->15 or React 18->19.
## Expected Behavior
packageJsonUpdates bump Next.js 14->15 (+eslint-config-next) and React
18->19 (+@types/*), each opt-in via x-prompt. Prompt migrations supply
AI instructions for the breaking-change code edits. Targets 23.1.
## Related Issue(s)
NXC-4548
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nextjs-14-removal-eslint-8-drop-prep-ded9154d)
<!-- polygraph-session-end -->
## Current Behavior
Gradle e2e tasks run with `org.gradle.parallel=true` and no worker cap,
so each build fans out across every core. The `e2e-ci**` assignment rule
in `dynamic-changesets.yaml` co-locates several e2e tasks on a single Nx
Cloud agent (3 on `linux-large`, up to 6 on `linux-extra-large`). When
multiple gradle e2e tasks land on the same machine, they each grab all
cores and oversubscribe the agent's CPU (and stack
daemon/Kotlin/test-fork JVMs in memory).
## Expected Behavior
Cap each gradle build to 2 task workers via
`GRADLE_OPTS=-Dorg.gradle.workers.max=2` in the shared agent
`common-env-vars`. Co-located gradle e2e tasks no longer oversubscribe
the shared agent. Only gradle invocations read `GRADLE_OPTS`, so other
tasks are unaffected. Daemon behavior is left untouched (the e2e harness
intentionally keeps daemons in the test body).
This is a first, low-risk step — it tames CPU fan-out but does not
isolate the shared `~/.gradle` / `~/.m2` state between simultaneous
gradle builds; pinning gradle e2e to `parallelism: 1` is a possible
follow-up if needed.
## Related Issue(s)
N/A — internal CI tuning.
## Summary
Adds support for `@rspack/core@2` and `@rsbuild/core@2` across
`@nx/rspack`, `@nx/rsbuild`, `@nx/angular-rspack`, and
`@nx/module-federation`. v1 stays in the supported window (multi-version
policy: latest + previous major).
- Catalog now resolves to `@rspack/core@2.0.4` / `@rsbuild/core@2.0.7`.
- Version maps + detection utilities pick v1 or v2 based on the
installed major.
- New `packageJsonUpdates` migrations move workspaces still on
`@rspack/core@^1` / `@rsbuild/core@^1` to v2 (`23.0.0-beta.20`, gated by
`requires`).
## v2 breaking changes and how each is handled
### 1. `@rspack/core@2` is pure ESM at the entry, but the bundle is CJS
A direct top-level `import` from `@rspack/core` no longer works in
places where Nx loads its own plugin modules synchronously. The CJS
`dist/index.js` is what actually resolves under `require()`.
**Addressed**: lazy-load `@rspack/core` where Nx eagerly imported it, so
the import is deferred until the consuming code actually runs. See
`feat(rspack): load @rspack/core lazily for v2 esm compatibility`.
### 2. `@rspack/dev-server@2` is a ground-up rewrite — no longer wraps
`webpack-dev-server`
v1's `@rspack/dev-server` depended on `webpack-dev-server`, whose
`Server.js` sets `process.env.WEBPACK_SERVE = 'true'` at module load. v2
dropped that dependency entirely; the env var is never set. The rspack 2
CLI signals serve mode via
`setBuiltinEnvArg(env, 'SERVE', true)` → `RSPACK_SERVE` on the
**config-function `env` arg**, not `process.env`.
Every `process.env['WEBPACK_SERVE']` check across `@nx/rspack`,
`@nx/angular-rspack`, and `@nx/module-federation` would silently fall
through to build mode on rspack 2.
**Addressed by three bridges** — one per config-shape:
- **`composePlugins`-based configs** (most `@nx/rspack` user configs):
bridge in `composePlugins.combined` reads `ctx['env']['RSPACK_SERVE']`
and sets `process.env.WEBPACK_SERVE`. See `fix(rspack): bridge rspack 2
RSPACK_SERVE to
WEBPACK_SERVE`.
- **`createConfig`-based configs** (`@nx/angular-rspack` `export default
createConfig(...)`): rspack never passes `env` to value exports, so the
env-arg bridge can't run. argv-based detection inside `createConfig`
(`process.argv[2] ∈ {serve,
server, s, dev}`) instead. See `fix(angular-rspack): bridge rspack 2
serve signal via argv detection`.
- **Plain-object configs** (`@nx/react:host` generated
`rspack.config.ts` is an object literal, not a function): neither bridge
above runs. Shared `bridgeRspackServeEnv()` helper called at the top of
each MF dev-server plugin's `apply()`. See
`fix(module-federation): bridge rspack 2 serve signal in dev-server
plugins`.
### 3. Tightened `RuleSetRule` typings broke flattened `oneOf` shapes
The v2 type refactor revealed that `rules: [{ oneOf: [...] }, { use }]`
(master) and `oneOf: [..., { use }]` (the simplification attempt) are
not equivalent. Flat `oneOf` picks a single matching branch — language
loaders left as a sibling `use`
got dropped for tagged style files.
**Addressed**: concatenate language loaders into each `oneOf` branch in
`style-config-utils.ts` so tagged files still preprocess. See
`fix(angular-rspack): apply language loaders to tagged style files`.
### 4. `experiments.outputModule` no longer accepted in the same shape
Setting `experiments.outputModule: true` is a v1 idiom; on v2 it
surfaces as a warning/typing issue depending on the context.
**Addressed**: only set it on v1, omit on v2. See `fix(angular-rspack):
omit experiments.outputModule on rspack v2` and `fix(module-federation):
only set experiments.outputModule on rspack v1`.
### 5. `stats.profile` / `statsJson` no longer accept the v1 shape
The v1 profile/stats notices fired on v2 spuriously.
**Addressed**: drop the v1-only branch on v2, keep an informational
notice for plugin authors. See `fix(angular-rspack): drop the v2
statsJson notice`, `fix(rspack): drop the v2 statsJson profile warning`.
### 6. `afterDone` may fire with `undefined` stats on error
v2 propagates compilation errors via the run callback before `afterDone`
resolves; the hook is still called but `stats` is `undefined`, masking
the real error.
**Addressed**: guard with `if (!stats) return;`. See
`fix(angular-rspack): guard afterDone handler against undefined stats`.
### 7. `--watch` flag rename in `@rsbuild/core@2`
The plugin snapshot diverged from the renamed flag.
**Addressed**: sync snapshot. See `fix(rsbuild): sync plugin snapshot to
renamed watch flag`.
### 8. Peer-dep auto-install picks first satisfiable sub-range
`@nx/angular-rspack`'s peer was `>=1.3.5 <1.7.0 || ^2.0.0`. pnpm's
`auto-install-peers` resolves multi-major OR ranges against the
**first** satisfiable sub-range, so v2 catalogs were ending up with
stray `@rspack/core@1.6.8`.
**Addressed**: reverse to `^2.0.0 || >=1.3.5 <1.7.0`. Empirically
verified the v1 sub-range no longer steals resolution. See
`fix(angular-rspack): order @rspack/core peer range v2-first`.
### 9. Migrations + v1→v2 `packageJsonUpdates`
- `packages/rspack/migrations.json`: new `23.0.0-rspack-v2`
packageJsonUpdates entry (gated by `requires: { "@rspack/core": ">=1.0.0
<2.0.0" }`) bumps `@rspack/core` + siblings to `^2.0.4`. Plus `requires`
gates added to existing
module-federation migrations (21.3.0, 22.2.0).
- `packages/rsbuild/migrations.json`: new `23.0.0-rsbuild-v2`
packageJsonUpdates entry (gated similarly) bumps to `^2.0.7`.
- All entries pinned to `23.0.0-beta.20`.
### 10. Docs
Supported-versions windows widened to include v2 in both rspack and
rsbuild docs pages.
## Known limitation: `rspack serve` under Cypress 15
The `should have interop between rspack host and webpack remote` case in
`e2e/react/src/module-federation/misc-rspack-interoperability.test.ts`
(re-enabled in master via #35764) hits an upstream incompatibility when
an **rspack 2** dev server
is launched under **Cypress 15**'s e2e runner:
```
> rspack serve --port=6104 --node-env=development
[rspack-cli] TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must
be of type string. Received undefined
at join (node:path:1339:7)
at key (.../@rspack/core/dist/index.js:2549:167) ← LOADER_PATH =
join(import.meta.dirname, 'cssExtractLoader.js')
at Object.<anonymous> (.../@rspack/core/dist/index.js:13607:16)
at Module._compile (.../cjs/loader:1760:14)
at Object.transformer
(.../Cypress/15.15.0/.../tsx/dist/register-D46fvsV_.cjs:3:1104)
```
**Root cause** (three layers):
1. `@rspack/core@2` ships `import.meta.dirname` in its **CJS** bundle
(`dist/index.js`, lines 2549 + 3462) without a `__dirname` fallback.
2. Cypress 15.x bundles `tsx@4.20.6` inside the Electron app and
registers it as a global CJS require-hook. The subprocess spawned for
`nx run shell:serve` inherits this via `NODE_OPTIONS`.
3. `tsx@4.20.6` doesn't synthesize `import.meta.dirname` when
transforming CJS, so the value is `undefined` and `path.join(undefined,
…)` throws.
**Upstream status**:
- rspack [#13420](https://github.com/web-infra-dev/rspack/issues/13420)
— **closed as not-rspack's-bug**; maintainer points to tsx.
- tsx [#781](https://github.com/privatenumber/tsx/issues/781) — **fixed
in tsx 4.22.0** (released 2026-05-14).
- Cypress 15.15.0 (current latest) still bundles tsx 4.20.6 — waiting on
Cypress to bump bundled tsx ≥ 4.22.0.
**Scope**: only the `rspack host` branch of the interop test trips it.
`webpack host + rspack remote` passes (no `rspack serve` under Cypress).
**Decision**: leave the test as-is, do not skip — once Cypress ships
with bundled tsx ≥ 4.22.0, the failure clears on its own.
## Test Plan
- [x] `nx run-many -t test,build,lint -p
rspack,rsbuild,angular-rspack,module-federation`
- [x] `nx affected -t build,test,lint`
- [x] `nx affected -t e2e-local` (see Known limitation above)
- [x] Manual smoke: scaffold workspaces against `@rspack/core@^1`, `^2`,
`@rsbuild/core@^1`, `^2`. Init + build + serve. Confirm no version
overwrite.
Fixes NXC-4460
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
New workspaces / `nx@23.0.0` ship dependencies with published security
advisories:
- `happy-dom@~9.20.3` (when the happy-dom test environment is selected)
- two **critical** RCE advisories:
[GHSA-37j7-fg3j-429f](https://github.com/advisories/GHSA-37j7-fg3j-429f)
(VM context escape) and
[GHSA-96g7-g7g9-jxw8](https://github.com/advisories/GHSA-96g7-g7g9-jxw8)
(server-side code execution via `<script>`).
- `tmp@0.2.6` - **high**,
[GHSA-7c78-jf6q-g5cm](https://github.com/advisories/GHSA-7c78-jf6q-g5cm)
(path traversal).
- `form-data@4.0.5` (transitive via `axios`) - **high**,
[GHSA-hmw2-7cc7-3qxx](https://github.com/advisories/GHSA-hmw2-7cc7-3qxx)
(CRLF injection).
`tmp` and `form-data` reach generated workspaces because `expand-deps`
pins nx's transitive deps from the monorepo lockfile at publish time.
## Expected Behavior
- `happyDomVersion` bumped `~9.20.3` -> `^20.10.4` in
`packages/vitest/src/utils/versions.ts` (caret matches sibling
`jsdomVersion` so it stays patched within the major).
- `tmp` forced to `~0.2.7` and `form-data` to `^4.0.6` via catalog +
overrides; lockfile re-resolved so the next release pins the patched
versions.
`pnpm audit` reports 0 critical repo-wide; `tmp` and `form-data` are
CLEAN.
## Related Issue(s)
N/A
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/rapid-panther-825e4172)
<!-- polygraph-session-end -->
## Current Behavior
When an Angular compilation fails to initialize, the error was swallowed
by a try/catch that only logged to the console. The build then continued
with a compilation that was never initialized and failed later in
confusing ways: a cascade of raw TypeScript parser errors that buried
the real cause, or a hung process that never exited. Failed builds also
leaked the esbuild stylesheet service and the JavaScript transformer
worker pool, so `rspack build` could hang on exit.
## Expected Behavior
Angular compilation initialization and emit failures are reported as
rspack build errors, mirroring @angular/build's application builder: an
initialization failure is reported and skips diagnostics, while an emit
failure is reported but still runs diagnostics since they usually carry
the root cause. In watch mode the error clears and the build recovers on
the next successful rebuild. The build loaders short-circuit when the
compilation failed so the real error is not buried under parser errors,
and the esbuild service and worker pool are released on shutdown so
`rspack build` exits cleanly.
## Implementation Details
- `setupCompilationWithAngularCompilation` rethrows initialization
errors instead of logging and continuing.
- `AngularRspackPlugin` tracks initialization and emit failures
separately and reports them as compilation errors in `thisCompilation`;
the `emit` hook gates diagnostics on the initialization failure only, so
emit failures still surface diagnostics.
- The transform loaders read an `angularCompilationFailed` flag from the
shared compilation state and emit empty or pass-through modules when
set. The partial-transform loader fails its module on a transform
rejection, and the `emit` hook is guarded so a diagnostics throw can no
longer leave the build hanging.
- A `shutdown` hook releases the JavaScript transformer worker pool and
disposes the component stylesheet bundler, covering failed builds that
skip the `done` hook.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/fix-angular-rspack-esbuild-leak-dcae7b74)
<!-- polygraph-session-end -->
## Current Behavior
The `@nx/angular` esbuild-based executors (`application`,
`browser-esbuild`, `unit-test`) and the dev-server builder load the
`indexHtmlTransformer`, `plugins`, and `esbuildMiddleware` option files
with `require()`. Nx first resolves the `{workspaceRoot}` and
`{projectRoot}` tokens in those options to a path relative to the
workspace root, so a transformer kept in a library resolved to something
like `libs/common/src/index-html-nonce-transform.ts`. `require()`
resolves a bare relative path against the loader's own directory under
`node_modules`, not the workspace root, so the build failed with `Cannot
find module 'libs/common/src/index-html-nonce-transform.ts'`.
## Expected Behavior
These option files load correctly when referenced with `{workspaceRoot}`
or `{projectRoot}` (or any workspace-relative path), including when they
live in a library. A plugin published as a package keeps resolving
through `node_modules` unchanged.
## Related Issue(s)
Fixes#35936
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/gh-35936-4126c3f7)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Nx supported both ESLint v8 and v9. The `@nx/eslint` runtime,
generators, executors, and inferred plugin carried v8-specific branches,
version floors allowed v8-only ranges, and `useFlatConfig` chose flat vs
eslintrc purely by the installed ESLint version - which could select
flat config on an eslintrc workspace and crash generators.
## Expected Behavior
ESLint v8 support is removed and Nx targets ESLint v9+. Flat config is
the default for new workspaces, while existing eslintrc workspaces stay
supported: `useFlatConfig` now respects a root flat/eslintrc config file
and the `ESLINT_USE_FLAT_CONFIG` env var. Version floors, the lockfile,
and docs move to v9+. Generator specs across the linting-capable plugins
assert flat config by default and each retains at least one eslintrc
test.
BREAKING CHANGE: ESLint v8 is no longer supported. Nx requires ESLint v9
or later.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/drop-eslint-v8-ad08cc1c)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: leosvelperez <leosvelperez@users.noreply.github.com>
## Current Behavior
The compile-multiple-formats guide cites `TypeScript 4.7+` as the
threshold for `exports` field type resolution and links the TS 4.7
release notes. TS 4.7 shipped in 2022; the current TypeScript is 5.x and
this behavior is universally supported now, so the version qualifier and
link are stale.
## Expected Behavior
The version qualifier is dropped (the requirement is stated
unconditionally) and the stale TS 4.7 release-notes link is removed.
## Related Issue(s)
Resolves DOC-533.
Co-authored-by: Jack Hsu <53559+jaysoo@users.noreply.github.com>
Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
## Current Behavior
The `withReact` section of the webpack plugins guide documents an `svgr`
option (`Type: undefined|false`) and shows a `svgr: false` example. That
option no longer exists: `WithReactOptions extends WithWebOptions` (no
`svgr`), and `applyReactConfig` only adds hot reload — there is no SVGR
handling. The intro also claims `withReact` "adds support for ... SVGR".
This mirrors DOC-523 (the same stale svgr docs on
`NxReactWebpackPlugin`).
## Expected Behavior
The svgr option subsection and the `svgr: false` example line are
removed, and the intro no longer claims SVGR support, so the docs match
the actual `withReact` API. The section is kept (it already carries a
deprecation notice — `withReact` is slated for removal in Nx v24) for
users still on the v22–23 compose-helper path.
## Related Issue(s)
DOC-524
Co-authored-by: Jack Hsu <53559+jaysoo@users.noreply.github.com>
Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
## Current Behavior
The Fly.io Node guide has a linkcard whose copy reads like a 2022 launch
announcement: "Starting with Nx 15.7 we now have first-class support for
building Node backend applications". Node backend support has been
standard for many majors, so the version reference is stale.
## Expected Behavior
The linkcard description is reworded to "Nx has first-class support for
building Node backend applications", dropping the Nx 15.7 reference.
## Related Issue(s)
DOC-525
Co-authored-by: Jack Hsu <53559+jaysoo@users.noreply.github.com>
Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
## Current Behavior
Both Nx Cloud authentication docs pages use a two-tab layout contrasting
the current `nxCloudId`/`nxCloudAccessToken` approach with a legacy
approach that uses `tasksRunnerOptions` and a separate `nx-cloud`
install. Since the minimum supported Nx version is 22+, the legacy tabs
are dead and only add noise.
- `access-tokens.mdoc`: "Nx >= 17" vs "Nx < 17" tabs
- `personal-access-tokens.mdoc`: "Nx >= 19.7" vs "Nx <= 19.6" tabs
## Expected Behavior
The legacy tab is removed on both pages, leaving just the single current
approach (no tab wrapper).
## Related Issue(s)
Documentation cleanup from a staleness audit; no GitHub issue.
Co-authored-by: Jack Hsu <53559+jaysoo@users.noreply.github.com>
Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
## Current Behavior
The Cache Task Results page shows a `{% tabs syncKey="nx-version" %}`
block with two approaches. The "Nx < 17" tab documents the deprecated
`tasksRunnerOptions.default.options.cacheableOperations` config.
`cacheableOperations` was deprecated in Nx 17 and `tasksRunnerOptions`
fully deprecated in Nx 20, and the minimum supported Nx is 22+, so this
tab is dead content.
## Expected Behavior
The tabs block is collapsed to the single, current
`targetDefaults.build.cache: true` approach.
Co-authored-by: Jack Hsu <53559+jaysoo@users.noreply.github.com>
Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
## Current Behavior
The Webpack plugins guide documents an `svgr` option for
`NxReactWebpackPlugin`, including an `svgr: false` example and a
deprecation note saying it "will be removed in Nx 22". The option no
longer exists in source — it was already removed in Nx 22, and
`applyReactConfig` only adds React Fast Refresh.
## Expected Behavior
The stale option docs and `svgr: false` example are removed. The section
now shows a minimal usage example. Users needing the old behavior can
refer to the Nx 22 docs archive at 22.nx.dev/docs.
## Related Issue(s)
Fixes DOC-523
Co-authored-by: Jack Hsu <53559+jaysoo@users.noreply.github.com>
Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
## Current Behavior
The "Automatically configure frontend executors" section of the Node
application proxies guide framed `--frontendProject` as something "meant
for Nx prior to version 18" and claimed projects use executors only
"prior to Nx version 18". Nx 18 shipped in early 2024, so this framing
is stale.
## Expected Behavior
The stale version references are removed. Note that `--frontendProject`
is **not** actually removed from the `@nx/node`, `@nx/nest`, and
`@nx/express` generators — it's still an active, documented option (and
`@nx/node` still ships the proxy-generation logic). So rather than
deleting the section as the issue originally suggested, this rewrites it
to drop the inaccurate version framing while keeping the still-valid
feature documented. The section heading is also updated to
"Automatically configure frontend proxies" since it no longer pertains
specifically to executors.
## Related Issue(s)
Fixes DOC-531
Co-authored-by: Jack Hsu <53559+jaysoo@users.noreply.github.com>
Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
## Current Behavior
Several stable concept/reference pages open with "Introduced in Nx X"
launch notes that no longer add value. One of them is also factually
stale: the Nx Daemon is described as "opt-in", but it's now default-on.
## Expected Behavior
The version-intro framing is dropped on each page, leaving plain
present-tense descriptions:
- `concepts/nx-daemon.mdoc` — removed "In version 13 we introduced the
opt-in Nx Daemon" (also fixes the opt-in/default-on inaccuracy)
- `concepts/sync-generators.mdoc` — removed "In Nx 19.8, you can use
sync generators"
- `reference/project-configuration.mdoc` — removed "Sync generators are
available in Nx 19.8+."
- `guides/Adopting Nx/preserving-git-histories.mdoc` — removed "In Nx
19.8 we introduced `nx import`"
## Related Issue(s)
Source: dot-ai-config staleness audit 2026-06-17.
Co-authored-by: Jack Hsu <53559+jaysoo@users.noreply.github.com>
Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
## Current Behavior
The docs don't explain that the first `nx` command to reach Nx Cloud
during a CI run creates the CI Pipeline Execution (CIPE) and locks in
its settings (access token scope, distribution config, assignment rules,
stop conditions). When another `nx` command contacts Nx Cloud before
`npx nx-cloud start-ci-run` — for example because the orchestrator runs
in a separate or downstream pipeline — the CIPE is created with defaults
and the intended `start-ci-run` flags silently have no effect. A
customer hit this with assignment rules not applying in GitLab.
## Expected Behavior
Adds a caution aside to the
[`start-ci-run`](https://github.com/nrwl/nx/blob/HEAD/astro-docs/src/content/docs/reference/nx-cloud-cli.mdoc)
reference explaining the snapshotting behavior and that `start-ci-run`
must run before any other `nx` command, including across downstream
pipelines that share the same CIPE. Adds a short cross-linked note on
the [assignment
rules](https://github.com/nrwl/nx/blob/HEAD/astro-docs/src/content/docs/reference/Nx%20Cloud/assignment-rules.mdoc)
page, where someone debugging "rules not applying" is likely to land.
## Related Issue(s)
Docs-only change tracked in DOC-527.
---------
Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
Co-authored-by: Jack Hsu <53559+jaysoo@users.noreply.github.com>
## Current Behavior
Root and npm package READMEs show CircleCI and gitter badges, with no
sandbox badge.
## Expected Behavior
Root README shows a for-the-badge style sandbox badge; generated npm
package READMEs show the default-style sandbox badge (via the shared
`scripts/readme-fragments/links.md` fragment). The CircleCI and gitter
badges are removed from the npm fragment. Both badges link to the nx.dev
sandboxing docs.
## Related Issue(s)
NXC-4568
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nx-cli-readme-badge-aaf40283)
<!-- polygraph-session-end -->
## Current Behavior
The `e2e/release` jest project sets a `120000` ms `testTimeout`
(`e2e/release/jest.config.cts`), which Jest applies to any setup hook
without an explicit timeout. Ten release suites instead hardcoded a
`60000` ms timeout on their `newProject`-based `beforeEach`/`beforeAll`
setup, half the suite default. Under CI load that setup (`newProject`
plus `@nx/workspace:npm-package` generators plus git tagging) can run
past 60s, so the hook times out and the suite fails intermittently.
These surface as recurring high-risk flaky tasks on the Nx Cloud
dashboard (version-plans, version-plans-check,
version-plans-only-touched, conventional-commits-config, among others),
and a CI run on this branch reproduced it in the `first-release`
`beforeAll`, where `newProject` alone took 58s.
## Expected Behavior
The setup hooks drop the explicit per-hook timeout and inherit the
suite's `120000` ms default, giving the heavy setup enough headroom and
removing the artificial sub-default cap. The `60000` values were
copy-paste boilerplate carried in by each suite's introducing PR, not a
deliberate limit, and this matches the common e2e idiom where setup
hooks omit a per-hook timeout and rely on the file default.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/troubleshoot-flaky-tasks-9deac4de)
<!-- polygraph-session-end -->
## Current Behavior
The `@nx/js` TypeScript plugin infers `.tsbuildinfo` outputs for `tsc
--build` tasks (build / typecheck). When a tsconfig sets `outDir`, the
plugin always declared the buildinfo at
`outDir/<configBaseName>.tsbuildinfo`. But when `rootDir` is also set
(and `tsBuildInfoFile` is not), `tsc` resolves the path differently: it
takes the config path relative to `rootDir` and resolves that against
`outDir`, which can place the file outside `outDir`. For the standard
generated library shape (`rootDir: "src"`), the buildinfo lands one
level above a nested `outDir`; with a sibling `outDir` it lands at the
project root. The declared output never matched the emitted file,
causing cache misses and task-sandboxing violations.
## Expected Behavior
The inferred `.tsbuildinfo` output matches where `tsc` actually writes
the file across all `outDir` + `rootDir` combinations, so the build
cache captures and restores it and task sandboxing reports no violation.
## Implementation Details
`getTsBuildInfoOutputPath` now mirrors tsc's
`getTsBuildInfoEmitOutputFilePath`: when `rootDir` is set it resolves
the config path (sans extension) relative to `rootDir` against `outDir`.
The `outFile`, `tsBuildInfoFile`, no-`outDir`, and
`outDir`-without-`rootDir` cases are unchanged. Two existing snapshots
that encoded the wrong path were corrected, and a regression test was
added for the case where the buildinfo escapes `outDir` to the project
root.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/nxc-4551-904de5a8)
<!-- polygraph-session-end -->
## Current Behavior
The Nx repo pins `nx` and its first-party `@nx/*` packages at
`23.0.0-rc.3`.
## Expected Behavior
The Nx repo is migrated to nx `23.0.0-rc.4`. `nx` and all `@nx/*`
packages on the 23.0.0 line are bumped rc.3 → rc.4 in `package.json`,
with `pnpm-lock.yaml` updated. Packages on separate version lines are
left unchanged (`@nx/graph` 1.0.5; `@nx/conformance`, `@nx/key`,
`@nx/powerpack-license` 5.0.4). `nx migrate` reported no migrations to
run, so no `migrations.json` was created. `nx.json` `targetDefaults` is
already in the object form (the array-shape support was reverted in nx
23), so no conversion was needed.
Commit:
- `chore(repo): migrate to nx 23.0.0-rc.4` (package.json +
pnpm-lock.yaml)
## Related Issue(s)
N/A — routine nx version bump, part of a coordinated multi-repo
migration (linked Polygraph session PRs: nrwl/ocean#11903,
nrwl/nx-examples#470, nrwl/nx-console#3165, nrwl/nx-labs#471).
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/Nx-23.0.0-rc.4-Migration-8e340447)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`nx.json` `targetDefaults` accepts the new filtered **array shape**
(entries matched by `target`/`executor` and narrowed by
`projects`/`plugin`), alongside the legacy record shape. This was
introduced and refined across:
- #35340 — feat: support filtered array-shape targetDefaults with
projects and source
- #35711 — fix: do not drop target defaults in the 23.0.0 array
migration
- #35752 — docs: document the `convert-target-defaults-to-array`
migration
- #35991 — docs: rewrite the targetDefaults reference and guide for the
array shape
## Expected Behavior
This PR **reverts the array-shape `targetDefaults` feature pending a
redesign**, with the intent that it be **reapplied** once the design is
finalized. `targetDefaults` returns to the record-shape-only form
(`Record<string, Partial<TargetConfiguration>>`).
To keep reapplication easy, the revert is split into focused commits
that mirror the original PRs — the feature can be brought back later by
reverting these reverts.
Changes:
- Restore the `TargetDefaults` type; remove `TargetDefaultEntry`,
`TargetDefaultsRecord`, and `NormalizedTargetDefaults`
- Restore the core target-defaults matcher and project-configuration
utils to the record-shape logic
- Remove the `convert-target-defaults-to-array` migration (and its
registration/docs)
- Remove the devkit `upsertTargetDefault`/`findTargetDefault` helpers
and the `normalize-target-defaults` utility; restore generators across
all plugins (angular, cypress, react, jest, eslint, vite, etc.) to write
the record shape
- Restore the `nx.json` schema `targetDefaults` definition and revert
the array-shape documentation
Unrelated changes that landed in the same files after the feature are
**preserved** (the `CreateNodesV2`→`CreateNodes` rename, the
`findMatchingConfigFiles` optimization, the `nx migrate` config,
`.gitignore` entries, migration-doc packaging globs, and the maven
`createNodesV2` migration).
## Related Issue(s)
Reverts #35340, #35711, #35752, #35991 (to be reapplied after redesign).
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## What
Adds a call-to-action at the top of the **Set up CI** getting-started
docs page prompting readers to take a guided tour of a live Nx Cloud
workspace.
## Why
Gives readers a way to see distributed task execution, caching, and run
analytics on a real workspace before wiring up their own CI.
## Details
- File: `astro-docs/src/content/docs/getting-started/setup-ci.mdoc`
- Reuses the existing `{% call_to_action %}` Markdoc component (same one
used on `nx-cloud.mdoc`, `conformance.mdoc`).
- Placed after the intro sentence, before the first `## Make sure you
have Nx` heading.
- Links to `https://cloud.nx.app/demo/intro` with UTM params
(`utm_source=nx-dev`, `utm_medium=ci-tutorial`,
`utm_campaign=workspace-tour`).
- One file, +2 lines.
Copy:
> **Tour an Nx Cloud workspace** — See distributed task execution,
caching, and run analytics on a live workspace before you wire up your
own CI.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/demo-CTAs-c0c4dc8b)
<!-- polygraph-session-end -->
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Current Behavior
The cache-eviction e2e tests `should evict cache if larger than max
cache size` and `should honor NX_MAX_CACHE_SIZE env var` (in both
`e2e/nx/src/cache.test.ts` and `e2e/nx/src/cache-no-daemon.test.ts`)
declare no explicit jest timeout, so they inherit the workspace default
of 35000ms. Their setup runs a reset plus ten cache writes that
regularly takes longer than 35s on CI; the trailing awaited size check
then lets the overdue timer fire, so the tests intermittently fail with
"Exceeded timeout of 35000 ms" even though the eviction result is
correct and deterministic. These are among the highest flake-rate e2e
tasks on the Nx Cloud flaky-task dashboard.
## Expected Behavior
The four tests declare an explicit 120000ms timeout, matching the slower
sibling tests already in the same files, so the deterministic eviction
work completes within budget and the tests stop flaking.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/troubleshoot-flaky-tasks-9deac4de)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`nx serve` for a module federation host can hang for several minutes on
`Starting static remotes proxies...`. On affected setups the verbose
logs show:
```
NX Starting static remotes proxies...
Connecting on localhost:4201
Error connecting on localhost:4201: ETIMEDOUT
Connecting on localhost:4202
Error connecting on localhost:4202: ETIMEDOUT
NX Static remotes proxies started successfully
```
This is a regression introduced in #33871 (released in v22.2.4). That PR
added an `isPortInUse()` check before starting each static remote proxy,
to avoid `EADDRINUSE` when two MF dev servers share a remote. The check
performed an **unbounded** TCP connect via `waitForPortOpen(port, {
retries: 0, host })`.
Callers pass `host: 'localhost'`. On Linux, `localhost` can resolve to
the IPv6 loopback `::1`; when nothing is listening there and the SYN is
dropped, the connect fails with a slow `ETIMEDOUT` rather than an
immediate `ECONNREFUSED`. Because the attempt had no socket-level
timeout, it blocked for the OS-level TCP connect timeout (~2 minutes on
Linux's default `tcp_syn_retries`) — once per remote — adding minutes to
startup. (`retries: 0` only disables *re-attempts*; it does not bound
how long a single attempt takes to fail.) Before #33871 (v22.2.3) the
same serve completed in ~23s.
## Expected Behavior
`isPortInUse()` now checks the port by **attempting to bind it** rather
than connecting to it:
- If the port is already taken, the bind fails with `EADDRINUSE` →
reported as in use (proxy is skipped).
- Otherwise the bind succeeds, the port is released, and it is reported
as free (proxy is started).
Binding is a local operation with no network round-trip, so it resolves
in ~1ms and **cannot** stall on a TCP connect timeout — the hang is
structurally impossible, not merely bounded. It also tests the exact
operation the caller is about to perform (binding the proxy to the
port), so it precisely predicts whether starting the proxy would
`EADDRINUSE`, preserving the intent of #33871.
A unit test (`port-utils.spec.ts`) covers the port-taken (`EADDRINUSE`)
and port-free cases, and asserts the probe releases the port so the
caller can bind it afterwards.
### Follow-up (separate PR)
`waitForPortOpen` itself has the same latent issue for its *waiting*
callers (`@nx/next`, `@nx/remix`, `@nx/angular`, `@nx/react`,
`@nx/rspack`, and MF's `get-static-remotes`): an unbounded per-attempt
connect defeats its retry loop on `ETIMEDOUT`-prone hosts. `ETIMEDOUT`
is already in its retryable allowlist, so adding a per-attempt socket
timeout (treated as a retryable error) would make the retry budget
behave as intended. Left out of this PR to keep the high-priority fix
tightly scoped.
## Related Issue(s)
Fixes#33909
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
When `NX_CACHE_FAILURES=true` is set, Nx writes failed task results to
the cache but never reads them back. The write side
(`shouldCacheTaskResult`) already honors the flag and stores failures,
but the read side (`fetchCacheHits` in `task-orchestrator.ts`) filtered
cache entries to `cachedResult.code === 0`. As a result, a cached
failure was treated as a cache miss and the task was re-executed on
every run, making the flag effectively a no-op.
This affected both the single-task path (`resolveCachedTasks`) and the
batch path (`applyBatchCachedResults`), since both funnel through
`fetchCacheHits`.
## Expected Behavior
With `NX_CACHE_FAILURES=true`, a failing cacheable task is replayed from
the cache on subsequent runs instead of re-executing:
- The cached failure is read back (the task does **not** re-run).
- The cached terminal output is replayed.
- The run still exits non-zero, and run summaries, the TUI, and
dependent-task skipping all correctly treat it as a failed run.
To get this right, a replayed cached failure is reported with `status:
'failure'` rather than a cache status — the exit-code logic and every
summary/TUI lifecycle treat the cache statuses (`local-cache`,
`remote-cache`, `local-cache-kept-existing`) as success, so a cached
failure had to surface as a failure to be counted correctly. Replayed
cache hits also no longer get redundantly re-written to the cache (new
`fromCache` guard in `postRunSteps`).
### Verification
A minimal workspace with a failing cacheable target (appending to a file
outside its inputs on each real execution):
- Before: second run re-executes the command (marker file grows).
- After: second run replays the cached failure (marker file unchanged)
and still exits with code `1`.
Covered by a new unit test in `task-orchestrator.spec.ts` and an e2e
test in `e2e/nx/src/cache.test.ts`.
## Related Issue(s)
Fixes#35901
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The integrated Remix e2e tests (`e2e/remix/src/nx-remix.test.ts`)
intermittently fail with:
```
thrown: "Exceeded timeout of 120000 ms for a test."
```
Two tests do the first `@nx/remix:app` generate in a fresh workspace:
- `--integrated (npm) › should not cause peer dependency conflicts`
- `--integrated (yarn) › should create app`
That first generate runs a full package install of the app's runtime
deps (React + Remix runtime + Vite + Vitest + ESLint). Under CI load
that install legitimately takes a couple of minutes — in the observed
failure the generate alone ran **137.3s**, and the same run's
workspace-setup npm install measured **154.9s** — which exceeds the
**120s** per-test jest budget.
Because the `runCLI` helper is a **synchronous `execSync`** call, jest
cannot interrupt it: the 120s timer can't fire until the blocked call
returns. So the test is flagged as timed out only *after* the generate
finishes (the failing test reported a 138.5s runtime), and the trailing
`await runCommandAsync('npm install')` is left running as an orphaned
promise — it then errors against the project that `afterAll` has already
cleaned up (the stray `npm error ... ENOENT ... package.json` in the
logs).
This is a slow-but-finite install exceeding too tight a budget — not a
product bug and not a hang.
## Expected Behavior
The two first-install tests are given a budget that covers the install
with headroom. The per-test jest timeout is raised from `120000` to
`600000` on those two tests.
`600000` is deliberately chosen to sit **above** `runCLI`'s own default
per-command `execSync` timeout (`5 * 60 * 1000` = 300s). That ordering
means a genuine *hang* in a single command now fails fast with
`runCLI`'s clear `Command timed out after 300s: ...` message before
jest's opaque 600s timeout — while a healthy ~150s install keeps ample
room. The other tests in the file stay at `120000`: they reuse the
dependency cache populated by the first generate in their `describe`, so
they don't pay the full-install cost.
Test-only change; no product code is touched.
## Related Issue(s)
No open issue tracks this flake (searched `nrwl/nx`). Standalone
test-stability fix.
<details>
<summary>Pre-create review (run before PR open)</summary>
The first draft of this fix added `runCLI(..., { timeout: 240_000 })` to
the generate calls and set the jest budget to `300000`. The pre-create
review (code-reviewer, silent-failure-hunter, comment-analyzer,
pr-test-analyzer on the local diff) caught that `runCLI` **already**
defaults its `execSync` timeout to 300s, so the explicit `240_000`
*lowered* the per-command bound and reduced healthy-install headroom.
The fix was revised accordingly: drop the override, and raise the jest
budget above 300s instead.
### Critical
- (resolved) `{ timeout: 240_000 }` lowered `runCLI`'s existing 300s
`execSync` default → removed; jest budget raised to 600000 (> 300s)
instead, which gives the clear-hang-message-before-opaque-timeout
behavior without sacrificing headroom.
### Important
- (pre-existing, out of scope) `runCommandAsync('npm install')` passes
no `timeout` to `exec`, so that specific install is unbounded; a hang
there would only be caught by the 600s jest budget. This predates the
change and did not cause the observed flake (the install fast-failed
with ENOENT, it did not hang). Tracked as a follow-up: plumb a `timeout`
option through `runCommandAsync` (mirroring `runCLI`) so it fails fast
with a clear message.
### Suggestions
- Comments rewritten to describe the real mechanism (synchronous
`execSync` blocking past the jest budget; 600s > runCLI's 300s
per-command timeout) and to drop unbenchmarked "two minutes" magnitude
claims.
</details>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
Slow CI
## Expected Behavior
Fast CI
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
- Replace the stale legacy map/key matching prose in the nx.json
reference's Target defaults section, which contradicted the
array-shape docs below it, and rewrite it from the tool's
perspective instead of first person
- Document the full within-tier specificity order (target+executor >
executor > exact target > glob) in the precedence paragraph
- Convert the Reduce Repetitive Configuration guide's example to the
array shape, update the same-name/different-executor caution for
entry-based matching, and recount the Ramifications line totals
- Also fixes up some missing config for migrations after earlier PRs
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
## Current Behavior
When a project sits at the workspace root (`projectRoot` is `.`), any
output that uses `{projectRoot}`
after the start of the expression — e.g. the `targetDefaults` output
`{workspaceRoot}/test/{projectRoot}` — fails task graph creation with:
```
NX Output '{workspaceRoot}/test/{projectRoot}' is invalid. When
{projectRoot} is '.', it can only be
used at the beginning of the expression.
```
This commonly bites when a plugin (e.g. `@nx/vitest` via a root
`vitest.config.ts`) infers a target
for the root project and a name-based target default supplies the
outputs. Because the error throws
during task graph creation, it fails any run that includes the root
project, and the user can't fix it
from `targetDefaults` alone since the default is shared by all projects.
## Expected Behavior
`{projectRoot}` is interpolated for root projects regardless of its
position, and the resulting path
is normalized:
- `{workspaceRoot}/test/{projectRoot}` → `test`
- `{workspaceRoot}/dist/{projectRoot}/sub` → `dist/sub`
### Why removing the guard is safe
The throw was added in 40b39b2e64 (Dec 2022, alongside the introduction
of root/standalone projects)
for two reasons, and neither holds anymore:
1. **Path normalization.** At the time, `interpolate()` was a naive
string replace, so
`coverage/{projectRoot}` for a root project would have produced the
unnormalized path `coverage/.`.
#26244 rewrote the function to split segments and `join()` them, which
resolves `.` cleanly — the
beginning-position case (`{projectRoot}/dist` → `dist`) already works
this way today.
2. **Output overlap.** For a root project,
`{workspaceRoot}/coverage/{projectRoot}` collapses to
`coverage`, a parent of every other project's `coverage/<root>` output
(which is why that commit also
migrated the jest defaults to `{projectName}`). But the guard only
blocks one spelling of this: a root
project with `{projectRoot}/coverage` or a literal
`{workspaceRoot}/coverage` output produces the
identical overlap and is allowed today. Nx has no overlap detection for
outputs in general —
overlapping outputs are already permitted everywhere else, while this
hard error leaves users of
shared target defaults with no escape hatch.
Verified against a minimal reproduction (root project with a `test`
target + `"outputs":
["{workspaceRoot}/test/{projectRoot}"]` in `targetDefaults`): fails on
nx 22.7.5, succeeds with this
change; non-root projects are unaffected.
## Related Issue(s)
Fixes#35839
## Current Behavior
`--help` is silently ignored by the commands that skip `initLocal` in
`bin/nx.ts` (`new`, `init`, `configure-ai-agents`, `mcp`, `completion`,
and `graph` outside a workspace) — the command executes instead of
showing help. For example, `nx configure-ai-agents --help` fetches the
latest nx and opens the interactive agent picker.
This is because yargs' built-in help is globally disabled
(`.help(false)`, added in #32662 so `--help` can be forwarded to
executors), and the manual `--help` interception only exists on the
`initLocal` path. `init` and `mcp` had grown per-command workarounds in
their builders to compensate; the other commands had nothing. `--help`
was also missing from every command's options list in help output.
## Expected Behavior
- `nx configure-ai-agents --help`, `nx completion --help`, `nx new
--help`, etc. print the command's help instead of executing it.
- The entry point in `bin/nx.ts` intercepts `--help` (when it appears
before any `--` separator) the same way `initLocal` does, using
`getHelp()` so commands with async builders (`init`, `mcp`) render
correctly.
- `init`'s now-redundant builder workaround is removed; its help output
improves (now includes the usage line and description). `mcp`'s builder
help is intentionally kept — it delegates to the nx-mcp package's own
help and still works.
- `--help` is declared as a global option (mirroring how `--version` is
declared but handled in `nx.ts`), so it shows up in every command's
options list.
- Executor help forwarding is unchanged: `nx run proj:target --help` and
infix `nx test proj --help` still show the executor's schema help
(verified against a scratch workspace), and tasks still run.
## Related Issue(s)
N/A — hit directly when running `nx configure-ai-agents --help`.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/Fix---help-ignored-for-CLI-commands-bypassing-initLocal-2a9721f5)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
The banner config fetched from the Framer endpoint can now carry an
optional artwork URL, so an event can ship custom key-art for the
bottom-right notification card without any code change — and the card
stays exactly as it is today when artwork is absent.
The asset contract is an 840x320 transparent image (420px rendered
width) anchored to the card's top-right corner at -28px top / -16px
right, allowed to crest past the card edges at md+ via a conditional
overflow-visible. A theme-aware gradient scrim keeps the title and
description legible over any art, the description caps at 58% width, and
the megaphone icon yields to the artwork. Below md the art is hidden
entirely so a full-width bottom sheet can never overflow the viewport.
Malformed artwork values are stripped during prebuild normalization
instead of dropping the whole banner.
## Current Behavior
nx and the `@nx/*` dev dependencies are pinned to `23.0.0-rc.2`.
## Expected Behavior
Bump nx and all `@nx/*` packages to `23.0.0-rc.3` via `nx migrate
23.0.0-rc.3`. This single-RC-step jump is **dependency-only** — `nx
migrate` reported "no migrations to run", so there are no source changes
(only `package.json` + `pnpm-lock.yaml`). Separately-versioned packages
(powerpack `@nx/conformance`, `@nx/key`, `@nx/powerpack-license`) are
intentionally left untouched.
## Related Issue(s)
Part of a coordinated multi-repo nx `23.0.0-rc.3` migration across nx,
ocean, nx-labs, nx-examples, and nx-console.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/Nx-23.0.0-rc.3-coordinated-migration-f5fcf7fd)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
There is no documented, repeatable workflow for migrating several
repositories to a target nx version in one coordinated pass. Doing it by
hand repeatedly rediscovers the same non-obvious pitfalls (e.g. `nx
migrate` reading the "from" version from `node_modules` rather than
`package.json`, `CI=true` silently making installs immutable so
migrations never run, pnpm's no-TTY purge guard).
## Expected Behavior
Adds a `.claude/skills/nx-multi-repo-migrate` skill that documents the
end-to-end flow: per-package-manager migrate steps (npm / Yarn Berry /
pnpm / bun), the five gotchas that cause silent failures,
cleanup/verification before committing, and pushing branches + opening
linked draft PRs via Polygraph.
Docs/tooling only — no product code changes.
## Related Issue(s)
N/A
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/humble-koala-715f4ad1)
<!-- polygraph-session-end -->
---------
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
## Current Behavior
The plugin authoring docs explain the `createNodesV2` API and how to
build a tooling plugin, but there's no guidance on writing a
*performant* one. `createNodesV2` runs on every graph computation
(before any task cache is consulted), so an inefficient plugin slows
down every command for every developer and CI machine — most painfully
on Windows, where one team reported ~8 minute graph creation.
## Expected Behavior
Adds a new guide, **Write a Performant Project Graph Plugin**, under
`extending-nx`, collecting the patterns Nx's own first-party plugins
use:
- Prefer the batched `createNodesV2` API over per-file v1
- Cache results to disk with a content hash (`PluginCache` +
`calculateHashesForCreateNodes`), writing in a `finally` block
- Hoist shared work (package-manager detection, presets, base configs)
out of the per-file loop
- Load config files in parallel with `Promise.all`
- Keep file globs narrow and output deterministic
- Avoid per-file process spawning and heavy top-level imports
- Develop/debug with `NX_DAEMON`/`NX_CACHE_PROJECT_GRAPH` overrides and
diagnose slow graphs with `NX_PERF_LOGGING` + `nx report`
Also adds cross-links from the project graph plugin and tooling plugin
pages, plus a sidebar entry.
Preview:
https://deploy-preview-35981--nx-docs.netlify.app/docs/extending-nx/performant-project-graph-plugins
## Related Issue(s)
Resolves Linear DOC-516 (auto-linked via the branch name).
Co-authored-by: Jack Hsu <53559+jaysoo@users.noreply.github.com>
Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
## Current Behavior
Workspace tooling is on nx `23.0.0-rc.1`.
## Expected Behavior
Workspace tooling upgraded to nx `23.0.0-rc.2` via `nx migrate`.
## Changes
- Bump `nx` + all `@nx/*` packages `23.0.0-rc.1` → `23.0.0-rc.2`
(`package.json` + `pnpm-lock.yaml`).
- Apply the one migration in this jump — `@nx/gradle:
change-plugin-version-0-1-22` — bumping `dev.nx.gradle.project-graph`
`0.1.21` → `0.1.22` in `gradle/libs.versions.toml`.
Commits:
- `chore(repo): migrate to nx 23.0.0-rc.2` (version bump)
- `chore(repo): apply nx migration change-plugin-version-0-1-22`
(subject avoids brackets/dots to satisfy `scripts/commit-lint.js`)
Part of a coordinated 5-repo migration to nx 23.0.0-rc.2 (nx, ocean,
nx-labs, nx-examples, nx-console) via Polygraph.
## Related Issue(s)
N/A — routine version migration.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/wise-bison-1497570d)
<!-- polygraph-session-end -->
---
### Additional cleanup (commit `5f6c6c4`)
Removes the now-redundant `"cache": false` overrides on the inferred
`gradle:*ToMavenLocal` publish targets in
`packages/gradle/project-graph/project.json`. The
`dev.nx.gradle.project-graph` **0.1.22** plugin (pulled in by this
migration via `gradle/libs.versions.toml`) marks any `*ToMavenLocal`
task non-cacheable itself — see `isCacheable` in `TaskUtils.kt` (`if
(task.name.endsWith("ToMavenLocal")) return false`), added in #35973
alongside the 0.1.22 bump. So the manual overrides are no longer needed.
## Current Behavior
In a workspace that has **no root `tsconfig.base.json` /
`tsconfig.json`** — packages wired purely through package-manager
workspaces and `package.json` `exports` — every workspace-local plugin
listed in `nx.json` fails to load:
```
NX Failed to load 6 Nx plugin(s):
- @scope/my-plugin: unable to find tsconfig.base.json or tsconfig.json
...
```
**This affects the latest stable release, not just the 23 RCs.** The
source-first local plugin resolution was backported to the 22.x line in
**`22.7.3`**. Verified against the published dists of every version in
between:
| nx version | symlinked local plugin, no root tsconfig |
|---|---|
| 22.7.1 | ✅ works |
| 22.7.2 | ✅ works |
| 22.7.3 | ❌ broken (backport of #35631 / #35751 lands) |
| 22.7.4 | ❌ broken |
| 22.7.5 (`latest`) | ❌ broken |
| 23.0.0-rc.0 (`next`) | ❌ broken |
| 22.7.5 + this fix | ✅ works |
Since the source-first local plugin resolution (#35631, #35751),
`resolveNxPlugin` runs `lookupLocalPlugin` for any plugin whose
`require.resolve` lands inside the workspace (true for every symlinked
workspace package). `findNxProjectForImportPath` then calls
`readTsConfigPaths`, which **throws** when no root tsconfig exists —
aborting the whole plugin load before the function ever reaches its
tsconfig-independent fallbacks (package-metadata matching, then Node
resolution of the built artifact).
### Minimal reproduction (5 files, released `nx@22.7.5`)
```jsonc
// package.json
{ "name": "repro", "private": true, "devDependencies": { "nx": "22.7.5" } }
```
```yaml
# pnpm-workspace.yaml
packages:
- 'packages/*'
```
```jsonc
// nx.json
{ "plugins": ["@repro/my-plugin"] }
```
```jsonc
// packages/my-plugin/package.json
{ "name": "@repro/my-plugin", "exports": { ".": { "default": "./dist/index.js" } } }
```
```js
// packages/my-plugin/dist/index.js
module.exports.createNodesV2 = ['**/never-matches.xyz', async () => []];
```
`pnpm install && pnpm exec nx show projects` →
```
NX Failed to load 1 Nx plugin(s):
- @repro/my-plugin: unable to find tsconfig.base.json or tsconfig.json
```
With this PR's change applied to
`dist/src/project-graph/plugins/resolve-plugin.js` in the same install:
exit 0, projects list as expected.
## Expected Behavior
A missing root tsconfig just means the workspace has no tsconfig path
mappings. `readTsConfigPaths` returns an empty mapping,
`findNxProjectForImportPath` falls through to
`getWorkspacePackagesMetadata` matching, and plugin resolution proceeds
exactly as before the change (source via `exports` conditions when
present, built dist otherwise).
This matches the function's own tolerance for a tsconfig *without*
`compilerOptions.paths` (`return tsconfigPaths ?? {}`).
Given the 22.x backport, a backport of this fix to the 22.x line would
also be appreciated.
## Related Issue(s)
Fixes#35970
Standalone repro (5 files, released `nx@22.7.5`):
https://github.com/agcty/nx-repro-local-plugin-no-root-tsconfig
Regression introduced with the source-first local plugin resolution
(#35631 / #35751), present in `23.0.0-rc.0` and backported into the
stable line in `22.7.3` (22.7.2 and below unaffected). Originally
encountered upgrading a bun-workspaces monorepo (six local inference
plugins, per-package tsconfigs, no root tsconfig) from 22.7.1 to
23.0.0-rc.0 — all `nx` commands fail at plugin load. Additionally
verified end-to-end: in the affected workspace, with this change
applied, all 57 projects and all six local plugins load and tasks run;
the new unit test fails with exactly the pre-fix error when the source
change is reverted.
---------
Co-authored-by: Jason Jean <jason@nrwl.io>
## Current Behavior
Since #34798, the post-batch re-hash of tasks with
`dependentTasksOutputFiles` inputs is a silent no-op.
`applyFromCacheOrRunBatch` collects `needsRehashAfterExecution` tasks
and calls `hashBatchTasks(tasksToRehash)` after the batch executes, but
the bulk `hashTasks` it delegates to filters out every task that already
has a hash — and all re-hash candidates carry the preliminary hash
assigned before the batch ran.
As a result, batch tasks are cached under hashes computed from the
**pre-execution** state of their dependencies' outputs. The next
invocation hashes the settled disk state, computes a different hash, and
misses the cache — even when nothing changed. On gradle workspaces
(`@nx/gradle` batches by default and its inferred targets hash
`**/*.jar` / `**/*.class` across transitive dep outputs) this guarantees
that any CI step re-running the same targets after a step that executed
gradle work misses the whole chain. The stale keys can also produce
false hits: a later invocation whose preliminary hash matches a
previously stored stale key restores an artifact that does not
correspond to the current inputs.
Reproduced on a large gradle workspace (nx 23.0.0-rc.0):
- two back-to-back identical `nx run-many -t package` invocations: run 2
re-executed 61/117 tasks, gradle reporting `UP-TO-DATE` on every one of
them (nothing changed except the hash keys)
- locally: a deterministic 8-task miss wave on every second run, where
each stale task's recorded hash matches the pre-execution disk state and
the recomputed settled hash differs
## Expected Behavior
A second identical invocation is 100% cache hits. With this fix applied
(via pnpm patch) to the same workspace, the same two-invocation CI job
goes from failing (61 re-executed) to 117/117 cache hits.
## The Fix
Clear the preliminary `hash`/`hashDetails` on the tasks queued for
re-hashing so the bulk hasher actually re-hashes them against the
freshly written dependency outputs. Kept localized to the orchestrator
call site rather than changing the `!task.hash` filter in `hashTasks`,
since the filter prevents redundant re-hashing at every level of the
batch walk.
Adds a regression spec: a consumer with `dependentTasksOutputFiles`
whose in-batch dependency executes must get a fresh post-execution hash
(fails without the fix), and tasks whose deps were all cache hits are
not pointlessly re-hashed.
## Known residual (not addressed here)
Tasks that **cache-hit** during the batch walk are recorded under their
lookup-time hash. If a preliminary hash false-hits on a previously
stored stale key (e.g. entries written by versions affected by this
bug), the restored artifact is recorded under a key the next invocation
will not recompute. This leg is timing-dependent and much rarer than the
executed-task leg fixed here; flagging it for follow-up.
## Related Issue(s)
Regression introduced by #34798.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/affected-package-is-not-cached-a5603ad8)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: rarmatei <rarmatei@users.noreply.github.com>
## Current Behavior
The migrate `parseMigrationsOptions` tests read the ambient
`process.stdin.isTTY`. Run locally in a TTY, `canPrompt()` returns true,
so the `--include` eligibility check fetches
`supportsOptionalMigrations` from the npm registry for nonexistent
package versions and 4 tests fail. They pass on CI only because `isCI()`
forces `canPrompt()` false.
## Expected Behavior
The `parseMigrationsOptions` block pins a non-TTY stdin (as the sibling
`resolveInclude` and `resolve-package-version` specs do), so the
eligibility gate stays off and the suite is deterministic regardless of
the host terminal. No production behavior changes.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/fix-local-test-failures-fc5f3aee)
<!-- polygraph-session-end -->
- STYLE_GUIDE: new IA principle 6 "The golden path" (one command form,
permutations in KB guides, remove deprecated options outright, merge
converged sections).
- CLAUDE.md: instruct `STYLE_GUIDE.md` to be used when authoring docs.
## Current Behavior
Workspaces reference the `dev.nx.gradle.project-graph` Gradle plugin at
version `0.1.21`.
## Expected Behavior
Bump the plugin to `0.1.22`. This release carries the deterministic
project-configuration-hash fix from #35972 (sorting
`options.includeDependsOnTasks` so the `ProjectConfiguration` hash no
longer drifts between JVM runs).
The bump updates the version constant, the plugin's `build.gradle.kts`,
and adds a migration (`change-plugin-version-0-1-22`, gated at
`23.0.0-rc.2`) that updates existing workspaces' version catalogs and
`build.gradle(.kts)` files on `nx migrate`.
> Note: the published `0.1.22` plugin artifact must include #35972
before this is released.
## Related Issue(s)
Follow-up to #35972 (the source fix).
## Current Behavior
The `@nx/gradle` project-graph plugin infers a
`dependentTasksOutputFiles: "**/*.bin"` input for any task that depends
on a Java/Kotlin compile task. That happens because the compile tasks
declare their incremental-compilation state as task outputs, and the
plugin consolidates dependent-output file extensions into globs:
- `compileJava` / `compileTestJava` →
`build/tmp/<task>/previous-compilation-data.bin`
- `compileKotlin` / `compileTestKotlin` →
`build/kotlin/<task>/cacheable` and `…/classpath-snapshot` (`*.bin`)
These `.bin` files are the compilers' incremental bookkeeping —
rewritten on **every** compile and embedding **machine-specific absolute
paths**. No downstream task consumes them. As a result, every consuming
task (`test`, `jar`, `classes`, `testClasses`, `javadoc`,
`validatePlugins`, …) gets a hash input that changes on every build and
never matches across machines/agents, so those tasks miss the cache
constantly.
## Expected Behavior
The `.bin` incremental-compilation state is excluded from the inferred
`dependentTasksOutputFiles` inputs. Consuming tasks still hash the real
`.class`/`.jar` outputs, so cache correctness is preserved while the
spurious churn is removed. A unit test asserts a dependent task's `.bin`
output never produces a `**/*.bin` input (while `.class` still does).
Note: this changes the Gradle plugin source, so it takes effect once
shipped via a `dev.nx.gradle.project-graph` version bump.
## Related Issue(s)
Found during a Gradle project-graph hash-drift investigation; no
separate GitHub issue.
## Current Behavior
The daily **NPM Audit** workflow (`.github/workflows/npm-audit.yml`,
which runs `pnpm dlx audit-ci --critical`) is failing on a critical
advisory:
-
**[GHSA-w7jw-789q-3m8p](https://github.com/advisories/GHSA-w7jw-789q-3m8p)**
— `shell-quote`'s `quote()` does not escape newlines in object `.op`
values.
- Vulnerable range: `>= 1.1.0, <= 1.8.3`; patched in `1.8.4`.
- The lockfile resolved `shell-quote@1.8.3`, pulled in transitively
(primarily via `webpack-dev-server > launch-editor > shell-quote`, also
`react-devtools-core`).
Failing run: https://github.com/nrwl/nx/actions/runs/27386736287
## Expected Behavior
`shell-quote` is pinned to the patched `^1.8.4` via a pnpm override, so
all consumers resolve the safe version and the audit passes with
`critical: 0`.
Verified locally with the exact CI command:
```
pnpm dlx audit-ci --critical --report-type summary
→ "critical": 0 → Passed pnpm security audit.
```
`shell-quote@1.8.4` (published 2026-05-22) clears the repo's
`minimumReleaseAge` gate.
## Related Issue(s)
Fixes the failing scheduled NPM Audit workflow.
## Current Behavior
The `@nx/gradle` project-graph plugin produces a non-deterministic
project configuration. For each target it emits
`options.includeDependsOnTasks` in the iteration order of a set that is
populated by walking Gradle's internal dependency containers
(`findProviderBasedDependencies` → lifecycle/input-property providers).
That iteration order follows JVM identity hashcodes, so it changes on
every fresh JVM.
Nx hashes a target's `options` verbatim (JSON string, unsorted) into the
project's `ProjectConfiguration` hash component. As a result, the
`ProjectConfiguration` hash drifts between otherwise-identical runs, and
tasks miss the cache on rerun even though nothing changed.
Reproduced by running the report task in 5 fresh JVMs: the
`gradle-project-graph` project produced 5 distinct hashed
configurations, differing only in `options.includeDependsOnTasks`
ordering.
## Expected Behavior
The project configuration is deterministic: identical inputs produce the
same `ProjectConfiguration` hash across runs, so task caching works on
reruns. Sorting `includeDependsOnTasks` collapses all 5 runs to a single
stable value.
A regression test (`processTask emits includeDependsOnTasks in sorted
order`) guards against reintroducing the unsorted ordering.
## Related Issue(s)
Caught via Nx Cloud task comparison (project configuration hash drift on
rerun); no separate GitHub issue.
## Current Behavior
The workspace pins nx `23.0.0-rc.0`.
## Expected Behavior
The workspace is upgraded to nx `23.0.0-rc.1` via `nx migrate`. This
single release-candidate step is **dependency-only** — `nx migrate`
generated no `migrations.json`, so there are no code migrations to run.
Only `package.json` (`nx` + `@nx/*` → rc.1) and `pnpm-lock.yaml` change.
## Related Issue(s)
N/A — routine version bump. Part of a coordinated 5-repo migration to nx
23.0.0-rc.1.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/Migrate-Nx-default-repos-to-23.0.0-rc.1-403697eb)
<!-- polygraph-session-end -->
## Current Behavior
When a package manager's minimum-release-age (cooldown) policy is
active, `nx migrate` resolves dist-tags through per-package-manager
emulation, and degrading a too-new tag produced inconsistent, often
nonsensical results. `nx migrate next` could resolve to an internal
`pr.*` preview build or a years-old release candidate; the npm path
diverged from what npm itself installs; and yarn errored outright. Each
of npm, pnpm, yarn, and bun carried its own tag-degrade implementation.
## Expected Behavior
Dist-tag degrade is unified into a single rule across all four package
managers. When the resolved tag target is within the cooldown window,
the candidate pool is every version at or below it that is stable, in
the target's own prerelease channel, or on a lower rung of an explicit
channel ladder (`alpha < beta < rc`). That pool is ordered, and the
first compliant version wins:
- Prereleases of the target's exact `major.minor.patch` come first — own
channel, then lower ladder rungs.
- Then everything else, ordered by most recently published.
So `latest` (or any stable tag) lands on the newest compliant stable. A
prerelease tag keeps a compliant prerelease of the release it points at:
every same-line release candidate is exhausted first, and a blocked
`rc.0` with no older rc sibling falls to a same-line `beta.x` rather
than skipping back to the previous stable. A newer-published cross-line
backport can't outrank the same-line beta, and the degrade never climbs
the ladder upward. Channels with no place on the ladder (such as the
internal `pr` builds, or `canary`) stay walled off entirely. `next` is
deliberately left off the ladder: it is pre-rc in some ecosystems
(Angular) but a rolling dev snapshot in others — exactly the class of
build a degrade must never land on. The channel is derived generically
from a version's prerelease identifier, so it works for any package's
naming convention.
## Implementation Details
A shared `degradeTagToCompliant` helper replaces npm's `<=tagTarget`
recursion, pnpm's same-major degrade (and its deprecation tie-break
machinery), yarn's latest-only walk-down, and bun's channel walk. It
builds the pool, orders it (same-line own-channel, then same-line
lower-rung, then by publish date), and returns the first version that
passes the caller's maturity test; each package manager supplies only
that test and its own violation shape. The helper guards against
non-semver dist-tag targets and registry version entries
(`semver.compare` previously threw, and the migrate consumer swallows
unknown errors into a real-install fallback).
The cleanup also drops the now-dead `latestTagDegrade` behavior axis and
the redundant pnpm 10.20 behavior row, along with the tests that pinned
the old version-specific behavior. The npm policy-reader specs are
isolated from the host's real `~/.npmrc` and reuse the real `.npmrc`
parser (`parseNpmrcContent`) instead of a hand-copied mirror, and npm's
tag-path ENOVERSIONS branch is now covered.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/fix-nx-migrate-min-release-99acf946)
<!-- polygraph-session-end -->
---------
Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
## Current Behavior
- The `update-all-repos` script fails to find its config file. Since the
build moved off the workspace-root `dist/` (#35915), `CONFIG_FILE` in
`update-repo.ts` and `setup-repos.ts` resolves to
`tools/tools/update-repos/config/repos.json`, which does not exist.
- `nx migrate` runs with the default (`latest`) migrate CLI version.
- Migrations run without the `--agentic` flag.
## Expected Behavior
- `CONFIG_FILE` resolves relative to the package root (two levels up
from the compiled `dist/src` output), so the scripts find
`tools/update-repos/config/repos.json` again.
- Both migrate invocations run with `NX_MIGRATE_CLI_VERSION=next`, so
the next version of the migrate CLI drives the flow. The env var is
passed through the spawn environment because commands are wrapped in
`mise exec --`, which would treat a `VAR=x` prefix as a program name.
- `--agentic` is passed to `nx migrate --run-migrations`.
## Related Issue(s)
N/A — tooling fix found while running the update-all-repos script.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/Fix-update-all-repos-tooling-script-199f7e9b)
<!-- polygraph-session-end -->
## Current Behavior
Nx Cloud distributed-execution workers each open their own connection to
the Nx daemon and send their full (filtered) process environment with
messages like `GET_ESTIMATED_TASK_TIMINGS`. Per-worker Nx Cloud vars
(`NX_CLOUD_WORKER_ID`, `NX_CLOUD_WORKER_INDEX`, `NX_CLOUD_EXECUTION_ID`,
`NX_CLOUD_ORCHESTRATOR_SOCKET`) are not excluded from the env the daemon
reflects, so they differ from the env the daemon was started with — and
differ between workers. Every worker that talks to the daemon rewrites
the daemon env and invalidates the project-graph cache, forcing a full
graph recompute on each worker message (`Graph recompute necessary due
to env variable refresh`). The daemon also does not log which keys
changed, so the churn is hard to diagnose.
## Expected Behavior
`NX_CLOUD_`-prefixed vars are excluded from the env reflected by the
daemon (they cannot affect the project graph), so connecting workers no
longer trigger graph recomputes. The daemon also logs the changed env
keys when a refresh does occur, to make future env-driven recompute
churn diagnosable.
## Related Issue(s)
Surfaced via staging DTE agent logs (no GitHub issue).
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nx-cloud-dte-debug-aee8e394)
<!-- polygraph-session-end -->
## Current Behavior
The workspace is on nx `23.0.0-beta.24`.
## Expected Behavior
The workspace is migrated to nx `23.0.0-rc.0`. All `nx` / `@nx/*`
packages are bumped to `23.0.0-rc.0` (and the catalog `webpack` entry
moves 5.105.2 → 5.107.2). The three generated migrations (`@nx/web`,
`@nx/react`, `@nx/angular` internal-subpath-import rewrites) ran and
made **no** changes — this repo doesn't use those deep `src/*` imports.
Dependency-only migration; no source code modified.
## Related Issue(s)
N/A — routine version migration.
---
Part of a coordinated nx `23.0.0-rc.0` migration across nrwl repos (see
linked PRs). Draft.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/Coordinated-Nx-23.0.0-rc.0-Migration-Across-nrwl-Repositories-39dcf816)
<!-- polygraph-session-end -->
## Current Behavior
When AI agents run Nx inside a network-restricted sandbox (e.g. Claude
Code's sandboxed Bash), analytics requests to
`https://www.google-analytics.com/g/collect` are blocked because the
hostname is not on the sandbox's allowlist. Depending on the agent's
configuration, this either silently drops the events or surfaces a
confusing permission prompt asking why running tests wants to reach
`google-analytics.com`.
## Expected Behavior
`nx configure-ai-agents` (via `setupAiAgentsGenerator`) now adds
`www.google-analytics.com` to `sandbox.network.allowedDomains` in
`.claude/settings.json`, alongside the existing marketplace/plugin
configuration it already manages. Analytics requests during sandboxed
CLI runs go through without prompting.
- Existing user-defined allowed domains are preserved; the entry is
appended idempotently (no duplicates on re-run).
- The domain lives in a shared constant
(`packages/nx/src/ai/constants.ts`) noted to stay in sync with
`GA_ENDPOINT` in `packages/nx/src/native/telemetry/constants.rs`.
- Existing workspaces pick this up through the regular `nx
configure-ai-agents --check` drift detection.
## Related Issue(s)
Internal:
[NXC-4091](https://linear.app/nxdev/issue/NXC-4091/allow-analytics-requests-during-sandboxed-cli-runs)
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The `migrate-to-vitest-4` migration defers `vitest.workspace.*` handling
entirely to the AI step, which inlines the project globs verbatim into a
root `vitest.config.*` under `test.projects`. After the upgrade,
packages that run tests through a package.json `vitest` script without a
local config fail with "No projects were found": Vitest 4 discovers the
new root config by walking up from the package directory and resolves
the `test.projects` globs relative to that directory. Workspaces
migrated without the AI step get no workspace-file handling at all.
Reported while testing the Nx 23 betas; reproduction:
https://github.com/juristr/nx23-vitest-issue-repro.
## Expected Behavior
The migration handles workspace files deterministically and produces a
working setup on its own:
- Static `vitest.workspace.*` files (the Nx-generated shape) are inlined
into the root `vitest.config.*` under `test.projects` and deleted. Only
dynamic shapes are forwarded to the AI step.
- Packages that run vitest via a package.json script and have no local
config get a minimal `vitest.config.*` generated, so their tests keep
running from the package directory and `@nx/vitest` infers their test
target.
- When the inlined globs match both a `vite.config.*` and a
`vitest.config.*` in the same directory resolving to the same project
name, the `vite.config.*` file is excluded with a negative glob. This
matches vitest's own vitest-over-vite preference and avoids the "Project
name ... is not unique" startup error that the copied globs previously
produced on root-level runs. Cases that can't be determined statically
are forwarded to the AI step as advisories.
Anything the migration cannot do safely (dynamic workspace files,
configs it can't merge into) is still deferred to the AI step with
accurate context, and the instructions teach the agent the same rules
for the files it inlines itself.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/troubleshoot-migrate-to-vitest-4-issue-ba08225a)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The `nx migrate` flow emits no analytics, so there is no visibility into
how migrations are invoked, where they fail, or how features like
multi-major and agentic runs are used.
## Expected Behavior
The migrate flow now reports Google Analytics events (gated by the
`nx.json` analytics opt-in) across the generate and run phases:
invocation and flags, interactive prompt choices, completion (resolved
include, fetch method, multi-major decision), run lifecycle with
migration counts, and structured errors (phase code, error name, and an
nx-relative throw location).
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nxc-4508-de945c44)
<!-- polygraph-session-end -->
---------
Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The generated GitLab CI workflow (`nx generate @nx/workspace:ci-workflow
--ci=gitlab`) fails on new repositories with errors like:
fatal: ambiguous argument 'HEAD~1': unknown revision or path not in the
working tree.
This happens because:
1. GitLab's default shallow clone doesn't have full git history
2. `CI_COMMIT_BEFORE_SHA` is always
`0000000000000000000000000000000000000000` for merge requests and first
commits
3. Environment variables `NX_HEAD` and `NX_BASE` weren't exported, so Nx
couldn't read them
## Expected Behavior
The GitLab CI workflow should work correctly on:
- New repositories with no previous commits
- Merge request pipelines
- Main branch push pipelines
## Related Issue(s)
Fixes https://linear.app/nxdev/issue/NXC-3707
## Changes Made
1. **Added `GIT_DEPTH: 0`** - Fetches full git history instead of
shallow clone
2. **Changed `only` branch to use template variable** - Uses `<%=
mainBranch %>` instead of hardcoded `main`
3. **Fixed `NX_BASE` and `NX_HEAD` exports** - Added `export` keyword
and improved fallback logic:
- Uses `CI_MERGE_REQUEST_DIFF_BASE_SHA` for merge requests
- Falls back to `HEAD~1` for regular commits
- Falls back to `HEAD` for initial commits (no parent)
## Testing
Verified on real GitLab CI at `gitlab.com/AgentEnder/nx-gitlab-ci-test`:
- ✅ Main branch push pipeline passed
- ✅ Merge request pipeline passed
## Current Behavior
Inferred (`createNodes`) targets that set an `executor` but omit
`continuous` force project-graph normalization (`normalizeTarget`) to
read the executor's `schema.json` — resolved from `dist` via
`executors.json` — solely to infer the `continuous` flag. In this
workspace that surfaces as Nx Cloud sandbox violations (e.g.
`playwright:test` reading
`packages/playwright/dist/src/executors/merge-reports/schema.json`).
## Expected Behavior
The affected `createNodes` targets declare `continuous` explicitly, so
normalization short-circuits the `!('continuous' in target)` guard and
never reads the executor schema:
- `@nx/playwright` — `merge-reports`
- `@nx/docker` — `release-publish`
- `@nx/expo` — `install`, `prebuild`, `build`
- `@nx/react-native` — `sync-deps`
The `@nx/web:file-server` targets in storybook/vite/webpack/rspack/nuxt
already declare `continuous: true`, which is why they were never
affected.
## Related Issue(s)
N/A — Nx Cloud sandbox violation cleanup.
## Current Behavior
The v23.0.0 `migrate-create-nodes-v2-to-create-nodes` migration (shipped
in 22 plugins) rewrites **only import/export named bindings** of
`createNodesV2` to `createNodes` — it never touches value references in
the file body.
So a lone `import { createNodesV2 }` (or one deduped against an existing
`createNodes`) is renamed to `import { createNodes }`, but any value
usage of `createNodesV2` is left dangling:
```ts
import { createNodesV2 } from '@nx/js/typescript'; // → renamed to createNodes
addPlugin(graph, '@nx/js/typescript', createNodesV2, {}); // ← left as-is → TS2304: Cannot find name 'createNodesV2'
```
The existing specs only ever asserted on import lines, never on a file
that *uses* `createNodesV2` as a value, so the gap went unnoticed.
(Found while running the migration against a real workspace.)
## Expected Behavior
When the migration renames a local `createNodesV2` import binding, it
now also renames in-file **value references** to `createNodes`, so the
file still compiles. The rename is AST-scoped and conservative — it
skips:
- property accesses (`x.createNodesV2`) and qualified type names
- object-literal keys
- declaration names that shadow the import
- strings and comments (never `Identifier` nodes)
and expands a shorthand property (`{ createNodesV2 }` → `{
createNodesV2: createNodes }`) to preserve the key. Aliased imports (`{
createNodesV2 as cn }`) and re-exports keep their local name, so they
never trigger a usage rewrite.
Applied to all 22 plugin copies of the migration; regression tests added
for the value-usage cases (21 specs; `@nx/gradle`'s multi-specifier spec
keeps its existing structure and the shared logic is covered by the
others).
## Related Issue(s)
N/A — follow-up hardening of the v23 migration.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/humble-koala-715f4ad1)
<!-- polygraph-session-end -->
---------
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Before we added AI assistance from the cloud sandboxing dashboard we had
added a skill to the nx repo to deal with it, and that skill is getting
reached for instead of the cloud prompt which is hurting dogfooding.
## Expected Behavior
The skill is removed so we are dogfooding the cloud prompt better
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
The `@nx/webpack` v23 migration adds `webpack`, `webpack-cli`, and
`webpack-dev-server` to **`dependencies`**. They're declared with
`"alwaysAddToPackageJson": true`, and a boolean `true` defaults to
`dependencies` (see `migrate.ts` — `alwaysAddToPackageJson` resolves to
`'dependencies'` when truthy-but-not-a-string).
These are build-time tools, so they don't belong in runtime
`dependencies` — and it's inconsistent with `@nx/webpack` itself, which
workspaces keep in `devDependencies`.
## Expected Behavior
The migration adds them to **`devDependencies`** by declaring
`"alwaysAddToPackageJson": "devDependencies"` (a string value routes to
the named section — this is already a supported, tested form, e.g.
`migrate.spec.ts`). Packages that already exist in `dependencies` are
left in place by the migration runner, so this only governs new
additions.
## Related Issue(s)
N/A
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/humble-koala-715f4ad1)
<!-- polygraph-session-end -->
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
## Current Behavior
The nx-dev docs app runs on Next.js 14.2.35, which is end-of-life.
## Expected Behavior
nx-dev runs on the latest Next 16 (16.2.7). React stays at 18.3.1
(peer-accepted). App-router course page `params` are now async, and the
unused `eslint-config-next` dependency is dropped (`next lint` is
removed in v16; nx-dev lints via a flat config).
The only affected pages are the courses:
https://deploy-preview-35923--nx-dev.netlify.app/courses
## Related Issue(s)
DOC-518
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/upgrade-next-to-v16-1eae0db6)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The workspace is on nx 23.0.0-beta.24.
## Expected Behavior
Migrated to nx 23.0.0-beta.25 via `nx migrate`. All 22 `@nx/*` packages
bumped beta.24→beta.25, the 3 internal-subpath-rewrite migrations were
run (no source changes), and the shared webpack catalog version was
bumped 5.105.2→5.107.2.
## Related Issue(s)
N/A — routine nx version migration.
Part of a coordinated multi-repo nx 23.0.0-beta.25 upgrade.
<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/humble-koala-715f4ad1)
<!-- polygraph-session-end -->
---------
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
## Current Behavior
The migration config flag that opts a package into `nx migrate
--include`
(optional migrations) is named `supportsOptionalUpdates`. It is read
from a
package's `nx-migrations` / `ng-update` config and threaded through the
migrate
command. The name says "updates", but the feature it gates is optional
**migrations**, so the name is misleading.
## Expected Behavior
The flag is renamed to `supportsOptionalMigrations` everywhere it is
defined and
consumed:
- The `NxMigrationsConfiguration` / `NxPackageJson` type field and the
`readNxMigrateConfig` parsing in `packages/nx/src/utils/package-json.ts`
- The `--include` gate and related plumbing in
`packages/nx/src/command-line/migrate/migrate.ts`
- The `"supportsOptionalMigrations": true` flag in every first-party
plugin's
`package.json`
- The associated unit tests
This is a purely internal rename — the flag is both defined and consumed
inside
the nx repo, so no backwards-compat shim is required. Behavior is
unchanged.
## Related Issue(s)
N/A — internal naming cleanup.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The single test in `e2e/release/src/custom-registries.test.ts` ("should
respect registry configuration for each package") intermittently fails
with a jest timeout:
```
thrown: "Exceeded timeout of 1000000 ms for a test."
```
The test runs ~13 sequential `nx release publish --dry-run` commands,
most of which target intentionally-unreachable registry hostnames (e.g.
`publish-config-registry.com`, `scoped-registry.com`,
`ignored-registry.com`). Under npm 11 (Node 24+), `npm publish
--dry-run` performs a network preflight to the configured registry. When
that host doesn't resolve, npm's default retry backoff
(`fetch-retry-mintimeout=10000` + `fetch-retry-maxtimeout=60000`) sleeps
~70s per command before giving up. Across all the unreachable-registry
calls this adds up to ~900s of pure idle waiting, pushing the whole test
past its `1_000_000` ms budget. Because it sits right on the boundary,
the test is flaky — small timing variations tip it over.
## Expected Behavior
The test completes well within its timeout. npm still retries registry
requests (preserving resilience for the real verdaccio reads in the
second half of the test), but the retry backoff is capped so the wasted
sleep per unreachable call drops from ~70s to ~1.1s.
This is done by adding the following to both `.npmrc` blocks the test
writes:
```
fetch-retry-mintimeout=100
fetch-retry-maxtimeout=1000
```
The dry-run output and all assertions are unchanged — `npm publish
--dry-run` exits 0 with identical output regardless of registry
reachability; only the idle backoff time is reduced.
## Related Issue(s)
N/A — test stabilization (flaky e2e fix).
## Current Behavior
Following the local-dist migration (#35900), several projects were still
writing build or typecheck output to the shared workspace-root `dist/`:
- **Five packages' spec configs** — `@nx/angular-rspack`,
`@nx/angular-rspack-compiler`, `@nx/dotnet`, `@nx/maven`, and `nx` —
kept their `tsconfig.spec.json` `outDir` pointing at
`dist/packages/<name>/spec` even though their lib output had been
migrated to a local `dist`.
- **`tools/workspace-plugin`** built to the shared
`dist/workspace-plugin` (its conformance rules are loaded from the built
output).
- **The graph client** bundled to `dist/apps/graph` and emitted its
typecheck declarations to `dist/graph/client`; the graph libs wrote
spec/storybook typecheck output under `dist/out-tsc`.
- **nx-dev** lib/spec tsconfigs pointed their `outDir` at
`dist/out-tsc/...`.
Separately, astro-docs documentation generation resolved each plugin's
`schema.json` from its **built** `dist` (the migrated
`generators.json`/`executors.json` refs point at `./dist/src/...`).
Reading another project's build output tripped the task sandbox with
undeclared `dist/**/schema.json` reads.
## Expected Behavior
Each project builds to its own local directory, leaving the
workspace-root `dist/` alone:
- The five spec configs now emit to local `dist/spec`.
- `tools/workspace-plugin` builds to `tools/workspace-plugin/dist`; the
seven conformance rule paths in `nx.json` are updated, and
`main`/`typings` are repointed into `dist` so the `@nx/js/typescript`
plugin still infers its build target.
- The graph client bundles to `graph/client/dist` (the `nx` package's
`assets.json` input is updated to match); its typecheck output goes to a
local `out-tsc` so the emitted declarations stay out of the copied
bundle dir. Graph lib spec/storybook output moves to local `dist`.
- nx-dev lib/spec outputs move to local `dist` / `dist/spec`
(preventative — these were vestigial as no target runs `tsc` on most
nx-dev libs today).
astro-docs now reads plugin `schema.json` from source (the verbatim
copy), which also matches `astro-docs:build`'s already-declared
`packages/*/src/.../schema.json` inputs — removing the sandbox violation
without weakening cache correctness.
Validation: `nx build workspace-plugin` + `nx conformance` pass from the
new path; the graph client builds and copies into
`packages/nx/dist/src/core/graph` with no declaration leakage; `nx build
astro-docs` is green (753 pages, no schema-resolution errors); affected
graph and nx-dev `typecheck`/`lint`/`test` pass.
## Related Issue(s)
Follow-up to #35900 (local-dist build migration). No separate issue.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The last four publishable packages still on the old layout —
`@nx/maven`, `@nx/dotnet`, `@nx/angular-rspack`, and
`@nx/angular-rspack-compiler` — build with classic `module: commonjs` /
`moduleResolution: node` and inherit `baseUrl`. They were never on the
main local-dist migration board, so they're the only first-party
packages left on classic resolution. This blocks moving the workspace to
a single compiler (tsgo / TS7), which hard-errors on both classic
`node10` resolution (`TS5108`) and `baseUrl` (`TS5102`).
## Expected Behavior
All four packages build with `nodenext` module resolution and expose the
`@nx/nx-source` export condition, matching the already-migrated packages
— so every first-party package is now on `nodenext`. `rootDir: src` is
intentionally kept (rather than `.`) so the
generators/executors/migrations manifest paths and the `versions.ts`
self-reference don't need rewriting; it's functionally identical for the
compiler.
Two nodenext-specific fixes were required:
- **`@nx/angular-rspack-compiler`**: `@angular/compiler-cli` ships
`"type": "module"` typings whose extensionless `export *` chains don't
resolve under nodenext, so `CompilerHost` / `readConfiguration` come
back as "no exported member". They're replaced with minimal local type
declarations. The package is now only referenced via a dynamic runtime
import, so it's added to the dependency-checks `ignoredDependencies`.
- **`@nx/angular-rspack`**: the render and routes-extractor workers
loaded the rspack-built CommonJS server bundle via `await
import(serverBundlePath)`. Under `commonjs` that downleveled to
`require()`, which exposes the bundle's named exports
(`ɵSERVER_CONTEXT`, `ɵgetRoutesFromAngularRouterConfig`); under
`nodenext` it stays a true ESM import and those resolve as `undefined`,
breaking the angular-rspack SSR/SSG example builds. They now load the
bundle with `require()`.
Validated with `nx affected -t build,lint,test` — 21 projects / 121
tasks green, including all 14 `@nx/angular-rspack` example apps.
The actual tsgo compiler flip (re-add `@typescript/native-preview`, drop
the base `baseUrl`, set `strict: false`, set `compiler: "tsgo"` on the
`@nx/js/typescript` plugin) is a separate follow-up.
## Related Issue(s)
Implements Linear NXC-4538 (auto-linked via the branch name). Follow-up:
NXC-4539 enables tsgo workspace-wide. No GitHub issue to close.
## Current Behavior
The React Native ecosystem plugins (`@nx/detox`, `@nx/expo`,
`@nx/react-native`) and `@nx/remix` were not compliant with the
multi-version support initiative (`nx migrate --first-party-only`). None
of them enforced a supported-version floor on their generators or
preserved user-pinned versions, and:
- **`@nx/detox`** pinned a single `detox` version with no floor; the
`22.0.0` migration bundled a cross-major `@config-plugins/detox` bump
together with same-major bumps, ungated.
- **`@nx/expo`** had SDK 53/54 lanes but not the current SDK 55; several
SDK-specific migrations ran unconditionally; `expo` was not declared as
a peer.
- **`@nx/react-native`** was hardcoded to a single, upstream-Unsupported
RN `0.79.3` — no version map, no floor, `react-native` undeclared as a
peer.
- **`@nx/remix`** generators overwrote installed `react` / `vite` /
`@remix-run/*` versions and had no floor enforcement.
## Expected Behavior
Each plugin now follows the canonical multi-version compliance shape
(`assertSupported<Pkg>Version` on every generator entry point, user-pin
preservation via `keepExistingVersions`, source-major-gated migrations,
and a parameterized floor spec):
- **`@nx/detox`** — v20-only floor (`detox` is v20-only upstream; v19
has been unmaintained since 2022). The `22.0.0` migration is split so
the cross-major `@config-plugins/detox` bump is gated on `expo >=53
<54`, while the `detox`/`jest-dom` bumps stay ungated for bare React
Native + Detox workspaces.
- **`@nx/expo`** — adds the **SDK 55** lane (RN `0.83.6`, React `19.2`)
as the new default with `isExpoV55` detection (53/54 lanes retained);
declares `expo` as a peer; floor at SDK 53; SDK-specific migrations
gated with `requires`.
- **`@nx/react-native`** — per-minor version map for the Active line
(`0.83`/`0.84`/`0.85`, default `0.85`) routed through `versions(tree)`;
declares `react-native` as a peer; floor at `0.83.0`; the
`remove-deprecated-deps` migration gated on `react-native >=0.76 <0.79`.
- **`@nx/remix`** — stays Remix v2 (React Router v7 remains in
`@nx/react`) and documents the split; floor at `@remix-run/dev >=2.0.0`;
generators preserve installed `react`/`vite`/`@remix-run/*` versions
instead of overwriting them.
A shared test utility in `@nx/devkit/internal-testing-utils`
(`assertGeneratorsEnforceVersionFloor`) was extended to resolve
generators declared via `implementation` as well as `factory`, so
`@nx/remix` can use the shared floor spec.
Supported-version docs for `@nx/expo`, `@nx/react-native`, and
`@nx/remix` are updated.
## Related Issue(s)
Tracked in Linear (not GitHub Issues): NXC-4385, NXC-4389, NXC-4400,
NXC-4402.
## Current Behavior
The publish workflow's **Build FreeBSD** job fails with the VM's OOM
killer killing the build (`/usr/bin/ssh` exit code 137):
```
swap_pager: out of swap space
kernel: pid ... (node) was killed: failed to reclaim memory
kernel: pid ... (cargo) was killed: failed to reclaim memory
```
The FreeBSD bindings build runs inside a QEMU VM via
`cross-platform-actions/action`, which defaults to **6G** of memory on a
Linux host. The job dies ~90s in — during project-graph computation (the
main `nx` process plus isolated plugin workers), before the native
(cargo) build even ramps up. That working set outgrew 6G, so the VM ran
out of memory and swap.
## Expected Behavior
The VM is allocated **12G** (the `ubuntu-latest` runner has ~16G,
leaving ~4G for QEMU + the host). Graph computation and the native build
complete without exhausting VM memory. This is a pure infra knob — no
change to nx behavior. `cpu_count` is left at the default.
## Related Issue(s)
N/A — CI fix.
## Current Behavior
The first-party Nx plugins still ship migration code (and
`packageJsonUpdates`) targeting Nx **v20 and earlier**. With v23 on the
way, those migrations are dead weight in the published packages and
their `migrations.json` manifests.
## Expected Behavior
All migrations prior to **v21** are removed across the first-party
plugins via the `@nx/workspace-plugin:remove-migrations` generator
(`--v=21`), keeping the two most recent prior majors (v21, v22) plus v23
— consistent with prior major-release prep (#30839 removed `< v19`,
#32904 removed `< v20`).
`packages/nx` and `packages/angular` are intentionally preserved (`nx`
is needed for `nx repair`; `angular` keeps its migrations until LTS
support is dropped).
Because the recent move to local dist builds (#35900) means every
plugin's `migrations.json` now references built (`./dist/...`) paths,
the generator can no longer auto-delete the corresponding source files.
The orphaned pre-v21 migration source directories were removed manually
as part of this change.
## Related Issue(s)
N/A — release preparation for v23.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
`nx release --group=<group>` walks the full commit range since the last
matching tag and resolves each commit's conventional-commit scope
against the **entire** project graph (`findMatchingProjects` over
`projectGraph.nodes`). If a scope is ambiguous against *any* projects in
the workspace, Nx throws unconditionally — even when those projects
aren't in the release group currently being processed.
In workspaces with slash-style subpath project names (e.g. a meta
package `@scope/cui` plus subpath packages `@scope/cui/forms`,
`@scope/cui/select`, …), a single commit with a short-form scope like
`fix(cui): …` permanently blocks every release group whose tag-range
includes it — including unrelated groups that don't contain any of the
matching projects.
Closes#35744.
## Expected Behavior
The ambiguity check should be scoped to the active release group's
projects. A scope that only collides with projects *outside* the group
should fall through to the file-affectedness path that's already used
when a commit has no scope at all.
## Fix
In `getCommitsRelevantToProjects`, `projects: string[]` (already in the
function signature as the active release group's projects, materialized
as `projectSet` at the top) is now applied to the per-pattern ambiguity
check and to the `scopedProjects` set:
1. **Per-pattern check filters to the group.** `inGroupMatches =
perPatternMatches.filter(p => projectSet.has(p))`. Throw only when
`inGroupMatches.length > 1` — ambiguity *within* the group is still a
real error.
2. **`scopedProjects` is intersected with the group.** Cross-group
matches no longer bleed into `isProjectScopedCommit` downstream.
When the filtered set is empty or singleton, the commit is treated
identically to a commit with no scope for this group's processing —
`isProjectScopedCommit` falls back to `false` for projects whose
`affectedGraph` includes them via files, matching the existing
scope-less behavior.
## Tests
`packages/nx/src/command-line/release/utils/shared.spec.ts`:
- **Existing test for in-group ambiguity** (`should throw when commit
scope matches multiple projects (ambiguous scope)`) — converted from a
`try { … } catch (err) { expect(…) }` pattern (which silently passed if
no throw happened) to `await expect(…).rejects.toThrow(…)`. Existing
behavior preserved: throws when `@foo/graph` + `@bar/graph` are both in
the active group with scope `graph`.
- **New test for cross-group ambiguity** — active group `['lib-a']`,
scope `graph` matches `@foo/graph` + `@bar/graph` (both outside the
group). No throw. Commit is included via file-affectedness,
`isProjectScopedCommit: false`.
- **New test for partial cross-group ambiguity** — active group
`['@foo/graph']`, scope `graph` matches `@foo/graph` (in group) +
`@bar/graph` (out). Filtered to one match. No throw, treated as scoped
to `@foo/graph` (`isProjectScopedCommit: true`).
Also extended `createMockCommit` to accept an explicit `scope` argument
so tests actually exercise the scope-parsing path. The existing
ambiguity test relied on `feat(graph): …` in the commit *message*, but
`commit.scope` itself was hardcoded to `''`, so `scopePatterns` was
always empty and the throw never fired in the test — the assertion lived
in a catch block that was never reached. The conversion to
`rejects.toThrow` plus the explicit `scope` argument makes the original
test actually verify what its name says.
## Verification
```
pnpm exec nx test nx --testPathPatterns=shared.spec
# Tests: 38 passed, 38 total
pnpm exec nx test nx --testPathPatterns="release"
# Tests: 1 skipped, 503 passed, 504 total
pnpm exec nx lint nx
# 0 errors (2 pre-existing warnings, unrelated)
```
Minimal real-world repro repo:
https://github.com/jmclellan-crexi/nx-release-scope-ambiguity-repro
## PR Checklist
- [x] Bug-fix only — no API surface changes
- [x] Tests added for new behavior + existing test converted to assert
what it claims
- [x] Backwards compatible — in-group ambiguity still throws; scope-less
commits unchanged; unambiguous scopes unchanged
- [x] No documentation changes needed (behavior is now closer to what
the existing docs imply)
---------
Co-authored-by: Justin McLellan <jmclellan@beast>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Current Behavior
`nx migrate --include` only applies when migrating Nx itself; for any
other target the option is rejected, and eligibility is derived from
Nx-specific version math.
## Expected Behavior
`--include` now applies to any package whose `nx-migrations` /
`ng-update` config declares `supportsOptionalUpdates: true`, read from
the package metadata via the shared fetcher (registry-first, install
fallback). It accepts `required` (the target package and the related
packages it ships with), `optional` (the optional dependency updates
those packages recommend), or `all` (default). The `--interactive` /
`x-prompt` confirmation flow is deprecated in favor of `--include`
(removal slated for Nx v24; no hard error yet), and
`supportsOptionalUpdates: true` is set on the first-party packages.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nxc-4517-37fe1336)
<!-- polygraph-session-end -->
## Current Behavior
During `nx migrate`, the up-front **"Enable the agentic flow?"** prompt
rendered every choice's description at once (via enquirer's built-in
per-choice `hint`), producing a wall of muted text beside the options.
## Expected Behavior
Each choice's description is shown **only when that option is focused**
— as a single dimmed line in the prompt footer that updates as you arrow
through the list. This mirrors the focused-description pattern already
used in `configure-ai-agents`.
Implementation: the per-choice blurb moved from enquirer's `hint` field
(which auto-renders on every row) to a plain `description` field that
enquirer ignores, and a `footer` function surfaces
`this.focused.description`.
## Related Issue(s)
N/A
## Current Behavior
The repository still contains a `.circleci/config.yml` file. It is a
leftover transition stub from when Nx migrated its own CI from CircleCI
to GitHub Actions — its only job (`main-linux`) does nothing but echo a
message:
> "We are in the process of transitioning from Circle CI to GitHub
Actions. For details about your build results, consult github actions
build logs."
The migration to GitHub Actions is long complete, so the file serves no
purpose.
## Expected Behavior
The dead `.circleci/config.yml` is removed. Nx's own CI continues to run
on GitHub Actions, unaffected.
Note: this only removes the nx repo's *own* CircleCI pipeline. CircleCI
as a supported provider in the `ci-workflow` generators
(`@nx/workspace`, `@nx/gradle`, `@nx/maven`, `@nx/dotnet`) and the Nx
Cloud setup docs are product features and remain untouched.
## Related Issue(s)
N/A
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/remove-circle-config-a3a01061)
<!-- polygraph-session-end -->
## Current Behavior
The remaining 14 publishable packages still build to the shared
workspace output `dist/packages/<name>` and use `commonjs`/`node` module
resolution. This is the last set of packages on the old layout — `nx`,
`devkit`, `js`, `web`, `node`, `webpack`, etc. were already migrated.
## Expected Behavior
Each of the remaining packages now builds to its own local
`packages/<name>/dist` directory with `nodenext` module resolution and
an `exports` map using the custom `@nx/nx-source` condition (so
in-workspace consumers resolve to source, while published/runtime
consumers resolve to built `./dist`). This matches the already-migrated
packages.
Packages migrated (one commit each):
- **Tier 0:** `@nx/react`, `@nx/esbuild`, `@nx/express`, `@nx/vue`,
`@nx/plugin`, `create-nx-workspace`, `@nx/angular`
- **Tier 1:** `@nx/react-native`, `@nx/next`, `@nx/remix`, `@nx/detox`,
`@nx/expo`, `@nx/nuxt`, `create-nx-plugin`
Notes:
- Kept the `./src/*` wildcard exports (the `@nx/nest` pattern) so no
consumer imports needed editing; the optional `./internal` lockdown is
deferred to a follow-up.
- Converted `ensurePackage` + `await import('@nx/...')` pairs to typed
`require()` (ESM dynamic import ignores `Module._initPaths` under
`nodenext`) and replaced `require('../../package.json')` self-references
with the dynamic `require(join('@nx/<name>', 'package.json'))` form.
- `@nx/angular` keeps its dual build: `tsc` (`build-base`) + ng-packagr
(`build-ng`); `ng-package.json` `dest`, both tsconfig `outDir`s, and the
publish `packageRoot` were relocated to `packages/angular/dist`.
- `create-nx-workspace`/`create-nx-plugin` were missing from the
migration board; tracked as NXC-4515 / NXC-4516.
Validation: `nx run-many -t build,lint` is green across all 14 packages
(including angular's real ng-packagr build and
`@nx/nx-plugin-checks`/`@nx/dependency-checks`). **Still needs CI
validation:** full e2e and the `nx release`/publish path (notably
angular's ng-packagr `packageRoot`).
## Breaking Changes
This is a breaking change because only `/internal` can be imported on packages now rather than importing from `src`. There is a migration to take care of this when necessary though.
## Related Issue(s)
Linear: NXC-3580, NXC-3582, NXC-3583, NXC-3584, NXC-3585, NXC-3587,
NXC-3589, NXC-3590, NXC-3596, NXC-3597, NXC-3600, NXC-3601, NXC-4515,
NXC-4516
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
When resolving package versions, `nx migrate` does not account for the
workspace package manager's minimum release age (cooldown) configuration
- npm `min-release-age`, pnpm `minimumReleaseAge`, yarn
`npmMinimalAgeGate`, and bun `minimumReleaseAge`. It can resolve to
versions newer than the package manager would actually allow to be
installed.
## Expected Behavior
`nx migrate` now honors the active package manager's minimum release age
policy, resolving to the newest version that satisfies the configured
cooldown. Registry-based resolution can be disabled with
`migrate.useRegistryResolution: false` in `nx.json` or the
`NX_MIGRATE_USE_REGISTRY_RESOLUTION` environment variable (the legacy
`NX_MIGRATE_SKIP_REGISTRY_FETCH` remains supported), in which case
versions are resolved through a package-manager install.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nxc-4499-7e1e10b2)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The Nx Cloud [Launch
Templates](https://nx.dev/docs/reference/nx-cloud/launch-templates#launch-templatestemplate-nameresource-class)
reference page lists `+` variant resource classes
(`docker_linux_amd64/medium+`, `docker_linux_amd64/large+`,
`docker_linux_amd64/extra_large+`) in the
`launch-templates.<template-name>.resource-class` section.
## Expected Behavior
The `+` variant resource classes are removed from the list. Only the
base resource classes remain.
## Related Issue(s)
Fixes DOC-515
## Current Behavior
The `createNodesV2` → `createNodes` rename migrations added in #35893
registered the `update-23-0-0-migrate-create-nodes-v2-import` migration
for **@nx/remix** and **@nx/nuxt** with a `./dist/src/migrations/...`
implementation path.
Both are flat-build packages (`main: ./index.js`) whose files are
published at the package root, not under a `dist/` directory. As a
result, `nx migrate` fails when it reaches these migrations:
```
NX Could not resolve implementation for migration
"update-23-0-0-migrate-create-nodes-v2-import" from .../node_modules/@nx/remix/migrations.json
```
## Expected Behavior
The migration resolves and runs. The implementation/documentation paths
for the flat-build packages point to `./src/migrations/...`, matching
the published package layout — consistent with the other flat-build
plugins (angular, next, expo, react-native, react), which already use
the `./src/...` prefix.
Audited all 23 plugins touched by #35893: only `@nx/remix` and
`@nx/nuxt` were mismatched. Dist-build packages (webpack, vite, jest,
etc.) correctly keep the `./dist/src/...` prefix.
## Related Issue(s)
Follow-up fix to #35893.
…ssets plugin
CreateNodesV2 and TargetConfiguration are types but were imported as
values. The @nx/workspace-plugin package is type: module, so under
Node's native TypeScript stripping (Node >= 22.18) those names are left
as runtime imports. @nx/devkit is CommonJS with no such runtime exports,
so the plugin fails to load -- surfacing on the FreeBSD publish job as
the misleading "imported again after being required. Status = 0" Node
error. Marking them import type lets native strip erase them so the
plugin loads on any Node.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
- The `@nx/react-native` library build (`tsc --build tsconfig.lib.json`)
fails with **TS2742**. The exported `addJest` helper has no explicit
return type, so TypeScript infers `GeneratorCallback` and can only name
it through a non-portable, pnpm-hashed `@nx/devkit` path when emitting
the declaration file.
- The `rewrite-rollup-internal-subpath-imports` and
`rewrite-webpack-internal-subpath-imports` migrations are scheduled to
run at `23.0.0-beta.25`.
## Expected Behavior
- `addJest` is explicitly annotated as `Promise<GeneratorCallback>`
(imported from `@nx/devkit`), giving `tsc` a portable name to emit. The
library now builds cleanly.
- Both internal-subpath-import migrations are retargeted to
`23.0.0-beta.24` to align with the rest of the `23.0.0` migration batch.
## Related Issue(s)
N/A
## Current Behavior
<!-- This is the behavior we have today -->
- The `@nx/dotnet` plugin is labeled **experimental** in two user-facing
places: the docs site introduction page (a `caution` aside) and the
published npm README (generated from `readme-template.md`).
- The package exposes the plugin via **two** specifiers: the bare
`@nx/dotnet` and the `@nx/dotnet/plugin` subpath. Both resolve to the
same plugin implementation, which is redundant and confusing.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
- The experimental banner/warning is removed from both the docs page and
the npm README. (Factual asides — .NET SDK compatibility, minimum Nx
version — are kept.)
- The `@nx/dotnet/plugin` subpath export is removed. The plugin is
registered solely via the bare `@nx/dotnet` specifier (already what `nx
add @nx/dotnet` / the init generator writes).
- A migration rewrites any existing `nx.json` plugin entries from
`@nx/dotnet/plugin` to `@nx/dotnet`, handling both the string and object
(`{ plugin, options }`) registration forms and de-duplicating if both
paths are present.
### Implementation notes
- Removed the `./plugin` entry from the package `exports` map.
- Inlined the plugin re-export into `src/index.ts`, converted the
internal plugin module to named exports, and deleted the now-redundant
`src/plugin.ts` shim. The bare `@nx/dotnet` public surface is unchanged.
- Added the `update-23-0-0-migrate-dotnet-plugin-path` migration with
unit tests (6 cases, all passing).
**BREAKING CHANGE:** the `@nx/dotnet/plugin` entry point has been
removed; use `@nx/dotnet`. The included migration updates `nx.json`
automatically.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
N/A
---------
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
## Current Behavior
When the Node.js runtime supports native TypeScript type stripping (Node
22.18+ LTS, 23.6+, or 22.6–22.17 with `--experimental-strip-types`), Nx
defers to native stripping and skips registering a transpiler entirely.
The `preferNodeStripTypes` gate is evaluated at module load and
short-circuits *before* the swc-vs-ts-node decision, where
`NX_PREFER_TS_NODE` is consulted. As a result, setting
`NX_PREFER_TS_NODE=true` has **no effect** on modern Node — native
stripping wins, even though the user explicitly asked for ts-node. This
is a problem for workspaces using constructs native stripping can't
handle the way ts-node would (e.g. relying on full type-aware
transpilation rather than stripping).
## Expected Behavior
Setting `NX_PREFER_TS_NODE=true` now opts out of native type stripping,
so the existing ts-node code path is used as intended. The opt-out is
added to the authoritative `preferNodeStripTypes` gate, keeping
`isNativeStripPreferred()` and `loadTsFile` aligned. Behavior is
unchanged when the flag is unset.
Added `isNativeStripPreferred` test coverage across the runtime-support
/ env-flag combinations.
## Related Issue(s)
N/A
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
#35386 renamed the canonical plugin API to drop the `V2` suffix — both
the exported `createNodesV2` **value** in first-party plugins (now
`createNodes`) and the `*V2` **types** in `@nx/devkit` (`CreateNodesV2`,
`CreateNodesContextV2`, `CreateNodesResultV2`, `CreateNodesFunctionV2`,
`NxPluginV2`). The old names are kept as deprecated aliases, so existing
code keeps compiling — but there is no migration to move workspaces onto
the canonical names, and `@nx/angular/plugin` / `@nx/vitest` only
re-exported `createNodesV2` from their public entry points (the rename
was incomplete there).
## Expected Behavior
- **Per-plugin value migration**
(`migrate-create-nodes-v2-to-create-nodes`): every first-party plugin
that publicly exposes `createNodesV2` ships a migration that rewrites
named imports/re-exports of `createNodesV2` from that plugin's public
specifier(s) to `createNodes` (22 plugins, incl. `@nx/gradle` +
`@nx/gradle/plugin-v1`, `@nx/js/typescript`, `@nx/react/router-plugin`).
- **devkit type migration** (`rename-create-nodes-v2-types`): rewrites
imports/re-exports of the deprecated `*V2` types from `@nx/devkit` to
their canonical names (`CreateNodesResultV2` → `CreateNodesResultArray`;
the unrelated `CreateNodesResult` is left alone).
- `@nx/angular/plugin` and `@nx/vitest` now also re-export
`createNodes`, completing the rename so the migration targets resolve.
All migrations are AST-based, modeled on devkit's `update-deep-imports`:
they handle `as` aliases, dedupe when both names are already imported,
preserve `type` modifiers and default imports, and leave
strings/comments, dynamic `import`/`require`, and unrelated specifiers
untouched. Each has unit + tree-runner specs and a `.md` doc, registered
at `23.0.0-beta.5`.
## Related Issue(s)
Follow-up to #35386. No issue to close.
## Current Behavior
The publish workflow's FreeBSD build fails computing the project graph:
`Failed to load 1 Nx plugin(s) … Cannot find module
@nx/js/src/utils/assets/copy-assets-handler`.
`tools/workspace-plugin` pins `@nx/js`/`@nx/devkit` at `23.0.0-beta.4`
(pre-dist-build
layout: `src` sources, no exports map) while the repo is on `beta.22`.
The plugin is
`type: module`, so under Node ≥ 22.18 (native TS strip) its `.ts` loads
as ESM, which
can't resolve the extensionless `@nx/js/src/...` import against an
exports-less package.
FreeBSD CI was the first env with a ≥ 22.18 Node (`pkg install node` →
node24).
The alpine/musl Node download URL also hardcoded the version, drifting
from `NODE_VERSION`.
## Expected Behavior
`workspace-plugin` resolves `@nx/js` through the beta.22 exports map (to
`dist`), so the
plugin loads under native-strip ESM on any Node — FreeBSD included. CI
Node is bumped to
26.3.0 and the musl download is derived from `${NODE_VERSION}` so it
can't drift again.
## Related Issue(s)
N/A — CI fix.
## Current Behavior
When `create-nx-workspace` is invoked with a third-party preset (e.g.
`--preset=@aws/nx-plugin`), a warning and interactive confirmation
prompt are always shown — even when the caller is a wrapper CLI that has
already established trust on behalf of the user. The only way to bypass
this today is `--no-interactive`, which suppresses *all* prompts and
still emits the warning to stdout.
## Expected Behavior
Callers that wrap `create-nx-workspace` (such as `pnpm create
@aws/nx-workspace`) can pass `--trustThirdPartyPreset` to skip the
third-party preset warning and confirmation entirely, without affecting
any other interactive prompts.
```bash
npx create-nx-workspace my-project \
--preset=@aws/nx-plugin \
--trustThirdPartyPreset
```
## Related Issue(s)
Fixes#35826
## Current Behavior
The remaining 11 first-party Nx plugins (`@nx/webpack`, `@nx/rollup`,
`@nx/docker`, `@nx/gradle`, `@nx/rsbuild`, `@nx/web`, `@nx/node`,
`@nx/nest`, `@nx/module-federation`, `@nx/rspack`, `@nx/storybook`)
build into `../../dist/packages/<name>/` with `module: commonjs` and no
`exports` map. Releases publish from a separate dist directory.
That layout has the same drawbacks the prior migrations (devkit,
workspace, nx, js, jest, eslint, eslint-plugin, vitest, cypress,
playwright, vite) already addressed:
- workspace consumers reach into `@nx/<name>/src/*` against the old
layout, blocking nodenext / ESM-friendlier resolution
- a single PR cannot publish a coordinated set because each package's
dist must round-trip through `nx release`
- `release.preserveLocalDependencyProtocols` cannot be turned on
workspace-wide
This PR is the final batch in the "Nx Local Dist Migration" project — it
unblocks every still-unmigrated workspace package and finishes the
rollout that started with `@nx/devkit` in #34946.
## Expected Behavior
All 11 packages build to `packages/<name>/dist/` with:
- `tsconfig.lib.json` set to `module: nodenext`, `moduleResolution:
nodenext`, `composite: true`, `outDir: dist`, `declarationDir: dist`,
`tsBuildInfoFile: dist/tsconfig.tsbuildinfo`
- `package.json` `main`/`types` pointing into `dist/`, an `exports` map
with `@nx/nx-source`/`types`/`default` conditions, `typesVersions` for
legacy `moduleResolution: node` consumers, and a `files` allowlist
- `project.json` `release.version.preserveLocalDependencyProtocols:
true`, `manifestRootsToUpdate: ["packages/{projectName}"]`, and
`nx-release-publish.packageRoot: packages/{projectName}`
- `assets.json` `outDir` and eslint `dist` ignore updated
- `src/utils/versions.ts` switched to `require(join('@nx/<name>',
'package.json')).version` so the self-reference survives the new layout
- `README.md` renamed to `readme-template.md` with the build's
`copy-readme.js` invocation passing explicit src/dest paths, and root
`.gitignore` updated for the generated `README.md`
- `scripts/nx-release.ts` `packagesToReset` extended so `nx release`
properly snapshots/restores these source `package.json`s
The 11 packages keep the `./src/*` wildcard in their exports map for now
— the `@nx/devkit/internal`-style lockdown (Step 14b of the
`dist-build-migration` skill) is intentionally deferred to per-package
follow-up PRs to keep this one focused on the layout move. Two runtime
fixes that mirror earlier work in this project:
- `@nx/node` now declares `@nx/webpack` as a `workspace:*` devDep so its
TS compile resolves `@nx/webpack/src/utils/ensure-dependencies` via the
new exports map; the dynamic `import()` of that subpath was swapped to
`require()` after `ensurePackage` so the temp install is visible (same
pattern as the vite/vitest fix in #35743 — under `module: nodenext` a
dynamic `import()` is preserved as a true ESM import and bypasses
`Module._initPaths`).
- `@nx/web` had two dynamic `await import('@nx/eslint/internal')` /
`await import('@nx/vitest/generators')` call sites; both swapped to
`require()` with `typeof import(...)` type annotations for the same
reason.
`e2e/nx-build/src/nx-build.test.ts` was extended to verify the new
output paths for all 11 packages.
## Related Issue(s)
Fixes NXC-3576
Fixes NXC-3577
Fixes NXC-3579
Fixes NXC-3586
Fixes NXC-3588
Fixes NXC-3591
Fixes NXC-3595
Fixes NXC-3598
Fixes NXC-3599
Fixes NXC-3602
Fixes NXC-4474
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
A watcher-driven project-graph recompute that fails while loading
plugins
takes the **whole Nx daemon down**.
`kickOffRecompute` (`project-graph-incremental-recomputation.ts`) builds
`cachedSerializedProjectGraphPromise` from an async IIFE.
`processFilesAndCreateAndSerializeProjectGraph` is wrapped in
`try/catch`
and always resolves to an `errorResult` — but the prologue hoisted in
front of it for the freshness gate (`readNxJson`, `getPluginsSeparated`,
`isStale`) is not. `getPluginsSeparated` **rejects** when a plugin fails
to load.
`scheduleProjectGraphRecomputation` calls `kickOffRecompute()`
fire-and-forget, so a rejected `myPromise` has no awaiter — Node reports
an unhandled promise rejection and the daemon process exits. (The
request
path, `getCachedSerializedProjectGraphPromise`, `try/catch`es its
`await`,
so only watcher-driven recomputes hit this.)
Observed downstream as a flaky `e2e/nx/src/spread.test.ts`: a test
mutates
`nx.json` / `tools/*` / `project.json`, the watcher-driven recompute
hits
a transient plugin-load failure, the daemon crashes mid-test, and the
following `show project` races a restarting daemon — surfacing as
`project.targets.build` being `undefined` (a graph whose plugin set
never
ran). The captured `daemon.log` shows the `AggregateError` from
`getPluginsSeparated` followed by a fresh daemon process starting.
## Expected Behavior
A plugin-load failure during a recompute resolves to a graph **error**
that the next requester surfaces — the daemon stays up. The IIFE body is
wrapped so it always resolves (never rejects), turning a prologue
failure
into an `errorResult`, the same contract
`processFilesAndCreateAndSerializeProjectGraph` already honors. The next
`getCachedSerializedProjectGraphPromise` reads `result.error`, returns
it
to the client, and clears the cached promise so the recompute retries.
Also in this PR (diagnostics / regression coverage for the flake):
- Restored the `afterEach` daemon-log dump in `spread.test.ts` (removed
in
`2f261d6903`) so a failing run prints `.nx/workspace-data/d/daemon.log`
next to the assertion in CI output.
- Added a `describe('rapid reconfiguration (race-condition stress)')`
block that mutates `nx.json` / `project.json` / `tools/*` in tight
loops with no settle time and no `reset` between iterations, so the
long-lived daemon is exercised hard — regression coverage for the crash.
## Related Issue(s)
Follow-up to PR #35650 (https://github.com/nrwl/nx/pull/35650), which
hoisted `getPluginsSeparated` out of the `try/catch`ed compute and into
the bare IIFE prologue for the freshness gate.
No separate issue number.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The e2e suite is slow and intermittently flaky, driven by a few
infrastructure issues:
- **Lazy dependency installs.** Generator dependencies (test runners,
bundlers, framework plugins) are
installed on demand during a test via `ensurePackage()`. This pushes
install cost into the middle of
the run and makes timing vary run-to-run.
- **Port collisions.** Tests pick ports semi-randomly
(`getRandomPort()`) from overlapping ranges and
don't coordinate across parallel e2e processes, so concurrent tests can
grab the same port and fail
with `EADDRINUSE`. Module federation tests also need a contiguous block
of ports (host + remotes) that
the old scheme couldn't guarantee.
- **Fixed-sleep races.** Some tests start a built app, `sleep 1`, then
assert on its output — which
loses the race on a loaded CI machine where boot + module evaluation
takes several seconds.
- **CI agent contention.** Module federation e2e tasks each build and
serve a host plus its remotes
(multiple webpack/rspack builds at once), saturating an agent; running
them alongside other e2e at
high parallelism caused timeouts.
## Expected Behavior
Faster and more deterministic e2e runs:
- **Pre-install generator deps up front.** Each `newProject({ packages:
[...] })` now declares every
plugin the test will use, so installs happen once during setup instead
of lazily mid-test.
- **Robust port reservation** (`e2e/utils/port-utils.ts`): lock files
are stamped with the owning PID,
abandoned locks are reclaimed (developer machines only — CI containers
are ephemeral), the scan
origin is randomized so parallel processes scatter instead of converging
on low ports, and
`reservePorts(count)` returns a contiguous run for module federation.
`getRandomPort()` is deprecated
in favor of `reservePort()`.
- **Poll-until-ready** instead of fixed sleeps — a built app is polled
for its "server ready" line (up
to 30s) before assertions.
- **CI tuning** (`.nx/workflows/dynamic-changesets.yaml`): module
federation e2e is pinned to
`linux-extra-large` at parallelism 1; remaining e2e runs at 3 (large) /
6 (extra-large).
- **Install visibility**: `installPackagesTask` now logs the install
command and how long it took.
## Related Issue(s)
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
There is no JSON schema for plugin `migrations.json` files, so editors
provide no validation or autocompletion when authoring migrations.
## Expected Behavior
The `nx` package ships a `schemas/migrations-schema.json` file
describing the `migrations.json` format. The `@nx/plugin:migration`
generator adds a `$schema` reference when creating a new
`migrations.json` file.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nxc-4455-96de17ed)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
The Automate Updating Dependencies feature page was orphaned during the
Astro docs migration - it existed as content but was not referenced in
sidebar.mts. Add it to the Maintenance section alongside the related
update/migration guides.
## Current Behavior
`nx migrate` shows interactive prompts even when `--no-interactive` is
passed: the mode selection prompt ("Which packages would you like to
migrate?"), the multi-major migration prompt, and the agentic flow
prompts in `--run-migrations` only check for a TTY and CI.
## Expected Behavior
`--no-interactive` suppresses all prompting: the mode prompt silently
defaults to `all`, the multi-major check falls back to the warn-only
path, and the agentic flow is skipped (with a warning when it was
explicitly requested).
## Related Issue(s)
Fixes NXC-4513
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/fix-nx-migrate-no-interactive-23fbc4b0)
<!-- polygraph-session-end -->
## Current Behavior
Each agentic step of `nx migrate` ends with a permission prompt when the
agent writes its handoff file with Claude Code. The agent also writes a
`status: "failed"` handoff on its own when it hits a problem, ending the
session without giving the user a chance to weigh in.
## Expected Behavior
The handoff write is pre-authorized for Claude Code via
`--allowedTools`, scoped to `.nx/migrate-runs/**`, so steps complete
without an approval prompt. The handoff contract now writes the success
handoff immediately, asks the user when the agent needs direction, and
only writes a failed handoff when the user says to give up.
## Implementation Notes
- Codex and opencode invocations are unchanged: codex's default sandbox
(workspace-write + on-request approvals) and opencode's default `edit:
allow` permission already allow the write without prompting. Overriding
a user-hardened config (codex read-only sandbox, opencode `edit: ask`)
would discard a deliberate choice, and for opencode an injected
permission object replaces, not merges with, the user's own patterns.
- The allow rule uses the constant `.nx/migrate-runs/**` glob
(cwd-relative; the runner pins cwd to the workspace root) instead of the
exact handoff path: absolute-path rules have unverified `//` prefix
semantics on Windows, and package/migration names can contain
glob-special characters that would silently break matching.
- Verified empirically with claude 2.1.165: the rule allows the handoff
write without prompting, and writes outside the glob (or outside the
cwd) are still denied. Also confirmed `--allowedTools` is variadic: a
positional argument placed right after its value is swallowed, so the
arg order keeps `--system-prompt` between the rules and the user prompt
(guarded by a comment and spec).
- The agent is steered to use its file-write tool for the handoff:
shell-based writes are not covered by the allow rule and would still
prompt.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nxc-4514-89cfaf38)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The `@nx/angular` SCAM generators (`scam`, `scam-directive`,
`scam-pipe`, `scam-to-standalone`) are not marked as deprecated, even
though SCAMs are superseded by Angular standalone components.
## Expected Behavior
The four SCAM generators are marked as deprecated via `x-deprecated` in
`generators.json` (reported by `nx g` and shown in `--help`) and
`@deprecated` JSDoc on their programmatic entry points. The messages
point to the `component`, `directive`, and `pipe` generators as
replacements, and `scam-to-standalone` users are told to convert any
remaining SCAMs before upgrading. They will be removed in Nx v24.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nxc-4512-9a06eb63)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Nx Console's migrate UI breaks on the two migration shapes introduced in
#35718: prompt-only migrations crash with
`MigrationImplementationMissingError` when run, and hybrid migrations
give no signal that the AI prompt phase is still pending nor a way to
record that it was run.
## Expected Behavior
Both shapes render and complete correctly in the migrate UI without
turning Nx Console into an agentic runner. Prompt-bearing cards show an
AI badge, the prompt status, and a clickable prompt path that opens the
prompt file in the editor; the user runs the prompt themselves and marks
it done (`Mark as Run` for prompt-only, `Approve Changes` for hybrid),
which persists the completion.
> [!IMPORTANT]
> Depends on the matching change in nrwl/nx-console#3153 to handle the
new `acknowledge-prompt` and `view-prompt` events. Without it,
completion never records from Nx Console and clicking the prompt path
does nothing until the extension is updated. This is accepted
version-skew degradation.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nxc-4477-03fa3f4b)
<!-- polygraph-session-end -->
---------
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
## Current Behavior
`@nx/next` has no below-floor guard; generators silently fall back to
latest install constants on unsupported Next versions. The base
`20.7.1-beta.0` migration bumps `eslint-config-next` ungated, hitting
v14 users.
## Expected Behavior
`assertSupportedNextVersion` (floor Next 14) throws on sub-floor and is
the first statement in every generator. The base `20.7.1-beta.0`
migration is gated to `>=15` so v14 users keep the v14 eslint-config
lane. Support window kept at v14+v15+v16.
## Related Issue(s)
Fixes NXC-4395
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/multi-4395-ae050ce9)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`@nx/react` has no `peerDependencies` for `react`/`react-dom`, installs
packages unconditionally without preserving user-pinned versions, and
`migrations.json` cross-major bumps lack `requires` gates. The `22.3.4`
entry mixes a `react-router-dom` v6 patch with a `react-router` v7
cross-major under AND-semantics, which cannot express two
mutually-exclusive user states.
## Expected Behavior
- `peerDependencies` `react`/`react-dom` `>=18.0.0 <20.0.0` declared
- `minSupportedReactVersion = '18.0.0'` floor constant +
`assertSupportedReactVersion` wrapper
- Floor assert fires as first statement in all 17 generator entry
functions
- `addDependenciesToPackageJson` preserves user-pinned versions
(`keepExistingVersions: true`) across all generators
- `react-router`/`react-router-dom` resolved per React major from the
version map
- `migrations.json` cross-major `packageJsonUpdates` entries gated with
bilateral `requires` ranges
- `22.3.4` split into dual lanes (v6 `react-router-dom` / v7
`react-router` + `@react-router/*`)
- `22.7.0` gated to v7 lane
- `all-generators-enforce-floor.spec.ts` parameterized spec (24/24 pass)
## Related Issue(s)
NXC-4399
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/multi-version-jack-398d33f1)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`@nx/rollup`, `@nx/webpack`, and `@nx/module-federation` pin their
primary third-party packages as **bundled `dependencies`**:
- `@nx/rollup` → `rollup` (`^4.14.0`)
- `@nx/webpack` → `webpack`, `webpack-dev-server` (v5)
- `@nx/module-federation` → `@module-federation/enhanced`,
`@module-federation/node`, `@module-federation/sdk` (v2)
This causes version conflicts with the version a workspace actually has
installed, prevents `nx migrate --first-party-only` from upgrading Nx
without dragging these along, and lets generators silently overwrite a
user's pinned version. Several cross-version `packageJsonUpdates`
migrations also bump packages **without `requires` gates**, so they fire
on workspaces outside the intended source range (e.g. the `@nx/webpack`
`sass-loader` v12 → v16 bump, and all of `@nx/module-federation`'s
`@module-federation/*` bumps).
## Expected Behavior
Each plugin declares the packages it invokes at runtime as **optional
peer dependencies** matching its real support window, so the workspace
controls the version:
- `@nx/rollup` → `rollup` `^3.0.0 || ^4.0.0`
- `@nx/webpack` → `webpack` / `webpack-dev-server` / `webpack-cli`
`^5.0.0` (the effective floor — the plugin already calls webpack-5-only
APIs such as `ids.HashedModuleIdsPlugin`, `webpack.sources`, and
`experiments.cacheUnaffected`)
- `@nx/module-federation` → `@module-federation/enhanced` /
`@module-federation/node` `^2.0.0` (`@module-federation/sdk` stays a
dependency — it is a type-only import surfaced in the public `.d.ts`)
Generators assert the supported floor (with a parameterized
`all-generators-enforce-floor` spec), preserve user-pinned versions
(`keepExistingVersions` now defaults to `true`), and install the
now-peer build tool for new workspaces. Because every workspace that
uses these tools already has the corresponding `@nx/*` plugin as a
direct dependency, a `packageJsonUpdates` bridge in each plugin installs
the peer into existing workspaces on `nx migrate` (and correctly skips
workspaces that only carry the plugin transitively). Cross-version
migrations are gated on their source range.
### Changes per plugin
- **`@nx/rollup` (NXC-4403)** — `rollup` → optional peer; floor assert
(v3) in all generators; `keepExistingVersions` default `true`; install
`rollup` on both the inferred-plugin and executor paths; bridge
migration for existing workspaces. No runtime branching/version map is
needed — the plugin's Rollup usage is already v2/v3/v4-agnostic.
- **`@nx/webpack` (NXC-4410)** —
`webpack`/`webpack-dev-server`/`webpack-cli` → optional peers (`^5`);
floor assert (v5) in all generators; `keepExistingVersions` default
`true`; gate the `sass-loader` v12 → v16 migration on `^12`; install
`webpack`/`webpack-dev-server` for new workspaces; bridge migration for
existing ones.
- **`@nx/module-federation` (NXC-4393)** —
`@module-federation/enhanced`/`node` → optional peers (`^2`); add
`requires` gates (on the `@module-federation/enhanced` source range,
mirroring the existing `@nx/angular` MF migrations) to every
`packageJsonUpdates` entry, and split the independent
`http-proxy-middleware` v2 → v3 bump into its own gated entry. This is a
runtime-only library (no generators/executors), so there is no floor
assert/spec.
## Related Issue(s)
Tracked in Linear under the "Multi-version supported across plugins"
milestone:
- NXC-4403 — `@nx/rollup`
- NXC-4410 — `@nx/webpack`
- NXC-4393 — `@nx/module-federation`
No GitHub issue to auto-close.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
First-party plugins can attach an AI `prompt` markdown file to a
migration in `migrations.json`. `@nx/next` and `@nx/nuxt` reference
prompt files under `src/migrations/**/*.md`, but their build
`assets.json` only copied `migrations.json` — not the `.md` files it
points at. Those prompt files were therefore **missing from the
published packages**.
As a result, `nx migrate` to a version that contains such a migration
fails while fetching migrations:
```
NX Failed to fetch migrations for @nx/next@23.0.0-beta.23
Command failed: pnpm add -w @nx/next@23.0.0-beta.23
Could not find prompt file "./src/migrations/update-22-2-0/ai-instructions-for-next-16.md"
for migration "update-22-2-0-create-ai-instructions-for-next-16" in package "@nx/next@23.0.0-beta.23".
```
The message is also misleading: the install (`pnpm add -w`) actually
succeeded — the missing prompt file is the real cause. The
fetch-via-install helper wrapped **every** failure (the install,
reading/validating migrations, and resolving prompt files) as a `Command
failed: <pm> add <pkg>` error, so non-install failures were attributed
to the install command.
## Expected Behavior
- Migration prompt markdown files are now included in the published
packages. Added `{ "glob": "src/migrations/**/*.md" }` to `@nx/next` and
`@nx/nuxt` (the affected packages), and to `@nx/storybook` for
consistency (its prompt currently ships via the existing `**/files/**`
glob). `@nx/vite`, `@nx/vitest`, and `@nx/expo` already had the glob.
- `nx migrate` no longer fails to fetch migrations for these packages.
- When fetching migrations via install fails, the error reports the
**actual** cause. Only genuine install failures are labeled `Command
failed: <pm> add <pkg>`; errors from reading/validating migrations or
resolving prompt files are surfaced as-is under `Failed to fetch
migrations for <pkg>`.
### Verification
Built `@nx/next` and `@nx/nuxt` and confirmed the prompt files now land
in the package output
(`dist/packages/<pkg>/src/migrations/update-22-2-0/ai-instructions-*.md`).
`@nx/storybook` still builds and continues to ship its prompt via
`**/files/**`. Existing `formatCommandFailure` unit tests pass.
## Related Issue(s)
Found while dogfooding `nx migrate` to `23.0.0-beta.23`; no existing
issue.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
tsconfig `include` entries like `"."` produce a bare `{projectRoot}`
task input that matches no files, so source changes don't invalidate the
cache for the inferred `typecheck`/`build` tasks. Directory `exclude`
entries (e.g. `"dist"`) are emitted as literal negations that match
nothing, and excludes from sibling tsconfigs can suppress files covered
by another tsconfig's include.
## Expected Behavior
Include and exclude entries are interpreted the way TypeScript
interprets them: `include: ["."]` expands to the project's source files,
directory excludes cover their whole subtree, and excludes covered by
another tsconfig's non-glob include are not emitted as negations.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/ts-plugin-include-dot-issue-7fb1ea4f)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`addProjectConfiguration` exposes a 4th `standalone` parameter
(defaulting to `true`). Nx only supports standalone projects, so any
value other than `true` is ignored — passing `standalone: false` simply
prints a warning and otherwise has no effect. Despite being a no-op, the
parameter is still part of the public signature, and a number of
first-party generators expose a matching `standaloneConfig` schema
option whose only job is to feed this dead parameter.
## Expected Behavior
`addProjectConfiguration` now presents two TypeScript overloads:
- a clean 3-argument signature (`tree`, `projectName`,
`projectConfiguration`), and
- a `@deprecated` 4-argument signature that still accepts `standalone`
for backwards compatibility (the runtime behavior is unchanged — it
continues to warn when set to `false`).
Existing callers keep compiling, but new ones are steered onto the
3-argument form and editors surface a deprecation strikethrough on the
old one.
The vestigial `standaloneConfig` option has been removed from the
affected generator schemas (`schema.json` + `schema.d.ts`) across
`expo`, `node` (application + library), `nest`, `storybook`, `workspace`
preset, `plugin`, and `express`, since it only ever populated the
ignored parameter. The remaining internal 4-argument call sites (`expo`,
`node`, and the `angular` ng-add e2e migrator) were reduced to the
3-argument overload, the `nest`/`workspace` passthroughs were cleaned
up, and the storybook generator specs no longer pass `standaloneConfig`.
Verified locally: `lint` passes for the 9 touched projects, `build`
(typecheck) passes for `nx`, `node`, `expo`, `nest`, and `workspace`,
and the storybook `configuration` spec passes.
## Related Issue(s)
N/A
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The `composePlugins`, `withNx`, `withWeb` (`@nx/webpack`, `@nx/rspack`)
and `withReact` (`@nx/rspack`, `@nx/react/webpack`) config helpers emit
an Nx-specific config function that only runs under the
`@nx/webpack:webpack` / `@nx/rspack:rspack` executors. There is no
deprecation signal steering users toward the inferred plugins.
## Expected Behavior
The helpers are deprecated for removal in Nx v24. They keep working in
v23 but log a warn-once-per-package message pointing at the real plugin
classes
(`NxAppWebpackPlugin`/`NxAppRspackPlugin`/`NxReactWebpackPlugin`/`NxReactRspackPlugin`)
and `nx g @nx/<bundler>:convert-to-inferred`. `@deprecated` JSDoc is
added to each helper, plus caution asides on the webpack config/plugins
and react asset docs. The warning fires only for user-authored configs:
the rspack executor, storybook preset, and next.js component-testing
preset compose these helpers internally and are wrapped in a suppression
scope. Warn-only, no codemod, no generator changes.
## Related Issue(s)
NXC-4324
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nxc-4324-2bacd010)
<!-- polygraph-session-end -->
## Current Behavior
`nx migrate --interactive` enables the per-package `x-prompt`
confirmations regardless of the target version. This conflicts with the
new `--mode` functionality: `--mode=third-party --interactive` still
fires the prompts, and the legacy opt-out flow runs for v23+ migrations
where `--mode` supersedes it.
## Expected Behavior
`--interactive` is gated behind the same v23+ availability gate as the
first-party/third-party modes: passing it for a v23+ Nx-equivalent
target throws with a pointer to `--mode`, and combining it with
`--mode=third-party` is rejected. Pre-v23 and non-Nx targets keep the
legacy interactive behavior.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nxc-4412-766d87a4)
<!-- polygraph-session-end -->
## Current Behavior
Running any `nx` command in this repo prints a warning on Node versions
with native TypeScript stripping:
```
Warning: Failed to load the ES module:
.../tools/workspace-plugin/src/plugins/copy-assets-plugin.ts.
Make sure to set "type": "module" in the nearest package.json file or use the .mjs extension.
```
The `copy-assets` plugin is registered in `nx.json` as a source `.ts`
file and is authored in ESM (`import`/`export`). The nearest
`package.json` (`tools/workspace-plugin/package.json`) declares `"type":
"commonjs"`, so Node classifies the file as CommonJS, hits the ESM
syntax, emits the warning, and fails the native load. Nx then recovers
via its swc fallback — so the plugin still works, but the warning is
printed as noise on every command.
## Expected Behavior
No warning. `tools/workspace-plugin` is `private` (never published) and
its sources are authored in ESM, so declaring `"type": "module"` is
accurate and lets Node load the plugin without the spurious
CommonJS-parse warning.
Verified with `"type": "module"`:
- `nx show projects --affected` — exit 0, **0 warnings**, project graph
builds
- `nx build workspace-plugin` — passes
- `nx test workspace-plugin` — 38/38 pass
- `nx lint workspace-plugin` — passes
- `nx conformance` — all 7 rules pass, no violations
## Related Issue(s)
N/A — internal developer-experience fix (no linked issue).
The Nx, Nx Cloud, Nx Console, and Lerna logo packs had no home in the
reposiotry, so there was no stable URL to download them from. Hosting
the zips on master makes them publicly accessible at predictable raw
URLs under `assets/brand-kits/`.
Add a dedicated docs page under Technologies > Module Federation covering a
Vite-based federation setup using @module-federation/vite, with Nx orchestrating
host/remote tasks. Links the example workspace maintained by Giorgio Boa.
Part of DOC-514.
## Current Behavior
Two Nx task-pipeline sandbox violations were reported by Nx Cloud:
1. **`nx publish MsbuildAnalyzer` — unexpected write** at
`packages/dotnet/analyzer/obj/Release/PublishOutputs.<hash>.txt`.
`dotnet publish` writes its incremental-publish state file into the
intermediate (`obj`) directory, but the `@nx/dotnet`-inferred `publish`
target only declared the publish directory (`bin/Release/publish`) as an
output — `obj` was undeclared. The `pack` target has the same latent gap
(it declares only `*.nupkg`).
2. **`nx test angular-rspack` — unexpected read** at
`packages/angular-rspack-compiler/tsconfig.spec.json`. Vitest runs on
Vite, which transforms a dependency's sources and resolves their
TypeScript project references. When a dependency's root `tsconfig.json`
references its `tsconfig.spec.json`, that file is read during resolution
— but the `production` named input excludes `tsconfig.spec.json`
(`!{projectRoot}/tsconfig.spec.json`), so `^production` does not cover
it. The dependency edge itself is correctly declared; only this one file
was missing from the inputs.
## Expected Behavior
The files the tools genuinely touch are declared, so no sandbox
violations:
- **`@nx/dotnet`:** the inferred `publish` and `pack` targets now
include the intermediate (`obj`) directory in their outputs, mirroring
how the `build` target already declares it. The `MsbuildAnalyzer`
`project.json` publish-outputs override is updated too (a project-level
`outputs` override *replaces* the inferred value, so it needs `obj` as
well). New/updated C# output-path unit tests cover both publish and pack
(29/29 passing).
- **`@nx/vitest`:** the inferred `test` target now declares `{ fileset:
'{projectRoot}/tsconfig.spec.json', dependencies: true }`, so a
dependency's spec tsconfig is tracked as an input. This mirrors the
existing dependency-fileset precedent in the `@nx/js/typescript` plugin.
Plugin snapshot updated.
Both fixes are systemic (at the plugin layer) so every dotnet/vitest
project benefits, not just the two that surfaced the violations.
The two commits are independent and scoped separately (`fix(dotnet)` and
`fix(vitest)`) — happy to split into two PRs if preferred.
## Related Issue(s)
Surfaced by Nx Cloud sandbox-violation reports; no standalone GitHub
issue.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/two-sandbox-viols-aefb1b3a)
<!-- polygraph-session-end -->
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`resolvePathsBaseUrl` in `packages/js/src/utils/typescript/ts-config.ts`
walks the tsconfig `extends` chain using `JSON.parse(readFileSync(...))`
to find where `paths` is defined. `JSON.parse` cannot handle `//` or `/*
*/` comments (JSONC), which are extremely common in tsconfig files. When
parsing fails, the `catch` block silently skips the file, breaking the
extends chain walk. The function never reaches `tsconfig.base.json`
(where `paths` and `baseUrl` are defined), so it falls back to the
project's own directory as the resolution base, producing incorrect path
mappings like `{workspaceRoot}/libs/my-lib/dist/libs/data-layer` instead
of `{workspaceRoot}/dist/libs/data-layer`.
This is a regression introduced in v22.7.0 by PR #34965.
## Expected Behavior
Buildable library builds should resolve workspace dependencies
correctly, even when tsconfig files in the extends chain contain JSONC
comments (`//` or `/* */`). The fix uses TypeScript's `readConfigFile`
(which handles JSONC) — the same approach already used by `readTsConfig`
in the same file.
## Related Issue(s)
Fixes#35537Fixes#35824
## Current Behavior
The repo's `mise.toml` defaults the Node.js toolchain to `24.11.0`.
Contributors — and the mise-provisioned CI jobs (`ci.yml`, CodeQL) —
therefore run on Node 24, which ships npm 11.6.1. That npm is too old to
honor the `min-release-age` supply-chain cooldown (added in npm
11.10.0).
## Expected Behavior
`mise.toml` defaults the Node.js toolchain to `26.3.0` (latest Node,
ships npm 11.16.0). Local development and the mise-provisioned CI jobs
move to Node 26.
Scope / notes:
- The default remains overridable via the `NODE_VERSION` env var — the
template is otherwise unchanged.
- `publish.yml` (pinned `NODE_VERSION: 22.16.0` plus hardcoded musl
native builds) and `e2e-matrix.yml` (matrix `node_version`) set
`NODE_VERSION` explicitly, so they are **unaffected** and stay on Node
22.
- Utility workflows using `actions/setup-node` (`pr-title-validation`,
`generate-embeddings`, `banner-monitor`, `issue-notifier`) remain on
Node 24 and are out of scope for this change.
- CI on this PR validates that the core build/test/lint suite passes on
Node 26.
## Related Issue(s)
N/A — internal toolchain bump.
## Current Behavior
The canonical plugin API types are suffixed with `V2` (`CreateNodesV2`,
`CreateNodesContextV2`, `CreateNodesFunctionV2`, `CreateNodesResultV2`,
`NxPluginV2`), left over from when the V1 signatures existed. V1 was
removed in Nx 22 (#32951), freeing up the un-suffixed identifiers but
leaving the V2 names as the public API.
## Expected Behavior
The un-suffixed names (`CreateNodes`, `CreateNodesContext`,
`CreateNodesFunction`, `NxPlugin`, plus a new `CreateNodesResultArray`)
become the canonical public types. The `V2` identifiers remain as
`@deprecated` type aliases so plugin code authored against them keeps
compiling. The plugin loader still checks both `plugin.createNodes` and
`plugin.createNodesV2` property names at runtime, so third-party plugins
that export under either name continue to load.
### Rename map
| Old (now `@deprecated` alias) | New canonical |
|-------------------------------|-----------------------|
| `CreateNodesV2<T>` | `CreateNodes<T>` |
| `CreateNodesContextV2` | `CreateNodesContext` |
| `CreateNodesResultV2` | `CreateNodesResultArray` |
| `CreateNodesFunctionV2<T>` | `CreateNodesFunction<T>` |
| `NxPluginV2<T>` | `NxPlugin<T>` |
### Changes
- `packages/nx` internals (plugin loader, isolation worker, utils,
lock-file, package/project.json plugins, specs) switched to canonical
types.
- `packages/devkit` utilities (`addPlugin`, `findPluginForConfigFile`,
`targetDefaultsUtils`, `calculateHashForCreateNodes`,
`replaceProjectConfigurationsWithPlugin`, `getNamedInputs`,
`executor-to-plugin-migrator`, etc.) migrated to canonical types.
`addPlugin` now registers plugin objects with `createNodes:` as the
canonical key.
- 50 first-party plugin packages updated to canonical type names. Seven
packages whose only primary export was `createNodesV2` now export
`createNodes` as the primary value, with `createNodesV2 = createNodes`
preserved as a backward-compat alias for older Nx consumers.
## Related Issue(s)
N/A
## Current Behavior
When `nx migrate` can't read a package's migrations from the registry,
it falls back to installing the
package in a temporary directory to read them. That fallback detected
the package manager from the
temp directory itself — which has no lock file — so detection fell back
to npm even in yarn / pnpm /
bun workspaces. The fetch then ran `npm install`, ignoring the workspace
package manager's registry,
auth, and release-age configuration (for example, a private registry
configured in `.yarnrc.yml`, or
pnpm's `minimumReleaseAge`), which can break migrations that resolve
through a private registry.
## Expected Behavior
The package manager is already resolved once in
`generateMigrationsJsonAndUpdatePackageJson` (before
the fetcher is created). That value is now threaded through
`createFetcher` into the install fallback,
so the temporary install uses the workspace's package manager. The fetch
helper no longer detects the
package manager at all, so it can't resolve it against the empty temp
directory.
For pnpm workspaces the install runs `pnpm add -w`, which requires a
`pnpm-workspace.yaml` to be
present in the directory (otherwise it fails with `--workspace-root may
only be used inside a
workspace`). A **sanitized** copy is now placed in the temp dir:
`packages` (workspace member globs)
and `patchedDependencies` (relative patch-file paths) are dropped
because they only resolve in the
real workspace, while `registry`, auth, and `minimumReleaseAge` are
kept. This fixes the `-w` failure
and lets the install honor the workspace's registry/auth and release-age
settings.
The install still runs in the temp directory; only the configuration and
package-manager *choice* now
come from the workspace. `createTempNpmDirectory` already copied
`.npmrc` / `.yarnrc` / `.yarnrc.yml`
/ `bunfig.toml`; this adds the sanitized `pnpm-workspace.yaml` alongside
them.
## Related Issue(s)
Relates to NXC-4499.
## Current Behavior
`withNx`/`composePlugins` from `@nx/next` wrap `next.config.js` to
transpile workspace libraries and patch CSS-module loaders. New apps
scaffold the wrapper.
## Expected Behavior
Both helpers are warn-only deprecated (removed in v24). New apps
generate a plain `next.config.js`. Next.js 16 transpiles workspace
libraries natively on Turbopack and webpack, so the wrapper is no longer
needed. Migration recipe added under the Next.js config docs.
## Related Issue(s)
NXC-4325
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nxc-4325-0010e859)
<!-- polygraph-session-end -->
## Current Behavior
`@angular/build@21.2.14` (published 2026-06-03) changed
`ComponentStylesheetBundler` to back its bundling with a persistent
esbuild `context()` instead of a one-shot `esbuild.build()`. That
context keeps an esbuild service — a child process and its sockets —
alive until it is explicitly disposed. `@angular/build`'s own
application builder handles this with a `finally { await
result.dispose() }`.
`@nx/angular-rspack` drives `ComponentStylesheetBundler` directly (a
shared singleton in `setup-compilation.ts`) but never disposed it. With
`@angular/build` <= 21.2.13 this was harmless because the one-shot
`build()` cleaned up automatically; with >= 21.2.14 the esbuild service
leaks and a one-shot `rspack build` never exits after the bundle is
written.
Because the catalog/peer range for `@angular/build` allows `< 22.0.0`, a
freshly created workspace now resolves `21.2.14`, so the `e2e/angular`
"Convert Angular Webpack Project to Rspack" test fails with `Command
timed out after 300s: build ...` even though the bundle builds
successfully.
## Expected Behavior
After a one-shot build finishes, `@nx/angular-rspack` disposes the
stylesheet bundler — releasing the esbuild service so `rspack build`
exits cleanly:
- Adds `disposeComponentStylesheetBundler()` to
`@nx/angular-rspack-compiler`, which disposes and resets the shared
singleton.
- The plugin calls it from the compiler's `done` hook on a one-shot
build. This is safe per-compiler: an SSR build runs the server compiler
after the browser one (`dependencies: ['browser']`), never concurrently,
and `setupCompilation` lazily recreates the singleton (`??=`) if a later
compiler needs it again.
- Watch builds keep the bundler for incremental rebuilds and tear it
down on process shutdown instead.
Verified directly against `@angular/build@21.2.14`: an undisposed
bundler leaves a `ChildProcess` + sockets alive (the process hangs);
calling `dispose()` closes them and the process exits.
## Related Issue(s)
Surfaced by the `e2e-angular` `misc.test.ts` regression after
`@angular/build@21.2.14` published; no existing GitHub issue.
## Current Behavior
Three error/diagnostic messages in the `nx` package misspell "occurred"
as "occured":
- `packages/nx/src/plugins/js/lock-file/lock-file.ts` — `title: 'An
error occured while creating pruned lockfile'` (shown to users on
lockfile pruning failure)
- `packages/nx/src/command-line/graph/graph.ts` — `"... occured while
processing the project graph. Showing partial graph."` (console output)
- `packages/nx/src/project-graph/error-types.ts` — doc comment `"...
errors which occured."`
## Expected Behavior
The word is spelled "occurred" in all three places.
## Related Issue(s)
N/A — spelling-only fix, no behavior change.
## Current Behavior
The recently added `nx migrate --mode` flag (`first-party` /
`third-party`) is offered and accepted regardless of the versions
involved. These modes rely on migration metadata that only exists in Nx
v23+, so running them against pre-v23 targets produces incorrect
results. In addition, a bare `nx migrate` defaults to `nx@latest` even
from old installs, and the multi-major prompt can offer a v22.x step
that would invalidate an already-selected mode.
## Expected Behavior
The modes are gated to where the metadata exists, and the surrounding
flow is made safe:
- `first-party` is offered/accepted only when migrating from Nx v22+
into a v23+ target.
- `third-party` only when the workspace is already on v23+.
- The target version (including `latest`/dist-tags) is resolved before
the mode is decided, so the gate reads a concrete major.
- A bare `nx migrate` on a pre-v22 install again requires an explicit
target instead of defaulting to `latest`.
- The multi-major prompt no longer offers a v22.x step, so a selected
mode can't be invalidated by a redirect below v23.
When the functionality isn't available, `nx migrate` falls back to
migrating all packages, matching prior behavior.
## Implementation Details
- The gate predicate lives in `resolveMode`: `first-party` requires
`installedMajor >= 22` and a v23+ target; `third-party` requires
`installedMajor >= 23`. An explicit `--mode` on an unavailable
combination throws with an actionable message; the interactive prompt
only offers the available modes (and falls back to `all` when none).
- `resolveTargetAndMode` resolves dist-tag targets up front. This moves
the single existing registry round-trip earlier — the multi-major check
then short-circuits on the concrete version. On registry failure for a
bare sentinel it assumes >= v23 so the gate stays sensible and still
degrades gracefully downstream.
- `multi-major.ts` suppresses the v22.x current-major step rather than
reordering the mode / multi-major flow.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nxc-4493-b48693de)
<!-- polygraph-session-end -->
## Current Behavior
`@nx/vue` and `@nx/nuxt` are not compliant with the multi-version
support initiative (`nx migrate --first-party-only`):
**`@nx/vue` (NXC-4409)**
- `vue`, `vue-tsc`, `vue-router` and `@vitejs/plugin-vue` are not
declared as peer dependencies, so workspaces have no signal that Vue is
required and no advertised compatible range.
- The `20.7.1` and `22.0.0` migrations bump `@vitejs/plugin-vue` across
a major version with no `requires` gate, so the bump fires for every
workspace regardless of source major.
- Generators silently overwrite user-pinned third-party versions (most
`addDependenciesToPackageJson` calls omit `keepExistingVersions`; the
init schema defaults it to `false`).
- There is no below-floor guard — a Vue 2 workspace silently gets Vue 3
scaffolding.
**`@nx/nuxt` (NXC-4397)**
- `nuxt` itself is not declared as a peer (only `@nuxt/schema` is).
- `@nuxt/schema` is declared as a peer but is missing from the version
map / install lanes.
- The `22.2.0` migration bumps `nuxt` from v3 to v4 with no `requires`
gate.
- Generators silently overwrite user-pinned versions, and there is no
below-floor throw when Nuxt 2 (or older) is detected.
## Expected Behavior
Both plugins declare an accurate support window and behave correctly
across it:
**`@nx/vue`** (supported window: Vue 3.x — Vue 2 is EOL since
2023-12-31)
- `vue` (`^3.0.0`), `vue-router` (`^4.0.0`), `vue-tsc` (`^2.0.0`) and
`@vitejs/plugin-vue` (`^5.0.0 || ^6.0.0`) are declared as **optional**
peer dependencies.
- `assertSupportedVueVersion` (floor `3.0.0`) runs as the first
statement of every generator, throwing a clear error on sub-floor Vue.
- The `22.0.0` migration gates on `@vitejs/plugin-vue` `>=5.0.0 <6.0.0`;
the v4→v5 bump is split out of `20.7.1` into its own gated entry so the
same-major `vue`/`vue-tsc`/`vue-router` bumps are not wrongly skipped.
- All generator `addDependenciesToPackageJson` calls pass
`keepExistingVersions: true`; the init schema default flips to `true`.
- The supported-versions docs table reflects the `^3.0.0` window.
**`@nx/nuxt`** (supported window: Nuxt 3 & 4 — Nuxt 2 is EOL since
2024-06-30)
- `nuxt` is declared as a peer (`>=3.10.0 <5.0.0`) alongside
`@nuxt/schema`.
- `@nuxt/schema` is added to the version map and installed per-major by
the application generator.
- `assertSupportedNuxtVersion` (floor `3.0.0`) runs as the first
statement of every generator.
- The `22.2.0` migration gates on `nuxt` `>=3.0.0 <4.0.0` (the single
gate covers the bundled Nuxt-monorepo siblings).
- All generator `addDependenciesToPackageJson` calls pass
`keepExistingVersions: true`; the init schema default flips to `true`.
Both plugins gain a parameterized `all-generators-enforce-floor` spec
that exercises every generator's floor assert.
## Related Issue(s)
Implements the multi-version support compliance tasks NXC-4409
(`@nx/vue`) and NXC-4397 (`@nx/nuxt`).
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The `@nx/angular:convert-to-with-mf` generator wasn't marked deprecated
— it was the only Angular Module Federation generator missing the
`x-deprecated` marker its siblings (`host`, `remote`, `setup-mf`,
`federate-module`) already carry.
Separately, those four siblings emitted their deprecation notice twice:
once from the `x-deprecated` manifest marker (printed by `nx generate`
and shown in `--help`) and again from a redundant in-body `logger.warn`.
## Expected Behavior
- `@nx/angular:convert-to-with-mf` is now marked deprecated via
`x-deprecated` in `generators.json`, so the CLI reports it on `nx g` and
in `nx g convert-to-with-mf --help`.
- The redundant in-body `logger.warn` calls are removed from all five
Angular MF generators; they rely solely on the declarative
`x-deprecated` marker, which already warns on run. The shared
`module-federation-deprecation.ts` helper now retains only the executor
warnings, which have no `x-deprecated` equivalent.
- All five generator `x-deprecated` messages now include the
migration-guide URL.
Angular Module Federation in Nx is no longer supported; users should
move to Angular Native Federation
(`@angular-architects/native-federation`). These generators will be
removed in Nx v24.
## Related Issue(s)
Resolves NXC-3706
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Loading a local Nx plugin from its TypeScript source (via the
`development`/source export condition, including secondary entry points)
fails when the source uses NodeNext-style explicit `.js` relative
imports — `import './nodes.js'` where the file on disk is `nodes.ts`.
Any `nx` command that builds the project graph errors with `Cannot find
module './nodes.js'`.
Node's native TypeScript stripping loads the `.ts` source but does not
rewrite the explicit `.js` specifier to its `.ts` sibling, and nothing
in the plugin-loading path did either.
## Expected Behavior
Local plugins whose TypeScript sources use NodeNext `.js` import
specifiers load correctly from source, for both `type: module` (ESM) and
`type: commonjs` packages.
## Implementation Details
Add dependency-free `.js` → `.ts` resolution on the native-strip
plugin-loading path, mirroring how `tsx` / `ts-node`'s
`experimentalResolver` patch module resolution:
- **CJS:** a `Module._resolveFilename` fallback that rewrites
`.js`/`.mjs`/`.cjs` → `.ts`/`.tsx`/`.mts`/`.cts` when the requesting
module is itself TypeScript and the `.js` file doesn't exist.
- **ESM:** a self-contained inline `module.register` resolve hook (no
`ts-node`/`swc-node` dependency) that does the same on the
dynamic-import path, leaving Node's native stripping to load the
resolved `.ts` — preserving true ESM semantics.
Both are best-effort, fire only on a not-found error, and never alter
resolutions that already succeed. `type: commonjs` sources still rely on
the existing swc/ts-node fallback to transpile their `import` syntax
(native strip can't run ESM syntax in a CJS module), after which the CJS
resolver patch handles the emitted `require('./x.js')`.
> [!NOTE]
> The ESM resolve hook is skipped when a transpiler is preloaded via
`--require`/`--import` (e.g. `--require ts-node/register`), which only
happens when Nx itself runs from `.ts` source — registering it there
would crash the `module.register` loader-hook worker. Published Nx
(compiled `.js` workers, no preload) is unaffected.
## Current Behavior
`findMatchingConfigFiles` still rematches every candidate file against
the plugin glob even though `multiGlobWithWorkspaceContext` already
returned the file list for that exact glob. The previous branch revision
only precompiled the minimatch pattern, which reduced some overhead but
still kept the redundant rematch in the hot path.
## Expected Behavior
Once `projectFiles` already comes from `multiGlobWithWorkspaceContext`
for a plugin's `createNodes` pattern, `findMatchingConfigFiles` should
only apply include/exclude filtering and should not rematch against the
same glob again.
This change removes the redundant rematch entirely and updates the unit
tests to reflect the actual contract of `findMatchingConfigFiles`: it
operates on a pre-matched file list plus include/exclude filters.
Benchmark and reproduction evidence:
- Repro issue: https://github.com/nrwl/nx/issues/35792
- Public repro repo:
https://github.com/cw-alexcroteau/nx-find-matching-config-files-repro-20260525
- Successful cross-platform perf summary:
https://github.com/cw-alexcroteau/nx-find-matching-config-files-repro-20260525/actions/runs/26409870118/attempts/1#summary-77741892587
## Related Issue(s)
Fixes#35792
---------
Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
## Current Behavior
`@nx/vite` and `@nx/vitest` don't consistently honor their supported
version windows. Cross-major `packageJsonUpdates` and version-specific
migrations run on workspaces outside their target range, generators can
overwrite pinned third-party versions, and unsupported versions silently
fall through to the latest install constants instead of erroring.
## Expected Behavior
Both plugins enforce their support windows. Cross-major migrations
declare source-major `requires` gates so they only run where they apply,
generators throw a clear below-floor error and preserve pinned versions
(`keepExistingVersions` defaults to `true`), and peer ranges and version
maps match what each plugin actually supports.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
## Current Behavior
Under Distributed Task Execution (DTE), the Nx Cloud agent runs each
task via the
programmatic `runDiscreteTasks` API. That path builds the task
orchestrator's
schedule graph from the single task assigned to the agent, with its
dependency
edges stripped — intentionally, so the scheduler runs only that task
without
blocking on dependencies that ran (and were cached) on other agents.
`TaskOrchestrator.runBatch` then forwarded that edge-less schedule graph
to batch
executors as `context.taskGraph`. With the edges missing, `@nx/gradle`'s
batch
executor could not see that, for example, `:proj:gradle:shadowJar`
`dependsOn`
`:proj:gradle:compileKotlin`. It therefore never passed `--exclude-task`
for the
dependency, and Gradle re-ran `compileKotlin` itself instead of reusing
the
restored cache outputs — which could fail the build.
This only affected the distributed path. A normal `nx run-many` / `nx
affected`
already passes a fully-connected task graph, so the exclusion worked
there.
## Expected Behavior
`runBatch` now forwards the **full** task graph to batch executors as
`context.taskGraph`, so dependency edges are visible to executors.
`@nx/gradle`
can compute `--exclude-task` for transitive Gradle tasks that already
ran on
other agents, so they are no longer re-run under DTE.
The orchestrator field previously named `taskGraphForHashing` is renamed
to
`fullTaskGraph`, since hashing was only one of its consumers — it is now
also the
executor context graph. For non-distributed runs the schedule graph and
the full
graph are the same object, so this is a no-op there. Other batch
executors
(`maven`, `jest`, `workspace`) don't read `context.taskGraph`;
`@nx/js:tsc` reads
it for project references and now receives, under DTE, the same
connected graph it
already receives in a normal run.
A regression test asserts that `runBatch` forwards the full (edged)
graph rather
than the edge-less schedule graph.
## Related Issue(s)
N/A — surfaced while debugging a Gradle `shadowJar` failure under DTE.
## Current Behavior
`nxViteTsPaths` and `nxCopyAssetsPlugin` from `@nx/vite/plugins/*` run
silently. New TS-solution workspaces never emit them; new
non-ts-solution workspaces still do.
## Expected Behavior
Both helpers log a one-time deprecation warning on call and carry
`@deprecated` JSDoc tags. Behavior unchanged in v23; removal candidate
for v24.
- TS-solution: helpers are unused (package-manager workspaces handle
path resolution; the project root is the publishable folder so no
`package.json` copy is needed). Generator output is helper-free and a
spec test locks that in.
- Non-ts-solution: workspaces still need both helpers, so generator
output is unchanged for now. A spec test locks in the current emit with
a TODO(v24) for the swap to `vite-tsconfig-paths`.
Configure-Vite docs lead with `vite-tsconfig-paths` and `publicDir` /
`vite-plugin-static-copy` and flag the helpers as deprecated.
## Related Issue(s)
NXC-4316
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
When using `nx release publish` with pnpm (or other package managers),
if a package version has already been published to the registry, the
executor fails with an error instead of gracefully handling it.
## Expected Behavior
The release-publish executor should detect "already published" errors
(E403 with 'previously published versions', EPUBLISHCONFLICT, E409
conflicts) and skip the publish gracefully with a warning, rather than
failing the entire release process.
## Related Issue(s)
Closes#35235
Adopts the canonical multi-version support shape so workspaces on jest
v29 stay on v29 unless they explicitly opt into v30, and workspaces
below v29 fail loudly with the standardized "Unsupported version of
\`jest\` detected" message instead of silently falling through to v30
install constants.
- Declare peerDependencies (jest, ts-jest, @types/jest as ^29 || ^30,
all optional) so workspaces have an advertised compatible range.
- Add assertSupportedJestVersion wrapper around the shared
assertSupportedPackageVersion helper; asserts jest and ts-jest
independently (they install on separate version trains).
- Rewrite versions.ts to use the shared getInstalledPackageVersion
helper from @nx/devkit/internal, drop the above-ceiling throw in
versions() in favor of latest-fallback, drop the bespoke
validateInstalledJestVersion. Add a ts-jest v30 install lane.
- Delete the dead version-utils.ts file (zero usages).
- Call the assert as the first statement in init, configuration, and
convert-to-inferred generators; flip init schema's keepExistingVersions
default to true and apply the ?? true fallback to preserve user-pinned
versions on re-runs.
- Gate the 21.3.0 cross-major packageJsonUpdates entry with requires: {
jest: ">=29.0.0 <30.0.0" } so v29 users get the v30 bump but
v28-or-below users do not get silently pushed across two majors.
- Add the parameterized all-generators-enforce-floor.spec.ts to
guarantee every entry asserts the floor.
NXC-4391
---------
Co-authored-by: Claude <noreply@anthropic.com>
Add a subsection explaining that createNodes/createNodesV2 output must
be deterministic. covers stable array ordering, avoiding run-specific
values; explains the consequences of cache misses.
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
Calling `nx release version` currently runs docker build, due to
detection of Dockerfile in the project. Users using docker outside of
nx/docker have no way of preventing this.
## Expected Behavior
Running `nx release version` should not trigger docker build unless,
docker use is explicitly enabled.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
The `@nx/eslint` package supports ESLint v8 as a first-class citizen.
Fresh installs without flat config get ESLint v8 + typescript-eslint v8,
and there is no signal to users that this support is going away.
In addition, workspaces that end up on ESLint v9 with eslintrc-style
config get a broken `workspace-rule` rule-test template. ESLint v9
dropped the eslintrc-style `RuleTester` class, but the generator emits
`TSESLint.RuleTester` (which extends `eslint.RuleTester`) regardless of
the installed major. v9 + eslintrc users currently can't run the
generated `.spec.ts` without manual edits.
## Expected Behavior
- ESLint v8 support is soft-deprecated. `@nx/eslint` generators (via
`assertSupportedEslintVersion`) and the `@nx/eslint:lint` executor emit
a deprecation warning on every invocation when the workspace's installed
ESLint major is 8. v8 keeps working unchanged; hard removal is scheduled
for Nx v24.
- Fresh installs always pull the latest supported ESLint stack (v9 +
typescript-eslint v8) regardless of the user's flat-config preference.
The eslintrc config-file shape is still honored at the file level
(ESLint v9 loads eslintrc files through `LegacyESLint`); only the
installed package versions move forward.
- The `workspace-rule` generator emits the flat-style
`@typescript-eslint/rule-tester` template (and installs that dep) for
any workspace ending up on ESLint v9+, not just flat-config ones. v8 +
eslintrc workspaces continue to use the existing `TSESLint.RuleTester`
template unchanged.
## Implementation Details
- New `warnEslintV8Deprecation()` in
`packages/eslint/src/utils/deprecation.ts`, fired from
`assertSupportedEslintVersion` (choke point for the `init`,
`lint-project`, `workspace-rule`, `workspace-rules-project`,
`convert-to-flat-config`, and `convert-to-inferred` generators) and the
lint executor.
- The `workspace-rule` generator now gates the rule-test template on
`useFlatRuleTester = flatConfig || effectiveEslintMajor >= 9`, derived
from `versions(tree).eslintVersion` so it covers both declared
workspaces and fresh installs.
- `versions(tree)` no longer consults `useFlatConfig` for the
fresh-install branch; both lanes return `latestVersions`.
`minSupportedEslintVersion` stays at `'8.0.0'` and `versionMap[8]` stays
in place so existing v8 workspaces keep getting a coherent stack until
v24 removes them.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nxc-3986-ac90716f)
<!-- polygraph-session-end -->
---------
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
## Current Behavior
The shared CLI examples for `nx show target` document input and output
inspection with the target before the subcommand, for example `nx show
target my-app:build inputs`.
## Expected Behavior
The examples use the canonical subcommand-first syntax shown in the
command help and intended for the Nx 22.7 sandboxing blog correction:
`nx show target inputs my-app:build` and `nx show target outputs
my-app:build`.
## Related Issue(s)
N/A - reported directly in Polygraph session
`fix-sandboxing-code-example-3443d65b`.
## Validation
- `npx prettier --write -- packages/nx/src/command-line/examples.ts`
- `git diff --check`
- Focused `pnpm exec tsx` metadata check confirming the stale
target-first examples are absent and the subcommand-first examples are
present.
Full `nx prepush` has not been run.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/fix-sandboxing-code-example-3443d65b)
<!-- polygraph-session-end -->
## Current Behavior
A migration's documentation (the co-located markdown explaining what it
does) is not referenced in `migrations.json`, so when `nx migrate
--run-migrations --agentic` drives an AI agent, the agent has no access
to it.
## Expected Behavior
Migration entries can declare a `docs` markdown path. `nx migrate`
carries it into the generated `migrations.json`, and under `--agentic`
the resolved path is surfaced to the agent (for prompt, hybrid, and
validation steps) so it has context on what the migration is meant to
do. Existing first-party migrations with co-located docs are wired up
via the new field and published with their packages.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nxc-4495-6c420036)
<!-- polygraph-session-end -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The `@nx/jest:jest` executor accepts a `setupFile` option that has been
deprecated in favor of declaring setup files via `setupFilesAfterEnv` in
the Jest configuration file. The executor merges `setupFile` into
`setupFilesAfterEnv` at runtime.
The `@nx/jest:configuration` generator accepts a `skipSetupFile` option
that has been deprecated in favor of `setupFile: 'none'`.
## Expected Behavior
The `@nx/jest:jest` executor's `setupFile` option is removed. The
`@nx/jest:configuration` generator's `skipSetupFile` option is removed.
Two migrations run automatically on `nx migrate`.
`migrate-jest-executor-setup-file` pushes existing executor `setupFile`
values into `setupFilesAfterEnv` in the project's Jest config (using
`<rootDir>/...` form), then strips the option from `project.json` and
`nx.json` target defaults. Coverage:
- Per-target `setupFile` in `project.json` (base options or
configurations).
- `setupFile` in `nx.json` `targetDefaults`, including for targets that
inherit the default without declaring their own — the migration walks
affected projects, expands `{projectRoot}` per project, and writes the
inherited value into each project's Jest config before stripping the
default.
- Configurations that override `jestConfig` and inherit the base
`setupFile` get their override Jest config migrated too, so `nx test
<project> -c <config>` keeps loading the setup file post-migration.
- Configurations with their own `jestConfig` and a different `setupFile`
from base are auto-migrated to the configuration's own Jest config.
- Static `module.exports = {...}` and `export default {...}` shapes,
including configs with spreads (`{ ...preset }` — multi-spread mirrors
object-spread "last wins" via a nullish-coalescing fallback chain) and
quoted property names.
- Custom `rootDir` declared as a string literal — the migrated path is
computed relative to the resolved `rootDir`.
- Path-aware deduplication: existing `'./src/setup.ts'` entries aren't
duplicated when the new entry resolves to the same file.
Edge cases that can't safely be auto-rewritten still strip the dead
option but surface a follow-up warning listing the affected targets so
the setup file path can be moved manually:
- Non-static configs (factory functions, dynamic exports) →
`unparseable`.
- Custom `rootDir` declared as a non-literal expression →
`nonLiteralRootDir`.
- Shared Jest config across targets with different setup files →
`sharedConfigConflict`.
- `setupFile` plus `setupFilesAfterEnv` passthrough in the same scope →
`passthroughCollision`.
- Configuration-scoped `setupFile` that would leak to the base run →
`configurationOnly`.
- Targets without any resolvable `jestConfig` →
`noResolvableJestConfig`.
`migrate-jest-configuration-skip-setup-file` rewrites `skipSetupFile`
defaults stored in `nx.json` `generators` or per-project `project.json`
`generators` (both flat `@nx/jest:configuration` and nested `@nx/jest` →
`configuration` forms): `skipSetupFile: true` becomes `setupFile:
'none'` (preserving original behavior), `skipSetupFile: false` is
dropped (it was a no-op). CLI invocations of `@nx/jest:configuration
--skipSetupFile` should pass `--setupFile=none` instead.
BREAKING CHANGE: The `setupFile` option of the `@nx/jest:jest` executor
and the `skipSetupFile` option of the `@nx/jest:configuration` generator
are removed.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
## Current Behavior
`nx migrate` is configured entirely through CLI flags — there's no way
to set workspace-wide defaults, so every run repeats the same
`--create-commits`, `--commit-prefix`, `--mode`, and so on.
When the agentic flow runs during `nx migrate --run-migrations`:
- The "Enable the agentic flow?" prompt is asked on every run, with no
way to remember the answer.
- The flow aborts when it can't start — for example in a non-interactive
terminal, or when a requested/pinned agent isn't installed.
## Expected Behavior
A new optional `migrate` section in `nx.json` sets workspace-wide
defaults for `nx migrate`. A CLI flag always takes precedence over the
`nx.json` value, which takes precedence over the built-in default (for
`multiMajorMode`: CLI flag > `NX_MULTI_MAJOR_MODE` > `nx.json` >
default).
| Option | Applies to |
| --- | --- |
| `createCommits`, `commitPrefix`, `agentic`, `validate` | running
migrations (`nx migrate --run-migrations`) |
| `mode`, `multiMajorMode` | generating migrations (`nx migrate
<target>`) |
The agentic flow is also more flexible and resilient:
- The "Enable the agentic flow?" prompt now offers to remember your
choice; choosing a "remember" option persists it to `migrate.agentic` so
you aren't asked again.
- It degrades gracefully instead of aborting. A non-interactive terminal
warns and continues without the agentic flow. A requested or pinned
agent that isn't installed warns and resolves from the agents that are
installed — prompting when several are present, auto-selecting the only
one, and erroring only when none are installed.
## Implementation Details
- Adds `NxMigrateConfiguration` to `NxJsonConfiguration`, the `nx.json`
JSON schema, and the `nx-json` docs reference.
- `nx.json` defaults are overlaid onto the parsed args in a phase-aware
way, so generate-only and run-only options never trip the existing
`--run-migrations` guards. Config values (`mode`, `multiMajorMode`,
`agentic`) are validated with the same rules as the CLI flags.
- The agentic enable prompt is a single prompt whose choices adapt to
how many agents are installed. Persisting writes the raw `nx.json` (so
an `extends` preset is never inlined into the user's file) and never
throws — a failed write just means the prompt is shown again next time.
- A custom `migrate.commitPrefix` that can't take effect because commits
aren't enabled now warns instead of being silently ignored.
- Option value lists and validation are consolidated into single sources
of truth shared by the CLI builder, the `nx.json` overlay, and the
config types.
- Covered by unit tests for the config overlay, the agentic prompt and
agent resolution, and the commit-prefix warning.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nxc-4457-5a746c6e)
<!-- polygraph-session-end -->
## Current Behavior
When running inside a Linux container or Kubernetes pod, resource
metrics report the host node's CPU and memory totals instead of the
pod's limits. A pod with constrained CPU/memory shows the underlying
node's resources.
The same gap exists for process-isolated Windows containers running
inside a Windows Job Object (e.g. Docker `--cpus` / `--memory`): metrics
report host values rather than the Job's limits.
## Expected Behavior
Resource metrics report the effective CPU and memory limits enforced by
the kernel for the calling process — derived from the cgroup it belongs
to on Linux, or the Job Object on Windows. macOS native processes
continue to report host values (no equivalent enforcement primitive
exists).
## Implementation Details
### Linux (`cgroup` module)
Resolves the calling process's actual cgroup directory by parsing
`/proc/self/cgroup` + `/proc/self/mountinfo`. Reads `cpu.max` /
`memory.max` (cgroup v2) or `cpu.cfs_{quota,period}_us` /
`memory.limit_in_bytes` (cgroup v1, including the systemd `cpu,cpuacct`
co-mount).
Walks from the leaf up to the mount point and takes the minimum finite
limit found at any level — the kernel enforces the tightest ancestor's
limit (hierarchical enforcement), so leaf-only reads can overreport when
a pod-level cgroup is tighter than the container's (common in K8s VPA
in-place resize and Burstable QoS).
Composition with `sched_getaffinity` covers cpuset / taskset
restrictions. We deliberately bypass
`std::thread::available_parallelism()` because rust-lang/rust's
implementation applies the cgroup quota with floor division internally,
which would silently underreport fractional quotas (e.g. 1.5 cores → 1).
Instead we ceil quota / period, matching HotSpot JVM, Go 1.25, .NET, and
the `num_cpus` crate.
mountinfo path fields containing spaces / tabs / newlines / backslashes
are kernel-encoded as `\040` / `\011` / `\012` / `\134` per `man 5
proc_pid_mountinfo`; we unescape before joining with the cgroup path.
`/proc/self/cgroup` itself emits paths raw (verified across kernels
v4.18 → v6.13), so no unescape is needed there.
Replaces the prior leaf-only path lookups (which broke on cgroup v1
co-mount and any non-namespaced container setup). The in-tree module is
preferred over `sysinfo`'s `cgroup_limits()` (now parent-aware in 0.39 —
see *Additional Changes*) because it also covers CPU quota and the v1
co-mount + bind-mount edge cases in a single place we control. 30 unit
tests cover cgroup discovery, parsing, ancestor walk, and the v1 / v2 /
co-mount / bind-mount / cgroupns=host cases.
### Windows (`job_object` module)
Detects Job Object resource limits via the Win32 API:
- **CPU**: `ceil(host_cpu_count × CpuRate / 10000)` from
`JobObjectCpuRateControlInformation` when `HARD_CAP` is set (Docker
`--cpus` translates to HARD_CAP). Plus the popcount of the Job's
affinity mask (when `LIMIT_AFFINITY` is set), and
`GetProcessAffinityMask` (covers Job + manual + system intersections).
Takes the minimum.
- **Memory**: minimum of `ProcessMemoryLimit` / `JobMemoryLimit` /
`MaximumWorkingSetSize` from `JobObjectExtendedLimitInformation`, gated
on the corresponding `LIMIT_*` flags. Mirrors HotSpot and .NET.
- **Skipped**: `WEIGHT_BASED` rate control (relative priority, not a
hard limit) and soft-cap rate control (kernel allows transient bursts) —
neither maps to a defensible "available cores" number.
Any Win32 failure is treated as "no information"; the caller falls back
to host values. No new crate dependency — the existing `winapi` dep is
extended with `jobapi`, `jobapi2`, `processthreadsapi`, `winbase`, and
`winnt` features.
#### Known limitation: nested Job hierarchies
[`QueryInformationJobObject(NULL,
...)`](https://learn.microsoft.com/en-us/windows/win32/api/jobapi2/nf-jobapi2-queryinformationjobobject)
returns the **immediate** Job's settings (per MSDN: *"If the job is
nested, the immediate job of the calling process is used."*) and Win32
exposes no documented API to enumerate parent Jobs.
Per
[`JOBOBJECT_CPU_RATE_CONTROL_INFORMATION`](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-jobobject_cpu_rate_control_information)
Remarks, *"the rates set for the job represent its portion of the CPU
rate that is allocated to its parent job"* — nested rates compose
multiplicatively. So with parent HARD_CAP 50% × child HARD_CAP 50%, the
effective rate is 25% of host but we read the child's 50% and report
`ceil(host × 0.5)`. Per-process / per-job memory has the same shape
(kernel enforces min across the chain; we see only the immediate Job).
Affinity is unaffected — `GetProcessAffinityMask` returns the
kernel-effective mask. HotSpot and .NET CoreCLR exhibit the same memory
limitation; HARD_CAP CPU-rate detection goes beyond them for Docker
`--cpus` parity in the common single-silo case, accepting the nested-Job
overreport as the documented cost.
### Cross-platform
`SystemInfo { cpuCores, totalMemory }` shape is unchanged — consumers do
not need to update. macOS native processes continue to report host
values (no container-style enforcement primitive exists; container
runtimes on macOS run Linux VMs where the Linux path applies).
The module doc-comments cross-reference Go 1.25
`internal/runtime/cgroup`, libuv `src/unix/linux.c`, OpenJDK
`cgroupSubsystem_linux.cpp` + `os_windows.cpp`, .NET CoreCLR
`gc/unix/cgroup.cpp` + `gc/windows/gcenv.windows.cpp`, and Rust stdlib
`library/std/src/sys/thread/unix.rs::cgroups`.
## Additional Changes
Two infrastructure chores are bundled into this PR as separate commits:
### `chore(core): bump sysinfo to 0.39.1`
Bumps `sysinfo` from `0.37.2` → `0.39.1`. No source changes were
required — all signatures we use (`System`, `Process`, `Pid`, `Signal`,
`Disks`, the `*RefreshKind` types, `UpdateKind`,
`MINIMUM_CPU_UPDATE_INTERVAL`) are unchanged. `Cargo.lock` deltas:
- New transitive dep `objc2-open-directory` from sysinfo 0.39's
soundness fix for user retrieval on Apple targets.
- `windows` family bumped to `0.62.x` per sysinfo's new constraint,
which lets the graph converge on single versions of `windows-core`,
`windows-link`, `windows-result`, and `windows-strings` — four duplicate
`windows-*` entries are deduplicated as a result.
sysinfo 0.39.0 also added `Process::cgroup_limits()` and parent-cgroup
memory walking inside `System::cgroup_limits()` — overlapping
conceptually with the in-tree `cgroup` module introduced by this PR. The
in-tree module is kept because it also covers CPU quota (sysinfo's
helpers are memory-only), the cgroup v1 co-mount, and bind-mount path
translation in a single implementation under our control. Future
consolidation onto the upstream APIs is possible but explicitly out of
scope here.
### `chore(repo): bump mise rust to 1.95.0 to match rust-toolchain.toml`
`rust-toolchain.toml` was bumped to `1.95.0` in #35665 to unblock the
sysinfo upgrade, but `mise.toml` was left at `1.90.0`. CI installs Rust
via mise (which exports `RUSTUP_TOOLCHAIN`, overriding
`rust-toolchain.toml`), so CI continued to run on `1.90.0` and failed
the sysinfo 0.39 MSRV check until this commit. The two files now agree.
## Current Behavior
The canary release pipeline fails while building the `@nx/dotnet`
analyzer:
```
error NETSDK1018: Invalid NuGet version string: 'canary'.
[/home/runner/work/nx/nx/packages/dotnet/analyzer/MsbuildAnalyzer.csproj]
```
**Root cause:** the `publish` workflow's `Publish` step exported
`VERSION=canary` for the whole step. MSBuild imports environment
variables as properties (case-insensitively), so `VERSION` becomes
`$(Version)` during the `dotnet build` that runs underneath `nx-release`
(the `@nx/dotnet` package ships the compiled `MsbuildAnalyzer.dll`, so
building it during publish runs the analyzer build). On canary the value
is the non-semver string `canary`, which `GenerateAssemblyInfo` rejects.
This only began failing once `@nx/dotnet` — the repo's first project
that runs `GenerateAssemblyInfo` — entered the publish build graph; the
`VERSION` env var itself is long-standing and unchanged.
## Expected Behavior
The publish step no longer exposes a generically-named `VERSION`
environment variable that MSBuild can adopt as `$(Version)`.
The env var is renamed to `NX_PUBLISH_VERSION`. `nx-release` reads the
version as a positional CLI argument (not from the environment), so no
script change is required — only the workflow. A comment is added
warning against reusing the generic `VERSION` name.
This fixes the leak at its source rather than working around it in each
.NET project.
## Related Issue(s)
N/A
Co-authored-by: Claude <noreply@anthropic.com>
## Current Behavior
The nightly "E2E matrix" `process-result` job times out and is
cancelled, so the Slack summary stopped posting after May 23. Its
failure-detail step makes many serial, uncached GitHub API calls - a
30-run history fetch plus a per-failure binary search to pin each
error's start date - which on runs with many golden failures exceeds the
15-minute job cap and trips GitHub's secondary rate limit before the
reporting steps run.
## Expected Behavior
The start-date/streak analysis is removed entirely, along with all
related wording in the report. The Slack message now lists the failing
projects and their actual errors, with no temporal claims. The job
finishes in ~30s and posts again.
## Current Behavior
The `update-repos` script (`tools/update-repos/src/update-repo.ts`)
pushes the
`upnx` update branch and then tries to create or update a pull request
directly
via the GitHub CLI (`gh pr create` / `gh pr edit`), finally opening it
in the
browser with `gh pr view --web`. When `gh` PR creation isn't available,
the run
fails to surface a usable PR link.
## Expected Behavior
The script still pushes the `upnx` branch, but no longer creates PRs via
`gh`.
Instead, at the very end it prints a ready-to-click GitHub "create pull
request"
URL with the title and description pre-filled, for each updated repo:
```
📦 nx
Title: chore(repo): update nx to <version>
Description: Updating Nx from <from> to <to>
Open PR: https://github.com/nrwl/nx/compare/master...upnx?expand=1&title=...&body=...
```
When updating all repos concurrently, the run now uses
`Promise.allSettled` so
the PR URLs for repos that succeeded are still printed even if another
repo
fails; the run then reports which repos failed.
## Related Issue(s)
N/A
## Current Behavior
The `projectsAffectedByDependencyUpdates: "auto"` setting only works for
pnpm lock files. For npm (`package-lock.json`), yarn (`yarn.lock`), and
bun (`bun.lock`/`bun.lockb`), auto mode silently returns an empty array
-- meaning lockfile-only changes (e.g. `npm audit fix`, transitive
dependency updates) produce zero affected projects.
## Expected Behavior
Auto mode detects affected projects from lock file changes for all
supported package managers:
- **pnpm** (`pnpm-lock.yaml`): Inspects the `importers` section to
determine exactly which workspace projects had dependency changes
(unchanged).
- **npm** (`package-lock.json`): Inspects the `packages` entries to
determine which workspace projects had dependency changes.
- **bun** (`bun.lock`): Inspects the `workspaces` section to determine
which workspace projects had dependency changes.
- **yarn** (`yarn.lock`): The lock file is a flat list with no
per-project structure, so all projects are marked as affected when any
dependency changes.
- **Binary lock files** (`bun.lockb`) / **WholeFileChange**: Cannot be
parsed for granular changes, so all projects are marked as affected.
The implementation uses a `LOCK_FILE_RESOLVERS` map with a
`SupportedLockFile` type guard so that adding a new lock file format
requires adding exactly one entry -- no separate lists to keep in sync.
## Related Issue(s)
Follow-up to #34937 which documented the pnpm-only limitation.
Fixes NXC-4185
Fixes https://github.com/nrwl/nx/issues/35173
---------
Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: llwt <llwt@users.noreply.github.com>
## Current Behavior
`@nx/angular`'s `zoneJsVersion` is pinned to `~0.15.0`, while
`catalogs.angular` in `pnpm-workspace.yaml` and the v21 migration entry
in `packages/angular/migrations.json` both pin `zone.js` at `~0.16.0`.
Generators and dependency-installation paths in `@nx/angular` therefore
emit `zone.js@~0.15.0` even when the project is on Angular 21, drifting
from what the migration and workspace catalog declare.
## Expected Behavior
`zoneJsVersion` matches the v21 pin (`~0.16.0`), keeping generator
output aligned with the workspace catalog and the v21 migration. The
bump is safe within Angular 21: `@angular/core@21.x` accepts `~0.15.0 ||
~0.16.0` (see `catalogs.angular-supported-versions` in
`pnpm-workspace.yaml`).
## Implementation Details
Bumps `zoneJsVersion` in `packages/angular/src/utils/versions.ts` from
`~0.15.0` to `~0.16.0`. The v21 entry in
`backward-compatible-versions.ts` inherits from `latestVersions` via
spread, so it picks up the bump automatically. The v20/v19 entries are
unchanged — those Angular majors correctly stay on `~0.15.0`.
Also extends `scripts/angular-support-upgrades/` so this drift doesn't
recur on the next Angular bump:
- `fetch-versions-from-registry.ts`: after the dist-tag fetch, resolves
`zone.js` and `rxjs` against the latest registry-published versions
matching `@angular/core@<resolved>`'s `peerDependencies` ranges. Skips
with a warning if a peer is missing or no satisfying version exists.
- `update-version-utils.ts`: adds regex bumps for `zoneJsVersion` and
`rxjsVersion` in `versions.ts`, guarded so they only run when the
version map carries those keys.
`update-package-jsons.ts` and `build-migrations.ts` need no changes —
their existing iteration over the version map already covers `zone.js`
(and `rxjs` where applicable) once the keys are populated.
`ngrxVersion` and the per-major back-compat blocks in
`backward-compatible-versions.ts` have similar drift risk but are out of
scope here (different mechanics; flagged for follow-up).
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/fix-zone-js-version-b63af779)
<!-- polygraph-session-end -->
---------
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
## Current Behavior
**`@nx/express`**
- Peer dep declares `express: ^4.21.2` only; Express v5 has been ACTIVE
since 2025-03-31 and v4 is in MAINT, so the plugin's advertised window
doesn't match the upstream support window.
- Generators install a single literal `express ^4.21.2` regardless of
what's installed; no per-major routing for `express` or
`@types/express`.
- `keepExistingVersions` schema default is `false`, so re-running the
generator silently overwrites a user's pinned `express` version.
- No floor enforcement: a workspace on an unsupported (sub-floor)
`express` version sees no error.
**`@nx/node`**
- No `peerDependencies` for `express`, `koa`, or `fastify`.
Init/application generators write framework deps unconditionally,
ignoring what's installed.
- `keepExistingVersions` defaults to `false`; framework installs
override user pins.
- `migrations.json` `22.0.2` bumps `koa` from v2 to v3 with no
`requires` block — every workspace on koa v2 gets pushed to v3
unconditionally.
- `migrations.json` `20.4.0` mixes a cross-major fastify v4→v5 bump with
same-major express bumps under one entry, with no `requires`
(AND-semantics would gate same-major bumps incorrectly if added
naively).
- No floor enforcement for any framework.
**`@nx/nest`**
- `peerDependencies` block is missing from `package.json` entirely;
workspaces have no advertised compatible range for any `@nestjs/*`
package.
- Versions module has flat constants; the plugin ships v11-only even
though NestJS v10.4.x still receives upstream patches (N & N-1 baseline
calls for v10 + v11).
- `migrations.json` `21.2.0-beta.2` uses a bare key `nest` which is not
a real npm package — the entry is effectively a no-op. The real
cross-major v10→v11 bump for `@nestjs/common`, `@nestjs/core`,
`@nestjs/platform-express`, `@nestjs/testing` is missing, as is a
`requires` source-major gate.
- No floor enforcement for any NestJS version.
## Expected Behavior
**`@nx/express` (NXC-4390)**
- Per-major `versionMap` keyed on `express` major covering both
`express` and `@types/express` for v4 and v5; fresh installs default to
v5.1.0.
- `assertSupportedExpressVersion(tree)` (calls shared
`assertSupportedPackageVersion`) is the first statement of
`initGenerator` and `applicationGeneratorInternal`.
- Peer widened to `express: ">=4.0.0 <6.0.0"` (still optional).
- All `addDependenciesToPackageJson` call sites from generators pass
`keepExistingVersions ?? true`; schema defaults flipped to `true`. Init
now installs `@types/express` (previously a dead export).
- New `all-generators-enforce-floor.spec.ts` exercises every generator
entry at `subFloorVersion: '~3.21.0'`.
- Supported-versions docs page updated.
**`@nx/node` (NXC-4396)**
- Per-package `versionMap` + `versions(tree)` for `express` (v4+v5),
`koa` (v2+v3), `fastify` (v4+v5), and `@types/node` (v22+v24). Fresh
installs default to active LTS / latest stable.
- `assertSupportedFrameworkVersion(tree, schema.framework)` (dispatches
to one wrapper per framework) is the first statement of
`applicationGeneratorInternal`, only firing when `--framework` selects a
non-`none`/`nest` lane.
- Framework + `@types/node` installs route through `versions(tree)` and
pass `keepExistingVersions ?? true`. Init schema default flipped to
`true`.
- `migrations.json`: `22.0.2` koa v2→v3 gated with `requires: { koa:
">=2.0.0 <3.0.0" }`. `22.6.0` koa CVE patch gated bilaterally to v3
only. `20.4.0` split into the original same-major express bumps (no
gate) and a new `20.4.0-fastify` entry gated on `requires: { fastify:
">=4.0.0 <5.0.0" }`.
- Optional `peerDependencies` declared for `express`, `koa`, `fastify`.
Added `semver: "catalog:"` to deps (now imported by `versions.ts`).
`@nx/dependency-checks` allow-list updated.
**`@nx/nest` (NXC-4394)**
- Per-major `versionMap` covering NestJS v10 and v11 for the full
`@nestjs/*` family plus `rxjs` and `reflect-metadata`. Fresh installs
default to NestJS v11, with `reflect-metadata` bumped from `^0.1.13` to
`^0.2.0` to match v11's requirement.
- `assertSupportedNestJsVersion(tree)` is the first statement of the
`init`, `application`, and `library` generators.
- `ensureDependencies` and the init `addDependencies` helper route
through `versions(tree)` and pass `keepExistingVersions: true` (`??
true` on the init path); init schema default flipped to `true`.
- Optional `peerDependencies` declared for `@nestjs/core`,
`@nestjs/common`, `reflect-metadata`, `rxjs`. Added `semver: "catalog:"`
to deps and extended `@nx/dependency-checks` allow-list.
- `migrations.json` `21.2.0-beta.2` rewritten with real package keys
(the previous bare `nest` key was a typo / no-op) and gated on
`requires: { "@nestjs/core": ">=10.0.0 <11.0.0" }`. The entry now also
bumps `reflect-metadata` to `^0.2.0` for v11 compatibility.
- Supported-versions docs page updated.
## Related Issue(s)
Fixes NXC-4390
Fixes NXC-4396
Fixes NXC-4394
---------
Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
The existing MF generators and setup are not modernized. We can simplify
the setup and remove strict dependency on Nx executors.
- Consolidate on official module naming: `host -> consumer`, `remote ->
provider`
- Always use dynamic runtime (i.e. use runtime utils from
`@module-federation/enhanced/runtime` so we don't have to start up every
provider app on dev machine
- No more Nx specific bundler plugins, only standard config as seen on
module-federation.io
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`@nx/eslint` and `@nx/eslint-plugin` have several multi-version support
compliance gaps:
- The bare `@nx/eslint:init` generator path lands fresh installs on EOL
ESLint v8 (`~8.57.0`) even though the flat-config path already ships v9.
- Local re-implementations of installed-version helpers in
`version-utils.ts` and per-major version aliases (`eslint9__*`) in
`versions.ts` drift from the shared `@nx/devkit/internal` helpers.
- Generators do not assert the supported ESLint floor at their entry
points — sub-floor workspaces silently get configs incompatible with
their installed ESLint.
- `addDependenciesToPackageJson` call sites in generators do not
preserve user-pinned ESLint dependency versions; `init`'s schema
defaults `keepExistingVersions` to `false`.
- The `@nx/eslint:lint` executor enforces a stale `7.6` floor with a
hand-rolled `Number(version[0])` check rather than the shared canonical
helper.
- Two ESLint migration generators (`update-typescript-eslint-v8.13.0`,
`add-file-extensions-to-overrides`) lack `requires` gates, so they run
on every workspace even when their target packages are absent or at
unrelated versions.
- `@nx/eslint-plugin` declares `@typescript-eslint/parser: ^6.13.2 ||
^7.0.0 || ^8.0.0` as a required peer but ships
`@typescript-eslint/utils` / `@typescript-eslint/type-utils` pinned to
`^8.0.0` — the peer claim is wider than the bundled runtime range, and
users only linting JavaScript see an unmet-peer warning.
## Expected Behavior
`@nx/eslint`:
- Fresh installs default to ESLint v9 via the canonical `versions(tree)`
route. Installed versions are respected through the version map (v8 →
`~8.57.0` lane; v9/v10 → `latestVersions`, with v10 silently falling
through — no above-ceiling throw).
- `versions.ts` follows the bundle pattern; `version-utils.ts` delegates
to `@nx/devkit/internal` helpers.
- Every `generators.json` entry asserts the supported floor (`8.0.0`) at
its working function's first statement via the canonical
`assertSupportedEslintVersion(tree)` wrapper. A parameterized
`all-generators-enforce-floor.spec.ts` pins this so a future generator
added without the assert fails the spec.
- All generator-side `addDependenciesToPackageJson` calls preserve
user-pinned versions: `init`'s schema defaults `keepExistingVersions` to
`true`, and programmatic callers default via `?? true`.
- The `@nx/eslint:lint` executor uses the shared
`assertSupportedInstalledPackageVersion` helper to enforce the `8.0.0`
floor with the canonical `Unsupported version of \`eslint\` detected`
message.
- The two migration generators are gated on `@typescript-eslint/parser
>=8.0.0` and `eslint >=8.57.0` respectively (open upper bound so `nx
migrate --from <older>` still applies them).
`@nx/eslint-plugin`:
- `@typescript-eslint/parser` peer is tightened to `^8.0.0` (matching
the bundled `@typescript-eslint/utils` / `type-utils` v8 runtime) and
declared optional via `peerDependenciesMeta` — users linting only
JavaScript no longer see unmet-peer warnings.
## Implementation Details
> [!IMPORTANT]
> This PR includes a cherry-picked commit (`feat(devkit): add
assertSupportedInstalledPackageVersion to @nx/devkit/internal`) that
originates from #35806. Whichever of the two PRs merges first, the other
will be rebased to drop the duplicate commit.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nxc-4387-fa5e84db)
<!-- polygraph-session-end -->
---------
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
- `@nx/angular-rspack` and `@nx/angular-rspack-compiler` silently fall
through to broken behavior when the workspace's `@angular/build`,
`@rsbuild/core`, or `@rspack/core` is below the supported floor.
- `@nx/angular-rspack-compiler` keeps `@angular/build` as a direct
`dependencies` entry, forcing a version that can conflict with the
user's installed Angular major.
- `@nx/angular-rspack` calls `createAngularCompilation` with the
`browserOnlyBuild` argument inverted: when the user has a server entry,
the compiler is told it's a browser-only build, and vice versa.
## Expected Behavior
- A user on `@angular/build < 19.0.0`, `@rsbuild/core < 1.0.5`, or
`@rspack/core < 1.3.5` gets the standardized `Unsupported version of
<pkg> detected` error from the shared
`assertSupportedInstalledPackageVersion` helper rather than a downstream
crash.
- `@angular/build` moves from `dependencies` to `peerDependencies`,
matching how the rest of the first-party plugin ecosystem declares its
ecosystem-locked peers.
- `createAngularCompilation` receives `browserOnlyBuild` semantics that
match upstream Angular CLI: `true` when there is no server entry,
`false` when there is.
## Implementation Details
- Add `assertSupportedInstalledPackageVersion` to `@nx/devkit/internal`
for runtime contexts where no `Tree` is available. The helper coerces
the installed version before comparing so a valid prerelease of the
supported major (e.g. `19.0.0-rc.1`) isn't wrongly flagged as below
floor.
- Add runtime floor guards at the public entry points of both packages,
delegating to the shared helper.
- Drop the named `RspackError` import from `@rspack/core` in
`rspack-diagnostics`. The named export only exists at `>=1.4.0`, but the
declared peer floor is `>=1.3.5`; derive the type from
`Compilation['errors'][number]` instead so it works at every supported
version.
- Document the supported version window in the
`@nx/angular-rspack-compiler` introduction page.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nxc-4383-fb1ae6fe)
<!-- polygraph-session-end -->
---------
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
## Current Behavior
`@nx/storybook` did not enforce a multi-version support window:
- Generators silently fell through to the latest install constants on
workspaces below any supported floor (e.g., Storybook v6/v7 setups).
- `packageJsonUpdates` entries pushed cross-major Storybook bumps
unconditionally — a workspace on Storybook v7 running `nx migrate` got
its `@storybook/*` siblings silently bumped to v8 while the `storybook`
core package stayed at v7 (broken mixed state).
- The `convert-to-inferred` transformers branched only on `major === 8`,
so v9 / v10 workspaces could emit incompatible CLI flags.
- The peer range advertised Storybook v7, which is no longer
upstream-supported per Storybook's top-3-majors policy.
## Expected Behavior
Resolved support window for `@nx/storybook`: Storybook v8 / v9 / v10.
- Generators reject sub-floor workspaces with a clear, standardized
error before any tree write.
- `packageJsonUpdates` entries are source-major-gated, so workspaces
outside the source range are skipped instead of being silently pushed
cross-major.
- The `convert-to-inferred` transformers branch per supported major with
above-ceiling silent fallthrough.
- The peer range tightens to `>=8.0.0 <11.0.0`.
- A parameterized floor spec exercises every generator's floor assert.
## Implementation Details
Follows the canonical multi-version-compliance shape established by the
prior plugin compliance PRs:
- **Support window declarations** (`versions.ts`):
`minSupportedStorybookVersion = '8.0.0'`; per-major `versionMap` (8 / 9
/ 10) using the canonical bundle pattern; `versions(tree)` with
`versionMap[major] ?? latestVersions` above-ceiling fallthrough (no
above-ceiling throw).
- **Floor assert** (`assert-supported-storybook-version.ts`): thin
wrapper around `assertSupportedPackageVersion` from
`@nx/devkit/internal`. Invoked as the first statement in
`initGeneratorInternal`, `configurationGeneratorInternal`,
`convertToInferred`, `migrate9Generator`, and `migrate10Generator`. The
existing `migrate-8` generator is excluded — it is the sub-floor
migrator that lifts v6/v7 workspaces onto the v8 floor.
- **`keepExistingVersions: true`** at every generator-side
`addDependenciesToPackageJson` call (`init`, `configuration`,
`convert-to-inferred`, `ensure-dependencies`). `init/schema.json`
default flipped from `false` to `true`.
- **Migration gates**: source-major gates on
`packageJsonUpdates["20.2.0"]`, `["20.8.0"]`, and `["21.1.0"]`
(`storybook >=8.0.0 <9.0.0`); post-bump tier-1 gates on
`update-21-2-0-migrate-storybook-v9` and
`update-21-2-0-remove-addon-dependencies` (`storybook >=9.0.0 <10.0.0`).
- **`convert-to-inferred` transformers**: CLI prop mappings extended to
`v8` / `v9` / `v10` with a v10 fallback for above-ceiling. The CLI flag
set across these majors was verified identical against the bundled
Storybook v9.0.6 and v10.1.0 CLI sources.
- **v7 cleanup**: peer tightened to `>=8.0.0 <11.0.0`; removed
`Constants.uiFrameworks7`, `pleaseUpgrade()`, the v7-throw branches in
both executors and `configurationGeneratorInternal`, and the `<8.0.0`
react/react-dom install branch in `ensure-dependencies`.
- **Bug fix in `storybookMajorVersion`**: the previous implementation
called `semver.major()` directly on declared ranges (e.g. `^10.1.0`),
which throws — the function then caught the throw and returned
`undefined`, defeating downstream `major === 10` checks. Now coerces via
`semver.coerce` first, so version-aware branching in `configuration`,
`convert-to-inferred`, and `edit-root-tsconfig` works as written.
- **Parameterized floor spec** (`all-generators-enforce-floor.spec.ts`):
`subFloorVersion: '~7.6.0'`, `excludeGenerators: ['migrate-8']`.
Upgrade path for any remaining v7 holdouts: the explicit `nx g
@nx/storybook:migrate-8` generator (intentionally excluded from the
floor assert) drives Storybook's own `upgrade` flow, which is cumulative
— it handles v7 → latest in one shot via Storybook's CLI.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nxc-4406-441f917f)
<!-- polygraph-session-end -->
---------
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
When running `nx migrate --run-migrations`, the end-of-run `<K> commits
created` tally undercounts by 1 in the HEAD-resolve-race case: a
per-migration commit lands cleanly but `git rev-parse HEAD` returns null
transiently right after, so the sha is unrecoverable. The user sees `N-1
commits created` when `N` actually landed.
Additionally, when a migration throws mid-loop (before its outcome can
be recorded), the failure recap silently omits it from the
applied/deferred tally categories. The user still knows which migration
failed (the error block names it), but the recap accounting is
incomplete.
## Expected Behavior
The `<K> commits created` tally counts every commit that landed,
including HEAD-resolve-race cases. A migration that threw mid-loop is
recorded in the recap as `aborted` and listed under retained
working-tree state when it left a diff behind.
## Implementation Details
### Tally fix
The bug exists *because* the previous outcome record (`{ committedSha:
string | null, commitFailed?: boolean, committedAsPartOf?: ... }`)
collapsed two distinct states into one shape: `committedSha: null` with
no other flags covered both `commit landed, sha lost` (HEAD-race) and
`no commit attempted` (`--no-create-commits` or no-op step). No filter
expression over the previous shape can disambiguate them.
This PR encodes commit state as a tagged union:
```ts
type CommitState =
| { kind: 'none' }
| { kind: 'landed'; sha: string | null }
| { kind: 'failed' }
| { kind: 'absorbed'; into: { name: string; sha: string | null } };
```
The corrected tally is `outcomes.filter(o => o.commit.kind ===
'landed').length` — TS-enforced.
### Bundled refactors enabled by the union
- `MigrationOutcome` lifts to a discriminated union over `status`
(`'completed'` | `'aborted'`); ~30 lines of comments documenting legal
field combinations collapse to one short doc per variant.
- The `pendingMigrations` parallel list is dropped. `outcomes` is the
single source of truth; recap, tally, and retained-state derivations all
read from it. The executor's catch block records the in-flight migration
with `status: 'aborted'`, closing the gap where a
generator-throw-mid-loop migration silently disappeared from the recap's
tally.
- New `countLandedCommits` and `retainedMigrations` helpers replace
inline filters previously duplicated across `migrate.ts` and
`migrate-output.ts`, with unit tests covering the HEAD-race case and
verifying that the absorbing case is not double-counted.
- `logFailureRecap`'s merge-and-dedupe over outcomes + pendingMigrations
collapses to a single iteration over `outcomes`.
### Independent cleanups bundled
- `cleanup(repo)` — extend the `require-windows-hide` lint rule to
accept `MemberExpression` as the spawn options argument, matching its
existing handling of `Identifier` and `SpreadElement`.
- `cleanup(core)` — consolidate three identical XML blocks
(`<migration>`, `<handoff_path>`, `<advisory_context>`) across agentic
prompt builders into shared helpers in `shared-rendering.ts`.
Agent-visible prompt output byte-identical.
- `cleanup(core)` — drop the single-use `spawnOptions` hoist in
`agentic/runner.ts` (newly clean under the updated lint rule).
- `cleanup(core)` — explicit no-op `if (result.status === 'failed') {}`
branch with comment in `run-migration-process.js`, documenting the
single-migration UI child's intentional success-with-warning behavior.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nx-migrate-hardening-c5cd4e73)
<!-- polygraph-session-end -->
---------
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The [GitHub App
Permissions](https://nx.dev/docs/guides/nx-cloud/source-control-integration/github-app-permissions)
reference page lists `Administration: Read & Write` as a required
repository permission and documents an "Administration (write)" detail
section. This permission was originally requested so users could create
workspaces from the browser.
## Expected Behavior
The Nx Cloud team has removed the browser-based workspace creation
feature and dropped the `Administration` (read & write) permission from
the GitHub App. The docs now:
- Remove `Administration: Read & Write` from the required repository
permissions list.
- Remove the corresponding `Administration (write)` detail section.
- Add a `note` callout above "Required permissions" explaining that the
permission is no longer requested and that existing installations and
functionality are unaffected.
The organization-level `Administration: Read Only` permission is
intentionally left unchanged.
## Related Issue(s)
N/A
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/update-github-app-scope-docs-d0c654d4)
<!-- polygraph-session-end -->
Co-authored-by: Nicole Oliver <nicole.oliver.42@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Current Behavior
`nx migrate --run-migrations` can only run generator-based migrations.
Prompt-based and hybrid migrations cannot execute, and there is no way
to validate generator output with an AI agent.
## Expected Behavior
`nx migrate --run-migrations` can drive an installed AI agent (Claude
Code, OpenCode, Codex) across the full migration pipeline.
Capabilities added:
- `--agentic[=<agent-id>]` opts into agentic mode; auto-detects an
installed agent or accepts an explicit id.
- Prompt-only and hybrid migrations (generator + prompt phases) run
end-to-end, with generator output handed to the agent.
- `--validate` runs an optional agent validation step after each
generator-only migration.
- Inside-agent mode defers prompt and validation steps and surfaces them
as directives to the outer agent.
- Per-migration commits are soft-forced under `--agentic`; redesigned
end-of-run output reports applied / deferred / retained state and gives
an explicit recap on failures.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: leosvelperez <leosvelperez@users.noreply.github.com>
The current versions `yaml@2.8.0` and `brace-expansion@5.0.5` that are
packaged with `nx` have medium vulnerabilities reported.
<img width="793" height="229" alt="image"
src="https://github.com/user-attachments/assets/c53c4a8f-fae7-47f1-9aae-6edd765967c0"
/>
https://npmx.dev/package/nx
This PR updates them to the newest versions without reported
vulnerabilities.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`createTaskGraph` builds a `Task` object for every target it schedules.
For targets that do not declare a `cache` property the field is left as
`undefined`; this flows through the NAPI boundary as
`Option<bool>::None`
and is serialised to JSON as `null`.
Nx Cloud DTE V4 has a Kotlin filter:
```kotlin
depTask.cache != false // gate: "might this task produce an artifact?"
```
In Kotlin `null != false` is `true`, so every non-cacheable
`run-commands`
target (i.e. one that never declared `cache: true`) is incorrectly
treated
as potentially cacheable. The DTE dispatcher then tries to materialise
an
artifact that was never uploaded, and the distributed worker throws a
fatal:
> Task dependency not found while downloading artifacts
## Expected Behavior
`task.cache` is always a literal `true` or `false` — never `null` /
`undefined`.
Downstream consumers (Nx Cloud, third-party runners) can rely on a
concrete
value without treating `null` as a third state.
## Changes
### `packages/nx/src/tasks-runner/create-task-graph.ts` — one line
```diff
- cache: project.data.targets[target].cache,
+ cache: project.data.targets[target].cache ?? false,
```
The `??` operator coerces both `undefined` (field absent from config)
and
`null` to `false`, matching Nx's opt-in caching semantics: a target is
cacheable only when `cache: true` is explicitly set (directly or via
`targetDefaults`).
### `packages/nx/src/tasks-runner/utils.ts` — **untouched**
`isCacheableTask` keeps its original `!== undefined` guard exactly as it
is
on `master`. The legacy `cacheableOperations`/`cacheableTargets`
fallback
inside that function is now effectively unreachable from the
`createTaskGraph`
path (because `task.cache` is always a boolean), but it stays as
harmless
defensive code for any `Task` object constructed outside of
`createTaskGraph`.
In practice `cacheableOperations` is dead in every modern workspace: the
Nx 17 migration `use-minimal-config-for-tasks-runner-options` rewrites
each
entry to `targetDefaults.<target>.cache = true` and deletes the field,
so
no plumbing is needed.
### `packages/nx/src/tasks-runner/create-task-graph.spec.ts`
All 68 expected `Task` objects updated to include `cache: false`,
matching
the normalised output.
## Related Issue(s)
https://linear.app/nxdev/issue/NXC-4486/artifact-download-fails-for-missing-task-dependency
<!-- Nx Cloud DTE V4 distributed-worker regression — no public issue
number -->
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/upnx-c498c-662192f8)
<!-- polygraph-session-end -->
---------
Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
We've removed the Nx Cloud in-app CNW flow. This PR removes references
to it from the docs, and directs users to run `npx create-nx-workspace`
from a terminal instead.
Several astro-docs pages link to
`https://cloud.nx.app/create-nx-workspace` (some with framework-specific
subpaths like `/typescript/github`, `/angular/github`, `/react/github`).
## Expected Behavior
These links now point to `https://cloud.nx.app/get-started` or `npx
create-nx-workspace`. Framework-specific subpaths were dropped, and
existing UTM parameters were retained.
Files updated:
- `astro-docs/src/content/docs/features/CI
Features/github-integration.mdoc`
-
`astro-docs/src/content/docs/getting-started/Tutorials/typescript-packages-tutorial.mdoc`
-
`astro-docs/src/content/docs/getting-started/Tutorials/angular-monorepo-tutorial.mdoc`
-
`astro-docs/src/content/docs/getting-started/Tutorials/react-monorepo-tutorial.mdoc`
-
`astro-docs/src/content/docs/getting-started/Tutorials/self-healing-ci-tutorial.mdoc`
## Related Issue(s)
N/A
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/update-links-to-cnw-1f738cb4)
<!-- polygraph-session-end -->
Co-authored-by: Nicole Oliver <nicole.oliver.42@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Updating the style guide based on feedback on the latest post.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
## Current Behavior
`@nx/vitest`, `@nx/cypress`, `@nx/playwright`, and `@nx/vite` all build
to the workspace-root `dist/packages/<name>/` directory and publish from
there. They use the legacy `module: commonjs` / `moduleResolution: node`
configuration, an `exports` map with a `./src/*` wildcard
(vitest/cypress), and a `nx-release-publish.options.packageRoot` that
points at the workspace-root dist tree. This is the same shape
`@nx/devkit`, `@nx/nx`, `@nx/js`, `@nx/eslint`, and `@nx/jest` already
moved off — but the M2 group of testing/build packages was still on the
old layout, blocking downstream packages (like `@nx/web`) from
migrating.
Separately, the 23.0.0 `rewrite-internal-subpath-imports` codemods
shipped with `@nx/js`, `@nx/jest`, `@nx/eslint` (and the new one in this
PR for `@nx/cypress`) walk `require(...)` / dynamic `import(...)` /
`jest.mock(...)` call expressions but leave `typeof
import('@nx/<name>/src/x')` type queries untouched. The common
typed-runtime-require idiom
```ts
const m = require('@nx/<name>/src/x') as typeof import('@nx/<name>/src/x');
```
would have its runtime arg rewritten to `/internal` while the type arg
kept pointing at the now-removed `./src/*` wildcard — leaving external
consumers with a TS error after running the migration.
## Expected Behavior
Each of the four packages now builds to `packages/<name>/dist/` with
`module: nodenext` + composite TypeScript, ships a `files` field in
`package.json` listing what to publish, declares
`release.preserveLocalDependencyProtocols: true` +
`manifestRootsToUpdate: ["packages/{projectName}"]` in `project.json`,
and publishes straight from the package directory via
`nx-release-publish.options.packageRoot: packages/{projectName}`.
Per-package highlights:
- **`@nx/vitest`** — straight structural migration. No
`@nx/vitest/src/*` consumers, so no codemod migration shipped.
`assets.json` updated to glob `src/migrations/**/*.md` so prompt-form
migrations resolve from `./dist/src/...`.
- **`@nx/cypress`** — drops the `./src/*` wildcard from the exports map,
ships a curated `internal.ts` re-export entry, and ships a
`23.0.0-beta.17` symbol-aware codemod
(`rewrite-internal-subpath-imports`) that routes `@nx/cypress/src/*`
imports to either the public `@nx/cypress` entry (for
`configurationGenerator`, `componentConfigurationGenerator`,
`cypressInitGenerator`, `migrateCypressProject`) or
`@nx/cypress/internal` (for everything else). 10 first-party consumers
in `@nx/angular` / `@nx/react` / `@nx/next` / `@nx/web` are codemodded
to match.
- **`@nx/playwright`** — straight structural migration; existing exports
already enumerated explicit subpaths, no `./src/*` wildcard to drop.
- **`@nx/vite`** — structural migration + three executor `.impl.ts`
files switched from `const schema = await import('./schema.json')` to a
top-level `import schema from './schema.json'` (required under
`nodenext`). Kept `./plugins/nx-tsconfig-paths.plugin` /
`./plugins/nx-copy-assets.plugin` /
`./plugins/rollup-replace-files.plugin` as explicit public entries
because storybook / react-native templates bake them into generated user
vite configs.
The fourth commit fixes the `typeof import()` blind spot across **all
four** of the 23.0.0 subpath-rewrite codemods (`@nx/js`, `@nx/jest`,
`@nx/eslint`, the new `@nx/cypress` one) by walking `ImportTypeNode` and
rewriting the literal-type-node argument when it points into the
package's `src/`. The cypress spec also adds explicit coverage for
`componentConfigurationGenerator` as a public-routed symbol, default and
default-plus-named imports, `jest.mock(..., factory)`, and
`it.each(MOCK_HELPER_METHODS)` for both the jest and vi mock families —
drift between those hardcoded sets and the real public/mock surface was
the most plausible future silent-regression path. The
dist-build-migration skill is updated to document the `ImportTypeNode`
handling and the expanded spec checklist.
## Validation
- `pnpm nx run-many -t build-base -p
angular,nuxt,remix,vue,web,react,vitest,cypress,playwright,vite,jest,js,eslint`
✅
- `pnpm nx affected -t build,lint --base=origin/master` ✅ (53 projects,
103 tasks).
- `pnpm nx run-many -t test -p jest,js,eslint,cypress
--testPathPatterns="rewrite-internal-subpath-imports"` ✅ — 99 passing
(32 cypress, 27 eslint, 20 jest, 20 js).
`@nx/web` is now unblocked on 4 of its 5 prior dist-build dependencies
(`@nx/vitest`, `@nx/cypress`, `@nx/playwright`, `@nx/vite`); only
`@nx/webpack` remains on the old layout.
## Related Issue(s)
Linear: [NXC-3581](https://linear.app/nxdev/issue/NXC-3581) — M2 epic,
migrate testing/build packages to local dist.
<details>
<summary>Pre-create review (run before PR open)</summary>
### Critical
- (Fixed in this PR) Codemod skipped `typeof
import('@nx/<name>/src/...')` type queries — backported the fix to
`@nx/js`, `@nx/jest`, `@nx/eslint`, and the new `@nx/cypress` migration.
- (Fixed in this PR) Spec didn't exercise
`componentConfigurationGenerator` (the 4th public symbol), default
imports, or `jest.mock` / `vi.mock` proper.
### Important
- `@nx/vite` / `@nx/vitest` / `@nx/playwright` ship no codemod despite
dropping their `./src/*` wildcards. No first-party consumers exist;
external plugin authors using deep subpaths will hit breakage. Noted as
a known breaking change for the upgrade guide rather than fixed in this
PR — codemods can land in a follow-up if the surface turns out to
matter.
- Empty `try { ... } catch {}` in `cypress-version.ts` (pre-existing;
diff only touched JSDoc). Tracked for a follow-up.
### Suggestions
- Vite executor schema imports are now eager at module load (theoretical
risk; matches the jest precedent).
- `vite/project.json` and `playwright/project.json` omit `dependsOn:
["^build", "build-base"]` while cypress/vitest include it (covered by
`nx.json` targetDefaults, cosmetic).
- `cypress` dropped legacy aliases `./generators` / `./executors` /
`./migrations` (no `.json` suffix) from the exports map — undocumented
surface, low risk.
</details>
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
15 inferred plugins call `calculateHashForCreateNodes` once per project
inside their `createNodesInternal` callback. The single-project helper
triggers a workspace-context glob per project, even when many projects
are being processed in the same `createNodesV2` invocation that all
share `context.workspaceRoot`. There is already a batch helper,
`calculateHashesForCreateNodes`, that runs a single multi-glob hash
across all project roots — only `vite`, `vitest`, `docker`, and `maven`
use it today. `nuxt` even has a `TODO(@nrwl/nx-vue-reviewers): This
should batch hashing like our other plugins` comment to that effect.
In the same per-project path, several plugins also re-invoke
`getLockFileName(detectPackageManager(context.workspaceRoot))` once per
project, which probes the lockfile redundantly.
## Expected Behavior
Each affected plugin's `createNodes` callback now does the per-workspace
work upfront:
- Pre-filter `configFiles` into `validConfigFiles` + `projectRoots`
(parallel arrays) using the existing sibling-file / metro / expo /
remix-compiler checks.
- Detect the package manager once and derive `pmc` and `lockFileName`
from it.
- Call `calculateHashesForCreateNodes(projectRoots, options, context,
additionalGlobsByProject)` once.
- Pass the pre-computed hash by index into `createNodesInternal` (4th
`idx` arg from `createNodesFromFiles`).
Plugins migrated:
- `angular`, `cypress`, `detox`, `expo`, `gradle` (v1 + v2 nodes),
`next`, `nuxt`, `playwright`, `react-native`, `remix`, `rollup`,
`rsbuild`, `storybook`, `webpack`.
`gradle`'s exported `makeCreateNodesForGradleConfigFile` factory gains
an optional `hashes?: string[]` parameter so external callers retain the
previous single-shot behavior; when a `hashes` array is supplied, each
callback invocation picks `hashes[idx]`. `nuxt`'s TODO comment is
removed since the batching is now in place.
`rspack` was not migrated — it does its own hashing with `hashFile` +
`hashArray` + `hashObject` and never used `calculateHashForCreateNodes`.
For `playwright`, the `additionalGlobs` array is per-project (each
project's `externalTsconfigInputs`), so the call passes
`projectRoots.map((_, idx) => [lockFileName,
...externalTsconfigInputsByIdx[idx]])`.
All affected plugin specs (cypress, next, storybook, rsbuild, angular,
playwright, webpack, rollup, nuxt, remix, gradle v1 + v2) pass locally.
## Related Issue(s)
N/A — this is an internal refactor / performance cleanup, not a fix for
a reported issue.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Repo pins pnpm 10.28.2. @nx/js's typescript inference plugin
bare-requires 'typescript' without declaring it, relying on Node's
resolver walking up into the workspace's node_modules. Under pnpm 11's
enableGlobalVirtualStore, the plugin's real path sits outside the
workspace tree and resolution fails with MODULE_NOT_FOUND.
Also, pnpm 11 no longer reads the `pnpm` field in package.json.
## Expected Behavior
- pnpm bumped to 11.2.2 across package.json and CI workflows.
- @nx/js inference plugin resolves typescript from workspaceRoot —
layout-agnostic across pnpm classic, global virtual store, npm, yarn.
- pnpm.overrides moved into pnpm-workspace.yaml; allowBuilds configured
to preserve prior onlyBuiltDependencies policy. Lockfile regenerated.
## Related Issue(s)
Fixes NXC-4432
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`pnpm install` will happily resolve and install package versions that
were published seconds ago. This is the supply-chain attack window —
when a popular package gets compromised (e.g. the recent `node-ipc`
incident), any CI/dev install during
that window can pick up the malicious version before the registry /
community has a chance to react.
## Expected Behavior
`pnpm install` enforces a minimum release age of 1 day (1440 minutes),
so packages published within the last 24 hours are not eligible to be
installed. Nx-published packages (`nx`, `@nx/*`, `@nrwl/*`,
`create-nx-workspace`, `create-nx-plugin`)
are excluded so freshly released Nx packages don't block installs or e2e
flows.
Configured via `pnpm-workspace.yaml`:
```yaml
minimumReleaseAge: 1440
minimumReleaseAgeExclude:
- nx
- '@nx/*'
- '@nrwl/*'
- create-nx-workspace
- create-nx-plugin
```
## Related Issue(s)
Refs
[NXC-4466](https://linear.app/nxdev/issue/NXC-4466/set-minimum-release-age-in-ci-with-nx-exclusions)
## Current Behavior
The macOS e2e job in ci.yml runs `pnpm install` but never restores .NET
packages, so any e2e test that triggers the `@nx/dotnet` plugin's
inferred `build` target (which uses `--no-restore`) fails with
NETSDK1004 (missing `project.assets.json`).
## Expected Behavior
Run `dotnet restore nx.sln` after `pnpm install` in the macOS e2e job,
matching what `main-linux` already does.
## Related Issue(s)
N/A
## Current Behavior
`@nx/cypress`'s `getInstalledCypressVersion` helper in
`packages/cypress/src/utils/versions.ts` open-codes the dist-tag
normalization logic — `getDependencyVersionFromPackageJson` + an
explicit `installedVersion === 'latest' || 'next'` check + a `clean(...)
?? coerce(...)?.version ?? null` chain. This duplicates the centralized
`getDeclaredPackageVersion` helper in `@nx/devkit/internal`, which
already handles the dist-tag list (`NON_SEMVER_DIST_TAGS`) and semver
cleaning (`normalizeSemver`).
The duplication creates a drift surface: the local copy hardcodes
`'latest'` / `'next'` and would silently miss new entries if
`NON_SEMVER_DIST_TAGS` grows.
## Expected Behavior
`getInstalledCypressVersion` calls `getDeclaredPackageVersion(tree,
'cypress')` directly, matching the consolidated shape in `@nx/rspack`
(`packages/rspack/src/utils/version-utils.ts`) and `@nx/rsbuild`
(`packages/rsbuild/src/utils/version-utils.ts`). Local `clean` /
`coerce` / `getDependencyVersionFromPackageJson` usage in `versions.ts`
is removed.
The multi-version compliance skill (`canonical-shape.md`,
`anti-patterns.md`) is updated to document the consolidated shape and
the deliberate decision to omit `getDeclaredPackageVersion`'s third arg.
## Implementation Details
### Third arg (`latestKnownVersion`) omitted
`getDeclaredPackageVersion`'s third arg falls back to
`normalizeSemver(latestKnownVersion)` whenever the declared range can't
be normalized to semver — both "package missing from `package.json`" AND
"package declared as a dist tag". The helper does not distinguish those
two cases.
Cypress's open-coded helper distinguished three cases (missing → `null`,
dist tag → cleaned `cypressVersion`, valid → normalized declared).
Passing the third arg would preserve dist-tag back-compat but break the
"missing" case — init generators would think cypress was always
installed and never add it to `devDependencies`. Omitting it matches the
rspack/rsbuild precedent: both "missing" and "dist tag" return `null`;
consumers that want `?? latestVersions` semantics encode it at the call
site.
### Consumer behavior under dist-tag declarations (`"cypress": "latest"
| "next"`)
| Consumer | Before | After | Net effect |
|---|---|---|---|
| `versions(tree)` | `versionMap[15] ?? latestVersions = latestVersions`
| early-returns `latestVersions` on `null` | Same return value. |
| `init.ts` updateDependencies | skips adding cypress to devDeps | adds
cypress to devDeps, then `keepExistingVersions: true` preserves the
existing `latest` pin | Same workspace file output. |
| `configuration.ts` / `component-configuration.ts` skip-init guards |
skip init | run idempotent init | Extra setup work; idempotent for
already-configured workspaces. |
| `migrate-to-cypress-11.ts:122` `>= 10` guard | short-circuits with
"already on v10+" | proceeds into the migration body | Sub-floor
migrator (excluded from floor enforcement); now runs its
`forEachExecutorOptions` iteration on dist-tag declarations. |
The `assertMinimumCypressVersion(8)` call at
`migrate-to-cypress-11.ts:121` is unaffected (no tree → FS path).
For workspaces with a pinned semver range or with cypress missing
entirely from `package.json`, no observable behavior change at any
consumer.
### Skill doc updates
- `canonical-shape.md` §"The `getInstalled<Pkg>Version(tree?)` helper":
replaced the open-coded example with the consolidated form; added
§"Dist-tag semantics — third arg" explaining the missing-vs-dist-tag
conflation and recommending omitting the third arg.
- `anti-patterns.md` §1: added "open-coded tree-branch in
`getInstalled<Pkg>Version(tree?)`" to the rejected-patterns list with
the consolidated cypress shape as the "do instead".
> [!NOTE]
> `@nx/jest` and `@nx/vitest` still carry the open-coded shape on
master. Both are tracked in-flight via separate multi-version compliance
work and are out of scope here.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/misc-compliance-fixes-91356768)
<!-- polygraph-session-end -->
---------
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`@nx/esbuild` does not enforce its declared `esbuild` peer range
(`>=0.19.2 <1.0.0`) at generator entry points. Generators silently
proceed on workspaces with a sub-floor `esbuild` and overwrite a
user-pinned `esbuild` version on re-runs (the `init` generator's
`keepExistingVersions` defaults to `false`).
## Expected Behavior
Generators throw a clear, standardized error naming the package, the
installed version, and the supported floor when `esbuild` is below
`0.19.2`. User-pinned `esbuild` versions are preserved by default.
## Implementation Details
- Add `minSupportedEsbuildVersion = '0.19.2'` to
`packages/esbuild/src/utils/versions.ts`.
- Add `assertSupportedEsbuildVersion(tree)` wrapper around the shared
`assertSupportedPackageVersion` from `@nx/devkit/internal`.
- Call the assert as the first statement in `esbuildInitGenerator` and
`configurationGenerator`.
- Flip `init/schema.json` `keepExistingVersions.default` to `true` and
use `schema.keepExistingVersions ?? true` at the
`addDependenciesToPackageJson` call site so user-pinned versions are
preserved by default.
- Add `all-generators-enforce-floor.spec.ts` exercising every
generator's floor assert via the shared
`assertGeneratorsEnforceVersionFloor` helper.
The executor was reviewed for cross-version API divergence; the
`esbuild` APIs used (`build`, `context`, `BuildOptions`, `BuildResult`,
`PluginBuild.onEnd`, and the forwarded options) are stable across the
supported range, so no runtime version branching is added.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nxc-4386-dda40801)
<!-- polygraph-session-end -->
---------
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Publish workflow fails when building MsbuildAnalyzer because the
`@nx/dotnet` plugin's inferred `build` target runs
`dotnet build --no-restore --no-dependencies --configuration Release`,
but no restore has happened, so
`project.assets.json` is missing:
error NETSDK1004: Assets file
'.../packages/dotnet/analyzer/obj/project.assets.json' not found.
## Expected Behavior
Run `dotnet restore nx.sln` once in the publish job (right after `pnpm
install`) so all .NET projects have assets files
before any inferred build runs with `--no-restore`.
## Related Issue(s)
N/A
## Current Behavior
The Angular vitest generators added `@oxc-project/runtime` to the user's
`devDependencies` on **both** the vitest-angular and vitest-analog
paths, with a comment claiming `@angular/build`'s rolldown usage emits
external `@oxc-project/runtime/helpers/*` imports. That claim doesn't
match `@angular/build`'s source: its vitest builder sets
`optimizeDeps.noDiscovery: true` plus an in-memory test provider, so no
rolldown pre-bundling against `@angular/*` runs on that path.
Separately, `addVitestAngular`/`addVitestAnalog` silently dropped the
`GeneratorCallback`s returned by `addDependenciesToPackageJson` and
`@nx/vitest`'s `configurationGenerator`. Install still ran in practice
because the parent application/library generators call
`installPackagesTask` separately, but the wiring was incorrect and any
caller that didn't double-call install would lose the post-add hooks.
## Expected Behavior
- `addVitestAngular` no longer adds `@oxc-project/runtime` (it's not
needed on that path).
- `addVitestAnalog` continues to add `@oxc-project/runtime`, with a
corrected comment that accurately attributes the cause.
- Both helpers return proper `GeneratorCallback`s; the application and
library generators chain them into their task lists.
- The matching `update-23-0-0` migration AI-instructions section is
scoped to the vitest-analog path with the corrected explanation.
## Why the dep is needed on the vitest-analog path
`@nx/vitest`'s `configurationGenerator` (for `uiFramework: 'angular'`)
registers `@analogjs/vite-plugin-angular`'s `angular()`. In test mode,
analog additionally registers an `angularVitestPlugin` whose `transform`
hook matches `@angular/*` `fesm2022` modules containing `async ` (plus
any `@angular/cdk` file) and calls:
```ts
vite.transformWithOxc(code, id, { target: 'es2016', … })
```
The downlevel is deliberate. The plugin source comments it as
*"downlevels any dependencies that use async/await to support zone.js
testing and tests w/fakeAsync"* — Zone.js relies on monkey-patching
promise scheduling for `fakeAsync` and friends, which it cannot do
against native `async`/`await`, so the plugin lowers them to a form
Zone.js can intercept.
With `target: 'es2016'`, oxc emits the helpers as external
`@oxc-project/runtime/helpers/*` imports (oxc's default `HelperMode =
'Runtime'`). Nothing in the upstream chain
(`@analogjs/vite-plugin-angular`, `@angular/core`, `vite`, `rolldown`)
declares `@oxc-project/runtime` in a way that's resolvable from the
consumer's workspace, so `vite:import-analysis` fails to resolve those
imports unless the dep is added explicitly. This behavior is unchanged
through analog `3.0.0-alpha.54` (latest at time of writing).
## Why the dep is NOT needed on the @angular/build path
- `@angular/build:unit-test` (and `@nx/angular:unit-test` for libraries)
bypasses analog entirely.
- It sets `optimizeDeps.noDiscovery: true` and uses an in-memory test
provider, so no rolldown pre-bundling runs against `@angular/*`.
- No `angularVitestPlugin` is loaded → no `target: 'es2016'` downlevel →
no `@oxc-project/runtime/helpers/*` imports emitted.
## Implementation Details
- `addVitestAngular`/`addVitestAnalog` return
`Promise<GeneratorCallback>`; the application and library generators
chain them through `runTasksInSerial(...)` so the install-packages and
configuration callbacks actually run through the generator pipeline.
- `@oxc-project/runtime` is added only by `addVitestAnalog`, with the
comment now describing the actual mechanism (analog's
`angularVitestPlugin` + `transformWithOxc({ target: 'es2016' })` for
Zone.js compatibility).
- `update-23-0-0/ai-instructions-for-vite-8.md` section 3 rewritten with
the corrected mechanism, scoped to the vitest-analog path. Detection:
`rg '"@nx/vitest:test"' --type json` + `rg
'@analogjs/vite-plugin-angular' --type ts --type js`.
- e2e: new case in `projects-build-and-test.test.ts` opts into
vitest-angular explicitly (app w/ `--bundler=esbuild`, lib w/
`--buildable`) and runs `nx test` against both. The existing test
exercises vitest-analog implicitly through `app1` (webpack) →
`setGeneratorDefaults` writes `unitTestRunner: vitest-analog` to
`nx.json`, locking subsequent generations to that runner regardless of
per-project defaults.
## Verification
- Reproduced the failure in an Nx e2e-generated workspace: with
`@oxc-project/runtime` absent, `nx run <lib>:test` fails at
`vite:import-analysis` trying to resolve
`@oxc-project/runtime/helpers/defineProperty` from
`@angular/core/fesm2022/testing.mjs`. The on-disk `testing.mjs` does
**not** contain those imports — they are injected in-memory by analog's
`angularVitestPlugin.transform`. Installing the dep makes the test pass.
- Confirmed the mechanism against analog plugin source in versions
`2.1.3`, `2.5.1`, and `3.0.0-alpha.54` — the `target: 'es2016'`
downlevel is unchanged.
- The new e2e covers the inverse: vitest-angular runs `nx test`
successfully without relying on `@oxc-project/runtime` for the path.
High confidence in the root cause and the path-scoped fix.
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
## Current Behavior
PR #35631 added a "collision guard" in `resolveSubpathFromExports`:
after resolving a subpath with the workspace's custom conditions, it
calls `resolve.exports` a second time with `conditions: []`. If both
calls return the same path, it assumes the match came from a
`default`/`import`/`require` fallback (i.e. a dist artifact) and
hard-fails via `throwUnresolvableLocalPluginError`.
This false-positives on local packages whose `exports` map points all
conditions at source — there's no dist to silently load, but the second
call collides with the first and the loader refuses to resolve.
Real-world example (from `nrwl/ocean`):
```json
"./plugin": {
"types": "./src/plugin/index.ts",
"import": "./src/plugin/index.ts",
"default": "./src/plugin/index.ts"
}
```
`pnpm nx sync:check` fails with:
> the package's 'exports' entry for './plugin' does not declare a
resolvable source-pointing condition recognized by Nx.
## Expected Behavior
- Source-pointing custom condition wins when one is declared and
matches.
- Otherwise, whatever `resolve.exports` returns is used as long as the
file exists on disk.
- The loader only hard-fails when nothing resolves at all (existing
`throwUnresolvableLocalPluginError` path when both source resolution and
`require.resolve` fail).
## Changes
- `packages/nx/src/project-graph/plugins/resolve-plugin.ts`
- Removed the second `resolve.exports` call and the equality check that
returned `null` on collision.
- Removed the now-unused `getRootTsConfigCustomConditions` import.
- Simplified the subpath branch of `throwUnresolvableLocalPluginError` —
the message no longer instructs users to add a custom condition; it now
just reports that the subpath has no resolvable entry or file on disk.
- `packages/nx/src/project-graph/plugins/resolve-plugin.spec.ts` (new)
- Unit tests covering: custom source condition wins;
types/import/default-only resolves to source (regression for this PR);
guided error when the matched file doesn't exist; guided error when the
subpath has no exports entry.
- `e2e/plugin/src/nx-plugin-ts-solution.test.ts`
- Updated the PR #35631 e2e test "should not load local plugin subpath
imports from dist" — that restriction is intentionally lifted; the test
now verifies a dist-only subpath export loads successfully.
Related: #35631
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/upnx-aab34-f5a75715)
<!-- polygraph-session-end -->
---------
Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Directory.Build.props and similar files are reported as sandbox
violations if they would be read by the .NET CLI commands
## Expected Behavior
They are considered in inputs
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
`@nx/js` has several multi-version compliance gaps:
- The `20.7.1-beta.0` and `22.5.0` migrations bump `@swc/cli` across
SemVer-breaking 0.x minor boundaries without a source-range `requires`
gate. Workspaces that missed an intermediate bump (e.g. via
`--mode=first-party`) can be jumped to the target lane in one write,
skipping the bridge. The `22.5.0` entry also mixes the cross-minor
`@swc/cli` jump with same-major patches under one combined gate.
- No migration path past `@swc/cli@^0.7.10` exists, so migrated
workspaces stay behind the fresh-install constant (`~0.8.0`).
- `@swc/cli` is not declared as a peer dependency, so the supported
window isn't visible from `package.json` and the SWC executor doesn't
surface an unmet-peer signal.
- Generators silently fall through to the latest install constants when
TypeScript is below the supported floor — no floor assert.
- Several `addDependenciesToPackageJson` call sites (`init`, `library`,
`convert-to-swc`, `setup-verdaccio`, SWC helpers) don't pass
`keepExistingVersions: true` and can overwrite pinned versions; `init`'s
schema defaults the flag to `false`.
## Expected Behavior
- `@swc/cli` migration entries gate on their source range; the
previously-mixed `22.5.0` entry is split so same-major patches still
apply on any same-major source.
- A `23.0.0-swc-cli` migration bridges `~0.7.x` to `~0.8.0`.
- `package.json` declares `@swc/cli: ">=0.6.0 <0.9.0"` as an optional
peer (via `peerDependenciesMeta`).
- All generators throw a clear "Unsupported version" message when
TypeScript is below `5.4.0`, naming the package, installed version, and
supported floor.
- Generators preserve user-pinned versions; `init`'s schema defaults
`keepExistingVersions` to `true`.
## Implementation Details
- Added bilateral `requires: { "@swc/cli": ">=N <M" }` gates on the
cross-breaking entries. `nx migrate`'s `filterDowngradedUpdates` already
handles past-target downgrades natively, so the gates are load-bearing
for stepwise chaining: a workspace at `0.3.12` running `nx migrate
--mode=all` against a newer Nx won't have the latest entry jump it
directly to `~0.8.0` — it must catch up stepwise via `nx migrate
--mode=third-party`, which walks each gated bridge in order.
- Split `22.5.0`: ungated entry keeps
`@swc/core`/`@swc/helpers`/`@swc-node/register` patches; new
`22.5.0-swc-cli` gates the `@swc/cli` jump separately so the patch bumps
still apply on workspaces outside the `@swc/cli` source range.
- Added `packages/js/src/utils/assert-supported-typescript-version.ts`
delegating to `assertSupportedPackageVersion` from
`@nx/devkit/internal`. Called as the first statement of
`initGeneratorInternal`, `libraryGeneratorInternal`,
`convertToSwcGenerator`, `setupVerdaccio`, `setupBuildGenerator`,
`setupPrettierGenerator`, and `syncGenerator`.
- Simplified `initGeneratorInternal`: removed the now-redundant
`getInstalledTypescriptVersion` helper and
`!satisfies(supportedTypescriptVersions, ...)` check. The floor assert
covers sub-floor; `addDependenciesToPackageJson` with
`keepExistingVersions: true` covers user-pin preservation.
- Renamed `supportedTypescriptVersions = '>=5.4.0'` to
`minSupportedTypescriptVersion = '5.4.0'` in `versions.ts` to match the
canonical floor-constant shape consumed by
`assertSupportedPackageVersion`. Constant was not exported from
`@nx/js/internal` so no public-API impact.
- `typescript` is intentionally **not** declared as a peer dep in this
PR. With a peer cap of `<7.0.0`, pnpm auto-resolves `@nx/js`'s
typescript subgraph to the highest matching version (e.g. `6.0.x`) even
when the workspace pins a direct `typescript: ~5.9.2`, so the `@nx/js`
executor loads a different typescript than the workspace `tsc`. Capping
at `<6.0.0` avoids that but warns users already on TS 6 + TS-solution.
Resolving implicitly (no peer declared, same as pre-compliance) keeps
the executor on the user's workspace typescript and matches prior
behavior. Deferred to a follow-up that resolves the subgraph divergence
properly.
- Added `all-generators-enforce-floor.spec.ts` (parameterized via
`assertGeneratorsEnforceVersionFloor`) and
`assert-supported-typescript-version.spec.ts` (5 canonical cases:
sub-floor / fresh-install / `latest` / `next` / in-range).
- Extended `assertGeneratorsEnforceVersionFloor` to strip a leading
`./dist/` from `generators.json` factory paths so plugins built to local
dist (like `@nx/js`, and `@nx/jest` going forward) load factories from
source under Jest. Backward-compatible — non-dist plugins are
unaffected.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`graph/migrate/jest.config.cts` and `graph/client/jest.config.cts` build
their config by spreading `...nxPreset` and then re-declaring
`modulePathIgnorePatterns`:
```js
module.exports = {
...nxPreset,
// ...
modulePathIgnorePatterns: [
'/graph/client/src/app/machines/match-media-mock.spec.ts',
],
};
```
Object spread is a shallow merge, so this **replaces** the preset's
`modulePathIgnorePatterns` (`['<rootDir>/dist/', '<rootDir>/out-tsc/']`)
rather than extending it. With `dist/` no longer ignored,
`jest-haste-map` crawls each project's own build output. After
`typecheck`/`build` emits declaration files into the project's `dist/`,
the `test` target reads those `.d.ts` files (they match the `ts` module
extension), producing **undeclared-input sandbox violations**.
Observed in a sandbox report for `graph-migrate:test`: 15 unexpected
reads, all `graph/migrate/dist/src/**/*.d.ts`, read by `jest-worker`
processes during the haste-map crawl — even though the only spec
(`machine.spec.ts`) imports none of those files.
Note: configs that use jest's native `preset:` field (e.g.
`packages/devkit`, `packages/gradle`) are unaffected, because jest's
`mergeOptionWithPreset` concatenates `modulePathIgnorePatterns` from the
preset. Only the configs that inline the preset via object spread were
affected.
## Expected Behavior
The preset's `modulePathIgnorePatterns` are preserved, so
`jest-haste-map` keeps ignoring `<rootDir>/dist/` (and
`<rootDir>/out-tsc/`) and never reads the project's own build output
during tests. The fix spreads the preset's patterns ahead of the
project-specific one:
```js
modulePathIgnorePatterns: [
...(nxPreset.modulePathIgnorePatterns ?? []),
'/graph/client/src/app/machines/match-media-mock.spec.ts',
],
```
Verified: both configs now resolve to `['<rootDir>/dist/',
'<rootDir>/out-tsc/', '/graph/client/.../match-media-mock.spec.ts']`,
and `graph-migrate:test` still passes (11/11).
## Related Issue(s)
N/A — surfaced by an Nx Cloud sandbox report.
## Current Behavior
Nx has no shell tab-completion. Users have to type full command,
project, target, and generator names from memory.
## Expected Behavior
After installing the generated completion script, users get instant
tab-completion for:
- **Commands**: `nx <TAB>` lists every command (`run`, `generate`,
`add`, ...) **plus** every infix target name found in the project graph
(`build`, `serve`, `compile`, `bundle-rsc`, ...).
- **Subcommands**: `nx show <TAB>` → `project`, `projects`, `target`
- **Projects**: `nx show project <TAB>` shows all workspace projects
- **`project:target` pairs**: `nx show target <TAB>` and `nx run <TAB>`
first complete project names with a trailing `:`, then a second TAB
lists that project's targets — no backspace + manual `:` needed
- **Target names**: `nx run-many -t <TAB>` / `nx affected -t <TAB>`
- **Infix target invocations**: `nx build <TAB>` shows projects that
actually have the target. Works for **any** target name in the graph,
not a hardcoded set.
- **`nx show target <project>:<target>` keywords**: `nx show target
i<TAB>` → `inputs`, `outputs`. Also `nx show target inputs <TAB>` / `nx
show target outputs <TAB>` complete project:target.
- **Generators**: `nx g <TAB>` lists plugins (with trailing `:` for
two-stage drilling) **and** the bare generator names so `nx g app<TAB>`
→ `application`. `nx g @nx/react:<TAB>` lists that plugin's generators.
Workspace-local plugin projects (a `libs/my-plugin` with its own
`generators.json`) are discovered alongside installed npm plugins.
- **`nx add`**: `nx add @nx/r<TAB>` lists the first-party `@nx/*` set
(`@nx/react`, `@nx/rspack`, `@nx/remix`, ...).
- **Descriptions**: shells that render them (zsh, fish) get
`value\tdescription` pairs in the menu. bash and powershell get bare
names — bash has no description protocol in `compgen -W`; powershell
uses the single-arg `CompletionResult` ctor.
### Installation
```sh
nx completion bash >> ~/.bashrc
nx completion zsh >> ~/.zshrc
mkdir -p ~/.config/fish/completions && nx completion fish > ~/.config/fish/completions/nx.fish
nx completion powershell | Out-File -Append $PROFILE
```
Zsh also requires `autoload -U compinit && compinit` above the nx block;
the generated script prints a friendly warning pointing to this if
`compdef` isn't loaded. Fish's `>` redirect doesn't auto-create parent
directories, hence the `mkdir -p`.
### Debugging
If completion produces no suggestions, set `NX_VERBOSE_LOGGING=1` and
press TAB again. The wrappers stop discarding completion `stderr` when
the flag is set, and the binary's `catch` surfaces the underlying error
there. Reuses Nx's existing verbose-logging knob — no
completion-specific env var.
## Implementation notes
- **Pure JS, no native binary.** The earlier Rust prototype is dropped
in favor of a JS completion handler that reads
`.nx/workspace-data/project-graph.json` directly.
- **Bin-level short-circuit.** `bin/nx.ts` intercepts at the top of
`main()`:
- When `NX_COMPLETE=<shell>` is set (per-TAB request from the wrapper),
it skips workspace-root detection, dotenv, daemon, native module load,
and the yargs command tree, then dispatches to the value /
command-surface completion paths.
- When `nx completion <shell>` (the install command) is invoked, the
same hoisted short-circuit prints the wrapper script and exits — no
further bootstrap.
This is required because `parserConfiguration({ 'strip-dashed': true })`
defeats yargs' own completion detection, otherwise causing the `$0`
infix handler to fire and build the full project graph (~3s cold,
spawning ~25 plugin workers). The hoisting also means no scattered
`argv[2] === 'completion'` special cases downstream.
- **Wrappers as plain files.** The four shell wrappers (`bash.sh`,
`zsh.zsh`, `fish.fish`, `powershell.ps1`) live as real files under
`packages/nx/src/command-line/completion/scripts/`. `scripts.ts` is a
thin loader. Editors give real shell syntax highlighting; `shellcheck` /
`fish_indent` work; no `\$` escape forest from TS template literals.
- **Trailing-colon UX for `project:target`.** Completions for `show
target` / `run` contexts return `project:` (with colon). Bash and zsh
scripts detect trailing colons and suppress the inserted space (`compopt
-o nospace`, `compadd -S ''`). Fish has no per-completion nospace
primitive (works in practice — the user deletes the space or types `:`);
powershell same limitation.
- **zsh uses `compadd -d`, never `_describe`.** `_describe` splits on
`:` and would mangle `my-app:build` into value `my-app` with description
`build`. The wrapper parses `value\tdescription` from each completion
line and feeds parallel value/display arrays to `compadd -d`.
`formatDescription` collapses any literal TAB in untrusted plugin
descriptions to a space so the separator can't be forged.
- **fish descriptions are native.** Fish's `complete -a` reads
`value\tdescription` candidates the same way zsh's `compadd -d` does —
no wrapper change needed, just the description-emitting branch broadened
from "zsh only" to "shells that render descriptions".
- **Stale graph tolerated.** Completion reads
`.nx/workspace-data/project-graph.json` directly without triggering a
recompute. A slightly stale graph is fine; recomputing it would blow the
latency budget. There's an explicit `// do not fix this by triggering a
recompute` comment guarding the choice.
- **Workspace-root walk-up.** The completion fast path skips the normal
bootstrap that sets `NX_WORKSPACE_ROOT_PATH`, so a local
`resolveWorkspaceRoot()` walks from `process.cwd()` up to the nearest
`nx.json`. The POSIX wrappers do a parallel walk-up at TAB time to find
a workspace-local `nx` binary (falling back to PATH only outside a
workspace).
- **Generator listing avoids one file read in stage 1.** Stage 1 (`nx g
<TAB>`) only needs *names* of plugins that declare a generator
collection, not their generators — `collectPluginDirs()` reads each
plugin's `package.json` for the `generators` field, skips reading
`generators.json` until stage 2.
- **INFIX target completion is graph-driven.** `run/completion.ts` walks
the cached project graph at module load and registers a completion path
for every unique target name. Falls back to a conventional set (`build`,
`serve`, `test`, ...) for cold workspaces with no graph yet.
End-to-end completion latency: **~140ms typical**, scales with
project-graph size.
## Related Issue(s)
<!-- No specific issue — this is a new feature -->
## How to test locally
1. `pnpm nx build nx`
2. `pnpm link ./packages/nx`
3. Install the wrapper for your shell (see Installation above).
4. Open a new shell and tab away.
Verify:
- `nx <TAB>` lists commands **and** infix targets (`build`, `serve`,
plus any custom ones in your workspace)
- `nx show target <TAB>` completes to `project:` (no trailing space) —
second TAB lists targets
- `nx run-many -t <TAB>` lists target names
- `nx build <TAB>` lists projects that have a `build` target
- `nx g <TAB>` lists plugins **and** bare generator names; `nx g
app<TAB>` → `application`
- `nx g @nx/react:<TAB>` lists that plugin's generators (workspace-local
plugins also discoverable)
- `nx add @nx/r<TAB>` lists first-party plugins
- `nx show target i<TAB>` → `inputs`, `outputs`
- `NX_VERBOSE_LOGGING=1 nx <TAB>` surfaces any swallowed errors to
stderr
- Completion feels instant (~140ms typical)
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
Corepack on CI defaults to the latest published pnpm whenever it runs in
a directory without a `packageManager` field. Our e2e tests create temp
workspaces (via `create-nx-workspace`) in `/tmp/...` and run `pnpm
install` there before the field is written. Corepack picks pnpm 11.x,
which fails installs across the e2e matrix.
## Expected Behavior
Corepack reuses the pnpm version activated from the repo's
`packageManager` field (pnpm 10.x today) regardless of the working
directory. Setting `COREPACK_DEFAULT_TO_LATEST=0` at the workflow `env:`
level tells corepack to keep the activated default instead of
auto-upgrading.
Also bumps the cache bust value in `nx.json` so the CI Nx Cache rolls.
## Related Issue(s)
N/A — internal CI fix.
## Current Behavior
PR #35753 skipped 9 webpack-based React Module Federation e2e suites
because webpack 5.107.0 (published 2026-05-20) reorganized its `lib/`
directory and removed `lib/ModuleNotFoundError.js`, which
`@module-federation/enhanced` deep-imports. Every webpack-based MF
build/serve in the affected suites was failing with `Cannot find module
'webpack/lib/ModuleNotFoundError'`.
## Expected Behavior
webpack 5.107.1 (published 2026-05-21) restored the path as a
backward-compat shim — `lib/ModuleNotFoundError.js` now re-exports from
`./errors/ModuleNotFoundError`. Verified locally that all 21
`webpack/lib/*` paths used by `@module-federation/enhanced` 2.4.0 /
2.5.0 now resolve in 5.107.1.
This PR removes the `describe.skip` + TODO + `//
eslint-disable-next-line jest/no-disabled-tests` lines from the 9
affected files, re-enabling:
-
`e2e/react/src/module-federation/core-webpack-basic-host-remote-generation.test.ts`
-
`e2e/react/src/module-federation/core-webpack-basic-playwright.test.ts`
- `e2e/react/src/module-federation/core-webpack-name-and-root.test.ts`
- `e2e/react/src/module-federation/core-webpack-query-params.test.ts`
- `e2e/react/src/module-federation/core-webpack-ssr.test.ts`
- `e2e/react/src/module-federation/dynamic-federation.webpack.test.ts`
- `e2e/react/src/module-federation/federate-module.webpack.test.ts`
-
`e2e/react/src/module-federation/independent-deployability.webpack.test.ts`
- `e2e/react/src/module-federation/misc-rspack-interoperability.test.ts`
## Related Issue(s)
Reverts the skip from #35753. Upstream:
- https://github.com/webpack/webpack/pull/20988 (compat shim, merged)
- https://github.com/webpack/webpack/pull/20989 (webpack 5.107.1
release, merged)
## Current Behavior
The `23-0-0-convert-target-defaults-to-array` migration (which converts
`nx.json` `targetDefaults` from the legacy record shape to the new array
shape) has no co-located documentation, so on the docs site it renders
only its JSON-derived heading, version, and one-line description.
More broadly: several first-party packages build **in-place** to
`packages/<pkg>/dist` and their `migrations.json`
`implementation`/`factory` paths point at `./dist/src/migrations/...`.
The docs loader locates a migration's companion `.md` by appending
`".md"` to that resolved path — i.e. it looks under `dist`. None of
these packages' `assets.json` copied migration `.md` files into `dist`,
so every affected migration's example body was silently dropped on the
docs site. This affects `nx`, `@nx/devkit`, `@nx/eslint`, `@nx/jest`,
and `@nx/js`.
(Packages that reference `./src/migrations/...` directly — e.g.
`@nx/angular`, `@nx/react`, `@nx/vite` — are unaffected because the
loader reads their docs straight from source.)
## Expected Behavior
- Adds
`packages/nx/src/migrations/update-23-0-0/convert-target-defaults-to-array.md`
documenting the migration: the record → array shape change (nothing
dropped, insertion order preserved), the `:`-key disambiguation rules
(target vs executor, the duplicate-entry case, and the syntactic
fallback when the project graph is unavailable), and before/after
`nx.json` samples covering target, glob, and `pkg:executor` keys.
- Adds a `src/migrations/**/*.md` asset glob to the `assets.json` of
`nx`, `@nx/devkit`, `@nx/eslint`, `@nx/jest`, and `@nx/js` so their
migration docs are copied into `dist` alongside the compiled
implementations, where the docs loader reads them. This fixes rendering
for this migration and for the existing migration docs in those packages
that were also missing from `dist` (13 existing docs across the four
sibling packages).
## Related Issue(s)
N/A
---------
Co-authored-by: Claude <noreply@anthropic.com>
## Current Behavior
The `nx.json` reference page documents that `targetDefaults` **replace**
the corresponding inferred-task config (`inputs`, `outputs`,
`dependsOn`, `options`, etc.) but never mentions the spread token
(`"..."`) that lets a target default *merge with* the base value instead
of overwriting it. The spread token is documented only on the project
configuration reference page, even though `nx.json` `targetDefaults` is
where most users define the base values it acts on.
## Expected Behavior
The `nx.json` reference now covers the spread token where users actually
configure target defaults:
- New **Spread token** subsection under **Target defaults**, showing the
array form (`["...", "{workspaceRoot}/babel.config.json"]`) and object
form (`"...": true`) with `nx.json` examples.
- A forward-pointer from the **inputs & namedInputs** subsection, where
the replace-by-default behavior is introduced, to the new section.
- A cross-link to the full spread token reference (level table +
cautions) on the project configuration page, so the detailed reference
stays in a single place rather than being duplicated.
The project configuration reference already documents the spread token
on `master` (the `### Spread token` section). This PR completes the
restoration by documenting it on the `nx.json` reference as well.
## Related Issue(s)
Fixes NXC-4438
Co-authored-by: Claude <noreply@anthropic.com>
## Current Behavior
`nx release publish` detects the workspace package manager and uses it
to run the publish command. In bun workspaces it runs `bun publish`.
However, `bun publish` doesn't support npm's OIDC trusted publishing —
where GitHub Actions exchanges a short-lived OIDC token with the npm
registry so you can publish without storing a static `NPM_TOKEN` secret.
Only `npm publish` (and modern pnpm/yarn) perform that token exchange
automatically.
The result is that a bun workspace configured for OIDC trusted
publishing fails with:
```
bun publish error:
error: missing authentication (run `bunx npm login`)
```
even when the workflow has `id-token: write` set up correctly.
## Expected Behavior
When `bun publish` fails with an authentication-shaped error and `npm`
is available, the executor falls back to `npm publish` to recover. This
lets bun workspaces use OIDC trusted publishing (and provenance, if
requested) transparently — no config changes needed.
Other bun failures (version conflicts, 5xx responses, generic errors)
still surface bun's error directly so we don't hide unrelated problems
behind an unnecessary npm retry.
Implementation:
- The publish step is extracted into a `runPublish(ctx)` helper so the
fallback can re-enter the existing publish + output-handling code with
just `pm` swapped to `'npm'`.
- The fallback is gated by a regex on bun's stderr/stdout: `missing
authentication | bunx npm login | unauthorized | 401`. Misses are soft —
the user gets bun's error as today, not a worse outcome.
## Related Issue(s)
Fixes #
Adds a short `(Requires Nx 22.1 or higher.)` inline next to the `Upload
agent resource metrics` comment in every provider example on the manual
DTE page (GitHub Actions, CircleCI, Azure Pipelines, Bitbucket
Pipelines, GitLab CI, Jenkins).
The note sits with each provider's existing comment rather than as a
top-level callout — this page is about manual distribution, not metrics,
so the version requirement is incidental.
Paired with the corresponding UI change in nrwl/ocean (see linked PR).
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/mention-nx-version-requirement-for-manual-metric-uploads-4724334b)
<!-- polygraph-session-end -->
---------
Co-authored-by: rarmatei <matei.rar@gmail.com>
## Current Behavior
All webpack-based React Module Federation e2e tests started failing on
2026-05-20.
Webpack 5.107.0 (published earlier today) reorganized its internal
`lib/` directory into subdirectories — e.g. `lib/ModuleNotFoundError.js`
moved to `lib/errors/ModuleNotFoundError.js`, `lib/DllPlugin.js` moved
to `lib/dll/DllPlugin.js`, etc. `@module-federation/enhanced` (which our
generators wire up for webpack-based host/remote apps) deep-imports
`require('webpack/lib/ModuleNotFoundError')`, which now throws
`MODULE_NOT_FOUND` and aborts every webpack-based MF build/serve in our
react e2e suite.
Sample failure:
```
NX Cannot find module 'webpack/lib/ModuleNotFoundError'
Require stack:
- node_modules/@module-federation/enhanced/dist/src/lib/sharing/resolveMatchedConfigs.js
- node_modules/@module-federation/enhanced/dist/src/lib/sharing/ConsumeSharedPlugin.js
- ...
- node_modules/@nx/module-federation/src/with-module-federation/webpack/with-module-federation.js
```
This is upstream's bug, not ours:
- webpack/webpack#20985 — closed by webpack maintainer pointing at
module-federation
- module-federation/core#4747 — open issue for module-federation to stop
using private webpack APIs
- webpack/webpack#20988 — adds back a `lib/ModuleNotFoundError` compat
shim (merged, awaiting a webpack patch release)
Even after the shim release lands, other deep imports may still be
broken, so we want to fully decouple our CI from this until both
ecosystems re-sync.
Angular MF goes through the same `@module-federation/enhanced/webpack`
code path and would theoretically fail too, but only react tests were
observed failing in CI. The angular suites are left enabled so we get a
real signal if/when they hit the same issue.
## Expected Behavior
CI passes. Webpack-based react MF e2e suites are temporarily skipped
with `describe.skip` and a TODO comment linking the upstream tracking
issues. Rspack-based MF e2e tests and angular MF e2e tests are untouched
and continue to run.
Skipped suites:
-
`e2e/react/src/module-federation/core-webpack-basic-host-remote-generation.test.ts`
-
`e2e/react/src/module-federation/core-webpack-basic-playwright.test.ts`
- `e2e/react/src/module-federation/core-webpack-name-and-root.test.ts`
- `e2e/react/src/module-federation/core-webpack-query-params.test.ts`
- `e2e/react/src/module-federation/core-webpack-ssr.test.ts`
- `e2e/react/src/module-federation/dynamic-federation.webpack.test.ts`
- `e2e/react/src/module-federation/federate-module.webpack.test.ts`
-
`e2e/react/src/module-federation/independent-deployability.webpack.test.ts`
- `e2e/react/src/module-federation/misc-rspack-interoperability.test.ts`
(both scenarios still build with webpack on one side)
## Related Issue(s)
Tracking upstream:
- https://github.com/webpack/webpack/issues/20985
- https://github.com/module-federation/core/issues/4747
- https://github.com/webpack/webpack/pull/20988
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
AI-instructions migrations in `@nx/next`, `@nx/expo`, `@nx/nuxt`,
`@nx/vite`, `@nx/vitest`, and `@nx/storybook` are implemented as
generator wrappers whose only purpose is to copy a bundled `.md` file
into the workspace's `tools/ai-migrations/` directory.
## Expected Behavior
These migration entries declare a `prompt` field pointing directly at
the `.md` source. `nx migrate` collects the prompt files into the
workspace and surfaces a review banner, replacing the per-package
generator wrappers. The `@nx/storybook` migration becomes a hybrid
`implementation + prompt` entry (the implementation still runs `sb
upgrade`).
## Implementation Details
- **`@nx/next`, `@nx/expo`, `@nx/nuxt`, `@nx/vite` (x2), `@nx/vitest`
(x2)**: 7 entries switched from `implementation`/`factory` to `prompt`;
the 6 corresponding wrapper `.ts` files removed; the bundled `.md` files
moved out of each migration's `files/` subdirectory (a convention
required only by the deleted wrappers' `__dirname/files/...` reads).
- **`@nx/nuxt`, `@nx/vitest`**: added a `migrations.spec.ts` so their
migration entries are covered by `assertValidMigrationPaths`. Removed
stale orphans surfaced by the new nuxt spec: `src/migrations/.gitkeep`
and `src/migrations/update-18-1-0/add-include-tsconfig.{ts,spec.ts}`
(orphaned by an earlier pre-v19 migrations cleanup).
- **`@nx/storybook`**: `update-22-1-0-migrate-storybook-v10` becomes a
hybrid `implementation + prompt` entry. The `migrate-10` generator gains
a `skipAiInstructions` schema property; the migration wrapper passes
`skipAiInstructions: true` to avoid duplicating the prompt write (the
framework now writes it to the managed
`tools/ai-migrations/<scope>/<package>/<version>-<basename>.md` path).
Standalone `nx g @nx/storybook:migrate-10` invocations are unaffected
and still write the legacy `tools/ai-migrations/MIGRATE_STORYBOOK_10.md`
file.
- **`assertValidMigrationPaths`** (in
`@nx/devkit/internal-testing-utils`): updated to handle `prompt`-only
entries — per-entry validation asserts the referenced `.md` exists, and
the "all folders" check collects known dirs from both `impl` and
`prompt` paths.
- **Lint coverage parity**: `@nx/nuxt`, `@nx/vite`, `@nx/vitest` eslint
configs now include `./migrations.json` in the files block for
`@nx/nx-plugin-checks`, matching `@nx/next` / `@nx/expo` /
`@nx/storybook`.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The `@nx/rsbuild` inferred-plugin computes the build target's `outputs`
by taking `dirname()` of `output.distPath.root`:
```ts
const buildOutputPath = normalizeOutputPath(
rsbuildConfig?.output?.distPath?.root
? dirname(rsbuildConfig?.output.distPath.root)
: undefined,
...
);
```
But `distPath.root` *is* the directory Rsbuild emits the build into, so
`dirname()` points one level too high. A project whose `distPath.root`
resolves to `dist/apps/my-app` gets its `outputs` inferred as
`{workspaceRoot}/dist/apps` — the parent directory, which captures
sibling projects' build artifacts. Nx then caches/restores the whole
`dist/apps` tree as that one project's output.
## Expected Behavior
The inferred `outputs` should be `distPath.root` itself
(`{workspaceRoot}/dist/apps/my-app`).
This PR drops the `dirname()` call so `distPath.root` is used as-is, and
adds `getOutputs` coverage for an unset, a project-relative, and a
workspace-relative `distPath.root` (the existing tests only exercised an
empty config).
## Related Issue(s)
N/A
## Current Behavior
The root `package.json` declares several devDependencies that are no
longer used anywhere in the repo:
- **PostCSS build tooling** — `postcss-preset-env`,
`rollup-plugin-postcss`. Leftovers from before the tailwind v3 → v4
migration (#35594) reshaped the postcss chain.
- **3D / homepage scene suite** — `@react-spring/three`,
`@react-three/drei`, `@react-three/fiber`, `three`. Leftovers from the
superseded homepage iteration introduced in #26893. #35625 was meant to
remove the full suite (`@types/three` and `shadergradient` were dropped
there), but these four entries didn't actually land.
## Expected Behavior
All six entries removed from root `package.json`. No source-code or
consumer-package changes.
## Implementation Details
Per-dep verification at HEAD:
- **0 real-code imports** of any of the six names — `from '<name>' |
require('<name>')` across all `.ts/.tsx/.js/.mjs/.cjs` files returns
empty. The only string mentions are:
- Comments in `@nx/rollup`'s own inlined postcss plugin
(`packages/rollup/src/plugins/postcss/index.ts` — file header: "replaces
the external rollup-plugin-postcss dependency to avoid peer dependency
conflicts").
- Lockfile test fixtures in
`packages/nx/src/plugins/js/lock-file/__fixtures__/**`.
- Demo JSON data in `astro-docs/src/assets/**` and the English word
"three" in unrelated tests/comments.
- **0 `peerDependencies` / `dependencies` declarers** in installed
`node_modules`.
- **0 references** in `project.json` / `nx.json` / `scripts/` /
`.github/`.
- **All 6 `postcss.config.*` files** in the repo (`nx-dev/nx-dev` + 5
graph projects) use only `@tailwindcss/postcss` + `autoprefixer` — no
preset-env, no rollup plugin.
### Impact
- `pnpm-lock.yaml`: **-1,679 lines, +0 lines** (pure subtraction).
- PostCSS subtree: -1,094 lines (`postcss-*` plugins, cssnano stack,
cssdb, mdn-data, p-queue, lilconfig).
- 3D subtree: -585 lines (`three`, `three-mesh-bvh`, `three-stdlib`,
`troika-three-*`, `draco3d`, `camera-controls`, `meshoptimizer`,
`react-reconciler`, `stats-gl`, etc.).
- `pnpm install` clean — no new unmet peers introduced.
### Validation
- `nx run-many -t build -p nx-dev graph-client rollup` — green (29
dependent tasks).
- `nx run-many -t test,lint -p rollup nx-dev` — green.
- `nx run-many -t build,test,lint -p graph-ui-project-details
graph-migrate graph-ui-code-block` — green.
- `nx run-many -t build,test,lint -p nx-dev rollup graph-client` (after
3D removal) — green (37 tasks total).
`nx format:write/check` was unconditionally escaping `$` in file paths
(e.g. `_app.chat.$id.tsx` → `_app.chat.\$id.tsx`), causing prettier to
report "No files matching the pattern were found" on Windows.
The `\$` escape exists solely to prevent Unix shells from interpolating
`$var` patterns. On Windows (`cmd.exe`), `$` carries no special meaning,
so the backslash becomes a literal part of the path passed to prettier.
## Change
- **`packages/nx/src/command-line/format/format.ts`** — guard the
`$`-escape behind a `process.platform !== 'win32'` check:
```typescript
// Before (always escaped):
(p) => `"${p.replace(/\$/g, '\\\$')}"`
// After (only escape on non-Windows):
const escaped = process.platform !== 'win32' ? p.replace(/\$/g, '\\\$') : p;
return `"${escaped}"`;
```
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AgentEnder <6933928+AgentEnder@users.noreply.github.com>
Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
When deciding whether a task can be scheduled, `TasksSchedule` checked
`task.parallelism === true` in two places:
- `canBeScheduled` — gating a task against already-running parallel
tasks
- `canBatchTaskBeScheduled` — gating a task for batch scheduling
A task whose `parallelism` is `undefined` failed both checks and was
blocked, even though `undefined` is meant to mean "parallel". This was
also inconsistent with the running-tasks check, which uses `parallelism
=== false` — treating `undefined` as parallel-capable.
## Expected Behavior
A task with `parallelism === undefined` is treated as parallel in both
the regular and batch scheduling checks. Both now use `parallelism !==
false`, matching the convention used elsewhere and the documented
default.
## Related Issue(s)
N/A
## Current Behavior
`@nx/eslint` and `@nx/eslint-plugin` build into the shared
`dist/packages/<name>` directory at the workspace root. `@nx/eslint`
exposes no `exports` map, so first-party packages reach into
`@nx/eslint/src/*` for internal utilities.
## Expected Behavior
Both packages build locally into `packages/<name>/dist/` with `nodenext`
module resolution and an `exports` map, matching the already-migrated
`nx`, `devkit`, `js`, and `jest`.
### `@nx/eslint`
- `tsconfig.lib.json` / `tsconfig.spec.json` switched to the nodenext +
local-`dist` pattern.
- `package.json` gains an `exports` map (`.`, `./plugin`, `./internal`,
plus the JSON configs), `typesVersions`, and a `files` field.
- `project.json` gains `release` and `nx-release-publish` configuration.
- `executors.json` / `generators.json` / `migrations.json` paths
repointed to `./dist/src/...`.
- `src/utils/versions.ts` resolves its own `package.json` via a
`@nx/eslint` self-reference.
- **Exports lockdown:** the broad `src/*` surface is dropped; a curated
`@nx/eslint/internal` entry replaces it. 27 first-party consumer files
are rewritten from `@nx/eslint/src/*` to `@nx/eslint/internal`, and a
`rewrite-eslint-internal-subpath-imports` migration rewrites user
imports (symbol-aware — public symbols → `@nx/eslint`, internals →
`@nx/eslint/internal`).
- `@nx/vite` and `@nx/remix` imported `@nx/eslint` utilities without
declaring the dependency; `@nx/eslint` is now a declared dependency of
both.
### `@nx/eslint-plugin`
- Same nodenext + local-`dist` migration.
- `package.json` `exports` map covers `.`, `./angular`, `./nx`,
`./react`, `./typescript`. No `./internal` entry — nothing imports its
`src/*`.
## Related Issue(s)
Tracked by Linear NXC-3575 (`@nx/eslint`) and NXC-4475
(`@nx/eslint-plugin`). No GitHub issue.
<details>
<summary>Notes</summary>
- `@nx/js` and `@nx/workspace` also call `@nx/eslint` utilities but
cannot declare the dependency — `@nx/eslint` depends on `@nx/js`, so
declaring it back would create a cycle. They use a runtime
`require('@nx/eslint/internal')` (no static import), which `tsc` does
not resolve, so their builds are unaffected.
- Validation: `nx affected -t build,lint` is green (only the
pre-existing `MsbuildAnalyzer` dotnet build fails). Affected `:test`
failures are pre-existing environment/snapshot drift — `devkit:test` and
`nx:test` fail identically with zero dependency on these packages, and
`web`/`react` test failure counts match `master` exactly. The
`@nx/eslint`/`@nx/eslint-plugin` suites pass apart from the pre-existing
`workspace-rules-project.spec.ts` "TS solution setup" failure (also
fails on `master`).
</details>
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Swap the visual hierarchy of the docs header: the call-to-action becomes
the primary (black) button labeled "Get started", and the GitHub "Star
us" widget moves to a secondary outlined/muted treatment. Link target,
target/rel, and the existing GTM event remain unchanged.
Fixes DOC-507
## Current Behavior
`packages/nx/src/plugins/js/project-graph/build-dependencies/` still
ships two TypeScript modules that were marked `@deprecated` "will be
removed in Nx 20":
- `strip-source-code.ts` — exports `stripSourceCode`, a TS-scanner-based
pre-parse pass that reconstructs only the import/export/require
statements from a file.
- `typescript-import-locator.ts` — exports `TypeScriptImportLocator`,
the legacy non-native scanner that walks a TS AST to extract
dependencies.
Both have zero remaining callers in the repo. The native (Rust) import
scanner used by `target-project-locator.ts` has fully replaced them in
the project-graph pipeline.
## Expected Behavior
The two deprecated files are deleted. `pnpm nx run nx:build-base` still
passes — confirming nothing in the workspace (inside or outside
`packages/nx`) was importing them.
No replacement API is needed: these symbols were never re-exported from
a public entry point and the JSDoc explicitly stated they "were not
intended to be exposed."
## Related Issue(s)
<!-- No specific issue; this completes the Nx 20 deprecation cycle for
these two APIs. -->
Fixes #
Co-authored-by: Claude <noreply@anthropic.com>
## Current Behavior
`packages/nx/src/command-line/report/report.ts` contains a stale `TODO
(v20)` comment referencing a workaround for hiding `@nrwl/*` packages
when a matching `@nx/*` package was found. We're on v23 — two majors
past the deadline.
The actual workaround was a `packageChangeMap` plus dedup logic inside
`findInstalledPackagesWeCareAbout` that compared `@nrwl/*` and `@nx/*`
versions and suppressed the `@nrwl/*` entry when both matched. That
logic was already removed in #30840 (May 2025). Only the orphaned
comment remained.
## Expected Behavior
The stale comment is removed. No runtime behavior change —
`findInstalledPackagesWeCareAbout` already lists every installed package
from `packagesWeCareAbout` without any `@nrwl/@nx` dedup logic.
Verified `nx report` output is unchanged for workspaces with only
`@nx/*` packages and for workspaces that still have legacy `@nrwl/*`
packages installed (e.g. `@nrwl/nx-cloud` from
`nx-migrations.packageGroup`, or `@nrwl/schematics` from the manual
entry on line 53 — both continue to be reported as before).
## Related Issue(s)
Ref: NXC-4300
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Local Nx plugins imported via subpath exports (e.g.
`@scope/pkg/cypress`) cannot be resolved from source. The loader
collapses every subpath onto the project's build-target `main`, ignoring
the imported subpath, so plugins either ship as committed `dist`
artifacts (workaround) or fail to load with an empty entry point.
Additionally, even for non-subpath imports of workspace plugins
symlinked into `node_modules`, Node's resolver picks the built `dist`
artifact via the `default` exports condition (Node doesn't honor
TypeScript `customConditions`), so source edits are ignored until the
plugin is rebuilt.
## Expected Behavior
Subpath imports of local plugins resolve to source via `package.json`
`exports` using the workspace-defined `customConditions`.
Workspace-local plugins (subpath or bare) prefer their source-pointing
condition over the built `dist` so source edits take effect without
rebuilding.
## Implementation Details
- New `getRootTsConfigCustomConditions` helper reads
`compilerOptions.customConditions` from the root tsconfig via the
TypeScript API, honoring `extends` chains.
- `resolveSubpathFromExports` invokes `resolve.exports` with the
workspace's conditions plus `development` as a backward-compat fallback
for pre-21.5 setups. A second `resolve.exports` call with `conditions:
[]` detects when only `default`/`import`/`require` matched, signaling
"no source-pointing condition" so the loader hard-fails with guidance
instead of silently loading dist.
- Hoists local-source resolution ahead of `require.resolve` in
`getPluginPathAndName` so symlinked workspace packages prefer source
over dist. Default plugins (absolute paths) and external installed
packages skip the local branch to avoid recursing through
`retrieveProjectConfigurationsWithoutPluginInference`.
- Updates `schema-utils` (executors/generators) to use the same
workspace conditions when resolving implementations from source.
- Adds an e2e test covering subpath plugin loading via the exports
condition.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: leosvelperez <leosvelperez@users.noreply.github.com>
## Current Behavior
`@nx/jest` builds into the shared `dist/packages/jest` directory at the
workspace root and compiles with `commonjs` module resolution. It
exposes no `exports` map, and first-party packages (`@nx/detox`,
`@nx/node`) reach into `@nx/jest/src/*` for internal utilities. It also
still re-exports the long-deprecated `jestProjectGenerator` alias.
## Expected Behavior
`@nx/jest` builds locally into `packages/jest/dist/` with `nodenext`
module resolution and an `exports` map, matching the already-migrated
`nx`, `devkit`, and `js` packages.
- `tsconfig.lib.json` / `tsconfig.spec.json` switched to the nodenext +
local-`dist` pattern.
- `package.json` gains an `exports` map (`.`, `./plugin`, `./preset`,
`./plugins/resolver`, `./internal`, plus the JSON configs),
`typesVersions`, and a `files` field.
- `project.json` gains `release` (`preserveLocalDependencyProtocols`,
`manifestRootsToUpdate`) and `nx-release-publish` configuration.
- `executors.json` / `generators.json` / `migrations.json` factory and
schema paths repointed to `./dist/src/...`.
- `assets.json` outputs to the local `dist/` and copies
`src/**/schema.json`.
- `src/utils/versions.ts` resolves its own `package.json` via a
`@nx/jest` self-reference instead of a `../../` relative path, which
breaks once source compiles into `dist/`.
- The `@nx/jest:jest` executor imports its `schema.json` statically
rather than via a dynamic `await import`.
- A curated `@nx/jest/internal` entry (mirroring `@nx/devkit/internal`)
replaces deep `@nx/jest/src/*` imports for first-party consumers;
`@nx/detox` and `@nx/node` are routed through it.
- A migration (`rewrite-jest-internal-subpath-imports`) rewrites user
`@nx/jest/src/*` imports: named imports/exports of public symbols go to
`@nx/jest`, everything else to `@nx/jest/internal`.
- A migration (`rewrite-jest-project-generator`) rewrites
`jestProjectGenerator` imported from `@nx/jest` to
`configurationGenerator`.
The `dist-build-migration` skill is also updated to document the
symbol-aware routing rule for the migration step.
### Breaking Changes
- The deprecated `jestProjectGenerator` export is removed — its "removed
in Nx v22" notice is two majors overdue. It was always just an alias for
`configurationGenerator`; the `rewrite-jest-project-generator` migration
rewrites existing usages automatically.
## Related Issue(s)
Tracked by Linear NXC-3592 — `[M4] [Epic] Migrate @nx/jest to local
dist`. No GitHub issue.
<details>
<summary>Pre-create review (run before PR open)</summary>
### Critical
- The subpath-import migration originally rewrote every `@nx/jest/src/*`
import to `@nx/jest/internal`, which would silently break consumers
importing a public symbol (e.g. `findJestConfig`) that way. **Fixed** —
the migration now partitions named imports/exports: public symbols route
to `@nx/jest`, internals to `@nx/jest/internal`, splitting mixed
declarations into two.
### Important
- `export { ... } from '@nx/jest/src/*'` and additional `jest`/`vi` mock
helpers were not handled by the initial migration. **Fixed** — export
declarations are now partitioned like imports, and all eight mock-helper
methods are covered with tests.
### Suggestions
- `versions.ts` self-resolve and the inferred `build-base` target were
flagged; both verified correct (matches the `@nx/js` pattern;
`build-base` is inferred by `@nx/js/typescript` from
`tsconfig.lib.json`).
- Softened an over-broad "used only" comment in `eslint.config.mjs`.
</details>
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Workspace depends on `glob@7.1.4` (EOL) for three usages: two
`tools/workspace-plugin` conformance rules and one release-pipeline
script.
## Expected Behavior
`glob` is dropped from the workspace; the three usages move to
`tinyglobby` (already a catalog dep used in 11+ places, and the
prevailing replacement enforced by `no-restricted-imports` rules
elsewhere in the repo).
## Implementation Details
-
`tools/workspace-plugin/src/conformance-rules/codeblock-language/index.ts`:
removed dead `globSync`/`join`/`relative` imports (rule reads
`fileMapCache.fileMap.projectFileMap` directly).
-
`tools/workspace-plugin/src/conformance-rules/relative-image-imports/index.ts`:
swapped `glob.sync(join(workspaceRoot, 'astro-docs/**/*.mdoc'), { ignore
})` for the idiomatic tinyglobby form (`cwd` + relative pattern +
`absolute: true`). Verified the new call returns the identical set of
501 absolute paths.
- `scripts/cleanup-tsconfig-files.js`: `glob.sync(p)` → `globSync(p)`.
- Dropped `glob` from root and `tools/workspace-plugin` `package.json`;
added `"tinyglobby": "catalog:"` to the latter.
- Lockfile diff is surgical: -6 / +3 lines (two `glob` importer entries
gone, one `tinyglobby` entry added).
### Validation
- `pnpm install` clean.
- `nx run-many -t lint,test,build -p workspace-plugin` — green (38/38
tests).
- `nx conformance` — all 7 rules pass; `image-import-paths` (the
migrated one) scans astro-docs `.mdoc` tree end-to-end.
- Side-by-side comparison: `glob@7.1.4` and `tinyglobby` return the
**identical 501-file set** for the migrated pattern.
- `scripts/cleanup-tsconfig-files.js` smoke run — removes the expected
files.
## Current Behavior
Loading `.ts` config files always registered `@swc-node/register` (or
`ts-node`) + `tsconfig-paths` up front. New workspaces shipped those
deps even though Node 22.6+ strips types natively.
## Expected Behavior
Native Node.js TypeScript stripping is now the default for `.ts` configs
and plugin loads. swc/ts-node + `tsconfig-paths` register lazily only
when native strip fails (matcher-based, ≤3 attempts; covers
`MODULE_NOT_FOUND`, `ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX`, ESM-as-CJS
syntax errors,`__dirname` in ESM scope, ESM-with-TLA). `@nx/js` init no
longer installs `@swc-node/register` / `@swc/core` / `@swc/helpers`. Opt
out via `NX_PREFER_NODE_STRIP_TYPES=false`.
Other changes in this PR are to ensure our generators create
Node-compatible config files. For example, not using extensions in
imports without `exports` will fail, like
`@nx/cypress/plugins/cypress-preset.js` instead of
`@nx/cypress/plugins/cypress-preset`. Or mixing in
`__dirname`/`__filename` into ESM config files. Or using
`import`/`export` in CJS files.
## Related Issue(s)
Fixes NXC-4299
## Current Behavior
Under Nx 23.0.0, the Nx Cloud agent client crashes when running tasks:
```
TypeError: runContinuousTasks is not a function
at AgentTaskManager.invokeContinuousTasks
at AgentTaskManager.reconcileContinuousTasks
at AgentTaskManager.invokeTasks
at executeTasksV3
```
The Nx Cloud client probes the nx task APIs by `require`-ing
`nx/src/index` (for the legacy `initTasksRunner`) and
`nx/src/tasks-runner/init-tasks-runner` (for the modern
`runDiscreteTasks` / `runContinuousTasks`) inside a single shared
`try/catch`.
PR #35708 removed the deprecated `initTasksRunner` API and **deleted
`packages/nx/src/index.ts` entirely** — even though that PR's own commit
message stated the file would be kept as an empty module so the `nx/src`
subpath export mapping stays valid. With the file gone, `nx/src/index`
no longer resolves (`./src/*` maps to `./dist/src/index.js`, which is
never built). The `require('nx/src/index')` throws, the client's shared
`catch` swallows it, and `runContinuousTasks` is never assigned — later
crashing when invoked.
`runContinuousTasks` itself was never removed; it still lives in
`packages/nx/src/tasks-runner/init-tasks-runner.ts` and is unreachable
here only because the earlier `nx/src/index` probe throws.
## Expected Behavior
`packages/nx/src/index.ts` is restored as an empty ES module, so the
`nx/src/index` entrypoint resolves again via the existing `./src/*`
mapping in `package.json` exports (no `package.json` change needed). The
deprecated `initTasksRunner` export stays removed — consumers still get
`undefined` for it — but the `require` no longer throws, so the Nx Cloud
client proceeds to load `runContinuousTasks` / `runDiscreteTasks`
correctly.
This restores compatibility for already-published/cached Nx Cloud client
bundles without requiring them to be updated.
## Related Issue(s)
Internal: NXC-4307 (follow-up to #35708)
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/master-1efdcf12-fbbd-49c0-954c-3637c415400a-d8a02644)
<!-- polygraph-session-end -->
---------
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The `convert-target-defaults-to-array` migration (Nx 23) converts the
legacy
record-shape `targetDefaults` in `nx.json` to the new array shape.
It declared a `projectGraph` parameter and classified each record key
against
the graph, dropping any key whose target name / executor wasn't found in
the
workspace. But the migration runner always invokes migrations as
`fn(tree, {})`
— the second argument is an empty object, never a project graph.
`{}` is truthy, so it was treated as a real (but empty) graph. Every
non-glob
key then matched neither a target name nor an executor, and the
migration
dropped it. As a result, upgrading to Nx 23 deletes every named
`targetDefaults`
entry (`build`, `test`, `lint`, …) and keeps only glob entries —
silently
breaking `build` (lost `dependsOn`) and `test` (lost env/options) across
the
workspace.
## Expected Behavior
The migration is a pure shape conversion and never drops entries:
- The entry point takes an honest `(tree)` signature — it no longer
pretends to
receive a project graph it is never given.
- Every legacy key produces at least one array entry. Globs and plain
(non-`:`)
keys become `{ target: key }`; `:` keys are disambiguated by the project
graph
(`target`, `executor`, or both), falling back to the syntactic heuristic
(`:` → executor) when the graph has no signal.
- The project graph is built internally, and only when a `:`-style key
actually
needs disambiguating.
- The pure conversion is exposed as `convertTargetDefaultsRecordToArray`
so the
disambiguation logic is unit-testable with injected graphs, without
standing
up a workspace or the migration runner.
## Related Issue(s)
N/A — caught while upgrading the Nx repo itself to `23.0.0-beta.13`.
## Problem
nx 23 changed `@nx/js`'s `package.json` `exports`: the `./src/internal`
subpath was removed (renamed to `./internal`), and `getRootTsConfigPath`
was moved to the main `@nx/js` entry.
`@nx/conformance@4.0.0` and `@nx/conformance@5.0.x` — published on npm
and still widely pinned — load workspace TypeScript conformance rules
via:
```js
const { getRootTsConfigPath } = await import('@nx/js/src/internal');
const { registerTsProject } = await import('@nx/js/src/internal');
```
On nx 23 this throws `ERR_PACKAGE_PATH_NOT_EXPORTED: Package subpath
'./src/internal' is not defined`. conformance swallows the error and
reports a misleading `Failed to resolve rule specifier`, so `nx
conformance:check` fails for every rule in any workspace using
conformance@4/5 on nx 23.
No published `@nx/conformance` supports nx 23 yet (latest `5.0.5` peers
`nx >=18 <23`), so consumers have no version to upgrade to.
## Fix
Restore `./src/internal` as a **deprecated backwards-compatibility
alias**. It cannot simply point at the same file as `./internal`,
because `getRootTsConfigPath` no longer lives there — so a small shim
re-exports both symbols from their new homes:
- `registerTsProject` ← `@nx/js/internal`
- `getRootTsConfigPath` ← main `@nx/js` entry
(`./utils/typescript/ts-config`)
### Changes
- New shim file `packages/js/src/internal.ts` re-exporting
`registerTsProject` and `getRootTsConfigPath`, clearly marked
`@deprecated` with guidance to migrate to `@nx/js/internal` / `@nx/js`.
- `package.json`: added `./src/internal` export entry (with
`@nx/nx-source` source condition) and a `src/internal` `typesVersions`
entry.
### Verification
- `import('@nx/js/src/internal')` resolves both `getRootTsConfigPath`
and `registerTsProject` as functions.
- TypeScript compilation clean (no new errors).
- Prettier reports no changes needed.
## Context
Surfaced by the failing `conformance:check` CI on nrwl/ocean PR #11347
(nx 23.0.0-beta.13 update). This fix needs to ship in a subsequent nx 23
beta for ocean to pick it up.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/upnx-51643-3e71e693)
<!-- polygraph-session-end -->
---------
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
## Current Behavior
`initTasksRunner` is exported from the `nx` package as a programmatic
entry point for running tasks outside of a CLI invocation. It has been
marked `@deprecated` since Nx 21 (#29993), the same release that
introduced `runDiscreteTasks` / `runContinuousTasks` as the replacement
task-execution path.
It also still carries a stale `TODO: Remove this in Nx 20` polyfill that
backfills `outputs` on tasks whose callers didn't provide them.
Modern Nx Cloud agents (Nx >= 21) route task execution through
`runDiscreteTasks` / `runContinuousTasks` and never call
`initTasksRunner` — it is reachable only on Nx < 21 (or when
`NX_DTE_LEGACY_APIS=true` is forced).
## Expected Behavior
The deprecated `initTasksRunner` function (and its stale `outputs`
polyfill) is removed from the `nx` package as part of the v23
deprecation cleanup.
- `initTasksRunner` and its now-unused imports are deleted from
`packages/nx/src/tasks-runner/init-tasks-runner.ts`.
- `runDiscreteTasks`, `runContinuousTasks`, and `createOrchestrator` —
the live continuous-task execution path — are kept untouched.
- `packages/nx/src/index.ts` is emptied (kept as a module so the public
`nx/src` subpath export mapping in `package.json` stays valid).
- The `Invoke Runner` e2e test, which exercised `initTasksRunner`, is
replaced with one covering `runDiscreteTasks` / `runContinuousTasks` —
the modern programmatic task-execution API that Nx Cloud agents actually
consume.
**BREAKING CHANGE:** The deprecated `initTasksRunner` export is removed
from the `nx` package. Consumers should use `runDiscreteTasks` /
`runContinuousTasks` instead.
## Related Issue(s)
Internal: NXC-4307
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`@nx/js` builds to the shared workspace-root `dist/packages/js/`
directory, uses CommonJS `module`/`moduleResolution`, has no `exports`
map, and relies on `.npmignore`-style filtering through assets.json
copies. This makes the package layout diverge from the new pattern
already adopted by `nx` and `@nx/devkit`.
## Expected Behavior
`@nx/js` follows the same local-dist build pattern as `nx` and
`@nx/devkit`:
- Builds to `packages/js/dist/` instead of `dist/packages/js/`.
- `tsconfig.lib.json` uses `module`/`moduleResolution: "nodenext"` with
`composite`, `rootDir: "."`, and `declarationDir: "dist"`.
- `package.json` declares an `exports` map with the `@nx/nx-source`
condition (workspace consumers resolve to `.ts` source, published
consumers get built `.js`), plus a `./src/*` wildcard so the ~296
existing internal imports of `@nx/js/src/...` keep working.
- `typesVersions` added for legacy `moduleResolution: "node"` consumers.
- Adopts an explicit `files` field on `package.json` instead of
asset-copying the root JSONs.
- `generators.json`, `executors.json`, `migrations.json` factory/schema
paths rewritten `./src/...` → `./dist/src/...` (matches `nx`). Workspace
dev still works via the `tryResolveFromSource` fallback in
`packages/nx/src/config/schema-utils.ts`.
- `README.md` → `readme-template.md`; build command writes the rendered
README to `packages/js/README.md`. Root `.gitignore` now ignores
`packages/js/README.md` and `packages/js/**/*.d.ts` (with
`!packages/js/src/**/schema.d.ts` exception for committed schema
declarations).
- ESLint flat config ignores `dist` and `**/*.d.ts`.
- `project.json` adds `release.version` config (with
`preserveLocalDependencyProtocols: false` so the not-yet-migrated
`@nx/workspace` dep is substituted to a concrete version at version-time
rather than left as `workspace:*` for pnpm publish to resolve from the
un-bumped source `packages/workspace/package.json`) and
`nx-release-publish.packageRoot`. The `build` target keeps its existing
`dependsOn: ["build-base"]` (cannot use `^build` because js's
`implicitDependencies` create a cycle through `eslint` /
`eslint-plugin`).
- `scripts/nx-release.ts`: adds `packages/js` to `packagesToReset` so
the source `packages/js/package.json` is restored after release.
## Related Issue(s)
Part of the ongoing migration of Nx packages to the local-dist build
layout (following `nx` and `@nx/devkit`).
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
`@nx/rspack` and `@nx/rsbuild` do not follow the multi-version support
compliance patterns used by other Nx plugins:
- `@rspack/core` is a regular dependency (not a peer) pinned via
`catalog:rspack`. `@rsbuild/core` is a regular dependency pinned at
exact `1.1.8`. No peerDependencies range, no per-major version map.
- The init generators unconditionally overwrite the workspace's
installed `@rspack/core` / `@rsbuild/core` with the plugin's pinned
version when `keepExistingVersions=false`, which is also the schema
default.
- Most generators do not assert a supported floor before mutating the
workspace, so sub-floor installs silently land in an unsupported state.
- `@nx/rspack` declares `@module-federation/enhanced` and
`@module-federation/node` as peer dependencies while
`@nx/module-federation` declares them as regular dependencies —
inconsistent across the two plugins.
- `packages/rspack/migrations.json` 21.3.0 bumps
`@module-federation/enhanced` cross-minor in the 0.x range without a
`requires` clause. The 22.2.0 entry bumps
`@module-federation/{enhanced,sdk,runtime}` in a single grouped entry
with no per-package gates.
- `packages/rsbuild/src/utils/versions.ts` (`1.1.10`) is out of sync
with `packages/rsbuild/package.json` (`1.1.8`).
- Supported-versions docs pages list only a default install version, not
the actual support window.
## Expected Behavior
Both plugins follow the multi-version compliance patterns, with the
cypress-style assert-at-entry approach:
- `@rspack/core`, `@rspack/dev-server`, `@rspack/plugin-react-refresh`
are peerDependencies of `@nx/rspack` with range `^1.0.0`.
`@rsbuild/core` is a peerDependency of `@nx/rsbuild` with range
`^1.0.0`.
- `@module-federation/enhanced` and `@module-federation/node` are
declared as regular dependencies in `@nx/rspack` to match
`@nx/module-federation`.
- New Angular-style `backwardCompatibleVersions` maps
(`packages/{rspack,rsbuild}/src/utils/versions.ts`) and detection
utilities (`version-utils.ts`) gate installed-version pickup, with
tree-based and runtime variants for generator-time and executor-time
callers.
- New `assertSupported{Rspack,Rsbuild}Version(tree)` wrappers around the
shared `assertSupportedPackageVersion` helper from
`@nx/devkit/internal`. Called at the top of **every** generator entry —
`init`, `configuration`, `convert-webpack`, `convert-to-inferred`, and
`convert-config-to-rspack-plugin` for rspack; `init` and `configuration`
for rsbuild — so sub-floor workspaces fail fast with a consistent
message before any mutation.
- Above-window installs are not thrown on. Following the pattern used by
`@nx/angular` and `@nx/playwright`, an unknown future major falls
through silently to the latest known install constants. The workspace
already has its own pin and we honor it.
- Init generator schemas flip `keepExistingVersions` default from
`false` to `true`. Init reads `options.keepExistingVersions ?? true`.
All `addDependenciesToPackageJson` callsites pass `keepExistingVersions:
true` (or thread the option through where the schema exposes it).
- Migration `requires` gates added to `@nx/rspack`:
- `21.3.0` → `requires: { "@module-federation/enhanced": ">=0.15.0
<0.17.0" }` for the enhanced bump; `@module-federation/node` bump split
out with its own `requires` clause.
- `22.2.0` split into per-package entries for `enhanced`, `sdk`,
`runtime`, and `node`, each with its own `requires` clause.
- rsbuild `versions.ts` / `package.json` drift resolved — both now flow
through the new map.
- Exact version pins (`1.6.8`, not `^1.6.8`) for the rspack/rsbuild
family entries in the version map. With caret ranges, `@rspack/cli`
floats to the latest 1.x (e.g. `1.7.11`) while `@rspack/core` stays at
`1.6.8` (pinned by `@nx/module-federation`'s transitive dep via the
catalog), causing a cli/core API skew that crashes `rspack serve` with
`Cannot read properties of undefined (reading 'web')`. Users who upgrade
rspack/rsbuild independently still work because
`getInstalled*MajorVersion` reads their pin and init honors it.
- Docs pages updated with explicit `^1.0.0` support window and a
`Default Installed` column.
### Tests
A parameterized `all-generators-enforce-floor.spec.ts` in each package
uses `assertGeneratorsEnforceVersionFloor` from
`@nx/devkit/internal-testing-utils` to verify every generator entry
throws on sub-floor installs. Catches future regressions where a new
generator forgets to call the assert.
### Scope notes
- **v0 dropped.** Plugins had no v0 support to preserve.
- **v2 in a follow-up PR.** `@rspack/core@2.0.3` and
`@rsbuild/core@2.0.6` ship as pure ESM and require additional work for
the Nx plugins (CJS) to interop. The version-map and detection
scaffolding here is structured so the v2 entries slot in trivially once
that work lands.
### Validation
- `nx run-many -t test,build,lint -p rspack,rsbuild` — green
- `nx run module-federation:test` — green
- `nx affected -t lint --base=origin/master` — green
- Floor-enforce specs pass: 6 generators (rspack) + 2 generators
(rsbuild) verified to throw on sub-floor `~0.7.0`.
## Related Issue(s)
Fixes #NXC-4404
Fixes #NXC-4405
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
Two structural sources of non-determinism existed inside Nx's
`createNodes`/`createNodesV2` pipeline. Both are invisible to plugin
authors but produce different project graphs across runs on the same
workspace.
### 1. `createNodesFromFiles` resolution-order race
`createNodesFromFiles` (in
`packages/nx/src/project-graph/plugins/utils.ts`) — the helper that ~20
first-party plugins use to fan out per-config-file work — runs callbacks
in parallel via `Promise.all(configFiles.map(async (file, idx) => { ...
results.push([file, value]) }))`. Tuples are pushed into the shared
`results` array in the **resolution order of the callbacks**, not the
input order of `configFiles`.
The matched file list arriving at the plugin is sorted (the Rust
`glob_files` impl `par_sort`s, and Rayon's
`par_iter().filter().collect()` preserves order). The downstream merge
(`mergeCreateNodesResultsFromSinglePlugin` → `for (const result of
pluginResults)` → `for (const root in projectNodes)`) walks results in
array / insertion order. So if any plugin returns multiple contributions
for the same project root, the *only* place the order can scramble is
the helper's `Promise.all + .push` race.
A second instance of the same pattern lives in `@nx/eslint`'s
`internalCreateNodesV2`, which mutates `projects[projectRoot] = project`
from inside a `Promise.all`. The `projects` object's key-insertion order
then tracks `eslint.isPathIgnored` / `getProjectUsingESLintConfig`
resolution races and propagates the same non-determinism.
### 2. Atomized target name insertion order
For atomizing plugins, the order of dynamically-generated target names
(`<ciTargetName>--<relativePath>`) leaks into the project graph through
`targets[name]` insertion order, `dependsOn[]`, and
`targetGroups[group][]`. The order is deterministic when the file list
comes from Nx's Rust glob (sorted), but **not** when it comes from a
non-Nx file-discovery layer:
- **`@nx/jest`** (runtime branch, `disableJestRuntime: false`) — uses
`jest.SearchSource.getTestPaths()` which walks via jest-haste-map's
parallel workers; ordering not guaranteed. The `disableJestRuntime:
true` branch was already fine (sorted glob).
- **`@nx/vitest`** and **`@nx/vite`** — both have a
`getTestPathsRelativeToProjectRoot` helper that returns
`vitest.getRelevantTestSpecifications()` directly. Vitest uses
tinyglobby internally, which doesn't sort.
`@nx/cypress`, `@nx/playwright`, `@nx/gradle` (v1 + v2), and
`@nx/eslint`'s atomizer paths all source from sorted Rust glob and
iterate synchronously — they're fine.
## Expected Behavior
Project graph construction is deterministic across runs given a
deterministic input file list. Specifically:
- `createNodesFromFiles` returns `results` and `errors` arrays in
`configFiles` input order, regardless of which callback resolves first.
- `@nx/eslint`'s `projects` map keys are inserted in input order of
`projectRootsByEslintRoots.get(configDir)`.
- Atomized target names from `@nx/jest`, `@nx/vitest`, and `@nx/vite`
are inserted in lexicographic order of relative path.
### How
- **`packages/nx/src/project-graph/plugins/utils.ts`** — settle each
callback into a discriminated tuple `{ kind: 'value' | 'empty' |
'error', ... }` from inside `Promise.all`. `await
Promise.all(arr.map(...))` returns an array indexed by input position,
so a synchronous post-pass over that array bins values and errors in
input order. No change to public API or error semantics.
- **`packages/eslint/src/plugins/plugin.ts`** — each parallel branch
*returns* its contribution (or `null`) instead of mutating the shared
`projects` object. A synchronous post-pass over `orderedProjectRoots`
`Object.assign`s contributions into `projects` in input order.
- **`packages/jest/src/plugins/plugin.ts`** — sort `specs.tests.map(({
path }) => path)` before constructing the `Set` of test paths.
- **`packages/vitest/src/plugins/plugin.ts`** +
**`packages/vite/src/plugins/plugin.ts`** — `.sort()` the relative paths
returned by `getTestPathsRelativeToProjectRoot` before they reach the
atomizer loop.
### Audit of other createNodes implementations
- `@nx/jest` (`disableJestRuntime: true`) — sources from
`globWithWorkspaceContext` (sorted by Rust glob).
- `@nx/cypress`, `@nx/playwright` — sources from
`globWithWorkspaceContext` / `getFilesInDirectoryUsingContext` (both
deterministic; `get_child_files` is a sequential
`into_iter().filter().collect()` over a sorted file list) and iterates
with `for (const ... of ...)`.
- `@nx/gradle` v1 — `splitConfigFiles` + `forEach` over already-sorted
glob output.
- `@nx/gradle` v2 — synchronous `for...of` over `Array.from(new
Set([...]))`; `Set` iterates in insertion order, source arrays
deterministic.
- `@nx/maven`, `@nx/nuxt`, `@nx/remix`, `@nx/rollup`, `@nx/detox`,
`@nx/dotnet`, `@nx/react/router-plugin`, and the nx-core `project-json`
/ `package-json` / `js` plugins — no parallel-write-to-shared-object
patterns.
### Tests
Two new regression tests in
`packages/nx/src/project-graph/plugins/utils.spec.ts` force later inputs
to resolve faster (e.g. `file1` waits 30ms, `file2` resolves
immediately) and assert that `results` and `errors` both follow input
order. Existing snapshot tests continue to pass — they had been passing
only coincidentally because trivial sync paths happened to push in input
order; now the guarantee is structural.
For the atomizer sort fixes, existing snapshot tests pass (jest 54/54,
vitest 7/7, vite 22/22, cypress, playwright, eslint). The fixes are pure
ordering — no observable change when test discovery happens to already
be sorted.
## Related Issue(s)
<!-- No tracked issue — this came out of an audit of createNodes for
non-determinism. -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The Gradle e2e tests (`e2e/gradle/src/`) generate a project with `gradle
init`. For a `kotlin-application`, `gradle init` bakes a fixed Java
toolchain version (e.g. 21) into the generated build files.
CI machines provision Java via mise (currently Java 24). When the
generated project's pinned toolchain version differs from the installed
JDK, Gradle cannot find a matching local JDK and falls back to
**auto-provisioning** it through the foojay disco API. The e2e tests
then fail whenever foojay is slow or unavailable — e.g. the recent
`Could not HEAD 'https://api.foojay.io/...' Received status code 400` /
`pkg cache is currently be restored` failures, where only the kotlin
tests failed (the groovy template doesn't pin a toolchain).
## Expected Behavior
The generated e2e project pins its Java toolchain to the JDK that is
actually installed on the machine, so Gradle detects it locally and
never needs to download one.
`createGradleProject` now reads `java -version`, derives the installed
major version, and passes it to `gradle init` via `--java-version`. This
removes the dependency on the foojay API from the Gradle e2e suite.
## Related Issue(s)
N/A — CI flakiness fix.
## Current Behavior
The Nx repo has no Claude Code skill for multi-version support
compliance work on first-party plugins. Each fix or audit reapplies the
canonical shape from scratch by reading prior PRs, which is slow and
inconsistent.
## Expected Behavior
A reusable Claude Code skill lives under
`.claude/skills/multi-version-compliance/` and provides:
- **Fix mode**: drive a per-plugin compliance fix from a tracked task
(primary), with discovery fallback when no task exists.
- **Review mode**: code-level review of a compliance PR against the
canonical shape, with scope-drift check vs. the corresponding tracked
task.
## Implementation Details
Files added under `.claude/skills/multi-version-compliance/`:
- `SKILL.md` — entry points, mode workflows, critical rules, findings
doc template.
- `references/canonical-shape.md` — how a compliant plugin looks, plus
the code-level verification rubric used in review mode.
- `references/anti-patterns.md` — 18 numbered anti-patterns with
file:line citations.
- `references/gotchas.md` — edge cases (dist-tags, pnpm catalogs,
ecosystem lockstep, effective floor, cross-plugin coordination).
- `references/examples.md` — reference files, commits, and PRs to grep.
Reference PRs the skill models on: #35587 (`@nx/angular`), #35642
(`@nx/playwright`), #35670 (`@nx/cypress`), #35671 (`@nx/vitest`).
## Current Behavior
The [Code Ownership concept
page](https://nx.dev/docs/concepts/decisions/code-ownership#defining-code-ownership)
only describes the raw GitHub `CODEOWNERS` file. It never points readers
to the `@nx/owners` plugin, even though that plugin's whole purpose is
project-based code ownership.
## Expected Behavior
The "Defining code ownership" section now includes a tip aside linking
to the [`@nx/owners` plugin overview](/docs/reference/owners/overview),
explaining that the plugin lets you define ownership by project (using
`nx run-many` matcher syntax) and compiles it into a valid `CODEOWNERS`
file for GitHub, Bitbucket, or GitLab.
## Related Issue(s)
N/A -- follow-up to an internal discussion about cross-linking the
codeowners plugin from the general ownership page.
## Current Behavior
The `@nx/nx-plugin-checks` ESLint rule requires every migration entry in
a `migrations.json` file to declare an `implementation` or `factory`
property. Migration entries that rely solely on the `prompt` field
(introduced in #35638) trip the `missingImplementation` error even
though they are a valid alternative.
## Expected Behavior
The rule accepts migration entries that declare a `prompt` instead of
`implementation`/`factory`, and validates the prompt path resolves to a
file on disk (mirroring how `implementation` paths are validated).
## Implementation Details
- `validateEntry` now detects a sibling `prompt` property and skips the
`missingImplementation` error when `mode === 'migration'` and a `prompt`
is present.
- A new `validatePromptNode` mirrors `validateImplementationNode`: it
asserts the value is a string literal and the path resolves to an
existing file, applying the same `outDir → rootDir` source-mapping
fallback used for implementation paths.
- The gating is restricted to migration mode — for
`generator`/`executor` entries, a stray `prompt` does not satisfy the
`implementation` requirement.
- New `invalidPromptPath` messageId follows the same single-message
pattern used by `invalidImplementationPath`.
## Current Behavior
#35648 dropped the compat shim in
`normalizeTargetDependencyWithStringProjects` that handled the legacy
`projects: 'self'` and `projects: 'dependencies'` magic strings. Without
the shim, those configs fall through to `findMatchingProjects(['self'],
…)` which returns `[]`, so the `dependsOn` task is **silently dropped**
— no warning, no error, broken task graph.
Separately, building `packages/nx` with `nodenext` resolution triggered
TS5055 ("would overwrite input file") because `@nx/key`'s `.d.ts`
transitively imports `@nx/devkit` which uses a bare
`nx/src/devkit-exports` specifier. The exports map fell through to dist
`.d.ts`, pulling nx's own emit outputs back in as inputs.
## Expected Behavior
**Shim restored, with deprecation warning.** Legacy values keep working
(`projects: 'self'` → owner project, `projects: 'dependencies'` → `{
dependencies: true }`). A `TODO(v24)` marks the shim for clean removal
next major.
The warning uses the project graph source maps to attribute each
offending entry to its origin, then collapses output by source:
- **Single external plugin** (e.g. `@nx/jest/plugin` emitting `'self'`):
one workspace-wide warning per plugin pointing at upgrading it.
- **Single config file** (`nx.json` / `project.json` / `package.json`):
one per `project:target`, lists the offending entries, advises `nx
repair`.
- **Mixed**: per-target warning with tailored advice (both `nx repair`
and plugin upgrade where applicable).
### How the warning is constructed
1. **Snapshot**: when normalization sees `projects: 'self'` or
`'dependencies'`, `warnLegacyDependsOnMagicString` pushes a
shallow-cloned violation `{ index, originalEntry }` into a per-target
collector array (or fires inline if no collector).
2. **Annotate**: at flush, each violation is looked up in the
project-graph source maps to get `plugin` and `file` (which
plugin/config emitted that `dependsOn[i]`).
3. **Shared fields**: a local `shared()` helper returns the value every
annotated item agrees on (or `null` if mixed) for `value`, `plugin`, and
`file` — these drive the variant choice.
4. **Dedupe**: external-plugin warnings key on `plugin::<name>::<value>`
(one per plugin workspace-wide); everything else keys on
`target::<project>::<target>`. A module-level Set ensures each key fires
once per process.
5. **Title + body**: one of three title templates (plugin / file /
mixed) plus, unless it's the external-plugin case, one body line per
entry rendered as ` - <JSON> (<index>[ from <plugin>...])`.
**`customConditions` added** to `packages/nx/tsconfig.lib.json` so
nodenext resolves nx's self-referencing bare imports to source instead
of dist, fixing the TS5055 cascade.
## Related Issue(s)
Partially reverts behavioral break from #35648.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
When running continuous tasks (e.g. `nx e2e` with a dev server
dependency), shutting down via Ctrl+C or SIGTERM can leave orphaned
processes and leaked resources (e.g. Docker containers still running).
The JS `tree-kill` package kills all processes flat — parents die before
children can run cleanup handlers. Children also receive redundant
signals (OS SIGINT + orchestrator SIGTERM + per-process handlers),
causing race conditions and unpredictable shutdown ordering.
## Expected Behavior
Graceful, ordered shutdown: leaf processes are signaled first, allowed
to run cleanup handlers (e.g. `docker compose stop`), then parents are
signaled. At most 1 orchestrator-dispatched signal per child. No
orphaned processes, no leaked resources, cleanup subprocesses are
protected during shutdown. Grace period configurable via
`NX_PROCESS_KILL_GRACE_PERIOD` (ms, default 5000).
## Changes
### 1. Native graceful process tree shutdown (`process_killer/mod.rs`)
Two Rust napi functions replace the `tree-kill` npm package:
- **`killProcessTree`** (sync) — snapshots tree, signals all descendants
in one pass. For `process.on('exit')` handlers where only sync work can
run. SIGKILL fallback on Windows.
- **`killProcessTreeGraceful`** (async, bottom-up) — signals leaves
first, awaits exit, promotes parents, repeats. Tracks cleanup
subprocesses spawned during shutdown so they're protected from SIGKILL
escalation. Grace period configurable via `NX_PROCESS_KILL_GRACE_PERIOD`
(default 5000ms).
Bottom-up ordering is the core decision: leaves get to run cleanup
(`docker compose stop`, etc.) before their parents die.
### 2. Single source of truth for signal dispatch
Pre-PR, children received signals from multiple paths (OS SIGINT,
orchestrator SIGTERM, per-process handlers) — race conditions,
double-signaling, unpredictable shutdown order.
- `running-tasks.ts`: `exec()` → `spawn({ shell: true, detached: true
})`. Children get their own process group via `setsid()` and no longer
receive OS SIGINT directly — only the orchestrator dispatches.
- `task-orchestrator.ts`: single `handleSignal` for
SIGINT/SIGTERM/SIGHUP, persistent listeners (no `process.once` re-raise
before async cleanup completes), `stopRequested` guard, dedup of
already-killing continuous tasks.
- `forked-process-task-runner.ts`: SIGTERM/SIGHUP handlers removed;
orchestrator owns dispatch. SIGINT runs cleanup without
`process.exit()`.
- Per-task signal handlers in `running-tasks.ts` gated by
`NX_FORKED_TASK_EXECUTOR` — direct path has the orchestrator, only the
forked path needs them.
### 3. Async cleanup, awaited end-to-end
Forked runner `cleanup()` is async and awaited by the orchestrator.
`killPromise` is cached so concurrent callers (e.g. `cleanup()` after a
fire-and-forget kill from `cleanUpUnneededContinuousTasks`) await the
same in-progress kill rather than no-oping and leaving orphans.
### Migration follow-ups
Surfaced during integration; included to keep the migration green:
- `setEncoding('utf8')` on spawn stdio — `exec()` decoded utf8 by
default; `spawn()` emits Buffers. Rust TUI `appendTaskOutput` expects
strings → crashed with `StringExpected` without this.
- Per-task process listener cleanup — `RunningNodeProcess` previously
registered `exit` (and signals in forked path) without removing, causing
`MaxListenersExceededWarning` in workspaces with many parallel
run-commands tasks.
- `BatchProcess.kill()` swept from `tree-kill` to native
`killProcessTreeGraceful` (the one usage missed in the original sweep).
- Continuous task exit treated as fulfilled when no incomplete
dependents remain — bottom-up kill can let a child exit before its
parent `nx` gets SIGTERM; pre-fix the parent saw it as a crash.
- Memoize `(exited, exitCode, exitTerminalOutput)` in
`RunningNodeProcess`/`ParallelRunningTasks`/`SeriallyRunningTasks` —
late `getResults()`/`onExit()` callers resolve immediately. Non-zero
exits before `readyWhen` matches now surface as failures (pre-PR
silently hung awaiters when a dev server crashed at startup).
### Tests & dependency sweep
- Integration tests for napi bindings: tree traversal, SIGTERM handler
execution, grace period, SIGKILL fallback, dead PID.
- `tree-kill` removed from `package.json`.
- `pseudo-terminal.ts`, `node-child-process.ts`, `run-script.impl.ts`:
tree-kill → native killers.
- Updated run-commands tests for spawn assertions.
## Related Issue(s)
Fixes#32438
---------
Co-authored-by: Alex <alex@gogl.io>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Updates the credits pricing reference to show only vCPU counts instead
of full RAM specs. Removes the intermediate resource classes (Medium+,
Large+, Extra large+) to simplify the table. Added a note explaining
that memory is available in approximate 1:4 ratio per vCPU core.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: StalkAltan <StalkAltan@users.noreply.github.com>
## Current Behavior
The `@nx/cypress`, `@nx/jest`, `@nx/playwright` and `@nx/gradle` plugins
infer atomized CI `dependsOn` entries that include `projects: 'self'`.
That value is the default for `projects` and is no longer supported as
an explicit value.
## Expected Behavior
The plugins infer the same atomized CI `dependsOn` entries without
setting `projects: 'self'`.
## Current Behavior
Migration entries in a plugin's `migrations.json` can only declare an
`implementation` or `factory` pointing at TypeScript code. To ship a
Markdown prompt as the migration content, plugins currently have to
write a small generator that calls `tree.write(...)` to drop the `.md`
file into the workspace, which adds boilerplate and an extra layer for
every prompt-based migration.
## Expected Behavior
A migration entry can declare a `prompt` field — the relative path to a
Markdown file shipped with the plugin (resolved from the directory of
`migrations.json`) — as an alternative or complement to
`implementation`/`factory`.
When `nx migrate` collects migrations:
- The referenced `.md` file is extracted from the package and written to
the workspace under
`tools/ai-migrations/<package>/<prompt-relative-path>` (e.g.,
`tools/ai-migrations/@nx/expo/src/migrations/update-22-2-0/files/ai-instructions-for-expo-54.md`),
preserving the prompt's location within the package.
- The entry's `prompt` field is rewritten to that workspace-relative
path so users can review and edit the prompt before running migrations.
- Hybrid entries (`prompt` together with `implementation` or `factory`)
are preserved as-is on the generated entry.
- Each entry must declare at least one of `implementation`, `factory`,
or `prompt`; missing prompt files error out at collection time.
- The post-migrate "Next steps" output reminds the user to review and
tweak the generated prompts before running migrations.
## Implementation Details
- New `prompt-files.ts` module under
`packages/nx/src/command-line/migrate/` contains the new validation,
prompt extraction (registry and install paths), and workspace-write
logic.
- `MigrationsJsonEntry` gains an optional `prompt` field.
`GeneratedMigrationDetails` gains an optional `prompt` field and
`implementation` becomes optional to allow prompt-only entries.
- `Migrator.migrate()` returns a `promptContents` map alongside
`migrations`, keyed by `<package>::<promptRelPath>`. The workspace-write
step looks up content from that map and rewrites each entry's `prompt`
to its workspace-relative path.
## Current Behavior
`getJestProjectsAsync()` from `@nx/jest` throws `TypeError: yargs is not
a function` whenever a project has an inferred `nx:run-commands` test
target whose command runs `jest`. This affects workspaces using
`@nx/jest/plugin`, which is the default.
## Expected Behavior
`getJestProjectsAsync()` returns the list of jest projects without
error.
## Related Issue(s)
Fixes#35654
## Implementation Details
`packages/jest/src/utils/config/get-jest-projects.ts` imported
`yargs-parser` as a namespace (`import * as yargs from 'yargs-parser'`).
With `esModuleInterop: true`, that compiles to
`tslib.__importStar(require("yargs-parser"))`, which wraps the
CJS-callable export in a non-callable namespace object — so the
subsequent `yargs(match, ...)` call throws.
Switched to a default import (`import yargs from 'yargs-parser'`),
matching the pattern used by every other file in the repo that consumes
`yargs-parser`. Under `esModuleInterop: true` this compiles to
`__importDefault(require("yargs-parser")).default`, which is the
callable parser.
Verified by building `@nx/jest` on master vs. this branch and invoking
`getJestProjectsAsync()` against a synthetic graph with an inferred
`nx:run-commands` jest target — master throws the reported `TypeError`,
this branch returns the project list.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/gh-35654-5acce614)
<!-- polygraph-session-end -->
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
## Current Behavior
- `@nx/cypress` does not consistently enforce its declared supported
Cypress version range across its generators. Generators run without
verifying the installed Cypress version is at or above the supported
floor, so sub-floor workspaces can silently land in an unsupported
state.
- The init generator overwrites already-installed `cypress` versions on
re-runs. The shared `add-linter` helper overwrites already-installed
`eslint-plugin-cypress` pins.
## Expected Behavior
- Every generator entry point asserts the installed Cypress version is
`>= 13.0.0` before executing. Sub-floor installs are surfaced with an
actionable error. Above-known-ceiling installs fall through silently to
the latest known install constants (matching the pattern used by
`@nx/angular` and `@nx/playwright`), without throwing.
- Generators preserve user pins for both `cypress` (init) and
`eslint-plugin-cypress` (configuration's linter helper).
## Implementation Details
- New `assertSupportedCypressVersion(tree)` wrapper around the shared
`assertSupportedPackageVersion` devkit helper, called first in `init`,
`configuration`, `component-configuration`, and `convert-to-inferred`.
- Parameterized floor spec (`all-generators-enforce-floor.spec.ts`) that
asserts every entry in `generators.json` throws on a sub-floor install,
with `migrate-to-cypress-11` explicitly excluded — that generator exists
to migrate sub-floor (v8–v10) workspaces onto v11 and retains its
bespoke `assertMinimumCypressVersion(8)` guard.
- The shared `assertGeneratorsEnforceVersionFloor` test helper gains an
`excludeGenerators?: string[]` option for this kind of intentional
sub-floor migrator (separate `cleanup(devkit)` commit).
- `versions()` no longer throws on unknown majors; below-floor is caught
by the generator-level assert.
- `getInstalledCypressVersion`'s filesystem path now delegates to the
shared `getInstalledPackageVersion` from `@nx/devkit/internal` for
reliable resolution in pnpm strict mode and nested install layouts. The
tree path stays inline because Cypress's "null on missing" semantics
differ from the shared helper's fallback-on-missing.
- `init` flips its `keepExistingVersions` schema default to `true` and
uses `options.keepExistingVersions ?? true` at the call site;
`add-linter` passes `keepExistingVersions: true`. `configuration` and
`component-configuration` already pass `true` and are unchanged.
Migration generators are intentionally exempt — they exist to bump.
## Current Behavior
`packages/angular/migrations.json` contains migration entries and
`packageJsonUpdates` targeting Angular versions older than 18, along
with their backing source files under
`packages/angular/src/migrations/update-17-*/`, `update-18-*/`, and
`update-19-1-0/`.
## Expected Behavior
Stale migration entries targeting Angular <18 are removed, and the
now-unreferenced migration source files are deleted.
## Current Behavior
The `@nx/eslint-plugin/dependency-checks` rule's
`peerDepsVersionStrategy: 'workspace'` option unconditionally rewrote
**every** peer dependency to `workspace:*`, including external npm
packages like `react`, `axios`, etc. Running `pnpm install` (or any
equivalent) on the resulting `package.json` failed with
`ERR_PNPM_WORKSPACE_PKG_NOT_FOUND` because those packages do not exist
in the workspace. `eslint --fix` was therefore actively breaking working
workspaces that enabled the strategy.
Two fix branches in
`packages/eslint-plugin/src/rules/dependency-checks.ts` had the bug:
- The `missingDependencies` fix (around L299), which inserted new peer
entries at `workspace:*`.
- The version-mismatch fix (around L370-382), which rewrote any
non-`workspace:*` range to `workspace:*`.
## Expected Behavior
`peerDepsVersionStrategy: 'workspace'` should only produce `workspace:*`
ranges for peer dependencies that are **workspace-published packages**.
External npm packages must retain the normal installed-version logic so
the resulting `package.json` is installable.
This PR:
- Builds a `workspacePackageNames` `Set<string>` once from
`projectGraph.nodes[*].data.metadata.js.packageName`.
- Gates both `peerDepsVersionStrategy === 'workspace'` branches on
`workspacePackageNames.has(packageName)`. Workspace packages continue to
get `workspace:*`; external packages fall through to the existing
installed-version / catalog / root-package-json resolution.
Three existing tests in `dependency-checks.spec.ts` that encoded the
buggy behavior (asserting `workspace:*` for external packages) were
updated to reflect the correct behavior. Two new focused tests were
added to pin down each case explicitly (workspace package →
`workspace:*`; external package → installed range preserved).
## Related Issue(s)
Fixes#35318
Follow-up to #33417, which introduced `peerDepsVersionStrategy`.
## Current Behavior
`packages/devkit/src/utils/replace-package.ts` exports
`replaceNrwlPackageWithNxPackage`, a helper introduced to support
migrations that renamed `@nrwl/*` packages to `@nx/*`. Those migrations
ran years ago. The function is no longer imported anywhere in the
codebase — only the file's own spec references it, and it isn't
re-exported from `packages/devkit/index.ts` or
`packages/devkit/internal.ts`.
## Expected Behavior
The dead helper and its spec are deleted. Less surface area to maintain.
## Related Issue(s)
None.
## Current Behavior
Follow-ups to review feedback on the just-merged #35497:
- `--mode` help text reads as if defaults are unconditional — the
interactive-prompt exception is buried in a parenthetical.
- `--multi-major-mode` combined with `--run-migrations` is silently
ignored (`--mode` correctly throws).
- `--multi-major-mode=gradual` silently degrades to `direct` when the
registry can't return an incremental version, hiding the disabled safety
rail.
- `getInstalledLegacyNrwlWorkspaceVersion` bypasses the
cache-pollution-safe resolver `getInstalledNxVersion` uses (regression
risk from #35444).
- After a gradual or interactive incremental redirect, the re-run
instruction is logged once at the top of the run and quickly scrolls out
of view; the closing "Next steps:" block offers no guidance to continue
toward the originally requested target.
- The `MigrateMode` union is re-typed inline in 8 places; the `<
14.0.0-beta.0` era check is duplicated across multiple call sites.
- Internal "stepwise" wording reads awkwardly for non-native English
speakers.
- Test gaps: `--to @nx/workspace@higher`, `--mode=third-party` +
`--multi-major-mode=gradual`, `filterDowngradedUpdates` with `~` ranges
and peer-deps-only, the continuation contract for gradual/prompt
redirects.
## Expected Behavior
- `--mode` describe leads with the interactive-prompt behavior; defaults
follow.
- `--multi-major-mode` + `--run-migrations` throws symmetrically with
`--mode`.
- `--multi-major-mode=gradual` surfaces a `warn` when no incremental
option can be looked up before falling through to the requested target.
- `getInstalledLegacyNrwlWorkspaceVersion` routes through
`resolvePackageJsonWithoutCachePollution`, consistent with
`getInstalledNxVersion`.
- After a gradual or interactive incremental redirect, "Next steps:"
prints a continuation command targeting the user's original target,
preserving `--mode` (including `--mode=all`) and
`--multi-major-mode=gradual` when they were the effective opt-ins.
- `MigrateMode` exported once; era checks route through a new
`isLegacyEra` helper and the existing `resolveCanonicalNxPackage`.
- "Stepwise" replaced with "incremental" across copy, comments, and the
doc-URL constant.
- New tests for each coverage gap above.
## Implementation Details
- `isLegacyEra(version)` lives in `version-utils.ts`. Four era-check
sites in `migrate.ts` plus `isNxEquivalentTarget` and
`resolveCanonicalNxPackage` all route through it. The remaining
`14.0.0-beta.0` literal mentions are now docstring/inline comments only.
- `MULTI_MAJOR_MODE_FLAG` constant extracted to keep the flag spelling
in one place; `STEPWISE_DOC_URL` → `INCREMENTAL_UPDATE_GUIDE_URL`.
- `warnGradualUnavailable` centralizes the warn message; two call sites
(dist-tag resolution failure, no-step-resolved branch in gradual) pass
distinct reason strings.
- `MigrateMode` exported from `migrate.ts`; `multi-major.ts` imports it
as a `type`-only import (erased at emit; no runtime cycle).
- `maybePromptOrWarnMultiMajorMigration` returns `MultiMajorResult = {
chosen: string; originalTarget?: string; gradual?: boolean }` so
dist-tag resolution alone isn't mistaken for a redirect, and so callers
can distinguish gradual-mode redirects (safe to propagate
`--multi-major-mode=gradual`) from interactive-prompt picks (don't lock
the user in).
- `GenerateMigrations` gains `originalTargetVersion?: string` and
`multiMajorMode?: MultiMajorMode` to thread the continuation contract
from `parseMigrationsOptions` to the Next Steps composer.
- `logGradualStep` is title-only; the re-run guidance lives in Next
Steps where it's adjacent to the other re-run lines and won't scroll out
of view.
## Current Behavior
The community-plugin submission criteria in
`extending-nx/publish-plugin.mdoc` state that `@nx/devkit` must be
listed as a `dependency`, but give no rationale. The guide is also
silent on whether to list the `nx` package itself — leading some
submitters to add it as a `peerDependency`, which the first-party
plugins never do.
## Expected Behavior
The publish-plugin guide:
- Explicitly notes that `@nx/devkit` should be a `dependency`, **not** a
`peerDependency`.
- States that the `nx` package itself should **not** be listed as a
dependency or peer dependency.
- Includes a short aside explaining why: `@nx/devkit` has no singleton
state (unlike React/ESLint/Babel ecosystems where peer deps are the
norm), and `nx` is always provided by the user's workspace.
This brings the documented criteria in line with what the first-party
`@nx/*` plugins already do.
## Related Issue(s)
N/A — drive-by docs improvement noticed while reviewing a
community-plugin submission.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
The Gradle project graph plugin is at version 0.1.20.
## Expected Behavior
The Gradle project graph plugin is bumped to version 0.1.21, with the
corresponding migration files created
so that users upgrading Nx will automatically get the new plugin
version.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
Currently our input globs are set without transitive: true, this means
during hashing the input only walks one hop by default — it looks at the
direct dependents, not the chain.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Add `transitive: true` to all dependent task output files.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #NXC-4461
## Current Behavior
`spread.test.ts` flakes on the middle-spread case ("should resolve
spread when middle specified plugin contains '...'"). CI logs showed two
distinct project-graph recomputes running back-to-back for one `show
project` invocation, with the stale IIFE's response — built against an
older `nx.json` snapshot — being returned to the client. The result:
`build` target undefined for the project the specified plugins had just
created, blowing up the assertion downstream.
Root cause: `cachedSerializedProjectGraphPromise` was last-kickoff-wins.
Two concurrent IIFEs (one watcher-triggered, one request-triggered)
could overlap; the later kickoff replaced the cached pointer with its
own promise, but if the *earlier* IIFE finished computing against a
stale plugin set, its result still flowed back to the awaiting caller
through the chain. There was no guard ensuring the committed graph was
built against the current `nx.json`.
Two adjacent bugs surfaced during the investigation:
- The Rust watcher's `force_flush_pending` handler waited only 5ms for
in-flight notify events before snapshotting. Too short on macOS FSEvents
(and tight inotify cases) — events that `fs::write` had just produced
could miss the window.
- The Rust watcher diagnostics lived behind a bespoke
`NX_DAEMON_DEBUG_WATCHER=1` env gate using `eprintln!`, separate from
the existing `tracing` + `NX_NATIVE_LOGGING` infrastructure the rest of
`native/` uses.
## Expected Behavior
**Freshness gate on `kickOffRecompute`**
(`project-graph-incremental-recomputation.ts`). Each IIFE reads
`nx.json` once at kickoff (inside the IIFE, before any await), hashes
the `plugins` field as a snapshot, and passes the same `nx.json` object
to `getPluginsSeparated` so the plugin set and the snap reflect the same
disk state. Two checkpoints (after `getPluginsSeparated` and after
`processFilesAndCreateAndSerializeProjectGraph`) compare current disk to
the snap; on mismatch the IIFE logs `Discarding stale recompute result`,
returns the cached pointer so awaiters chain to the successor, and (if
it's still the cached one) starts the successor recompute. Closes the
spread-test race at the source.
**Per-OS force-flush grace** (`watcher.rs`). New `FORCE_FLUSH_GRACE`
constant: 50ms on macOS, 10ms on Linux. Used in the in-handler
`recv_timeout` so the kernel→notify-crate hop has room to deliver
in-flight events before the snapshot. Fixes the related
`force_flush_pending_captures_in_flight_writes` rust unit-test flake.
**Required `nxJson` parameter on `getPlugins` / `getPluginsSeparated`**.
Previously optional and defaulted to `readNxJson(root)`, which meant
callers that already held an `nxJson` were double-reading with a race
window between the two reads. Now required, forcing each caller to think
about which snapshot the plugin loader uses. All in-repo callers
updated.
**Rust watcher diagnostics on `tracing`**. Per-event `ingest path=…`
moved to `trace!` and now sits *after* the existing filterer
(Access-event flood was already dropped there); force-flush END +
backfill summaries at `debug!`. Bespoke `NX_DAEMON_DEBUG_WATCHER` env
gate removed. Users opt in via
`NX_NATIVE_LOGGING="nx::native::watch=trace"` (or `=debug`), same as
every other native module.
**Always-on TS seam logs**. Kept on the daemon: `[watcher]
routeWorkspaceChanges batch:` at the Rust→TS boundary (folded together
with the dropped-paths summary) and the human-readable `Recomputing
project graph…` / `Reusing in-memory cached project graph…` decision
log. Both write to the daemon log file only, providing a complete trail
through watcher → routing → recompute decision for future
investigations.
**Unit test in `project-graph-incremental-recomputation.spec.ts`** that
exercises the freshness gate end-to-end — verified to fail without the
gate and pass with it. Mocks `getPluginsSeparated` only to park the
first IIFE between its synchronous snap and its commit (a
millisecond-scale timing gap that's hard to control without mocks); the
gate logic itself is real.
## Related Issue(s)
Follow-up fix to #35646 (watcher hardening). No linked issue.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
<!--
_[Please make sure you have read the submission guidelines before
posting an
PR](https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#submit-pr)_
# Community Plugin Submission
Thanks for submitting your Nx Plugin to our community plugins list. Make
sure to follow these steps to ensure that your PR is approved in a
timely manner.
## Plugin Requirements
Before you submit your plugin to be listed in our registry, it needs to
meet the following requirements:
- Run some kind of automated e2e tests in your repository
- Include `@nx/devkit` as a `dependency` in the plugin's `package.json`
- List a `repository.url` in the plugin's `package.json`
i.e.
```
{
"repository": {
"type": "git",
"url": "https://github.com/nrwl/nx.git",
"directory": "packages/web"
}
}
```
Note: We reserve the right to remove unmaintained plugins from the
registry. If the plugins become maintained again, they can be
resubmitted to the registry.
## Steps to Submit Your Plugin
- Use the following commit message template: `chore(core): nx plugin
submission [PLUGIN_NAME]`
- Update the `astro-docs/src/content/approved-community-plugins.json`
file with a new entry for your plugin that includes `name`, `url`,
`description`:
Example:
```json
// astro-docs/src/content/approved-community-plugins.json
[{
"name": "@community/plugin",
"url": "https://github.com/community/plugin",
"description": "This plugin provides the following capabilities."
}]
```
Once merged, your plugin will be available when running the `nx list`
command, and will also be available in the Plugin Registry on
[nx.dev](https://nx.dev/docs/plugin-registry)
-->
# Community Plugin Submission
## @anarchitects/nx-typeorm
<!--
Describe what your plugin is and what is its goal or issues it
addresses. If you don't provide a description, we will not merge your
PR.
Is it focused on a technology, tooling or behaviour? Does the plugin
provide generators, executors or graph support?
Do you know who is already using the plugin? Mention who is the author
of the plugin.
-->
Nx plugin for TypeORM integration in Nx backend applications and
libraries. It provides:
- nx add / init setup for minimal TypeORM dependencies.
- bootstrap scaffolding for app runtime datasource wiring and library
infrastructure-persistence templates.
- name-first scaffold generators for TypeORM file creation workflows.
- inferred database targets via createNodesV2.
- thin executors that wrap TypeORM CLI workflows.
---------
Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
## Current Behavior
`@nx/workspace` builds to the shared workspace-root
`dist/packages/workspace/` directory, uses CommonJS
`module`/`moduleResolution`, and has no `exports` map. Other Nx packages
and external plugins reach into `@nx/workspace/src/*` subpaths for
internal utilities.
## Expected Behavior
`@nx/workspace` follows the same local-dist build pattern as `nx` and
`@nx/devkit`:
- Builds to `packages/workspace/dist/` instead of
`dist/packages/workspace/`.
- `tsconfig.lib.json` uses `module`/`moduleResolution: "nodenext"` with
`composite`, `rootDir: "."`, `declarationDir: "dist"`.
- `package.json` declares an `exports` map matching `@nx/devkit`'s
locked-down surface — only named entry points, no `./src/*` wildcard.
- `generators.json` / `executors.json` factory/schema paths rewritten
`./src/...` → `./dist/src/...`. Workspace dev still works via the
`tryResolveFromSource` fallback in
`packages/nx/src/config/schema-utils.ts`.
- `README.md` → `readme-template.md`; build command writes the rendered
README to `packages/workspace/README.md`.
- `project.json` adds `release.version` config
(`preserveLocalDependencyProtocols: true`, matching nx/devkit).
- `scripts/nx-release.ts`: adds `packages/workspace` to
`packagesToReset`.
Internal-leak cleanup so the locked-down exports map doesn't break
first-party callers:
- `TypeScriptCompilationOptions` + `compileTypeScript` moved from
`@nx/workspace` to `@nx/js` (the package that owns TypeScript
compilation).
- `@nx/remix` inlines `directoryExists` (was a one-line re-export
wrapper).
- `@nx/rspack` drops the Nx 15.7-era
`@nx/workspace/src/utils/create-ts-config` fallback.
- e2e helpers source `angularDevkitVersion` from `@nx/angular/src/utils`
instead.
- `@nx/workspace` and `@nx/remix` no longer declare `@nx/workspace` deps
they don't use.
- Adds a preflight step to the `dist-build-migration` skill that warns
about `workspace:*` deps on not-yet-migrated packages.
## Breaking Changes
**`@nx/workspace/src/*` subpath imports are no longer supported.** The
`exports` map no longer declares a `./src/*` wildcard. ~150 public
consumers across GitHub use these subpaths today (the largest cluster is
`src/utilities/fileutils` with ~60 hits). Migration:
- `@nx/workspace/src/utilities/fileutils` (`directoryExists`,
`fileExists`, `isRelativePath`, `createDirectory`) — these are thin
re-exports from `nx/src/utils/fileutils`. Inline with `node:fs`
(`statSync(p).isDirectory()` for `directoryExists`) or import from
`@nx/devkit` where equivalent.
- `@nx/workspace/src/utilities/typescript/compilation` — moved to
`@nx/js`. Internal callers should import from there; if you were
depending on the type externally, copy it or use the equivalent from
`@nx/js`.
- `@nx/workspace/src/utils/versions` — values are duplicated across
packages; reimplement against the version of Nx you're targeting.
- Other subpaths — check the `@nx/workspace`'s public `index.ts`
re-exports first; otherwise reimplement.
**`@nx/workspace/tasks-runners/default` now throws when invoked.** This
module was an alias for `nx/src/tasks-runner/default-tasks-runner`. The
Nx 17 (`use-minimal-config-for-tasks-runner-options`) and Nx 21
(`remove-custom-tasks-runner`) migrations rewrite/remove legacy
`tasksRunnerOptions` entries. If your `nx.json` still references
`@nx/workspace/tasks-runners/default`, replace it with
`nx/tasks-runners/default`.
## Related Issue(s)
Part of the ongoing migration of Nx packages to the local-dist build
layout (following `nx` and `@nx/devkit`).
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
`rust-toolchain.toml` pins Rust to `1.94.0`. This blocks upgrading
`sysinfo` to `0.39.x`, which requires Rust `1.95` and brings upstream
support for several cgroup limit features that nx currently needs to
implement locally.
## Expected Behavior
Toolchain bumped to `1.95.0`. Verified:
- `cargo build -p nx` produces zero new warnings vs. 1.94.
- `cargo build -p nx --all-targets` (compiles tests too) produces zero
new warnings vs. 1.94 — identical warning set.
- Clippy delta: +11 new clippy lints (style suggestions only — not
gating, none correctness-related).
## Related Issue(s)
N/A — maintenance/hygiene change. Unblocks the sysinfo bump that, in
turn, lets PR #35622 drop its memory-side cgroup parsing in favor of
upstream `Process::cgroup_limits()` + parent cgroup memory walking
(sysinfo PRs
[#1643](https://github.com/GuillaumeGomez/sysinfo/pull/1643) and
[#1651](https://github.com/GuillaumeGomez/sysinfo/pull/1651)).
## Current Behavior
Running `@nx/eslint:convert-to-flat-config` against a workspace with
legacy `.eslintrc` configs produces flat configs that don't faithfully
reflect the source, and leaves stale references to the deleted files
behind:
- Every converted config — root and leaves — gets an unconditional `{
ignores: ['**/dist', '**/out-tsc'] }` block prepended, even when the
legacy config never ignored those paths.
- Leaves whose only compat-looking field was a custom `parser` (e.g.
`jsonc-eslint-parser` for `package.json`) get a full `@eslint/eslintrc`
FlatCompat scaffold — `FlatCompat` import, `dirname`, `fileURLToPath`,
`js`, the `const compat = new FlatCompat({...})` block — that's never
referenced.
- Rule option values that embedded legacy filenames (most notably
`@nx/dependency-checks`'s `ignoredFiles`) keep pointing at
`.eslintrc.json` / `.eslintrc.base.json` / `.eslintignore` after those
files are deleted.
- `nx.json` gets the new `eslint.config.<fmt>` entry added to the lint
target and `production` named input, but legacy `.eslintrc.json` /
`.eslintignore` entries stay in place. Other `targetDefaults` inputs and
`namedInputs` are never rewritten.
- `project.json` files aren't touched at all — every `targets[*].inputs`
or `namedInputs[*]` that referenced the deleted files stays stale.
- Configs with `extends: '../../.eslintrc'` (extensionless — ESLint's
JSON-by-convention form) aren't supported: the source file isn't
converted, and if a leaf extends one that was converted elsewhere the
leaf ends up with `...compat.extends('../../.eslintrc')` pointing at
nothing.
- Legacy `ignorePatterns` entries that started with `!` were being
dropped wholesale, destroying real un-ignores like `['dist/**',
'!dist/keep.js']` that flat config still honors.
- `files` / `excludedFiles` arrays with source-side duplicates (e.g.
`package.json`, `./generators.json`, `./executors.json` repeated in the
same override) are emitted with the duplicates intact.
## Expected Behavior
Conversion preserves the semantics and intent of the legacy config,
rewrites everything that referred to the deleted files, and drops noise
that serves no purpose in flat config:
- The implicit `**/dist` / `**/out-tsc` ignore block is no longer added.
If the legacy config didn't ignore those paths, the converted one
doesn't either — migration stays faithful to the source. (`lint-project`
still adds them for fresh scaffolding, where there's no source intent to
preserve.)
- Parser-only overrides emit a clean flat entry with a hoisted static
parser import and no unused FlatCompat scaffold. Leaf configs shed the
dead boilerplate.
- Rule option values that embed `.eslintrc[.base].json` or
`.eslintignore` are rewritten to the flat-config equivalent.
Accidentally collapsed duplicates inside string arrays are deduped.
- `nx.json` is swept generically — every `targetDefaults[*].inputs` and
`namedInputs[*]` gets legacy filenames rewritten, with dedup so the
rewrite doesn't collide with freshly-added entries. `{ fileset }` shapes
are handled; non-path shapes (`runtime`, `env`, `externalDependencies`,
`dependentTasksOutputFiles`, named-input refs) are left untouched.
- Every project's `project.json` receives the same sweep across
`targets[*].inputs` and `namedInputs[*]`.
- Extensionless `.eslintrc` is now a convertible source, and `extends`
paths that point at `../../.eslintrc` (or `.eslintrc.base`) are
rewritten to the generated base config and imported as `baseConfig`.
- Real negated `ignorePatterns` like `!dist/keep.js` survive the
conversion; only the legacy `**/*` / `!**/*` / `node_modules` catch-alls
are dropped.
- `files` / `excludedFiles` arrays are deduped after glob mapping, so
source-side duplicates and glob-normalization collisions collapse.
## Current Behavior
`create-nx-workspace --preset=<name>` silently installs any npm package
matching the name when the preset is not a built-in Nx preset. A user
following a tutorial that suggested `--preset=core` ended up installing
[`core`](https://www.npmjs.com/package/core) — an unrelated ancient
package — without any warning. This is a supply-chain risk: a typo or
malicious preset name could execute untrusted code with no user-visible
signal.
The path through the code:
1. `create-workspace.ts` calls
`getPackageNameFromThirdPartyPreset(preset)`.
2. If the preset isn't in the built-in `Preset` enum, the helper returns
the package name as long as `validateNpmPackage` accepts it.
3. `createPreset` then installs and runs the resolved package — no
confirmation, no warning.
## Expected Behavior
Before installing a third-party preset npm package, surface the npm
package name and ask the user to confirm.
- **Interactive (TTY) mode**: prompt with `enquirer.autocomplete`,
defaulting to **No**, so a reflex `Enter` is safe.
- **`--interactive=false`, CI, or AI-agent contexts**: skip the prompt
(we can't read a TTY) but always emit an `output.warn` describing the
package about to be installed. Automated workflows like
`--preset=@nx-go/nx-go --no-interactive` keep working, but the warning
still appears in logs.
The confirmation runs before sandbox creation — declining doesn't waste
work.
### Out of scope
The original report also suggests validating that the package is
genuinely an Nx plugin. That requires a registry round-trip and a
definition of "is an Nx plugin" (peerDeps on `nx`? keyword? presence of
a preset generator?). The confirmation step alone closes the
silent-install vector; the deeper validation is left as a follow-up.
## Related Issue(s)
Fixes
[NXC-3331](https://linear.app/nxdev/issue/NXC-3331/warn-users-before-installing-unknown-npm-packages-with-preset).
## Current Behavior
In `examples-angular-rspack-csr-tailwind:build`, Tailwind v4's automatic
source-detection scanner (`@tailwindcss/oxide`) walks the project root
and reads every file with a known extension (`.html`, `.js`, `.mjs`,
`.cjs`, `.ts`, `.tsx`, `.vue`, …) honoring `.gitignore`.
`eslint.config.mjs` sits at the project root with a scanned extension,
so Tailwind reads it looking for utility class names. The Nx build
target excludes `eslint.config.@(js|cjs|mjs|ts|cts|mts)` from its
inputs, so the read shows up as an undeclared-read sandbox violation:
```
examples/angular-rspack/csr-tailwind/eslint.config.mjs
```
Other root-level files Tailwind also scans (e.g. `rspack.config.js`)
don't trigger violations because they are declared as inputs by their
respective plugins. `eslint.config.mjs` is unique in being both scanned
and excluded.
## Expected Behavior
Tailwind should not scan eslint config. Adding `@source not
"../eslint.config.mjs";` to `src/styles.css` tells Tailwind to skip that
file during its scan. No more sandbox violation; the eslint config
exclusion in the build inputs stays correct (eslint config has no effect
on build output).
## Related Issue(s)
N/A — sandbox-report finding, not a tracked issue.
## Current Behavior
`nx migrate <target>` processes every entry in the resolved
`packageJsonUpdates`, including third-party packages outside the
target's `nx.packageGroup`. There is no way to scope a run to Nx itself,
no safety rail when jumping multiple major versions, and the cascade can
propose downgrades for deps that have already been bumped past the
historical pin.
## Expected Behavior
Adds two new flags to `nx migrate`, plus a downgrade-prevention
safeguard and a bare-invocation default.
### `--mode={first-party|third-party|all}`
Scope which packages get migrated. Only valid when the target is the
canonical Nx package (`nx`, or `@nx/workspace` as an era-aware alias).
- **`first-party`** — bumps Nx and the packages in its
`nx.packageGroup`; third-party deps are left untouched.
- **`third-party`** — anchors at the installed Nx version and walks
`--from=nx@0.0.0 --exclude-applied-migrations` internally, surfacing
third-party catch-up that earlier first-party-only runs left behind.
- **`all`** (default) — everything; matches existing behavior.
Defaults to `all` outside a TTY; prompts in an interactive terminal when
the target is canonical Nx. Rejects `--from`,
`--exclude-applied-migrations`, and out-of-bounds `--to nx@…` (or a
higher `nx@…` positional) when `mode=third-party` — those already imply
going past the installed version.
### `--multi-major-mode={direct|gradual}`
Handles the case where the target jumps two or more major versions from
installed.
- **Interactive (TTY):** prompts with the smallest-step recommendations
— `latest in current major` (when at least a minor ahead of installed),
`next major`, and `migrate directly to <target>`. The smallest available
step is tagged `[recommended]`.
- **Non-interactive:** warns and proceeds with the requested target.
- **`--multi-major-mode=direct`** (or `NX_MULTI_MAJOR_MODE=direct`) —
skip the prompt/warn and migrate directly to the requested target.
- **`--multi-major-mode=gradual`** (or `NX_MULTI_MAJOR_MODE=gradual`) —
skip the prompt and pick the smallest recommended step automatically;
re-run `nx migrate` to continue toward the originally requested target.
Falls back silently to the requested target when no stepwise option is
available.
### Downgrade prevention
`packageJsonUpdates` entries that would move a workspace dep backwards
from its current pin are now dropped from the proposed updates. Common
case: a workspace that manually bumped a dep past the version a
historical `packageJsonUpdates` entry pins.
### Bare invocation
`nx migrate` with no positional now defaults to `nx@latest` instead of
erroring, then runs through the mode + multi-major flow.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Workspace `package.json` files declare common dependencies with mixed
literals and ranges (e.g. `webpack: 5.101.3` in root vs `^5.101.3` in
plugin consumers). pnpm materializes the resulting permutations as
separate peer-resolution variants in the lockfile, even when every
consumer resolves to the same version.
## Expected Behavior
Declarations route through `pnpm-workspace.yaml` catalogs. Redundant
peer-resolution variants collapse and the catalog becomes the single
source of truth for cross-package version alignment. Lockfile shrinks by
~1,980 lines.
## Implementation Details
### Catalog changes
- **New named catalogs:** `catalogs.css` (postcss family + `less-loader`
/ `sass-loader`), `catalogs.tailwind` (`@tailwindcss/postcss`,
`tailwind-merge`), `catalogs.vite` (`vitest`).
- **Default `catalog:` additions:** `@babel/core`, `@heroicons/react`,
`@module-federation/enhanced`, `@playwright/test`, `@svgr/webpack`,
`ajv`, `gpt3-tokenizer`, `http-proxy-middleware`, `http-server`,
`jsonc-parser`, `next-seo`, `ora`, `react-textarea-autosize`, `rxjs`,
`tmp`, `tree-kill`, `verdaccio`, `webpack`, `webpack-dev-server`.
- **`catalogs.eslint` additions:** `@typescript-eslint/type-utils`,
`@typescript-eslint/utils`, `eslint-config-prettier`.
- **Existing-catalog routings:** `semver`, `tslib`, `typescript` routed
at additional consumers.
### Per-dep notes
- `ora` cataloged at `^5.3.0` — caret preserves `<6.0.0` since `ora` 6+
is ESM-only and Nx packages are CommonJS.
- `@module-federation/enhanced` (`^2.3.3`) and `verdaccio` (`^6.3.2`)
cataloged at security floors (2.3.1 had a compromised `axios`;
`verdaccio` <6.3.2 carried a vulnerable `handlebars`).
### Out of scope
- **Transitive-only drift** in `yaml`, `less`, `esbuild` — would require
`pnpm.overrides`, not catalog routing.
- **`packages/vitest` peer range** `^1 || ^2 || ^3 || ^4` — left alone
(tightening is breaking for downstream consumers).
- **Peer ranges spanning multiple majors** (`next`, `@nuxt/*`,
`@rsbuild/core`, `metro-*`, `nx`, `@typescript-eslint/parser`, etc.) —
intentional cross-major support contracts.
- **Cross-major direct declarations** (`storybook`, `loader-utils`,
`memfs`, `cypress`, `eslint`, `vite`, etc.) — each is its own migration.
- **`@nx/devkit` / `@nx/js` literal `23.0.0-beta.4`** in
`tools/workspace-plugin` — stale workspace reference; not
catalog-fixable.
- **`verdaccio` peerDep `^6.0.5`** in `packages/js` — not tightened to
security floor to avoid changing the peer constraint for downstream
consumers.
### Note on `pnpm dedupe`
Evaluated separately and abandoned — `pnpm dedupe` actively bumps minor
versions across declared ranges, which cascaded into 21 plugin `:test`
snapshot regressions when attempted (#35628). The targeted catalog
routing here avoids that class of change.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`jest-haste-map` crawls `<rootDir>` to build a module map and indexes
any file matching `moduleFileExtensions`. When build outputs land inside
the project (e.g. `<projectRoot>/dist` or `<projectRoot>/out-tsc`,
common in TS solution setups but possible in any workspace), those
emitted `.js`/`.d.ts` files get stat'd by haste-map. The `@nx/jest`
preset doesn't exclude them, so consumers either see redundant
filesystem work or — when running under the Nx sandbox — get flagged for
unexpected reads from undeclared task inputs.
## Expected Behavior
The `@nx/jest` preset excludes `<rootDir>/dist/` and
`<rootDir>/out-tsc/` from `modulePathIgnorePatterns` by default. Both
directories are conventional project-local build outputs (Nx's TS
solution setup already treats them as the canonical pair to exclude from
tsconfig). Consumers that need to scan those directories can override
the field in their own jest config.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
- `@nx/playwright` generators silently fall through to the latest
install constants when a workspace has `@playwright/test` detected below
the supported floor (`1.36.0`). Generators write incompatible config
rather than failing fast.
- The init generator overwrites already-installed `@playwright/test`
versions on re-runs. The configuration generator's linter helper
overwrites already-installed `eslint-plugin-playwright` pins.
- `nxE2EPreset` auto-injects the `blob` reporter in CI regardless of
installed Playwright version — workspaces on 1.36.x silently pick up a
reporter that Playwright doesn't recognize and fail at test run time
with an unhelpful error.
- The `@nx/playwright:merge-reports` executor invokes a Playwright CLI
subcommand that doesn't exist on 1.36.x and surfaces a confusing CLI
error rather than a clear remediation message.
- Fresh installs land on `^1.36.0`, a version that pre-dates the `blob`
reporter and `merge-reports` CLI, so users adopting the plugin don't get
the full feature surface out of the box.
## Expected Behavior
- Every generator entry point asserts the supported floor up front via a
shared `assertSupportedPackageVersion`, throwing a standardized error
naming the package, installed version, and supported floor.
- Generators preserve user pins for both `@nx/playwright` and
`@playwright/test`. The configuration generator's linter helper
preserves a user-pinned `eslint-plugin-playwright`.
- `nxE2EPreset` skips auto-injecting the `blob` reporter on Playwright <
1.37.0 by default; an explicit `generateBlobReports: true` on an
unsupported version throws with a clear remediation message.
- The `merge-reports` executor fails fast with a clear "requires
Playwright >= 1.37.0" message when invoked against a sub-1.37 workspace.
- Fresh installs land on `^1.37.0` so users get the full feature set
(blob reporter + merge-reports CLI). The peer dep stays at the existing
`^1.36.0` — no regression for existing workspaces.
## Implementation Details
Bundled into this PR:
- **`@nx/devkit/internal` shared helpers** (consumed by all subsequent
plugin compliance PRs):
- `getInstalledPackageVersion(packageName)` — FS-based, for
executor/runtime contexts.
- `getDeclaredPackageVersion(tree, packageName, latestKnownVersion?)` —
tree-based, normalized, with `latest`/`next` fallback support.
- `assertSupportedPackageVersion(tree, packageName,
minSupportedVersion)` — the floor-enforcement guard.
-
**`@nx/devkit/internal-testing-utils.assertGeneratorsEnforceVersionFloor`**
— parameterized spec helper asserting every generator in a plugin's
`generators.json` throws on sub-floor detection.
- **`@nx/angular` adoption** — `assertSupportedAngularVersion`,
`getInstalledAngularVersion`, the executor-side
`getInstalledAngularVersionInfo`, and the angular all-generators floor
spec all delegate to the shared helpers. No behavioral change.
- **`@nx/playwright`** — adopts the helpers, adds the feature-vs-version
gates for blob/merge-reports, fixes user-pin preservation.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The `@nx/react`, `@nx/react-native`, and `@nx/expo` component generators
expose a `--js` option to generate JavaScript files instead of
TypeScript. The option was deprecated in #29111 (targeted for removal in
Nx v21) in favor of including the file extension directly in the `path`
argument:
```sh
nx g @nx/react:component mylib/src/lib/foo.jsx
```
We are now on Nx v23 — past the deprecation window — but the option
still exists.
## Expected Behavior
The `--js` option is removed from the component generators in
`@nx/react`, `@nx/react-native`, and `@nx/expo`. Users include the
desired file extension directly in the `path` argument; the path's
extension drives whether `.tsx`, `.jsx`, `.ts`, or `.js` files are
generated. When no extension is provided, the generators default to
`.tsx`, matching prior behavior.
The library generators in those three packages still keep their own
`--js` option (out of scope for this change). Internally they now encode
`.js` into the `path` they pass to the component generator instead of
forwarding the now-removed `js` flag.
### BREAKING CHANGE
The `--js` option has been removed from:
- `@nx/react:component`
- `@nx/react-native:component`
- `@nx/expo:component`
Migration: include the file extension in the `path` argument, e.g. `nx g
@nx/react:component mylib/src/lib/foo.jsx`.
## Related Issue(s)
Linear:
[NXC-3665](https://linear.app/nxdev/issue/NXC-3665/common-remove-js-option-from-component-generators)
## Current Behavior
Running `nx mcp` from a directory that is not part of an Nx workspace
prints the workspace-not-found banner and exits with code 1, **before**
the `mcp` command's handler is ever invoked.
This breaks MCP clients (e.g. Codex CLI) that spawn `npx nx mcp` over
stdio JSON-RPC. The banner is written to **stdout**, corrupting the
JSON-RPC stream, and the process exits before the MCP `initialize`
handshake completes. Clients surface this as:
> MCP startup failed: handshaking with MCP server failed: connection
closed: initialize response
Reproduction:
```
$ cd /tmp/empty-dir
$ npx nx mcp
NX The current directory isn't part of an Nx workspace.
...
# exit 1, written to stdout
```
## Expected Behavior
`nx mcp` should be able to run outside of an Nx workspace, the same way
`nx init`, `nx configure-ai-agents`, and `nx graph` already can. The
`mcp` command delegates entirely to `nx-mcp@latest` via the package
manager's `dlx`, and `nx-mcp` already handles non-workspace directories
correctly.
## Root Cause
`packages/nx/bin/nx.ts` contains a hard-coded allow-list of commands
that bypass the `!workspace → handleNoWorkspace()` guard. The list
currently includes `new`, `_migrate`, `init`, `configure-ai-agents`, and
`graph && !workspace`. The `mcp` command was added to the command
registry in `nx-commands.ts` but was never added to this allow-list, so
execution hits the workspace check first and exits before reaching
`mcpHandler`.
## Fix
Add `'mcp'` to the allow-list in `packages/nx/bin/nx.ts`. The handler
uses `workspaceRoot` only as the `cwd` for spawning `nx-mcp@latest`, and
`workspaceRoot` already gracefully falls back to `process.cwd()` when no
workspace is detected.
```diff
process.argv[2] === '_migrate' ||
process.argv[2] === 'init' ||
process.argv[2] === 'configure-ai-agents' ||
+ process.argv[2] === 'mcp' ||
(process.argv[2] === 'graph' && !workspace)
```
## Related Issue(s)
Fixes: discovered via nx-console / Codex CLI MCP integration — `nx mcp`
configured as an MCP server in a non-workspace cwd fails to start.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nx-mcp-error-in-non-repo-path-e0a28-a87e5654)
<!-- polygraph-session-end -->
---------
Co-authored-by: Max Kless <maxk@nrwl.io>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
Issues with matching on Windows paths because there are hardcoded
forward slashes `/`
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Works on Windows or otherwise
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes#34987
Want to note that while the Unit tests are all passing, I don't have a
way to test this on a unix machine myself. Also, I'm not super familiar
with gradle plugins, how can I "export" my changes here to try in my own
project that uses the plugin?
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
_This PR description was generated by Amp._
## Current Behavior
The TUI task sidebar appears scrolled down by one entry on render,
hiding the first continuous task from view.
<img width="1167" height="829" alt="Screenshot 2026-03-03 at 10 56 09
AM"
src="https://github.com/user-attachments/assets/83421f89-3be9-4f80-80b2-c57bd0c2c161"
/>
Here you can see that `watch-deps` is running in addition to `dev`.
## Expected Behavior
All continuous tasks should be visible in the sidebar without any
initial scroll offset.
## Related Issue(s)
N/A (discovered via visual inspection)
## Details
The viewport height calculation in `tasks_list.rs` subtracted 4 rows for
header overhead, but the actual overhead is only 3 rows:
1. `top_margin` (1 row)
2. Header content (1 row)
3. Spacing row (1 row)
This off-by-one caused the viewport to be 1 row too small, making the
task list appear scrolled down by one entry on initial render.
### Changes
1. **Unified overhead constant**: Introduced `TABLE_HEADER_OVERHEAD_ROWS
= 3` and `SCROLLBAR_Y_OFFSET = 2`, replacing scattered hardcoded values.
The scrollbar y-offset is 2 (not 3) because the scrollbar visually spans
from the spacing row downward, while the viewport only counts content
rows.
2. **Stale scroll correction**: Added logic in `set_viewport_height` to
reset `scroll_offset` to 0 when the viewport grows from a small
placeholder size (≤ 5) to the real terminal size, provided the selected
item is still visible from the top. This prevents stale scroll positions
set during initialization from hiding top items.
3. **Regression tests**: Added
`test_viewport_growth_resets_stale_scroll_offset` and
`test_viewport_growth_preserves_scroll_when_selection_not_visible` to
cover the stale scroll correction.
4. **Snapshot updates**: 14 snapshot files updated to reflect one
additional visible row.
### History
The `4` originated in commit `6541751a` (James Henry, Apr 2025) with the
comment "Reserve space for pagination and borders." However, the table
had no borders (`Block::default()` without `Borders`), and pagination
lived in its own layout chunk. The `4` was a rough estimate that was
never precisely derived from the actual header structure. When scrolling
replaced pagination in commit `1782e8c7` (Leosvel Perez Espinola, Sep
2025), the value was carried forward unchanged. The same commit also
introduced a separate `header_and_spacing_rows = 2` for the scrollbar
y-offset, confirming these values were set independently without unified
accounting.
Also documents the `copy-built-package` script in `AGENTS.md` for
AI-assisted development workflows.
Co-authored-by: Amp <amp@ampcode.com>
## Current Behavior
`TaskSelectionManager::handle_task_status_change` and its only callee
`handle_in_progress_task_finished` remain in
`packages/nx/src/native/tui/components/task_selection_manager.rs`, even
though #35640 rewired the TUI selection lifecycle and no production code
calls them anymore. The methods are kept alive only by a single unit
test (`test_awaiting_pending_task_state`).
## Expected Behavior
The dead methods are deleted. The orphaned test is removed because its
assertions are already covered by `test_selection_state_transitions`,
which exercises the same `AwaitingNextAllocation` entry/exit transitions
directly through `await_next_allocation()` and `next()`.
Lifecycle today:
- Entering `AwaitingNextAllocation` happens in
`tasks_list.rs::handle_standalone_task_finished` via
`selection_manager.lock().await_next_allocation()`.
- Exiting `AwaitingNextAllocation` happens in the draw-time
`perform_initial_in_progress_selection_if_needed` override.
No external (`#[napi]`) callers existed; the methods were Rust-internal
helpers.
Net: 130 deletions, 0 additions. All 14 `task_selection_manager` tests
and all 70 `tasks_list` tests pass.
## Related Issue(s)
Follow-up cleanup from #35640.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`TargetDependencyConfig.projects` accepts the legacy magic strings
`'self'` and `'dependencies'` via a compat shim in
`packages/nx/src/tasks-runner/utils.ts`. The shim was added for
pre-v8.1.4 Lerna's `prepNxOptions` call shape, with a
`TODO(@agentender): Remove this part in v20` that has now been deferred
twice (originally v17, then v20 — we're on v22.6).
## Expected Behavior
The Lerna-driven case is no longer relevant:
- Lerna switched to the modern `{ dependencies: true }` shape on
2024-06-06
([lerna/lerna#4017](https://github.com/lerna/lerna/pull/4017), first
shipped in v8.1.4).
- Any user-authored `projects: 'self'` / `projects: 'dependencies'`
configs (including `{self}`/`{dependencies}` token variants) are already
rewritten to the modern form by the existing v16
`update-depends-on-to-tokens` migration.
The shim, the LERNA SUPPORT comment markers, and the stale TODO are
removed. The single-project shorthand (`projects: "my-lib"`
auto-wrapping to `["my-lib"]`) is preserved.
## Related Issue(s)
Linear:
[NXC-4308](https://linear.app/nxdev/issue/NXC-4308/address-stale-todo-v20-in-tasks-runner-utilsts-drop-lerna-compat-for)
Closes#35630 — supersedes the input-drift snapshot approach with a
simpler watcher-side fix.
## Current Behavior
The daemon's `flushPendingWorkspaceChanges` → native
`force_flush_pending` drains the watcher's notify channel with
`try_recv`. This misses events where the notify-crate thread has read
the kernel inotify event but hasn't yet finished sending it on
`notify_rx` — a window normally of microseconds but unbounded under
scheduling pressure (parallel CI jobs, container cgroups, NFS). When the
window hits, the daemon serves a project graph computed against the
pre-write state of the workspace. The `spread.test.ts` e2e has been
flaking on this race.
## Expected Behavior
**Core fix (Rust):** the force-flush handler now waits up to 5ms via
`recv_timeout` for an in-flight notify-thread send to land before
draining and snapshotting. The change is scoped to the force-flush
handler — the steady-state idle-window flush is unchanged, so there's no
latency cost on the normal event flow. `RecvTimeoutError::Disconnected`
is now surfaced via the same `fatal` path the main select arm uses,
instead of being silently masked as an empty snapshot.
**Regression coverage:**
- Rust unit test (`force_flush_pending_captures_in_flight_writes`) — 20
iterations of write-then-immediate-flush with no sleeps. Passes
deterministically with the fix; fails / is racy without it.
- TypeScript integration spec
(`project-graph-incremental-recomputation.spec.ts`) — real native
`Watcher` against a TempFs workspace, real production routing callback,
mutate a project then immediately request the graph. Asserts the daemon
returns a graph reflecting the new project (not a stale cached graph).
**Supporting refactors (needed for the integration spec to work
cleanly):**
- Extracted `routeWorkspaceChanges` from `server.ts`'s
`handleWorkspaceChanges` so the spec can exercise the real production
event-routing path without the daemon's inactivity-timer /
error-tracking bookkeeping leaking into the test.
- Live-bound `workspaceDataDirectory`, `cacheDir`, `nxProjectGraph`,
`nxFileMap`, `nxSourceMaps`, `DAEMON_DIR_FOR_CURRENT_WORKSPACE`,
`DAEMON_OUTPUT_LOG_FILE`, and `taskHistoryFile`. They were previously
`const` values frozen at module load — fine in production (the daemon
has one workspace root for its lifetime), but tests that swapped
`workspaceRoot` couldn't update them, so the daemon would write its
cache into the real workspace under test. Added an
`onWorkspaceRootChanged` subscription in `workspace-root.ts` and
converted each derived path to a refresh-on-change `let`. Production
behaviour is unchanged — listeners only fire when `setWorkspaceRoot` is
called, which never happens in normal daemon startup.
## Related Issue(s)
Closes#35630.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
In the TUI, when running many tasks (e.g. `nx run-many -t test`), the
selection indicator (`>`) ends up on a pending task instead of an
in-progress one and stays there as tasks come and go. As tasks complete
and new ones start, the highlight bounces around unexpectedly between
renders.
## Expected Behavior
- Before any task starts, the selection anchors on the first selectable
entry as a visual indicator (initial placeholder).
- Once an in-progress entry appears, the selection latches onto the
first in-progress task, replacing the placeholder. If the user navigated
away from the placeholder first, their choice is preserved.
- When the selected in-progress task finishes while pending tasks
remain, the selection enters a waiting state — the highlight stays
hidden until the next allocation puts a task into the in-progress
section, at which point it latches on. It does not drop down to a
pending task between allocations.
- An explicit user selection is never overridden by the render loop.
## Implementation Details
Replaces `Option<SelectionEntry>` in `TaskSelectionManager` with a
four-variant `SelectionState`:
- `Empty` — never selected yet; the render-time fallback (anchor on
first selectable) is only allowed from here.
- `InitialPlaceholder(_)` — auto-anchored before any task starts;
replaced by the first in-progress entry once one appears, unless the
user navigates first.
- `Explicit(_)` — user navigation, programmatic `select_task`,
mode-switch restore, etc. Never auto-overridden.
- `AwaitingNextAllocation` — set by `handle_standalone_task_finished`
when the selected in-progress task finishes with pending tasks
remaining. Never falls back to first-available; the render loop only
exits this state when a new in-progress entry appears.
`perform_initial_in_progress_selection_if_needed` runs the state machine
on every draw under a single lock. `update_entries_track_by_*` preserve
the variant across re-sorts, so the placeholder/explicit identity
survives sort cycles.
Navigation from the unselected states (`Empty`/`AwaitingNextAllocation`)
now anchors on the first selectable entry visible in the current
viewport instead of jumping to entry 0.
Batches: standalone in-progress tasks are preferred over batch groups by
the auto-select (they sort first in `entries`). When only batches are
running, the first batch group wins — regardless of its
expanded/collapsed state.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`@nx/vite`'s `ensure-vitest-package-migration-23` imports the deep path
`@nx/devkit/src/generators/executor-options-utils`, which v23 dropped
from the `@nx/devkit` `exports` map. `nx migrate --run-migrations` halts
with `ERR_PACKAGE_PATH_NOT_EXPORTED` and skips every subsequent
migration. The Vite 8 AI doc also carried two stale upstream claims
(`.d.mts` types, "silently ignores `rollupOptions`"), which was true in
8.0.0, but not in subsequent patch releases.
Additionally, three v23 codemods shipped without the per-migration `.md`
doc the rest of `update-23-0-0/` provides.
## Expected Behavior
Migration import swapped to `@nx/devkit/internal`. Migration `version`
bumped to `23.0.0-beta.10` so prior-beta users get a clean re-run. AI
doc corrected.
Missing `.md` docs added for
`cypress/remove-experimental-prompt-command`,
`rspack/add-svgr-to-rspack-config`, and
`vite/rename-rollup-options-to-rolldown-options`.
## Related Issue(s)
Related to NXC-4154
## Current Behavior
Jest 30 enforces that every `.snap` file's first-line guide link points
at the current snapshot-testing docs URL. Snapshot files generated under
earlier Jest versions begin with the legacy short link:
```
// Jest Snapshot v1, https://goo.gl/fbAQLP
```
When users upgrade to Jest 30 (already supported as of `@nx/jest` 21.3 /
22.3), every project that has pre-existing `.snap` files fails at test
setup with:
```
Outdated guide link: The snapshot guide link at the top of this snapshot is outdated.
Please update all snapshots during this upgrade of Jest.
Expected: https://jestjs.io/docs/snapshot-testing
Received: https://goo.gl/fbAQLP
```
There is currently no `nx migrate` step that rewrites these headers, so
users have to do it by hand or run `jest -u` per project.
## Expected Behavior
`nx migrate` runs a new Jest migration (gated on `jest >= 30.0.0`) that
walks every `**/__snapshots__/*.snap` file in the workspace and rewrites
the first-line guide link from `https://goo.gl/fbAQLP` to
`https://jestjs.io/docs/snapshot-testing`. Snapshot bodies and files
that already use the new URL are left untouched.
This PR also brings the 45 outdated `.snap` files inside this repo onto
the new URL so the workspace's own test suites pass under Jest 30.
### What's in this PR
- New migration `update-snapshot-guide-link` registered in
`packages/jest/migrations.json` at `23.0.0-beta.6` with `requires: {
jest: ">=30.0.0" }`.
- Migration implementation, `.md` doc, and unit tests under
`packages/jest/src/migrations/update-23-0-0/`.
- Bulk rewrite of the 45 `.snap` files in this repo that still carried
the legacy link.
## Related Issue(s)
N/A — surfaced while running unit tests against Jest 30 in this
workspace.
## Current Behavior
Three React module-federation e2e tests in
`e2e/react/src/module-federation/` have been flaking in CI:
### `misc-rspack-convert-to-rspack.test.ts` — silent test gap + 30s
timeout
1. **Wrong `updateFile` path.** Line 47 wrote to
`apps/${shell}-e2e/src/example.spec.ts`, but `@nx/react:host` (without
an `apps/` prefix in the project name) generates the e2e project at
workspace root. `updateFile` calls `ensureDirSync(...)` and silently
created the file under the wrong path tree, leaving the default
Nx-generated 1-test Playwright spec to run instead. The test has been
quietly exercising the default Welcome page rather than the
convert-to-rspack module-federation remote-loading behavior. Same root
cause that `dc9ba49fe3` fixed for the sibling `updateJson` call — the
matching `updateFile` was missed.
2. **Missing `runCommandUntil` timeout.** Line 63 omitted the `timeout`
option, falling back to the 30s default in
`e2e/utils/command-utils.ts:300`. The e2e step needs to bootstrap nx,
cold-compile rspack for host + remote, and run the spec across 3 browser
projects sequentially with 1 worker — which routinely exceeds 30s. PR
#34148 added `{ timeout: 120_000 }` to several sibling MF tests but
missed this one because it landed later in #32948.
### `core-rspack-basic-playwright.test.ts` and
`core-webpack-basic-playwright.test.ts` — port collisions on parallel
agents
Both tests generated their host on the default port **4200** with no
reservation. Once #35325 enabled parallel `e2e-ci` execution on the same
agent, any other concurrent test that also defaulted to 4200 (another MF
test, an `e2e-playwright` test, etc.) collided with this one. Symptoms
across recent failures:
- Shell preview server gets SIGKILL'd (`Killed` / `code 137`) mid-run
while another test fights for the same port and the OS picks a loser.
- Subsequent Playwright requests fail with `NS_ERROR_CONNECTION_REFUSED`
/ `Connection refused`.
- On retry the **other** test's dev server answers, so the assertion
fails with strings like `Welcome app9409916` or `Welcome
pw-react-app6359154` — app names that aren't even in the running test.
This is exactly the gap called out in #35325:
> e2e/cypress, e2e/playwright, and the React module-federation tests
still hardcode ports through their generators; those will be addressed
in follow-up PRs as CI surfaces collisions.
## Expected Behavior
- `misc-rspack-convert-to-rspack` writes the spec file to the correct
path so it actually exercises the convert-to-rspack MF behavior, and
gets the same 120s timeout as the sibling MF tests so cold rspack
compile + 3 browser runs fit reliably.
- `core-rspack-basic-playwright` and `core-webpack-basic-playwright` use
`reservePorts(4)` for shell + 3 remotes, pass
`--devServerPort=${shellPort}` to the host generator (which propagates
to the host's serve, preview, and the e2e project's playwright `baseUrl`
per `packages/react/src/generators/host/`), and `updateJson` each
remote's `project.json` to use its reserved port — matching the pattern
already in `misc-rspack-convert-to-rspack` and
`independent-deployability.webpack`.
## Verification
- **Local** (`NX_E2E_RUN_E2E=true pnpm nx run
e2e-react:e2e-ci--src/module-federation/<file> --skip-nx-cache`):
- `misc-rspack-convert-to-rspack.test.ts` → PASS (~162s wall, ~108s
test) with e2e step actually running.
- `core-rspack-basic-playwright.test.ts` → PASS with the port fix
applied.
- **CI**: relying on the PR's `e2e-ci` aggregate and re-runs to confirm
the flake is gone.
## Related Issue(s)
N/A — internal flake fix surfaced by repeated `e2e-react:e2e-ci`
failures on parallel CI agents. Not tracked as a GitHub issue.
## Current Behavior
- `@nx/angular` silently falls through to the v21 install constants when
a workspace has `@angular/core` detected below the supported floor
(v19). Generators add v21 packages incompatible with the user's setup
instead of failing fast.
- Three cross-major Module Federation `packageJsonUpdates` entries
(`20.2.0-module-federation`, `22.2.0`, `22.6.0-module-federation`) lack
`requires` gates, so each bump fires for every workspace regardless of
the installed `@module-federation/*` source major.
- The `update-unit-test-runner-option` migration declares
`@angular/core: >=21.0.0` despite only rewriting an Nx generator default
in `nx.json` (`unitTestRunner: 'vitest'` → `'vitest-analog'`). Pre-v21
workspaces that already have the stale default never run the migration
and stay broken (the value is no longer in the generator schema's enum).
- The `add-linting` generator overwrites already-installed third-party
versions because it doesn't pass `keepExistingVersions=true` to
`addDependenciesToPackageJson`.
## Expected Behavior
- A workspace with `@angular/core` detected below v19 throws with a
clear, standardized message naming the package, the installed version,
and the supported floor (no silent fall-through). The fresh-install path
(no `@angular/core` detected) still resolves to the latest supported
version.
- Each MF `packageJsonUpdates` entry gates on the
`@module-federation/enhanced` source range it is bumping from, so the
bump only fires for workspaces actually in that range.
- The `update-unit-test-runner-option` migration runs for any workspace
with the stale generator default, regardless of installed Angular
version.
- The `add-linting` generator preserves already-installed dependency
pins.
A new internal helper `throwForUnsupportedVersion` is added to
`@nx/devkit/internal` so first-party plugins can produce a consistent
below-floor error format. It is intentionally not part of the public
`@nx/devkit` surface — only first-party plugins will consume it as the
rest of the multi-version compliance rollout lands.
## Current Behavior
No `nx migrate` path for Vite 7 -> 8. Users hit `rollupOptions` rename,
plugin-react v6 / Oxc transition, and Angular+Vitest
`@oxc-project/runtime` gap by hand.
## Expected Behavior
`nx migrate 23` bumps `vite` to `^8.0.0` and `@vitejs/plugin-react` to
`^6.0.0`, codemods `build.rollupOptions` -> `build.rolldownOptions` in
vite config files (top-level + nested environments), and writes
`tools/ai-migrations/MIGRATE_VITE_8.md` with LLM-driven cleanup steps.
Blocked by NXC-4448 (cypress bump).
## Related Issue(s)
Fixes NXC-4154
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`@nx/cypress` pins `cypress` at `^15.8.0` and `@cypress/vite-dev-server`
at `^7.0.1`. `component-configuration` generator throws when `vite >=
8`, citing missing Cypress support. Cypress 15.14.0 (2026-04-16) shipped
Vite 8 support, so both pin and guard are stale.
## Expected Behavior
`cypress` -> `^15.14.2`, `@cypress/vite-dev-server` -> `^7.3.1`. Vite 8
guard removed. `nx migrate 23` bumps users below those versions. New
codemod strips the `experimentalPromptCommand` config flag (removed in
Cypress 15.13.0).
Unblocks NXC-4154.
## Related Issue(s)
Fixes NXC-4448
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
When running a batch executor (Gradle or Maven) under the TUI:
1. **Double logs.** Each task's captured `terminalOutput` is written
into the per-task PTY twice — `printTaskTerminalOutput` lazily creates
the PTY with the terminalOutput, then `appendTaskOutput` immediately
writes the same content again, producing repeated text in the pane (e.g.
`> Task :foo:bar UP-TO-DATE> Task :foo:bar UP-TO-DATE`).
2. **Generic failure on dependents.** When one task in the batch fails,
Gradle aborts and the JVM exits non-zero. Every peer that never got to
run is yielded as `success: false` with a generic `Gradlew batch failed`
(or `Maven batch runner exited with code N`) message — both in the
streaming output and in every dependent task's TUI pane. The actual root
failure is hidden in noise. Same applies for Maven.
3. **Run reported as "Cancelled".** Skipped peers were never marked as
completed in the lifecycle, so the in-progress set stayed non-empty and
the run summary printed `Cancelled` even though the failure was real.
4. **Misleading `> nx run X` headers in run-one.** Tasks that never
actually ran still got a header printed in the streaming output,
suggesting they were executed.
## Expected Behavior
1. The captured `terminalOutput` for a batch task is written to its PTY
exactly once.
2. Peers that never ran because a sibling failed are reported with
`status: 'skipped'` and empty terminal output. The actual failed task
keeps its full error in its own pane (✖). Dependents show as ⏭ in the
task list with empty panes — the user navigates to the failed task to
see why.
3. The run summary correctly shows `Ran target build … N/M failed`
rather than `Cancelled`.
4. Skipped tasks are no longer printed in the run-one streaming summary.
## Implementation
This PR changes the batch executor protocol so the **Kotlin batch
runners are the source of truth** for per-task outcomes — the TS
executors are a thin relay instead of inferring missing results.
### Wire protocol
`TaskResult` gains an optional `status?: 'success' | 'failure' |
'skipped'` field. When set, the orchestrator and lifecycle honor it
instead of inferring from `success: boolean`. Existing batch executors
that don't emit `status` are unaffected — the orchestrator falls back to
the boolean.
`NX_RESULT:{json}` lines now carry `status` alongside `success` for
back-compat with older Nx versions.
### Kotlin runners (Gradle and Maven)
- Track requested vs reported task IDs.
- At end-of-batch, walk the requested set and emit an explicit `skipped`
`NX_RESULT` for any task without a finish event (e.g. a peer compilation
failed and the build aborted before this task could be scheduled).
- For Gradle: applied to both `runBuildLauncher` and `runTestLauncher`.
- For Maven: the work-stealing scheduler already tracked
`TaskState.SKIPPED` for tasks removed due to a failed dependency — it
now emits an `NX_RESULT` for each instead of silently dropping them.
### TS executors (`gradle-batch.impl.ts`, `maven-batch.impl.ts`)
Become thin relays:
- Parse `NX_RESULT`, pass `status` through.
- Only fallback path: if the runner crashes (non-zero exit) before
reporting on every task, backfill the missing ones with a generic
failure so Nx doesn't hang.
- The previous `sawFailure`-based inference is gone — it was fragile (a
regression in this PR's CI revealed that Maven's stderr can interleave
concurrent task output and stash one task's `NX_RESULT` inside another's
`terminalOutput` string, defeating the inference).
### Nx core (orchestrator + TUI lifecycle)
- `runBatch`'s `onTaskResults` honors `result.status ?? (success ?
'success' : 'failure')`.
- The double-logs fix is a one-line ordering swap so `appendTaskOutput`
runs before `printTaskTerminalOutput` — the latter then no-ops in the
TUI because the PTY is already populated.
- TUI summary lifecycle clears skipped tasks from `inProgressTasks` on
`setTaskStatus(Skipped)` (so the run summary doesn't say "Cancelled"),
and skips them in `printRunOneSummary` (so we don't print a misleading
`> nx run X` header for tasks that never ran).
### Tests
- `tui-summary-life-cycle.spec.ts`: snapshot test that a `Skipped` task
does not print a `> nx run` header and the run summary reports as a real
failure (not cancelled).
- `gradle-batch.impl.spec.ts` and `maven-batch.impl.spec.ts`:
spawn-mocked tests verify the executor relays `status: 'skipped'` from
the runner unchanged, and backfills as `failure` only when the runner
crashes before reporting.
## Related Issue(s)
Fixes NXC-4439 (Linear) — Show root Gradle failure for dependent tasks
in TUI.
Fixes NXC-4449 (Linear) — TUI shows duplicate terminal output for batch
tasks.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
`@nx/rspack` exposes an `svgr` option on `withReact` and
`NxReactRspackPlugin` that wires `@svgr/webpack`. Slated for removal in
v23.
## Expected Behavior
`svgr` removed from the rspack public API. SVG handling consolidated
into the images asset rule. New
`update-23-0-0-add-svgr-to-rspack-config` migration inlines a `withSvgr`
helper into user configs to preserve behavior, mirroring the v22 webpack
migration.
## Related Issue(s)
NXC-4156
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`@nx/gradle/plugin-v1` is the legacy non-atomized Gradle plugin entry.
In Nx 21, when v2 (the atomized default) became the new `@nx/gradle`,
existing users were migrated *to* `plugin-v1` to preserve their
behavior. Since then it's been a stable opt-out, with no `@deprecated`
annotation, no runtime warning, and no docs note signaling it's going
away.
## Expected Behavior
The `@nx/gradle/plugin-v1` entry remains fully functional in Nx 23 but
emits a clear deprecation warning on plugin load and is annotated
`@deprecated` on every public symbol. Removal is scheduled for Nx 24,
giving users on the legacy non-atomized plugin one major to switch to
the default `@nx/gradle` entry.
### What changed
- `packages/gradle/plugin-v1.ts` — emits a deprecation warning on module
load via `emitPluginWorkerLog` so the message surfaces to the user even
when the daemon is enabled. Routing it through `logger.warn` would have
been swallowed into the daemon log file.
- `packages/gradle/src/plugin-v1/{nodes,dependencies}.ts` —
`@deprecated` JSDoc on the public `createNodesV2` / `createDependencies`
exports.
- `packages/nx/src/devkit-internals.ts` + `packages/devkit/internal.ts`
— re-export `emitPluginWorkerLog` so `@nx/devkit/internal` becomes the
canonical path for plugin authors who want a daemon-aware warning
channel (instead of deep-importing into
`nx/src/project-graph/plugins/isolation/`).
### Notes
- Purely additive: no code or exports removed. Plugin behavior is
unchanged.
- Runtime-guarded with `typeof emitPluginWorkerLog === 'function'` to
handle the `@nx/devkit@23 + nx@22.0–22.6` skew (the helper shipped in
nx@22.7).
- A swap migration (rewriting `@nx/gradle/plugin-v1` → `@nx/gradle` in
`nx.json`) will ship alongside the v24 removal.
## Related Issue(s)
<!-- No tracking issue yet -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Most Nx executors that have an inferred-plugin alternative
(`@nx/<pkg>/plugin`) and a `convert-to-inferred` generator are still
wired up like first-class citizens. There is no signal — at scaffold
time, schema browsing, or task execution — that they are on a path to
removal, and the existing `cypress`/`detox` deprecation messages are
inconsistent with the canonical pattern shipped most recently.
## Expected Behavior
Every executor that has an inferred-plugin migration target is now
deprecated through three surfaces, matching the canonical pattern:
- **Runtime warning.** The executor logs that it is deprecated, will be
removed in Nx v24, and points at `nx g @nx/<pkg>:convert-to-inferred`.
- **Schema-root `x-deprecated`.** Surfaces in editor / Nx Console / `nx
show project` views.
- **Generation-time warning.** When a generator is about to scaffold a
target that uses one of these executors because the corresponding
inferred plugin isn't registered, it warns at generation time and points
at the same migration path.
All warnings link to
<https://nx.dev/docs/guides/tasks--caching/convert-to-inferred>.
### Executors deprecated in this PR
| Package | Executors |
|---|---|
| `@nx/webpack` | `webpack`, `dev-server` |
| `@nx/vite` | `build`, `dev-server`, `preview-server` |
| `@nx/rollup` | `rollup` |
| `@nx/next` | `build`, `server` |
| `@nx/remix` | `build`, `serve` |
| `@nx/jest` | `jest` |
| `@nx/playwright` | `playwright` |
| `@nx/eslint` | `lint` |
| `@nx/storybook` | `storybook`, `build` |
| `@nx/rspack` | `rspack`, `dev-server` |
| `@nx/expo` | `build`, `export`, `install`, `prebuild`, `run`, `serve`,
`start`, `submit` |
| `@nx/react-native` | `build-android`, `build-ios`, `bundle`,
`pod-install`, `run-android`, `run-ios`, `start`, `upgrade` |
| `@nx/vitest` | `test` |
### Generation-time warnings wired in
- `@nx/<pkg>:configuration` for webpack, vite, rollup, jest, playwright,
storybook, rspack, vitest
- `@nx/<pkg>:application` for next, expo, react-native
- `@nx/eslint:lint-project` legacy fallback path
- `@nx/react:application` (webpack, rspack branches) and
`@nx/react:library` (rollup legacy fallback) — warning text is inlined
rather than imported. Two distinct reasons:
- **rspack:** `@nx/react` does not declare a tsconfig project reference
to `@nx/rspack`, so the deep import would not even type-check.
- **webpack and rollup:** the project reference exists, so the deep
import compiles, but `@nx/webpack` and `@nx/rollup` only expose
`./index.js` in their package `exports` field. The import would resolve
in source but throw `Cannot find module
'@nx/<pkg>/src/utils/deprecation'` at runtime in published packages.
- `@nx/react-native:web-configuration` (webpack) — inline for the same
reason as the rspack case (no project reference).
### Scope notes
- `@nx/vite:test` is intentionally **not** included — it is being
removed entirely by PR #35517 (deprecation messaging would ship as dead
code). `@nx/vitest:test` is now in scope: the `convert-to-inferred`
generator for `@nx/vitest` is in place, so the deprecation has a real
migration target.
- `@nx/cypress`/`@nx/detox` already shipped earlier; this PR retitles
their generation-time messages to the new wording (drop the redundant
"register the plugin first" guidance, swap "Scaffolding" for
"Generating") and points the detox URL at the general
convert-to-inferred guide.
- `@nx/react-native:storybook` is intentionally **not** included. The
companion `@nx/react-native:storybook-configuration` generator was
already removed in v21 and no generator has emitted this target since
Jan 2024 (RN 0.73 upgrade). It will be removed outright in a follow-up
PR rather than going through the deprecate-now / remove-in-v24 cycle.
- `@nx/webpack:ssr-dev-server`, `@nx/expo:build-list`,
`@nx/expo:sync-deps`, `@nx/expo:update`, `@nx/expo:ensure-symlink`,
`@nx/react-native:sync-deps`, and `@nx/react-native:ensure-symlink` are
intentionally left as-is — none are covered by `convert-to-inferred` and
they have no clean replacement (Nx-specific glue or non-migrating
utilities).
- `@nx/esbuild:esbuild` and `@nx/nuxt:*` ship neither an inferred plugin
nor a `convert-to-inferred` generator yet, so they're out of scope.
- Per-package READMEs, introduction-doc banners, per-package "migration
recipe" pages, and `migrations.json` entries are intentionally skipped
per the canonical pattern (NXC-4422). The runtime warning carries the
migration story.
## Related Issue(s)
Fixes NXC-4423.
Fixes NXC-4296.
Fixes NXC-4294.
Fixes NXC-4290.
Fixes NXC-4285.
Fixes NXC-4283.
Fixes NXC-4286.
Fixes NXC-4297.
Fixes NXC-4293.
Fixes NXC-4292.
Fixes NXC-4282.
Fixes NXC-4287.
Fixes NXC-4295.
Fixes NXC-4447.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
When a Cypress config produces absolute paths for `screenshotsFolder` or
`videosFolder` (e.g. via `path.resolve(__dirname, ...)` or
`__dirname`-based composition), the inferred Nx outputs don't match
where Cypress actually writes. Caching is broken because Nx scans the
wrong location, and per-spec atomized targets compound the mismatch.
## Expected Behavior
Inferred outputs match where Cypress writes, regardless of whether the
user's config used relative or absolute paths. The atomized `--config`
override is normalized to a project-root-relative form so Cypress (cwd =
project root) writes exactly where Nx declares its outputs.
## Implementation Details
- `getOutputs` rewritten to use `resolve(workspaceRoot, projectRoot)` +
`relative(fullProjectRoot, fullPath)`. A single branch on whether the
relative result starts with `..` chooses between `{projectRoot}/<rel>`
(path inside the project) and `{workspaceRoot}/<rel>` (path outside the
project but inside the workspace). Matches the canonical pattern used in
the playwright plugin and unifies absolute, relative, and `..`-prefixed
inputs.
- New `serializeConfigPath` helper applies the same `resolve`+`relative`
transformation to rewrite absolute folders to project-root-relative form
before appending the per-spec subfolder; used by `getTargetConfig` for
top-level and nested (`e2e.*`, `component.*`) folder overrides.
- Three snapshot tests cover: (1) e2e + atomized e2e-ci with absolute
paths under a `.` project root, (2) component + atomized
component-test-ci with absolute paths under a `.` project root, and (3)
e2e + atomized e2e-ci with a deeper project root and absolute paths
outside the project but inside the workspace (exercises the
`{workspaceRoot}` branch and validates the `--config` override produces
dotted-relative paths).
## Current Behavior
The root `package.json` declares dependencies that are no longer used
anywhere in the repo (leftovers from feature/site migrations, replaced
libraries, and deprecated tooling).
## Expected Behavior
The root `package.json` only declares deps that are actually used. This
PR removes 27 such entries with no source code or consumer-package
`package.json` changes.
Beyond cleaner manifests, this also:
- **Shrinks the lockfile by ~5,350 lines** (the transitive subtree of
the removed entries), giving faster `pnpm install` and a smaller
`node_modules`.
- **Reduces supply-chain attack surface** — every package we don't
install is one that can't be compromised upstream and pulled into our
builds. Recent ecosystem incidents (`chalk`, `debug`, `is`, etc.
takeovers) all reached projects through transitive deps.
## Implementation Details
Removed entries by bucket:
- **Docusaurus** (docs migrated to `astro-docs/` Starlight):
`@docusaurus/core`, `@docusaurus/preset-classic`,
`@docusaurus/module-type-aliases`, `@docusaurus/tsconfig`,
`@docusaurus/types`, `@mdx-js/react`, `prism-react-renderer`
- **3D / homepage scene** (superseded homepage iteration): `three`,
`@types/three`, `@react-three/drei`, `@react-three/fiber`,
`@react-spring/three`, `shadergradient`
- **Other unused**: `@notionhq/client`, `@iconify-json/ph`,
`@iconify-json/svg-spinners`, `starlight-typedoc`,
`@monaco-editor/react`, `react-markdown`, `fast-glob` (eslint configs in
`packages/{esbuild,js}` actively ban it in favour of `tinyglobby`),
`cytoscape-popper` (not in `@nx/graph` peer deps), `npm-package-arg`
- **Deprecated `@types/*`** (underlying package ships its own types or
isn't installed): `@types/detect-port`, `@types/marked`,
`@types/cytoscape`, `@types/npm-package-arg`
- **Stale tooling**: `conventional-changelog-cli` (2018-era
release-helpers script, since replaced by `nx nx-release`)
Each entry was verified individually with:
- 0 real-code imports anywhere in the repo (excluding lockfile fixtures
and demo JSON)
- 0 `peerDependencies` declarers across installed `node_modules`
- 0 references in scripts, CI workflows, husky hooks,
`project.json`/`nx.json`
After removal, `pnpm install` is clean (no new unmet peers) and `pnpm nx
prepush` passes locally.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Nightly `E2E matrix` jobs on node 26 hang during `pnpm playwright
install --with-deps` after Chromium download (zip extract never
finishes). 20m job timeout cancels the preinstall, e2e jobs skip,
`process-result` fails on missing `outputs/*/matrix.json`.
Root cause: yauzl regression on node 26
([microsoft/playwright#40724](https://github.com/microsoft/playwright/issues/40724),
[thejoshwolfe/yauzl#168](https://github.com/thejoshwolfe/yauzl/pull/168)).
## Expected Behavior
Node 26 commented out of preinstall matrix and `process-matrix.ts`
node_versions for both ubuntu + macos. Nightly runs on v22 + v24 only.
Re-add when playwright ships the fix.
## Related Issue(s)
NXC-4451
## Current Behavior
Compat matrix lists Node 24, 22, 20 for Nx 22.x. No mention of Node 26.
## Expected Behavior
Add Node 26 to Nx 22.x row. New aside notes Node 26 is on Current track,
hits LTS Oct 2026, supported today via CI.
## Notes
- There are new deprecation warnings for `module.register` usage, which
will be removed in Node 28. This is fine for workspace that can use the
default Node.js type-stripping (https://github.com/nrwl/nx/pull/35608),
and for other workspaces we need swc/ts-node (or something else) to move
away from the deprecated API before it goes away.
- Also a deprecation warning for `fs.Stats`, but it's not in our code so
it's from a dep or transitive dep.
## Related Issue(s)
NXC-4374
## Current Behavior
`packages/angular/src/plugins/plugin.spec.ts` mocks
`nx/src/utils/cache-directory.workspaceDataDirectory` to the relative
string `'tmp/project-graph-cache'`. The spec doesn't `chdir` away from
the project root, so when `@nx/angular/plugin`'s `createNodesV2` calls
`PluginCache.writeToDisk`, the hash file lands at
`packages/angular/tmp/project-graph-cache/angular-<hash>.hash` — inside
the angular project tree. This shows up as a sandbox violation on the
`angular:test` task.
## Expected Behavior
The plugin cache file lands outside the project tree, like in the other
plugin specs (`jest`, `docker`, `react`) that already follow this
pattern. `packages/angular/tmp/` is no longer created during
`angular:test`.
The fix mirrors `packages/jest/src/plugins/plugin.spec.ts`:
`process.chdir(tempFs.tempDir)` in `beforeEach` and restore the original
cwd in `afterEach`. The relative `'tmp/project-graph-cache'` then
resolves under the `tempFs` directory (which is in `os.tmpdir()`), and
`tempFs.cleanup()` already removes it. The explicit `mkdirSync`/`rmSync`
of `'tmp/project-graph-cache'` is redundant — `PluginCache.writeToDisk`
does its own `mkdirSync(dirname(cachePath), { recursive: true })` — and
has been removed.
## Current Behavior
For angular-rspack i18n production builds with `extractLicenses: true`,
the third-party license file is written inside the browser bundle dir at
`dist/browser/3rdpartylicenses.txt` instead of at the application
builder's documented location `dist/3rdpartylicenses.txt`.
The same misbehavior also surfaces sandbox violations on
`examples-angular-rspack-csr-i18n:build` (unexpected reads of
`dist/browser/<locale>/../3rdpartylicenses.txt`).
## Expected Behavior
The license file is written once at `dist/3rdpartylicenses.txt`,
matching Angular CLI's application builder layout. The sandbox report
for `examples-angular-rspack-csr-i18n:build` shows no unexpected reads.
## Implementation Details
`LicenseWebpackPlugin` is configured by angular-rspack with an asset
name that escapes the browser dir, so it lands at `outputPath.base`:
```ts
outputFilename: posix.join(relative(outputPath.browser, outputPath.base), '3rdpartylicenses.txt')
// → '../3rdpartylicenses.txt'
```
`I18nInlinePlugin` re-emits every non-`$localize` asset under each
locale subdirectory. With three locales (`en-GB`, `es-ES`, `fr`) the
license asset becomes three asset names:
```
en-GB/../3rdpartylicenses.txt
es-ES/../3rdpartylicenses.txt
fr/../3rdpartylicenses.txt
```
These are different *path strings* but the `<locale>/..` segments cancel
out, so all three resolve to the same physical file inside
`dist/browser/`. Two consequences:
1. The license file lands at `dist/browser/3rdpartylicenses.txt` (the
collision target) instead of `dist/3rdpartylicenses.txt` (the
LicenseWebpackPlugin's intent — `outputPath.base`).
2. Rspack's `compareBeforeEmit` (default `true`) writes the file once
for the first locale and then opens it `O_RDONLY` to compare contents
for the other two. The sandbox tracker captures the literal syscall
paths (no `..` canonicalization), so a single physical file appears as
one write and two reads under three distinct path strings, and the two
reads are flagged as `unexpectedReads`.
The fix updates `I18nInlinePlugin`'s skip predicate so assets whose path
contains a `..` segment are not duplicated per locale. This mirrors how
Angular CLI's application builder excludes `BuildOutputFileType.Root`
files from i18n inlining; the rspack-asset-name analog is detected via
the `..` segment.
## Current Behavior
`nx test` for any React Native or Expo app crashes immediately:
```
TypeError: this._moduleMocker.clearMocksOnScope is not a function
at Runtime.resetModules (.../jest-runtime/build/index.js:3782:28)
```
`@nx/jest` defaults to installing `jest@^30.0.2` (a caret range). Today
(2026-05-07) `jest-runtime@30.4.0` was published, which is the first
version to call `_moduleMocker.clearMocksOnScope()`. That method only
exists on `jest-mock@30.x`'s `ModuleMocker`.
React Native's preset (`@react-native/jest-preset`, current latest
0.85.3) hard-pins `jest-environment-node@^29.7.0`, whose env constructor
instantiates a `jest-mock@29` `ModuleMocker`. When `jest-runtime@30.4.0`
calls `clearMocksOnScope` on that 29.x instance, the call is missing →
crash. Same root cause hits both `preset: 'react-native'` and `preset:
'jest-expo'`.
`pnpm-workspace.yaml`'s catalog stays at `^30.0.2`, but the lockfile has
it resolving to 30.0.2 (because the workspace lockfile was written
before 30.4.0 existed); user workspaces don't get that protection. They
re-resolve fresh on first `pnpm install` and land on 30.4.0.
## Expected Behavior
`nx test` keeps working for RN/Expo apps until Meta ships a
Jest-30-aware `@react-native/jest-preset`.
This PR:
- Pins `@nx/jest`'s scaffold defaults from `^30.0.2` → `~30.3.0` for
`jest`, `babel-jest`, and `~30.0.0` for `@types/jest`. New workspaces
get a known-good range.
- Adds `update-23-0-0/pin-jest-30-3-for-rn-compat`, a migration that
walks existing workspaces' root `package.json` and tightens any
`jest`/`babel-jest`/`@types/jest` range that resolves entirely within
major 30 down to the same pinned ranges. Skips ranges that escape major
30 (like `*` or `>=29.0.0`), file-links, and entries already at the pin.
- Lift the pin once `@react-native/jest-preset` bumps
`jest-environment-node` to `^30`. Tracking comment is in
`packages/jest/src/utils/versions.ts`.
## Related Issue(s)
This is a tourniquet: jest 30.5+ may add another `jest-mock@30`-only
call and we'd be back here. Long-term fix is either (a) Meta updating
their preset, or (b) `@nx/jest` writing a self-contained RN-aware jest
config that doesn't rely on `preset: 'react-native'`.
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`dotnet:lint` has violations from json files in subprojects
## Expected Behavior
`dotnet:lint` excludes subprojects
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
`gradle:test` (the Jest target for `packages/gradle`) reads several
files under `packages/gradle/project-graph/build/reports/tests/test/`
(Gradle test report HTML/JS) that are not declared as task inputs. The
sandbox flags them as unexpected reads.
Root cause: jest-haste-map crawls `<rootDir>` to build a module map and
indexes any file matching `moduleFileExtensions` (`ts`, `js`, `html`
from the preset). The `project-graph/` subdirectory is a separate
Kotlin/Gradle sub-project whose `build/` outputs are gitignored — so
they're (correctly) excluded from Nx's `{projectRoot}/**/*` input glob —
but Jest doesn't honor `.gitignore`. The existing
`modulePathIgnorePatterns` covers `<rootDir>/batch-runner/` but not
`<rootDir>/project-graph/`.
Sandbox report:
https://staging.nx.app/runs/PywWp3NK7G/task/gradle%3Atest
## Expected Behavior
Jest does not scan into `packages/gradle/project-graph/`. The
`gradle:test` task no longer produces unexpected reads from that
directory.
## Related Issue(s)
Fixes NXC-4444
## Current Behavior
Every `nx` invocation re-parses and re-compiles the same JS modules from
disk. Node 22.8+ ships an opt-in V8 cache for compiled bytecode
(`require('module').enableCompileCache()`), but bin/nx.ts doesn't enable
it.
> ⚠️ This is **not the same** as the `v8-compile-cache` npm package that
was previously used in nx and removed in #20454 due to ESM
incompatibility (`Invalid host options` error). The npm package was a
userspace `Module.prototype._compile` monkey-patch and famously broke
when ESM modules were loaded. The Node 22.8 built-in is implemented
inside Node's loader and was designed specifically to support both CJS
and ESM cleanly. It does not have the bug that motivated #20454.
## Expected Behavior
Call `enableCompileCache()` at the top of bin/nx.ts so the cached
bytecode is reused on subsequent runs. The optional chaining (`?.`) plus
try/catch make it a no-op on older Node versions, and the cache itself
is a no-op on the first run — every run after that pays only the
cached-bytecode load instead of full parse+compile.
Cache files live in Node's default location
([`os.tmpdir()/node-compile-cache`](https://nodejs.org/api/module.html#moduleenablecompilecachecachedir))
and Node manages them automatically. Cache entries are keyed on source
mtime+size and Node version, so they invalidate automatically when
source changes or the user upgrades Node.
This also enables the cache in the daemon and plugin workers, which has
shown to be promising for speeding up plugin load times.
## Related Issue(s)
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
## Current Behavior
The `@nx/angular:ngrx` generator is exposed and functional, but has been
deprecated since Nx v18 in favor of `@nx/angular:ngrx-root-store` (root
state) and `@nx/angular:ngrx-feature-store` (feature state).
## Expected Behavior
The `@nx/angular:ngrx` generator is removed. Users invoke
`@nx/angular:ngrx-root-store` for root state and
`@nx/angular:ngrx-feature-store` for feature state instead.
A migration splits any `@nx/angular:ngrx` generator defaults set in
`nx.json` across the two replacement generator keys, renames the
deprecated `module` option to `parent`, and drops the obsolete `root`
toggle (intent is now expressed by which generator is invoked). The
migration handles both the flat (`"@nx/angular:ngrx"`) and nested
(`"@nx/angular": { "ngrx": ... }`) defaults shapes.
BREAKING CHANGE: The `@nx/angular:ngrx` generator has been removed. Use
`@nx/angular:ngrx-root-store` for root state and
`@nx/angular:ngrx-feature-store` for feature state instead.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
CI matrices test Node 20. `@types/node` pinned to v20 in the repo
catalog and generator constants.
## Expected Behavior
Generator `typesNodeVersion` bumps to `^22.0.0`.
Node 20 dropped from e2e + nightly matrices and ESLint docs (EOL Apr
2026). Repo catalog bumps to `^24.11.0` to match
`mise.toml`. Nightly `nodeTLS` renamed to `lowestNodeLTS` and bumped to
22.
## Related Issue(s)
NXC-4159
I saw a bug on a bad import line, but when I fixed it, more lint
errors/warnings popped up in the same file visually. But fixing that
file wasn't enough, because I kept finding even more lint problems in
files without that import error. Ran the lint command and saw an ENOENT
crash. After a small fix, _many_ lint issues emerged. Looks like eslint
doesn't catch errors you throw at it. (I wrote this paragraph myself,
but I worked with _Claude Opus 4.6 Thinking_ for fixing and detailing
the rest of this PR.)
## Current Behavior
When a project uses wildcard tsconfig path aliases (e.g.
`@myorg/mylib/*` → `libs/mylib/src/*`), the `enforce-module-boundaries`
rule's auto-fixer calls `getRelativeImportPath` with the unresolved glob
path (e.g. `libs/mylib/src/*`). The function's `lstatSync` returns
`null` for this path, no file extension resolves it either, and
execution falls through to `readFileSync` — which throws `ENOENT: no
such file or directory`.
Because ESLint does not catch errors thrown inside `fix()` functions
(see
[eslint/eslint#13872](https://github.com/eslint/eslint/issues/13872)),
the impact depends on the context:
- **CLI** (`nx lint` / `eslint .`): the error propagates to
`eslint-helpers.js` where `controller.abort()` kills the **entire lint
run**, suppressing every diagnostic across all files. The ENOENT error
itself is printed, but there is no indication that all other diagnostics
were lost — the user sees an error about one file and reasonably assumes
everything else was checked.
- **IDE extension**: the ESLint language server lints open files
individually with no shared `AbortController`, so only the **crashing
file's** diagnostics are lost. The crash is effectively invisible — no
squiggles, no Problems panel entry, no notification (the error is only
logged to the ESLint Output channel). Every other open file still shows
its lint errors normally, so the linter appears to be working fine.
### Reproduction
Clone
[SharonLougheed/module-boundaries-bug](https://github.com/SharonLougheed/module-boundaries-bug/)
and run `nx lint libA`. The linter crashes with:
```
ENOENT: no such file or directory, open '.../libs/libB/src/lib/*'
Rule: "@nx/enforce-module-boundaries"
```
**0 lint errors are reported**, even though there are 7 real violations
in the library.
## Expected Behavior
`getRelativeImportPath` should return `undefined` when the file path
cannot be resolved, instead of falling through to `readFileSync`. The
callers already handle `undefined` — they skip the auto-fix suggestion
but still report the lint error.
After this fix, the same `nx lint libA` run reports:
```
LibA.ts:3:1 error Projects cannot be imported by a relative or absolute path @nx/enforce-module-boundaries
utils.ts ...4 errors, 3 warnings (no-var, prefer-const, no-explicit-any, no-unused-vars, no-debugger)
✖ 7 problems (4 errors, 3 warnings)
```
### Relationship to #34066#34066 (merged in v22.5.3 by @JesseZomer) resolves wildcard paths at the
call site in `enforce-module-boundaries.ts`, which fixes the specific
wildcard scenario. This PR adds a defensive guard inside
`getRelativeImportPath` itself, so that _any_ unresolvable path — not
just wildcards — returns `undefined` instead of crashing. Issues #30491
and #16716 describe non-wildcard variants of the same crash that are not
addressed by #34066.
## Related Issue(s)
Fixes#35006
Related: #30491, #16716, #21889, #32190 (closed by #34066)
---------
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
## Current Behavior
The `cypress-legacy` and `vite` e2e tests downgrade `vite` +
`@vitejs/plugin-react` in `package.json` and then run `install` to
verify backward compatibility with Vite 7. On yarn classic this trips a
linker bug:
```
error Invariant Violation: could not find a copy of vite to link in
.../node_modules/vitest/node_modules
```
Root cause: `vitest@~4.1.0` declares `vite` as both a regular
`dependency` AND a `peerDependency`. yarn 1's hoisting algorithm fails
when intersecting a top-level `^7.0.0` range with that combo (the bug
does not fire for `^8.0.0`, exact versions, or differently-formatted
ranges like `7.x`). It is not specific to the in-test downgrade — the
same setup fails from a completely empty directory.
`Linux/yarn/20 e2e-cypress` and `Linux/yarn/20 e2e-vite` have been
failing every nightly since the in-test Vite 7 downgrade was introduced
in #34850.
## Expected Behavior
The `cypress-legacy` and `vite` e2e tests pass on yarn classic again
with Vite 7.
The fix adds a yarn-only `resolutions` entry pinning `vite` so yarn
commits to a single version up front and skips the buggy hoisting code
path. npm/pnpm don't have the bug and ignore the field.
## Validation
Verified via a manually-dispatched e2e nightly run on this branch (with
the matrix temporarily narrowed to `Linux/yarn/20 × {e2e-cypress,
e2e-vite}`, the matrices that fail on master):
https://github.com/nrwl/nx/actions/runs/25431401390 — both jobs passed.
## Current Behavior
The `splitArgs` describe block in
`packages/nx/src/utils/command-line-utils.spec.ts` saves and clears
`NX_BASE`, `NX_HEAD`, and `NX_PARALLEL` so the surrounding shell
environment doesn't bleed into assertions. However, the production code
in `splitArgsIntoNxArgsAndOverrides` also reads four cache-related env
vars as fallbacks for the `skipNxCache` / `skipRemoteCache` defaults:
- `NX_SKIP_NX_CACHE`
- `NX_DISABLE_NX_CACHE`
- `NX_SKIP_REMOTE_CACHE`
- `NX_DISABLE_REMOTE_CACHE`
These are not isolated. When a developer runs the test suite via the
outer `nx` invocation with flags like `--skipNxCache`, nx propagates
them to spawned child processes as `NX_SKIP_NX_CACHE=true`. That env var
leaks into the Jest worker, flips `skipNxCache` to `true`, and breaks
every test in the `splitArgs` block that asserts on the defaults — six
failures in total (`should split nx specific arguments into nxArgs`,
`should default to having a base of main`, `should return configured
base branch from nx.json`, `should return a default base branch if not
configured in nx.json`, `should split projects when it is a string`,
`should set base and head based on environment variables in affected
mode`).
## Expected Behavior
The `splitArgs` specs should pass regardless of which cache-related
flags or env vars are set in the surrounding environment, just like they
already do for `NX_BASE` / `NX_HEAD` / `NX_PARALLEL`.
This PR extends the existing save/clear/restore pattern to cover the
four cache env vars and factors the conditional-restore logic (delete
when originally unset, otherwise reassign — to avoid Node's
`process.env[key] = undefined` coercing to the string `"undefined"`)
into a small `restoreEnv` helper.
## Related Issue(s)
N/A
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`get_machine_id()` is invoked from `connect_to_nx_db()` to derive the
workspace DB filename, so every `nx` invocation hits it. On macOS the
underlying `machine_uid::get()` shells out to `ioreg -rd1 -c
IOPlatformExpertDevice` and parses its output. The fork+exec, dyld,
code-signature verification, and IOKit framework init together cost
**~90ms per call**, and the result is identical for every run on the
same machine.
## Expected Behavior
Use `libc::gethostuuid(3)` on macOS, which is the BSD syscall the same
kernel data is fronted by. It returns the same 16-byte `uuid_t` that
`ioreg` prints — bit-for-bit identical, same hyphenation, same casing —
but in **~10µs** (about 9000× faster) because it skips the subprocess
entirely.
```
ioreg: 398DA1D5-608C-58D6-BA32-FAE0E01A0ED3
gethostuuid: 398DA1D5-608C-58D6-BA32-FAE0E01A0ED3
```
Other platforms are untouched: `machine_uid::get()` already uses fast
direct file/registry reads on Linux and Windows; only macOS was paying
the subprocess tax.
Measured on a hot-cache `run-many --parallel 10` over 5 next.js apps:
| | median wall time |
| ------ | ---------------- |
| before | 290 ms |
| after | 200 ms |
## Related Issue(s)
Fixes #
## Current Behavior
\`printNxKey()\` runs at the end of every \`nx run-many\` / \`nx run\`
invocation. It calls \`handleImport('@nx/powerpack-license')\` and
\`handleImport('@nx/key')\` to dynamically discover whichever optional
license package the workspace has installed. When neither package is
installed (the common case for OSS users), both calls walk
\`node_modules\` and miss, costing roughly 50 ms per nx command.
## Expected Behavior
Cheaply probe whether either package is resolvable from the workspace
root (\`require.resolve(name, { paths: [workspaceRoot] })\`) before
attempting the dynamic import. The probe is microseconds when the
package is absent, so the wasted ~50 ms goes away.
While here, kick the lookup off at the very top of
\`runCommandForTasks\` so it overlaps with task execution, and split the
log out to the existing late call site so output ordering is unchanged —
the licensee line still lands after task output, never mid-task.
Measured on a hot-cache \`run-many --parallel 10\` over 5 next.js apps:
| | median wall time |
| ------ | ---------------- |
| before | 350 ms |
| after | 310 ms |
## Related Issue(s)
Fixes #
## Current Behavior
After running `nx-release` locally, `packages/devkit/package.json` is
left modified in the working tree.
The `nx-release.ts` script snapshots and restores the source
`package.json` for every package whose source folder is the publish root
(so the temporary edits made during `nx release version` — real version,
expanded `workspace:*` deps, etc. — don't leak into the working tree).
The list of those packages lives in `packagesToReset` near the top of
`scripts/nx-release.ts`.
When `@nx/devkit` was migrated to publish from its source folder in
#34946, it was not added to that list, so its source `package.json` is
no longer restored.
## Expected Behavior
`packages/devkit/package.json` is restored to its committed contents
after `nx-release` finishes (or is interrupted), matching the behavior
already in place for `nx`, `dotnet`, `maven`, `angular-rspack`, and
`angular-rspack-compiler`.
## Related Issue(s)
N/A — internal release-tooling fix.
## Current Behavior
Sandbox reads only exclude `packages/nx/src/native/*.node`. The native
`.node` binary is also copied to `packages/nx/dist/src/native/` during
the nx package build, and reads of that dist copy from other tasks show
up as sandbox violations.
## Expected Behavior
Both the source and dist locations of the prebuilt native `.node` binary
are excluded from sandbox read tracking.
## Related Issue(s)
N/A
## Current Behavior
`e2e-maven:e2e-ci--src/maven.test.ts` is flaking on CI. Example failed
run: https://github.com/nrwl/nx/actions/runs/25398728205
The failure pattern is consistent across the failures we have logs for:
```
FAIL e2e-maven src/maven.test.ts (533.603 s)
Maven
✓ should detect Maven projects (13261 ms)
✓ should have proper Maven targets (2190 ms)
✕ should build Maven project with dependencies without batch mode (300955 ms)
✓ should run tests for Maven project without batch mode (10902 ms)
✓ should handle Maven project with complex dependencies (2933 ms)
✓ should support targetNamePrefix option (164161 ms)
Maven › should build Maven project with dependencies without batch mode
Command timed out after 300s: run app:install --no-batch
Process output:
NX Running target install for project com.example:app and 87 tasks it depends on:
...
```
## Root cause
Without `--batch`, Nx expands `app:install` into one task per Maven
lifecycle phase per project (`validate, initialize, generate-sources,
..., install` × 4 projects ≈ 87 tasks), and each task spawns its own
`mvn` JVM that runs the `nx-maven-plugin:apply` and `:record` mojos.
Sampling `Run Command: run app:install --no-batch (Xs)` across the last
few master CI runs:
| Run | Duration |
| --- | --- |
| 25406383967 (passed) | 222.7s |
| 25395898487 (passed) | 261.7s |
| 25393915435 (passed) | 278.9s |
| 25395728023 (passed) | 286.8s |
| 25398728205 (failed) | 300s+ (timeout) |
`runCLI`'s default timeout is `5 * 60 * 1000` ms. On a healthy host the
assertion finishes around 220s. On loaded CI runners the same 87
JVM-spawning tasks routinely climb to 285s+, leaving very little
headroom — any extra noise puts it past 300s. There is no regression in
the underlying graph or in the `cbcd4d552b` / `44ae15eef6` /
`04ec111c88` Maven fixes; those commits are pre-existing on every
passing run as well.
## Expected Behavior
Pass `timeout: 10 * 60 * 1000` to the two `--no-batch` `runCLI` calls in
`e2e/maven/src/maven.test.ts` (the `app:install` case at line 51 and the
`app:mvn-compile` case in `should support targetNamePrefix option` at
line 121). That gives both calls 5 extra minutes of slack, well clear of
the observed 220–290s range, while leaving everything else (including
all four other `e2e-maven` test files, which already use `--batch` and
are fast) unchanged.
This is the smallest possible fix that addresses the actual root cause
(timeout headroom on the slowest run-shape we ship). It is not a
workaround for a regression — there is nothing to revert.
## Why not just speed it up?
A real perf fix is possible (e.g. a single batched `mvn` invocation for
the lifecycle expansion) but is out of scope for a flake fix. This
change only widens the timeout for the two known slow `--no-batch`
calls; everything else is untouched.
## Related Issue(s)
N/A — internal CI flake.
## Current Behavior
The `Install system deps` step in `.nx/workflows/agents.yaml` rewrites
`/etc/apt/sources.list` to point exclusively at
`azure.archive.ubuntu.com`, then runs `apt-get update && apt-get
install` for the system packages every Nx Agent needs
(`ca-certificates`, `lsof`, `libvips-dev`, `libglib2.0-dev`,
`libgirepository1.0-dev`, `zip`, `unzip`).
When Azure's Ubuntu mirror is unreachable from the agents — which
started happening today — every pipeline fails on init with:
```
Could not connect to azure.archive.ubuntu.com:80, connection timed out
E: Unable to locate package lsof
E: Unable to locate package libvips-dev
E: Package 'libgirepository1.0-dev' has no installation candidate
```
The original switch to Azure's mirror was made because the canonical
`archive.ubuntu.com` periodically serves a `Packages.gz` that doesn't
match its own `InRelease` metadata while mid-sync. So the previous
design traded one flaky upstream for another with no fallback between
them.
## Expected Behavior
Use apt's native `mirror+file://` failover, configured the same way
GitHub Actions runner-images sets up its hosted Ubuntu runners. A single
`/etc/apt/apt-mirrors.txt` lists mirrors in priority order, and
`sources.list` points at `mirror+file:/etc/apt/apt-mirrors.txt`. Apt
itself handles priority ordering and transparent failover.
The mirror order in this PR (canonical first) was chosen based on what
the diagnostic probes actually showed (see findings below):
```
https://archive.ubuntu.com/ubuntu/ priority:1
https://security.ubuntu.com/ubuntu/ priority:2
http://azure.archive.ubuntu.com/ubuntu/ priority:3
```
Plus a small apt config drop-in
(`/etc/apt/apt.conf.d/80-nx-mirror-failover`) with:
- `Acquire::http::Timeout "5"` / `Acquire::https::Timeout "5"` — cap
each per-fetch stall at 5s instead of the 120s default.
- `Acquire::Retries "0"` — mirror+file already provides
retry-via-failover; apt-level retries on top multiplied the stall when
Azure was consistently dead (60s × dozens of fetched files =
double-digit minutes per agent boot).
## Investigation findings
We probed every mirror from three different vantage points to figure out
what was actually broken:
| Mirror | From your laptop (residential) | From a GitHub-hosted runner
(inside Azure) | From an Nx Agent (GCP) |
| --- | --- | --- | --- |
| `archive.ubuntu.com` (Cloudflare) | ✅ HTTP & HTTPS, sub-second | ✅ | ✅
HTTP & HTTPS, sub-second |
| `security.ubuntu.com` (Cloudflare) | ✅ HTTP & HTTPS, sub-second | ✅ |
✅ HTTP & HTTPS, sub-second |
| `azure.archive.ubuntu.com` HTTP (port 80) | ❌ TCP timeout | ✅ HTTP
200, fast | ❌ TCP timeout |
| `azure.archive.ubuntu.com` HTTPS (port 443) | ❌ TCP timeout | ❌ TCP
timeout | ❌ TCP timeout |
Several things fall out of this:
1. **Azure mirror's HTTPS endpoint is broken in multiple regions** —
even from inside Azure (the GitHub-hosted runner) port 443 times out.
This is why we use `http://` for the Azure entry, matching GitHub's
`configure-apt-sources.sh`.
2. **From our agents' GCP egress, both ports are unreachable** to the
Azure mirror — TCP handshake never completes against `52.154.174.208`
(centralus). This is a path-level issue between Google's network and
Microsoft's edge for the geo-DNS region the agents resolve to.
3. **Canonical mirrors are fully healthy from every vantage point**,
including from agents. Both are CDN-fronted by Cloudflare, which is why
response times are consistent across networks.
4. **`azure.archive.ubuntu.com` is documented as publicly accessible**
(Microsoft Q&A confirms) but historically flaky for non-Azure consumers
— multiple Microsoft Q&A threads and a notable 2020 incident where the
entire `pool/` disappeared. Treating it as best-effort rather than
load-bearing matches what GitHub does.
That's why this PR puts Azure last instead of first. With mirror+file
failover, apt tries `archive.ubuntu.com` first, succeeds in
milliseconds, and never has to touch Azure. The 5s timeout and 0 retries
make the worst case (canonical down + Azure has to be tried) bounded.
A "what about ocean?" datapoint: the ocean agents don't apply the Azure
rewrite at all and have been working fine. This PR effectively converges
on that behavior for normal operation while keeping the original
mismatched-metadata-mid-sync escape hatch via Azure-as-fallback.
## Related Issue(s)
N/A — addresses ongoing CI flakiness from Azure mirror reachability
issues.
## Current Behavior
The `publish` workflow's matrix builds (Linux/macOS/Windows native
binaries via N-API) install Java, Node.js, and pnpm manually inside each
runner/container. With `@nx/dotnet` now in `nx.json`, these builds also
need .NET to be available before the project graph can be loaded — and
there's no .NET install on any of the matrix entries today, so the
workflow fails at the `pnpm nx run-many --target=build-native` step.
The macOS and `armv7-unknown-linux-gnueabihf` matrix entries already use
`mise-action` and `mise.toml`, but the four Linux *docker* entries
(Debian + Alpine, x64 + arm64) bypass mise entirely and provision tools
through hand-rolled `apt-get` / `apk` / `nodesource` / `npm i -g pnpm`
steps.
## Expected Behavior
- All four Linux docker matrix entries now install `mise` from a
signed/distro source (apt repo at `https://mise.jdx.dev/deb` for Debian,
`apk add mise` from Alpine `community` for Alpine) and provision their
entire toolchain — Node.js, Java, .NET, Maven, corepack — from
`mise.toml`. This drops ~30 lines of bespoke install logic per entry and
keeps versions in lockstep with the non-docker matrix entries, which
already use `mise-action`.
- Windows entries gain `choco install dotnet-9.0-sdk -y` alongside the
existing OpenJDK install (mise's Windows .NET path is broken upstream —
see [jdx/mise#4738](https://github.com/jdx/mise/discussions/4738)).
- The FreeBSD build sets `NX_DOTNET_DISABLE=true` (added to both the
`env:` block and the `cross-platform-actions/action`
`environment_variables` allowlist so the var actually crosses into the
FreeBSD VM) to opt out of the plugin entirely.
- `NODE_VERSION` is now forwarded into `docker run` so containers honor
the workflow's pinned Node version through `mise.toml`'s tera template
instead of falling back to its `24.11.0` default.
- `mise` itself is installed only via signed repositories — no `curl
https://mise.run | sh` — so a hijacked DNS lookup against `mise.run`
cannot drop a malicious script into our publish pipeline.
## Related Issue(s)
N/A — workflow fix triggered by `@nx/dotnet` being added to `nx.json`.
## Current Behavior
`readParallelFromArgsAndEnv` reads `process.env.NX_PARALLEL` as a
fallback before the hardcoded `'3'` default. When developers set
`NX_PARALLEL` in their workspace `.env` (or shell), that value bleeds
into the test suite and breaks several specs that assume the default is
`3`:
- `packages/nx/src/utils/command-line-utils.spec.ts` — multiple
`splitArgs` cases plus the `--parallel` defaults.
- `packages/nx/src/command-line/yargs-utils/shared-options.spec.ts` —
`default parallel should be 3`.
CI doesn't set `NX_PARALLEL`, so the failures only show up locally.
## Expected Behavior
The specs should pass regardless of whether `NX_PARALLEL` is set in the
surrounding environment.
Both spec files now save/clear/restore `process.env.NX_PARALLEL` in
their relevant `beforeEach`/`afterEach`, mirroring the pattern already
used for `NX_BASE` and `NX_HEAD`.
## Related Issue(s)
N/A
## Current Behavior
The pnpm lockfile still resolves `@phenomnomnominal/tsquery` to `6.1.4`
for the rollup importer, even though the catalog was bumped to `~6.2.0`
in #35560.
## Expected Behavior
The lockfile resolves `@phenomnomnominal/tsquery` to `6.2.0`, matching
the catalog entry.
## Related Issue(s)
N/A
## Current Behavior
In a Nx 21.x workspace, generators that call `ensurePackage(...)` for a
not-yet-installed plugin (e.g. `@nx/webpack` from `@nx/nest:app`,
`@nx/react:app`, `@nx/js:lib`, etc.) can crash with:
```
Cannot find module nx/dist/src/command-line/release/config/use-legacy-versioning.js
```
immediately after the `Fetching @nx/webpack…` line. The bug was
triggered purely by publishing `nx@22.7.0` on 2026-04-24 — no dependency
change in the user's workspace.
Resolution chain:
1. `ensurePackage` runs `installPackageToTmp`, which installs the
requested plugin into a fresh tmp directory and appends that tmp's
`node_modules` to `NODE_PATH`.
2. `@nx/devkit@21.x` declares a peer dep `"nx": ">= 20 <= 22"`.
3. npm 7+ auto-installs missing peers, picking the highest matching
version → `nx@22.7.x` lands in the tmp dir.
4. `@nx/js@21`'s `library.js` does a top-level
`require("nx/src/command-line/release/config/use-legacy-versioning")`.
5. Pre-22.7.0, `nx`'s `package.json` had no `exports` field, so this
require fell through to filesystem lookup and (when the file wasn't in
the tmp's `nx`) Node continued the search to the workspace's `nx@21` and
resolved successfully.
6. `nx@22.7.0` added a new `"./src/*"` exports wildcard that maps to
`./dist/src/*.js`. Resolution now stops at the tmp's `nx@22.7.x` and
tries to load
`dist/src/command-line/release/config/use-legacy-versioning.js` — which
doesn't exist (deleted in v22). MODULE_NOT_FOUND.
## Expected Behavior
`@nx/js@21`'s top-level `require` resolves successfully, and the library
generator's existing legacy-vs-modern release-config branch makes the
correct decision.
## Fix
Restore
`packages/nx/src/command-line/release/config/use-legacy-versioning.ts`
as a deprecated compat shim. The function body matches the 21.x
implementation exactly (env var override, then
`releaseConfig?.version?.useLegacyVersioning`, defaulting to `false`),
so 21.x callers behave identically to before.
The shim is intentionally not imported anywhere inside Nx 22+ — it
exists purely so external 21.x consumers loaded via `ensurePackage` can
resolve the path. A `TODO(v24)` marks when it's safe to remove.
This needs to ship in the 22.7.x line (so the patched `nx@22.7.x` is
what `@nx/devkit@21`'s peer range resolves to). Please cherry-pick to
`22.7.x` for an `nx@22.7.2` patch release.
## Related Issue(s)
Fixes #
Update the minimatch catalog entry in line with #34660 so catalog
consumers resolve minimatch 10.2.5 and brace-expansion 5.0.5.
This reduces scanner noise for GHSA-7h2j-956f-4vf2 without implying a
practical Nx vulnerability.
## Current Behavior
`useLegacyTypescriptPlugin` opt-in keeps the `rollup-plugin-typescript2`
code path alive in `@nx/rollup` with a deprecation warning. The `withNx`
plugin defaults `buildLibsFromSource` to `false` while the executor
schema defaults to `true` — same option, two defaults.
## Expected Behavior
- `useLegacyTypescriptPlugin` option, schema entry, and
`rollup-plugin-typescript2` dep removed; only
`@rollup/plugin-typescript` remains.
- `withNx` `buildLibsFromSource` default flipped to `true` so it matches
the executor.
- `update-23-0-0-remove-use-legacy-typescript-plugin` migration strips
the deprecated option from existing project.json
`options`/`configurations` and from `rollup.config.{cjs,mjs,js,ts}`
`withNx({...})` calls so users see no behavior change after upgrade.
## Breaking Change Note
`@rollup/plugin-typescript` enforces that the TS `outDir` is inside the
rollup `output.dir`. Users whose custom `rollup.config.{cjs,mjs}`
overrides `output.dir` to a path outside the executor's `outputPath`
will fail with `Path of Typescript compiler option 'outDir' must be
located inside Rollup 'dir' option`. The legacy
`rollup-plugin-typescript2` was permissive here. Affected users should
align their custom config's `output.dir` with the executor's
`outputPath` (or use the function form `(config) => ({ ...config,
output: { ...config.output[0], ...overrides } })` to preserve `dir`).
Two e2e cases that exercised this legacy pattern were dropped from
`rollup-legacy.test.ts`.
## Related Issue(s)
Fixes NXC-4157
## Summary
Resolves Nx Atomizer sandbox violations on
`graph-client:build-client:release`. The original report flagged ~1116
unexpected reads — the entire `packages/nx` source tree (965 files),
`packages/devkit` source (80 files), `graph/client-e2e` Cypress files,
and various `eslint.config.mjs` / `tsconfig.spec.json` / `*.stories.tsx`
siblings across `graph/*` projects.
## Root cause
The bare `graph/` directory was registered as a **webpack context
dependency** for the styles.css module, causing
`FileSystemInfo._readContextHash` to recursively walk and hash every
file underneath it for snapshot validation.
The trigger was a single line in `graph/client/tailwind.config.js`:
```js
path.join(__dirname, '..', 'ui-*/src', glob),
```
The `ui-*` segment is a wildcard at a directory level. To resolve it,
Tailwind has to `readdir` the parent (`graph/`) to enumerate which
subdirs match. Tailwind reports that parent to PostCSS as a
`dir-dependency`, which `postcss-loader` translates into a webpack
context dependency. From there, webpack's snapshot walker:
1. Recursively walks all of `graph/` — including unrelated siblings like
`graph/client-e2e` and `graph/migrate`'s test/eslint configs.
2. Encounters `graph/ui-project-details/node_modules/@nx/devkit` — a
pnpm workspace symlink installed because that lib declares
`"@nx/devkit": "workspace:*"` as a dev dep (purely for `import type`
references).
3. Webpack's `_resolveContextTimestamp` follows the symlink target into
`packages/devkit/`, then through `packages/devkit/node_modules/nx →
packages/nx/`, hashing every file along the way (including `.rs`,
`.snap`, `.fixture` source files that aren't part of any bundle).
## Fix
Enumerate the ui-* dirs explicitly in `graph/client/tailwind.config.js`:
```js
path.join(__dirname, '..', 'ui-code-block/src', glob),
path.join(__dirname, '..', 'ui-common/src', glob),
path.join(__dirname, '..', 'ui-icons/src', glob),
path.join(__dirname, '..', 'ui-project-details/src', glob),
path.join(__dirname, '..', 'ui-render-config/src', glob),
```
With no wildcard at a directory segment, Tailwind reports each
individual `src/` dir as the context dep instead of the bare `graph/`.
Each `src/` subtree contains only source files (no `node_modules`), so
the symlink chain into `packages/{nx,devkit}` is unreachable, and
unrelated siblings like `graph/client-e2e` are no longer touched.
A comment in the config explains the trap so future maintainers know to
add new `ui-*` projects here.
## Empirical results
Local trace of file reads from the webpack-cli subprocess
(`NODE_OPTIONS=--require trace-fs.js` instrumenting `fs.read*`):
| stage | webpack-cli workspace reads | `packages/nx` |
`packages/devkit` | `graph/client-e2e` |
| --------------------- | --------------------------- | ------------- |
----------------- | ------------------ |
| baseline (before fix) | 3010 | 2030 | 112 | 24 |
| after fix | 482 | **0** | **0** | **0** |
Bundle output is byte-identical (2,930,784 bytes for
`dist/apps/graph/main.js`).
The remaining 482 reads are all inside dirs Tailwind legitimately scans
(`graph/client/src`, the explicit `ui-*/src` list, `graph/shared/src`,
plus actual project-graph deps like `graph/migrate`). The `*.stories.*`
and `*.{spec,test}.*` files within those dirs are still hit by the
snapshot walker but are already handled by the existing
`graph-client:build-client` entries in
`.nx/workflows/sandboxing-config.yaml`.
## Other changes
- **`.nx/workflows/sandboxing-config.yaml`** — removed an
outdated/incorrect comment block above the `graph-client` entry. The two
existing exclude patterns (`**/*.stories.*`, `**/*.{spec,test}.*`) cover
the residual noise inside the `ui-*/src` dirs and remain unchanged.
- **`nx.json`** — bumped `bust` to invalidate caches against the
previous attempt.
## Caveats
- New `graph/ui-*` projects require a manual entry in this list — the
config comment calls this out. Worth a follow-up if more `ui-*` packages
are added regularly; an alternative is to read the dir list from the
workspace package map at config-eval time.
- This addresses the Tailwind-driven entry path. The underlying pattern
(a `workspace:*` type-only dep planting a pnpm symlink that webpack's
snapshot walker follows) still exists for any future build that
registers an over-broad context dep. The Tailwind change closes the only
currently-known entry point.
## Verification
- ✅ `pnpm nx run graph-client:build-client:release --skip-nx-cache` →
`webpack compiled successfully`
- ✅ Bundle byte-identical to pre-fix master
- ✅ Empirical file-read trace confirms `packages/nx`, `packages/devkit`,
and `graph/client-e2e` are no longer walked
## Test plan
- [ ] Re-run `graph-client:build-client:release` in CI with sandbox
monitoring; confirm the Atomizer report shows the unexpected-reads count
drop to roughly the count of `*.stories.*` / `*.{spec,test}.*` siblings
inside the ui-* src dirs (already covered by existing sandbox excludes).
- [ ] Confirm dev-server (`nx serve graph-client`) still picks up
Tailwind class changes in each `ui-*` project. The watcher now monitors
each enumerated dir individually instead of via the parent glob.
- [ ] Visual smoke check that the production bundle still renders with
all expected Tailwind classes from `ui-*` libs (no class regressions due
to a missed enumeration).
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/graph-client-build-client-bugs-81a94635)
<!-- polygraph-session-end -->
---------
Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`NX_E2E_SKIP_CLEANUP` is set to `'true'` in every Linux/macOS e2e matrix
entry to gate the build-cache reuse check in `e2e/utils/global-setup.ts`
(skip wiping `e2eCwd` + republishing to verdaccio when `./build` already
exists).
PR #35042 added an early-return to `cleanupProject` in
`e2e/utils/create-project-utils.ts` guarded by the same env var name to
expose a *local* debug opt-in (preserve the per-test tmp project for
inspection). Because CI already had the var set, the new early-return
fires on every CI run, silently disabling the per-test `nx reset` +
`tmpProjPath()` removal that previously kept orphan daemons from
leaking. Jest hangs after all tests pass and the workflow times out at
60 minutes.
## Expected Behavior
Each lifecycle hook is gated by a distinct, scope-specific env var:
- `NX_E2E_SKIP_GLOBAL_CLEANUP` — global-setup.ts (CI sets it).
- `NX_E2E_SKIP_PROJECT_CLEANUP` — cleanupProject (developer-set locally
for debugging only).
Per-test cleanup runs in CI again, jest exits cleanly, and nightly e2e
jobs no longer hit the 60-minute cap.
## Validation
Verified via a manually-dispatched e2e nightly run on this branch (with
the matrix temporarily narrowed to `Linux/{npm,pnpm,yarn}/20 e2e-node`,
the matrix that hung at 60 min on master):
https://github.com/nrwl/nx/actions/runs/25375231501 — all 3 jobs passed
in ~25 minutes each.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The 23.0.0-beta.6 `@nx/devkit` deep-import migration (#35541) catches
non-named-import shapes (default / namespace / side-effect / `require()`
/ dynamic `import()` / `jest.mock`-style calls) by running a regex sweep
over the file:
```ts
const FALLBACK_RE = /(['"])@nx\/devkit\/src\/[^'"\n]+?\1/g;
updated = updated.replace(
FALLBACK_RE,
(_match, quote: string) => `${quote}${INTERNAL_SPECIFIER}${quote}`
);
```
That sweep matches **any** `'@nx/devkit/src/...'` literal anywhere in
the file, regardless of context. As a result the migration mangled:
- **Test fixtures inside template literals** — including the migration's
own `update-deep-imports.spec.ts`. Examples observed in the wild:
`packages/devkit/src/migrations/update-23-0-0/update-deep-imports.spec.ts`,
`packages/expo/src/utils/expo-project-detection.spec.ts`,
`packages/nuxt/src/plugins/plugin.spec.ts`,
`packages/react-native/src/utils/react-native-project-detection.spec.ts`,
etc. ([nrwl/nx#35565](https://github.com/nrwl/nx/pull/35565))
- **`typeof import('@nx/devkit/src/...')` type queries** — e.g.
`libs/shared/npm/src/lib/local-nx-utils/parse-target-string.ts` in
nrwl/nx-console, where the type now claims `@nx/devkit/internal` while
the runtime `importPath` next to it is built dynamically and still
points at `src/...`.
([nrwl/nx-console#3131](https://github.com/nrwl/nx-console/pull/3131))
- **Deep-import paths in comments**, doc strings, and arbitrary
string-literal arguments to unrelated functions.
## Expected Behavior
The migration only rewrites deep-import paths that are *actually* import
sites. Everything else (template strings, type queries, comments,
unrelated calls) is left alone.
This is implemented by replacing the regex sweep with a TypeScript-AST
visitor that only rewrites the string-literal argument of
`CallExpression` nodes whose callee is one of:
- `require` (identifier)
- the dynamic-`import` keyword
- `jest.mock` / `jest.unmock` / `jest.doMock` / `jest.dontMock` /
`jest.requireActual` / `jest.requireMock`
- `vi.mock` / `vi.unmock` / `vi.doMock` / `vi.dontMock` /
`vi.requireActual` / `vi.requireMock` / `vi.importActual` /
`vi.importMock`
Type queries (`ImportTypeNode`), template literals, and comments are all
naturally untouched because they are not `CallExpression` nodes — no
allowlist needed. Quote style is preserved per literal.
The named-import bucketing pass and the duplicate-collapse pass are
unchanged.
### Tests
10 new unit tests:
- 5 in a new `non-runtime string literals` block guarding template
literals, `typeof import(...)` type queries, block comments, line
comments, and unrelated call expressions.
- 5 in a new `mock helper calls` block covering `jest.mock`,
`jest.requireActual`, `vi.mock`, `vi.importActual`, and a paired
`import` + `jest.mock` + `jest.requireActual` combination.
All 39 unit tests pass; `nx build devkit` is clean.
## Related Issue(s)
Follow-up to #35541. Workspaces that have already merged the bad
rewrites need to revert those files by hand — there's no general way to
undo the over-rewrites without losing the legitimate ones.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
The pnpm workspace catalog pins `@phenomnomnominal/tsquery` to `~6.1.4`,
which declares a TypeScript peer dependency of `^3 || ^4 || ^5`.
Downstream Nx consumers running TypeScript 6 under strict peer-deps
(e.g. via `strict-peer-deps=true`) must add an npm `overrides` entry to
install successfully.
## Expected Behavior
Bump the catalog pin to `~6.2.0`. Version 6.2.0 relaxes the TypeScript
peer dependency to `>3.0.0`, allowing TS6 consumers to install Nx
without the workaround.
The diff between 6.1.4 and 6.2.0 is mechanical:
- A perf optimization (cache `parse.ensure()` result rather than calling
each iteration)
- `esquery` dependency bump from `^1.5.0` to `^1.7.0` (additive features
and bugfixes; no selector-syntax breaking changes between 1.5 and 1.7)
- The peer-dep relaxation itself:
https://github.com/phenomnomnominal/tsquery/pull/103
No tsquery API changes between the two versions.
## Related Issue(s)
No tracking issue — small dependency catalog bump.
## Current Behavior
`readTargetsFromPackageJson` (in
`packages/nx/src/utils/package-json.ts`) receives a `workspaceRoot`
argument but never passes it to package manager detection:
```ts
for (const script of includedScripts) {
packageManagerCommand ??= getPackageManagerCommand(); // ← no workspaceRoot
res[script] = buildTargetFromScript(script, scripts, packageManagerCommand);
}
```
Two consequences:
1. **Wrong package manager** — `detectPackageManager()` defaults `dir =
''`, so the lockfile probe runs in the CWD, not the workspace. When that
finds nothing it falls back to `npm_config_user_agent`, so the inferred
`runCommand` (`npm run X` vs `pnpm run X` vs `yarn X`) on script targets
ends up depending on whoever invoked the nx process rather than on the
workspace's actual lockfile.
2. **Module-level cache** — `let packageManagerCommand` (cleared with
`??=`) memoizes the first detection result across all subsequent calls
in the process. So even if the first call had the right `workspaceRoot`,
every later call inherits that detection regardless of *its*
`workspaceRoot`. This is also why
`packages/nx/src/plugins/package-json/create-nodes.spec.ts` had four
pre-existing snapshot failures locally (`pnpm run …` instead of the
expected `npm run …`) — the first test in any process locked detection
to the host's PM.
This is a follow-up to #35116, which moved package manager detection
into the `createNodes` callback for the inferred plugins but missed this
code path.
## Expected Behavior
- Drop the module-level cache.
- Thread `workspaceRoot` into both `detectPackageManager` and
`getPackageManagerCommand`, so the lockfile probe runs in the right
directory.
```ts
if (includedScripts.length > 0) {
const packageManagerCommand = getPackageManagerCommand(
detectPackageManager(workspaceRoot),
workspaceRoot
);
for (const script of includedScripts) {
res[script] = buildTargetFromScript(script, scripts, packageManagerCommand);
}
}
```
The `packages/nx/src/plugins/package-json/create-nodes.spec.ts` fixture
now seeds `package-lock.json` into memfs in a `beforeEach`, matching the
pattern #35116 established for plugin specs. Without the lockfile the
detector still falls back to the env var; with it, every test
deterministically picks `npm`, matching the existing snapshots.
## Verification
Before this PR: 4 failures in
`packages/nx/src/plugins/package-json/create-nodes.spec.ts`:
```
✕ should build projects from package.json files
✕ should store js package metadata
✕ should add a script target if the sibling project.json file does not exist
✕ should add a script target if the sibling project.json exists but does not have a conflicting target
Tests: 4 failed, 7 passed, 11 total
```
After this PR:
```
Tests: 11 passed, 11 total
```
## Related Issue(s)
Follow-up to #35116.
## Current Behavior
After #34946, `@nx/devkit` ships a strict `exports` map. Deep imports
like `@nx/devkit/src/utils/...` and `@nx/devkit/src/generators/...` are
no longer reachable through Node module resolution. Workspaces upgrading
to `23.x` that reference those paths break with module-resolution errors
at runtime / type-check time.
## Expected Behavior
`nx migrate` runs an automated rewrite that covers the realistic shapes
of these deep imports.
The migration walks every `.ts`/`.tsx`/`.cts`/`.mts` file in the
workspace and:
1. **Buckets named imports by symbol.** Each `@nx/devkit/src/...` import
declaration is parsed via the TypeScript compiler API (lazy-loaded with
`ensurePackage`). Specifiers in the `internal.ts` re-export list go to
`@nx/devkit/internal`; all others go to `@nx/devkit`. Mixed imports
split into two declarations.
2. **Falls back for non-named shapes.** Default imports, namespace
imports, side-effect imports, `require(...)` calls, and dynamic
`import(...)` get the specifier swapped to `@nx/devkit/internal` (the
safe default — it re-exports every previously deep-importable symbol).
3. **Collapses duplicate imports.** A second AST pass groups `import {
... } from '@nx/devkit'` and `import { ... } from '@nx/devkit/internal'`
declarations by `(specifier, isTypeOnly)`, merging each 2+ group into
one declaration with deduplicated specifiers. This handles both the
duplicates the rewrite produced and any pre-existing devkit imports the
user already had.
Edits are stacked via `applyChangesToString` (devkit's offset-tracking
text-mutation helper), then `formatFiles` normalizes formatting.
### Example
Before:
```ts
import { Tree } from '@nx/devkit';
import { dasherize, names } from '@nx/devkit/src/utils/string-utils';
import { addPlugin } from '@nx/devkit/src/utils/add-plugin';
```
After:
```ts
import { Tree, names } from '@nx/devkit';
import { dasherize, addPlugin } from '@nx/devkit/internal';
```
### Tests
29 unit tests cover: single-bucket and mixed-bucket rewrites, `as`
aliases, `import type` and inline `type` modifiers, multi-line imports,
side-effect / default / namespace fallbacks, `require()` and dynamic
`import()` fallbacks, quote-style preservation, pre-existing-import
merge, public/internal independence, value-vs-type segregation,
specifier deduplication, and a sanity test that every name in
`DEVKIT_INTERNAL_SYMBOLS` is bucketed as internal.
A user-facing `update-deep-imports.md` lives next to the implementation;
the astro-docs build picks it up via the existing
`packages/*/src/migrations/**/*.md` input glob.
## Related Issue(s)
Follow-up to #34946.
## Current Behavior
`packages/nx/Cargo.toml` pins `ratatui = "0.29"` and consumes `tui-term`
from a personal git fork (`JamesHenry/tui-term @ 88e3b614…`). The fork
carries two custom commits on top of upstream `tui-term`: a vt100 →
vt100-ctt swap, and a `Modifier::DIM` mapping for dimmed PTY content.
`tui-logger` is also held back at `0.17.2`.
The upstream `a-kenji/tui-term` repo has since merged the dim-modifier
patch (PR #340) and shipped `tui-term 0.3.4` against the new modular
`ratatui-core 0.1` / `ratatui-widgets 0.3` crates that came with
`ratatui 0.30`. This means we no longer need to maintain a tui-term fork
at all — the only remaining reason for it (the dim patch) is upstream.
## Expected Behavior
- `ratatui` bumped to `0.30.0`.
- `tui-term` swapped from the `JamesHenry/tui-term` git dep to crates.io
`tui-term = { version = "0.3.4", default-features = false }`.
- `tui-logger` bumped `0.17.2` → `0.18.2` (which targets `ratatui
^0.30`).
- `vt100-ctt` stays as-is — we still need its `all_contents`,
`all_contents_formatted`, `get_total_content_rows`, and
`Parser::get_raw_output` APIs that aren't in upstream `vt100`. Its
default `tui-term` feature is now disabled so it doesn't drag old
`ratatui 0.29` / `tui-term 0.2` back into the dep tree.
- New `packages/nx/src/native/tui/vt100_adapter.rs` (~95 lines)
implements `tui_term::widget::{Screen, Cell}` for `vt100_ctt::{Screen,
Cell}` via `#[repr(transparent)]` newtypes (orphan-rule workaround). The
two `PseudoTerminal::new(&*screen)` call sites in `terminal_pane.rs` and
`inline_app.rs` now wrap the screen with
`Vt100CttScreen::wrap(&screen)`.
- One unused `Stylize` import dropped from `tasks_list.rs` (ratatui 0.30
made the styling methods inherent).
Resolved dep tree after the bump:
```
ratatui v0.30.0
ratatui-core v0.1.0
ratatui-widgets v0.3.0
ratatui-crossterm v0.1.0
ratatui-macros v0.7.0
tui-logger v0.18.2
tui-term v0.3.4 (crates.io, no fork)
vt100-ctt v0.16.0 (fork) (kept for scrollback / raw_output APIs)
```
`cargo check` and `cargo clippy --frozen --all-targets` are clean
(warnings only, all pre-existing on master). `cargo test --lib` passes
350/352; the two flaky failures are in `native::watch::watcher::tests`
(filesystem-event timing tests, unrelated and inconsistent across runs).
## Related Issue(s)
None — refactor / dependency hygiene.
## Current Behavior
Many e2e-ci projects are pinned to serial execution on each CI agent via
project-specific overrides in `.nx/workflows/dynamic-changesets.yaml`:
- `e2e-gradle`, `e2e-angular`, `e2e-node`, `e2e-react` → 1 task per
`linux-extra-large`
- `e2e-next`, `e2e-plugin` → 2 tasks per `linux-extra-large`
- `e2e-release`, `e2e-nuxt`, `e2e-web`, `e2e-eslint`, `e2e-remix`,
`e2e-cypress`, `e2e-docker`, `e2e-js`, `e2e-nx`, `e2e-nx-init`,
`e2e-dotnet`, `e2e-workspace-create`, `e2e-rollup` → 1 on large / 2 on
xlarge
These overrides exist because the underlying tests collide on hardcoded
ports when run concurrently on the same agent.
## Expected Behavior
A single rule lets every `e2e-ci**` task run with parallelism 2 on
`linux-large` and 3 on `linux-extra-large`, shrinking total agent time
and removing per-project special cases.
To make that safe, this PR:
1. Adds `reservePort()`/`reservePorts()` to `e2e/utils/port-utils.ts`.
The helper claims a port via an atomic `O_EXCL` lock file under
`/tmp/nx-e2e-port-locks`, so two parallel processes on the same agent
cannot reserve the same port. The existing `getAvailablePort()` is
deprecated — probing port 0 and then binding it seconds later opens a
TOCTOU race where another e2e task could grab the same port in between.
2. Replaces hardcoded ports in the highest-risk e2e tests with
`reservePort()`:
- `e2e/vite/src/vite.test.ts` — `serve-static` test (was 8081)
- `e2e/web/src/web-webpack.test.ts` — webpack ssl serve (was 5000)
- `e2e/storybook/src/storybook-angular.test.ts` and
`storybook-nested.test.ts` (was 4400)
- `e2e/node/src/node-server.test.ts` — express/fastify/koa/nest
framework tests (was 7000–7003) and waitUntilTargets test (was
4444/4445)
- `e2e/node/src/node-esm-support.test.ts` — 9 tests previously
defaulting to port 3000
`e2e/cypress`, `e2e/playwright`, and the React module-federation tests
still hardcode ports through their generators; those will be addressed
in follow-up PRs as CI surfaces collisions.
## Related Issue(s)
N/A — internal CI optimization.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
`@nx/js:prune-lockfile` does not recursively walk workspace→workspace
dependencies. Given an `app → @myorg/lib-a → @myorg/lib-b → lodash`
chain (where `lib-a` and `lib-b` are workspace packages), the executor
produces a pruned `pnpm-lock.yaml` that:
1. **Misses the importer block** for `workspace_modules/@myorg/lib-b`
(the transitive workspace dep).
2. **Misses `lodash`** (lib-b's npm dep) from the `packages:` section.
3. **Keeps `specifier: workspace:*`** for `lib-b` inside `lib-a`'s
importer block, even though `@nx/js:copy-workspace-modules` rewrites
`lib-a/package.json` to `"@myorg/lib-b": "file:../lib-b"`.
The specifier mismatch causes `pnpm install --frozen-lockfile` to fail
in the deployed output:
```
ERR_PNPM_OUTDATED_LOCKFILE Cannot install with "frozen-lockfile" because
pnpm-lock.yaml is not up to date with workspace_modules/@myorg/lib-a/package.json
- @myorg/lib-b (lockfile: workspace:*, manifest: file:../lib-b)
```
## Expected Behavior
`@nx/js:prune-lockfile` recursively discovers transitive workspace
dependencies and produces a lockfile that:
1. Contains an `importers` block for every workspace module that
`copy-workspace-modules` writes to disk.
2. Includes the npm dependencies of all transitive workspace modules in
the `packages:` section.
3. Rewrites workspace-package references inside nested importers to the
flat `workspace_modules/` layout (`specifier: file:<rel>` / `version:
link:<rel>`), matching what `copy-workspace-modules` writes to each
package's `package.json`.
`pnpm install --frozen-lockfile` succeeds in the pruned output
directory.
### Implementation
Two coordinated changes in lockfile-side code:
- **`project-graph-pruning.ts`** — `traverseWorkspaceNode` now recurses
into workspace→workspace dependency edges with a `visited` set, so
transitive workspace npm deps reach the pruned graph.
- **`pnpm-parser.ts`** — `stringifyPnpmLockfile` BFS-collects transitive
workspace importers, deep-clones each importer block, and rewrites
workspace-package references to the flat `workspace_modules/` layout.
### Tests
- 3 new unit tests in `pnpm-parser.spec.ts` covering the canonical
chain, dependency cycles, and diamond shapes.
- 1 new e2e test in `e2e/js/src/js-executor-prune-lockfile.test.ts`
exercising the canonical chain end-to-end.
- Verified end-to-end against the reported reproduction repo: `pnpm
install --frozen-lockfile` now succeeds in the pruned output where it
previously failed with `ERR_PNPM_OUTDATED_LOCKFILE`.
### Credit
Diagnosis and original fix sketch by @estevaolucas in #35347 — this PR
carries forward the recursion + specifier rewrite portions in a focused
change. The other concerns from #35347 (devDep/peerDep stripping,
catalog reference resolution in `copy-workspace-modules`) are real but
separable and intentionally left for follow-up issues.
## Related Issue(s)
Fixes#34655
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
The `@nx/cypress:cypress` executor runs silently with no signal that
it's slated for removal. Users running `nx g @nx/cypress:configuration`
or `nx g @nx/cypress:component-configuration` on workspaces that don't
have `@nx/cypress/plugin` registered get executor-based `e2e` (or
`component-test`) targets scaffolded into their `project.json` with no
warning.
The `@nx/cypress:cypress` executor is a thin wrapper over the Cypress
CLI. The `@nx/cypress/plugin` inferred plugin produces equivalent `e2e`
and `component-test` targets directly from `cypress.config.*`, and `nx g
@nx/cypress:convert-to-inferred` is the one-shot migration tool to move
existing executor-based targets onto the inferred plugin.
## Expected Behavior
The `@nx/cypress:cypress` executor is marked as deprecated for removal
in Nx v24. Users see the deprecation message on three surfaces:
- **Runtime warning** at the top of `cypress.impl.ts` — fires every
executor invocation, no throttling.
- **Scaffold-time warning** in `configuration.ts` and
`component-configuration.ts` — fires when the generators are about to
emit an executor-based target (i.e., when `@nx/cypress/plugin` isn't in
`nx.json`).
- **Schema-root `x-deprecated`** on `executors/cypress/schema.json` —
surfaces in IDE / docs metadata.
All three messages link to the general convert-to-inferred guide at
https://nx.dev/docs/guides/tasks--caching/convert-to-inferred.
The `@nx/cypress` package itself, the `@nx/cypress/plugin` inferred
plugin, the `configuration` and `component-configuration` scaffolding
generators, and the `convert-to-inferred` migration tool all **remain
supported**. Only the `:cypress` executor is deprecated. Removal is
targeted for v24; this PR is warnings only, no behavior removal.
The PR title uses `feat!` so the deprecation surfaces in the v23
changelog / release notes; individual commits stay `chore` / `docs` /
`fix` for clean per-commit history.
### Internal usage / dogfood note
`graph/client-e2e/project.json` uses `@nx/cypress:cypress` and is
**intentionally not migrated** in this PR. Hitting the deprecation
warning on every internal CI run is the team's continuous dogfood signal
that the migration tool, the wording, and the suggested path actually
work. If it's painful for us, it's painful for users; that pressure
should drive iteration on `convert-to-inferred`. The internal project
will be migrated alongside the v24 removal PR (or earlier if the warning
becomes legitimately disruptive).
### Surfaces explicitly NOT touched
- Framework-specific cypress component-testing generators (angular /
react / next / remix) — unchanged.
- The Angular `ng-add` e2e migrator — unchanged.
- `migrations.json` — no migration entry. Runtime warnings + docs are
enough.
- `npm deprecate` — not run; the package isn't deprecated, only the
executor.
- Cypress-specific docs page — no per-plugin convert-to-inferred page
exists for any other plugin (Detox, Playwright, etc.). Pointing the
warnings at the general guide keeps that consistent and avoids one more
page to maintain.
### Test plan
- [x] `pnpm nx build cypress` passes.
- [ ] `pnpm nx test cypress` — verify no regressions from the new
warning calls.
- [ ] Verify the runtime warning fires when running `nx e2e <project>`
on a project that uses the executor (e.g., `graph/client-e2e`).
- [ ] Verify the scaffold-time warning fires on `nx g
@nx/cypress:configuration` / `:component-configuration` in a workspace
without `@nx/cypress/plugin`.
- [ ] Verify `convert-to-inferred` still works end-to-end as the
migration path.
## Related Issue(s)
Fixes NXC-4264
This PR follows the canonical executor-deprecation pattern documented in
Linear NXC-4422, established by the `@nx/detox` executor deprecation
(NXC-4272 / nrwl/nx#35529).
Related Linear context:
- **NXC-4422** — Pattern: executor deprecation (canonical reference).
- **NXC-4272** / **nrwl/nx#35529** — `@nx/detox` executor deprecation,
the reference implementation.
- **NXC-4420** — Nx core: surface schema-root `x-deprecated` on
executors at runtime. Once landed, the explicit `logger.warn` in this PR
becomes redundant.
## Current Behavior
`@nx/web`, `@nx/js`, and `@nx/cypress` declare `detect-port: ^1.5.1`.
Per the bulk-dependency-update sweep (NXC-4329), this is due for a major
bump. The queue task initially flagged this as ESM-only — that was stale
info; v2 is actually dual-published.
## Expected Behavior
Bumped to `detect-port@^2.1.0`.
## Validation
`detect-port@2.1.0` ships both ESM and CJS builds via the package.json
exports map:
```jsonc
{
"type": "module",
"main": "./dist/commonjs/index.js",
"exports": {
".": {
"import": { "types": "./dist/esm/index.d.ts", "default": "./dist/esm/index.js" },
"require": { "types": "./dist/commonjs/index.d.ts", "default": "./dist/commonjs/index.js" }
}
},
"engines": { "node": ">= 16.0.0" }
}
```
The three call sites in this repo:
```ts
// packages/web/src/executors/file-server/file-server.impl.ts
const detectPort = require('detect-port'); // CJS path
// packages/js/src/executors/verdaccio/verdaccio.impl.ts
import detectPort from 'detect-port'; // compiles to require → CJS
// packages/cypress/src/utils/start-dev-server.ts
import detectPort from 'detect-port'; // compiles to require → CJS
```
All three resolve to `./dist/commonjs/index.js` — no code changes
needed. The default-export function shape (`detect(port, callback?) →
Promise<number>`) is unchanged across v1 → v2.
`pnpm nx run-many -t build -p web,js,cypress` — passes.
## Related Issue(s)
Part of [NXC-4329](https://linear.app/nxdev/issue/NXC-4329)
bulk-dependency-update sweep.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
Maven 4 batch invocations can overlap build-state recording with another
resident invocation on the same adapter. When Maven logs from that path,
the Maven 4 logging context can have a null terminal and throw a
`context.terminal` NPE.
## Expected Behavior
Maven 4 invocation and build-state recording are serialized on the
adapter, so build-state logging uses a valid Maven context. The Maven 4
batch e2e spec also exercises parallel `resources` and `after:resources`
targets.
## Related Issue(s)
N/A
## Current Behavior
The `@nx/detox:build` and `@nx/detox:test` executors run silently with
no signal that they're slated for removal in a future major. Users
picking Detox via `@nx/react-native:application` or
`@nx/expo:application` (when `@nx/detox/plugin` isn't registered) get
executor-based `build-ios`, `test-ios`, `build-android`, and
`test-android` targets scaffolded into their `project.json` with no
warning.
The executors fork the Detox CLI as a child process with argv glue —
they add no behavior the CLI doesn't already provide. The
`@nx/detox/plugin` inferred plugin produces equivalent `build` / `test`
/ `start` targets directly from the user's `.detoxrc` config, and `nx g
@nx/detox:convert-to-inferred` is the one-shot migration tool to move
existing executor-based targets onto the inferred plugin.
## Expected Behavior
The `@nx/detox:build` and `@nx/detox:test` executors are marked as
deprecated for removal in Nx v24. Users see the deprecation message on
three surfaces:
- **Runtime warning** at the top of `build.impl.ts` and `test.impl.ts` —
fires every executor invocation, no throttling.
- **Scaffold-time warning** in `application/lib/add-project.ts` — fires
when the application generator is about to emit executor-based targets
(i.e., when `@nx/detox/plugin` isn't in `nx.json`). Catches both direct
`nx g @nx/detox:application` invocations and the transitive
`@nx/react-native:application` / `@nx/expo:application` paths.
- **Schema-root `x-deprecated`** on the build/test executor schemas —
surfaces in IDE / docs metadata.
All three messages link to the general convert-to-inferred guide at
https://nx.dev/docs/guides/tasks--caching/convert-to-inferred.
The `@nx/detox` package itself, the `@nx/detox/plugin` inferred plugin,
the `application` and `init` generators, and the `convert-to-inferred`
migration tool all **remain supported**. Only the two executors are
deprecated. Their actual removal is targeted for v24; this PR is
warnings only, no behavior removal. The `convert-to-inferred` generator
will be removed alongside the executors in v24 since it only exists to
migrate off them.
The PR title uses `feat!` so the deprecation surfaces in the v23
changelog / release notes; individual commits stay `chore` / `docs` /
`fix` for clean per-commit history.
### Bonus: convert-to-inferred quality fixes
While walking the deprecation flow end-to-end against a test workspace,
two issues with the existing `@nx/detox:convert-to-inferred` generator
surfaced and are fixed in this PR:
1. **Mislabeled migration log** — option-migration warnings on
`@nx/detox:test` were rendering under "Encountered the following while
migrating '@nx/expo:test'" due to a wrong `executorName` constant. Now
labeled correctly.
2. **`buildTarget` option dropped silently** — the generator was
deleting the `buildTarget` option from migrated test targets and
emitting an opaque "use --reuse" warning, leaving users without the
build-before-test chain they had before. Now the original target
reference is preserved verbatim as a `dependsOn` entry on the migrated
`test` target, so the chain keeps working post-migration with no manual
fix-up.
### Surfaces explicitly NOT touched
- `init` generator — doesn't emit executor targets, no warning needed.
- `application` and `convert-to-inferred` generator entry points —
`application`'s emission is caught at the `add-project` level instead;
`convert-to-inferred` IS the migration tool.
- `packages/react-native/.../add-e2e.ts`, `packages/expo/.../add-e2e.ts`
— transitive scaffolding falls through to `add-project.ts` which warns
at executor emission.
- `migrations.json` — no migration entry. Runtime warnings + docs are
enough.
- `npm deprecate` — not run; the package isn't deprecated, only the
executors.
- Detox-specific docs page — no per-plugin convert-to-inferred page
exists for any other plugin (Cypress, Playwright, etc.). Pointing the
warnings at the general guide keeps that consistent and avoids one more
page to maintain.
### Test plan
- [x] `pnpm nx build detox` passes.
- [x] `pnpm nx run-many -t lint -p detox react-native expo` passes.
- [ ] Verify the runtime warning fires on `nx test-ios <project>` in a
test workspace.
- [ ] Verify the scaffold-time warning fires when running `nx g
@nx/detox:application <name>` without `@nx/detox/plugin` registered.
- [ ] Verify `convert-to-inferred` end-to-end: `buildTarget` becomes
`dependsOn`; warnings label as `@nx/detox:test`.
## Related Issue(s)
Fixes NXC-4272
Related Linear context:
- **NXC-4420** — Nx core: surface schema-root `x-deprecated` on
executors at runtime. Once landed, the explicit `logger.warn` in this
PR's executors becomes redundant and can be removed.
- **NXC-4419** — `@nx/rsbuild` maintenance plan (parallel audit decided
to maintain rsbuild rather than deprecate).
- **NXC-4291** — Original `@nx/rsbuild` deprecation, canceled in favor
of the maintenance path above.
## Current Behavior
Unit tests intermittently fail with `ValueExpected` JSON parse errors
against `.nx/workspace-data/<plugin>-<hash>.hash` files, e.g.:
```
Error: ValueExpected in /home/workflows/workspace/.nx/workspace-data/jest-7930610538513362720.hash at 1:1
> 1 |
| ^
```
Each createNodes plugin (jest, next, vite, vitest, storybook, docker,
rsbuild, rspack, webpack, rollup, react-native, expo, detox, eslint,
remix, react/router, nuxt, angular) maintains its own targets cache via
a roughly identical `readTargetsCache` / `writeTargetsToCache` pair
built on `readJsonFile` / `writeJsonFile`. The write is non-atomic
(`writeFileSync`); when two callers race on the same cache path, a
reader can observe the empty truncated file mid-write, and
`readJsonFile` throws `ValueExpected`.
A few drive-by issues uncovered along the way:
- `detox` was reading from `expo-${hash}.hash` (wrong filename
copy/paste).
- `detox`, `expo`, `react-native` writes were `{ ...oldCache,
targetsCache }` without spread — storing the cache object literally
under the key `targetsCache` instead of merging entries, so on-disk
cache was effectively useless across runs.
## Expected Behavior
All createNodes plugins now use the shared `PluginCache` utility from
`@nx/devkit/internal` (already in use by Gradle and the .NET analyzer).
`PluginCache`:
- Wraps the cache read in `try/catch`, treating empty/corrupt files as a
cache miss instead of throwing — eliminates the `ValueExpected` flake.
- Wipes the cache file on write error so corruption can't persist across
runs.
- Tracks LRU access order and evicts entries when serialization hits
`RangeError`.
Per-plugin pattern change:
```ts
// before
const targetsCache = readTargetsCache(cachePath);
targetsCache[hash] ??= await buildTargets(...);
const result = targetsCache[hash];
// later:
writeTargetsToCache(cachePath, targetsCache);
```
```ts
// after
const targetsCache = new PluginCache<TargetsType>(cachePath);
if (!targetsCache.has(hash)) {
targetsCache.set(hash, await buildTargets(...));
}
const result = targetsCache.get(hash);
// later:
targetsCache.writeToDisk(cachePath);
```
Plugins migrated: `angular`, `detox`, `docker`, `eslint`, `expo`,
`jest`, `next`, `nuxt`, `react-native`, `react` (router-plugin),
`remix`, `rollup`, `rsbuild`, `rspack`, `storybook`, `vite`, `vitest`,
`webpack`.
Skipped:
- `packages/dotnet/src/utils/cache.ts` — orphaned dead code with no
callers; `dotnet/src/analyzer/analyzer-client.ts` already uses
`PluginCache`.
- `packages/js/src/plugins/typescript/plugin.ts` — already has its own
resilient read (try/catch) and atomic temp+rename write; migrating would
regress on the write side.
### On-disk cache format change
`PluginCache` stores `{ entries, accessOrder }` instead of a flat
record. Stale caches written by older versions are simply discarded on
read — perf-only impact, no correctness implication.
## Validation
`pnpm nx run-many -t test --parallel=8 --skip-nx-cache` produces the
same set of failed tasks on this branch as on `master` (24 tasks, all
pre-existing snapshot/env issues unrelated to this change). Net new
failures introduced: 0.
## Related Issue(s)
N/A — addresses observed flake during unit tests; no specific issue
tracked.
## Current Behavior
`tools/workspace-plugin` pins `@nx/devkit`, `@nx/js`, and `@nx/plugin`
at `22.7.0-beta.16`, lagging behind the rest of the workspace which is
on `23.0.0-beta.4`. Powerpack packages (`@nx/conformance`, `@nx/key`,
`@nx/powerpack-license`) are still on the `3.x`/`4.x` line.
## Expected Behavior
The workspace plugin uses the same Nx version as the rest of the repo,
and powerpack packages are on the latest stable (`5.0.4`).
- `tools/workspace-plugin/package.json`: `@nx/devkit`, `@nx/js`,
`@nx/plugin` → `23.0.0-beta.4`; `@nx/conformance` → `5.0.4`
- root `package.json`: `@nx/conformance`, `@nx/key`,
`@nx/powerpack-license` → `5.0.4`
## Related Issue(s)
N/A
## Current Behavior
`@nx/devkit`'s `build-base` target overrode `outputs` in `project.json`:
```json
"outputs": [
"{projectRoot}/dist/**/*.{js,cjs,mjs,d.ts}",
"{projectRoot}/*.d.ts",
"{projectRoot}/src/**/*.d.ts"
]
```
This override is missing `tsconfig.tsbuildinfo`. Any downstream task
whose inputs include `dependentTasksOutputFiles:
"**/*.{d.ts,d.cts,d.mts,tsbuildinfo}"` (e.g. `docker:build-base`) trips
a sandbox violation: when `tsc --build` walks project references it
reads devkit's tsbuildinfo, but Nx never registered that file as a dep
output, so the read isn't covered by any declared input.
The same gap exists in the `dist-build-migration` Claude skill, so every
package migrated with that playbook would reproduce the violation.
Sandbox report that surfaced this:
https://staging.nx.app/runs/HNBpzgdeNi/task/docker%3Abuild-base/sandbox-report-raw?sandboxReportId=10b903d8-b860-41c7-80b2-756b0541e690
## Expected Behavior
Drop the override entirely. The `@nx/js/typescript` plugin already reads
`outDir` and `tsBuildInfoFile` from `tsconfig.lib.json` and infers a
strictly more complete set of outputs:
```
{projectRoot}/dist/**/*.{js,cjs,mjs,jsx,json,d.ts,d.cts,d.mts}{,.map}
{projectRoot}/dist/tsconfig.tsbuildinfo
```
The inferred set picks up the tsbuildinfo plus
`.cjs`/`.mjs`/`.json`/`.d.cts`/`.d.mts`/`.map` that the manual override
was missing. Verified the violation is resolved:
```
$ pnpm nx show target inputs docker:build-base --check packages/devkit/dist/tsconfig.tsbuildinfo
✓ packages/devkit/dist/tsconfig.tsbuildinfo is an input for docker:build-base (depOutputs)
```
The `dist-build-migration` skill is updated to tell future migrations to
leave `build-base.outputs` to the plugin.
## Related Issue(s)
Follow-up to #34946.
## Current Behavior
`@nx/devkit` builds to `<workspaceRoot>/dist/packages/devkit/` — outside
its own package directory. Other packages reach into that shared
workspace `dist/` to consume devkit, and consumers using
`moduleResolution: nodenext` need a custom Module._resolveFilename hack
to find it.
This is inconsistent with `nx` itself, which already builds to
`packages/nx/dist/` (#34111).
## Expected Behavior
`@nx/devkit` builds to `packages/devkit/dist/` and is consumed via a
standard `exports` map. Workspace consumers resolve through normal pnpm
symlinks; the `@nx/nx-source` condition still lets in-repo code resolve
to `.ts` source during dev.
This unblocks the rest of the workspace from migrating in the same
direction (a `dist-build-migration` Claude skill is included as a
per-package playbook).
## How to review
The PR has **307 files but only 3 categories of work**. Most of the diff
is mechanical.
### 1. The actual migration — review carefully (~10 files)
Devkit package config and the `@nx/nx-source` condition wiring:
- `packages/devkit/package.json` — `exports` map, `typesVersions`,
`files`, `type: commonjs`, `main`/`types` repointed to `dist/`
- `packages/devkit/tsconfig.lib.json` — `outDir: dist`, `nodenext`
module/resolution
- `packages/devkit/project.json` — `build-base` outputs,
`nx-release-publish.packageRoot`, `manifestRootsToUpdate`
- `packages/devkit/internal.ts` — re-exports `src/utils/*` and
`src/generators/*` symbols (replaces the `@nx/devkit/src/*` deep-import
pattern)
- `packages/devkit/eslint.config.mjs` — ignore `dist`
- `packages/devkit/{README.md → readme-template.md}` — README is now
generated, gitignored
- `.gitignore` — ignore the generated `packages/devkit/README.md`
- `scripts/nx-release.ts` — devkit's `package.json` now lives at
`packages/devkit/package.json`, not `dist/packages/devkit/package.json`
- `scripts/patched-jest-resolver.js` — adds `@nx/nx-source` condition
### 2. Mass import rewrites — already marked viewed (~205 files)
Every internal import of `@nx/devkit/src/utils/<x>` or
`@nx/devkit/src/generators/<x>` was rewritten to `@nx/devkit/internal`:
```diff
-import { foo } from '@nx/devkit/src/utils/bar';
+import { foo } from '@nx/devkit/internal';
```
I marked these files as **viewed** in the GitHub review UI to clear them
from the unread queue. They're across nearly every plugin (angular,
react, next, vite, webpack, rspack, expo, jest, etc.).
### 3. Follow-on cleanups (~10 files)
These appear in their own commits and are easy to review individually:
- **`cleanup(angular-rspack)`** — removes the `patchDevkitRequestPath`
runtime hack from 14 example configs; devkit now resolves through
standard node_modules (the MF patch stays — module-federation isn't
migrated yet).
- **`cleanup(devkit)` typedoc** — drops dead path mappings + redundant
include manipulation in
`astro-docs/src/plugins/utils/typedoc/typedoc.ts` (verified docs build
is byte-identical with/without the removed config).
- **`cleanup(devkit)` eslint ignores** — drops `'**/*.d.ts'` from
`packages/devkit/eslint.config.mjs` since `.d.ts` files only emit to
`dist/` (already ignored).
- **`fix(angular)` eslint quote** — quote-agnostic regex in
`e2e/angular/src/projects-linting.test.ts` so the test still disables
`prefer-standalone` after the angular-eslint generator switched to
double quotes (latent bug surfaced by CI).
- **`fix(testing)` jest migration** — removes a stray unused
`@nx/devkit/internal` import.
- **e2e test fallout** — `e2e/{angular,next}/src/*.test.ts` lose access
to `@nx/devkit/src/utils/string-utils` (e2e tests can't use
`/internal`); inline equivalents using public `names()` API.
`e2e/nx-build/src/nx-build.test.ts` updates the expected output path.
## Local verification
- `pnpm nx run-many -t test,build,lint -p devkit` ✓
- `pnpm nx run astro-docs:build` ✓ — devkit reference pages still
generate (148 markdown files; identical to a build with the path
mappings re-added as a control)
- `pnpm nx run-many -t build -p
examples-angular-rspack-csr-css,examples-angular-rspack-ssr-css,examples-angular-rspack-zoneless-csr-css,examples-angular-rspack-mf-host,examples-angular-rspack-mf-remote
--skip-nx-cache` ✓ — examples build without the devkit patch
## Known follow-up (not in this PR)
- `scripts/nx-release.ts` still has `hackFixForDevkitPeerDependencies()`
(a band-aid from #32406 that re-adds `<=` to devkit's `nx` peer-dep
range after `nx release version` strips it). The proper fix is to set
`preserveMatchingDependencyRanges: true` in
`packages/devkit/project.json` and delete the hack — that needs a
release dry-run to verify, so it's queued separately.
## Related Issue(s)
Follow-up to #34111.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The Gradle batch executor (`@nx/gradle:gradle` in batch mode) returns a
`Promise<BatchResults>`. The Kotlin batch runner serializes the entire
result map to a single JSON blob and `println`s it once at the end of
the run. The Node-side executor accumulates stdout chunks and
`JSON.parse`s them after the JVM exits, so Nx only learns about task
outcomes in one burst when the whole batch is done.
The Maven batch executor was migrated to streaming a while ago — it
returns an `AsyncGenerator` and the Kotlin runner emits
`NX_RESULT:{json}` lines as each task finishes — but the Gradle batch
executor was never updated to match.
## Expected Behavior
The Gradle batch executor now mirrors the Maven batch executor's
streaming protocol, with per-task results streamed live during both
**build** and **test** task execution.
### Kotlin runner (`packages/gradle/batch-runner`)
- **`ResultEmitter`** writes `NX_RESULT:{json}` lines to stdout, one per
task, with a thread-safe dedupe set so emission can happen from
build/test listeners without double-reporting.
- **`runBuildLauncher`** emits per build task as `TaskOutputCapture`
detects the next task's `> Task :foo:bar` header, with an end-of-build
flush for the final task.
- **`runTestLauncher`** emits per Nx test task at its class-level
`TestFinishEvent`, with a `TaskFinishEvent` fallback for tasks that
never produced a class event (compile failure, exclusion). Method-level
failures are sticky so a later passing method in the same class can't
mask an earlier failure.
- **`NxBatchRunner.main`** ends with `exitProcess(0)` so lingering
non-daemon threads from the Gradle Tooling API can't keep the JVM alive
after task work completes. The trailing `results.forEach { emit }` loop
now only covers `finalizeTaskResults`-synthesized entries
(excluded/skipped tasks).
### Node executor
(`packages/gradle/src/executors/gradle/gradle-batch.impl.ts`)
- `gradleBatch` is now an `async function*` returning `AsyncGenerator<{
task; result: TaskResult }>`.
- `streamTasksInBatch` spawns the JVM, reads stdout via `readline`, and
**drains `NX_RESULT` lines into an in-memory queue**, yielding from the
queue. Yielding inside the readline loop creates back-pressure — slow
consumers block `yield`, readline pauses, the OS pipe between Java and
Node fills, and Java's `println` blocks on a full pipe. The queue
decouples reading from yielding so back-pressure can no longer deadlock
the JVM.
- Stderr stays inherited so Gradle/JUnit progress flows to the terminal
in real time.
- Tasks the runner never reports get yielded as failed at the end so Nx
never hangs.
### Project graph dependency
`packages/gradle/project.json` adds `:gradle-batch-runner` to
`implicitDependencies`. The gradle package bundles the batch-runner JAR
and references it at runtime via `batchRunnerPath`; without this, `nx
affected` wouldn't pick up gradle when only the runner changed.
### Bug along the way: className format mismatch
`RegexTestParser.kt` records `testClassName` as the **simple** class
name (e.g. `MyTest`), but Gradle's
`JvmTestOperationDescriptor.className` is the **fully qualified** name
(`com.example.MyTest`). The exact-match lookup in the test listener was
failing for every class, so per-class `TestStartEvent`/`TestFinishEvent`
never matched an Nx task — every Nx task fell through to the
`TaskFinishEvent` fallback at the end of the Gradle test task, all
sharing the same emission time and the cumulative shared output buffer.
`resolveNxTaskId` now looks up by FQN first, then by the suffix after
the last `.`, so events match either format.
### Known trade-off
Tests under the same Gradle test task share the captured per-Gradle-task
output for `terminalOutput`. With JUnit `--parallel` the bytes
interleave anyway, and Gradle's `TestLauncher` doesn't expose per-test
stdout segmentation through `setStandardOutput` — getting truly
per-class `terminalOutput` would require either subscribing to
`OperationType.TEST_OUTPUT` (which diverts stdout away from the standard
output stream and didn't reliably fire for some setups in testing) or
recording fully-qualified class names in the project-graph plugin so we
can match `TestOutputEvent` parents precisely. Filed as a follow-up.
The on-the-wire change matches the existing Maven contract
(`run-batch.ts` already special-cases `isAsyncIterator`), so no Nx core
changes are needed.
## Related Issue(s)
## Current Behavior
Running `nx:test` (jest) from `packages/nx` produces ~4500 cross-project
sandbox violations. Reads span every plugin's `src/generators/**`,
`src/migrations/**`, `src/executors/**`, schemas, docs, spec files and
`.gitignore`s — essentially the entire monorepo.
Root cause is that tests end up computing the **real** project graph:
- `packages/nx/src/utils/workspace-root.ts` freezes `workspaceRoot` on
first import by walking up from `process.cwd()`. With jest's cwd set to
`packages/nx` and no `NX_WORKSPACE_ROOT_PATH` env var, it resolves to
the real repo root.
- `scripts/patched-jest-resolver.js` did set `NX_WORKSPACE_ROOT_PATH`,
but only when `process.argv[1]` contained `jest-worker` or `argv[3]` had
`:test`. Under `jest --passWithNoTests --detectOpenHandles --forceExit`
with `maxWorkers: 1` (what the Nx test runs use), neither branch
matches, so the env var was never set.
- The existing `@nx/devkit.createProjectGraphAsync` mock in
`scripts/unit-test-setup.js` is specifier-keyed, so relative imports
inside `packages/nx` (e.g. `'../../project-graph/project-graph'`) bypass
it and call the real graph builder.
The smoking gun in the process tree: hundreds of `plugin-worker.ts`
subprocesses and `git remote-https` calls to `nrwl/nx-ai-agents-config`
— both only happen when the real, isolated project graph is computed.
## Expected Behavior
Unit tests never touch the real workspace filesystem or spawn plugin
workers. Three layered fixes:
1. **`scripts/patched-jest-resolver.js`** — always set
`NX_WORKSPACE_ROOT_PATH` to a throwaway `tmp/unit` dir. Runs at resolver
module-load, which is before any `require('nx/...')`, so
`workspace-root.ts` is guaranteed to freeze to the sandbox dir.
2. **`scripts/unit-test-setup.js`** — add
`jest.doMock('nx/src/project-graph/project-graph', …)` that returns an
empty graph for `createProjectGraphAsync`,
`createProjectGraphAndSourceMapsAsync`, and
`buildProjectGraphAndSourceMapsWithoutDaemon`. Jest keys mocks by
resolved absolute path, so the relative imports inside `packages/nx` hit
the same mock.
3. **`scripts/unit-test-setup.js`** — guard mock on
`loadIsolatedNxPlugin`. If any test slips past the graph mocks and tries
to spawn a plugin worker, it throws with an actionable error instead of
silently scanning the monorepo.
`packages/nx/src/utils/workspace-root.spec.ts` directly exercises the
walk-up logic in `workspaceRootInner`, which now short-circuits on the
always-set env var. The suite scopes the var away in
`beforeAll`/`afterAll`.
## Related Issue(s)
This is opened as a draft to validate the hypothesis by re-running the
CI sandbox report and confirming the cross-project violations drop.
Local smoke test: `packages/nx` suites across `workspace-root`,
`run-many`, `affected`, `release/config`, `migrate`, `task-hasher`,
`native-task-hasher-impl`, `package-json/create-nodes`,
`project-json/build-nodes`, `explicit-*-dependencies`,
`normalize-project-nodes`, `show/projects`, and both `isolation/*` specs
— 11 suites, 177 tests, all passing.
Adds `nx-cloud upload-agent-metrics` to every provider example in the
manual DTE guide, plus a short intro section on why it matters.
- GitHub Actions / Azure / Circle CI — explicit always-run flag
- Bitbucket / GitLab — lives in `after-script:` / `after_script:`
- Jenkins — inside `post { always { } }`
The always-run mechanic is the point: if `start-agent` OOMs, you still
want metrics uploaded so you can see which task killed the agent.
Companion to nrwl/ocean PR which links to this page from the in-product
setup prompt.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/add-manual-DTE-resource-metrics-docs-and-link-to-frontend-764c85f2)
<!-- polygraph-session-end -->
---------
Co-authored-by: rarmatei <matei.rar@gmail.com>
Some bots are hitting the server, scanning for wordpress paths. This is
resulting in 500 server error, but we should return 404.
This fails in prod (500):
```
curl -sS -o /dev/null -w "%{http_code}\n" --path-as-is 'https://nx.dev//wp/wp-includes/wlwmanifest.xml'
```
Fixed in preview (404):
```
curl -sS -o /dev/null -w "%{http_code}\n" --path-as-is 'https://deploy-preview-35527--nx-dev.netlify.app//wp/wp-includes/wlwmanifest.xml'
```
## Current Behavior
Bot scanners hit `GET //wp/wp-includes/wlwmanifest.xml` (leading `//`).
`new URL(pathname, framerUrl)` parses `//wp/...` as protocol-relative,
promoting `wp` to upstream host. Fetch fails with DNS lookup error and
the function 500s.
## Expected Behavior
Leading `/+` collapsed before resolving against `framerUrl`. Common
WordPress / exploit probes (`wp-includes`, `wp-admin`, `xmlrpc.php`,
`wlwmanifest`, `.env`, `.git/`) short-circuit to 404.
## Related Issue(s)
DOC-498
## Current Behavior
`@nx/js`, `@nx/next`, and `@nx/react-native` declare `ignore: ^5.0.4`.
The companion packages `@nx/nx` and `@nx/dotnet` are already on `^7.0.5`
(queue file flagged not to re-bump those). Per the
bulk-dependency-update sweep (NXC-4329), the remaining three packages
should align.
## Expected Behavior
Bumped to `ignore@^7.0.5` in all three. ignore@6/7 are API-compatible
with v5 — only changes are dropping Node ≤12 support and internal perf
improvements.
## Validation
The 5 source-level call sites all use the default-import default-export
shape:
```ts
// packages/js/src/utils/generate-globs.ts
// packages/js/src/utils/assets/copy-assets-handler.ts
// packages/js/src/generators/typescript-sync/typescript-sync.ts
// packages/react-native/src/generators/init/lib/add-git-ignore-entry.ts
// packages/next/src/utils/add-gitignore-entry.ts
import ignore from 'ignore';
```
Stable across v5/v6/v7. The `ignore()` constructor + `.add()` +
`.filter()` + `.ignores()` chain is unchanged.
`pnpm nx run-many -t build -p js,react-native,next` — passes.
## Related Issue(s)
Part of [NXC-4329](https://linear.app/nxdev/issue/NXC-4329)
bulk-dependency-update sweep.
## Current Behavior
When Nx's task-history life cycle detects more than one flaky task, the
summary header renders with **two consecutive spaces and no number** in
place of the count, e.g.:
```
> NX Nx detected flaky tasks
myproject:test
otherproject:e2e
```
The singular case (one flaky task) renders correctly as `Nx detected a
flaky task`.
## Expected Behavior
```
> NX Nx detected 2 flaky tasks
myproject:test
otherproject:e2e
```
## Root Cause
Both `task-history-life-cycle.ts` and the legacy
`task-history-life-cycle-old.ts` had:
```ts
title: `Nx detected ${
this.flakyTasks.length === 1 ? 'a flaky task' : ' flaky tasks'
}`,
```
The plural branch is a literal `' flaky tasks'` string with a leading
space and **no count interpolation** — so the template renders `Nx
detected ` + `' flaky tasks'` = `Nx detected flaky tasks` (two spaces,
no number).
## Fix
Replace the plural literal with `\`\${this.flakyTasks.length} flaky
tasks\`` so the count appears between the leading space and the word
`flaky`. Singular wording is unchanged.
```ts
title: \`Nx detected \${
this.flakyTasks.length === 1
? 'a flaky task'
: \`\${this.flakyTasks.length} flaky tasks\`
}\`,
```
Same fix applied symmetrically in both life-cycle files.
## Tests
No existing unit test covers `printFlakyTasksMessage()`'s formatted
output (the surrounding life cycles don't have a `*.spec.ts`). Adding
one would require mocking the task-history daemon channel and life-cycle
hooks — out of scope for a one-line formatting fix. The change is small
enough to verify by inspection of the diff.
## Related Issue(s)
(reported internally; no public issue)
## Current Behavior
The `@nx/angular:move` generator is exposed as a deprecated thin wrapper
that delegates to `@nx/workspace:move`. It was deprecated in Nx v18.
## Expected Behavior
The `@nx/angular:move` generator is removed. Consumers must use
`@nx/workspace:move` (or its `mv` alias) directly. Angular-specific
post-move logic (module/class renames, `ng-package.json` `dest`
recomputation, secondary entry-point README updates) is preserved:
`@nx/workspace:move` continues to invoke it via the internal `move-impl`
plugin export, which remains in the package.
`@nx/workspace` is dropped from `@nx/angular`'s production dependencies,
since the wrapper was its sole consumer.
The Angular e2e suite (`e2e/angular/src/misc.test.ts`) now exercises
`@nx/workspace:move` against Angular apps and libraries directly.
## Implementation notes
- Deleted:
`packages/angular/src/generators/move/{move.ts,schema.json,schema.d.ts}`
and the `"move"` entry in `generators.json` / corresponding export in
`generators.ts`.
- Kept: `packages/angular/src/generators/move/move-impl.ts` and `lib/`,
plus the `./src/generators/move/move-impl` package export — these are
still loaded at runtime by
`@nx/workspace/src/generators/move/lib/run-angular-plugin.ts`.
- The unit spec was renamed `move.spec.ts` → `move-impl.spec.ts` and
refactored to call `move-impl` directly via a small helper that mimics
the file/config moves `@nx/workspace:move` performs before invoking the
plugin. Two assertions that exercised `@nx/workspace:move`'s import-path
rewriting (not move-impl's job) were narrowed to the class-rename
behavior the plugin actually performs.
BREAKING CHANGE: The `@nx/angular:move` generator is removed. Use
`@nx/workspace:move` (or its `mv` alias) instead.
## Current Behavior
- During project graph normalization, dependsOn entries whose target
doesn't exist are left in place, which means downstream consumers have
to re-validate against the real target set every time.
- `nx show target` resolves the `Depends On` list using a heuristic on
raw `dependsOn` configs. That heuristic doesn't fully match what
`createTaskGraph` would schedule — e.g. a `{ projects: ['lib-a'] }`
entry is listed even if `lib-a` doesn't actually have the referenced
target, and there's no indication of what transitively runs when the
target is scheduled.
## Expected Behavior
### 1. Normalize-project-nodes drops broken same-project dependsOn
entries
A new `normalizeTargetsDependsOn` pass runs inside
`normalizeProjectNodes` after target defaults have been merged. It
filters:
- Bare-string entries (e.g. `"prebuild"`) when the referenced target
doesn't exist on the same project.
- Object entries with no `projects`, `projects: 'self'`, or `projects:
'{self}'` when the referenced target doesn't exist on the same project.
Cross-project entries are intentionally left alone — `"^target"`, `{
target: 'X', dependencies: true }`, and `{ projects: [...] }` need the
real project graph (and in some cases the dep edges) to validate, so
they're resolved later during task graph creation.
### 2. `nx show target` is task-graph-driven
`showTargetInfoHandler` now builds a task graph rooted at the requested
target via `createTaskGraph`. The `Depends On` list reads direct task
dependencies off the graph, giving the same filtering and resolution
behavior as `nx run`:
- Non-existent targets disappear from the list.
- `^target` expands to real dependency-project task IDs.
- `{ projects: [...] }` entries drop projects that don't actually have
the target.
A new `transientTasks` field on the JSON output surfaces everything that
runs transitively. The text renderer shows a summary line beneath
`Depends On`:
```
Depends On:
lib-a:build
and 5 build, compile transient tasks
```
With >3 unique target names, the summary collapses to a bare count (`and
12 transient tasks`) to stay scannable. Source-map hints for `--verbose`
are preserved — each direct dep task ID is matched back to its
originating `depConfig` entry.
## Related Issue(s)
None — proactive correctness + observability improvement.
## Current Behavior
`logger.[x]` doesn't get decorated despite being emitted to the daemon
log, perf logs aren't decorated, etc
## Expected Behavior
daemon logs are decorated
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
## Current Behavior
#34425 ("remove redundant `allWorkspaceFiles` from the project graph
pipeline") dropped `allWorkspaceFiles` from the `WorkspaceFileMap`
interface without a deprecation cycle.
Plugins that import `createFileMapUsingProjectGraph` from the deep path
`nx/src/project-graph/file-map-utils` — e.g. `@nx/owners` — fail to
type-check against the beta release:
```
src/plugin/plugin.ts(73,20): error TS2339: Property 'allWorkspaceFiles' does not exist on type 'WorkspaceFileMap'.
src/plugin/plugin.spec.ts(799,5): error TS2353: Object literal may only specify known properties, and 'allWorkspaceFiles' does not exist in type 'WorkspaceFileMap'.
```
The path is technically internal (not exported from `@nx/devkit`), but
real consumers (including cached nx-cloud workers — see #35502) reach in
there, and #35502 already established the pattern of restoring removed
members as `@deprecated` shims rather than breaking them outright.
## Expected Behavior
`allWorkspaceFiles` is restored on `WorkspaceFileMap` as `@deprecated`
and exposed from `createFileMap` via a **non-enumerable lazy getter**:
- **Lazy** — derived from `fileMap.projectFileMap` +
`fileMap.nonProjectFiles` only when read. Zero allocation for callers
that don't touch it.
- **Non-enumerable** — stays out of `JSON.stringify` / `Object.keys`, so
the daemon-serialization wins from #34425 are preserved if a
`WorkspaceFileMap` ever ends up on the wire.
- **Optional** — daemon-internal call sites (`updateFileMap`) that never
set the field still satisfy the type.
The hot-path improvements from #34425 stay intact:
- No `allWorkspaceFiles` on `SerializedProjectGraph` going across the
daemon wire
- No `copyFileData()` on every graph serialization
- No `buildAllWorkspaceFiles()` on incremental updates
- No `storedAllWorkspaceFiles` retained in `build-project-graph` state
- `updateFileMap` return value unchanged
The shim only sits at the plugin boundary —
`createFileMapUsingProjectGraph`, which runs in the plugin's process,
not the daemon.
Same back-compat pattern as #35502 (`hydrateFileMap` 3-arg overload).
Both can be removed in a future major once known consumers (ocean,
cached cloud workers) age out.
## Related Issue(s)
<!-- Reported via internal channels (ocean) — no public issue. -->
## Current Behavior
The Core Team section of the top-level `README.md` lists 23 people
across 6 markdown tables (5 rows of 4 + 1 final row of 3). Three of
those names are former team members who have offboarded:
- Philip Fulcher (`philipjfulcher`)
- Colum Ferry (`Coly010`)
- Austin Fahsl (`fahslaj`)
## Expected Behavior
Remove the three offboarded entries and reflow the surviving 20 names
into 5 even rows of 4. Original ordering preserved; gaps closed
left-to-right, top-to-bottom.
## Notes
- README only — no other files modified.
- CODEOWNERS uses team handles (`@nrwl/nx-cli-reviewers`), not
individual usernames, so no change needed there.
- 20 surviving members fit cleanly into 5 rows of 4 — no awkward partial
row.
- Source padding regenerated per-row so the raw markdown stays visually
aligned.
## Related Issue(s)
(internal cleanup; no public issue)
## Current Behavior
The CLI docs generator that produces `/docs/reference/nx-commands` only
flattens the top-level command and its direct children. Any subcommand
nested deeper than that is silently dropped from the rendered page, even
though the underlying yargs parser already produces the full nested
tree.
In practice this means commands like `nx show target inputs` and `nx
show target outputs` have no public docs entry — users have no way to
discover their flags (`--check`, `--target`, `--configuration`, etc.)
without running `--help` locally.
## Expected Behavior
The generator walks the full subcommand tree and renders every command.
Three-deep commands like `nx show target inputs` appear as `###`
headings alongside `nx show target`, with their own usage block and
options table.
Verified locally — running the generator before/after this change adds
exactly two new sections (`nx show target inputs`, `nx show target
outputs`) and changes nothing else.
A new e2e test in `astro-docs/e2e/cli-subcommand-formatting.spec.ts`
locks the third-level rendering in so this can't regress silently again.
## Related Issue(s)
Fixes #
## Current Behavior
Running `nx run-many -t clean` (or any target that wipes `target/`
before `record` runs) in batch mode against a Maven 4 project fails
with:
```
Caused by: java.nio.file.NoSuchFileException: .../target/consumer-<hash>.pom
at java.nio.file.Files.setLastModifiedTime(...)
at org.apache.maven.internal.transformation.impl.TransformedArtifact.mayUpdate(...)
at org.apache.maven.internal.transformation.impl.TransformedArtifact.getFile(...)
at dev.nx.maven.shared.BuildStateRecorder.captureAttachedArtifacts(BuildStateRecorder.kt:173)
```
In Maven 4, the consumer POM is exposed as a `TransformedArtifact`.
Reading `Artifact.file` invokes `getFile()`, which lazily materializes
the file by touching its `lastModifiedTime`. After `clean` deletes
`target/`, that touch throws and propagates out of
`BuildStateRecorder.captureAttachedArtifacts`, failing the `record` mojo
and the entire build. The existing `consumer-<hash>.pom` filter is dead
code on this path because the throw happens at the property access,
before the filter runs.
## Expected Behavior
`record` is a best-effort metadata recorder. When an attached artifact's
file cannot be resolved — null, missing on disk, or a
`TransformedArtifact` whose materialization throws — we skip that
artifact and continue, the same way the existing
null/exists/consumer-POM guards do. The build succeeds.
The fix resolves `artifact.file` once with `try/catch`, logs the failure
at `debug`, and returns `null` from the mapper. Subsequent
`null`/`exists`/consumer-POM checks run against the local. Behavior is
unchanged when `getFile()` succeeds.
Verified locally with `nx run
e2e-maven:e2e-ci--src/maven-batch-v4.test.ts` — the previously-failing
`should clean multiple projects with run-many in batch mode` test now
passes (no more `NoSuchFileException`).
## Related Issue(s)
None.
## Current Behavior
On macOS, `transform_event_to_watch_events` infers Create vs Update from
inode timestamps because FSEvents reports a Create flag for in-place
updates of recently-active paths. The check compared `st_mtime` and
`st_birthtime` at **whole-second** precision:
```rust
if t.st_mtime() == t.st_birthtime() {
EventType::create
} else {
EventType::update
}
```
When an in-place update lands in the same wall-clock second as the
file's creation, both timestamps round to the same value and the event
is mis-classified as `Create`. This:
- Flakes `native::watch::watcher::tests::plain_update_yields_update`
~70% of the time (writes v1, sleeps 150ms, writes v2 — both timestamps
in the same second on most runs).
- Mislabels real `nx watch` events when a developer or tool edits a file
shortly after it's created/checked-out.
Strict ns-precision equality isn't viable either: `fs::write` is
`open(O_CREAT)` (stamps birthtime) followed by a separate `write`
syscall (stamps mtime) ~100µs later, so every fresh write would
mis-classify as Update.
## Expected Behavior
Same-second in-place updates classify as `Update`; fresh writes still
classify as `Create`.
The fix compares `Metadata::modified()` and `Metadata::created()`
(`SystemTime`, ns precision) with a 50ms tolerance:
- Below the threshold → `Create`. Covers the kernel's ~100µs
`O_CREAT`→`write` gap with comfortable headroom for syscall jitter.
- Above the threshold → `Update`. Real edits separated from creation by
anything more than tens of ms classify correctly.
The watcher's accumulator merges events within `IDLE_WINDOW` (100ms) and
the `(Create, Update) → Create` rule keeps the first state, so per-event
labels inside the tolerance window don't reach consumers — only events
in separate bursts do, and those are reliably outside the window.
### Verification
- 10/10 stress runs of `npx nx test-native nx --skip-nx-cache
--no-cloud` no longer fail `plain_update_yields_update` (was 7/10 fail
rate before).
- All other 351 native tests pass on every run.
- Empirical kernel-timing probe on macOS confirmed: fresh `fs::write`
produces `mtime - birthtime ≈ 100µs`; in-place update 150ms after
creation produces `mtime - birthtime ≈ 152ms` — comfortably on either
side of the 50ms cutoff.
## Current Behavior
`@nx/eslint-plugin` declares `globals: ^15.9.0`. Per the
bulk-dependency-update sweep (NXC-4329), this dependency is due for a
major-version bump.
## Expected Behavior
Bumped to `globals@^17.0.0`. The published v17 package is still CommonJS
(`module.exports = require('./globals.json')`); no ESM migration
required. Engine floor moves to `node >= 18` (which Nx's own floor
already exceeds).
## Validation
The three call sites in `@nx/eslint-plugin` use only stable environment
keys that are unchanged across `15 → 16 → 17`:
```ts
// packages/eslint-plugin/src/flat-configs/javascript.ts
import globals from 'globals';
{ ...globals.browser, ...globals.node }
// packages/eslint-plugin/src/flat-configs/react-base.ts
{ ...globals.browser, ...globals.commonjs, ...globals.es2015, ...globals.jest, ...globals.node }
// packages/eslint-plugin/src/flat-configs/angular.ts
{ ...globals.browser, ...globals.es2015, ...globals.node }
```
`pnpm nx run eslint-plugin:build` — passes.
`pnpm nx run eslint-plugin:test` — passes.
Lockfile delta: 2 lines (specifier + resolved version), no peer-hash
churn.
## Related Issue(s)
Part of [NXC-4329](https://linear.app/nxdev/issue/NXC-4329)
bulk-dependency-update sweep.
## Current Behavior
The `@nx/angular/module-federation` entry point is exposed as a
deprecated re-export of `withModuleFederation` and
`withModuleFederationForSSR` from `@nx/module-federation/angular`. It
was deprecated in Nx v20.2.
## Expected Behavior
The `@nx/angular/module-federation` entry point is removed. Consumers
must import from `@nx/module-federation/angular` directly. A migration
(`update-23-0-0-update-with-module-federation-import`) rewrites existing
imports/requires in webpack configs of projects depending on
`@nx/angular` and adds `@nx/module-federation` to `package.json`.
The `@nx/angular:convert-to-with-mf` generator now installs
`@nx/module-federation` since the webpack config it emits imports from
that package.
The webpack guides (`webpack-config-setup`, `webpack-plugins`) were
updated to reflect the recommended import paths and to fix broken
Angular MF snippets that wrapped `withModuleFederation` with
`composePlugins` (not exported from `@nx/module-federation/angular`).
BREAKING CHANGE: The `@nx/angular/module-federation` entry point is
removed. Update imports to use `@nx/module-federation/angular` instead.
The `update-23-0-0-update-with-module-federation-import` migration
handles this automatically when running `nx migrate`.
## Current Behavior
The `@nx/angular` package contains deprecated functions and stale
documentation that are no longer needed.
## Expected Behavior
- Remove dead `extendAngularEslintJson` and `createEsLintConfiguration`
functions (were not exported or used internally)
- Clean up stale deprecation notice in Cypress component testing docs
(referenced Nx 18 removal which has already passed)
## Current Behavior
Four `typecheck` tasks fail on master:
- `eslint-rules:typecheck` — TS1541 in
`tools/eslint-rules/rules/valid-schema-description.ts`. The type-only
import of `jsonc-eslint-parser` (an ESM-only module) needs a
`resolution-mode` attribute under `module: node16`.
- `e2e-nx:typecheck` — TS6307: `e2e/nx/src/import-utils.ts` is imported
by tests but isn't matched by `tsconfig.spec.json`'s `include` list (the
file is a helper, not a `*.test.ts`).
- `e2e-angular:typecheck` and `e2e-storybook:typecheck` — TS2307: deep
imports into `nx/src/internal-testing-utils/*` (used by
`packages/devkit/internal-testing-utils.ts`,
`packages/workspace/migrations.spec.ts`, and several
`packages/*/src/**/*.spec.ts`) can't be resolved. The catch-all
`typesVersions` entry `src/*` redirects these to
`dist/src/internal-testing-utils/*.d.ts`, which doesn't exist — the
files are excluded from `tsconfig.lib.json` so they're never built into
`dist/`.
## Expected Behavior
All `typecheck` tasks pass. Specifically:
- The ESLint rule's type-only import declares `resolution-mode:
'import'`, satisfying TS1541.
- `e2e/nx/tsconfig.spec.json` includes `src/**/*.ts`, picking up helper
files alongside tests. The redundant `*.test.ts` / `*.spec.ts` glob
variants (no `.tsx`/`.jsx`/`.js` tests in this project, no `.ts` files
outside `src/`) collapse into `["src/**/*.ts", "**/*.d.ts",
"jest.config.ts"]`.
- `packages/nx/package.json` adds a more-specific entry for
`src/internal-testing-utils/*` to both `typesVersions` (so Node10 module
resolution finds the source `.ts` files instead of nonexistent built
declarations) and `exports` (so the `types-versions-exports-sync`
conformance rule stays satisfied). The exports object only needs `types`
and `default` — both pointing to the same `.ts` source — since these
utilities are workspace-internal: `default` is reached at jest runtime
via the resolver's fallback condition list, and `types` covers modern TS
resolution. The published `files` list still excludes the source `.ts`
files, so external consumers are unaffected.
## Current Behavior
When generating an `@nx/js` library with `--unitTestRunner=vitest` and
`--linter=eslint` (and a non-vite bundler such as
`tsc`/`swc`/`rollup`/`esbuild`/`none`), the generated eslint config adds
`{projectRoot}/vite.config.{js,ts,mjs,mts}` to the
`@nx/dependency-checks` `ignoredFiles` list — but the file actually
generated is `vitest.config.mts`, so the ignore pattern never matches.
## Expected Behavior
The eslint config references
`{projectRoot}/vitest.config.{js,ts,mjs,mts}` whenever
`@nx/vitest:configuration` produces a dedicated vitest config (i.e.
bundler is not `vite`). When bundler is `vite`, the existing
`vite.config.{...}` ignore is still correct since the same file holds
both build and test config.
Since #33670 (Nx 22.2), `@nx/vitest:configuration` calls
`shouldUseVitestConfig()` and emits `vitest.config.mts` for
non-framework JS libraries with no existing `vite.config`. The eslint
ignore pattern in `packages/js/src/generators/library/library.ts` was
never updated to match.
## Related Issue(s)
Fixes#35450
## Current Behavior
`nx@23.0.0-beta.2` Nx Cloud V4 distributed-agent workers crash on every
task with:
```
Failed to get external value
at new NativeTaskHasherImpl (.../native-task-hasher-impl.js:25:23)
at new InProcessTaskHasher (.../task-hasher.js:68:27)
at createTaskHasher (.../create-task-hasher.js:13:16)
at createOrchestrator (.../init-tasks-runner.js:86:60)
at runDiscreteTasks (.../init-tasks-runner.js:114:32)
at executeAndStoreTask (.../discrete-task-worker.js:1:832861)
```
#34425 ("remove redundant `allWorkspaceFiles` from the project graph
pipeline") changed two helpers in
`packages/nx/src/project-graph/build-project-graph.ts`:
- `hydrateFileMap(fileMap, allWorkspaceFiles, rustReferences)` →
`hydrateFileMap(fileMap, rustReferences)`
- `getFileMap()` no longer returns `allWorkspaceFiles`
Cached Nx Cloud V4 workers (e.g.
`.nx/cache/cloud/2604.29.7/lib/core/runners/distributed-agent/v4/discrete-task-worker.js`)
`require('nx/src/project-graph/build-project-graph')` directly and still
call the 3-arg form:
```js
hydrateFileMap(
{ projectFileMap, nonProjectFiles },
allWorkspaceFiles, // lands in rustReferences slot on beta.2
rustReferences // silently dropped
);
```
The `FileData[]` array poisons `storedRustReferences`. Later
`createTaskHasher` reads `.projectFiles` / `.allWorkspaceFiles` off the
array (both `undefined`), passes them to `new TaskHasher(...)`, and
napi-rs throws `"Failed to get external value"` trying to coerce
`undefined` into `&External<Arc<…>>`.
## Expected Behavior
`hydrateFileMap` accepts both the new 2-arg shape and the legacy 3-arg
shape, detected by `Array.isArray()` on the 2nd argument. `getFileMap()`
re-exposes `allWorkspaceFiles: []` so cached workers that destructure it
(for telemetry / `v4log`) see the property instead of `undefined`. Both
surfaces are flagged `@deprecated` so we can remove them in a later
major once cached V4 workers age out.
A regression test pins both arities — verified red on the pre-fix code
and green with the fix.
## Related Issue(s)
<!-- Reported via internal channels (ocean) — no public issue. -->
## Current Behavior
`astro-docs:build` produces a large number of sandbox violations. The
build reads files from packages it doesn't declare as inputs (plugin
schemas, dependent task outputs, source TypeScript via `ts-node`), and
the TypeDoc setup mutates devkit's `dist/` output by writing a modified
`tsconfig.lib.json` into it. The combination produces stale caches and
unexpected reads/writes that make sandboxing reports noisy.
## Expected Behavior
The build only reads files it has declared, and never writes outside its
own outputs.
### Changes
- **Inputs** (`astro-docs/project.json`): declare plugin schemas
(`packages/*/{generators,executors,migrations}.json`,
`packages/*/src/{generators,executors}/**/schema.json`), migration and
docs markdown, plugin `package.json`, and a transitive
`dependentTasksOutputFiles` glob covering
`**/*.{d.ts,json,md,js,cjs,mjs}` so devkit / cnw / nx / dotnet / maven
dist outputs flow correctly through the task dependency chain. Also
declare the workspace `tsconfig.json` as a selective JSON input scoped
to `compilerOptions` so esbuild's ancestor walk-up registers as declared
without making the cache sensitive to references-only changes.
- **TypeDoc** (`astro-docs/src/plugins/utils/typedoc/typedoc.ts`,
`devkit-generation.ts`): write the mutated devkit `tsconfig.lib.json` to
`os.tmpdir()/nx-devkit-docs/packages/devkit/` instead of
`dist/packages/devkit/`. Rewrite the `include` patterns to absolute
paths anchored at `dist/packages/devkit/` so TypeDoc still finds the
declaration files from the relocated tsconfig.
- **CNW subprocess**
(`astro-docs/src/plugins/utils/cnw-subprocess.cjs`): drop the
`ts-node.register(...)` shim and load `create-nx-workspace` from
`dist/packages/create-nx-workspace/...` instead of the TypeScript
source. Aligns the docs generator with what end users actually consume
from the published package.
- **TS project** (`astro-docs/tsconfig.json`): extend `exclude` with
`e2e/` and the `eslint.config.{js,mjs,cjs}` files so the main TypeScript
program no longer pulls in the e2e specs and lint configuration.
- **Tailwind** (`astro-docs/src/styles/global.css`): add `@source not`
directives for `**/*.{spec,test}.*`, `**/eslint.config.*`,
`**/tsconfig*.json`, and `e2e/**`. Tailwind v4's oxide scanner walks
`@source` paths and the project root reading every file with a
recognized extension; without these exclusions it parses test, config,
and tsconfig files looking for class usages they cannot contain. The
exclusions stop reads of these files in the astro-docs project root and
in the symlinked nx-dev/* packages declared via `@source`.
- **Rollup docs path** (`packages/rollup/docs/rollup-examples.md`,
`packages/rollup/src/executors/rollup/schema.json`): move
`rollup-examples.md` from `packages/rollup/src/docs/` to
`packages/rollup/docs/` and update the schema's `examplesFile`
reference. Every other plugin in the workspace already keeps example
markdown under `packages/<pkg>/docs/`; rollup was the lone outlier
(introduced by accident in #14963), and the non-standard path required a
special input glob in `astro-docs` just to cover one file.
## Current Behavior
Running `nx test gradle` in a sandboxed CI environment produces sandbox
violations: jest is observed reading files that belong to a sibling Nx
project (`:gradle-batch-runner`), e.g.:
- `packages/gradle/batch-runner/build/reports/tests/test/index.html`
- `packages/gradle/batch-runner/build/reports/tests/test/js/report.js`
Root cause: jest's `rootDir` defaults to `packages/gradle/` (the dir of
`jest.config.cts`). With `moduleFileExtensions` including `.html` and
`.js`, `jest-haste-map` walks the entire tree under `rootDir` and reads
matching files to build its module map. `packages/gradle/batch-runner/`
is a separate Nx project whose root happens to be a subdirectory, so its
gradle build output gets pulled into haste-map.
These files are not — and should not be — declared as inputs to
`gradle:test`; they belong to a different project.
## Expected Behavior
`jest-haste-map` skips the `batch-runner/` subtree, so `gradle:test` no
longer reads files owned by the `:gradle-batch-runner` project,
eliminating the sandbox violations.
Fix: add `modulePathIgnorePatterns: ['<rootDir>/batch-runner/']` to
`packages/gradle/jest.config.cts`. `modulePathIgnorePatterns` (vs
`testPathIgnorePatterns`) is the correct knob — it excludes the path
from the haste map entirely so it's never read; `testPathIgnorePatterns`
only filters which files run as tests.
## Related Issue(s)
N/A — surfaced by Nx Cloud sandbox report on `gradle:test`.
## Current Behavior
Many Nx plugin packages lazy-load other plugins at runtime via
`ensurePackage()` or the `require('@nx' + '/...')` pattern. These
dependencies weren't declared in `package.json` at all, which meant:
- `pnpm install` in the monorepo happened to find them only via hoisting
from unrelated `devDependencies`.
- Published packages gave no install-time signal about what optional
peers a consumer might want.
- The dependencies were invisible to dependency-graph tooling,
supply-chain audits, and any future semantic-versioning logic.
## Expected Behavior
Every lazy-loaded plugin or tool is declared as `peerDependencies` +
`peerDependenciesMeta: { X: { optional: true } }`, matching the existing
convention in `@nx/angular`, `@nx/angular-rspack`, `@nx/eslint`, etc.
This gives consumers correct install/publish semantics without requiring
them to install peers they don't use.
For two packages — `@nx/workspace` and `@nx/js` — several of their
newly-declared peers transitively reverse-depend on them. Raw
package.json edges would cause `@nx/js:typescript-sync` to produce
circular TypeScript project references (TS6202). Those two packages use
`implicitDependencies: ["!name", …]` in `project.json` to drop the
cyclic graph edges, keeping the task graph and tsc builds cycle-free
without modifying the sync generator itself.
Commits:
1. **workspace + js**: 14 optional peers on `@nx/workspace`, 5 on
`@nx/js`, plus `implicitDependencies` negations in each `project.json`.
2. **Plugin packages**: `@nx/angular`, `@nx/expo`, `@nx/next`,
`@nx/nuxt`, `@nx/react-native`, `@nx/storybook`, `@nx/vite`, `@nx/vue`,
`@nx/web`. Cycles don't form for any of these, so no
`implicitDependencies` negations were needed. `@nx/js:typescript-sync`
populated the corresponding `tsconfig.lib.json` project references,
which are committed alongside the `package.json` changes.
## Related Issue(s)
Fixes #
## Test plan
see:
https://staging.nx.app/runs/m3Otv2Xl7m?sandboxViolations=true&query=%3Atest
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Generated Angular projects using `vitest-angular` create a test target
without an explicit `watch` value. The Angular unit-test builder
defaults watch mode to `true` in TTY environments, so running generated
projects through monorepo workflows such as `nx run-many` can leave test
tasks running instead of exiting.
## Expected Behavior
Generated Angular `vitest-angular` test targets explicitly set `watch:
false`, matching Nx Vitest's default non-watch behavior and preserving
terminating test tasks for `run-many` and affected workflows. Users can
still opt into watch mode with `--watch` or a watch configuration.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
When we calculate the excludes for the gradle executors, we currently
traverse the surface level dependencies and also via the project graph.
We need to cover all transitive dependencies instead.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Use the task graph to derive excludes instead. This also is easier to
traverse multiple levels of dependencies than using the project graph.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
This branch started as a fix for a flaky `e2e/nx/src/watch.test.ts`.
Once we flipped `NX_DAEMON='true'` in the e2e harness, several
long-latent daemon issues surfaced; they're fixed in the same PR. The
most user-visible regression — `nx release version` silently dropping
projects from a fixed release group after `git checkout` — is the last
item below and is what
`e2e/release/src/multiple-release-branches.test.ts` exercises.
**Native watcher.** `nx watch` dropped and duplicated events on
Linux/Windows:
1. `watchexec.config.pathset()` registered new directories
asynchronously — files written before the real inotify registration were
lost.
2. Every raw `notify` event fired a callback (no debounce) so one
logical write produced multiple `nx watch` invocations.
3. The daemon called `notifyFileWatcherSockets` twice per event (eagerly
for updates/deletes, again post-recompute for creates), doubling
invocations.
4. `getProjectsAndGlobalChanges` looked changed files up in
`fileMap.projectFileMap`, so brand-new files fell through to
`globalFiles` and `--projects=foo` watchers never matched.
5. `nx:test-native` didn't compile on master (walker test used
`nix::unistd::mkfifo` behind a feature that wasn't enabled).
6. macOS regressed after the notify migration: `FsEventWatcher` dropped
immediately because the closure had no cfg-active references on darwin.
**Daemon (surfaced once it ran end-to-end in CI):**
7. `getPluginsSeparated` reused `pendingPluginsPromise` via `??=` after
the plugins-config hash changed, serving stale plugins forever after `nx
add`.
8. The auto-recompute path updated the in-memory graph but never
persisted it; subprocesses reading the disk cache saw stale data (flaky
`eslint dependency-checks`).
9. `startInBackground` had an fd race: a concurrent `reset()` could null
`this._out`/`this._err` while we awaited `open()`, crashing the spawn
with `Cannot read properties of null (reading 'fd')`.
10. The daemon's env-reflection was additive only — `NX_*` vars set by
one CLI invocation persisted forever, leaking (e.g.,
`NX_PREFER_NODE_STRIP_TYPES=true` from a single `nx report` poisoned
every later plugin load). Plugin worker's `setWorkerEnv` had the same
bug.
11. Several e2e cache-hit assertions matched `[local cache]`, but the
daemon-on path emits `[existing outputs match the cache, left as is]`
(output kept rather than restored).
**Daemon graph cache (more flake hunting after the watcher rewrite
landed):**
12. `resetInternalStateIfNxDepsMissing` ran before every
`getCachedSerializedProjectGraphPromise` and reset state whenever
`project-graph.json` was missing AND a cached promise existed — but on
cold start, *every* request before the first persist matches that
condition. It was firing constantly, tearing down in-flight first
computes and forcing a redundant recompute. Worse, its `catch` branch
unconditionally reset on any `fileExists` error.
13. When `resetInternalState` fires from inside
`processCollectedUpdatedAndDeletedFiles`'s catch (e.g.,
`retrieveWorkspaceFiles` throws on partial disk state),
`cachedSerializedProjectGraphPromise` is set to `undefined`. A
concurrent stale compute that hits `chainToLatest` after the reset would
read back `undefined`; the `if (stale) return stale` guard treats
`undefined` as falsy, so the stale compute falls through and commits its
(now-stale) data to module state.
**Force-flush concurrency (flagged by Graphite review):**
14. The processing thread dropped any extra `ForceFlush` messages it
found while draining the channel (the `ProcMsg::ForceFlush(_) => { /*
drop the extras */ }` arm). Their reply senders were silently discarded;
those callers waited the full 500 ms `recv_timeout` and returned an
empty Vec. Concurrent CLI requests would each see that latency hit.
**Watcher event misclassification (the `nx release version` failure on
`multiple-release-branches.test.ts`):**
15. `git checkout` does `unlink + write` on some tracked files. inotify
fires `IN_DELETE` then `IN_CREATE` for the same path; both landed in the
watcher's accumulator within one burst.
16. `merge_event`'s priority rule was *Delete > Create > Update* —
meaning a `Create` arriving after a `Delete` for the same path was
silently dropped. The accumulator emitted only the Delete.
17. Downstream `updateFilesInContext` on the JS side then *removed* the
(still-existing) file from the Rust workspace context's index.
`multiGlobWithWorkspaceContext` no longer returned it. Plugins didn't
process it. The project was missing from the resulting graph, with no
error.
18. `release version` then evaluated `isProjectPublic`, which checks for
the project's `package.json` in the file map. With the file gone from
the index, the check returned `false`, the project was excluded as
not-public, and the release group ran with one fewer project than
expected.
19. Two related classification bugs surfaced during the diagnosis: (a)
the early-return for `!meta_exists` emitted Delete *unconditionally*,
even for `Modify`/`Create` events whose stat happened to fail during a
transient atomic-rename window; (b) notify-rs delivers a third coalesced
rename event (`Modify(Name(Both))`) that fell through to the generic
`Modify(_) → Update` arm, overriding the per-side `From` Delete on the
source path of an `fs::rename`.
**Maven (separate but bundled):**
20. Tests asserted on `BUILD SUCCESS` in batch mode, but the resident
batch runner's `BatchExecutionListener.sessionEnded` is a no-op —
Maven's footer is replaced by `Nx Maven Summary`. Those assertions could
never match.
## Solution
### Native watcher
#### Architecture
```
Before: Watcher → watchexec (async config loop) → notify → inotify/FSEvents
After: Watcher → notify → inotify/FSEvents (direct, synchronous)
```
The watcher is one Rust struct
(`packages/nx/src/native/watch/watcher.rs`) that owns:
- a `notify::RecommendedWatcher` (the OS-level subscription),
- a single `mpsc::Sender<ProcMsg>` shared by the notify event handler
and the napi force-flush method,
- a long-running **processing thread** that reads from the corresponding
`Receiver`.
#### How events are streamed
```
notify (OS) ─► NotifyForwarder ─┐
├─► mpsc<ProcMsg> ─► processing thread ─► napi tsfn ─► JS callback
JS force_flush_pending() ───────┘ (or sync_channel reply)
```
1. **Notify side.** `notify::recommended_watcher(NotifyForwarder { tx
})` hands every fs event to `NotifyForwarder::handle_event`, which wraps
it as `ProcMsg::Notify(event)` and pushes it onto the channel. No work
is done on the notify thread beyond the send.
2. **Processing thread.** A dedicated thread runs an infinite loop with
three branches:
- `ProcMsg::Notify(event)` — filter through gitignore/nxignore
(`watch_filterer.rs`), run the new-directory fast path on Linux/Windows
if the event is a `Create(Folder)`, transform notify's kinds into our
`EventType` (Create/Update/Delete), and merge each resulting
`WatchEventInternal` into a per-path `HashMap` accumulator. The merge
rules are:
- `(_, Delete)` → Delete (file is gone, final state wins)
- `(_, Create)` → Create (file is in its initial state from our
perspective; overrides earlier Update or Delete — the regression case
15-18 above)
- `(Delete, Update)` → Update (file came back with new content)
- `(Create, Update)` / `(Update, Update)` → keep existing (Create wins
on Linux's IN_CREATE+IN_MODIFY pair)
- `ProcMsg::ForceFlush(reply)` — drain whatever notify has buffered
(`while let Ok(msg) = rx.try_recv()` so anything in flight at the moment
of the request is included), **collect every queued ForceFlush reply
channel encountered during the drain**, snapshot the accumulator, and
send the same snapshot to all collected callers (closes#14 above). Used
by the daemon to absorb pending events before serving a cached project
graph (closes the IDLE_WINDOW race where a 99 ms-old event would
otherwise miss the next read).
- `Err(RecvTimeoutError::Timeout)` — the wake-up deadline elapsed; emit
the accumulator to JS via the napi `ThreadsafeFunction`
(`callback_tsfn.call(Ok(events), NonBlocking)`), clear it, and reset.
3. **Trailing-edge debounce.** Each `Notify` ingest updates a single
`flush_deadline = min(now + IDLE_WINDOW, burst_start + MAX_WAIT)`:
- `IDLE_WINDOW = 100 ms` — flush when the channel goes quiet for this
long. Resets on every arriving event, so any burst with gaps <100 ms
coalesces into one flush.
- `MAX_WAIT = 500 ms` — starvation cap from the first event of the
burst.
- The loop's `rx.recv_timeout(wait)` either picks up the next message or
returns `Timeout` exactly at `flush_deadline`. No separate timers, no
cross-flush state, no `recent_paths` book-keeping.
- When idle (`flush_deadline = None`) the loop polls on `SHUTDOWN_POLL`
so `stop_flag` is observed promptly.
4. **napi tsfn handoff.** The processing thread invokes
`callback_tsfn.call(Ok(Vec<WatchEvent>), NonBlocking)` to schedule the
JS callback on Node's main thread. The notify thread itself never
crosses into JS — only the processing thread does, and only at flush
time. `Watcher::watch` is now a thin wrapper around an internal
`watch_inner` that takes a generic callback, so unit tests can exercise
the same loop without a JS runtime.
5. **`force_flush_pending` (synchronous drain).** The daemon calls
`watcher.force_flush_pending()` from JS before reading the cached
project graph. JS-side napi method creates a
`sync_channel::<Vec<WatchEvent>>(1)` reply pair and sends
`ProcMsg::ForceFlush(reply_tx)` on the same channel notify events flow
through. The processing thread sees the request in order with any
buffered notify events, drains, takes the accumulator, and replies.
Concurrent callers all receive the same snapshot (closes#14).
#### Event classification (`types.rs::transform_event_to_watch_events`)
- A failed stat (`!meta_exists(metadata)`) only short-circuits to
`EventType::Delete` when the notify event_kind is actually `Remove(_)`.
For `Modify`/`Create` events whose stat happened to fail during a
transient atomic-rename window, we fall through to the platform-specific
path which derives the type from `event_kind` alone (closes #19a).
- `Modify(Name(RenameMode::Both | Any))` — the coalesced rename event
notify-rs delivers in addition to per-side `From`/`To` events — is now
skipped, so it can't override the `From` Delete on the source path of a
rename (closes #19b).
#### New-directory fast path (Linux/Windows)
When notify reports a `Create(Folder)` on inotify/ReadDirectoryChangesW
(which only watch the dirs they were given), the processing thread:
1. Calls `watcher.watch(new_dir, NonRecursive)` **synchronously** — the
OS watch is active the moment this returns.
2. Re-walks the new directory and merges every existing entry into the
accumulator with `EventType::Create`.
3. Recursively registers any subdirectories it found (so a `mkdir -p
a/b/c/d` still hooks every level).
Because (1) is synchronous, files written between `mkdir` and the
`watch()` call are caught by the re-walk in (2). Notify events for those
same files arriving later just merge into the same accumulator entry —
no duplication. macOS doesn't need this path: `FsEventWatcher` is
recursive.
`notify_watcher` is held as `Option<Arc<Mutex<RecommendedWatcher>>>` on
the struct so the processing-thread closure can clone the `Arc`;
otherwise the macOS `move`-closure (which has no cfg-active references)
wouldn't capture the watcher and `FsEventWatcher` would drop the moment
`watch()` returned.
### Daemon
- **Plugin reload on config change.** `getPluginsSeparated` clears
`pendingPluginsPromise` and tears down workers when `nx.json#plugins`'s
hash changes.
- **Persist project graph after auto-recompute.** New
`persistProjectGraphToDisk` helper called from `kickOffRecompute` and
`getCachedSerializedProjectGraphPromise`.
- **fd race fix.** Open log handles into locals first, assign to
`this._out`/`this._err` after both opens resolve, pass the local `fd`s
to `spawn`.
- **Two-way `NX_*` env reflection.** New
`applyDaemonEnvFromClient(newEnv)` helper writes new keys AND deletes
any `NX_`-prefixed key the daemon has that the client doesn't (skipping
daemon-side exclusions and required settings).
- **`scheduleTimeoutId` → in-flight promise** for self-documenting
recompute scheduling.
- **Single `notifyFileWatcherSockets` registration** at daemon startup;
one notification per batch.
- **Map new files by project root.** `getProjectsAndGlobalChanges` uses
`createProjectRootMappings` + `findProjectForPath`, cached by reference
identity on `currentProjectGraph`.
- **Cache invalidation gated on first persist** (closes#12). New
`cacheHasBeenPersisted` flag set in `persistProjectGraphToDisk`.
`resetInternalStateIfNxDepsMissing` returns early if the flag is false —
before the first successful write, a missing `project-graph.json` is the
*expected* state. Its `catch` branch no longer auto-resets on transient
stat errors.
- **`chainToLatest` defensive kickOff** (closes#13). When a stale
compute hits `chainToLatest` and finds
`cachedSerializedProjectGraphPromise === undefined` (because
`resetInternalState` ran), it now kicks off a successor synchronously
and returns that promise instead of `undefined`. Prevents the "stale
compute commits stale data" path that the falsy guard let through.
### Test suite
- `NX_DAEMON='true'` is the default in `e2e/utils/command-utils.ts` so
CI exercises the daemon path.
- Cache-hit assertions updated to `[existing outputs match the cache,
left as is]` for the daemon-on no-op restore path. One assertion in
`cache.test.ts:216` reverted to `[local cache]` because that test adds
an extra file to `dist/`, invalidating the outputs hash and forcing a
real restore.
- Maven batch tests assert on `Successfully ran target X` instead of
`BUILD SUCCESS`. `verbose: true` dropped where it only added Maven `-X`
debug noise.
- Watch e2e harness uses `tree-kill` and waits for `close` before
reading output. Default wait shortened from 6s/8s → 1s/2s now that
debounce is deterministic.
- Walker test uses `std::os::unix::net::UnixListener::bind` instead of
`nix::unistd::mkfifo` — same `is_hashable_file` rejection, no `nix`
feature flag needed.
- **10 Rust watcher tests** (`packages/nx/src/native/watch/watcher.rs`)
drive `Watcher::watch_inner` end-to-end with real fs ops on a tempdir —
inotify/FSEvents → notify-rs → ProcMsg channel → EventIngestor →
accumulator → callback. Cover: git-style unlink+write, vim-style atomic
rename, plain in-place update, fresh create, rm, create+rm, cross-name
rename, multi-file burst coalescing, hardcoded-ignored paths never reach
callback, and 8-way concurrent `force_flush_pending` (regression for
#14). Tests use canonicalized tempdir paths so the synthetic-gitignore
filter scopes correctly on macOS where `/tmp` symlinks to
`/private/tmp`.
## Testing
- `pnpm nx run e2e-nx:e2e-ci--src/watch.test.ts --skip-nx-cache` — 8/8
passed.
- `pnpm nx run e2e-js:e2e-ci--src/js-strip-types.test.ts` — 3/3 (was
failing on test 3 before the env-reflection fix).
- `pnpm nx run e2e-maven:e2e-ci--src/maven-batch.test.ts` — 3/3 (was
failing on test 1 + 3 before the assertion fix).
- `pnpm nx run
e2e-release:e2e-ci--src/multiple-release-branches.test.ts` — 2/2 (was
failing both before the merge-event fix; reproduced locally and
confirmed via the daemon log dump that `git checkout` was emitting a
stray Delete for one of the package.json files).
- `pnpm nx run nx:test-native` — 349 tests pass, including the 10 new
watcher tests on Linux and macOS.
- macOS path validated end-to-end with the Arc fix.
## Related Issue(s)
N/A — flaky-test investigation that grew into a daemon-stability
hardening pass.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
When a project's build target is inferred via the `@nx/js/typescript`
plugin, its `outputs[0]` is a glob pattern (e.g.
`{projectRoot}/dist/**/*.{js,cjs,mjs,jsx,d.ts,d.cts,d.mts}{,.map}`).
Several executors and utilities pass that value directly to filesystem
APIs:
- `@nx/js:prune-lockfile` and `@nx/js:copy-workspace-modules` crash with
`ENOENT: no such file or directory, lstat '.../dist/**/*.{js,...}'`
because `lstatSync` is called on the literal glob.
- `@nx/web:file-server` returns the glob as the static-serve directory.
- `update-package-json` and `buildable-libs-utils` silently skip
dependent-lib version resolution when `outputs[0]` is a glob
(`existsSync` returns false / try-catch swallows).
## Expected Behavior
The glob portion is stripped back to the last path separator before the
value is used as a filesystem path, recovering the base output directory
(e.g. `apps/foo/dist`). Behavior is unchanged for non-glob output paths.
The `stripGlobToBaseDir` helper that already existed locally in
`@nx/js:node` is extracted to
`packages/js/src/utils/strip-glob-to-base-dir.ts` and reused by all
affected sites. Unit tests cover the helper's contract.
## Related Issue(s)
Fixes#35452
## Current Behavior
`graph-client:build-client` triggers 21 sandbox-read violations on
staging. Webpack snapshots context dirs registered by postcss-loader via
tailwind's `dir-dependency` emission, so it hashes `*.stories.*` and
`*.spec.*` siblings of declared content (in graph-client and its
workspace deps) even though those files never enter the bundle and are
intentionally excluded from inputs.
The reads are structural to how postcss-loader translates tailwind's
`dir-dependency` into webpack's `addContextDependency(dir)` — webpack
snapshots and hashes the entire directory regardless of which entries
the glob actually matches. They happen with **any** tailwind content
glob that resolves to a directory, not because of the specific patterns
used.
Separately, this repo's tailwind configs use broad globs (or the buggy
`*!(...)` extglob form) that scan stories and specs alongside production
code — a correctness issue independent of the sandbox violations.
## Expected Behavior
The sandbox report comes back clean for `graph-client:build-client`.
Tailwind content scanning excludes stories and specs.
## Validation
This run shows all violations are gone (except the one for the root
`tsconfig.json` file, which will be addressed by [a separate
PR](https://github.com/nrwl/nx/pull/35477)):
https://staging.nx.app/runs/0W9AQdqnyQ/task/graph-client%3Abuild-client%3Arelease?batchId=635272b4-2d1b-4b5b-89df-ebac5e1f04a8
## Changes
- Add a `graph-client:build-client` task exclusion in
`.nx/workflows/sandboxing-config.yaml` for the stories/specs reads.
**This is what resolves the sandbox violations.** The files remain on
disk but aren't correctness-relevant: they're excluded from inputs by
the `production` named-input set, never enter the bundle, and only exist
in the snapshot because webpack hashes the parent dir.
- Fix tailwind content globs in
`graph/{client,migrate,ui-code-block,ui-project-details,ui-render-config}/tailwind.config.js`
and `nx-dev/nx-dev/tailwind.config.js` to actually exclude `*.stories.*`
and `*.spec.*`. **This is independent of the sandbox fix** — the file
reads still happen at the OS level; this just stops tailwind from
scanning the content of those files when computing classes for the
production CSS.
## Current Behavior
The `Nx Commands show show target human-readable output should render
target info` snapshot test in `e2e/nx/src/misc.test.ts` fails on master.
Recent changes that include the tsconfig solution input for webpack
added a new input entry
(`{"json":"{workspaceRoot}/tsconfig.json","fields":["extends","files","include"]}`)
to the resolved target info, but the snapshot was not updated.
## Expected Behavior
Snapshot reflects the new tsconfig solution input so the e2e test
passes.
## Related Issue(s)
N/A — follow-up to 7a6f796047 / ca7671afd6 which added the tsconfig
solution input to webpack/rollup.
When a `targetDefaults` entry keyed by target name carries an executor
that differs from an inferred (specified-plugin) target's executor, the
synthetic plugin built by `createTargetDefaultsResults` would be merged
on top of the specified target. The downstream "incompatible executor"
branch in `mergeTargetConfigurations` then dropped the specified
target's options/configurations and let the synthetic's executor win,
silently replacing the inferred command with the unrelated default
executor.
Skip synthesis in the specified-only branch when the resolved default is
incompatible with the specified target. The default was authored for a
different executor and shouldn't apply.
Surfaces in polyglot workspaces — e.g. a `targetDefaults['test-native']`
configured for `@monodon/rust:test` was overriding a dotnet plugin's
inferred `test-native` (`command: 'dotnet test'`), causing CI to run
`cargo test` against C# projects.
## Current Behavior
The `@nx/webpack` executor, inferred plugin, and config builder all call
`isUsingTsSolutionSetup()` and let its result influence task outputs
(e.g. `useTsconfigPaths`). However, the root `tsconfig.json` is not part
of the build task's cache inputs — so edits to root `tsconfig.json`
(`extends`, `files`, `include`) don't invalidate webpack task caches and
stale outputs are reused.
The same gap exists in `@nx/node`'s webpack-bundler branch of the
application generator: it calls `addBuildTargetDefaults(tree,
'@nx/webpack:webpack')` without the tsconfig input, even though the
parallel esbuild branch in the same file already passes
`TS_SOLUTION_SETUP_TSCONFIG_INPUT`.
## Expected Behavior
Matches the rollup fix in #35476: the root `tsconfig.json` is included
as a structured input (`{ json: '{workspaceRoot}/tsconfig.json', fields:
['extends', 'files', 'include'] }`) on `@nx/webpack:webpack` task
defaults and in the inferred plugin's build target inputs, so changes to
the relevant fields invalidate caches.
### Changes
- `packages/webpack/src/plugins/plugin.ts` — append
`TS_SOLUTION_SETUP_TSCONFIG_INPUT` to the inferred build target's
`inputs`. Also gate the targets cache on `NX_CACHE_PROJECT_GRAPH`
(mirrors the rollup PR) and update the spec accordingly.
- `packages/webpack/src/generators/configuration/configuration.ts` —
pass `'build', [TS_SOLUTION_SETUP_TSCONFIG_INPUT]` to
`addBuildTargetDefaults`.
- `packages/node/src/generators/application/lib/create-project.ts` —
same on the webpack branch (the esbuild branch already had it).
- `packages/webpack/src/plugins/plugin.spec.ts` — mock spreads
`requireActual` so the constant is real; sets/restores
`NX_CACHE_PROJECT_GRAPH`; snapshot updated to include the new input.
<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/73d1eed2)
<!-- polygraph-session-end -->
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
## Current Behavior
Rollup build target defaults and inferred Rollup build targets do not
include the root `tsconfig.json` fields that TypeScript solution setup
detection reads. Changes to `extends`, `files`, or `include` in
`{workspaceRoot}/tsconfig.json` can therefore cause Rollup tasks to miss
cache invalidation.
## Expected Behavior
Rollup target defaults and inferred Rollup build targets include
`{workspaceRoot}/tsconfig.json` fields `extends`, `files`, and `include`
as task inputs.
## Current Behavior
nx console's status check is missing a provenance check
## Expected Behavior
provenance is validated before installing latest nx
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
`@nx/next/plugins/with-nx.ts` calls `createProjectGraphAsync()` from
inside `next.config.js` evaluation. This has two negative effects:
1. **Sandbox violations.** Calling `createProjectGraphAsync` inside the
build re-runs every registered Nx plugin's `createNodesV2`. For example,
on `nx-dev:next:build` this generates 562 unexpected reads — including
548 files from `packages/nx/dist/**/*.js` (Nx core internals loaded by
the graph machinery) plus sibling project files like
`nx-dev/nx-dev-e2e/playwright.config.ts`, spec files, and
`eslint.config.mjs` files that are read by `@nx/playwright/plugin` and
`@nx/eslint/plugin` while inferring targets. None of these are real
input dependencies of the Next.js build — they are an implementation
detail of graph creation.
2. **Daemon socket leak (workaround in #34518).** The same call also
opens a daemon client socket that keeps the Node event loop alive. PR
#34518 patched this with `resetDaemonClient: true` after Jest started
hanging in #32880. The socket exists only because we are talking to the
daemon to (re)build the graph at all.
Both problems share a root cause: graph creation is being run inside the
build, when the graph has already been built and cached by the Nx task
runner before `next build` ever starts.
## Expected Behavior
`withNx` reads the already-cached graph instead of rebuilding it.
This matches the pattern used by `@nx/webpack`
(`packages/webpack/src/plugins/nx-webpack-plugin/lib/normalize-options.ts`)
and `@nx/rspack`
(`packages/rspack/src/plugins/utils/plugins/normalize-options.ts`), both
of which call `readCachedProjectGraph()` with the comment _"Since this
is invoked by the executor, the graph has already been created and
cached."_
The early-return guard already in `withNx` (no `NX_TASK_TARGET_TARGET`
env var) ensures we only reach the graph-reading branch when running
inside an Nx task, which is exactly when the cached graph is guaranteed
to exist.
This change:
- Eliminates the 562 sandbox violations on `nx-dev:next:build` (verified
locally by patching `node_modules/@nx/next/plugins/with-nx.js` and
re-running the build).
- Removes the need for `resetDaemonClient: true` since no daemon
connection is opened in the first place — also obviating the original
Jest hang.
- Speeds up `next build` slightly by skipping a full graph re-creation
pass.
## Related Issue(s)
Follow-up to #34518 / #32880 — fixes the underlying cause that the
daemon-reset workaround was treating.
## Current Behavior
On every first `nx` command run by a 22.6.x workspace once `22.7.0`
shipped to npm, the daemon spuriously shuts itself down with:
```
[Server] Daemon outdated: NX_VERSION_CHANGED
[Server] Shutting down daemon (no restart)…
Server stopped because: "NX_VERSION_CHANGED"
```
Visible symptoms in the field (#35444): "stuck on Calculating project
graph", `EPIPE` mid-task, and broken CI/CD pipelines for users who
haven't upgraded past `22.6.x`.
### Root cause
The daemon's `handleGetNxConsoleStatus` and
`handleGetConfigureAiAgentsStatus` install `nx@latest` to a temp dir and
`require()` files from that install **into the daemon's own Node
process**. Two `nx` packages now share one process — the workspace's
installed copy and the temp copy.
Inside the temp copy, `setupAiAgentsGenerator(..., inner: true)` calls
`getNxVersion()`, which calls `readModulePackageJson('nx')`, which
calls:
```ts
require.resolve('nx/package.json', { paths: getNxRequirePaths(workspaceRoot) })
```
The calling file lives inside the temp's `nx` package, whose
`package.json` has `name: "nx"` and an `exports` map. Per Node's CJS
resolver, that makes `'nx/package.json'` qualify as a **package
self-reference**, which resolves to the calling package's own
`package.json` — `/tmp/.../nx/package.json` — **ignoring the `paths`
argument**.
`Module._findPath` then writes the result to its process-wide cache, but
builds the cache key from the (workspace-rooted) `paths` argument:
```
Module._pathCache["nx/package.json\0<workspace>/.nx/installation/node_modules\0…"]
= /tmp/.../node_modules/nx/package.json
```
20 ms later, the daemon's own watchdog runs `getInstalledNxVersion()`
with the same `paths`, hits the polluted cache entry, and reads back the
`/tmp` `package.json` — version `22.7.0`. Compared against the daemon's
frozen `nxVersion` (`22.6.5`), they differ; `daemonIsOutdated()` returns
`'NX_VERSION_CHANGED'`; the daemon tears itself down.
The bug stayed dormant from `22.6.0` (when the in-process latest-pull
pattern shipped, #34463) until `22.7.0` was published, because while
`latest === installed` the polluted cache value matched the constant. PR
#34111 (which gave `nx`'s `package.json` an `exports` field for the
first time) is what enabled self-reference, without which `paths` would
have been honored and no pollution would have occurred.
## Expected Behavior
The daemon stays alive across `nx` commands. Pulling `nx@latest` and
running the in-process console / AI-agents checks does not poison the
resolver cache, and `getInstalledNxVersion()` keeps returning the
workspace's actual installed version.
### Fix
Replace both `require.resolve('nx/package.json', { paths })` callsites
with a direct filesystem walk over the same
`getNxRequirePaths(workspaceRoot)`. Walking the filesystem bypasses
Node's resolver entirely and is immune to `Module._pathCache` pollution.
**`packages/nx/src/ai/set-up-ai-agents/set-up-ai-agents.ts:getNxVersion`**
— the polluter. Once this ships, every existing `22.6.x` daemon picks up
the fix automatically via its next `nx@latest` pull. The daemon's own
(still-buggy) check then has nothing to misread, and stops dying —
without users having to upgrade their workspace at all.
**`packages/nx/src/daemon/is-nx-version-mismatch.ts:getInstalledNxVersion`**
— the victim. Defensive: if any future code path added to the in-process
latest-pull triggers the same `require.resolve('nx/package.json', {
paths })` pattern, the daemon's verdict on "what version is installed?"
stays correct anyway.
### Verification
Reproduced locally with a `22.6.5` workspace (yarn 4) using the original
unfixed `is-nx-version-mismatch.js`:
- **Before**: daemon shut itself down with `NX_VERSION_CHANGED` on every
first command after `22.7.0` published.
- **After** (fix published as `nx@latest` to a local Verdaccio): daemon
pulled the fixed temp, ran both inner checks (`[NX-CONSOLE]: Console
status check completed`, `[AI-AGENTS]: Agent configuration status
computation completed`), and survived three consecutive `nx` commands.
No `Daemon outdated`, no `Server stopped`, same daemon PID across
commands.
This empirically confirms the `set-up-ai-agents` fix retroactively heals
`22.6.x` users without them touching their workspace.
## Related Issue(s)
Fixes#35444
This PR removes Less, Tailwind, and CSS-in-JS style options from React
and Vue generators.
Moving forward, it is preferable to use either CSS or SCSS, and set up
other options manually or with AI. The CSS-in-JS solutions have fallen
out of favor. Tailwind and shadcn are more likely to be used by AI, and
neither needs our generator support.
For anyone who depends on these removed options, they can wrap their own
generators on top of ours to customize further.
## Current Behavior
All non-Angular generators (React, Next.js, Vue, Nuxt, Web, Workspace)
prompt users with a long list of stylesheet options including. These
make the decision much more complicated, where a basic CSS/SCSS setup
opens up for more customization if desired without our official support.
## Expected Behavior
**Simplified style prompt** — the interactive prompt now shows only:
- CSS (default)
- SCSS
- None
Note: Code will continue to compile for now, we're removing the option
from generators. For LESS, users will receive a deprecation warning, the
same way we deprecated Stylus previously. For styled-jsx,
styled-components, emotion we will do a follow-up when we decide what to
do with built-in configs for webpack/rspack/rollup.
## Related Issue(s)
Fixes NXC-4178
Since the napi v2→v3 migration (#34619) moved Tui::start() out of
enter() and
into an async block, there is a window between enable_raw_mode() and
EventStream::new() during which bytes can sit in the kernel tty buffer
and
later be parsed as keypresses by the reader. Two known sources:
1. Tail bytes of the OSC 11 color-scheme reply that terminal-colorsaurus
doesn't fully consume (the reply contains '/' separators, e.g.
`\x1b]11;rgb:RRRR/GGGG/BBBB\x07`, which trigger filter mode and feed
hex digits as filter text).
2. Leftover input from the analytics prompt (#34144, new in v22.6) that
uses enquirer/raw-mode and may not drain stdin completely.
Either path manifests as the TUI booting with a bogus filter pre-applied
that hides every task.
start() was moved out because tokio::spawn requires a Tokio runtime
context,
and the sync __init NAPI method runs on the JS main thread without one.
Switching the inner spawn to napi::bindgen_prelude::spawn (which uses
napi's
static runtime and works from any thread — already used elsewhere for
the
same reason) lets enter() call start() synchronously again, so
EventStream
exists before enter() returns and consumes those bytes itself.
https://claude.ai/code/session_01RwrzRzTCZ7k8Uzw2xux6kM
Co-authored-by: Claude <noreply@anthropic.com>
## Current Behavior
`@nx/react:application` (and any path that calls `@nx/vite` or
`@nx/vitest` `ensureDependencies`) crashes when the root `package.json`
declares `vite` via a pnpm catalog alias such as `"vite": "catalog:"` or
`"vite": "catalog:tooling"`:
```
NX Invalid version. Must be a string. Got type "object".
TypeError ... at new SemVer ... at major ... at ensureDependencies
```
`@nx/storybook`'s `convert-to-inferred` migration is broken in the same
way: `getInstalledPackageVersion` reads dependencies straight from
`package.json` with no catalog resolution, so a literal `catalog:` value
reaches `coerce` and throws via `major(null)` in
`getInstalledPackageVersionInfo`.
## Expected Behavior
- pnpm catalog aliases in `package.json` are resolved through
`pnpm-workspace.yaml` (default and named catalogs) before semver
parsing.
- When the resolved/raw range is not coercible (missing catalog entry,
missing `pnpm-workspace.yaml`, `workspace:*`, `link:`, `file:`, `git:`),
the code falls back to the existing default rather than throwing.
- Existing v4-vs-v6 `@vitejs/plugin-react` selection for plain semver
ranges is preserved.
## Changes
- `@nx/vite` / `@nx/vitest`: `ensureDependencies` now reads `vite` via
`getDependencyVersionFromPackageJson` (catalog-aware) and null-guards
the `coerce` result.
- `@nx/storybook`: `getInstalledPackageVersion` now uses
`getDependencyVersionFromPackageJson`, and
`getInstalledPackageVersionInfo` null-guards `coerce`.
- Tests added in `packages/vite/src/utils/ensure-dependencies.spec.ts`
for named catalog, default catalog, missing catalog entry, and
unparseable ranges (`workspace:*`).
## Related Issue(s)
Fixes#35453
## Current Behavior
The node app generator with `bundler=esbuild` does not include the
field-scoped `tsconfig.json` input needed for proper cache hashing. This
means builds may return stale cached results when the workspace's
`tsconfig.json` is edited (e.g., changing `extends`, `files`, or
`include` fields).
## Expected Behavior
The node app generator now includes `TS_SOLUTION_SETUP_TSCONFIG_INPUT`
as an input for `@nx/esbuild:esbuild` targets, ensuring cache hashes
properly reflect changes to the workspace root `tsconfig.json`.
Additionally, exports `TS_SOLUTION_SETUP_TSCONFIG_INPUT` from `@nx/js`
so other Nx packages can reuse this constant for their own executors and
plugins.
## Current Behavior
The default tailwind content glob in `@nx/react/tailwind` and
`@nx/vue/tailwind` helpers, plus their `setup-tailwind` generator
templates, is meant to skip `*.stories.*` and `*.spec.*` files, but the
leading `*` in the `!()` extglob makes the negation a no-op — story and
spec classes end up scanned alongside production code.
## Expected Behavior
Stories and specs are excluded from tailwind content scanning, matching
the intent of the existing pattern.
## Technical details
The current pattern `*!(*.stories|*.spec).{...}` fails because the
leading `*` lets the matcher split the input across `*` and `!()` (e.g.,
`foo.stories` → `*` = `foo.`, `!()` = `stories`, which doesn't end in
`.stories`), so the file matches the glob and gets included. Removing
the leading `*` makes `!(*.stories|*.spec)` apply to the full filename
portion. Affects:
- `packages/react/tailwind.ts` (helper default)
- `packages/vue/tailwind.ts` (helper default)
-
`packages/react/src/generators/setup-tailwind/files/tailwind.config.js__tmpl__`
-
`packages/vue/src/generators/setup-tailwind/files/tailwind.config.js.template`
Someone got confused because the build is restoring from cache when the
env var the app uses is different from prod vs local. We should mention
that env vars need to be added as inputs.
Fixes#33331
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
## Current Behavior
The `@nx/js/typescript` plugin walks the project reference chain to
collect tsconfig paths as inputs for the inferred `typecheck` target,
but does not follow `extends` chains on those referenced tsconfigs. When
`tsc --build` walks into an external project reference whose
`tsconfig.lib.json` extends a sibling tsconfig (e.g. `./tsconfig.json`),
that extended file is read at compile time but is not declared as an
input — causing sandbox violations and incorrect cache keys for the
consumer's `typecheck` task.
## Expected Behavior
The plugin walks `extends` chains for every tsconfig visited during the
reference chain traversal, emitting `^{projectRoot}/...` patterns for
the extended files alongside the existing reference-chain patterns.
Tasks consuming external project references with `extends` chains now
correctly declare the full set of tsconfig files tsc reads.
## Changes
`getExternalProjectReferenceTsconfigPatterns` now walks `extends` for
each tsconfig visited during the worklist traversal. The walk:
- Reuses the same `visited` set as the reference-chain walk (no
redundant work).
- Reuses the cached parsed tsconfig data (`tsConfigCacheData`) and
`getConfigContext` cache (no extra parsing).
- Pushes extended files onto the same worklist so extends-of-extends and
references-from-extends are covered transitively.
- Skips workspace-root files (no owning project) — those are already
covered by the local project's existing extends walk.
Patterns emit in the same `^{projectRoot}/relPath` form as the rest of
the function, so the existing transitive resolution through the project
graph applies unchanged.
A focused unit test pins the new behavior: a project with an external
ref whose `tsconfig.lib.json` extends a same-project
`tsconfig.shared.json` correctly emits
`^{projectRoot}/tsconfig.shared.json` as an input.
## Current Behavior
`multiGlobWithWorkspaceContext` in
`packages/nx/src/utils/workspace-context.ts` is missing the
`workspaceRoot === '/virtual'` short-circuit that its sibling
`globWithWorkspaceContext` already has (added in #31805).
When the Nx daemon is running and a generator test uses
`createTreeWithEmptyWorkspace` (which sets the root to the `/virtual`
sentinel), `multiGlobWithWorkspaceContext` forwards to the daemon
attached to the real workspace, gets back real paths, and then plugins
(e.g. project-graph-inferring plugins) ENOENT trying to read those paths
at `/virtual/<path>`.
This silently breaks generator test suites for any workspace that has
project-graph-inferring plugins (which is most modern Nx workspaces).
## Expected Behavior
`multiGlobWithWorkspaceContext` should bypass the daemon when called
with `workspaceRoot === '/virtual'`, just like
`globWithWorkspaceContext` does. Generator tests run against the
in-memory virtual tree without any daemon round-trip.
## Related Issue(s)
Fixes#35373
Related: #32588 reported the same symptom via `@nx/cypress` in Sept 2025
and was auto-closed stale; this PR addresses the root cause.
The original `globWithWorkspaceContext` `/virtual` guard was added in
#31805 — this PR mirrors it on the multi-glob path.
## Current Behavior
Four `typecheck` tasks in the Nx repo — `graph-client`, `graph-migrate`,
`graph-project-details`, `graph-ui-project-details` — trigger sandbox
violations on staging. tsc reads files from `packages/devkit`,
`packages/nx`, and `nx-dev/ui-fence` that are not declared as inputs,
and rebuilds `nx-dev/ui-fence` inside the consumer's sandbox, producing
cross-project writes that cascade down the graph chain.
## Expected Behavior
The four graph typecheck tasks have all required cross-project tsconfigs
declared as inputs and their cross-project source/output reads covered
by task-level dependencies. tsc no longer rebuilds `nx-dev/ui-fence`
inside consumer sandboxes. Sandbox runs come back clean.
## Changes
Three independent causes, addressed together.
1. **`nx-dev/ui-fence` had no upstream `typecheck` task.** Its
`.d.ts`/`tsbuildinfo` outputs weren't being materialized, so consumers
rebuilt it inside their own sandboxes — causing cross-project writes
that cascaded down the graph chain. Added `nx-dev/ui-fence/**` to the
existing `@nx/js/typescript` plugin block in `nx.json`. ui-fence now has
its own task, its outputs flow through `dependentTasksOutputFiles`, and
the cascading writes disappear.
2. **`graph-ui-project-details` declares `implicitDependencies:
["!devkit"]`** to break a project graph cycle, which removes devkit (and
transitively nx) from its nx project graph. The plugin's
`^{projectRoot}/...` patterns walk the project graph, so they never
reach `packages/devkit/tsconfig.lib.json` or
`packages/nx/tsconfig.lib.json` — even though tsc walks into them via
tsconfig project references. Addressed by:
- Adding `packages/devkit/tsconfig.lib.json` as an explicit project
reference in `graph/ui-project-details/tsconfig.lib.json` (with
`nx.sync.ignoredReferences` to keep typescript-sync from stripping it).
- Adding a task-level `dependsOn` on `devkit:build-base` so the task
graph actually has devkit producing outputs.
- Declaring `{workspaceRoot}/packages/devkit/tsconfig.lib.json` and
`{workspaceRoot}/packages/nx/tsconfig.lib.json` as explicit inputs on
the four graph projects' typecheck overrides.
3. **`nx-dev/ui-fence/tsconfig.lib.json` extends `./tsconfig.json`** —
previously the plugin emitted `^{projectRoot}/tsconfig.lib.json` only,
so the extended `tsconfig.json` was an undeclared read. Resolved by
#35457, which makes the plugin walk `extends` chains in external project
references — no per-project workaround needed here.
The four `project.json` overrides use `"..."` spread tokens for `inputs`
to inherit the plugin-inferred input set rather than redeclaring it,
keeping the diff minimal and the intent ("plugin defaults plus these
specific cross-graph-cut tsconfigs") clear.
## Current Behavior
`nx migrate --run-migrations` crashes on workspaces that use the
`@nx/jest:jest` executor (via `targetDefaults`) instead of
`@nx/jest/plugin`:
```
NX Failed to run replace-removed-matcher-aliases-v22-3 from @nx/jest. This workspace is NOT up to date!
NX Jest: Failed to parse the TypeScript config file .../libs/.../jest.config.ts
ReferenceError: __dirname is not defined in ES module scope
```
The `convert-jest-config-to-cjs` migration (update-22-2-0) is gated on
`@nx/jest/plugin` being registered in `nx.json`, so executor-based
workspaces skip it entirely. Their `jest.config.ts` files (often a mix
of ESM syntax and CJS globals like `__dirname`) never get converted. The
later `replace-removed-matcher-aliases-v22-3` migration then calls
`jest-config.readConfig` on every `jest.config.ts`, which on Node
22+/24+ with native type-stripping reparses the file as ESM and crashes.
Separately, the conversion logic also didn't handle `import type`
declarations — it rewrote them to `const { X } = require('mod')`, which
unnecessarily pulls the module at runtime and drops type references the
IDE/tsc relied on. For types-only specifiers, it could even crash at
runtime.
## Expected Behavior
`convert-jest-config-to-cjs` runs for every `jest.config.ts` whose
project is CommonJS (plugin registration is no longer required), so
executor-based setups are covered. The `type: module` guard still skips
ESM projects.
Type-only imports are preserved:
- `import type { Config } from 'jest'` — left untouched (Node's
type-stripping erases it, so it doesn't force ESM parsing at runtime).
- `import { type Foo, bar } from 'mod'` — split into `import type { Foo
} from 'mod'` plus `const { bar } = require('mod')`.
- Renames (`import { type Foo as JestFoo, run } from 'mod'`) preserved.
## Related Issue(s)
Fixes#34593
## Current Behavior
When a generator adds a lint target to the root project and then creates
a new non-root project in the same run, the root eslint config is not
split into a base config on that run. Subsequent projects created
afterwards (in the same run or in separate runs) end up wired
incorrectly, and users have to re-run the generator to get the migration
to actually happen.
## Expected Behavior
The root eslint config is split into a base config on the first run
where a non-root project is created alongside a root lint target, so
projects are wired correctly without requiring a second invocation.
## Implementation Notes
`isMigrationToMonorepoNeeded` previously relied on
`createProjectGraphAsync()` to detect the root lint target. The project
graph reflects the filesystem at its last rebuild and misses targets
written to the tree earlier in the same generator run.
The check now reads the tree first via `getProjects(tree)`. The project
graph is only consulted as a fallback when `@nx/eslint/plugin` is
registered, since plugin-inferred targets do not appear on the tree —
preserving the behavior introduced in #23147.
### Known gaps (not addressed)
The symmetric in-flight cases on the inferred branch remain open:
- a root eslint config file written during the same generator run with
`@nx/eslint/plugin` already registered, and
- `@nx/eslint/plugin` registered in `nx.json` during the same run with a
root config already on disk.
Closing them would require reimplementing `@nx/eslint/plugin`'s
`createNodes` pipeline against the tree — the maintenance burden #23147
explicitly avoided. No known user report exercises those paths today.
## Related Issue(s)
Fixes#34531
## Current Behavior
When running `nx migrate --run-migrations` in a workspace with `npm` as
the package manager, sometimes the automatic package installation
performed by the command can fail due to peer dependency constraint
violations. In such cases, no actionable feedback is provided to the
user to help resolve the issue.
## Expected Behavior
When running `nx migrate --run-migrations` in a workspace with `npm` as
the package manager, and the automatic package installation performed by
the command fails, Nx should provide actionable feedback to resolve the
issue and to re-run the command while skipping the package installation.
## Related Issue(s)
Fixes#33942
Reverts the temporary describe.skip blocks added in #35214. The
@module-federation/enhanced dependency has been bumped (now 2.3.3) and
upstream webpack compatibility is expected to be restored, so we let CI
verify whether the suites pass again.
NXC-4220
The detox application generator's .detoxrc.json template left a trailing
comma after the last entry of the apps and configurations blocks when
the optional expo-only entries were not emitted, producing invalid JSON
for react-native apps. Move the comma inside the EJS conditional so it
is only included when the following expo entry is also emitted.
## Current Behavior
Daemon env reflection sends filtered process.env on first message. Server compares key-by-key; any diff invalidates the project graph cache and forwards env to plugin workers. hyperfine rotates HYPERFINE_RANDOMIZED_ENVIRONMENT_OFFSET per iteration, so every run busts the graph cache and respawns plugin workers (~170ms overhead).
## Expected Behavior
hyperfine env vars do not affect graph construction. Filter them alongside existing editor, terminal, and CI runner prefixes.
When merging project configurations (e.g., from plugins, `project.json`,
target defaults in `nx.json`), array and object properties are
completely replaced by the new value. There is no way to extend or merge
with the base value.
For example, if a plugin infers:
```json
{
"targets": {
"build": {
"inputs": ["default", "{projectRoot}/**/*"]
}
}
}
```
And `nx.json` has target defaults:
```json
{
"targetDefaults": {
"build": {
"inputs": ["production"]
}
}
}
```
The result would be `["production"]` — completely replacing the inferred
inputs rather than combining them.
This PR adds support for `"..."` as a spread token when merging
configurations. Users can now control how arrays and objects are merged
by specifying where the base value should be inserted.
**Array spread:**
```json
{
"inputs": ["production", "...", "{workspaceRoot}/.eslintrc.json"]
}
```
Results in: `["production", "default", "{projectRoot}/**/*",
"{workspaceRoot}/.eslintrc.json"]`
**Object spread:**
```json
{
"options": {
"env": {
"NEW_VAR": "value",
"...": true,
"OVERRIDE_VAR": "overridden"
}
}
}
```
Spreads the base object's properties at the position of `"..."`, with
keys defined after the spread taking precedence.
This works in:
- Top-level target properties (`inputs`, `outputs`, `dependsOn`)
- Target `options` and nested option objects (one layer deep)
- Target `configurations` and their options (one layer deep)
- Both `project.json` merging and `nx.json` target defaults
Fixes #
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`allWorkspaceFiles` (a flat `FileData[]` of every file in the workspace)
is built, stored, copied, and passed through the project graph pipeline
— even though no consumer uses it. The data is fully redundant with
`fileMap.projectFileMap` + `fileMap.nonProjectFiles`. In the daemon,
it's deep-copied on every graph serialization via `copyFileData()` and
rebuilt on every incremental update via `buildAllWorkspaceFiles()`.
## Expected Behavior
`allWorkspaceFiles` is removed from:
- `retrieveWorkspaceFiles` return value
- `buildProjectGraphUsingProjectFileMap` parameters
- `hydrateFileMap` / `getFileMap` / `storedAllWorkspaceFiles`
- `fileMapWithFiles` daemon state and `SerializedProjectGraph` interface
- `WorkspaceFileMap` interface and `updateFileMap` return value
This eliminates redundant allocations, copies, and retained references.
Native/Rust code is unaffected — it uses
`rustReferences.allWorkspaceFiles` (`ExternalObject`), which is a
separate code path.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Calling `delayedSpinner.setMessage` before it would have already
appeared causes it to appear earlier than it should
## Expected Behavior
Calling delayedSpinner.setMessage doesn't actually invoke the spinner
update message if the delayed spinner hasn't fired yet, instead it
stores the message under `lastMessage`, and whenever the spinner fires
it reads lastMessage
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
Implements a ci-workflow generator for .NET projects, following the
pattern established by the gradle ci-workflow generator.
## Blocked
Waiting for https://github.com/nrwl/nx-cloud-workflows/pull/112
## Changes
- **Generator implementation**
(`packages/dotnet/src/generators/ci-workflow/`)
- Supports GitHub Actions and CircleCI
- Configures .NET SDK 8.x
- Uses `linux-medium` agent type for Nx Cloud task distribution
- Includes Nx affected commands and self-healing CI (`nx fix-ci`)
- **Templates**
- GitHub Actions: uses `actions/setup-dotnet@v4`
- CircleCI: uses `mcr.microsoft.com/dotnet/sdk:8.0` docker image
- **Registration**: Added to `generators.json` alongside existing `init`
generator
## Usage
```bash
# Generate GitHub Actions workflow
nx g @nx/dotnet:ci-workflow --ci=github
# Generate CircleCI workflow
nx g @nx/dotnet:ci-workflow --ci=circleci
```
Fixes
https://linear.app/nxdev/issue/NXC-3356/generate-ci-workflow-for-net
> [!WARNING]
>
> <details>
> <summary>Firewall rules blocked me from connecting to one or more
addresses (expand for details)</summary>
>
> #### I tried to connect to the following addresses, but was blocked by
firewall rules:
>
> - `repo.gradle.org`
> - `staging.nx.app`
>
> If you need me to access, download, or install something from one of
these locations, you can either:
>
> - Configure [Actions setup
steps](https://gh.io/copilot/actions-setup-steps) to set up my
environment, which run before the firewall is enabled
> - Add the appropriate URLs or hosts to the custom allowlist in this
repository's [Copilot coding agent
settings](https://github.com/nrwl/nx/settings/copilot/coding_agent)
(admins only)
>
> </details>
<!-- START COPILOT CODING AGENT SUFFIX -->
<details>
<summary>Original prompt</summary>
> Issue Title: Generate `ci-workflow` for .NET
> Issue Description: This should be similar to the existing ci-workflow
generator for gradle, reference
[https://github.com/nrwl/nx/tree/master/packages/gradle/src/generators/ci-workflow](https://github.com/nrwl/nx/tree/master/packages/gradle/src/generators/ci-workflow)
>
> The generator should be scaffolded using `nx g generator
./packages/dotnet/src/generators/ci-workflow/ci-workflow`
> Fixes
https://linear.app/nxdev/issue/NXC-3356/generate-ci-workflow-for-net
>
>
> Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6:
> [https://github.com/nrwl/nx](https://github.com/nrwl/nx)
>
> Comment by User d484ef82-7f7d-4a95-be09-9d82ca3905dc:
> 📋 I wasn't able to determine which GitHub repository to work in.
>
> I think it's one of these, but can you tell me which one is right?
>
> Comment by User :
> This thread is for an agent session with githubcopilot.
>
>
</details>
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AgentEnder <6933928+AgentEnder@users.noreply.github.com>
Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
## Current Behavior
The `@nx/esbuild:esbuild` executor calls `isUsingTsSolutionSetup` at
task runtime, which reads `extends`, `files`, and `include` from the
workspace-root `tsconfig.json`. Targets using the executor do not
declare those fields as inputs, so cache hashes do not reflect changes
to them and builds can hit stale cache entries after a `tsconfig.json`
edit.
## Expected Behavior
Targets using the `@nx/esbuild:esbuild` executor include a field-scoped
`{workspaceRoot}/tsconfig.json` input covering exactly the fields the
executor reads, so hashes change when those fields change and remain
unaffected by unrelated edits.
- `addBuildTargetDefaults` gains an optional `extraInputs` parameter so
plugin generators can extend the default/production inputs when seeding
executor-keyed target defaults. Existing call sites are unchanged.
- The `@nx/esbuild:configuration` generator uses that parameter to add a
field-scoped `{workspaceRoot}/tsconfig.json` input (fields: `extends`,
`files`, `include`) to the `@nx/esbuild:esbuild` target defaults it
seeds.
- This repo's `nx.json` gains the same executor-keyed target defaults so
`tools-documentation-create-embeddings` (the only esbuild target in the
repo) inherits the fix. `dependsOn` mirrors the existing `build`
target-name default to preserve the inferred `typecheck` and
`build-base` dependencies.
## Description
Adds a `compiler` option to the `@nx/vite` plugin, mirroring the same
option already in `@nx/js` (added in #33821).
This lets users specify an alternative TypeScript compiler for the
inferred `typecheck` target — specifically `tsgo` from
`@typescript/native-preview` (TypeScript 7 Go compiler).
## Problem
`@nx/vite` hardcodes `'tsc'` for typecheck while `@nx/js` already
supports a configurable `compiler` option. Users who want `tsgo` have to
patch `@nx/vite` manually.
## Solution
3 lines in `packages/vite/src/plugins/plugin.ts`:
1. Add `compiler?: string` to `VitePluginOptions` (with JSDoc)
2. `options.compiler ?? 'tsc'` instead of hardcoded `'tsc'` (Vue
projects still use `vue-tsc`)
3. `options.compiler ??= 'tsc'` in `normalizeOptions`
## Usage
```json
// nx.json
{
"plugins": [
{
"plugin": "@nx/vite/plugin",
"options": {
"compiler": "tsgo"
}
}
]
}
```
## Benchmarks (large monorepo, ~400 projects)
| Project | `tsc` | `tsgo` | Speedup |
|---|---|---|---|
| `dashboard-web` | 2.33s | 0.43s | **5.4×** |
| `market-react` | 11.57s | 3.64s | **3.2×** |
On CI: total typecheck CPU dropped **2.7×**, allowing us to eliminate
worker sharding entirely.
Currently working around this with a pnpm patch on `@nx/vite` — happy to
remove it once this lands.
## Prior art
- #33821 — same `compiler` option added to `@nx/js`
- #35047 / #35167 — Nx team experimented with tsgo internally
---------
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
We want a page to link users to when we deprecate and start removing
`nx` exports. There should be zero imports from `nx` in the wild.
KB article:
https://deploy-preview-35412--nx-docs.netlify.app/docs/guides/tips-n-tricks/migrate-nx-imports-to-devkit
## Current Behavior
No guidance for users importing from `nx` (CLI). These imports break in
future major when `nx` stops exporting them.
## Expected Behavior
New recipe under Tips & Tricks. Lists common devkit symbols,
before/after code blocks, and a copy-prompt for AI-driven migration.
Calls out `@nx/devkit/testing` and `@nx/devkit/ngcli-adapter` subpaths.
Also fixes `llm_copy_prompt` transform: inline code, links, and ordered
list numbering were being stripped from extracted prompt text.
## Related Issue(s)
Fixes DOC-462
## Current Behavior
`NodeChildProcessWithNonDirectOutput` in
`packages/nx/src/tasks-runner/running-tasks/node-child-process.ts:48`
listens for the child process `'exit'` event. In that listener it joins
`terminalOutputChunks` into a single string and clears the buffer before
notifying `exitCallbacks`.
The child process `'exit'` event fires when the child exits, but its
stdout/stderr streams may still have buffered `'data'` events pending on
Node's event queue. When a task writes output and exits on the same tick
(e.g. `echo X && exit 0`), the `'exit'` listener can run *before* the
final `'data'` callback lands in `terminalOutputChunks`. The result: the
late chunk is pushed into a new (now-empty) array that nobody reads, and
the joined output that reaches `exitCallbacks` is missing its tail. The
effect is visible as missing stdout when running many tasks at once
(#35302).
## Expected Behavior
Switch the listener from `'exit'` to `'close'`. Per Node.js docs,
`'close'` fires only after the child has exited **and** its stdio
streams have closed — so every `'data'` event has been delivered by
then. The listener body is otherwise unchanged (`(code, signal)` is
still the argument shape).
No callers of `exitCallbacks` are timing-sensitive: they only read
`code` and `terminalOutput`. The slight delay from waiting for stdio
drain is precisely what we want here.
Added a regression test that simulates the race (`'exit'` fires, then
stdout `'data'` arrives, then `'close'` fires) and asserts the late
output is captured in the joined `terminalOutput`.
## Related Issue(s)
Fixes#35302
## Current Behavior
In 22.7.0-rc.1, `$schema` references in `nx.json` and `project.json` no
longer resolve in VS Code / JetBrains / any editor that reads `$schema`
as a filesystem path.
Root cause — two commits combined:
1. #34111 moved schemas from `packages/nx/schemas/` to
`packages/nx/dist/schemas/`.
2. #35109 introduced a `files` allowlist that shipped only `dist/`,
dropping the legacy top-level `schemas/` from the published npm tarball.
Node subpath `exports` (`"./schemas/*": "./dist/schemas/*.json"`) do
**not** redirect filesystem paths — editors bypass Node resolution
entirely. So the regression affected both existing workspaces upgrading
from 22.6.x (with `$schema` already in their configs) and fresh installs
(since `nx init`, `create-nx-workspace`, and the project-configuration
generator all write `./node_modules/nx/schemas/...`).
## Expected Behavior
`schemas/*.json` ship at the root of the published `nx` package again,
so `./node_modules/nx/schemas/nx-schema.json` (and the project/workspace
variants) resolve on disk — no migration required, no changes to the
paths generators write.
Schemas are static JSON assets, not build artifacts — they're now
published from source, not copied through `dist/`:
- `packages/nx/package.json` `files`: `"dist/schemas"` → `"schemas"`.
- `packages/nx/package.json` `exports`: `./schemas/*` and
`./schemas/*.json` both map to `./schemas/*.json`.
- `packages/nx/assets.json`: dropped the `schemas/*.json` → `dist/` copy
step.
Verified via `npm pack --dry-run` — tarball now contains
`schemas/nx-schema.json`, `schemas/project-schema.json`,
`schemas/workspace-schema.json` at root, with no `dist/schemas/`
entries.
## Related Issue(s)
Fixes#35411
## Current Behavior
The patched Jest resolver does not redirect imports of `@nx/cypress`,
`@nx/maven`, `@nx/plugin`, or `@nx/vitest` to their source entrypoints,
so Jest runs against the built output for those packages.
## Expected Behavior
The patched Jest resolver redirects imports of `@nx/cypress`,
`@nx/maven`, `@nx/plugin`, and `@nx/vitest` to source, matching the
treatment of the other `@nx/*` packages.
## Current Behavior
Nx reads its own version through a package.json path derived from
__filename. When Nx internals are bundled into another tool, that
relative path can point outside the original package layout.
## Expected Behavior
Nx resolves its version through the exported nx/package.json
self-reference, so bundlers can resolve it statically and the runtime no
longer depends on the source/dist file layout.
## Related Issue(s)
N/A
## Current Behavior
When `@nx/maven` runs Maven targets in Nx batch mode, the Maven 3 batch
runner builds a `MavenExecutionRequest` and only calls
`MavenExecutionRequestPopulator.populateDefaults()`. That path injects
default remote repositories but does not merge user and global
`settings.xml` (mirrors, servers, proxies, profile repositories, etc.).
Resolution can ignore corporate mirrors and behave differently from the
`mvn` CLI and from non-batch execution.
## Expected Behavior
Batch mode should apply the same effective settings as the `mvn` CLI:
build settings with `SettingsBuilder`, then `populateFromSettings()`
before `populateDefaults()`. Global `settings.xml` should resolve the
same way as the launcher (`${maven.conf}/settings.xml`), so `maven.conf`
is set from `maven.home` when unset.
## Related Issue(s)
Fixes https://github.com/nrwl/nx/issues/34948
## Current Behavior
I noticed this while investigating a 6-minute hang in our ~2600-project
monorepo for any target that uses `run_command` (in our case, a
`check-types` target that runs `tsc --noEmit`). The hang happens after
Nx appears to have run all the tasks, as seen in github actions log with
the "timestamps" setting enabled:
<img width="1258" height="336" alt="image"
src="https://github.com/user-attachments/assets/14a66362-3253-4649-b750-a69d9baf7c1d"
/>
We've patched our Nx instance locally (`pnpm patch`) with the changes in
this PR and the hang is now gone for us. Offering this PR upstream in
case anyone else is affected.
What seems to be happening is that each `RunningNodeProcess` registers 4
process-level event handlers (`exit`, `SIGINT`, `SIGTERM`, `SIGHUP`) in
`addListeners()` but never removes them after the child process exits.
This causes two problems:
1. **`MaxListenersExceededWarning`** when more than ~10 `run-commands`
tasks execute in parallel
2. **Multi-minute synchronous hang at process exit** — when
`process.exit()` is called, Node.js runs every leaked `exit` handler
sequentially. Each one calls `treeKill()` on an already-dead PID, which
takes ~143ms. With thousands of tasks, this adds minutes of dead time at
the end of every CI run.
### Reproduction
Run any target using `nx:run-commands` on a monorepo with 1000+
projects. After "Successfully ran target..." prints, the process hangs
for several minutes before exiting.
### Measured impact
On our 2610-project monorepo:
- **2610 leaked exit handlers** × **~143ms each** = **~6.2 minutes** of
synchronous blocking after every `check-types` CI run
- Identified via `process.on('exit')` instrumentation showing each
handler calling `treeKill` on dead PIDs from
`RunningNodeProcess.addListeners` at `running-tasks.ts:522`
## Expected Behavior
Process exit handlers registered by `RunningNodeProcess` are cleaned up
when the child process exits. The Node.js process exits promptly after
task completion with no leaked listeners.
## Fix
Store the 4 signal/exit handlers as named references and remove them via
`process.removeListener()` when the child process exits or errors. This
is a minimal, targeted change — no new dependencies, no behavioral
changes to signal handling during task execution.
## Current Behavior
`nx run -p my-project -t test:unit` crashes with a `TypeError` instead
of running the target:
```
NX parsedArgs[PROJECT_TARGET_CONFIG]?.lastIndexOf is not a function
TypeError: parsedArgs[PROJECT_TARGET_CONFIG]?.lastIndexOf is not a function
at parseRunOneOptions (.../nx/src/command-line/run/run-one.js:105:44)
```
Two bugs are combining here:
1. `--target` / `-t` is never registered as a yargs option on the `run`
command (`withRunOneOptions` only declares `--project` and `--help`), so
the flag silently falls through into the overrides array and the target
value is lost.
2. With no positional value provided but flags present, yargs assigns
boolean `true` to the `project:target:configuration` positional that the
`run [project][:target][:configuration] [_..]` signature declares.
`parseRunOneOptions` then calls `.lastIndexOf(':')` on `true` and
crashes.
Workaround today is to escape the colon: `nx run my-project:test\:unit`.
That works but is non-obvious, and the flag-based form is what most
users reach for first.
## Expected Behavior
`nx run -p my-project -t test:unit` runs the `test:unit` target on
`my-project`. Escaped and long-form invocations continue to work.
Changes:
- Register `--target` / `-t` as a real yargs option in
`withRunOneOptions`, and add `-p` as an alias for `--project`.
- Guard the positional check in `parseRunOneOptions` with `typeof
parsedArgs[PROJECT_TARGET_CONFIG] === 'string'` so a non-string value
can never crash `.lastIndexOf`.
- Add unit tests for the flag-based invocation (short, long, and `=`
forms) and for the boolean-positional guard. Update `compareArgs` in
`command-object.spec.ts` to strip the new `p`/`t` alias keys when
comparing infix vs. `run` invocations.
Verified end-to-end against a reproduction workspace: `nx run -p
my-project -t test:unit` now runs successfully.
## Related Issue(s)
Fixes#35098
## Current Behavior
`nx init` error telemetry is largely opaque: ~22% of starts land in a
bare `Command failed: npm install` bucket, and ~5% record an empty
`errorMessage`. We can't tell what's actually going wrong. `nx connect`
has no start/error events at all — failures (missing remote, auth,
network) go untracked.
## Expected Behavior
Telemetry-only change. Child-process calls in init pipe stderr so the
captured output reaches the error payload; error events now include
`errorName` (from Node `e.code` or an extracted `E…`/`ERR_…` token like
`E404`, `ERESOLVE`, `EINTEGRITY`, `ERR_PNPM_*`) and the same env context
(`nodeVersion`, `os`, `packageManager`, `isCI`, `aiAgent`) as start
events. `toErrorString` fixes the empty-message bucket. `nx connect`
gains proper start/complete/error events.
No behavioral fixes — once the enriched data comes in we'll prioritize
real fixes by actual failure distribution.
## Related Issue(s)
Fixes NXC-4262
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
## Current Behavior
The `nx-dev:sitemap` task invokes `next-sitemap`, whose `ManifestParser`
reads four manifests from the `nx-dev:next:build` output
(`build-manifest.json`, `export-marker.json`, `prerender-manifest.json`,
`routes-manifest.json` under `.next/`). None of these are declared as
inputs on `sitemap`, so the reads show up as sandbox violations and the
sitemap cache key doesn't change when those manifests change.
## Expected Behavior
The four manifests are declared inputs via a single
`dependentTasksOutputFiles` entry, narrowly scoped to
`**/.next/{build-manifest,export-marker,prerender-manifest,routes-manifest}.json`
so unrelated `.next/` artifacts don't invalidate the sitemap cache. `nx
show target inputs nx-dev:sitemap --check` confirms all four paths
resolve as declared inputs.
## Current Behavior
The `@nx/js:prune-lockfile` executor only identifies workspace module
dependencies when the `package.json` version string starts with
`workspace:`, `file:`, or `link:`. npm workspaces reference sibling
packages using plain semver (e.g. `"@repo/schemas": "0.0.1"` or
`"@repo/schemas": "*"`), so those dependencies are never rewritten to
point at `workspace_modules/` and pruning fails for npm-based
workspaces.
## Expected Behavior
Workspace module dependencies are correctly identified and rewritten to
`file:./workspace_modules/<pkg>` regardless of package manager,
including npm with plain-semver versions.
The fix pulls the set of workspace packages from the project graph via
`getWorkspacePackagesFromGraph` and treats any dependency whose name
matches a workspace package as a workspace module, in addition to the
existing protocol-prefix checks.
A regression e2e test covers the npm plain-semver case (`"*"`): it
asserts the pruned `package.json` has its dependency rewritten to
`file:./workspace_modules/@scope/nodelib`. The existing npm test case
used `file:../<lib>` which matched the `file:` prefix check and would've
passed even without the fix, so it didn't actually cover the reported
scenario.
## Related Issue(s)
Fixes#33523
## Current Behavior
The Resources dropdown in the astro-docs header contained links
returning 404 on nx.dev:
- `/podcast`
- `/resources-library?*` -> `/resources?...`
Additionally, `/webinar` redirected to `/webinars` (canonical).
## Expected Behavior
- Podcasts entry removed (page no longer exists).
- Books / Case Studies / Whitepapers repointed to the new
`/resources?filterBy=...` URLs.
- Webinars points directly to `/webinars` (no redirect).
All 12 Resources dropdown links verified returning 200. Books link
clicked through from the local astro-docs dropdown and confirmed to load
the Resources Library page.
## Current Behavior
The project's eslint config ignores common generated directories
(`dist/`, `.astro/`, `.netlify/`, ...) but not
`astro-docs/src/content/banner.json`. That file is a build artifact
produced at prebuild time by `astro-docs:prebuild-banner` (fetches
banner config from a remote URL and writes it to disk) and is
`.gitignore`d. The root eslint config registers `jsonc-eslint-parser`
for `**/*.json`, so `eslint .` walks into this generated file and parses
it — wasted work, and its content is irrelevant to lint correctness.
As a side effect, the undeclared read also shows up as a sandbox
violation for `astro-docs:lint` because Nx's `{projectRoot}/**/*` input
expansion (correctly) excludes gitignored files.
## Expected Behavior
`astro-docs/eslint.config.mjs` excludes the generated banner file
alongside the other build-artifact paths it already ignores, so eslint
no longer reads it. The sandbox violation disappears as a consequence.
## Current Behavior
`@nx/js:typescript-sync` always materializes every project-graph edge of
a project as a TypeScript project reference in the corresponding runtime
tsconfig. The existing `nx.sync.ignoredReferences` opt-out keeps
user-authored reference paths from being pruned, but there is no way to
tell the generator "don't add a reference for this dependency in the
first place."
That becomes a problem when the project graph contains intentional
cycles — for example when `@nx/workspace` declares its lazy-loaded
plugin peers (`@nx/js`, `@nx/angular`, etc.) as optional peers, and
those same plugins depend back on `@nx/workspace`. Materializing both
edges as TS project references produces `TS6202: Project references may
not form a circular graph`. The only workaround today is
`implicitDependencies: ["!name", …]` in `project.json`, which removes
the edge from the project graph entirely and hides it from every other
consumer (dependency tooling, graph visualizations, supply-chain
audits).
## Expected Behavior
Tsconfig files now accept an `nx.sync.ignoredDependencies: string[]`
field (sibling of the existing `nx.sync.ignoredReferences`). When the
sync generator processes that tsconfig, any project-graph dependency
whose project name is in the set is skipped — no new reference is added
for it, and any existing reference for that dependency is pruned as
stale.
This lets a workspace keep real, cyclic project-graph edges (so the
package.json peer relationships stay visible) while opting the affected
tsconfig out of materializing the cycle into project references. The
task graph is kept acyclic separately via explicit `dependsOn` entries.
Co-authored-by: Claude <noreply@anthropic.com>
## Current Behavior
`nx-dev:build` is a no-op aggregator over `sitemap` and `copy-redirects`
but only declares `{projectRoot}/.next` as its output. Tasks that depend
on `nx-dev:build` and use `dependentTasksOutputFiles` — e.g.
`astro-docs:validate-links`, which reads
`nx-dev/nx-dev/public/sitemap-0.xml` to cross-check links — never pick
up the sitemap as a declared input. Under sandboxing this surfaces as an
unexpected read, and it also means the sitemap doesn't participate in
the consumer's input hash.
Separately, `nx-dev:next:build` lists `public/sitemap*.xml` as an output
even though `next build` never writes sitemaps (the `sitemap` target
does). At best the glob captures nothing; at worst, on a re-run it
snapshots stale files from a previous build into `next:build`'s cache.
## Expected Behavior
`nx-dev:build` and `nx-dev:deploy-build` declare
`{projectRoot}/public/sitemap*.xml` alongside `{projectRoot}/.next`,
matching what their dependsOn chain actually produces. This mirrors the
existing `nx:noop` atomizer pattern used by `@nx/playwright` and
`@nx/cypress`, where the rollup target declares the superset of outputs
its children produce.
`nx:next:build` no longer claims an output it doesn't write.
Verified: `nx show target inputs astro-docs:validate-links --check
nx-dev/nx-dev/public/sitemap-0.xml nx-dev/nx-dev/public/sitemap.xml` now
reports both as inputs.
## Current Behavior
Running `nx test dotnet` resolves `@nx/dotnet/*` specifiers to compiled
`dist/*.js` files instead of TypeScript source.
`scripts/patched-jest-resolver.js` maintains a `workspacePackages`
allowlist of `@nx/*` packages that should route to their TS source
during tests. `@nx/dotnet` is missing from that list, so the resolver
falls through to `enhanced-resolve`, which honors
`packages/dotnet/package.json#exports` — and those entries all point at
`./dist/*.js`.
Every other `@nx/*` plugin in the repo is on the allowlist, so this is
specific to `@nx/dotnet`.
## Expected Behavior
`nx test dotnet` resolves `@nx/dotnet/*` imports to TypeScript source
files under `packages/dotnet/src/` like every other workspace package,
so tests exercise the current source and don't require a prior build.
## Related Issue(s)
<!-- None; internal dev loop fix surfaced while running the dotnet test
suite. -->
The generate-workspace-files spec wrote each rendered README to
`__dirname/tmp/<preset>-<nxCloud>/README.md` via raw fs, landing inside
the source tree and triggering Nx sandbox violations during `nx test
workspace`. The toMatchSnapshot assertion that followed was always the
real check; the filesystem writes were leftover debug scaffolding from
the bulk README regeneration in #27038.
Also drops the now-unused `fs` and `path` imports.
## Summary
- avoid prepending `outputPath` twice when `@nx/js:node` derives the
runnable file for `@nx/js:tsc` and `@nx/js:swc`
- keep the existing source-relative behavior for normal `src/...`
entries while preserving nested paths already inside the output
directory
- respect a configured `rootDir` on the build target so the node
executor does not add an extra path segment that tsc/swc stripped from
the output
- reuse the canonical `normalizePath` and
`getRelativeDirectoryToProjectRoot` utilities instead of re-declaring
them
- add focused coverage for the `dist/main.js`, source-entry,
nested-output, and `rootDir` cases
Fixes#35044Fixes#33577
## Validation
- `corepack pnpm exec prettier --check
packages/js/src/executors/node/node.impl.ts
packages/js/src/executors/node/lib/output-file.ts
packages/js/src/executors/node/lib/output-file.spec.ts`
- `corepack pnpm exec eslint packages/js/src/executors/node/node.impl.ts
packages/js/src/executors/node/lib/output-file.ts
packages/js/src/executors/node/lib/output-file.spec.ts`
- `pnpm nx test js --testPathPatterns=output-file` — all 4 cases pass
---------
Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
## Current Behavior
Running `nx run rollup:test` produces three unexpected writes to the
workspace root that are not declared as outputs:
- `dist/index.js/index.cjs.default.js`
- `dist/index.js/index.cjs.mjs`
- `dist/index.js/package.json`
These show up as sandbox violations because the spec file at
`packages/rollup/src/plugins/package-json/update-package-json.spec.ts`
is, in reality, writing to the filesystem during test runs:
1. `jest.spyOn(utils, 'writeJsonFile')` is used without
`.mockImplementation(...)`. `spyOn` wraps the function to record calls
but does not replace its behavior, so the real `writeJsonFile` runs and
writes `package.json`.
2. `update-package-json.ts` also calls `fs.writeFileSync` directly (for
the `.cjs.mjs` / `.cjs.default.js` CJS-interop shims), which the spec
never mocked at all.
Every run pollutes the workspace root with a `dist/index.js/` directory.
## Expected Behavior
Tests verify call arguments via the spies without touching the real
filesystem. No `dist/index.js/` directory is created, and the three
sandbox violations for `rollup:test` go away.
- All 11 `writeJsonFile` spies now use `.mockImplementation(() =>
undefined)`.
- A `beforeEach` / `afterEach` adds a `jest.spyOn(fs,
'writeFileSync').mockImplementation(() => undefined)` using
`require('fs')` so the spy lands on the real fs module (using `import *
as fs from 'fs'` would go through `esModuleInterop`'s `__importStar`
copy and miss the call site in `update-package-json.ts`).
- Assertions continue to work — `toHaveBeenCalledWith(...)` tracks calls
on spies with or without a mock implementation.
Verified: `nx test rollup --testPathPatterns=update-package-json.spec` →
11/11 passing, and no `dist/index.js/` is created during the run.
## Related Issue(s)
N/A — caught via the sandbox-violation report for `rollup:test`.
## Current Behavior
When running \`nx release version --preid rc\` with a project filter,
projects with a dependent patch bump (from another project being bumped)
do not have the preid applied. semver.inc('1.0.0', 'patch', 'rc')
returns '1.0.1' because semver silently ignores preid for non-prerelease
specifiers.
## Expected Behavior
The preid is applied to dependent patch bumps, so dependents get
'0.0.2-rc.0' instead of '0.0.2'. The fix adds `applyPreidToBumpType`
which converts patch to prepatch, minor to preminor, and major to
premajor when a preid is set.
## Related Issue(s)
Fixes#33488
## Current Behavior
Several `ensurePackage('@nx/X', nxVersion)` and `ensurePackage<typeof
import('@nx/X')>(...)` references are invisible to Nx's project-graph
source analysis, so the corresponding workspace edges are missing today:
-
`packages/storybook/src/generators/configuration/lib/util-functions.ts`
lazy-loads `@nx/web`
- `packages/web/src/generators/application/application.ts` lazy-loads
`@nx/cypress`, `@nx/eslint`, `@nx/jest`, `@nx/playwright`, `@nx/vite`,
`@nx/webpack`
- `packages/vitest/src/utils/ignore-vitest-temp-files.ts` lazy-loads
`@nx/eslint`
- `packages/vue/src/**` lazy-loads `@nx/cypress`, `@nx/playwright`,
`@nx/rsbuild`, `@nx/storybook`
This means tasks in those packages read files from their lazy-loaded
dependencies at module-load time without declaring them as inputs — the
motivation behind the sandbox-violation work in #35377.
## Expected Behavior
Declares each lazy-loaded package as an optional `peerDependency` with
`peerDependenciesMeta.*.optional: true`.
`explicit-package-json-dependencies` walks `peerDependencies` alongside
`dependencies`, materializing the edges in the graph. `optional: true`
keeps them off the user's install surface — package managers don't
auto-install optional peers and don't warn when they're missing.
`@nx/vitest` is already declared as a `devDependency` of `@nx/web`, so
the `web → vitest` edge is already present; no change needed there.
Follows the pattern established in #35377 (`@nx/eslint → @nx/jest`).
### Scope
Narrower subset of #35392, scoped to `@nx/storybook`, `@nx/web`,
`@nx/vitest`, and `@nx/vue`. Those four close the most commonly hit
transitive lazy-load chains (`storybook → web → jest`, `vue → storybook
→ web → jest`, `web → vitest → eslint`, etc.) without needing the
`implicitDependencies: ["!name", ...]` cycle negations that
`@nx/workspace` and `@nx/js` require in #35392.
Note: `@nx/vitest` is not covered by #35392 at all, so this PR adds at
least one edge that isn't in the broader PR.
### Verification
- Regenerated project graph locally — all new edges appear as `static`
type:
- `storybook → web`
- `web → {cypress, eslint, jest, playwright, vite, webpack}`
- `vitest → eslint`
- `vue → {cypress, playwright, rsbuild, storybook}`
- Full cycle scan: **0 cycles introduced**.
- `nx prepush` passes cleanly.
## Related Issue(s)
<!-- No open issue; follow-up to #35377 and narrower alternative to
#35392 -->
## Current Behavior
`create-nx-workspace` randomly serves one of three Nx Cloud prompt copy
variants during the template flow; `nx init` pins a baseline copy that
predates the test.
A/B results (NXC-4336):
- Variant 0 (baseline): `Enable remote caching to speed up builds with
Nx Cloud?` — 15.4% yes / 29.2% never
- Variant 1: `Never rebuild the same code twice — enable Nx Cloud?` —
13.4% yes / 28.4% never
- **Variant 2: `Speed up GitHub Actions, GitLab CI, and more with Nx
Cloud?` — 17.8% yes / 22.4% never**
## Expected Behavior
Both `create-nx-workspace` (`setupNxCloudV2`) and `nx init`
(`setupNxCloud`) serve the winning variant. Footer is reworded to lead
with the free-tier messaging:
> Free for small teams. Remote caching and task distribution. 2-minute
setup: https://nx.dev/nx-cloud
The CNW `setupNxCloudV2` array collapses from 3 variants to 1;
`PromptMessages.getPrompt` already falls back to index 0 when
`flowVariant >= length`, so existing flow-variant plumbing keeps
working. Spec updated to assert the locked-in code for all flow variants
(0/1/2) and docs generation.
## Related Issue(s)
Fixes NXC-4336
## Current Behavior
Three devkit specs exercise helpers that do `await
import('@nx/<plugin>/plugin')` at runtime (`findPluginForConfigFile`
when a registration has `include`/`exclude`, and
`addE2eCiTargetDefaults` unconditionally for every e2e plugin
registration). Combined with the custom jest resolver in
`scripts/patched-jest-resolver.js` — which maps `@nx/<pkg>` subpaths to
workspace source — the dynamic imports pull the real plugin source plus
everything transitively re-exported by `@nx/js` and `@nx/vite` into the
jest process.
Concretely, `devkit:test` ends up reading ~49 files across
`packages/js/src/**` and `packages/vite/**` that are not declared (and
cannot be declared without creating a project-graph cycle since `@nx/js`
and `@nx/vite` both depend on `@nx/devkit`). The sandbox flags all 49 as
undeclared-read violations.
## Expected Behavior
None of these specs aim to validate the real plugin's behavior. They
exercise devkit's own logic — how a registration is matched to a config
file, how target defaults are written into `nx.json`, and how e2e web
server info is resolved from a registered plugin — treating
`@nx/<plugin>/plugin` as an opaque reference. The plugin's
`createNodesV2[0]` glob is used by the devkit helpers only as a pattern
pre-filter against conventional config filenames (`vite.config.ts`,
`cypress.config.ts`); what the real plugin does beyond that is outside
the scope of these tests.
Stubbing the three plugin modules with `jest.mock(..., { virtual: true
})` that exposes just the real plugin's glob pattern:
- Preserves the minimal contract the devkit helpers rely on (the module
resolves, and its glob matches the conventional config filenames used in
the tests).
- Drops the incidental loading of the real plugin and its entire
transitive graph — removing all 49 reported sandbox violations.
- Eliminates an accidental coupling to the pinned `@nx/cypress` version
currently pulled from `node_modules`, which also transitively requires
`@nx/js` workspace source via the custom resolver.
Changes are test-only; no production code is touched.
## Current Behavior
`update-repos` spawns child processes to run `pnpm install`, `nx
migrate`, etc. inside each cloned target repo. The child inherits the
launching shell's `PATH`, which mise activation populated with hardcoded
version-specific install paths (e.g. `/.../installs/node/24.11.0/bin`).
Mise activation is a shell hook on `cd` — it does not re-fire when a
child process changes its own `cwd`. As a result, every cloned repo's
commands run against the outer shell's pinned tool versions rather than
the versions in the cloned repo's own `mise.toml`.
## Expected Behavior
Each cloned repo's commands honor the tool versions pinned in that
repo's `mise.toml`.
`execWithOutput` now prefixes every spawned command with `mise exec --`
(except commands that already start with `mise`, so `mise trust` / `mise
install` aren't wrapped in themselves). `mise exec` re-reads `mise.toml`
from the `cwd` at invocation time and activates the correct tool
versions for that repo.
## Related Issue(s)
Fixes #
## Current Behavior
`NX_*` env vars can be added to Nx source without being documented in
`astro-docs/src/content/docs/reference/environment-variables.mdoc`.
Nothing catches the drift.
## Expected Behavior
Adds a conformance rule (`env-vars-documented`) that fails when an
`NX_*` var is read in source but missing from the docs. Covers TS/JS
`process.env.NX_*` and Rust `env::var` / `env!` patterns, skipping tests
and fixtures. An `ignore` list in `nx.json` handles internal markers not
meant to be documented.
Also documents the user-facing vars the first run surfaced (self-hosted
cache, provenance, plugin isolation, Nx Cloud timeouts, etc.) and marks
`NX_CLOUD_AUTH_TOKEN` and `NX_CLOUD_DISTRIBUTED_EXECUTION_AGENT_COUNT`
as deprecated.
## Related Issue(s)
N/A — internal tooling improvement.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: barbados-clemens <barbados-clemens@users.noreply.github.com>
This pull request contains a small fix in the `build-project-graph.ts`
file to correct the way `Set` is initialized for tracking in-progress
plugins. The change removes the unnecessary spread operator when
creating the `Set` from plugin names.
* Fixed `inProgressPlugins` initialization in both
`updateProjectGraphWithPlugins` and `applyProjectMetadata` functions by
removing the spread operator, ensuring plugin names are correctly added
to the `Set`.
[[1]](diffhunk://#diff-14bd07dde50d74ec748f1bce0f9d8cf05504e36c83928d1ad077dcff088b6822L323-R323)
[[2]](diffhunk://#diff-14bd07dde50d74ec748f1bce0f9d8cf05504e36c83928d1ad077dcff088b6822L441-R441)
## Current Behavior
`packages/eslint/src/generators/workspace-rules-project/workspace-rules-project.ts:37`
calls `ensurePackage<typeof import('@nx/jest')>('@nx/jest', nxVersion)`.
This runtime-optional reference (type-only import plus a string
argument) is invisible to Nx's static project-graph analysis, so the
graph has no `@nx/eslint → @nx/jest` edge today.
Combined with the custom jest resolver in
`scripts/patched-jest-resolver.js` — which maps `@nx/jest` subpaths to
workspace source — the `eslint:test` task ends up reading
`packages/jest/index.ts` and 19 files under `packages/jest/src/**` at
module-load time. Those reads are undeclared inputs and are flagged as
sandbox violations by the staging sandbox reports.
## Expected Behavior
Declaring `@nx/jest` as an **optional** `peerDependency` of `@nx/eslint`
materializes the missing edge in Nx's project graph (the graph reader in
`explicit-package-json-dependencies.ts` walks `peerDependencies`
alongside `dependencies`). The inferred test-task input `^production` is
transitive, so `packages/jest/**` production source is then covered as
declared inputs of `eslint:test` and the 20 violations disappear.
`peerDependenciesMeta.@nx/jest.optional: true` keeps `@nx/jest` off the
user's install surface — package managers don't auto-install it and
don't warn about missing optional peers. Users who invoke the
`workspace-rules-project` generator with jest scaffolding still get
`@nx/jest` via the existing `ensurePackage` runtime install path,
unchanged.
## Current Behavior
`@react-router/dev` peer dep tops out at Vite 7, so the React app
generator forces Vite 7 when `--use-react-router` is passed, and the
react-router typecheck e2e test is skipped in CI.
## Expected Behavior
`@react-router/dev` 7.14.2 expands its peer dep to include Vite 8
(`^5.1.0 || ^6.0.0 || ^7.0.0 || ^8.0.0`), so:
- `useViteV7: true` is no longer forced when generating a React Router
app
- The `useViteV7` field is removed from
`ViteConfigurationGeneratorSchema` (was only added for this workaround
and was never wired into the configuration generator body)
- The react-router typecheck e2e test is un-skipped
- `reactRouterVersion` is bumped to `^7.14.2`
- A new `22.7.0` packageJsonUpdate migrates `@react-router/*` packages
to `7.14.2` in existing workspaces
## Related Issue(s)
Fixes NXC-4182
## Current Behavior
When using @nx/next:build with Yarn PnP, the generated
.nx-helpers/with-nx.js requires semver at runtime, but semver is not
included in the generated package.json. This causes the application to
fail when starting in a deployed environment with the error: "Required
package: semver, Required by: /app/.nx-helpers/with-nx.js"
## Expected Behavior
The generated package.json should include semver as a dependency so that
.nx-helpers/with-nx.js can resolve it at runtime in all package manager
environments, including Yarn PnP.
## Related Issue(s)
Fixes#34095
allow setting a timeout for nx agents based on the last time output was
emitted.
By default this is 10m. this is set per launch template but can be
overridden per step via env var: `NX_NO_OUTPUT_TIMEOUT`
fixes DOC-488
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
The nx repo uses the legacy eslintrc format (`.eslintrc.json`,
`.eslintignore`) with ESLint v8 and outdated plugin versions. Several of
those plugins ship code that crashes under ESLint v9 (`context.getScope`
removed, etc.), and the `@nx/eslint` / `@nx/eslint-plugin` sources still
reference APIs removed in v9 (`Linter.defineParser`,
`Linter.defineRule`, classic `Linter.Config` shape).
## Expected Behavior
Every project in the repo uses an `eslint.config.mjs` flat config, the
ESLint ecosystem is on the latest v9-compatible versions, and
`@nx/eslint` / `@nx/eslint-plugin` sources compile and test cleanly
against the v9 type split.
### Main changes
- Replaced every `.eslintrc.json` / `.eslintignore` with an
`eslint.config.mjs`.
- Rewrote the root `eslint.config.mjs`:
- Exports a named `baseConfig` that leaf configs import, plus a default
export that opts out of linting (`**/*` ignored) so the root acts as a
shared base and isn't lint-checked directly.
- Drops `FlatCompat` in favor of native plugin imports.
- Exports shared `reactHooksV7Off`, `e2eTestOnlyIgnores`, and a
`storybookConfigs` wrapper that filters v10's preset down to
`storybook/*` rules (the preset references foreign rules like
`import-x/*` that we don't register).
- Cleaned up leaf configs emitted by the generator: deduplicated file
entries, merged split `package.json` blocks, fixed the jsonc parser
namespace import after v3 dropped its default export.
- Scoped the 15 e2e projects to `*.test.ts` files only via a shared
export.
- Bumped the ESLint ecosystem to the latest v9-compatible versions:
`eslint@^9.39.4`, `@eslint/js@^9.39.4`, `@typescript-eslint/*@^8.58.2`,
`eslint-plugin-storybook@^10.3.5`, `eslint-plugin-react-hooks@^7.1.0`,
`eslint-plugin-jsx-a11y@^6.10.2`, `eslint-plugin-import@^2.32.0`,
`eslint-plugin-playwright@^2.10.1`, `eslint-plugin-cypress@^6.3.1`,
`eslint-plugin-jest@^29.15.2`, `toml-eslint-parser@^1.0.3`,
`jsonc-eslint-parser@^3.1.0`, `angular-eslint@^21.3.1`. Dropped
`@eslint/eslintrc`, `@types/eslint`, `@types/eslint__js`.
- Aligned `@nx/eslint` source with the v9 type split: `Linter.Config` →
`Linter.LegacyConfig`, `ESLint.Options` → `ESLint.LegacyOptions`
wherever the underlying shape is eslintrc.
- Rewrote `@nx/eslint-plugin` rule specs (`dependency-checks`,
`enforce-module-boundaries`) as flat-config inline tests since
`Linter.defineParser`/`defineRule` were removed in v9.
- Aligned react/angular/expo/react-native add-linting helpers with the
v9 type split.
- Migrated `tools/eslint-rules` specs from `TSESLint.RuleTester` (legacy
config, rejected by v9's flat Linter) to
`@typescript-eslint/rule-tester`; added `isolatedModules: true` so
ts-jest resolves the new types under `module: node16`.
- Downgraded the new react-hooks v7 rules to `off` via a shared export
so the migration doesn't require rewriting legacy code.
- Auto-fixed unused eslint-disable directives
(`linterOptions.reportUnusedDisableDirectives` defaults to warn in v9).
- Ignored `packages/workspace/**/__fixtures__/**` in lint and updated
the affected snapshot so the fixture matches what the jest generator
templates actually emit.
### Note on ESLint v10
This PR stays on ESLint v9. A few plugins we rely on
(`eslint-plugin-import`, `eslint-plugin-jsx-a11y`,
`eslint-plugin-react`) still don't declare v10 peer support. The jump to
v10 will happen in a follow-up PR once those plugins publish
v10-compatible releases.
## Current Behavior
`isUsingTsSolutionSetup()` (in both `@nx/js` and `@nx/workspace`) falls
back to `new FsTree(workspaceRoot, false)` when called without a tree,
reading the real repo's `tsconfig.json` / `tsconfig.base.json`. Many
unit tests indirectly invoke it (cypress-preset, playwright-preset,
plugin `createNodesV2`, executor `normalize`, etc.), which surfaces as
sandbox violations across ~13 test targets (angular, rspack, vite,
rollup, webpack, react, next, js, cypress, remix, workspace, nest,
node).
## Expected Behavior
Unit tests should never touch the real workspace FS. A global mock in
`scripts/unit-test-setup.js` short-circuits `isUsingTsSolutionSetup()`
when called without a tree, returning `true` to match the de-facto
behavior of hitting the real FS (the Nx repo is a TS solution workspace)
and preserve every test's existing expectations. Calls that pass an
explicit (virtual) tree still run the real implementation.
Two specs that deliberately simulate a non-TS-solution workspace (via
`node:fs` / `workspaceRoot` mocks) add a per-file override returning
`false` to keep expressing that intent:
- `packages/vite/src/plugins/plugin-vitest.spec.ts`
- `packages/rollup/src/plugins/with-nx/normalize-options.spec.ts`
## Current Behavior
`.claude/settings.json` enables `polygraph@nx-claude-plugins`, but the
`nx-claude-plugins` marketplace only ships the `nx` plugin. The
`polygraph` plugin lives in the separate `polygraph-plugins`
marketplace, which is not declared in `extraKnownMarketplaces` either —
so the entry refers to a plugin+marketplace pair that doesn't exist.
Every contributor picks up the broken id when they pull the repo.
The bad id was introduced in #34790, which moved to the new plugin but
left the marketplace name pointing at the old one.
## Expected Behavior
Polygraph is now installed globally via the session-start opt-in prompt,
so the repo-level `enabledPlugins` entry is no longer needed. Remove it
entirely rather than pointing it at a marketplace the settings file
doesn't declare.
## Current Behavior
The createNodesV2 function in the nx/cypress plugin calculates the hash
based only on the output of the standard `calculateHashForCreateNodes`.
This means that when multiple Cypress configuration files exist within a
single application hash. As a result, only one set of inferred targets
is generated, and the additional configuration variants are effectively
ignored.
## Expected Behavior
The createNodesV2 function now takes the configuration file name into
the hash calculation, removing the limitation of a single config within
an application.
---------
Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
## Current Behavior
The scheduled `NPM Audit` workflow is failing on `master` due to a
critical advisory
([GHSA-xq3m-2v4x-88gg](https://github.com/advisories/GHSA-xq3m-2v4x-88gg))
in `protobufjs@7.5.3`, pulled in transitively via:
```
@grafana/faro-web-sdk > @grafana/faro-core > @opentelemetry/otlp-transformer > protobufjs
```
Failing run: https://github.com/nrwl/nx/actions/runs/24753360724
Separately, the workspace carries a `@nx/jest@22.7.0-beta.12` patch even
though the workspace is on `22.7.0-beta.16`, and `allowUnusedPatches:
true` was set in `pnpm-workspace.yaml` to suppress the warning.
## Expected Behavior
- `NPM Audit` workflow passes.
- No stale patches, and unused patches fail loudly rather than being
silently allowed.
### Changes
1. **Remove `@grafana/faro-web-sdk` and `@grafana/faro-web-tracing`.** A
`git grep` confirms neither package is imported anywhere in the codebase
— they were listed in `package.json` but unused. Removing them drops the
transitive `protobufjs@7.5.3` entirely and clears the critical advisory
(audit verified locally).
2. **Delete the stale `@nx/jest@22.7.0-beta.12` patch and drop
`allowUnusedPatches: true`** from `pnpm-workspace.yaml` so future stale
patches surface immediately.
## Related Issue(s)
N/A (fixes failing scheduled CI workflow).
Add sitemap for blog:
- https://deploy-preview-35363--nx-dev.netlify.app/sitemap.xml
- https://deploy-preview-35363--nx-dev.netlify.app/sitemap-2.xml
(rewritten to blog)
## Current Behavior
The root sitemap.xml index references only /sitemap-1.xml (Framer via
edge-function proxy) and /docs/sitemap-index.xml (astro-docs). Blog
posts published by nx-blog (BLOG_URL) are not advertised to crawlers
through the root nx.dev sitemap.
## Expected Behavior
The root sitemap index additionally references /sitemap-2.xml, which is
served by a consolidated `additional-sitemaps.ts` edge function that
proxies the per-source sitemaps:
/sitemap-1.xml -> <NEXT_PUBLIC_FRAMER_URL>/sitemap.xml
/sitemap-2.xml -> <BLOG_URL>/blog/sitemap.xml
URLs in the upstream XML are rewritten from the source origin to nx.dev.
The previous per-source edge functions (framer-sitemap.ts,
blog-sitemap.ts) are replaced by the single additional-sitemaps.ts to
match the "additionalSitemaps" language used in the Next.js sitemap
config.
## Related Issue(s)
Fixes DOC-486
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
The Maven analyzer prints `[Maven Analyzer] Starting analysis with
options: { verbose: false }` unconditionally to stdout. This corrupts
commands that expect clean stdout output, such as `nx show projects
--json`, where consumers end up parsing:
```
[Maven Analyzer] Starting analysis with options: { verbose: false }
["@somnex/krista","@somnex/somnex"]
```
## Expected Behavior
The startup message should only be shown in verbose mode, matching the
rest of the analyzer diagnostics in this file that already use
`logger.verbose`. Normal `nx show projects --json` output stays clean.
## Related Issue(s)
Fixes NXC-4253
## Current Behavior
On process segfault there is no way at all to diagnose where it is
coming from
## Expected Behavior
On segfault we write a report file that has information in it and log
that info on the next nx command if one happens to be ran.
Because segfaults exit the node process immediately, we do not have
another way of handling them so we do have to rely on a follow up
command to surface them. Unfortunately the errors are pretty rare so the
likelihood of this helping isn't awesome, but it could give us some
info.
## AI summary
This pull request introduces a new utility for surfacing Node.js fatal
error diagnostic reports and refactors how project graph data is
accessed in the format command. The most significant changes are the
addition of the fatal error reporting mechanism and the simplification
of how project graph data is loaded, which should improve
maintainability and reliability.
**Fatal error reporting utility:**
* Added a new `surfaceFatalErrorReports` function in
`report-on-fatal-error.ts` that scans for Node.js fatal error reports in
the workspace data directory, surfaces them in the console, and emits
GitHub Actions workflow annotations and step summaries if running in CI.
Reports are renamed after processing to prevent duplication.
**Format command refactoring:**
* Refactored `format.ts` to remove unused imports (`FileData`,
`allFileData`) and to only load the project graph when necessary, which
reduces unnecessary file system and computation overhead.
[[1]](diffhunk://#diff-ff26ab1495d5bd9854f0377c1aad51b1a749fa0ea82d2c66d363c06d417ebc67L6-R6)
[[2]](diffhunk://#diff-ff26ab1495d5bd9854f0377c1aad51b1a749fa0ea82d2c66d363c06d417ebc67L23)
* Updated the logic in `getPatterns` and related functions to lazily
load the project graph and to remove the need for passing all workspace
files, simplifying function signatures and improving performance.
[[1]](diffhunk://#diff-ff26ab1495d5bd9854f0377c1aad51b1a749fa0ea82d2c66d363c06d417ebc67L104)
[[2]](diffhunk://#diff-ff26ab1495d5bd9854f0377c1aad51b1a749fa0ea82d2c66d363c06d417ebc67R111)
[[3]](diffhunk://#diff-ff26ab1495d5bd9854f0377c1aad51b1a749fa0ea82d2c66d363c06d417ebc67L135-R138)
[[4]](diffhunk://#diff-ff26ab1495d5bd9854f0377c1aad51b1a749fa0ea82d2c66d363c06d417ebc67L155)
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
The `@nx/js:node` executor fails when combined with the inferred
`@nx/js/typescript` build target — in two distinct ways:
1. **Script-to-run resolution**: fails with `Could not find
<project>/dist/**/*.{js,cjs,mjs,jsx,d.ts,d.cts,d.mts}{,.map}/main.js.
Make sure your build succeeded.` — the executor's `getFileToRun` was
appending `main.js` onto the glob output pattern.
2. **Buildable-dep import resolution**: when the node app imports a
buildable lib that also uses the inferred build target,
`require('@scope/my-lib')` throws `MODULE_NOT_FOUND` because
`calculateResolveMappings` passes the literal glob into `NX_MAPPINGS`,
which the CJS require override / ESM loader then tries to resolve.
Both regressed after #35041 narrowed the inferred build target's
`outputs[0]` from `{projectRoot}/dist` to
`{projectRoot}/dist/**/*.{js,...}{,.map}` (to prevent cross-OS cache
pollution).
## Expected Behavior
`outputs` entries are cache patterns and may legitimately contain globs.
The node executor now strips the glob portion back to the last path
separator before using the value as a directory, in both `getFileToRun`
and `calculateResolveMappings`. Handles `**`, `*`, `?`, character
classes, brace expansion, extglob, and Windows/POSIX separators.
## Related Issue(s)
Fixes#35198Fixes#35301
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The `astro-docs:format` target runs `prettier **/*.mdoc --check`.
Prettier has `--editorconfig` enabled by default and walks up from each
source file to read `.editorconfig`, deriving options like `endOfLine`,
`tabWidth`, `useTabs`, and `printWidth` from it. The target's declared
inputs do not include `.editorconfig`, so the task sandbox flags it as
an unexpected read and changes to `.editorconfig` do not invalidate the
cache even though they can change `--check` results.
## Expected Behavior
`.editorconfig` is declared as an input of the `astro-docs:format`
target so the sandbox recognizes the read and the cache key reflects
changes to the file.
## Current Behavior
The `@nx/js/typescript` plugin's inferred `typecheck` target only
declares `.d.ts`/`.d.ts.map` as outputs (in the `emitDeclarationOnly`
branch) and only `.d.ts` in the dependency/dependent-task input globs.
When a project has `.mts` or `.cts` sources, `tsc` emits
`.d.mts`/`.d.mts.map` (and `.d.cts`/`.d.cts.map`) declaration files that
fall outside the declared outputs, causing sandbox violations and missed
cache inputs from upstream projects that produce those files.
## Expected Behavior
Declaration file globs cover all three TypeScript declaration extensions
(`.d.ts`, `.d.cts`, `.d.mts`) consistently across inputs and outputs,
matching what `tsc` actually emits for `.ts`/`.cts`/`.mts` sources.
Changes in `packages/js/src/plugins/typescript/plugin.ts`:
- `getOutputs` (`emitDeclarationOnly` branch): `**/*.d.ts{,.map}` →
`**/*.{d.ts,d.cts,d.mts}{,.map}`
- `getInputs` `dependentTasksOutputFiles`: `**/*.{d.ts,tsbuildinfo}` →
`**/*.{d.ts,d.cts,d.mts,tsbuildinfo}`
- `getInputs` dependencies fileset: `{projectRoot}/**/*.d.ts` →
`{projectRoot}/**/*.{d.ts,d.cts,d.mts}`
The non-`emitDeclarationOnly` branch already used the full extension
set, so this brings the other paths in line.
## Current Behavior
Direct `nx:run-commands` tasks on the non-PTY path can forward `Buffer`
chunks into the TUI lifecycle after the recent `exec()` to `spawn()`
change. When native progressive output handling receives that value,
task execution can fail with `StringExpected` instead of surfacing the
underlying command output.
## Expected Behavior
Spawned `run-commands` output is decoded to UTF-8 strings before it
reaches progressive output consumers, so TUI rendering can continue
streaming command output without crashing.
## Current Behavior
On aarch64 Linux kernels with page sizes larger than 4 KiB — 16 KiB on
Asahi Linux / Apple Silicon, 64 KiB on some Ampere/Fedora server configs
— every Nx command (including `nx --version`) aborts immediately:
```
<jemalloc>: Unsupported system page size
<jemalloc>: arena_new: allocation failed
memory allocation of 576 bytes failed
Aborted (core dumped)
```
The cause: jemalloc is compiled with `--with-lg-page=12` (4 KiB
allocator page) by default, and refuses to operate when the system page
size exceeds the compiled-in value.
## Expected Behavior
Nx native binaries run correctly on every shipping aarch64 Linux kernel
(4 KiB, 16 KiB, 64 KiB pages), without losing the performance benefits
of jemalloc on the common 4 KiB-page configurations (Graviton, cloud
ARM, Raspberry Pi OS, Debian/Ubuntu ARM).
The fix: build `@nx/nx-linux-arm64-gnu` and `@nx/nx-linux-arm64-musl`
with `JEMALLOC_SYS_WITH_LG_PAGE=16` (64 KiB allocator page). jemalloc's
documented contract is `allocator_page_size ≥ system_page_size`, so a
binary built this way runs on any aarch64 Linux kernel with page size ≤
64 KiB — which covers all known configurations.
## Prior art
The same `JEMALLOC_SYS_WITH_LG_PAGE=16` approach is used by:
- **rustc** —
[rust-lang/rust#145353](https://github.com/rust-lang/rust/pull/145353):
`cargo.env("JEMALLOC_SYS_WITH_LG_PAGE", "16")` for aarch64 targets in
`src/bootstrap/src/core/build_steps/tool.rs`
- **swc** —
[swc-project/swc#6541](https://github.com/swc-project/swc/pull/6541):
`export JEMALLOC_SYS_WITH_LG_PAGE=16` in the publish workflow for both
aarch64 gnu and musl rows
- **fd** — [sharkdp/fd#1547](https://github.com/sharkdp/fd/pull/1547)
and follow-up [#1549](https://github.com/sharkdp/fd/pull/1549) moving
the env var into `Cross.toml` via `passthrough`
- **Homebrew** —
[`Library/Homebrew/extend/os/linux/extend/ENV/super.rb`](https://github.com/Homebrew/brew/blob/master/Library/Homebrew/extend/os/linux/extend/ENV/super.rb):
`self["JEMALLOC_SYS_WITH_LG_PAGE"] = "16"` (applied globally to Homebrew
Linux aarch64 builds)
- **Arch Linux ARM** —
[`extra/fd/PKGBUILD`](https://github.com/archlinuxarm/PKGBUILDs/blob/master/extra/fd/PKGBUILD):
`[[ $CARCH == "aarch64" ]] && export JEMALLOC_SYS_WITH_LG_PAGE=16`
## Related Issue(s)
Fixes#35345
## Current Behavior
The `e2e-gradle:e2e-ci--**/*.test.ts` tasks produce hundreds of sandbox
violations: ~163 unexpected reads + ~160 unexpected writes under
`packages/gradle/project-graph/build/**`, plus reads of workspace-root
gradle config files.
The root cause is `e2e/gradle/src/utils/create-gradle-project.ts`
invoking `./gradlew :gradle-project-graph:publishToMavenLocal
-PskipSign=true` inline via `execSync` during test setup, which compiles
Kotlin sources inside the sandboxed task and produces all those file
accesses.
## Expected Behavior
The `publishToMavenLocal` step runs as a proper Nx task dependency
before the e2e test, outside the sandbox. Its outputs are declared by
the `@nx/gradle`-inferred target.
### Changes
- **Move publishing out of the test** — delete the inline
`execSync(gradlew :gradle-project-graph:publishToMavenLocal)` in
`create-gradle-project.ts`; make the `e2e-local` and `e2e-ci--**/**`
targets on `e2e-gradle` depend on
`:gradle-project-graph:gradle:publishToMavenLocal`.
- **Fix signing** — the inferred `publishToMavenLocal` target doesn't
pass `-PskipSign=true`, so replace the `skipSign` flag in
`packages/gradle/project-graph/build.gradle.kts` with `setRequired({
gradle.taskGraph.hasTask(":gradle-project-graph:publish") })`. Signing
is required for the Maven Central path (`publish` lifecycle task) but
silently no-ops for `publishToMavenLocal` when no GPG keys are
provisioned.
- **Declare workspace wrapper inputs** — the bootstrap `gradle init`
call must use the workspace wrapper, so add
`gradle/wrapper/gradle-wrapper.jar`,
`gradle/wrapper/gradle-wrapper.properties`, and `gradle.properties` as
inputs on the e2e targets, matching the `@nx/gradle` plugin's inferred
gradle-task input set.
- **Disable cache on the publish task** — `publishToMavenLocal` writes
to `~/.m2/repository/` (outside the workspace, can't be declared as an
Nx output). A remote cache hit would skip launching gradle, leaving
`~/.m2/` empty on the agent and breaking plugin resolution. Override
`cache: false` on `:gradle-project-graph:gradle:publishToMavenLocal`.
- **Narrow `e2eInputs` tsconfig input** — use `{ json, fields }` in
`nx.json` to hash only the fields that affect compilation (same pattern
as `@nx/playwright`).
- **Tighten `astro-docs:validate-links` inputs** — swap the
`**/sitemap*.xml` dep-task-outputs glob for `**/*.html` +
`**/sitemap*.xml` (the actual files the script reads) and drop the dead
`nx-dev/public/sitemap-0.xml` branch since #35315 removed `next-sitemap`
from nx-dev.
## Related Issue(s)
NXC-3981
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`nx --version` (and other trivial CLI invocations) eagerly load the
daemon client, dotenv loader, analytics + perf-logging stack, and
`init-local` at the top of `bin/nx.ts`. That's ~225ms of unrelated
module load time before `main()` starts running.
On a typical workspace, the cost looks like:
| Layer | Time |
|---|---|
| `daemonClient` import | ~88ms |
| `perf-logging` (loads `analytics` → native binding) | ~63ms |
| `init-local` | ~36ms |
| `dotenv` | ~19ms |
| `analytics-prompt` | ~19ms |
End-to-end: `nx --version` was ~440ms via `pnpm exec`.
## Expected Behavior
`--version` (and other no-workspace fast paths) only load what they
actually need.
This PR:
- Lazy-loads the heavy modules inside the code paths that actually use
them (cloud commands, local install handoff, etc.).
- Adds a `--version` fast path at the top of `main()` that exits before
any heavy module is touched.
- Reworks `src/utils/perf-logging.ts` to lazy-require `analytics` /
daemon logger inside the `PerformanceObserver` callback. Importing the
module no longer pulls in the native binding.
- Updates `benchmarks/bench:*` scripts to invoke the workspace nx
directly via `node ../packages/nx/dist/bin/nx.js` instead of `pnpm exec
nx`, which removes ~220ms of pnpm wrapper overhead from the measurement
and works on CI agents (which don't sync per-project
`node_modules/.bin`). `goals.json` is adjusted to the new floor.
## Benchmark Results
Measured locally against the `benchmarks/` workspace (1,110 projects).
Deltas are shown as `(vs Goal | vs Baseline)`.
| Benchmark | Goal | Baseline | Current |
|---|---|---|---|
| version | 50ms | 440ms | **41ms** (-19% \| -91%) |
| show-projects | 100ms | 1.07s | **434ms** (+334% \| -60%) |
| cat-warm | 300ms | 1.88s | **1.04s** (+245% \| -45%) |
| copy-warm | 500ms | 1.48s | **1.31s** (+163% \| -11%) |
| build-warm | 750ms | 1.71s | **1.16s** (+54% \| -32%) |
`version` clears the goal; the other benchmarks pick up the
wrapper-overhead delta too, but several are still above goal and remain
targets for follow-up work.
## Related Issue(s)
N/A — internal performance work on the `speed-version` branch.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
The previous clean-up was overly zealous and removed both `sitemap.xml`
and `sitemap-0.xml` because I assumed there was nothing from Next.js
app. This restores both.
A follow-up will be to move these out of Next.js, but for now the root
one will point to both Framer and Next.js routes, and the latter has
`/courses/*` which we use still.
## Current Behavior
[isitagentready.com](isitagentready.com) scan flagged two gaps on
nx.dev: no agent-useful Link relation types in response headers, and no
Content-Signal directives in robots.txt.
## Expected Behavior
- Netlify serves Link response headers pointing agents at /llms.txt
(describedby), /llms-full.txt (service-doc), and /sitemap-index.xml
(sitemap).
- Production robots.txt declares permissive Content-Signal preferences
(search=yes, ai-input=yes, ai-train=yes) so AI crawlers know the docs
are intentionally open.
<img width="873" height="1269" alt="image"
src="https://github.com/user-attachments/assets/70fbcbee-04df-4f5c-b274-c360f420060d"
/>
Note: robots.txt won't work until we go to prod, so I'll re-run
benchmarks again once deployed.
## Related Issue(s)
DOC-479
---------
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
This PR updates our `@module-federation/enhanced` range from `^2.1.0` to
`^2.3.3` since `2.3.1` has a dependency on a compromised axios version.
This does not block existing workspaces from updating themselves, but
ensures `npm install` in an existing workspace will force an update of
the enhanced package.
Fixes ##35311
## Current Behavior
The `dev.nx.gradle.project-graph` Gradle plugin is pinned to version
`0.1.19` in the Nx Gradle plugin source and referenced version constant.
## Expected Behavior
The plugin version is updated to `0.1.20`, with an accompanying
migration that updates consumer `build.gradle(.kts)` files and version
catalogs automatically when users upgrade to `22.7.0-beta.16`.
## Related Issue(s)
No related issue.
## Current Behavior
The `main-linux` orchestrator job in `.github/workflows/ci.yml` installs
several system packages, Chrome, and Playwright browsers on every run:
```yaml
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y ca-certificates lsof libvips-dev libglib2.0-dev libgirepository1.0-dev
- name: Install Chrome
uses: browser-actions/setup-chrome@...
- name: Install Playwright
run: pnpm playwright install --with-deps
```
None of these are actually used by tasks the orchestrator executes. The
orchestrator runs `format:check`, `sync:check`, `conformance:check`,
`check-imports`, `check-lock-files`, `check-codeowners`, and kicks off
`nx affected` -- which distributes all
`lint`/`test`/`build`/`e2e`/`e2e-ci` tasks to Nx agents per
`.nx/workflows/dynamic-changesets.yaml`.
Historically these steps were copy-pasted during the CircleCI to GHA
migration (5b9d43f9) without evaluating whether the orchestrator
actually needed them. Tracing each back to its origin:
- **`lsof`** (added in c9cd67ea) -- only used by `kill-port` in e2e
tests (`e2e/next`, `e2e/storybook`, etc.), which run on agents.
- **`libvips-dev` / `libglib2.0-dev` / `libgirepository1.0-dev`** (added
in cfcedb48 for storybook/Next.js image tests) -- only needed if `sharp`
falls back to a source build. Sharp ships prebuilt binaries for
linux-x64 and is only reached via `astro-docs`/Next.js builds, which run
on agents.
- **`ca-certificates`** -- already present on `ubuntu-latest`.
- **`browser-actions/setup-chrome`** -- no orchestrator-local task uses
Chrome. No Karma/Puppeteer tests run here. Browser-driven tests run on
agents, which get Chromium via Playwright.
- **`pnpm playwright install --with-deps`** -- only needed by browser
tests, which run on agents (agents install Playwright themselves, see
`.nx/workflows/agents.yaml` lines 51-54).
## Expected Behavior
The `main-linux` orchestrator skips the unnecessary install steps,
saving ~30-60s per CI run with no functional change. Nx agents continue
to install these packages themselves when they need them.
The macOS React Native job (`main-macos`) keeps its Playwright install
because those e2e tests run directly on the macOS runner, not on agents.
## Related Issue(s)
N/A -- cleanup.
## Current Behavior
`inferExtensionsFromInputProperties` uses `is AbstractCompile` to
determine when to add `.class` as an inferred input extension. As a
result, Kotlin compile tasks are silently excluded from the `.class`
branch, producing incomplete inferred inputs in the Nx project graph for
Kotlin projects.
## Expected Behavior
All Kotlin compile tasks should be recognized alongside
`AbstractCompile` tasks and produce `.class` as an inferred input
extension, regardless of which Gradle plugin hierarchy they belong to.
Java-only projects are unaffected.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
The pseudo-IPC channel (used when Rust forks a Node wrapper that then
re-forks the Node task process) and the plugin isolation channel (used
between the main Nx process and its plugin-worker subprocesses) both
serialize every message with `JSON.stringify` / `JSON.parse`. That means
payloads such as `Buffer`, `Date`, `Error`, `undefined`, or objects with
cycles can't flow over these channels, even though the daemon
client/server already supports them via the shared `serialize()` helper
in `daemon/socket-utils.ts` (v8 with JSON fallback).
Notably, `Error` objects over the plugin channel currently flatten to
`{}` under JSON, losing stack and message.
The read-side `isJsonMessage ? JSON.parse :
v8.deserialize(Buffer.from(..., 'binary'))` detection was also
duplicated across three files.
## Expected Behavior
Pseudo-IPC and plugin isolation both use the same opt-in v8/JSON
serialization convention as the daemon channel, so non-JSON-safe
payloads flow through consistently. Default behavior is unchanged — v8
serialization is gated by `isV8SerializerEnabled()` and falls back to
JSON otherwise.
The duplicated read-side detection is hoisted into a single
`parseMessage<T>()` helper next to `isJsonMessage` in
`utils/consume-messages-from-socket.ts`. Round-trip coverage for the
helper is added.
Commits:
1. `fix(core): use v8 serialization in pseudo-IPC channel` — matches
daemon wire format on the pseudo-IPC server/client.
2. `refactor(core): hoist parseMessage helper for socket payloads` —
deduplicates the parse pattern and adds unit tests.
3. `refactor(core): adopt parseMessage/serialize in plugin isolation
IPC` — applies the same convention to the internal plugin-worker channel
(not public API).
## Related Issue(s)
N/A
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
Add support for `--redirect-to-prod` flag for 16, 17, 18 which we do not
have versioned docs for. Also update README.md with more details on how
Netlify and Squarespace are used to support versions docs.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
## Current Behavior
`nx-dev` is a monolithic Next.js app still serving blog, docs,
changelog, pricing, podcast, resources-library, whitepaper pages, etc. —
all of which now live in `astro-docs` (docs), `nx-blog` (blog), or have
been deprecated. The top-level `docs/` folder (~333MB) duplicates
content already in astro-docs, and 20+ `nx-dev/*` UI/feature libraries
are maintained despite only a handful being reachable from a live route.
## Expected Behavior
`nx-dev` only serves:
- `/ai-chat` — the AI chat UI
- `/api/query-ai-handler` — streaming chat endpoint
- `/api/query-ai-embeddings` — doc-search endpoint used by the Nx MCP
doc search tool
- `/courses` — video courses landing + detail + lesson pages (moved into
nx-dev from `docs/courses`)
### Changes
- Deleted routes: `/blog`, `/podcast`, `/pricing`, `/changelog`,
`/resources-library`, `/whitepaper-fast-ci`, `/500`, `/brands`, and
every other page that previously rendered here.
- Deleted `nx-dev/*` libraries not reachable from the surviving routes:
`feature-feedback`, `ui-podcast`, `ui-pricing`, `ui-resources` (plus a
handful of now-unused transitive helpers).
- Moved `docs/courses/` → `nx-dev/nx-dev/courses-content/` so the
top-level `docs/` folder could be deleted. Updated `CoursesApi` to
accept a configurable `authorsPath`.
- Deleted the entire top-level `docs/` folder (~333MB of stale content).
- Removed consumers of `docs/`:
- Removed `blog-description` and `blog-cover-image` conformance rules +
their registrations in `nx.json`.
- Removed
`scripts/documentation/{map-link-checker,internal-link-checker,prebuild-banner}.ts`.
- Removed `check-documentation-map` npm script.
- Removed `validateCrossSiteLinks` from `astro-docs/validate-links.ts`.
- `tools/documentation/create-embeddings` no longer includes
`docs/*.json` in its tsconfig; default `--mode` is now `astro`.
- Simplified `feature-ai`: dropped `feature-analytics`, `ui-common`, and
`ui-markdoc` deps. AI markdown rendering now uses a minimal inline
renderer on top of `@markdoc/markdoc` core.
- Simplified `_app.tsx`, `_document.tsx`, and `app/layout.tsx`: no more
`bannerCollection`, `GlobalSearchHandler`, `FrontendObservability`, or
GTM scripts.
- `/ai-chat` uses an inline minimal header instead of the full marketing
Header.
### Verification
- `pnpm nx build nx-dev` ✅ — emits all four routes above (`Route (app)`
for `/courses/*`; `Route (pages)` for `/ai-chat`, `/api/query-ai-*`).
- `/_redirects` still copied into `.next/` for Netlify.
- `/docs/*`, `/llms.txt`, `/llms-full.txt` rewrites to astro-docs
preserved.
## Related Issue(s)
Fixes DOC-478
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
## Current Behavior
Nx Cloud agents crash on warm-cache runs with:
```
Error: Failed to convert JavaScript value `Null` into rust type `String`
at DbCache.getBatch (.../cache.js:115:41)
at TaskOrchestrator.fetchCacheHits (.../task-orchestrator.js:270:47)
at TaskOrchestrator.resolveCachedTasks (.../task-orchestrator.js:564:38)
at runDiscreteTasks (.../init-tasks-runner.js:133:42)
code: 'StringExpected'
```
Regression introduced by #35172 (warm-cache perf optimization). Cloud
agents call `runDiscreteTasks` with `task.hash = null` (they
intentionally null hashes — see `ocean/.../execute-tasks-v3.ts`).
`init-tasks-runner.ts::createOrchestrator` queues `processTask` promises
via fire-and-forget `processAllScheduledTasks()`, but `runDiscreteTasks`
immediately calls `resolveCachedTasks` which doesn't await them.
`cache.getBatch(tasks.map(t => t.hash))` then receives nulls and the
napi binding rejects them.
## Expected Behavior
`resolveCachedTasks` awaits the queued `processTask` promises (which set
`task.hash` via `hashTask`) before passing hashes into `cache.getBatch`.
The single-task `runTaskDirectly` path already awaits
`this.processedTasks.get(task.id)` for the same reason — this fix
mirrors that pattern in the bulk path.
Bonus: a second tiny commit changes coordinator step 1's pre-hash guard
from `unhashed.length > 1` to `> 0`. The `> 1` micro-optimization
silently skipped cache lookup for single-task cycles, because
`resolveCachedTasksBulk` filters candidates by `task.hash &&` — a
length-1 unhashed task got dropped and ran without a cache check. Cost
more in lost cache hits than it saved in batch setup.
## Related Issue(s)
Surfaced internally; no GitHub issue.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
CI agents launched by `.nx/workflows/agents.yaml` are failing during the
`Install system deps` step on a majority of agents:
```
E: Failed to fetch http://archive.ubuntu.com/ubuntu/dists/jammy-updates/main/binary-amd64/Packages.gz
File has unexpected size (4263778 != 4263737). Mirror sync in progress? [IP: 91.189.92.22 80]
```
`archive.ubuntu.com` is serving a `Packages.gz` whose size/hash doesn't
match its own `InRelease` metadata while the canonical mirror is
mid-sync. Apt detects the mismatch and refuses to use the index. Without
that index, `apt-get install` fails to find packages and the agent dies
before any task runs. Every agent hits the same mirror, so every agent
fails simultaneously — taking down distributed CI runs.
This is currently affecting both PR CI and master CI on staging
(confirmed via `gh run` and Nx Cloud CIPE status).
## Expected Behavior
`apt-get update` succeeds reliably on agent provisioning, regardless of
canonical-mirror sync races.
Switch the agent's apt sources from `archive.ubuntu.com` /
`security.ubuntu.com` to `azure.archive.ubuntu.com` via a one-line `sed`
on `/etc/apt/sources.list`. Azure's mirror is what GitHub Actions
runners use by default — historically much more stable than canonical
for the "many concurrent CI agents hammering one mirror" workload that
triggers the sync race.
## Related Issue(s)
No GitHub issue — surfaced today via the linked CI failures.
## Current Behavior
Before this change, `nx/js/dependencies-and-lockfile` assumed
`pnpm-lock.yaml` contained a single YAML document.
When `pnpm-workspace.yaml` enables `managePackageManagerVersions: true`,
pnpm 11 writes a package-manager metadata document before the workspace
lock document. Nx then fails while building the project graph with:
```text
An error occurred while processing files for the nx/js/dependencies-and-lockfile plugin.
- pnpm-lock.yaml: expected a single document in the stream, but found more
```
## Expected Behavior
After this change, Nx reads multi-document pnpm lockfiles, selects the
workspace lockfile document, and continues parsing dependencies
normally.
This allows monorepos to keep pnpm 11 package-manager metadata enabled
while still running Nx commands such as `bun x nx sync` without
disabling `managePackageManagerVersions`.
## Related Issue(s)
Fixes Issue https://github.com/nrwl/nx/issues/35270
## Validation
- `pnpm exec jest
packages/nx/src/plugins/js/lock-file/pnpm-parser.spec.ts --config
packages/nx/jest.config.cts --runInBand`
- Verified `getPnpmLockfileNodes` against the repository multi-document
`pnpm-lock.yaml` via direct source invocation
---------
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
- fix(core): allow controlling migration dep install concurrency
there are cases where parallel installs of a migration dependencies can
cause concurrent writes to package managers cache which can cause fs
errors of files overwriting each others peer deps. this most often
occurs when a migration for a 3rd party plugin is hosted in a private
registry that doesn't support custom metadata e.g. GH npm registry.
This is now controllable via the `NX_MIGRATE_INSTALL_CONCURRENCY` env
var. if it's not set then the default behavior of running all installs
in parallel still occurs
- docs(core): add `NX_MIGRATE_INSTALL_CONCURRENCY` env var info
## Current Behavior
The FreeBSD native build in the publish workflow fails with "filesystem
full" or OOM. Two issues:
1. **Jest plugin OOM**: PR #35231 changed `createNodes` to load all jest
configs upfront in a sequential `for` loop before hash computation. Each
`loadConfigFile` registers a ts-node transpiler via `registerTsProject`
(`packages/nx/src/plugins/js/utils/register.js`), whose dedup is
refcounted. Serial register/unregister cycles drive refCount to 0
between iterations and delete the Map entry — but ts-node's
`transpilerCleanup` is a no-op, so the service stays alive in
`require.extensions`. The next iteration creates a fresh ts-node
service. Across 96 TS jest configs under `NX_PREFER_TS_NODE=true` (set
for the FreeBSD build), 96 ts-node services stack and OOM at V8's ~2GB
heap limit.
2. **Core dump fills disk**: When the jest plugin OOMs, FreeBSD writes a
2.3GB `node.core` file to the workspace root, filling the remaining 4GB
of free disk space before cargo can run.
### Failing runs
- [beta.13 publish (Apr
15)](https://github.com/nrwl/nx/actions/runs/24480325293/job/71547815608)
— `filesystem full` during project graph, cargo never ran
- [Canary after beta.12 (Apr
10)](https://github.com/nrwl/nx/actions/runs/24260184601/job/70841801347)
— jest plugin OOM at 2GB heap, `node.core` filled disk
- [Diagnostics
run](https://github.com/nrwl/nx/actions/runs/24490680528/job/71574929552)
— confirmed 2.3GB `node.core` file in workspace root
### Passing run (with pnpm patch)
- [Fix validation run (Apr
16)](https://github.com/nrwl/nx/actions/runs/24508325992/job/71632565433)
— jest plugin no longer OOMs, cargo compiles successfully (validated an
earlier lazy-load form of the patch; the current form uses parallel load
but is equivalent for FreeBSD's resource envelope)
## Expected Behavior
The FreeBSD build completes successfully. The jest plugin loads configs
in parallel so the ts-node transpiler dedup holds across all
registrations and only one service is created.
### Changes
**Jest plugin memory fix** (`packages/jest/src/plugins/plugin.ts`):
- Convert the upfront config-loading `for` loop to
`Promise.all(validConfigFiles.map(async ...))`. Keeps all
`registerTsProject` registrations alive concurrently so refCount goes
`0→1→2→…→N→N-1→…→0` and only one ts-node service is ever created.
- Preserves #35231's hash correctness: preset path and tsconfig extends
chain remain inputs to `calculateHashesForCreateNodes`; `needsDtsInputs`
is still derived from the real jest config + ts-jest transform
inspection.
**pnpm patch** (`patches/@nx__jest@22.7.0-beta.12.patch`):
- Same fix applied to the installed `@nx/jest@22.7.0-beta.12` so the
FreeBSD build (which uses the published package, not source) gets the
fix immediately. Generated via `pnpm patch` from the built source output
— installed `plugin.js` is byte-identical to
`dist/packages/jest/src/plugins/plugin.js`.
**Workflow hardening** (`.github/workflows/publish.yml`):
- `ulimit -c 0` to disable core dumps (prevents 2.3GB files from filling
disk)
- `NODE_OPTIONS='--max-old-space-size=4096'` as a safety net
- Disk usage diagnostics on build failure for future debugging
### Local verification
Cold cache (`npx nx reset` before each run), `NX_PREFER_TS_NODE=true
NX_CACHE_PROJECT_GRAPH=false NX_DAEMON=false`, 96 TS jest configs:
- **Pre-fix (serial for-loop)**: per-iteration heap grows `40 → 60 → 160
→ 756 MB` at iter 1/10/20/30, then OOM at iter ~33 with
`--max-old-space-size=4096`.
- **Post-fix (parallel)**: heap flat at **272 MB from load 1 through
96**. `require.cache` constant at 599 entries. No accumulation.
- Real `nx show projects` end-to-end: 600 MB RSS, 5 s.
### Follow-up (separate PR)
`packages/nx/src/plugins/js/utils/register.js` has a latent leak: when
the transpiler has no real cleanup (ts-node always, swc sometimes),
`registered.delete(registrationKey)` on refCount==0 allows the next
registration with the same key to stack a fresh service. Any Nx plugin
that loads configs serially hits this. Worth gating `registered.delete`
on whether the transpiler is actually disposable, analogous to the
existing `isTsEsmLoaderRegistered` flag. Not in scope for this PR.
## Related Issue(s)
Fixes the FreeBSD build failure in the publish workflow.
---------
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
## Current Behavior
No skill for investigating Nx sandbox violations.
## Expected Behavior
The `diagnose-sandbox-report` skill provides a structured workflow for
diagnosing sandbox violations, with a TypeScript script that automates
report parsing, Nx context gathering, violation validation, and file
classification.
## Current Behavior
Running cached tasks in a large workspace takes much longer than the
work actually warrants. The task orchestrator hashes, cache-checks,
schedules, and reports each task individually:
- Per-task JS→Rust→SQLite cache lookups (~N boundary crossings).
- Per-task daemon IPC calls for recording/matching output hashes (~2N
sequential round-trips).
- Per-task filesystem scans for output expansion.
- `TasksSchedule` re-sorts the full schedule array on every insert
(O(n²·log n)).
- The coordinator awaits each worker one at a time via a single-task
dispatch path.
On a 1,110-project benchmark workspace, this accumulates into
multi-second overhead even when every task is a cache hit with nothing
to do.
## Expected Behavior
Warm cache runs should resolve quickly. Every hot-path operation that
can be batched — cache lookups, daemon calls, scheduling, output-hash
tracking, filesystem scans — is batched, and the coordinator dispatches
discrete workers in parallel rather than sequentially.
### Rust native
- **`NxCache.get_batch`** (`cache.rs`) — single `UPDATE … WHERE hash IN
(…) RETURNING` with Rayon-parallel terminal-output reads replaces N
individual round-trips. Uses `rarray` for the `IN` clause, groups the
query / build stages as separate helpers, and collects rows straight
into a `HashMap`.
- **`get_files_for_outputs_batch`** — Rayon-parallel filesystem scanning
for cached-output expansion. Drop the old singular
`get_files_for_outputs` napi export now that every caller goes through
the batch path.
### Daemon output tracking
- Replace the non-batch `recordOutputsHash` / `outputsHashesMatch` chain
(client → server handlers → outputs-tracking helpers) with `*Batch`
equivalents. The single-entry path was dead after the orchestrator
routed everything through the batch methods.
- Short-circuit `outputsHashesMatchBatch` when the daemon has no
recorded hashes — avoids an unnecessary Rayon filesystem scan right
after `nx reset`.
- Skip recording for `local-cache-kept-existing`: the daemon already has
the right hash.
### Task orchestrator
- **Coordinator loop** rewrite: bulk-resolve all cache hits up-front,
batch-hash remaining unhashed tasks before any per-task lifecycle fires,
then dispatch cache-miss workers concurrently up to `parallelism`.
Tracks in-flight workers as a `Set<Promise<void>>` instead of a counter,
and the dispatch is extracted to `dispatchDiscreteWorker` +
`handleDiscreteWorkerFailure` instead of an inline fire-and-forget IIFE.
- **Split cache check from task execution**: `resolveCachedTasksBulk`
reports hits/misses without touching the execution path, so the
coordinator can batch lifecycle calls for the hit set and only dispatch
workers for misses.
- **Route `applyCachedResults` through `DbCache.getBatch`** — one daemon
call per cycle instead of one per task.
- **Group slots**: `closeGroup` can overflow without throwing when all
slots are claimed (parallelism gating is enforced elsewhere), and every
task keeps a single `groupId` through `runDiscreteTasks`.
- **Drop the dead single-entry fallback paths** in the orchestrator
(`shouldCopyOutputsFromCache`, `recordOutputsHash`, and the `processTask
will hash individually` try/catch around `hashTasks`).
- **`getExecutorForTask` takes a `projects` record directly** instead of
re-deriving it from the project graph per call via a module-level
`WeakMap`. Callers compute the record once.
### Task scheduler
- **Parallelism gating + sort stability** — batch scheduling collects
all schedulable roots, pushes them in one pass, and sorts the array once
per cycle instead of after every insert.
### Repo / infra
- `benchmarks/package.json`: run benches through `pnpm exec nx` so
hyperfine sub-shells always hit the workspace-local CLI.
- `fix(core): honor NX_NO_CLOUD / neverConnectToCloud in runner
selection` (`run-command.ts`) — surfaced while unblocking the benchmark
CI: with an ambient `NX_CLOUD_AUTH_TOKEN` on the CI agent,
`getTasksRunnerPath` still routed through the cloud shell even when
`NX_NO_CLOUD=true` was set. The cloud client's light-client require
bridge then loaded the default tasks runner from the parent workspace's
`node_modules/nx` (a published version), creating a cross-version API
mismatch. The guard now short-circuits runner selection to the default
path when cloud is explicitly opted out of.
- Test updates for the new `TasksSchedule(projectGraph, projects,
taskGraph, options)` signature.
## Benchmark Results
Measured locally against the `benchmarks/` workspace (1,110 projects)
with the `bench:*` scripts. Baseline is committed in
`benchmarks/baseline.json`.
| Benchmark | Goal | Baseline | Current | vs Goal | vs Baseline |
|---|---|---|---|---|---|
| version | 50ms | 348ms | 333ms | +566% | -4% |
| show-projects | 100ms | 710ms | 647ms | +547% | -9% |
| cat-warm | 300ms | 2.44s | 1.17s | +290% | -52% |
| copy-warm | 1.00s | 6.85s | 1.35s | +35% | -80% |
| build-warm | 5.00s | 17.75s | 1.44s | -71% | -92% |
## Related Issue(s)
Fixes#31067
No specific issue — performance-focused, driven by benchmark wall time
on large workspaces.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
Vite <8 uses esbuild to bundle config files (`vitest.config.mts`).
esbuild walks up from the entry point and reads every `tsconfig.json` in
ancestor directories plus their `extends` chains. These files are not
declared as task inputs, so changes to them don't invalidate the test
cache.
Sandbox violation:
https://staging.nx.app/runs/B5EjkJA6p1/task/angular-rspack-compiler%3Atest?batchId=7f8749c3-4e9a-4a93-b8f4-56efa69f14ab
## Expected Behavior
The `@nx/vite` and `@nx/vitest` plugins walk ancestor directories and
`extends` chains, declaring discovered tsconfig files as selective JSON
inputs that only hash `compilerOptions`. This avoids cache invalidation
from irrelevant tsconfig changes (`include`, `exclude`, `references`,
etc.) while correctly invalidating when compilation-affecting settings
change.
Files already covered elsewhere are excluded:
- Inside the project root (covered by `default`)
- The root tsconfig handled by the native `TsConfiguration` hasher
- Inside `node_modules` (invalidated via lockfile)
- Outside the workspace
## Current Behavior
No automated way to create static snapshots of the docs site for
versioned branches (e.g., v22.nx.dev). The existing `release-docs.ts`
force-pushes the full source branch, requiring Netlify to build from
source.
## Expected Behavior
`node ./scripts/create-versioned-docs.mts v22` creates a deployable
orphan branch with the pre-built static site:
- Fetches latest stable git tag for the major version (e.g., `22.6.4`)
- Builds `astro-docs` (v21+) or `nx-dev` with static export (v18-v20)
- Creates orphan branch with pre-built files at `nx-dev/nx-dev/.next/`
- Includes `netlify.toml` that skips `@netlify/plugin-nextjs` for pure
static serving
- Resolves `GITHUB_TOKEN` from 1Password or env var
- Server-side redirects for `/docs` → `/docs/getting-started/intro`
- `--force` flag to overwrite existing branches
Deployed via Netlify branch deploys at `v{major}.nx.dev`.
### Usage
```bash
node ./scripts/create-versioned-docs.mts v22
node ./scripts/create-versioned-docs.mts v21 --force
git push -f origin v22
```
### Tested
- v21 https://v21.nx.dev/docs
- v20 https://v20.nx.dev/docs
- v19 https://v19.nx.dev/docs
## Related Issue(s)
Fixes DOC-69
## Current Behavior
The secondary entry point generator rewrites all tsconfig
include/exclude entries by stripping the `src/` prefix (e.g.
`src/**/*.ts` → `**/*.ts`). This also strips `src/` from literal file
paths like `src/test-setup.ts`, breaking the exclude rule since the file
still lives at that path.
## Expected Behavior
The generator should add new include/exclude entries scoped to the
secondary entry point directory instead of mutating existing entries.
This is additive — existing entries are left untouched and new
`<name>/src/**/*.ts` patterns are appended for the secondary entry
point.
## Related Issue(s)
Fixes#33051
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: Caleb Ukle <caleb@nrwl.io>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: meeroslav <meeroslav@users.noreply.github.com>
## Current Behavior
In large monorepos where the root tsconfig lacks `files`/`include`,
`@nx/next:server` (and other executors that rely on
`readTsConfigPaths()` via `withNx`) can hang for minutes and fail with
`ECONNREFUSED` as the Next.js server never gets a chance to start.
## Expected Behavior
`readTsConfigPaths()` returns the configured path mappings quickly
regardless of workspace size, so the Next.js dev server starts normally.
## Technical Details
`readTsConfigPaths()` only needs `compilerOptions.paths`, but
TypeScript's default `ParseConfigHost` enumerates every `.ts` file under
the tsconfig directory when `files`/`include` are absent. Stubbing
`readDirectory` on the host skips the source-file scan while preserving
`extends` resolution.
## Current Behavior
When generating a `@nx/node:application` from a pnpm workspace, the
generated `launch.json` sets `runtimeExecutable` to the full
`getPackageManagerCommand().exec` string (e.g. `"pnpm exec"`). On
Windows, VS Code rejects this:
```
Can't find Node.js binary "pnpm exec": path does not exist.
Make sure Node.js is installed and in your PATH, or set the
"runtimeExecutable" in your launch.json.
```
The same issue affects npm (`npm exec --`) and yarn (`yarn exec`).
## Expected Behavior
`runtimeExecutable` should be a real binary path (`pnpm`, `npm`,
`yarn`), and the `exec` / `exec --` tokens should live in `runtimeArgs`.
## Fix
Split `getPackageManagerCommand().exec` on whitespace, feed the first
token into `runtimeExecutable`, and prepend the remaining tokens to
`runtimeArgs`. Bun (`bunx`) is unaffected because it has no space.
## Related Issue(s)
Fixes#35276
HMR is freezing on compilation failures but should support recompilation
when updating files after a previous compilation error
## Current Behavior
When running dev server with HMR, compilation errors are freezing the
dev server
## Expected Behavior
HMR should not abort or freeze and should allow recompilation when
saving updated files.
Fixes#35040
## Current Behavior
Playwright reads tsconfig files that aren't declared as task inputs:
- The config loader walks the project tsconfig `extends` chain at
compile time.
- The Playwright worker reads the workspace root `tsconfig.json` at
runtime via `isUsingTsSolutionSetup` (called by `nxE2EPreset`).
When any of these files live outside the project root, sandboxed runs
report violations and cached tasks can become stale when those files
change.
## Expected Behavior
The `@nx/playwright` plugin walks the project tsconfig `extends` chain
and also declares the workspace root `tsconfig.json` (when present and
not already handled by the native `TsConfiguration` hasher), exposing
them as selective JSON filesets that hash only `compilerOptions`,
`extends`, `files`, and `include`. This invalidates the cache for
compilation-affecting changes while staying stable when unrelated fields
(e.g. `references`) churn.
Files already covered elsewhere are excluded:
- Inside the project root — covered by `default`
- The native `TsConfiguration` hasher file (`tsconfig.base.json` when it
exists, otherwise `tsconfig.json`)
- Inside `node_modules` — invalidated via the lockfile
- Outside the workspace
Conformance commands should remain as `nx-cloud conformance` not `nx
conformance`.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Current Behavior
<!-- This is the behavior we have today -->
When `npm publish` or `pnpm publish` fails, the executor assumes
`err.stdout` (always) contains valid JSON. If a lifecycle script (e.g.
`prepublishOnly`) fails, it writes plaintext to stdout instead. This
causes `JSON.parse` to throw and the actual error to be swallowed by the
outer catch, printing only a generic "something unexpected went wrong"
message.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Wrap `JSON.parse(err.stdout...)` in a try/catch block. If parsing fails,
fall back to logging raw stderr/stdout directly and return early, so
lifecycle script failures and other non-JSON errors are always visible
to the user.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes#34497
## Current Behavior
The `fileReplacements` option in `@nx/angular-rspack` is passed to the
Angular AOT compiler but not added to rspack's `resolve.alias`
configuration. This means file replacements only work during TypeScript
compilation, not during module resolution/bundling.
## Expected Behavior
File replacements should work consistently, replacing modules both
during compilation and bundling - matching the behavior of `@nx/rspack`.
## Related Issue(s)
https://github.com/nrwl/nx/issues/32647
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
## Current Behavior
Daemon graph errors are cached until a file change triggers
recomputation
## Expected Behavior
Avoid caching daemon errors, and refresh daemon env on request start
## AI Summary
This pull request introduces several important improvements to the Nx
daemon's client-server communication, focusing on standardizing message
types, improving environment variable handling, and cleaning up imports
and type usage. The main changes include the introduction of a unified
`DaemonMessage` type, a new mechanism for synchronizing environment
variables between client and daemon, and significant import and type
refactoring for clarity and maintainability.
**Key changes:**
### Message Type Standardization
- Introduced a new `DaemonMessage` type in `daemon-message.ts` to serve
as the standard for all messages exchanged between the Nx client and
daemon, replacing the previous generic `Message` type. This includes a
type guard function `isDaemonMessage` for runtime checks.
- Updated all relevant client and server methods, such as `sendMessage`,
`sendToDaemonViaQueue`, and `sendMessageToDaemon`, to use the new
`DaemonMessage` type, ensuring type safety and consistency throughout
the codebase.
[[1]](diffhunk://#diff-dbd790ee3e31e772f1c6d54bf0681b982d9eac6adacc3149463d28e60e11f59dL7-R9)
[[2]](diffhunk://#diff-dbd790ee3e31e772f1c6d54bf0681b982d9eac6adacc3149463d28e60e11f59dL26-R25)
[[3]](diffhunk://#diff-ac77bbed6ea29034032e92619972b1ad989e0accad1b05b5462a276707549da0L1053-R1062)
[[4]](diffhunk://#diff-ac77bbed6ea29034032e92619972b1ad989e0accad1b05b5462a276707549da0R1222-R1232)
### Environment Variable Synchronization
- Implemented a new mechanism to synchronize environment variables
between the client and daemon:
- Added a `getDaemonEnv` function to centralize the construction of the
environment object.
- Modified the client to send environment variables to the daemon with
the first message after startup, and the daemon to update its
`process.env` accordingly.
[[1]](diffhunk://#diff-ac77bbed6ea29034032e92619972b1ad989e0accad1b05b5462a276707549da0R1222-R1232)
[[2]](diffhunk://#diff-ac77bbed6ea29034032e92619972b1ad989e0accad1b05b5462a276707549da0L1323-R1343)
[[3]](diffhunk://#diff-0b91261b5503dc84fcf6ba119ee1c27ae7edff8cf9535a126a6a46439179f1fdR255-R260)
### Import and Type Refactoring
- Refactored imports in both client and server files to remove unused or
redundant imports, group related imports, and improve code organization
and readability.
[[1]](diffhunk://#diff-ac77bbed6ea29034032e92619972b1ad989e0accad1b05b5462a276707549da0R45-R52)
[[2]](diffhunk://#diff-ac77bbed6ea29034032e92619972b1ad989e0accad1b05b5462a276707549da0L86-L92)
[[3]](diffhunk://#diff-ac77bbed6ea29034032e92619972b1ad989e0accad1b05b5462a276707549da0L120-L123)
[[4]](diffhunk://#diff-d5bf3c66e62cac1884a071bf07fd1991320a3e62b07bfc03af3b9557b714c892R11-R17)
[[5]](diffhunk://#diff-d5bf3c66e62cac1884a071bf07fd1991320a3e62b07bfc03af3b9557b714c892R26-R29)
[[6]](diffhunk://#diff-d5bf3c66e62cac1884a071bf07fd1991320a3e62b07bfc03af3b9557b714c892L30-L43)
[[7]](diffhunk://#diff-0b91261b5503dc84fcf6ba119ee1c27ae7edff8cf9535a126a6a46439179f1fdL4-R135)
[[8]](diffhunk://#diff-0b91261b5503dc84fcf6ba119ee1c27ae7edff8cf9535a126a6a46439179f1fdR144-R148)
[[9]](diffhunk://#diff-0b91261b5503dc84fcf6ba119ee1c27ae7edff8cf9535a126a6a46439179f1fdR161-L173)
### Project Graph Error Handling
- Improved error handling in project graph recomputation by ensuring
that if errors are encountered, the cached project graph promise is
cleared, preventing stale or erroneous state from persisting.
These changes collectively make the daemon-client architecture more
robust, maintainable, and extensible, particularly as Nx evolves to
support more complex workflows and integrations.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`nx daemon` (status, no flags) calls `generateDaemonHelpOutput`, which
`spawnSync`s a helper Node process (`exec-is-server-available.js`) just
to bridge the async `daemonClient.isServerAvailable()` probe into a sync
caller. The only caller — `daemonHandler` in
`packages/nx/src/command-line/daemon/daemon.ts` — is already `async`, so
the sync workaround isn't needed.
That subprocess also has a bug. It is spawned with `cwd: __dirname`,
which points inside `packages/nx/dist/src/daemon/client`. When `nx` is
installed via a pnpm workspace symlink from a nested workspace (e.g. a
`benchmarks` project with `"nx": "workspace:*"`), the child's `cwd`
resolves through the symlink into the parent repo. The child's
workspace-root detection walks up from there and stops at the **outer**
`nx.json`, so the probe queries the wrong workspace's socket.
Reproducer:
```
cd benchmarks
nx daemon --start # starts benchmarks daemon, succeeds
nx daemon # reports "Nx Daemon is not running."
```
The daemon is running — the status command is just looking at the parent
repo's socket.
## Expected Behavior
`nx daemon` reports the status of the workspace it was invoked from,
regardless of how `nx` is installed, and without paying for a second
Node process.
This PR inlines the status check directly in `daemonHandler` via `await
daemonClient.isServerAvailable()` and deletes the two helper files
(`generate-help-output.ts`, `exec-is-server-available.ts`).
Behavioral equivalence: socket errors are already resolved to `false`
inside `isServerAvailable()`, so the "running"/"not running" outputs are
byte-identical. The one deliberate change is that `VersionMismatchError`
— which `isServerAvailable()` explicitly `reject`s — now surfaces to the
caller instead of being swallowed as "not running" by the old child's
try/catch.
## Related Issue(s)
N/A — surfaced while benchmarking against a nested workspace that
symlinks `nx` from the host repo.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The `index.d.ts` for native bindings had an outdated doc comment for
`inspectInputs` that didn't reflect the `JsonFileSet` resolution added
in #35248.
## Expected Behavior
The doc comment accurately describes `JsonFileSet` resolution behavior.
## Current Behavior
The `run-commands` and `run-script` executors use Node.js
`child_process.exec()` to run shell commands. `exec()` internally
buffers **all** stdout/stderr into memory and compares the total against
a `maxBuffer` limit (set to ~1GB via `LARGE_BUFFER`). When a command
produces output exceeding this limit, Node.js kills the child process
and throws `ERR_CHILD_PROCESS_STDIO_MAXBUFFER`:
```
NX stdout maxBuffer length exceeded
Pass --verbose to see the stacktrace.
```
This is the **default code path on CI**, because
`PseudoTerminal.isSupported()` checks `process.stdout.isTTY`, which is
`false` when stdout is piped (as it is on all CI runners). So while
local development typically uses the Rust PTY path (which streams output
with no buffer limit), every `run-commands` task on CI goes through the
`exec()` fallback.
The reason this hasn't been hit more often is that the 1GB
`LARGE_BUFFER` is large enough for most commands. But commands that
produce very large output quickly (e.g., deploying a site with thousands
of files) can exceed it.
## Expected Behavior
Commands that produce arbitrarily large output should complete
successfully without crashing Nx. Output should stream through data
events with no internal buffering limit.
This PR replaces `exec()` with `spawn()` + `{ shell: true }` in both the
`run-commands` and `run-script` executors. `spawn()` provides identical
shell-based command execution but uses streaming I/O — there is no
`maxBuffer` at all. Since both executors already consumed output via
stream event listeners (`stdout.on('data')`) rather than the `exec()`
callback, this is a safe swap with no behavioral change.
The PR also includes a test that directly demonstrates the issue: the
same command that crashes `exec()` with a maxBuffer error completes
successfully under `spawn()`.
## Related Issue(s)
N/A — encountered during a deploy task producing large stdout.
## Current Behavior
When configuring task inputs for cache hashing, Nx hashes entire files.
For JSON config files like `tsconfig.json` or `package.json`, changing
any field invalidates the cache — even fields irrelevant to the task
(e.g., changing `description` in package.json invalidates a build task).
The only special case is tsconfig, which has hardcoded selective hashing
in the native hasher.
## Expected Behavior
A new `json` input type allows users and plugins to specify exactly
which fields from a JSON file should be included in the hash. This
enables more granular cache invalidation.
```jsonc
// Only hash the "engines" field from package.json
{ "json": "{projectRoot}/package.json", "fields": ["engines"] }
// Hash all of compilerOptions except paths
{ "json": "{workspaceRoot}/tsconfig.json", "fields": ["compilerOptions"], "excludeFields": ["compilerOptions.paths"] }
```
### Features
- **`{workspaceRoot}` and `{projectRoot}` tokens** — same syntax as
`fileset` inputs
- **Glob support** — e.g. `{projectRoot}/tsconfig*.json`
- **Dot notation** — nested field paths like `compilerOptions.target`
- **Allowlist (`fields`) and denylist (`excludeFields`)** — can be used
together
- **Deterministic hashing** — canonical JSON serialization with sorted
keys
### Files changed
- **TypeScript**: `InputDefinition` type + JSON schemas for IDE support
- **Rust NAPI bridge**: `JsonInput` struct, `Either8` → `Either9`,
`Input::Json` variant
- **Rust hash planning**: `HashInstruction::JsonFileSet`, planner emits
it from `gather_self_inputs`
- **Rust hasher**: new `hash_json.rs` with field filtering, canonical
serialization, and 10 unit tests
## Related Issue(s)
<!-- Link related issues here -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
add a kb article explaining using nx w/ claude code sandboxes w/ the
recommended fix (`allowAllUnixSockets: true`) and alternative
workarounds with their tradeoffs.
added some cross-linking to this article so users can discover it in ai
specific pages
Fixes DOC-456
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: barbados-clemens <barbados-clemens@users.noreply.github.com>
The nx.dev/docs site has several SEO issues a few minor a few larger
impacts.
Changes:
- **robots.txt** is served by Next.js with correct `Sitemap:
https://nx.dev/sitemap.xml` and explicit AI crawler policies
- **AI crawlers** (GPTBot, ClaudeBot, Google-Extended, PerplexityBot,
OAI-SearchBot) have explicit Allow rules
- **llms.txt** and **llms-full.txt** are served correctly
(Astro-generated, proxied via Next.js)
- **Sitemap index** has no duplicate entries
- **Every docs page** has BreadcrumbList + TechArticle JSON-LD schema
markup
- **Logo images** are eager-loaded (`loading="eager"`), improving mobile
LCP
- **Docs sitemap** includes `lastmod` dates (build timestamp)
- **Security headers** (Referrer-Policy, Permissions-Policy) set on
Astro docs
- **Page title** expanded to 47 chars with key terms
- **Twitter card** meta tags explicitly set on all docs pages
- **CSS bundle** no longer scans unused @nx/nx-dev-ui-icons and
@nx/nx-dev-ui-animations packages
## Related Issue(s)
closes DOC-473
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Current Behavior
The Nx Release guide pages (`/docs/features/manage-releases` and
`/docs/guides/nx-release/*`) reference CLI commands like `nx release`,
`nx release version`, `nx release publish`, etc. but don't link to their
CLI reference pages. Developers configuring CI pipelines have to
navigate separately to find the full list of available flags and
options.
## Expected Behavior
Release guide pages now link to the relevant CLI reference pages
(`/docs/reference/nx-commands#nx-release-*`) so developers can quickly
look up available flags and options while reading the guides.
Changes across 8 files:
- **`features/manage-releases.mdoc`** — Added all 5 release subcommand
links to the "References" section
- **`publish-in-ci-cd.mdoc`** — Linked `nx release`, `nx release
publish`, `nx release version`, and `nx release changelog` where
subcommands are introduced
- **`file-based-versioning-version-plans.mdoc`** — Linked `nx release
plan` where the command is introduced
- **`release-projects-independently.mdoc`** — Linked `nx release` CLI
reference near the `--projects` flag discussion
- **`release-groups.mdoc`** — Linked `nx release` CLI reference in the
filters section
- **`release-docker-images.mdoc`** — Linked `nx release` CLI reference
near docker-specific flags
- **`programmatic-api.mdoc`** — Linked `nx release` CLI where the CLI is
mentioned as the counterpart to the programmatic API
- **`automatically-version-with-conventional-commits.mdoc`** — Linked
`nx release version` CLI reference for versioning options
## Related Issue(s)
<!-- Linear issue DOC-472: Add CLI reference links from Nx Release guide
-->
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: barbados-clemens <barbados-clemens@users.noreply.github.com>
## Current Behavior
The `nx-dev:test` task reads 124 `.js`/`.ts` files from the `.next/`
build output directory. These reads come from jest-haste-map's
filesystem crawl — it scans all files under the project root matching
`moduleFileExtensions` to build its module index and compute SHA-1
hashes, and `.next/` is not excluded.
## Expected Behavior
Jest should not scan the `.next/` build output directory during its
haste-map crawl, as these files are not needed for test discovery or
execution.
## Fix
Add `modulePathIgnorePatterns: ['<rootDir>/.next']` to the nx-dev jest
config. This tells jest-haste-map to skip the `.next/` directory
entirely during its initial filesystem crawl, eliminating all 124
unexpected file reads.
## Current Behavior
- The Next.js plugin infers `default` instead of `production` for build
inputs, meaning test file changes invalidate the build cache
unnecessarily
- The Next.js plugin doesn't infer `.d.ts` dependent task output files,
causing sandbox violations when builds read type declarations from
dependencies
- nx-dev's `project.json` overrides inputs but is missing
`externalDependencies: [next]` and `.d.ts` dependent task outputs that
the plugin would normally provide
- nx-dev's `next:build` doesn't depend on `^build` or `^typecheck`, so
dependency type declarations aren't produced before the build
- `banner.json` (a generated file) was listed as a fileset input instead
of a dependent task output file
- `banner.json` was not excluded from eslint
## Expected Behavior
- Next.js plugin uses `production` for build inputs (matching Vite)
- Next.js plugin infers `dependentTasksOutputFiles: **/*.d.ts` for
builds
- nx-dev build has all necessary inputs and dependsOn to work correctly
with the sandbox
- Generated files are properly handled as dependent task outputs
## Related Issue(s)
N/A - discovered during sandbox violation investigation
On Node 22.18+ (which supports native TypeScript execution via type
stripping), Nx incorrectly warns "Unable to locate swc-node or ts-node.
Nx will be unable to run local ts files without transpiling." even
though Node can handle .ts files natively without any transpiler.
The warning should only appear on Node versions that cannot natively
execute TypeScript files. On Node 22.6+ (where
process.features.typescript is truthy), no warning should be emitted
when swc-node and ts-node are absent.
Fixes#32567
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
Somehow, these versions got out of sync
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
These versions are in sync with the package.json in the root
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
On Windows, `nx serve project --configuration=some-i18n-configuration`
fails with "cannot find project in graph.nodes" because
`posix.normalize()` does not convert backslashes to forward slashes.
Windows-style paths from `path.relative()` produce backslash-separated
strings that never match the forward-slash keys in the project root map.
## Expected Behavior
i18n configurations should resolve correctly on Windows. The path lookup
in `findProjectForPath` should succeed regardless of whether the input
path uses backslashes (Windows) or forward slashes (POSIX).
## Related Issue(s)
Fixes#32864
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
## Current Behavior
Vitest test targets can resolve workspace dependency imports to build
artifacts (e.g., `dist/*.js`). These build outputs aren't declared as
task inputs, causing sandbox I/O violations (unexpected reads from
dependency `dist/` directories).
## Expected Behavior
The vite and vitest plugins include `dependentTasksOutputFiles` in the
inferred test target inputs. This declares dependency build outputs as
inputs when a test target depends on build tasks via `dependsOn`. The
input is a no-op when there are no build task dependencies. When
`typecheck.enabled` is configured, `.d.ts` files are also included since
`tsc --noEmit` reads type declarations from dependencies.
Also removes redundant explicit `inputs` overrides from `angular-rspack`
and `angular-rspack-compiler` test targets — the plugin-inferred
defaults are now more complete.
## Current Behavior
- `/powerpack` redirects to the `/enterprise` marketing page, which
doesn't help users who have an expired or missing activation key for
shared cache plugins (`@nx/s3-cache`, `@nx/gcs-cache`, etc.)
- The self-hosted caching guide lists available packages but requires
clicking through to individual reference pages to find install commands
and key setup instructions
- No short, stable URL exists for CLI error messages to link to
## Expected Behavior
- `/powerpack` redirects to the self-hosted caching guide
(`/docs/guides/tasks--caching/self-hosted-caching`), which is the right
landing page for cache plugin users
- `/powerpack/conformance` and `/powerpack/owners` redirect to their
respective enterprise docs pages
- A new `/remote-cache` short URL points to the self-hosted caching
guide for use in CLI error messages (e.g., the `nx-key` hardcoded link
in ocean)
- The self-hosted caching page includes a table with install commands
(`nx add @nx/...`), key setup instructions (`.nx/key/key.ini`, `NX_KEY`
env var, `nx register`), and a section pointing former Powerpack users
to conformance/owners enterprise docs
Preview:
https://deploy-preview-35240--nx-docs.netlify.app/docs/guides/tasks--caching/self-hosted-caching
## Related Issue(s)
Fixes DOC-477
**Follow-up:** The `nx-key` hardcoded link in the ocean repo
(`libs/nx-packages/nx-key/src/consts.rs` L1) should be updated to
`nx.dev/remote-cache` in a separate PR.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Nx generators set `compilerOptions.baseUrl: "."` in generated tsconfig
files and write path mappings as bare relative paths (e.g.,
`my-lib/src/index.ts`). `baseUrl` is deprecated in TS 6 and removed in
TS 7.
## Expected Behavior
Nx no longer generates `baseUrl` in any tsconfig. Path mappings use `./`
prefix (e.g., `./my-lib/src/index.ts`), making them relative to the
tsconfig file without needing `baseUrl`. Existing user tsconfigs with
`baseUrl` continue to work correctly.
### Generator and template changes
- Remove `baseUrl` from all templates and generator code
- `addTsConfigPath` normalizes lookup paths with `./` prefix
- Move and remove generators handle `./`-prefixed paths correctly
- Angular secondary entry points and Remix server entry paths use `./`
prefix
### `resolvePathsBaseUrl` helper
New function (in `ts-config.ts`, duplicated in `register.ts`) that walks
the tsconfig `extends` chain to determine the correct directory for
resolving `paths` values. Finds where `paths` is defined, then looks for
the applicable `baseUrl` from that point toward the root — ignoring
child overrides that don't apply to the paths-defining tsconfig. When no
`baseUrl` applies, returns the directory of the tsconfig that defines
`paths`. All path resolver plugins and buildable-libs-utils use this
helper.
### Runtime and bundler fixes for baseUrl-less tsconfigs
- **Rollup**: resolve path mappings to absolute in compiler options
override using `resolvePathsBaseUrl`; use original tsconfig path (not
tmp) for resolution base
- **Module Federation**: add `workspaceRoot` to `resolve.modules` in all
8 MF plugin variants (Angular/React webpack, Angular/React rspack,
webpack SSR, rspack SSR, Angular rspack plugin, rspack plugin) so
workspace-relative expose paths resolve without `baseUrl`
- **Next.js/Jest**: null out SWC `resolvedBaseUrl` in generated jest
configs to prevent SWC from doing incorrect path alias resolution; Nx
jest resolver handles this via `resolvePathsBaseUrl`
- **register.ts**: use `resolvePathsBaseUrl` for correct path alias
registration
- **Path resolver plugins** (webpack, rspack, vite, expo, react-native,
jest, react component testing): use `resolvePathsBaseUrl` for correct
path resolution
- **buildable-libs-utils**: resolve tmp tsconfig paths to absolute so
they work without `baseUrl`
- **eslint-plugin**: handle `./`-prefixed paths in AST utils
## Related Issue(s)
Fixes#32958
## Current Behavior
Searching for `nx.json` on nx.dev/docs does not surface the actual
nx.json reference page in the top results. The `.NET Plugin for Nx` and
other technology introduction pages rank higher because they have
`weight: 5` in frontmatter, which inflates their body text scoring via
Pagefind's `data-pagefind-weight` attribute (~25x impact via quadratic
scaling). The same issue affects `project.json`, `inputs`, and other
reference pages.
## Expected Behavior
Reference pages whose title exactly matches the search query should rank
first or near the top. Technology introduction pages should still be
discoverable for their framework name but should not outrank reference
pages for terms they merely mention in body text.
**After this change:**
- `nx.json` → "nx.json Reference" is `#1` (was `#7+`)
- `project.json` → "Project Configuration" is `#1`
- `angular` → "Angular Plugin for Nx" is `#1` (preserved)
- `nest` → "Nest.js Plugin for Nx" is `#1` (preserved)
- `react` → "React Plugin for Nx" is `#2` (React Native `#1` — valid
match)
**Changes:**
- Reduce `weight` on 35 technology introduction pages from `5` → `2` to
reduce body text inflation while keeping a modest boost
- Reduce `weight` on adding-to-monorepo guide from `6.4` → `2`
- Adjust Pagefind ranking params: `termFrequency: 0.75 → 0.65`,
`pageLength: 0.5 → 0.3` to reduce the penalty on long reference pages
## Related Issue(s)
Fixes DOC-475
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Current Behavior
After the custom eslint hasher was removed in d64aeef5df, the
`@nx/eslint:lint` executor target defaults are missing `^default` and
`{workspaceRoot}/tools/eslint-rules/**/*` from their inputs. The old
custom hasher was compensating for this by manually handling dependency
hashing, but now that it's gone, the inputs need to be self-sufficient.
This means:
- Changes in dependencies don't invalidate the lint cache (problematic
for type-aware rules that inspect imported types)
- Changes to custom workspace eslint rules don't trigger re-linting
The inferred/plugin path (`plugin.ts`) already includes these inputs
correctly.
## Expected Behavior
The executor target defaults should include `^default` (dependency file
changes) and `{workspaceRoot}/tools/eslint-rules/**/*` (custom workspace
rules) to match what the eslint plugin already sets for inferred
targets.
## Related Issue(s)
Follows up on d64aeef5df (remove custom eslint hasher).
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
When running `nx init` in an empty git directory (no `package.json`),
the V2 init handler silently defaults to the `.nx` installation method
without prompting the user. This may not be suitable for users who
intend to create a JavaScript/TypeScript project and would prefer a
`package.json`-based setup.
## Expected Behavior
When running `nx init` in an empty git directory, users are now prompted
to choose between two setup methods:
- **`.nx installation`** — recommended for non-JavaScript projects
(Gradle, .NET, etc.)
- **`package.json installation`** — recommended for
JavaScript/TypeScript projects
The prompt only appears when:
- No `package.json` exists in the directory
- The `--useDotNxInstallation` flag was not explicitly passed
- Running in interactive mode (not AI agent mode)
If the user chooses `package.json`, a minimal `package.json` is created
and the existing npm-repo setup flow takes over. If they choose `.nx`,
the existing dot-nx setup flow is used. In both cases, the workspace is
created in the current directory (not a subfolder).
## Related Issue(s)
Fixes NXC-3983
## Current Behavior
When a `package.json` has both a script entry and an `nx.targets` entry
for the same target name, and the `nx.targets` entry uses command
shorthand (`command: "tsc"`) or an explicit executor, the two targets
are merged together. This produces an invalid hybrid target that has
both `executor: "nx:run-script"` (from the inferred script target) and
the `command` property (from the nx prop), causing the node to fail to
merge into the project graph.
## Expected Behavior
When the `nx.targets` entry specifies how to run (via `executor` or
`command`), it should completely overwrite the inferred script target
instead of merging with it. Targets without `executor` or `command`
(e.g., just adding `outputs` or `dependsOn`) should continue to merge as
before.
## Related Issue(s)
Fixes NXC-3923
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The `dotnet:build-analyzer` target runs `node
./scripts/run-native-target.js _build-analyzer dotnet` but does not
include the `run-native-target.js` script itself in its `inputs` array.
This means changes to the script won't invalidate the Nx cache,
potentially leading to stale cached results.
## Expected Behavior
The `run-native-target.js` script is included as an input to the
`build-analyzer` target, ensuring cache correctness when the script
changes.
## Related Issue(s)
Fixes NXC-4219
Large refactor for `nx show target` to increase clarity and fixup a few
issues.
## Custom Hashers
Notes when a custom hasher is used and inputs will not be considered.
See screenshots
<img width="646" height="107" alt="image"
src="https://github.com/user-attachments/assets/a6fecf17-a512-4956-964b-f04557567334"
/>
<img width="646" height="225" alt="image"
src="https://github.com/user-attachments/assets/0d4e0a85-2a8b-4bac-b0ff-4b6ea9e6b0dd"
/>
## Duplicated inputs
- Fixes issue where inputs could show up multiple times and appear
identical. In practice, this was from filesets that had specific
projects arrays that differed.
e.g. `nx show target populate-local-registry-storage` in latest shows
```
Inputs:
- !{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)
- !{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)
- !{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)
- !{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)
- !{projectRoot}/.eslintrc.json
- !{projectRoot}/.eslintrc.json
- !{projectRoot}/.storybook/**/*
- !{projectRoot}/.storybook/**/*
- !{projectRoot}/jest.config.[jt]s
- !{projectRoot}/jest.config.[jt]s
- !{projectRoot}/src/test-setup.[jt]s
- !{projectRoot}/src/test-setup.[jt]s
- !{projectRoot}/tsconfig.spec.json
- !{projectRoot}/tsconfig.spec.json
- !{projectRoot}/tsconfig.storybook.json
- !{projectRoot}/tsconfig.storybook.json
- default
- default
- {projectRoot}/**/*.rs
- {projectRoot}/**/Cargo.*
- {workspaceRoot}/scripts/local-registry
- {"runtime":"node -p '`${process.platform}_${process.arch}`'"}
- {"runtime":"rustc --version"}
- {"externalDependencies":["npm:@monodon/rust","npm:@napi-rs/cli"]}
```
After this PR it is
```
Inputs:
- {projectRoot}/**/*.rs
- {projectRoot}/**/Cargo.*
- {workspaceRoot}/.cargo/config.toml
- {workspaceRoot}/Cargo.lock
- {workspaceRoot}/Cargo.toml
- {workspaceRoot}/clippy.toml
- {workspaceRoot}/scripts/local-registry
- {"input":"production","projects":["tag:npm:public"]}
- {"input":"production","projects":["tag:maven:dev.nx.maven"]}
- {"runtime":"node -p '`${process.platform}_${process.arch}`'"}
- {"runtime":"rustc --version"}
- {"externalDependencies":["npm:@monodon/rust","npm:@napi-rs/cli"]}
Outputs:
- {workspaceRoot}/dist/local-registry/storage
```
## Others
- When running with --verbose, data includes its source location.
- Changes defaultConfiguration display to `(default)` badge after a
config if it is indeed default, instead of (default: ...) at the end of
the list
Fixes NXC-4077
Fixes NXC-4068
Fixes NXC-4200
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
## Current Behavior
The `@nx/eslint` plugin does not infer extended tsconfig files as inputs
for the inferred lint target. Projects whose `tsconfig.json` extends a
file outside the project root (e.g. `../../tsconfig.base.json`) omit
that upstream file from the lint task's inputs. Tools that walk the
tsconfig chain during linting — the typescript-eslint parser in
type-aware mode, `@nx/enforce-module-boundaries`, Angular template
parsers, and similar — read these files, so sandboxing reports them as
undeclared reads and changes to upstream tsconfigs don't invalidate the
lint cache.
## Expected Behavior
The inferred lint target declares every tsconfig file reached via the
`extends` chain of the project's `tsconfig.json` as an input, when those
files live outside the project root. Files inside the project root are
already covered by `default` (`{projectRoot}/**/*`); shareable configs
resolved from `node_modules` are invalidated via the lockfile; paths
that escape the workspace cannot be declared as `{workspaceRoot}/...`
inputs.
A new lightweight `walkTsconfigExtendsChain` helper is introduced in
`@nx/js/src/internal` so other plugins that need to inspect a tsconfig
extends chain can reuse it. It reads tsconfigs as JSONC without loading
the `typescript` package, walks `extends` arrays in reverse precedence
(matching TypeScript semantics), and accepts a visitor that can
short-circuit for precedence-aware lookups or walk exhaustively for
input collection. The helper is cycle-safe and accepts a caller-supplied
JSON cache to dedupe reads across overlapping chains.
## Current Behavior
When ts-jest runs without `isolatedModules`, it creates a TypeScript
Language Service that reads `.d.ts` files from dependency projects.
Changes to those `.d.ts` files don't invalidate the test cache, leading
to stale test results.
## Expected Behavior
The jest plugin detects ts-jest usage without `isolatedModules` and adds
`dependentTasksOutputFiles: '**/*.d.ts'` as a transitive input. This
ensures dependency type declaration changes correctly invalidate the
test cache.
The plugin:
- Inspects jest config transforms (including presets) for ts-jest
- Walks the tsconfig `extends` chain to resolve the effective
`isolatedModules` value, reusing the lightweight walker from `@nx/js`
(intentionally avoids loading `typescript` for performance)
- Handles `verbatimModuleSyntax` as implying `isolatedModules`
- Respects ts-jest v29 vs v30 semantics for the deprecated
`isolatedModules` transform option
- Mirrors `ts.findConfigFile` upward walk (capped at workspace root)
when no explicit tsconfig is configured
- Tracks external file references (presets, tsconfigs outside project
root) for correct hash computation
## Additional Changes
- **Plugin hash correctness**: config loading moved before hash
computation so external file references (preset files, tsconfig extends
chains) are included in the hash. Previously, changes to files outside
the project root that the plugin reads during inference (e.g., a shared
jest preset or base tsconfig) would not invalidate the cached target
configuration. The lockfile is also now included as a hash input.
- **e2eInputs**: added `dependentTasksOutputFiles` to the `e2eInputs`
named input in `nx.json` since e2e target defaults override
plugin-inferred inputs.
- **Jest preset**: pre-resolve the `@swc-contrib/mut-cjs-exports` SWC
plugin to an absolute path. Tests that `chdir` into temp dirs would fail
SWC plugin resolution since the temp dir has no `node_modules`.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: juristr <juristr@users.noreply.github.com>
## Current Behavior
When using `generateLockfile: true` (e.g. with `@nx/next:build`), the
generated `package-lock.json` is missing overridden packages for two
reasons:
1. `normalizePackageJson()` strips the `overrides` field, so the
generated lockfile lacks overrides both at the top level and in
`packages[""]`.
2. `findTarget()` uses semver satisfaction to match dependency edges,
but npm overrides can force versions outside the declared range (e.g.
`minimatch@^9.0.4` overridden to `10.2.1`). This causes overridden
packages and their transitive deps to be dropped from the pruned graph
entirely.
Running `npm ci` in the output directory fails:
```
npm error Missing: minimatch@10.2.1 from lock file
```
Note: yarn (`resolutions`) and pnpm (`pnpm.overrides`) were already
working correctly.
## Expected Behavior
The generated `package-lock.json` includes `overrides` and all
overridden packages. `npm ci` succeeds.
This is tested in the original issue repro repo, where with the applied
patch `npm ci` works from dist.
<img width="1272" height="362" alt="image"
src="https://github.com/user-attachments/assets/2cdbb266-71f8-45c8-8ee0-cec6dcd12705"
/>
The missing parts are both `overrides` in `package.json`, but also the
lockfile must include the pacakges in the overrides. In this example it
is like this in `package-lock.json`:
```
"node_modules/minimatch": {
"version": "10.2.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.1.tgz",
"integrity": "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==",
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.2"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
```
## Related Issue(s)
Fixes#34529
The custom eslint hasher was an optimization from the Node.js hashing
era that stripped dependency file hashes for non-type-aware lint rules.
With the native Rust hasher this optimization is no longer needed.
Deprecate `hasTypeAwareRules` option (removal in v23).
The example builds read dependent task outputs (devkit, angular-rspack,
angular-rspack-compiler dist JS files) and shared workspace files
(patch-devkit-request-path.js, tsconfig.base.json) that were not
declared as inputs, causing sandbox violations.
## Current Behavior
The `angular:test` task sandboxing run shows unexpected file reads from
`@nx/storybook` and `@nx/playwright` packages — including `.template`
and `__tmpl__` generator files. These packages are loaded dynamically
via `ensurePackage()` calls (in `generate-storybook-configuration.ts`
and `add-e2e.ts`), so the static dependency analyzer doesn't detect them
as dependencies of the angular project. This means their files are not
included in the test task's input hash, causing sandbox violations.
Relevant run: [staging.nx.app task
run](https://staging.nx.app/runs/B5EjkJA6p1/task/angular%3Atest?batchId=77ad1700-bc33-4663-b344-cbfab899c6c4)
## Expected Behavior
The `storybook` and `playwright` projects are declared as
`implicitDependencies` of the angular project (alongside the existing
`vite` entry which exists for the same reason). This ensures their files
are included in the angular test task's input hash via the `^production`
input qualifier, resolving the sandbox failures.
## Related Issue(s)
Fixes NXC-4216
## Current Behavior
The `astro-docs:format` target is cached but has no explicit `inputs`,
so it uses the default named input which only tracks
`{projectRoot}/**/*`. Changes to workspace-root prettier configuration
files (`.prettierrc`, `.prettierignore`) don't invalidate the cache,
potentially returning stale format check results.
## Expected Behavior
The `format` target explicitly lists its inputs: the `.mdoc` files it
formats and the prettier config files that control formatting behavior.
This ensures the cache invalidates correctly when prettier configuration
changes.
## Related Issue(s)
Fixes NXC-4217
## Current Behavior
Three packages (`angular-rspack-compiler`, `angular-rspack`, `esbuild`)
have build targets that run `copy-readme.js` but don't correctly declare
all required inputs:
- `angular-rspack-compiler` and `angular-rspack` manually listed partial
inputs (missing `.prettierignore` and using a bare directory path
`{workspaceRoot}/scripts/readme-fragments` that doesn't resolve to
files)
- `esbuild` had no inputs at all for its build target, falling back to
the default `["production", "^production"]`
This means changes to `.prettierignore` or `readme-fragments/*.md` would
not invalidate the build cache for these projects.
## Expected Behavior
All three packages use the `copyReadme` named input (defined in
`nx.json`), consistent with every other package in the repo. This
ensures `.prettierignore`, `readme-fragments/**/*`, `copy-readme.js`,
and project README files are all correctly tracked as build inputs.
## Related Issue(s)
Fixes NXC-4214
## Current Behavior
`nx init` uses a different cloud prompt (`code: "enable-ci"`, message:
"Would you like to enable AI-powered Self-Healing CI and Remote
Caching?") with only Yes/Skip choices. There is no way to permanently
opt out of the prompt, and package manager install output is printed
during init.
## Expected Behavior
`nx init` now uses CNW's variant 1 prompt copy:
- **Prompt:** "Enable remote caching to speed up builds with Nx Cloud?"
- **Footer:** "Free for small teams. 2-minute setup with GitHub — cache
locally and in CI"
- **Choices:** Yes / Skip for now / No, don't ask again
Behavior per choice:
| Choice | Action |
|--------|--------|
| **Yes** | Connect to Nx Cloud (set `nxCloudId`) |
| **Skip for now** | Do nothing |
| **No, don't ask again** | Set `neverConnectToCloud: true` in nx.json |
Additional changes:
- Telemetry now tracks the raw choice via `nxCloudArg` field
(yes/skip/never)
- `nx migrate` cloud prompt also supports the "never" choice
- Install stdout suppressed during init (stderr preserved for errors)
<img width="1392" height="978" alt="init"
src="https://github.com/user-attachments/assets/fdacc72b-64ed-4e87-b4c3-01a467051e24"
/>
## Related Issue(s)
Fixes NXC-4189
The setInterval(async, 10) polling loop used to connect to plugin
workers created overlapping connection attempts because setInterval does
not await its callback. This caused phantom socket connections, event
loop saturation, and cascading worker deaths.
Changes:
- Replace setInterval with recursive setTimeout so only one connection
attempt is ever in flight at a time
- Close the worker's server on first connection to reject phantom
connections that would create duplicate load timeouts
- Detect worker exit during polling and reject immediately instead of
burning through 10,000 attempts against a dead socket
- Clear _connectPromise on failure so ensureAlive() retries instead of
re-awaiting a permanently rejected promise
Fixes: #34388
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`closed` issues weren't scraped correctly. If no prior data was present,
we scraped the full repo which caps at 10k issues, when we have 11k now.
## Expected Behavior
Scraping works consistently
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
The `native` named input in `nx.json` only includes
`{projectRoot}/**/Cargo.*`, which misses workspace-root files that
Cargo/Clippy read: `Cargo.toml` (workspace manifest), `Cargo.lock`,
`clippy.toml`, and `.cargo/config.toml`. This means changes to these
files don't invalidate the cache for native tasks.
Additionally, Cargo writes intermediate build artifacts to
`dist/target/` which causes sandbox violations in Nx Cloud CI.
## Expected Behavior
Native task cache is correctly invalidated when workspace-root
Cargo/Clippy config files change. Sandbox no longer flags `dist/target/`
reads/writes as violations.
## Related Issue(s)
N/A
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
React module federation e2e suites that exercise webpack generation
paths are failing because webpack 5.106.0 removed
`lib/util/create-schema-validation.js`, which
`@module-federation/enhanced@2.3.1` still depends on.
## Expected Behavior
These known-broken suites should be skipped until the upstream module
federation dependency is compatible with webpack 5.106.0+ so they do not
keep failing CI.
## Related Issue(s)
N/A
## Summary
This PR temporarily disables the affected React module federation e2e
suites by switching their top-level test groups to `describe.skip(...)`
and adding a short note about the webpack /
`@module-federation/enhanced` incompatibility.
It covers the webpack-specific suites as well as the mixed Rspack
interoperability/convert flows that still generate webpack-based module
federation apps.
## Validation
- `npx prettier --write` on the modified test files
- `git push -u origin fix/disable-mf-webpack-tests`
- repository pre-push hook passed during push (`nx` prepush checks)
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
When e2e tests publish packages to the local Verdaccio registry with the
same version number (e.g., `23.0.0`), npm and yarn serve stale cached
tarballs from previous test runs instead of fetching the freshly
published packages. This causes e2e tests to run against outdated code.
## Expected Behavior
Each e2e test run uses a fresh package manager cache directory, ensuring
that npm and yarn always fetch the latest packages from the local
registry — even when the version number hasn't changed.
- **npm**: `npm_config_cache` set to a temp directory
- **yarn v1**: `YARN_CACHE_FOLDER` set to a temp directory
- **yarn v2**: `YARN_ENABLE_GLOBAL_CACHE` set to `false`
- **pnpm**: not affected (content-addressed store)
- **bun**: already handled via `--no-cache` flag
## Related Issue(s)
<!-- No specific issue — discovered during local e2e testing -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
If the publish fails for any reason, the `package.json` files are not
reset, this can leads to `expand-deps` erroring in subsequent runs. Wrap
`resetPackageJsons` in finally block.
Also ensures `expand-deps` can run twice without errors, at least
locally, not in CI.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
When Nx pulls `nx@latest` into a temporary directory (for example from
daemon latest resolution or `configure-ai-agents`/`init` handoff), Yarn
Berry can still run lifecycle scripts unless disabled via environment
configuration.
## Expected Behavior
All temporary `nx@latest` installs disable lifecycle scripts
consistently, including Yarn Berry, by always setting
`YARN_ENABLE_SCRIPTS=false` in the install process environment.
## Related Issue(s)
N/A. Follow-up parity change related to
https://github.com/nrwl/nx-console/pull/3108.
Fixes #N/A
## Current Behavior
When Nx loads multiple .env files (such as `apps/nx-22-5/.env`,
`.local.env`, `.env.local`, and `.env`), each file is loaded and has its
variables expanded in sequence. This means referencing works up the
priority list but not down. For example, root `.env` correctly
references variables in project-specific `apps/example/.env`, but not
vice versa.
**Example:**
If root `.env` contains:
```env
WILL_RESOLVE=$FIRST_APP_NAME
GLOBAL_NX_VERSION=22.5.1
```
And `apps/nx-22-5/.env` contains:
```env
WILL_NOT_RESOLVE=$GLOBAL_NX_VERSION
FIRST_APP_NAME=nx-22-5
```
The `WILL_NOT_RESOLVE` variable will not expand correctly to `22.5.1`.
Instead, it becomes an empty string because `GLOBAL_NX_VERSION` is not
available in the environment at the time `apps/nx-22-5/.env` is being
processed. However, `WILL_RESOLVE` correctly resolves to` nx-22-5`.
This is because project-specific .env files are loaded before root .env
files, and variable expansion happens immediately during loading rather
than after all files are loaded.
## Expected Behavior
Variables in one .env file should be able to reference variables from
other .env files that are loaded together, regardless of their loading
priority. Both up-chain and down-chain references should work.
Using the example above:
- `WILL_RESOLVE` should resolve to nx-22-5 ✅ (works today)
- `WILL_NOT_RESOLVE` should resolve to 22.5.1 ✅ (fixed by this PR)
## Changes Made
Updated `loadAndExpandDotEnvFile` in
`packages/nx/src/tasks-runner/task-env.ts` to accept an array of file
paths instead of a single path. The function now:
1. Loads all .env files first to collect the complete set of variables
2. Performs variable expansion once with all variables available
3. This ensures bi-directional cross-file variable references work
correctly
Also updated `loadRootEnvFiles` in `packages/nx/src/utils/dotenv.ts` to
use the new batched loading approach.
Testing
A reproduction repository demonstrating the issue is available at:
[https://github.com/dullbenz/nx-22-5-env-referencing-example](vscode-file://vscode-app/Applications/Visual%20Studio%20Code.app/Contents/Resources/app/out/vs/code/electron-browser/workbench/workbench.html)
After this fix, both `WILL_RESOLVE` and `WILL_NOT_RESOLVE` should
correctly expand their variable references.
Related Issue(s)
Fixes#34955
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
## Current Behavior
When Nx receives SIGINT (Ctrl+C), `performCleanup()` in the task
orchestrator kills continuous tasks and run-commands tasks, but **not
discrete tasks** (executor-based builds like `@nx/js:tsc`,
`nx:run-script`, etc.). In TUI mode, fork workers run in separate PTY
process groups and don't receive SIGINT directly, so cleanup hangs
waiting for tasks that are never terminated.
Additionally, `BatchProcess.kill()` only sends a signal to the immediate
child process, leaving grandchild processes (e.g. Java JVM, Gradle
daemon, Gradle workers) alive.
The Gradle batch executor also uses `execSync`, which blocks the Node
event loop and prevents the worker from responding to signals.
## Expected Behavior
All task types — discrete, continuous, and run-commands — should be
explicitly killed during SIGINT cleanup. Batch processes should use
`tree-kill` to terminate the entire process tree. The Gradle batch
executor should use async `spawn` instead of blocking `execSync`.
## Related Issue(s)
N/A — found via code inspection and confirmed with tests.
## Current Behavior
The Maven plugin version is `0.0.16`.
## Expected Behavior
The Maven plugin version is bumped to `0.0.17`, with a corresponding
migration for Nx `22.7.0-beta.11` that updates `pom.xml` files in user
workspaces.
## Related Issue(s)
N/A — routine version bump.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Gradle project graph plugin 0.1.19 should be tied to Nx `22.7.0.beta.11`
## Current Behavior
The `ci.yml` workflow runs `pnpm install --frozen-lockfile` and `pnpm
playwright install --with-deps` without any caching. Every CI run:
- Downloads all npm packages from the registry from scratch
- Downloads Playwright browser binaries (~hundreds of MB)
- Installs ~100+ system apt packages for Playwright dependencies
This was partially caused by #33772 which migrated from
`actions/setup-node` (which had built-in `cache: 'pnpm'`) to
`mise-action` without adding back equivalent caching.
## Expected Behavior
- **pnpm store** is cached between runs, so `pnpm install` only links
from the local store instead of downloading
- **Playwright browsers** are cached by version, so browser downloads
are skipped on cache hit
- System apt deps still install on cache hit (they're fast), but browser
downloads are skipped
## Related Issue(s)
N/A — performance improvement for CI install times.
## Current Behavior
During atomized CI target generation, `buildTestCiTarget` is called once
per test class discovered in a project. Each invocation independently
recomputes `taskInputs`, `outputs`, and `dependsOn` for the same
`testTask`. Internally, `getInputsForTask` was called with `null` for
`dependsOnTasks`, which forced `getDependsOnTask(task)` — an uncached
call to `task.taskDependencies.getDependencies(task)` — on every
invocation. For a project with 158 test classes backed by the same
Gradle task, this results in 158 redundant dependency tree walks during
project graph discovery.
## Expected Behavior
The shared computation (`getDependsOnTask`, `getInputsForTask`,
`getOutputsForTask`, `getDependsOnForTask`) is performed once per
`testTask` in `processTestFiles` and the pre-computed values are passed
into `buildTestCiTarget`. This eliminates redundant work proportional to
the number of test classes, reducing CPU overhead during project graph
generation — especially on cold Gradle daemon starts.
## Related Issue(s)
Fixes #
## Current Behavior
The `astro-docs` project is not listed in the isolated build assignment
rules in the dynamic changesets CI workflow. This means it runs
alongside other projects instead of being isolated like `nx-dev`.
## Expected Behavior
The `astro-docs` project is added to the isolated build assignment rules
alongside `nx-dev`, ensuring its build runs in isolation during CI.
## Related Issue(s)
N/A — internal CI configuration improvement.
## Current Behavior
- The **Tutorials** sidebar section is collapsed by default with a "New"
badge
- **How Nx Works** (Concepts) and **Platform Features** sections are
expanded by default
- Only ~10% of traffic reaches the CI tutorial, suggesting tutorials are
not discoverable enough
## Expected Behavior
- **Tutorials** section is expanded by default (no badge) to increase
discoverability
- **How Nx Works** and **Platform Features** sections are collapsed by
default to reduce noise and draw attention to tutorials
- This should drive higher engagement with the tutorial flow including
CI setup
<img width="502" height="735" alt="image"
src="https://github.com/user-attachments/assets/76a63a36-ab5d-4459-aab6-dc04282e3779"
/>
## Related Issue(s)
Fixes DOC-474
## Current Behavior
The Gradle project graph plugin is at version 0.1.18.
## Expected Behavior
The Gradle project graph plugin is bumped to version 0.1.19, with the
corresponding migration files created so that users upgrading Nx will
automatically get the new plugin version.
## Related Issue(s)
N/A - routine version bump
## Current Behavior
When output directories are empty (e.g. on a clean build), the plugin
cannot discover file extensions from actual files on disk. This means
`dependentTasksOutputFiles` glob patterns are missing for extensions
like `.class` and `.jar`, leading to incomplete cache inputs.
## Expected Behavior
Add `inferExtensionsFromInputProperties` to supplement file-based
extension discovery using task type checks:
- `Test` tasks → `class` + `jar` (they consume compiled code and library
jars on the test classpath)
- `AbstractCompile` tasks → `class` only (they produce/consume compiled
classes, not jars)
- `AbstractArchiveTask` dependents → their declared archive extension
(jar, war, zip, etc.)
This works at configuration time without requiring files to exist on
disk.
## Related Issue(s)
N/A
## Current Behavior
`nx` depends on `@ltd/j-toml` which is licensed under **LGPL-3.0**. This
creates licensing concerns for projects that bundle or distribute nx, as
LGPL requires downstream users to allow relinking/modification of the
LGPL component.
A previous attempt
([3446dd2](https://github.com/nrwl/nx/commit/3446dd2f77a1b182f9b64a83586ab68a2f0c063f))
replaced it with `@iarna/toml`, but that library:
- Only supports TOML 1.0.0-rc.1 (not even the final 1.0.0 spec)
- Has been effectively unmaintained since ~2020
- Is significantly slower than alternatives
## Expected Behavior
Use a permissively-licensed, actively maintained, fast TOML parser.
## Solution
Replace `@ltd/j-toml` with
[`smol-toml`](https://github.com/squirrelchat/smol-toml) (BSD-3-Clause):
| | @ltd/j-toml (current) | @iarna/toml (other PR) | **smol-toml (this
PR)** |
|---|---|---|---|
| License | LGPL-3.0 | ISC | **BSD-3-Clause** |
| TOML spec | 1.0.0 | 1.0.0-rc.1 | **1.1.0** |
| Weekly downloads | — | 2.7M | **6.8M** |
| Maintained | Yes | Dormant (~2020) | **Active (2026)** |
| Performance | Baseline | ~same | **2-4x faster** |
| CJS support | Yes | Yes | **Yes** |
### Changes
- Replace `@ltd/j-toml` imports with `smol-toml` in
`set-up-ai-agents.ts` and `test-utils.ts`
- Remove j-toml-specific APIs (`TOML.Section()`, `TOML.inline()`,
`newlineAround` option) in favor of smol-toml's simpler
`parse()`/`stringify()`
- Update inline test snapshots (single quotes → double quotes, minor
formatting differences)
### Note on inline snapshots
Some inline snapshots may need a final update via `--updateSnapshot`
once CI runs the full test suite. The snapshot changes included here
follow the pattern observed from smol-toml's output format
(double-quoted strings, no leading newline before first section).
## Test plan
- [ ] CI passes with updated snapshots
- [ ] `nx configure-ai-agents` generates valid Codex config.toml
- [ ] Release versioning for Rust/Cargo projects still produces correct
Cargo.toml output
---------
Co-authored-by: Alexandre Ducarne <aducarne@ripple.com>
Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
- Add `handles_cursor_movement: Arc<AtomicBool>` field to PtyInstance
- Short-circuit `has_cursor_movement_in_output` on subsequent calls once
a cursor-movement sequence has been detected, skipping the O(n) UTF-8
buffer scan on every arrow-key event
- The flag is monotonic (false → true, never reverts) so Relaxed
ordering
is sufficient; Arc makes it Clone-safe across async resize threads
This pull request refactors the TUI (terminal user interface) codebase
to improve performance, safety, and code clarity. The most significant
changes include switching from debouncing to throttling for PTY resize
operations, updating method signatures to use string slices (`&str`)
instead of owned `String` where possible, and making related adjustments
throughout the codebase. These changes help reduce unnecessary
allocations, improve responsiveness, and clarify intent.
**PTY Resize Handling Improvements:**
* Replaced the `debounce_pty_resize` method with `throttle_pty_resize`,
which limits PTY resize operations to at most one every 200ms
(fire-then-block), reducing excessive work during rapid events like
window resizing. All calls to the old debounce method are updated to use
the new throttle method.
[[1]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL473-R471)
[[2]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL490-R493)
[[3]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL590-R590)
[[4]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL615-R608)
[[5]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL1149-R1131)
[[6]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL1543-R1525)
[[7]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL1852-L1862)
[[8]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL2550-R2541)
[[9]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL2659-R2643)
**API and Type Signature Updates:**
* Changed many method signatures (such as `update_task_status`,
`select_task`, and `select_batch_group`) to accept `&str` instead of
`String`, reducing unnecessary allocations and clarifying ownership. All
corresponding call sites and trait implementations are updated.
[[1]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL220-R220)
[[2]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL417-R426)
[[3]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL534-R527)
[[4]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL590-R590)
[[5]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL1543-R1525)
[[6]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL2550-R2541)
[[7]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL2573-R2558)
[[8]](diffhunk://#diff-c899b0b1f501248ea65ff62c761294b7e6c5f5ece77ed422eae9c3a4849ac85aL218-R224)
[[9]](diffhunk://#diff-c899b0b1f501248ea65ff62c761294b7e6c5f5ece77ed422eae9c3a4849ac85aL709-R709)
[[10]](diffhunk://#diff-c899b0b1f501248ea65ff62c761294b7e6c5f5ece77ed422eae9c3a4849ac85aL833-R847)
**Cloning and Ownership Adjustments:**
* Replaced some `.clone()` and `.to_string()` calls with `.to_vec()` and
`.to_owned()` where more appropriate, further reducing unnecessary
allocations and clarifying intent.
[[1]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL168-R172)
[[2]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL189-R189)
[[3]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL203-R203)
[[4]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL368-R368)
[[5]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL2630-R2614)
**Code Cleanliness and Logic Simplification:**
* Simplified and cleaned up logic in several places, such as input
handling and filter mode transitions, to make the code more readable and
maintainable.
[[1]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL972-R970)
[[2]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL1006-R990)
[[3]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL1017)
Overall, these changes improve performance, safety, and maintainability
of the TUI code.
---------
Co-authored-by: Claude <noreply@anthropic.com>
When a Maven project sets maven.install.skip=true, the install:install
goal is a no-op at runtime. However, the NxTargetFactory still generated
a full batch executor target with cache:false, causing unnecessary Maven
invocations on every CI agent. This led to flaky failures in DTE when
the batch runner's graph setup failed on some agents.
Now detects maven.install.skip=true and emits an nx:noop target with
cache:true instead. The target still acts as a synchronization point in
the task graph (dependsOn chain is preserved) but avoids spinning up the
batch runner entirely.
<img width="2124" height="842" alt="image"
src="https://github.com/user-attachments/assets/b6c09b3e-c34e-45f8-9c33-2d6ce493bc3d"
/>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Current Behavior
issue-notifier.yml uses an outdated syntax that throws
## Expected Behavior
it doesn't throw
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
There is no e2e test that verifies the nx build process works correctly
end-to-end — that the source code compiles, produces expected output
files, and correctly detects source changes on rebuild.
The verdaccio `max_body_size` was set to 20mb, which is too small for
the nx package. The nx `.npmignore` was also missing exclusions for
`.rs` and `.snap` files.
## Expected Behavior
- A new `e2e-nx-build` test project that:
- Clones the nx repo to a temp directory
- Swaps `@nx/*` and `nx` dependency versions to match what's published
in the local verdaccio registry
- Installs dependencies from the local registry
- Builds all `tag:npm:public` packages (same as nx-release)
- Verifies key output files exist (`bin/nx.js`, `src/index.js`,
`src/index.d.ts`)
- Modifies a source file (`bin/nx.ts`), rebuilds, and verifies the
change appears in the output — catching cache misconfiguration (verified
by sabotaging `inputs: []` and confirming the test fails)
- Verdaccio `max_body_size` bumped to 100mb
- `.rs` and `.snap` files excluded from the nx npm package
## Related Issue(s)
N/A — new test infrastructure
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
When `skipProjectGraph` is passed to the `generate()` function, use
`retrieveProjectConfigurationsWithoutPluginInference` instead of
`createProjectGraphAsync`. This loads only default plugins (js,
package-json, project-json) and skips dependency edge computation,
making generation faster while still supporting local plugins.
This is a private API — callers must import the `generate` function
directly and pass `skipProjectGraph: true`.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Current Behavior
When running `nx run-many -t clean --batch` in a multi-module Maven
project, the batch executor hangs indefinitely. Workers use
`taskQueue.poll()` which returns `null` immediately when the queue is
temporarily empty. Workers exit their loop prematurely — even though
other workers are still processing tasks that will produce new root
tasks. Eventually all workers exit and `completionLatch.await()` blocks
forever.
## Expected Behavior
`nx run-many -t clean --batch` completes successfully for Maven projects
of any size. Worker threads now use `taskQueue.take()` which blocks
until a task is available, instead of exiting when the queue is
momentarily empty. When all tasks complete, `executor.shutdownNow()`
interrupts any workers still blocked on `take()`, allowing clean
shutdown.
## Related Issue(s)
Fixes#34757
## Current Behavior
The `packages/nx` project uses `tsgo` (Go-based TypeScript compiler) via
a dedicated `@nx/js/typescript` plugin entry with `compiler: "tsgo"`.
All other packages use `tsc`.
When a downstream package (e.g., `node:build-base`) runs `tsc --build`
with project references to `nx`, `tsc` detects the `.tsbuildinfo` was
produced by a different compiler version (`7.0.0-dev` vs `5.9.2`) and
recompiles `nx` entirely. This cascades through the reference chain —
every project referencing `nx` is then considered out of date,
triggering rebuilds across devkit, js, workspace, eslint, jest, docker,
and the originating project.
Verified locally with `tsc --build --verbose`:
```
Project '../nx/tsconfig.lib.json' is out of date because output for it
was generated with version '7.0.0-dev.20260327.2' that differs with
current version '5.9.2'
```
## Expected Behavior
All packages use `tsc`, eliminating the compiler version mismatch. `tsc
--build` finds all referenced projects up to date and skips
recompilation.
> **Note:** We'll switch all packages to `tsgo` once they're all
migrated to `nodenext` and prepared for it, so we use a single compiler
across the workspace at a time.
## Current Behavior
When file changes trigger rapid project graph recalculations (e.g.,
during development with `nx graph` or a dev server running), each call
to `populateProjectGraph` spawns a new Gradle process via
`execGradleAsync`. If the previous Gradle daemon is still busy
processing the prior request, Gradle spawns a **new daemon**. These
daemons persist for 3 hours by default (Gradle's idle timeout), leading
to dozens of orphaned `java.exe` processes consuming significant memory.
Maven has a similar issue — `runMavenAnalysis` spawns a long-lived
process with no timeout or cancellation support at all.
### Root Cause Analysis
The daemon explosion happens due to three compounding issues:
1. **No cancellation of in-flight processes**: When a newer project
graph request arrives, the previous invocation continues running. Each
concurrent invocation finds the existing daemon busy and spawns a new
one.
2. **Windows process tree issue**: On Windows, `execFile` with `shell:
true` runs `cmd.exe → gradlew.bat → java.exe`. Node's `AbortSignal` only
terminates `cmd.exe` (the immediate child process), leaving `java.exe`
running as an orphan.
3. **No timeout for Maven**: Maven analysis could run indefinitely with
no way to cancel or time out.
### Reproduction
1. Run `nx graph` in a workspace with `@nx/gradle` registered
2. Rapidly modify a `build.gradle.kts` file (e.g., `while true; do echo
"// tick" >> build.gradle.kts; sleep 0.01; done`)
3. Watch `java.exe` processes accumulate: `tasklist | grep java`
(Windows) or `ps aux | grep java` (Unix)
4. Without this fix: **15+ Gradle daemons** within seconds, persisting
for 3 hours each
5. With this fix: **1 Gradle daemon** remains stable under the same
conditions
## Expected Behavior
When rapid file changes trigger multiple project graph recalculations:
- The previous invocation is cancelled before starting a new one
- Cancelled calls that have already spawned a process get their entire
process tree killed (not just the shell wrapper)
- Only 1 daemon remains active at any time, rather than accumulating
dozens
- Both Gradle and Maven have configurable timeouts with clear error
messages
## Changes
### Gradle
#### 1. Self-contained cancellation in `get-project-graph-lines.ts`
Moved the `AbortController` from
`get-project-graph-from-gradle-plugin.ts` into
`get-project-graph-lines.ts`, closer to where processes are spawned.
`getNxProjectGraphLines` now manages its own abort controller —
cancelling any in-flight request before starting a new one. Uses
`abort('cancelled')` reason to distinguish external cancellation from
timeout.
#### 2. Tree-kill on abort (`exec-gradle.ts`)
Instead of passing the `AbortSignal` directly to Node's `execFile`
(which only kills the immediate child process), we intercept the signal
and use `tree-kill` to terminate the entire process tree. This ensures
`java.exe` is killed along with `cmd.exe` and `gradlew.bat` on Windows.
### Maven
#### 3. Timeout and cancellation support for `maven-analyzer.ts`
Added the same abort controller + tree-kill + timeout pattern to Maven
analysis:
- Configurable timeout via `NX_MAVEN_ANALYSIS_TIMEOUT` env var (default:
120s local, 600s CI)
- `cancelPendingMavenAnalysis()` cancels in-flight processes on repeated
calls
- `tree-kill` ensures the entire Maven process tree is killed on abort
- Clear timeout error messages with actionable steps
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
We are using an old version of `ejs` and it is not pinned.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
We are using the latest version of `ejs` and it is pinned.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The `nx-cloud onboard` command and its subcommands are not documented in
the Cloud CLI reference page.
## Expected Behavior
The Cloud CLI reference page includes complete documentation for the
`nx-cloud onboard` command, covering:
- Main `onboard` command with interactive and non-interactive modes
- All subcommands: `status`, `connect-workspace`, `connect github`,
`connect github poll`, `orgs list`, `orgs create`, `repos list`,
`templates list`, `vcs status`, and `workspace create`
- Options tables for each subcommand
- Automation notes for AI agent integration
Also changed `--non-interactive` to `--no-interactive` to match the Nx
CLI convention (yargs `--no-` prefix).
## Related Issue(s)
Fixes DOC-451
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Current Behavior
The `nx`, `create-nx-workspace`, and `nx-dev` packages pin `axios` at
version `1.12.0`, which has a known security vulnerability
([CVE-2026-25639](https://github.com/advisories/GHSA-43fc-jf86-j433)).
## Expected Behavior
Axios is pinned at `1.13.5`, which includes the fix for CVE-2026-25639,
eliminating the security vulnerability.
## Related Issue(s)
Fixes#35145
## Current Behavior
The launch templates reference page only lists Node 20-based agent
images (`ubuntu22.04-node20.11-*` and `ubuntu22.04-node20.19-*`).
## Expected Behavior
The launch templates reference page also lists the new Node 22 and Node
24 agent images:
- `ubuntu22.04-node22.22-v1`
- `ubuntu22.04-node24.14-v1`
These images were added to the cloud infrastructure config map via
[CLOUD-4403](https://linear.app/nxdev/issue/CLOUD-4403).
## Related Issue(s)
N/A (documentation update to reflect infrastructure changes from
CLOUD-4403)
---------
Co-authored-by: Caleb Ukle <caleb@nrwl.io>
## Current Behavior
Disabled tests (`.skip()`, `.todo()`, `xit()`, `xdescribe()`, `xtest()`)
can be committed freely with no guardrails. Over time this leads to
tests silently rotting — there are currently ~65 disabled tests across
the workspace.
## Expected Behavior
ESLint warns on any disabled test via `jest/no-disabled-tests`, and
`--max-warnings` caps prevent the count from growing.
### How it works
The setup is like a ratchet — existing disabled tests are grandfathered
at their current counts, but adding new ones fails lint:
- **Global default**: `max-warnings: 5` in `nx.json` target defaults —
covers most projects
- **Per-project overrides** for projects that exceed 5, capped at their
current count:
- `graph-client`: 15 (react-hooks/exhaustive-deps, unused-vars)
- `e2e-angular`: 9, `e2e-react`: 10, `e2e-next`: 8, `e2e-node`: 6,
`e2e-storybook`: 6
- `nx-dev-feature-package-schema-viewer`: 7, `nx-dev-ui-markdoc`: 7
- **e2e `.eslintrc.json` files** only un-ignore test files (`*.test.ts`,
`*.spec.ts`) to avoid exposing unrelated lint errors in non-test code
### Changes
- Install `eslint-plugin-jest` and add `jest/no-disabled-tests: warn` to
root `.eslintrc.json` for test file overrides
- Add `.eslintrc.json` to 15 e2e projects
- Set `max-warnings: 5` globally and per-project overrides where needed
- Ignore Maven `target/` build output from linting
## Current Behavior
The Gradle project graph plugin (`dev.nx.gradle.project-graph`) has no
observability into where time is spent during project graph generation.
When users report slow `nx show projects` or project graph resolution,
there is no way to identify bottlenecks in the Kotlin plugin code.
## Expected Behavior
The plugin now has opt-in OpenTelemetry distributed tracing. When
`OTEL_EXPORTER_OTLP_ENDPOINT` is set, spans are created for key
operations and exported via OTLP/gRPC to any compatible collector
(Jaeger, Grafana Tempo, etc.). When the env var is not set, tracing is
completely no-op with zero overhead.
Run any Nx command that triggers Gradle project graph generation:
```bash
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 pnpm nx show projects
```
## Related Issue(s)
N/A — internal observability improvement for debugging Gradle project
graph performance.
## Current Behavior
The `workspace-plugin:build` target explicitly runs `tsc --build
tsconfig.lib.json`, which is the same command the inferred `build-base`
target runs. Since `build` depends on `build-base`, tsc runs twice — the
second time finding nothing to do.
The `nx` package was listed as a dependency but is only referenced in
generator template files, not in compiled source.
## Expected Behavior
- The `build` target is a pure dependency orchestrator with no command.
`build-base` (inferred by `@nx/js/typescript`) handles the actual
compilation.
- The `@nx/dependency-checks` rule is configured with `buildTargets:
["build-base"]` to align with all other packages in the repo.
- The `nx` package is removed from dependencies since it's not imported
in any source file.
## Current Behavior
If a user has `overrides` set for the `nx` package, we error with
"cannot find the implementation of xxx" if the override pins nx to a
version prior to that migration's inclusion.
## Expected Behavior
We point out the `overrides` field for easier debugging
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Two issues in plugin code:
1. **Package manager detection at module scope**:
`getPackageManagerCommand()` is called at module level in all 18 plugin
files. This detects the package manager based on the CWD or
`npm_config_user_agent` at import time, rather than using the actual
workspace root. This produces inconsistent `pmc.exec` commands (e.g.
`pnpm exec` vs `npx`) depending on how the process was invoked.
2. **Platform-dependent paths in target configs**: Plugins use
`path.join()` and `path.relative()` to build target configuration values
(outputs, commands, env vars). On Windows this produces backslashes,
making the project graph differ between platforms.
## Expected Behavior
1. Package manager detection uses `context.workspaceRoot` from the
`createNodes` callback, then passes `pmc` down to per-project
target-building functions.
2. All paths in target configs use forward slashes regardless of OS,
using `joinPathFragments` and `normalizePath` from `@nx/devkit`.
## Changes
**Plugin source (20 files):**
- All 18 plugin files: moved `getPackageManagerCommand()` from module
scope into the `createNodes` callback where `context.workspaceRoot` is
available
- `packages/playwright/src/plugins/plugin.ts`: replaced
`path.join`/`posix.join`/`posix.relative` with
`joinPathFragments`/`normalizePath` for target config paths
- `packages/webpack/src/plugins/plugin.ts`,
`packages/vite/src/plugins/plugin.ts`,
`packages/vitest/src/plugins/plugin.ts`,
`packages/nuxt/src/plugins/plugin.ts`: replaced `path.join` with
`joinPathFragments` in `normalizeOutputPath()`, added `normalizePath`
for relative test paths
**Tests (19 files):**
- Added `package-lock.json` to TempFs dirs for deterministic package
manager detection
- Converted next/nuxt root project tests from `workspaceRoot: ''` to
TempFs-based setup
- Fixed all backslash path separators in snapshots and inline snapshots
## Related Issue(s)
<!-- No specific issue — discovered during test debugging -->
## Current Behavior
When a workspace-local Nx plugin cannot be resolved (e.g., the
`node_modules` symlink for a workspace package is missing), the error
message displays `[object Object]` instead of the underlying module
resolution error:
```
NX Failed to load 1 Nx plugin(s):
- @repro/my-plugin/plugin: [object Object]
```
This happens because plugin workers serialize errors as plain objects
via `createSerializableError()`. When these serialized errors are
received back in the parent process, the `instanceof Error` check fails,
and `String(reason)` on a plain object produces `[object Object]`.
## Expected Behavior
The actual error message is displayed:
```
NX Failed to load 1 Nx plugin(s):
- @repro/my-plugin/plugin: Cannot find module '@repro/my-plugin/plugin'
```
The fix adds a `reasonToError` helper that checks for an object with a
`message` property (serialized error) before falling back to `String()`
conversion. This properly handles:
- Real `Error` instances (unchanged behavior)
- Serialized error objects from plugin workers (now extracts `message`
and `stack`)
- Other non-error rejection reasons (unchanged `String()` fallback)
## Screenshot of fix
<img width="972" height="244" alt="image"
src="https://github.com/user-attachments/assets/ca87d9a6-43a4-4fd4-a215-71277f6dcc8b"
/>
## Related Issue(s)
Fixes#35137
## Current Behavior
Broad globs in `assets.json` (`**/*.json`, `**/*.js`, `**/*.d.ts`) cause
`copy-assets` targets to claim cache ownership over files also produced
by `build-base` (tsc). Even though a recent change reordered
`copy-assets` to run before `build-base` (reducing the likelihood of the
race condition), the underlying task ownership model is still broken —
both targets claim overlapping files in their output patterns.
## Expected Behavior
Each target exclusively owns its output files. `copy-assets` only claims
non-tsc assets (templates, type declarations, native artifacts), and
`build-base` owns all compiler outputs.
## Changes
**37 `assets.json` files** — replaced broad extension globs with narrow,
destination-safe patterns: template dirs (`**/files/**`), schema type
declarations (`src/**/schema.d.ts`), non-tsc extensions (`.jar`,
`.node`, `.wasm`, `.md`), and explicit file paths for package-specific
assets.
**30 `tsconfig.lib.json` files** — removed `**/*.json` from `include` so
tsc only compiles TypeScript. JSON files are now handled by copy-assets
with explicit entries
(`@(package|executors|generators|migrations).json`,
`src/**/schema.json`).
**Exception:** jest and vite use `import('./schema.json')` which
requires JSON in tsconfig scope with `composite: true`. These keep
`src/**/schema.json` in tsconfig.
**vite** — excludes `test-utils.ts` from lib build (only used by specs)
and includes it in `tsconfig.spec.json`.
## Current Behavior
`nx migrate` can hide the actual package manager failure behind parent
wrapper noise, and invalid migration metadata can lead to confusing
follow-up failures.
## Expected Behavior
`nx migrate` should surface the underlying fallback install error
clearly, avoid noisy parent-level wrapper failures, and only fail on
invalid migration metadata when the invalid update is actually consumed.
## Current Behavior
The generated package.json (used for deployment) only copies
`pnpm.overrides` from the root package.json. Other pnpm fields that
affect `pnpm install` behavior are missing, causing issues like
lifecycle scripts not running (pnpm v10+) or wrong platform-specific
dependencies being installed.
## Expected Behavior
All pnpm configuration fields that affect `pnpm install` in a deployment
context should be copied to the generated package.json:
- `onlyBuiltDependencies` — allowlist for lifecycle scripts (pnpm v10
requirement)
- `neverBuiltDependencies` — denylist for lifecycle scripts
- `allowBuilds` — unified replacement for the above two (pnpm 10.26+)
- `supportedArchitectures` — platform-specific dependency selection
- `ignoredOptionalDependencies` — skip optional dependencies
## Related Issue(s)
Fixes#30240
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: leosvelperez <leosvelperez@users.noreply.github.com>
## Current Behavior
When `configure-ai-agents` runs for Gemini, it copies skills to the
shared `.agents/skills` directory (used by Codex, Cursor, and Gemini).
However, workspaces that were configured with an older version of Nx
still have a `.gemini/skills` directory containing duplicate Nx-managed
skills. These legacy files are never cleaned up, leaving stale
duplicates in the workspace.
## Expected Behavior
When `configure-ai-agents` runs for Gemini, it should remove any
`.gemini/skills` entries that also exist in `.agents/skills` (indicating
they are Nx-managed skills that have been migrated). User-created custom
skills in `.gemini/skills` that have no counterpart in `.agents/skills`
are preserved.
## Related Issue(s)
N/A — discovered during investigation of template repo AI agent
configurations.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
- The `docker` package has its build output path pointing to
`{workspaceRoot}/build/packages/docker/README.md`, but the
`copy-readme.js` script writes to `dist/packages/docker/README.md`. This
means the cache output doesn't match the actual output location.
- The `vue` package has an empty build target (`{}`) with no README copy
step, unlike every other publishable package.
## Expected Behavior
- The `docker` package output path correctly points to
`{workspaceRoot}/dist/packages/docker/README.md`.
- The `vue` package has a proper build target that copies and processes
the README, matching the pattern used by all other packages.
## Related Issue(s)
N/A — found during audit of build target outputs.
## Current Behavior
Newly generated JS/esbuild-based projects still default `esbuild` to
`^0.19.2`. That conflicts with Vite 8 which requires `esbuild ^0.27.0`.
## Expected Behavior
Newly generated projects should default to an `esbuild` version
compatible with Vite 8. If a workspace already has `esbuild` installed,
generators should preserve that version instead of blindly bumping it,
and Vite init should fall back to Vite 7 with a warning when the
installed `esbuild` range is incompatible with Vite 8.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`nx-dev-e2e` has no `tsconfig.json`. When `eslint .` runs,
`@typescript-eslint/parser` walks up from the project root to the
workspace root `tsconfig.json`. This read is not declared as a lint
input, causing a sandbox violation.
## Expected Behavior
A minimal `tsconfig.json` extending `tsconfig.base.json` exists in the
project, so the parser resolves locally and the sandbox violation is
eliminated.
## Why not fix in the `@nx/eslint/plugin`?
The plugin would need to resolve the ESLint config chain, identify which
parser is in use, and replicate that parser's tsconfig resolution logic
(walking up directories, following `extends` chains). This adds file I/O
and config parsing to `createNodesV2`, which runs on the critical path
during project graph computation — every `nx` command pays the cost. Not
worth it for an edge case that only surfaces when a project has no local
tsconfig.
## Why not an input override?
An input override in `project.json` would duplicate the plugin's
inferred inputs and could drift if the plugin changes what it infers in
the future.
## Current Behavior
Nightly `e2e-nx-init` runs can fail when the Angular CLI legacy suite
restores a cached workspace into an already-existing temp project
directory. With `NX_E2E_SKIP_CLEANUP=true`, the previous test workspace
is intentionally left on disk, and `fs-extra.copySync` then hits
symlinked `node_modules/.bin` entries and aborts with errors like
`Cannot copy '../which/bin/which.js' to a subdirectory of itself`.
## Expected Behavior
Restoring the cached Angular CLI workspace should be idempotent even
when cleanup is skipped between tests. The restore step should start
from a clean target directory so the suite can reuse the cached baseline
without tripping over stale symlinks from the previous test run.
## Related Issue(s)
No tracked issue. Investigated from nightly GitHub Actions run
`23833817151`.
Validation:
- `pnpm nx typecheck e2e-nx-init`
- Focused local repro on macOS/npm with `NX_E2E_SKIP_CLEANUP=true`
failing before the change and passing after it
## Current Behavior
`start-local-registry.ts` uses `require.resolve('nx')` to find the nx
CLI binary for forking a child process. This resolves the package's
`main` entry point, which only incidentally points to the binary. In
pnpm strict mode, this resolution fails when called from within `@nx/js`
because `@nx/js` doesn't declare `nx` as a direct dependency.
## Expected Behavior
Use `require.resolve('nx/bin/nx')` to explicitly resolve the CLI binary
entry point. This is semantically correct (the intent is to fork the nx
CLI) and doesn't rely on the `main` field or pnpm hoisting behavior.
`require.resolve('nx)` can fail for a number of reasons:
- If `./nx` exists, it'll resolve to that first, and fail
- If module resolution is set differently it may resolve to the index
file, or something unexpected
## Current Behavior
pre-install says rust isn't available if mise isn't trusting the dir,
but doesn't mention mise and suggests installing rust. This can trip up
AI agents if they see the output and think rust should be installed
## Expected Behavior
Error message mentions mise
## Notes
This pull request significantly refactors and improves the
`scripts/preinstall.js` dependency check script. The script now performs
more robust version checks for Node, pnpm, and Rust, and adds support
for detecting and guiding users regarding `mise` trust status. The
refactoring also improves maintainability by modularizing checks and
error handling.
**Dependency checks and error handling:**
* Refactored version checks for Node, pnpm, and Rust into separate
functions for better readability and maintainability.
* Improved error messages and guidance, including specific instructions
for updating or installing missing tools, and added checks for minimum
required versions (`Node 20.19.0+`, `pnpm 10.0.0+`, `Rust 1.70.0+`).
**Support for mise integration:**
* Added detection of `mise` installation and trust status, with user
guidance to run `mise trust` when the directory is untrusted and Rust is
missing or outdated.
**Codebase improvements:**
* Consolidated tool version gathering into a single `getToolData`
function and replaced repeated code with a reusable `execOrNull` helper.
* Changed process exit logic to only exit when errors are detected,
improving script robustness. (F43b11f
## Current Behavior
The `lint-pnpm-lock` task only declares `pnpm-lock.yaml` as input.
ESLint also reads its config chain (`.eslintrc.json`, `.eslintignore`),
`tsconfig.json`, and the custom rules plugin (`tools/eslint-rules/**`),
causing sandbox violations for 10 undeclared reads.
## Expected Behavior
All files ESLint needs are declared as inputs so sandbox reports no
violations for `@nx/nx-source:lint-pnpm-lock`.
## Current Behavior
The `nx` npm package includes unnecessary files in the published
artifact:
- ~137 Rust source files (`.rs`) from `src/native/`
- Test fixtures, snapshots, and other dev-only files
- Duplicate source directories (`bin/`, `plugins/`, `schemas/`, etc.)
alongside their compiled `dist/` equivalents
The `.npmignore` blocklist approach missed several file types, and the
CI `.node` file cleanup (`find ./dist`) no longer works because the `nx`
package now builds to `packages/nx/dist/` instead of
`dist/packages/nx/`.
## Expected Behavior
- Only `dist/` and essential root JSON files (`migrations.json`,
`executors.json`, `generators.json`) are published
- No Rust source, test fixtures, snapshots, or duplicate source dirs in
the published package
- Native type declarations (`src/native/index.d.ts`) are copied to
`dist/` via `assets.json` so all exports reference `dist/` consistently
- CI correctly removes `.node` files from `packages/nx/dist/` before
publishing to npm
## Related Issue(s)
N/A — discovered during package audit.
---------
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
## Current Behavior
Packages that build to `{projectRoot}/dist` (`maven`, `dotnet`,
`angular-rspack-compiler`) use `!**/*` in their project-level
`.eslintrc.json` `ignorePatterns` to un-ignore dotfiles. This negation
also overrides the root config's `**/dist` ignore pattern, causing
`eslint .` to traverse and read build artifacts in `dist/` during
linting. This produces sandbox violations (46 unexpected reads for
`@nx/maven:lint`).
## Expected Behavior
ESLint skips the `dist/` directory during linting, matching the behavior
already in place for `packages/nx` and `packages/angular-rspack` which
explicitly re-ignore `dist` after the `!**/*` pattern.
## Current Behavior
Tutorial pages are standalone with no visible series navigation. Path
analysis shows most users drop off after the first tutorial, even though
they generally follow the intended path.
## Expected Behavior
Each of the 8 tutorial pages now shows a "Tutorial Series" aside after
the intro paragraph, listing all tutorials with the current one bolded.
This makes the series feel connected while still allowing users to jump
around or skip as needed.
Additionally, prerequisites are standardized as plain paragraphs (not
asides) across all tutorial pages for a cleaner, less noisy layout.
<img width="971" height="816" alt="image"
src="https://github.com/user-attachments/assets/b5389d98-4809-4400-8f9f-ab9834caba94"
/>
## Related Issue(s)
Closes DOC-466
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
They don't specify the right inputs, so you will get the wrong bin file
whenever you build CNW and CNP.
The problem is ordering:
1. `build-base` runs → sees source change → compiles fresh
`bin/create-nx-workspace.js` into `dist/`
2. `build` runs next → checks inputs (`copyReadme`) → cache hit →
restores its cached outputs
3. That cached output includes the old `bin/create-nx-workspace.js`,
which overwrites the fresh one from step 1
So even though the `build-base` is compiling new source with `tsc`, the
`build` task overrides just the bin. This is for both CNW and CNP.
## Current Behavior
The react-router typecheck e2e test fails in CI because pnpm resolves
both vite 7 and vite 8 in the generated project. `@react-router/dev`
picks up vite 8 plugin types while `defineConfig` uses vite 7 types,
causing a TypeScript incompatibility.
## Expected Behavior
The test is skipped until `@react-router/dev` adds Vite 8 support, at
which point the `useViteV7` workaround and this skip can both be
removed.
## Related Issue(s)
Follow-up to #35101
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
Telemetry initialization fails with `no such table: metadata` because
the metadata table was removed during the DB schema refactor that
decoupled DB version from Nx version. The telemetry service depends on
this table to store and retrieve session IDs for analytics tracking.
## Expected Behavior
The metadata table is created as part of DB initialization alongside the
other tables (`task_details`, `running_tasks`, `task_history`).
Telemetry session tracking works without errors. The DB version is
bumped from `1` to `2` so existing databases without the table are
recreated with the correct schema.
### Changes
- Add metadata table creation to `create_all_tables` in `initialize.rs`
- Bump `DB_VERSION` from `1` to `2` to trigger fresh DB creation for
users with v1 databases
- Widen `initialize` module and `initialize_db` visibility to
`pub(crate)` for testability
- Add regression tests in `telemetry/mod.rs` that verify the session
query works against a freshly initialized DB and that session
persist/retrieve round-trips correctly
- Refactor `initialize.rs` tests to use `NxDbConnection` instead of raw
rusqlite `Connection`
## Related Issue(s)
<!-- No open issue for this bug -->
## Current Behavior
Six e2e tests were skipped in #34969 because they were failing with a
Cypress uncaught exception: `[HMR] Hot Module Replacement is disabled`.
The error originated from the webpack `styles.js` bundle during the
`before each` hook due to an upstream tapable issue
(webpack/webpack#20693).
Skipped tests:
- `e2e-nx:e2e-ci--src/workspace-legacy.test.ts`
-
`e2e-react:e2e-ci--src/module-federation/independent-deployability.webpack.test.ts`
-
`e2e-react:e2e-ci--src/module-federation/core-webpack-basic-host-remote-generation.test.ts`
-
`e2e-react:e2e-ci--src/module-federation/misc-rspack-interoperability.test.ts`
-
`e2e-react:e2e-ci--src/module-federation/dynamic-federation.webpack.test.ts`
-
`e2e-react:e2e-ci--src/module-federation/federate-module.webpack.test.ts`
## Expected Behavior
The upstream tapable issue has been resolved (webpack/webpack#20693).
All 6 tests should be re-enabled and passing.
## Related Issue(s)
Upstream fix: webpack/webpack#20693
Reverts the skip from #34969
## Current Behavior
The Gradle project graph plugin is at version 0.1.17.
## Expected Behavior
The Gradle project graph plugin is bumped to version 0.1.18 with a
corresponding migration entry at version 22.7.0-beta.9
## Related Issue(s)
N/A - routine version bump
## Current Behavior
`copy-assets` depends on `build-base`, which means it has to wait for
the entire `build-native` → `build-base` chain to finish before it can
start — even though asset copying doesn't need compiled output.
## Expected Behavior
`build-base` depends on `copy-assets` instead. This lets `copy-assets`
start immediately (in parallel with `build-native` and `^build-base`)
rather than waiting for them to complete first.
<img width="767" height="721" alt="image"
src="https://github.com/user-attachments/assets/6a1d8090-8390-4075-850e-bfd309cdc6c9"
/>
### Changes
- Removed `dependsOn: ['build-base']` from the `copy-assets` target
generated by `copy-assets-plugin.ts`
- Added `copy-assets` to `build-base.dependsOn` in `nx.json` and
project-level overrides (`gradle`)
- Removed now-transitive `copy-assets` references from `build.dependsOn`
in `nx.json`, `packages/nx`, `packages/angular`, and `packages/gradle`
- Removed `build-base` from `copy-assets.dependsOn` in `packages/nx` and
`packages/dotnet` project overrides
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
When Kotlin compilations are associated (e.g. test -> main), the
`friendPathsSet` `@Input` provider creates implicit dependencies on
producer tasks like `compileKotlin`, `compileJava`, and `jar`. Without
detecting these, Nx excludes them via `--exclude-task`, causing a
provider resolution error at execution time when Gradle tries to resolve
the `friendPathsSet` provider.
The previous implementation only detected lifecycle-based provider
dependencies (Phase 1), missing the `@Input` property-based ones.
## Expected Behavior
`findProviderBasedDependencies` now detects both:
1. **Lifecycle dependencies** — `ProviderInternal` and `TaskProvider`
entries in `lifecycleDependencies` (e.g.
`checkKotlinGradlePluginConfigurationErrors`)
2. **`@Input` property dependencies** — producer tasks discovered by
walking task properties with Gradle's `PropertyWalker` +
`PropertyVisitor`, resolving `taskDependencies` from each `@Input`
`PropertyValue` (e.g. `compileKotlin`, `compileJava`, `jar`)
These tasks are added to `includeDependsOnTasks` so they are not
excluded from Gradle execution.
The function is refactored into two immutable collectors
(`collectLifecycleDependencies` and `collectInputPropertyDependencies`)
merged in the parent, replacing the previous mutable-set-passing
pattern.
## Related Issue(s)
Fixes NXC-4174
## Current Behavior
Every lock threads run is failing
## Expected Behavior
Lock threads at least occasionally passes
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
Creating a custom React workspace with React Router for server rendering
(framework mode) fails due to a peer dependency conflict between Vite 8
and React Router. Vite 8 is the current default for new workspaces, but
`@react-router/dev` does not yet support it.
## Expected Behavior
The React application generator uses Vite 7 when React Router is
selected, avoiding the peer dependency conflict until React Router adds
Vite 8 support.
<img width="1270" height="1204" alt="image"
src="https://github.com/user-attachments/assets/87495e85-20df-4086-a1f0-eb3380a78771"
/>
## Related Issue(s)
Fixes NXC-4176
## Current Behavior
`@nx/webpack` depends on `postcss-loader@^6.1.1`, which pulls in
`cosmiconfig@7` → `yaml@1.x`. The `yaml@1.x` package has a known stack
overflow vulnerability
([GHSA-48c2-rrv3-qjmp](https://github.com/advisories/GHSA-48c2-rrv3-qjmp)).
## Expected Behavior
By bumping `postcss-loader` to `^8.2.1`, the transitive dependency chain
is eliminated entirely — `postcss-loader@8` uses `cosmiconfig@9`, which
no longer depends on `yaml` at all. This is a cleaner fix than applying
a `pnpm.overrides` workaround.
The upgrade is safe because:
- `postcss-loader@8` peer deps (`postcss ^7||^8`, `webpack ^5`) are
unchanged
- The `implementation` option and function-based `postcssOptions` API
used by `@nx/webpack` are fully supported in v8
- Nx already requires Node 18+, matching postcss-loader@8's engine
requirement
## Related Issue(s)
Fixes#35025
## Current Behavior
1. **Sandboxing false positives**: `tsc --build` reads `.tsbuildinfo`
files as an optimization hint, and the `nx-plugin-checks` lint rule
reads `schema.json` from `dist/` directories. Both are flagged as
sandbox violations even though they don't affect caching correctness.
2. **Missing dependencies in project graph**: `typeof import('...')`
inside multi-line generic type parameters (e.g. `ensurePackage<typeof
import('@nx/playwright')>()`) is not detected by the import analyzer.
The newline between `<` and `import()` resets the import type to
Dynamic, so packages like `@nx/playwright` and `@nx/storybook` are
missing from the dependency graph.
3. **ensurePackage mock duplication**: Multiple test files individually
mock `@nx/devkit` just to override `ensurePackage` so it resolves from
source instead of `node_modules`. This is repetitive and easy to miss in
new tests.
## Expected Behavior
1. **Sandboxing**: `.tsbuildinfo` reads are globally excluded.
`dist/**/*.json` reads are excluded for lint targets.
2. **Import analyzer**: `typeof import('...')` inside multi-line
generics is correctly detected as a static import by tracking angle
bracket depth and preserving import type across newlines inside
generics.
3. **ensurePackage mock**: A global `ensurePackage` mock in
`scripts/unit-test-setup.js` replaces per-file mocks, using
`jest.requireActual` to resolve from source code.
## Related Issue(s)
<!-- No directly related open issues found -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The `nx` package's `exports` field uses conditional exports that
restrict subpath access. Consumers relying on deep imports (e.g.,
`nx/src/command-line`, `nx/src/project-graph/plugins`) or importing with
file extensions (e.g., `nx/bin/nx.js`) can't resolve modules or types.
The `typesVersions` field is also incomplete and out of sync with
`exports`, breaking type resolution for consumers using
`moduleResolution: "node"` (node10).
## Expected Behavior
The `nx` package exposes all necessary subpaths through both `exports`
(for modern resolution) and `typesVersions` (for node10 resolution),
keeping them in sync. A new conformance rule prevents future drift
between the two fields.
> **Note:** This restores backwards compatibility to avoid breaking
changes in the current major version. Deep imports into `nx/src/*`
access private/internal APIs that are not part of the public contract —
they are not guaranteed to be stable and may break without notice. In Nx
v23, we plan to constrain the exports to a well-defined public API,
which will be a breaking change.
## Changes
- **Restore `nx` package exports**: expand `exports` and `typesVersions`
to cover all public subpaths including `bin/*`, `plugins/*`,
`src/command-line`, `src/project-graph/plugins`, `release/*`,
`tasks-runners/*`, and their `.js` extension variants
- **Add `types-versions-exports-sync` conformance rule**: enforces that
every `exports` entry with a `types` condition has a corresponding
`typesVersions` entry and vice versa, preventing future drift
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: meeroslav <meeroslav@users.noreply.github.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
PR #35041 narrowed the `build-base` target outputs to only match
tsc-produced file types (e.g. `**/*.{js,d.ts,...}{,.map}`), preventing
cross-OS cache pollution from native binaries. However, `.tsbuildinfo`
files were not included in the narrowed glob, so they are no longer
captured as build outputs. The tsbuildinfo handling was also spread
across multiple conditional branches with duplicated logic.
## Expected Behavior
`.tsbuildinfo` files are always included as a build output since `tsc
--build` implicitly enables `incremental: true` and always produces
them. A new `getTsBuildInfoOutputPath` helper centralizes the logic for
determining the tsbuildinfo file location (respecting `tsBuildInfoFile`,
`outFile`, `outDir`, or the default), and is called once unconditionally
at the end of the output resolution loop.
## Related Issue(s)
Follow-up to #35041
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
The Gradle executor's task exclusion logic represents running tasks and
dependency relationships using colon-delimited string IDs (e.g.
"project:target"). Parsing task identity by splitting on : breaks
silently when project or target names contain colons — a common pattern
in Gradle (e.g. :sub:project, compile:java).
## Expected Behavior
Task identity is represented as a structured ProjectTarget object with
explicit project and target fields, eliminating string-splitting
ambiguity. The getExcludeTasks, getAllDependsOn, and getGradleTaskName
functions now accept and return typed objects, and test fixtures use
object-notation dependsOn entries. A new test case verifies correct
behavior when names contain colons.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: lourw <lourw@users.noreply.github.com>
This PR removes 62% of dependabot alerts, stemming from unit test
fixtures.
## Current Behavior
Dependabot scans `package.json` files in lock-file test fixtures and
raises vulnerability alerts for packages that are only used as test data
(e.g. `express`, `minimatch`, `postcss`). These are not real
dependencies.
## Expected Behavior
Renaming fixture files from `package.json` to `package.fixture.json`
prevents dependabot from scanning them. The `.fixture.json` extension
still ends in `.json`, so Node's `require()` continues to work without
any test logic changes — only the file paths in the spec files needed
updating.
## Related Issue(s)
Fixes NXC-4169
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
## Current Behavior
`nx init` emits telemetry meta as a CSV string (e.g. "22.6.3,enable-ci")
via `recordStat`, only at the cloud prompt step. There is no start or
error tracking, no AI detection, and no environment context.
## Expected Behavior
`recordStat` now accepts a typed `RecordStatMeta` object and serializes
as JSON, matching the CNW format. Three lifecycle events are recorded:
- **start**: nodeVersion, os, packageManager, aiAgent, isCI
- **complete**: same env info plus pluginsInstalled, useCloud
- **error**: errorCode, errorMessage, aiAgent
The existing cloud prompt `recordStat` calls also now include env info.
## Related Issue(s)
Closes NXC-4168
## Current Behavior
CNW rejects "." and absolute paths (e.g. `/tmp/acme`) as workspace
names, causing over 1,300 INVALID_WORKSPACE_NAME and DIRECTORY_EXISTS
errors per month. This is the #1 input validation error for both AI
agents and humans.
## Expected Behavior
- "." and "./" in a non-empty directory suggests using `nx init` instead
- "." and "./" in an empty directory resolves to the directory's
basename and creates the workspace in-place
- Absolute paths like `/tmp/acme` extract the basename as the workspace
name and create the workspace at the specified location
The `workingDir` override is threaded through `CreateWorkspaceOptions`
to downstream functions (`createWorkspace`, `createEmptyWorkspace`,
`createPreset`, `cloneTemplate`) without mutating `process.cwd()`.
## Related Issue(s)
Closes NXC-4172
## Current Behavior
The pnpm catalog pins picomatch to `4.0.2`, which has two high-severity
vulnerabilities:
-
[GHSA-3v7f-55p6-f55p](https://github.com/advisories/GHSA-3v7f-55p6-f55p)
— Method Injection in POSIX Character Classes causes incorrect glob
matching
-
[GHSA-c2c7-rcm5-vvqj](https://github.com/advisories/GHSA-c2c7-rcm5-vvqj)
— ReDoS via extglob quantifiers
Running `npm audit` on any workspace using `@nx/angular`, `@nx/js`, or
`@nx/workspace` reports these vulnerabilities.
## Expected Behavior
No picomatch-related vulnerabilities reported by `npm audit`. The bump
to `4.0.4` is a patch release that only fixes the security issues with
no API changes.
## Related Issue(s)
Fixes#35068
## Current Behavior
CNW vue-monorepo and nuxt presets pin sass@1.62.1, but Vite 8 requires
sass >= ^1.70.0, causing ERESOLVE failures on npm during workspace
creation.
## Expected Behavior
Workspaces created with vue-monorepo and nuxt presets install
successfully with Vite 8 by using a compatible sass version range.
## Related Issue(s)
Fixes NXC-4171
## Current Behavior
When `--bundler=vite` is passed with an Angular preset
(`angular-monorepo` or `angular-standalone`), yargs accepts it since
`--bundler` is a shared `type: 'string'` option with no per-stack
`choices` constraint. The invalid value flows through to the preset
generator which rejects it after a full pnpm install (~25s), wasting the
user's time.
## Expected Behavior
Validate the bundler early in `determineAngularOptions` before any
install starts. Invalid bundlers throw `CnwError('INVALID_BUNDLER')` so
the error is:
- Properly recorded via `recordStat` for telemetry
- Surfaced as NDJSON for AI agents
- Shown as a clear `output.error()` for interactive users
Valid Angular bundlers: `esbuild`, `rspack`, `webpack`.
## Related Issue(s)
Fixes NXC-4166
## Current Behavior
All packages in the workspace use the standard `tsc` compiler via the
`@nx/js/typescript` plugin.
## Expected Behavior
The `packages/nx` project uses the new Go-based TypeScript compiler
(`tsgo`) for faster builds and typechecks, while all other projects
continue using `tsc`.
### Changes
- Install `@typescript/native-preview` as a dev dependency
- Add a separate `@nx/js/typescript` plugin entry scoped to
`packages/nx` with `compiler: "tsgo"`
- Override `baseUrl: null` in the nx tsconfig to clear the inherited
`baseUrl` (removed in tsgo)
- Set `strict: false` to match existing tsc behavior (tsgo defaults to
strict mode)
## Related Issue(s)
N/A — exploratory adoption of tsgo
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
When creating a workspace with `create-nx-workspace` using presets that
install vitest (react-monorepo with vite bundler, nuxt with vitest, vue,
etc.) and **npm** as the package manager, `npm install` fails with
`ERESOLVE unable to resolve dependency tree`.
**Root cause:** vitest `~4.0.x` depends on `@vitest/mocker@4.0.x` which
has a peer dependency on `vite: "^6.0.0 || ^7.0.0"` — it does **not**
support vite 8. Since Nx now defaults to installing `vite@^8.0.0`, npm
cannot satisfy `@vitest/mocker`'s peer dependency.
Additionally, `@vitejs/plugin-react-swc@^3.5.0` has a peer dependency on
`vite: "^4 || ^5 || ^6 || ^7"` (no vite 8 support), and the SWC compiler
path in `ensure-dependencies.ts` lacks the vite-version detection logic
that the babel path already has.
## Expected Behavior
- Workspaces using vite 8 + vitest install successfully with npm
- `@vitejs/plugin-react-swc` version is selected based on the installed
vite version (v4.3+ for vite 8, v3.x for older vite)
## Changes
1. **Bump `vitestV4Version`** from `~4.0.0`/`~4.0.8` to `~4.1.0` in both
`@nx/vite` and `@nx/vitest` — vitest 4.1.x's dependencies support vite 8
2. **Bump `vitestV4CoverageV8Version` and
`vitestV4CoverageIstanbulVersion`** to `~4.1.0` to match
3. **Update `vitePluginReactSwcVersion`** from `^3.5.0` to `^4.3.0`
(first version with vite 8 peer dep support), add
`vitePluginReactSwcV3Version = '^3.5.0'` for backward compat
4. **Add vite-version detection** to the SWC path in
`ensure-dependencies.ts` (both `@nx/vite` and `@nx/vitest`), mirroring
the existing babel path logic
## Related Issue(s)
Fixes NXC-4164
## Current Behavior
The `publish.yml` workflow defines `NX_GRADLE_PROJECT_GRAPH_TIMEOUT` and
`NX_VERBOSE_LOGGING` as workflow-level env vars, but the `docker run`
command for Linux native builds only passes `-e PNPM_VERSION`. This
means those env vars are not available inside the Docker containers,
causing Gradle timeouts and missing verbose logs during publish.
## Expected Behavior
`NX_GRADLE_PROJECT_GRAPH_TIMEOUT` and `NX_VERBOSE_LOGGING` are passed
into the Docker containers via `-e` flags so they take effect during
Linux native builds.
## Related Issue(s)
N/A
## Current Behavior
The npm security audit CI job fails due to two critical vulnerabilities:
- **handlebars** (GHSA-2w6w-674q-4c4q): JavaScript Injection via AST
Type Confusion in versions `>=4.0.0 <=4.7.8`, pulled in transitively via
`verdaccio > @verdaccio/hooks > handlebars@4.7.7`
- **underscore** (GHSA-cf4h-3jhx-xvhq): Arbitrary Code Execution in
versions `<1.13.8`, pulled in via `parse-markdown-links > remarkable >
argparse > underscore`
## Expected Behavior
The npm security audit CI job passes with zero critical vulnerabilities.
## Changes
- Update `verdaccio` from `6.0.5` to `6.3.2` (drops direct handlebars
dependency)
- Remove unused direct `handlebars` devDependency (nothing in the repo
imports it)
- Add `pnpm.overrides` for `handlebars@4.7.9` (needed because
`@verdaccio/hooks` still pins `handlebars@4.7.7` with no stable fix
available) and `underscore@^1.13.8` (needed because
`parse-markdown-links` pins `remarkable@1.7.1` with no fix available)
- Update verdaccio generator version to `^6.3.2` so new workspaces get
the safe version
- Add migration for `22.6.4` to bump verdaccio in existing workspaces
## Related Issue(s)
Fixes the failing [npm-audit CI
job](https://github.com/nrwl/nx/actions/runs/23672915671/job/68970040091)
## Current Behavior
The Gradle plugin's `createNodes` project graph generation has a fixed
60-second timeout. For large Gradle workspaces, this is too short and
causes timeouts — especially in CI environments where builds may be
slower.
## Expected Behavior
- **Local**: Default timeout increased to 3 minutes (180s)
- **CI**: Default timeout increased to 10 minutes (600s)
- The `NX_GRADLE_PROJECT_GRAPH_TIMEOUT` env var still works as an
override
- The publish workflow explicitly sets
`NX_GRADLE_PROJECT_GRAPH_TIMEOUT=600` for extra safety
## Current Behavior
The `@nx/js/typescript` plugin claims the entire `outDir` (e.g.
`{projectRoot}/dist`) as the output for `build-base` targets. When other
tasks like `copy-assets` also write into the same directory (e.g.
`.node` or `.wasm` native binaries), those files get captured in the
`build-base` cache. This causes cross-OS cache pollution — linux native
binaries get cached and restored on macOS (or vice versa).
## Expected Behavior
`build-base` outputs are scoped to only the file types that `tsc`
actually produces:
- `**/*.{js,cjs,mjs,jsx,d.ts,d.cts,d.mts}{,.map}` (default)
- `**/*.{js,cjs,mjs,jsx,json,d.ts,d.cts,d.mts}{,.map}` (when
`resolveJsonModule` is enabled)
Native binaries (`.node`, `.wasm`) and other non-tsc files in the same
output directory are no longer captured by the `build-base` cache,
preventing cross-OS cache corruption.
## Related Issue(s)
N/A — discovered during investigation of cross-OS cache artifacts.
## Current Behavior
The `@nx/dependency-checks` lint rule detects `tslib` as a required
dependency by checking if the build command contains `tsc` (via
`/\btsc\b/` regex). When a project uses `tsgo` as its compiler, the
build command is `tsgo --build` which doesn't match — causing a false
positive "tslib is not used" lint error.
## Expected Behavior
The regex also matches `tsgo`, so projects using either `tsc` or `tsgo`
correctly detect `tslib` as needed when `importHelpers: true` is set.
## Related Issue(s)
N/A — discovered while enabling tsgo for the nx package
## Current Behavior
- The copy-assets plugin copies `assets.json` into the output directory
(it doesn't exclude itself)
- The copy-assets executor catches errors with `error.message` but
`error` is typed as `unknown`, which can fail at runtime
- No way to skip e2e cleanup when debugging locally
## Expected Behavior
- `assets.json` is excluded from being copied into the output directory
- Error handling properly checks `instanceof Error` before accessing
`.message`
- Setting `NX_E2E_SKIP_CLEANUP=true` preserves the test project
directory for debugging
## Related Issue(s)
Follow-up to #34994 — addresses review comments from that PR.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`/blog` and `/changelog` paths are always served by the Next.js app. The
Netlify edge function excludes these paths (`/blog/*` in `excludedPath`,
`/blog` and `/changelog` in `nextjsPaths`) so they bypass the Framer
proxy and fall through to Next.js.
## Expected Behavior
When the `BLOG_URL` env var is set in Netlify, `/blog/*` and
`/changelog/*` requests are proxied to the standalone blog site (e.g.,
`nrwl-blog.netlify.app`) via the existing Netlify edge function. When
`BLOG_URL` is **not** set, behavior is unchanged — paths fall through to
Next.js as before.
**Changes to `netlify/edge-functions/rewrite-framer-urls.ts`:**
- Read `BLOG_URL` env var
- Conditionally remove `/blog` and `/changelog` from `nextjsPaths` when
`BLOG_URL` is set
- Remove `/blog/*` and `/changelog` from static `excludedPath` so the
edge function can intercept these paths
- Add blog proxy branch: when `BLOG_URL` is set and path matches, proxy
to blog site with URL rewriting (`blogUrl` → `https://nx.dev`) and
security headers (`X-Frame-Options`, `CSP`)
- When `BLOG_URL` is unset, blog/changelog paths fall through to Next.js
via explicit `context.next()` guard
**Asset resolution note:** The edge function only handles `text/html`
requests. Blog site assets (CSS, JS) will need to be served from the
blog site's own CDN (via `build.assetsPrefix` or equivalent in the blog
repo). All existing `/_next/*` assets remain in `excludedPath` and are
unaffected.
## Related Issue(s)
Fixes DOC-455
This PR address some common errors seen during CNW around `--template`
and `--preset` during non-interactive flows that are not AI agents. Also
sees that some template names are not qualified with
`nrwl/<name>-template` so we can normal though (which also makes it
shorter in docs).
## Current Behavior
In non-interactive contexts (IDE terminals, scripts, SSH without `-t`),
`determineTemplate()` returns `'custom'` which routes to the preset
flow. Without `--preset` provided, this throws "Preset is required",
which affects ~15 users/day (~145 occurrences Mar 18-27).
Users must also pass full template paths like
`--template=nrwl/angular-template`.
## Expected Behavior
Non-interactive mode defaults to `nrwl/empty-template` (template flow)
instead of `'custom'` (preset flow) when neither `--preset` nor
`--template` is provided.
Shorthand template names are supported:
- `--template=angular` → `nrwl/angular-template`
- `--template=react` → `nrwl/react-template`
- `--template=typescript` → `nrwl/typescript-template`
- `--template=empty` → `nrwl/empty-template`
## Related Issue(s)
Fixes NXC-4153
## Current Behavior
The TypeScript plugin emits a bare `**/*.d.ts` fileset input (without a
`{projectRoot}/` prefix) for dependency file tracking. The Nx hasher
expects all filesets to start with either `{projectRoot}/` or
`{workspaceRoot}/`, so it logs a warning for every project:
```
NX **/*.d.ts does not start with {workspaceRoot}/. This will throw an error in Nx 20.
```
## Expected Behavior
No warning is emitted. The fileset correctly uses
`{projectRoot}/**/*.d.ts` so the hasher knows it's scoped to the
dependency project's root.
## Related Issue(s)
N/A — discovered while debugging e2e test failures.
## Current Behavior
Each package defines a `legacy-post-build` target in `project.json` with
inline asset copy configuration. Inputs are not accurately declared,
leading to sandbox violations in CI. The asset globs, ignores, and
outputs must be manually kept in sync across 37 packages.
## Expected Behavior
A `copy-assets` createNodesV2 plugin reads `assets.json` from each
package and automatically generates the target with:
- Inputs derived from asset globs (positive patterns first, then
negations)
- Outputs derived using the same dest logic as `CopyAssetsHandler`
- `dependentTasksOutputFiles` for gitignored build artifacts (jars,
native binaries)
- Automatic `outDir` exclusion from asset copies
## Changes
**New infrastructure:**
- Add `copy-assets` createNodesV2 plugin in `tools/workspace-plugin`
- Add `copy-assets` executor (simplified from `legacy-post-build` — just
copies assets, no package.json field manipulation)
- Extract `normalizeAssets` and `getAssetOutputPath` into reusable
module in `packages/js`
- Add `copyReadme` namedInput for copy-readme build targets
**Migration (all 37 packages):**
- Create `assets.json` for every package defining what to copy
- Remove all `legacy-post-build` targets from project.json files
- Remove `legacy-post-build` target defaults from nx.json
- Remove redundant `copy-local-native` target (replaced by
`.node`/`.wasm` in asset glob)
**Cleanup:**
- Remove dead config: `creator-files` globs, non-existent template
files, typo'd directory names
- Use root-level `tsconfig*.json` ignore instead of recursive (so
template tsconfigs in `files/` dirs are still copied)
- Replace `!(*.ts)` extglob patterns with explicit globs (extglobs don't
work correctly in Nx inputs)
- Fix gradle lint: add `buildTargets: ["build-base"]` and `tslib`
dependency
- Add jar outputs to maven `_package` target for correct
`dependentTasksOutputFiles` resolution
**Other fixes:**
- Exclude `.swc` directories from sandbox write checks
- Enable typecheck for `angular-rspack` packages (remove
`addTypecheckTarget: false`)
- Pin workspace-plugin deps to explicit versions, add `@nx/plugin` to
root package.json
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
The cloud prompt in CNW is disabled (shouldShowCloudPrompt returns
false). Users see no cloud prompt during workspace creation.
## Expected Behavior
Re-enables the cloud prompt with 3 copy variants tied to the flow
variant (NX_CNW_FLOW_VARIANT), using the existing A/B testing
infrastructure:
- Variant 0 (baseline): "Connect to Nx Cloud?"
- Variant 1 (remote caching): "Enable remote caching to speed up builds
and CI?"
- Variant 2 (CI-first): "Speed up your CI with Nx Cloud?"
Each variant has a unique tracking code for measuring yes/skip/never
rates. New variants emphasize concrete benefits (remote caching, CI
speed), mention CI providers (GitHub, GitLab), and note free tier and
2-minute setup.
Also removes unused shouldShowCloudPrompt() function.
## Screenshots
Variant 0 (current prompts):
<img width="1392" height="935" alt="cnw-variant-0"
src="https://github.com/user-attachments/assets/38187e44-5c8f-41b1-b2d9-bdb0eead3f7d"
/>
Variant 1 (remote cache to speed up builds, free for small teams):
<img width="1392" height="939" alt="image"
src="https://github.com/user-attachments/assets/ba7ab127-c91e-4566-bae6-6f7fe797959e"
/>
Variant 2 (speed up CI, mention CI provides):
<img width="1392" height="935" alt="cnw-variant-2"
src="https://github.com/user-attachments/assets/05bc027a-dcc9-47c4-944c-35ca2fce2007"
/>
## Related Issue(s)
Closes NXC-4113
Currently, updating to 22.7.0-beta.5 causes Angular and React unit tests
to fail when we are pulling in Vite 8 instead of 7.
Cypress component tests do not support Vite 8 yet, so let's ensure that
the unit tests set up v7 for now.
https://github.com/cypress-io/cypress/issues/33078
## Current Behavior
The FreeBSD native build in the publish workflow runs out of disk space
(`No space left on device`). The VM has an 11G disk and the Rust build
fills it completely. The build was already on the edge — the Mar 25 run
succeeded with only 11MB to spare, and the Mar 26 run failed after rustc
1.94.1 slightly increased artifact sizes.
A major contributor is OpenJDK17 and its ~30 X11/font dependencies
(~500MB) being installed solely for the `@nx/gradle` plugin's project
graph step, which is irrelevant to building native Rust bindings.
## Expected Behavior
The FreeBSD build completes successfully with comfortable disk headroom
by disabling the Gradle plugin via `NX_GRADLE_DISABLE=true`, which
eliminates the need for Java and its heavy dependency tree.
## Related Issue(s)
Fixes the FreeBSD build failure:
https://github.com/nrwl/nx/actions/runs/23613960439/job/68776656960
## Current Behavior
The webinar banner has a hardcoded dark background (`bg-zinc-950`) with
white text in both light and dark mode. This makes the close button
barely visible in light mode since Starlight's global styles override
inherited text colors. The banner design also differs noticeably from
the Framer marketing site, which uses a light background in light mode.
## Expected Behavior
The banner adapts to the current theme:
- **Light mode**: White background, subtle border, dark text, dark CTA
button — matching the Framer marketing site design
- **Dark mode**: Retains the existing dark background with white text
and pink CTA button
All interactive elements (close button, CTA buttons) have proper
contrast in both modes.
Dark:
<img width="759" height="499" alt="image"
src="https://github.com/user-attachments/assets/684113e8-cc48-43df-b17d-431ae8c864fc"
/>
Light:
<img width="635" height="318" alt="image"
src="https://github.com/user-attachments/assets/9ed514b7-dfe9-45c6-91d7-158434aa6cdc"
/>
## Related Issue(s)
Fixes DOC-457
## Current Behavior
When the Gradle plugin invokes `gradlew nxProjectGraph` to generate the
project graph, there is no timeout. If Gradle hangs or takes an
unexpectedly long time, the Nx process blocks indefinitely with no
feedback to the user.
## Expected Behavior
The Gradle project graph generation now has a configurable timeout that
defaults to 60 seconds. If the process exceeds this limit:
- The Gradle process is aborted via an `AbortController` signal
- A clear error message is shown with actionable remediation steps:
- Run `gradlew --stop` and `gradlew clean`
- Increase the timeout via `NX_GRADLE_PROJECT_GRAPH_TIMEOUT`
- Disable the plugin entirely via `NX_GRADLE_DISABLE=true`
Users can configure the timeout by setting the
`NX_GRADLE_PROJECT_GRAPH_TIMEOUT` environment variable (in seconds).
Invalid or non-positive values fall back to the 30-second default.
Documentation for the new environment variable has been added to the
environment variables reference page.
## Related Issue(s)
Fixes NXC-4140
## Current Behavior
Getting started pages have inconsistent navigation patterns. Some use
card grids, some use bullet lists, and some have no next steps at all.
The new tutorial series is not linked from most getting started pages.
## Expected Behavior
All getting started pages use consistent bullet-list navigation with
clear next steps. Every page links to the tutorial series so first-time
users can discover it from any entry point.
For example, on the "Add to an existing project" page:
<img width="1011" height="709" alt="image"
src="https://github.com/user-attachments/assets/6534d490-1bdf-4026-9962-0a60506564ce"
/>
### Changes
- **intro.mdoc**: Updated tutorial link from generic "Follow a tutorial"
to "Follow the tutorial series" pointing to the first tutorial
- **installation.mdoc**: Added "Next steps" section linking to new
project, existing project, and tutorial series
- **start-new-project.mdoc**: Added "Next steps" section with tutorial
series, editor setup, and CI setup links
- **start-with-existing-project.mdoc**: Replaced card grid with bullet
list for in-depth guides, added "Keep learning" section with tutorial
series link
- **sidebar.mts**: Fixed label consistency ("Reduce boilerplate" →
"Reducing boilerplate")
## Current Behavior
Running `nx watch --projects nx -i -- nx build nx` enters an infinite
rebuild loop. Two issues cause this:
1. **Overly broad output globs**: `build-base` declared
`{projectRoot}/src/**/*.d.ts` and `legacy-post-build` declared
`{projectRoot}/**/*.d.ts` as outputs. Cache restore deletes and
recreates committed `.d.ts` files (like `schema.d.ts`,
`perf-hooks.d.ts`) that aren't actual build outputs, triggering the file
watcher.
2. **`declarationDir` in source tree**: With `declarationDir: "."`, tsc
wrote `.d.ts` files into the source tree, making them vulnerable to
cache restore churn.
## Expected Behavior
`nx watch` should not loop when the build produces the same outputs.
Only files actually produced by a build target should be declared as
outputs.
## Changes
- **Move `declarationDir` to `dist`**: Declaration files now go to
`dist/` alongside `.js` output, keeping the source tree clean
- **Remove manual output overrides from `build-base`**: The
`@nx/js/typescript` plugin correctly infers `{projectRoot}/dist`
- **Narrow `legacy-post-build` outputs**: Only declares
`{projectRoot}/dist` since that's the only directory the executor writes
to
- **Fix package.json exports**: Replace invalid `**` subpath patterns
with `*` (Node.js exports only support `*` as wildcard)
- **Add `typesVersions`**: Ensures TypeScript projects using
`moduleResolution: "node"` (which ignores exports maps) can still
resolve `.d.ts` files in `dist/`
- **Remove `@types/minimatch`**: `minimatch@10` ships its own types; the
`@types` package was outdated and conflicted
- **Add verbose watcher logging**: Behind `NX_VERBOSE_LOGGING`, shows
which files were created/updated/deleted by the workspace watcher
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
The nightly failure report already includes per-error details, but has
several accuracy and completeness issues:
1. **Incorrect per-error start dates** — e.g., `e2e-web`'s
`file-server-legacy.test.ts` reports "failing since 2026-03-24 (1 day)
NEW" when the `edgesOut` error actually started on 2026-03-18 (7 days).
The validation only compared test file names, not error content, so
same-file-different-error changes were missed.
2. **Different combo errors merged** — `e2e-web` fails with different
root causes on different combos (yarn: "could not find a copy of vite to
link", npm: "Cannot read properties of null (reading 'edgesOut')"), but
only one error block is shown with all combos listed together.
3. **Build failures show no useful details** — when tests don't run
because a build step fails (e.g., `gradle:build-base` with TS errors),
the report shows "No test output available" instead of the actual build
errors.
4. **No clickable links to jobs** — the golden projects table is a plain
code block with no links to the specific failed jobs.
5. **Other projects table** wastes vertical space with a full code-block
table.
## Expected Behavior
All issues above are fixed:
**Summary section** — golden projects with clickable job links, compact
other-projects list:
```
🌟 Golden Projects
✅ Passing: 18 | ❌ Failing: 2
🚨 Failed Golden Projects
e2e-react-native
· MacOS/npm/20 ← clickable link to job
e2e-web
· MacOS/npm/20 ← clickable link to job
· Linux/yarn/20 ← clickable link to job
· Linux/npm/20 ← clickable link to job
⚠️ Failed Other Projects: e2e-angular (4), e2e-nuxt (4), e2e-nx (10), ...
```
**Failure details** — per-combo errors shown separately with accurate
start dates:
```
e2e-web — 3 combos (npm+yarn)
Project failing since 2026-03-13 | Last fully passing: 2026-03-12
📋 file-server-legacy.test.ts (Linux/yarn/20) — failing since 2026-03-13 (12 days)
error Invariant Violation: could not find a copy of vite to link ...
📋 file-server-legacy.test.ts (MacOS/npm/20, Linux/npm/20) — failing since 2026-03-18 (7 days) ⚠️ error changed mid-streak
npm error Cannot read properties of null (reading 'edgesOut') ...
```
**Build failures** — when tests don't run, the report now shows the Nx
failure summary AND the actual failed task output:
```
⚠️ Tests did not run — failed at step: Run e2e tests with pnpm (Linux/Windows)
NX Running target e2e-local for project e2e-docker and 153 tasks it depends on failed
Failed tasks: gradle:build-base
❌ > nx run gradle:build-base
> tsc --build tsconfig.lib.json
error TS6307: File '...assert-supported-platform.ts' is not listed within the file list ...
```
## Changes
- **Error signature comparison** — compares normalized error messages
(not just file names) between the current run and first-failing run.
Dynamic parts (temp paths, random IDs, timestamps, versions) are
stripped before comparison. Different errors on different combos are
detected and reported separately.
- **Signature-based binary search** — when the error changed mid-streak,
finds when the current error ACTUALLY started by checking error
signatures in historical runs across all combos.
- **Failed Nx task block capture** — extracts the `❌ > nx run <task>`
output block with actual build errors, plus a fallback for generic error
patterns when no Nx task markers are found.
- **Clickable job links** — each failing combo in the golden projects
summary links to its specific GitHub Actions job.
- **Compact other-projects** — replaced the code-block table with a
one-liner count summary.
- **Graceful degradation** — if failure details collection fails, the
brief report still posts with a warning.
## Current Behavior
The Tutorials section has three large framework-specific tutorials
(React Monorepo, Angular Monorepo, TypeScript Monorepo) that each teach
everything at once. They are 500+ lines, tightly coupled to a specific
tech stack, and don't build knowledge incrementally.
## Expected Behavior
Seven focused, technology-agnostic tutorials that each teach one concept
in ~5 minutes. The design follows progressive disclosure so users learn
one thing well before moving to the next.
Preview:
https://deploy-preview-34998--nx-docs.netlify.app/docs/getting-started/tutorials/crafting-your-workspace
### Design goals
- **Learn the basics in an hour**: A user going through all 7 tutorials
covers workspace structure, dependencies, tasks, running, caching,
debugging, and plugins.
- **AI agent friendly**: Each page has an `llm_copy_prompt` at the top
that an AI agent can use as a tutor prompt. A user can paste this into
Claude Code, Cursor, or any AI tool and be guided through the topic in
their own workspace.
- **No assumed workspace state**: Each tutorial works whether you used
`create-nx-workspace`, ran `nx init` on an existing repo, or jumped to a
specific topic. Pages link back to prerequisites when needed.
- **One concept per page**: Progressive disclosure means plugins aren't
mentioned until tutorial 7, `nx graph` isn't shown until tutorial 6, and
caching configuration doesn't appear until tutorial 5.
### Tutorial sequence
1. **Crafting your workspace** - Nx as a build intelligence layer,
workspace structure, package manager workspaces, TypeScript
solution-style project references, adding projects
2. **Managing dependencies** - Workspace libraries, buildable vs
non-buildable (with `exports` and `customConditions`), `workspace:*`
protocol, single version policy with catalogs
3. **Configuring tasks** - `package.json` scripts vs `project.json`
targets, `dependsOn` with `^` syntax, continuous tasks, named
configurations, target defaults
4. **Running tasks** - `nx run`, shorthand, `run-many` with
`--targets`/`--projects`, task ordering with SVG diagram, parallelism
control, passing arguments
5. **Caching tasks** - Run-twice demo, computation hashing with SVG
diagram, inputs/outputs, named inputs, env vars/runtime inputs,
sandboxing, remote caching with `nx connect`
6. **Understanding your workspace** - `nx graph` as entry point, project
graph with edge screenshots, task graph with screenshots, `nx show
project`/`nx show target`, affected analysis with `--base`/`--head`,
cache debugging
7. **Reducing configuration boilerplate** - `targetDefaults`, Nx
plugins, inferred tasks, `nx add`, configuration cascade, presented as
optional
### Other changes
- **CI tutorial rewritten** to cover remote caching, affected (with
`NX_BASE`/`NX_HEAD`), Nx Agents, and self-healing (was previously
self-healing only)
- **Concept pages** ("How Nx works") link to relevant tutorials via
"Learn by doing" callouts
- **Redirects** from old tutorial URLs to new pages
- **Cross-references** updated across 11 technology/guide pages
- **SVG diagrams** for task dependency ordering and cache hash flow
(same style as sandboxing page)
- **Screenshots** for project graph edge detail and task graph views
- **Sidebar**: Tutorials group has "New" badge, collapsed by default
## Screenshots
Tutorials topics as ordered in sidebar:
<img width="324" height="290" alt="image"
src="https://github.com/user-attachments/assets/87b27e6a-30f6-4c9b-b52d-b371f66e72e8"
/>
AI instructions that can be copied and pasted into Claude Code to act as
a tutor:
<img width="803" height="308" alt="image"
src="https://github.com/user-attachments/assets/2dcf402e-19a6-4cf4-bcac-7005491529bf"
/>
Linking to prev/next tutorials at the bottom of each tutorial topic:
<img width="871" height="457" alt="image"
src="https://github.com/user-attachments/assets/b9a198b5-edc7-4afd-8c13-fd70c10cf858"
/>
For concept pages that are covered by a tutorial, link to the tutorial
in `Next steps`:
<img width="945" height="435" alt="image"
src="https://github.com/user-attachments/assets/a05d1c0b-c95a-42c5-baec-1fb30dff7c64"
/>
## Related Issue(s)
Fixes DOC-452
## Current Behavior
The `@nx/vite` plugin only supports Vite 5, 6, and 7. Users on Vite 8
get peer dependency errors:
```
peer vite@"^5.0.0 || ^6.0.0 || ^7.0.0" from @nx/vite
```
## Expected Behavior
Full Vite 8 support for new and existing workspaces:
**Nx plugin support:**
- Update peer deps to include `^8.0.0` in `@nx/vite` and `@nx/vitest`
- Default new workspaces to Vite 8 (`viteVersion = '^8.0.0'`)
- Bump `@vitejs/plugin-react` to `^6.0.0` (required for Vite 8, uses Oxc
instead of Babel)
- Add `useViteV7` backward compatibility flag (follows existing
`useViteV5`/`useViteV6` pattern)
**Rolldown migration (Vite 8 replaced Rollup with Rolldown):**
- Handle both `rollupOptions` (Vite <8) and `rolldownOptions` (Vite >=8)
in build executor and plugin detection
- Fix build executor environments API to preserve env-specific
`rolldownOptions` config
- Update e2e tests for Rolldown's different module counts
**Type fixes for Vite 8's ESM-only declarations:**
- Vite 8 ships `.d.mts` type declarations not resolvable under
`moduleResolution: "node"`
- Fix `typeof import('vite')` and `import type` usages across
`@nx/vite`, `@nx/vitest`, `@nx/cypress`, `@nx/react`, `@nx/angular`,
`@nx/remix` with inline casts
- All fixes have `TODO(jack)` comments to remove when switching to
`moduleResolution: "nodenext"`
**Plugin compatibility:**
- `@vitejs/plugin-react@^6.0.0` only supports Vite 8; `^4.2.0` for Vite
<=7
- `ensure-dependencies` detects installed vite version and picks the
correct plugin-react version
- Cypress CT does not support Vite 8 yet — e2e test downgrades to Vite 7
**Angular vitest fix:**
- `@angular/build` depends on `rolldown` which injects
`@oxc-project/runtime` helpers at transform time without declaring it as
a dependency
- Add `@oxc-project/runtime` as an explicit devDependency in the Angular
vitest generator
**Docs:**
- Updated supported versions table to include `^8.0.0`
- Added `rolldownOptions.input` to buildable project detection docs
**E2E coverage:**
- New Vite 8 + React build/test e2e test
- New Vite 7 + React backward compat e2e test (downgrades vite +
plugin-react)
- Updated environments API test for Vite 8 `rolldownOptions`
- Updated incremental build test for Rolldown's module counts
- Cypress CT e2e downgrades to Vite 7 (Cypress doesn't support Vite 8
yet)
## Other notes
We'll do a follow-up PR to include migrations for 22.7.0. This PR's
scope is only to ensure peer deps and our generators work for workspaces
that are already using Vite 8.
## Related Issue(s)
Fixes#34849
Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
## Current Behavior
The `update-all-repos` target explicitly lists each repo target in its
`dependsOn`. Adding a new repo requires updating this list manually.
There is no target for updating the nx-labs repo.
## Expected Behavior
- New `update-nx-labs-repo` target added for updating the nx-labs
repository
- `update-all-repos` uses a `update-*-repo` glob pattern in `dependsOn`,
so new repo targets are automatically picked up
## Current Behavior
The `pushToGitHub` flow in create-nx-workspace calls `gh api user` and
`gh repo create --push` with no timeout. When `gh` CLI is wrapped by
1Password SSH agent, credential managers with GUI prompts, SSH keys with
passphrases, or corporate SSO, these calls hang indefinitely — freezing
the CLI after workspace creation with no indication of what's happening.
YTD data shows a **96.3% failure rate** (16,426 failures out of 17,058
push attempts).
## Expected Behavior
- The initial `gh` auth check (`gh api user`) has a **2-second
timeout**. If `gh` is slow to respond (hung on credential prompt), the
push flow is skipped gracefully instead of freezing.
- The `gh repo create --push` command has a **15-second timeout**. If
the push hangs, the process is killed and a helpful message is shown.
- If `gh` CLI is not installed, the push flow is skipped immediately
without attempting any network calls.
- Error messages now include actionable fallback: *"You can push
manually with: git push -u origin main"*.
The error is now captured in `recordStat` as well, so when users see
this we'll also see it in our stats.
<img width="1026" height="334" alt="image"
src="https://github.com/user-attachments/assets/10c0b4a8-9083-4063-9558-88ebb8a5a850"
/>
## Related Issue(s)
Fixes#34482, NXC-4141
## Current Behavior
When a task is prematurely completed (e.g., due to a failure that causes
early termination) before its dependents have been scheduled, calling
`scheduleNextTasks` crashes the batch executor. The crash occurs in
`processTaskForBatches`, which traverses reverse dependencies and
attempts to read `notScheduledTaskGraph.dependencies[task.id]` for a
task that was already removed from `notScheduledTaskGraph` via
`complete()`. This yields `undefined` for the dependencies array,
causing downstream code to throw.
## Expected Behavior
When a task has been prematurely completed before batch scheduling runs,
`processTaskForBatches` should skip that task gracefully rather than
crashing. Tasks that were removed from `notScheduledTaskGraph` early
should be detected and skipped during batch traversal.
## Related Issue(s)
Fixes NXC-4144
This pull request makes a targeted improvement to how TypeScript
dependency tracking is handled in the build system. Specifically, it
ensures that all `*.d.ts` files from dependent projects are included as
inputs, improving type safety and correctness in incremental builds.
Dependency tracking improvements:
* Updated the `getInputs` function in `plugin.ts` to add all `*.d.ts`
files from dependencies as tracked inputs, ensuring that changes to type
definition files in dependent projects are properly detected and trigger
rebuilds.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
## Current Behavior
When a user selects "yes" to connect to Nx Cloud during
`create-nx-workspace`, the Cloud setup URL is only displayed in the
terminal banner. The user must manually click or copy the link to
complete onboarding.
## Expected Behavior
After displaying the banner, the setup URL is automatically opened in
the user's default browser, removing friction at the critical Cloud
conversion moment.
- Skips browser open in CI environments (`CI=true`, GitHub Actions,
etc.)
- Fails gracefully if the browser cannot be opened (e.g., headless/no
display server) — the URL remains visible in the terminal
- Only triggers when user actually connected to Cloud (not for "Maybe
later" / `skipCloudConnect`)
## Related Issue(s)
Fixes NXC-4112
## Current Behavior
When `nx migrate latest` runs, it installs the target version of `nx`
into a temporary directory and executes that published CLI. During
migration, Nx checks whether `nx` is already installed in the workspace
by resolving `nx/package.json`.
This used to resolve through the workspace-oriented lookup paths, so
migrate correctly detected the workspace-installed `nx` package and
expanded the first-party Nx package group.
That changed recently with `chore(core): build nx to local dist and use
nodenext (#34111)`. As part of that work, the published `nx` package
gained an `exports` map. In Node, a package with `name: "nx"` and
`exports` can self-reference by package name. As a result, when code
running inside the temporary published `nx` package resolves
`nx/package.json`, Node now resolves that request back to the temporary
package itself.
The resolved path points outside the workspace root, so migrate's
existing safety check treats `nx` as not installed in the workspace.
Once that happens, migrate only updates `nx` and never expands the rest
of the first-party Nx package group.
Separately, provenance package-group lookup assumes a
source-layout-relative path to `package.json`, which does not hold for
published artifacts built into local `dist/`.
## Expected Behavior
Published temporary migrate CLIs should still resolve the
workspace-installed `nx` package when migrate determines what is
installed in the workspace. That preserves the existing package-group
migration behavior even after the recent `exports`-based packaging
change.
Provenance package-group lookup should resolve the manifest of the
currently running `nx` package in a way that works for published
artifacts as well as source checkouts.
## Current Behavior
When using `compiler: 'swc'` with `useLegacyTypescriptPlugin: false` in
rollup config, builds fail with errors like:
```
ERROR failed to read input source map: failed to find input source map file "index.js.map"
```
SWC defaults `inputSourceMap` to `true`, causing it to look for
`.js.map` files for TypeScript source inputs that obviously don't have
them.
## Expected Behavior
Builds should succeed without source map resolution errors. Rollup
handles source maps via its own output pipeline — the SWC transform step
should not independently try to resolve input source maps.
## Related Issue(s)
Fixes#32671
## Current Behavior
When removing or moving a project in an Nx workspace, the `owners` and
`conformance` sections in `nx.json` are not updated. This leaves stale
project references in:
- `conformance.rules[].projects` (both plain strings and `{ matcher }`
objects)
- `owners.patterns[].projects` (top-level and section-level for GitLab)
## Expected Behavior
- **On project removal**: Strip the removed project from all conformance
rules and owners patterns. Remove entries that become empty after
cleanup (rules with no projects, patterns with no projects).
- **On project move/rename**: Rename all references to the old project
name with the new name in conformance rules and owners patterns
(including section-level patterns for GitLab-style CODEOWNERS).
- **Schema**: Add `owners` configuration schema to `nx-schema.json` for
validation and IDE support, including sections (GitLab CODEOWNERS).
## Related Issue(s)
<!-- No specific issue linked -->
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: barbados-clemens <barbados-clemens@users.noreply.github.com>
## Current Behavior
The `TaskResult` interface exported from the devkit is missing
`startTime` and `endTime` properties. At runtime, these properties are
present on `TaskResult` objects — set by the task orchestrator during
execution and consumed by multiple lifecycle implementations (profiling,
timings, history, store-run-information) — but the public type does not
declare them. This means the [TaskResult reference
docs](https://nx.dev/docs/reference/devkit/TaskResult) don't show these
fields.
## Expected Behavior
The `TaskResult` interface includes optional `startTime` and `endTime`
properties (Unix timestamps) matching what is actually present on
runtime objects. The generated devkit reference docs will now include
these fields.
## Related Issue(s)
N/A
## Current Behavior
`shut down after last hook`
## Expected Behavior
`shut down after [hook]`
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
Currently, rebuilds produce a summary table that is not accurate.
A key difference in how Rspack sets `chunks.rendered` compared to
Webpack resulted in the logic for determining rebuilt chunks to be
incorrect.
## Expected Behavior
Ensure the summary table for emitted chunks is accurate on rebuild
## Related Issues
It does not _fully_ solve #34936, however it should be a good first step
to finding out if too many chunks are being emitted
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
lists invalid images
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
lists valid images
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes#34970
## Current Behavior
The `@nx/js` TypeScript plugin ignores the `configName` option when
constructing the typecheck target command. It always runs `tsc --build
--emitDeclarationOnly` without specifying which tsconfig to use,
defaulting to whatever `tsc` resolves on its own. This means custom
`configName` values (e.g. `tsconfig.lib.json`) have no effect on
typechecking.
## Expected Behavior
The typecheck target should pass `configName` to the `tsc --build`
command, matching how the build target already works: `tsc --build
<configName> --emitDeclarationOnly`.
## Related Issue(s)
Fixes#34274
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
Current plugin version is at 0.1.16
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Bump version to 0.1.17
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Current Behavior
- Searching for a framework (e.g. "nestjs", "vite", "angular") returns
random pages that mention the term instead of the plugin introduction
page.
- The launch templates page is too long impacting word saturation
- Search event tracking uses wrong event
## Expected Behavior
- Technology introduction pages rank first or second when searching for
their framework name.
- Launch templates are split into a focused reference page and a
separate examples page, both cross-linked.
- Use correct search event and track user selected item from query
- enforce usage of pagefind filter names in content collection schemas
Fixes DOC-408
## Current Behavior
The `postinstall` script in `packages/nx/package.json` runs `node
./dist/bin/post-install` which prints noisy error logs to stderr when
the `dist` directory hasn't been built yet (e.g. during `pnpm install`
in the workspace). The script already exits cleanly via `|| exit 0`, but
the error output is confusing.
## Expected Behavior
The postinstall script silently succeeds when the dist directory doesn't
exist, without printing error logs to the console.
## Related Issue(s)
N/A - minor DX improvement
## Current Behavior
Running `nx add @nx/vitest` does not register the `@nx/vitest` plugin in
`nx.json`. The init generator had a wrapper function (`initGenerator`)
that hardcoded `addPlugin: false` before spreading the user-provided
schema. Since `nx add` calls the generator via CLI without passing
`addPlugin`, the value stayed `false` — and the `??=` default logic in
`initGeneratorInternal` never fired because `false` is not nullish.
## Expected Behavior
Running `nx add @nx/vitest` should register the `@nx/vitest` plugin in
`nx.json`, matching the behavior of other plugins like `@nx/jest`,
`@nx/vite`, and `@nx/playwright`.
## Related Issue(s)
<!-- No existing issue tracked for this -->
## Changes
- Merged `initGenerator` and `initGeneratorInternal` into a single
`initGenerator` function that uses the `??=` default logic directly
- Added unit tests for the init generator covering plugin registration
defaults, `NX_ADD_PLUGINS` env var, `addPlugin: true` vs `false`
behavior, package.json handling, and namedInputs
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
When the Gradle plugin's regex-based test parser (used as a fallback
when AST parsing fails) encounters a Kotlin file
containing enum classes, those enum classes are incorrectly included as
test atomization targets. This causes spurious
test entries to appear for enum types like TestStatus or Priority that
are not actual test classes.
## Expected Behavior
Enum classes are excluded from the list of discovered test classes
during atomization, consistent with how abstract
classes and annotation classes are already filtered out. Only real test
classes should be identified as atomization targets.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
The `update-repos` tool clones repositories into `/tmp` and runs `pnpm
install` / `nx migrate` without trusting the repo's mise configuration.
This means the wrong tool versions (node, bun, etc.) may be used.
Additionally, `nrwl/nx-labs` is not included in the update-repos config.
## Expected Behavior
- Run `mise trust` before installing dependencies if the cloned repo has
a `mise.toml` or `.mise.toml`, ensuring correct tool versions are
activated.
- Include `nrwl/nx-labs` in the repos that get updated.
## Related Issue(s)
N/A - internal tooling improvement
This pull request refactors the caching strategy in the task hashing
logic to improve cache isolation and correctness. The main change is to
eliminate shared, long-lived caches in favor of creating fresh caches
for each invocation, preventing stale data from persisting across CLI
commands. Additionally, new caches for project and workspace file set
hashes are introduced, and cache handling is streamlined for better
maintainability.
**Cache management improvements:**
* Removed the `runtime_cache` field from the `TaskHasher` struct and now
create fresh `DashMap` caches for runtime, project file set, and
workspace file set hashes within each invocation of the main hash
function. This ensures no stale cache data persists across CLI commands.
[[1]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL153)
[[2]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL184)
[[3]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL196-R208)
* Updated the `hash_runtime` function and its usage to accept a
reference to a `DashMap` instead of an `Arc<DashMap>`, simplifying
ownership and concurrency concerns.
[[1]](diffhunk://#diff-77cbfff0572545027ac8ad107859d5135e6f78ee7c32babc56b2c9bbf89cfaccL5-R11)
[[2]](diffhunk://#diff-77cbfff0572545027ac8ad107859d5135e6f78ee7c32babc56b2c9bbf89cfaccL54-R61)
**File set hash caching:**
* Introduced the `CachedFileSetHash` struct to store both the hash value
and the list of matched files for project and workspace file set
hashing, enabling more efficient input collection and cache lookups.
* Added per-invocation caches for project and workspace file set hashes,
with logic to check the cache before computing a new hash and to insert
results after computation.
[[1]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL196-R208)
[[2]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR353-R385)
[[3]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR412-R438)
[[4]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR554-R556)
**Function signature and argument updates:**
* Updated function signatures and argument passing to propagate the new
cache references throughout the hashing logic, including
`HashInstructionArgs` and related functions.
[[1]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR264-R266)
[[2]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR341-R343)
[[3]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR554-R556)
Fixes#30170
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Nx uses its package version as the DB version. Any Nx version change
**wipes the entire database**, losing all task history and cache
metadata. With multiple worktrees, each worktree maintains its own
separate DB — so switching between them constantly nukes task history,
and worktrees can't benefit from each other's cached results.
## Expected Behavior
1. **Versioned DB filenames**: DB version is decoupled from Nx version.
A new `DB_VERSION` constant is bumped only when the schema changes. The
version is encoded in the filename (`{machine_id}-v{DB_VERSION}.db`), so
multiple schema versions coexist on disk without wiping each other.
2. **Shared DB across worktrees**: When running inside a git worktree,
Nx detects the main repo root and stores the DB in the main repo's
`.nx/workspace-data/` directory. All worktrees share the same task
history, cache metadata, and task details — but **running tasks are
tracked per-worktree** since they are inherently local to each working
copy.
### Changes
- Add `DB_VERSION` constant in `initialize.rs` (bumped only on schema
changes)
- Encode version in DB filename: `{machine_id}-v1.db`
- Remove `nx_version` parameter from `connect_to_nx_db` and
`initialize_db`
- Remove metadata table entirely — schema version is in the filename, so
no runtime version check needed
- Simplify `initialize_db` to use file-existence check instead of
querying metadata
- Add `get_main_worktree_root` napi function in a dedicated `worktree`
module for worktree detection via `git rev-parse --git-common-dir`
- Add `sharedWorkspaceDataDirectory` in TypeScript that resolves to the
main repo's workspace-data dir when in a worktree
- Add `getLocalDbConnection` for per-worktree data (e.g. running tasks)
that should not be shared
- Running tasks use a local DB to avoid false "already running"
conflicts between worktrees
- Daemon stays per-worktree (no changes needed)
- Stale DB files from old schema versions are cleaned up automatically
after 7 days
### What's shared vs local
| Data | Scope | Why |
|------|-------|-----|
| `task_details` | Shared | Keyed by content hash — same code = same
hash regardless of worktree |
| `task_history` | Shared | More data = better time estimates and flaky
detection |
| `cache_outputs` | Shared | Cache lives in one place, tracking should
too |
| `running_tasks` | **Per-worktree** | Each worktree runs tasks
independently — sharing would cause false conflicts |
### Migration
- Old `{machine_id}.db` files are simply ignored (Nx now looks for
`{machine_id}-v1.db`)
- No data migration — fresh DB on first use (same as today on version
bumps)
- Old files cleaned up on `nx reset` and automatically after 7 days of
inactivity
## Related Issue(s)
<!-- N/A - internal improvement -->
## Current Behavior
`INSERT OR REPLACE` is used in `task_details` and `cache_outputs`
tables. The bundled SQLite (`libsqlite3-sys`) is compiled with
`SQLITE_DEFAULT_FOREIGN_KEYS=1`, so FK constraints are enforced by
default. `INSERT OR REPLACE` does a DELETE + INSERT on PK conflict, and
the implicit DELETE on `task_details` fails when child rows exist in
`task_history` or `cache_outputs`:
```
NX DB transaction error: SqliteFailure(Error { code: ConstraintViolation, extended_code: 787 }, Some("FOREIGN KEY constraint failed"))
```
This happens because `record_task_details` is called multiple times with
the same hash across different code paths (`hashTask`, `hashTasks`,
`hashBatchTasks`), and by the second call, child rows already reference
that hash.
## Expected Behavior
Use `INSERT ... ON CONFLICT DO UPDATE` (upsert) which updates the
existing row in-place without deleting it. No DELETE means no FK
violation, while preserving the same idempotent behavior the code relies
on.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The patched jest resolver handles `nx/*` imports differently for unit
tests and e2e tests:
- **Unit tests**: Manual filesystem-based resolution with regex matching
and `fs.existsSync` checks to find `.ts` source files
- **E2e tests**: Falls through to `options.defaultResolver`, which
follows pnpm `workspace:*` symlinks to `packages/nx/` and resolves via
the package.json exports map — landing on `dist/*.js` files instead of
`.ts` source
This causes ~200 sandbox warnings for undeclared file reads from
`packages/nx/dist/` during e2e task execution, since those files aren't
listed as task inputs.
Additionally, `e2eInputs` in `nx.json` references
`{workspaceRoot}/jest.preset.js` (the root unit test preset) instead of
the actual e2e preset at `{workspaceRoot}/e2e/jest.preset.e2e.js`.
## Expected Behavior
Both unit and e2e tests resolve `nx/*` imports using the `@nx/nx-source`
custom export condition via `options.defaultResolver`. This leverages
the existing exports map in `packages/nx/package.json` to resolve to
`.ts` source files, eliminating:
- The manual filesystem resolution code for `nx/*` (regex matching,
`fs.existsSync` calls)
- The e2e-specific code path that fell through to compiled `.js` files
- Sandbox warnings for undeclared reads from `packages/nx/dist/`
The `e2eInputs` preset reference is corrected to point to the actual e2e
preset file.
## Related Issue(s)
<!-- No specific issue — this was discovered during sandbox warning
investigation -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
After the nodenext PR (#34111) added an `exports` map to
`packages/nx/package.json`, the nx-cloud light client (ocean) fails to
import `nx/src/native`.
The wildcard exports pattern `"./src/*"` resolves `nx/src/native` to
`./dist/src/native.js` — but the actual file is
`./dist/src/native/index.js` (it's a directory with an index file).
Node's exports map does literal `*` substitution and does **not**
perform CJS-style directory/index resolution.
The cloud client wraps all its nx imports in a single try/catch, so when
the native import fails, `getDbConnection` is never assigned either,
causing `getDbConnection is not a function` errors in CI.
This was not an issue with `nx@22.7.0-beta.1` because that version had
no `exports` map — Node fell back to normal CJS resolution which handles
directory/index lookups.
## Expected Behavior
`require('nx/src/native')` correctly resolves to
`dist/src/native/index.js` via an explicit export entry, and the cloud
client can load the native module and `getDbConnection` without errors.
## Related Issue(s)
N/A — discovered during version bump CI failure investigation.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Six e2e tests are failing on master due to a Cypress uncaught exception:
`[HMR] Hot Module Replacement is disabled`. The error originates from
the webpack `styles.js` bundle during the `before each` hook, causing
Cypress to abort the test suite. This appears to be triggered by an
external dependency update.
Failing tests:
- `e2e-nx:e2e-ci--src/workspace-legacy.test.ts`
-
`e2e-react:e2e-ci--src/module-federation/independent-deployability.webpack.test.ts`
-
`e2e-react:e2e-ci--src/module-federation/core-webpack-basic-host-remote-generation.test.ts`
-
`e2e-react:e2e-ci--src/module-federation/misc-rspack-interoperability.test.ts`
-
`e2e-react:e2e-ci--src/module-federation/dynamic-federation.webpack.test.ts`
-
`e2e-react:e2e-ci--src/module-federation/federate-module.webpack.test.ts`
## Expected Behavior
The flaky tests are skipped so that master CI is green while the root
cause (likely an external dependency update) is investigated separately.
## Related Issue(s)
N/A — CI stabilization fix.
## Current Behavior
`update-all-repos` only updates ocean and nx-console repositories. There
is no way to update just those two repos separately from the full set.
## Expected Behavior
- `update-all-repos` now includes all four repos: nx, ocean,
nx-examples, and nx-console
- A new `update-repos` target updates only ocean and nx-console (the
most commonly used subset)
## Related Issue(s)
Internal tooling improvement — no external issue.
## Current Behavior
When the globally installed Nx binary (`bin/nx.ts`) runs, it calls
`setupWorkspaceContext()` **before** determining whether it should hand
off to a local Nx installation. If the global Nx version differs from
the local version (e.g. global `22.7.0-beta.2` vs local
`22.7.0-beta.1`), `isNxVersionMismatch()` returns true,
`daemonClient.enabled()` returns false, and `setupWorkspaceContext()`
creates an in-process `WorkspaceContext` that locks the workspace data
directory.
When the local Nx then tries to start the daemon, it conflicts with this
lock and the daemon fails to start. This means running `nx reset`
followed by any command (e.g. `nx show projects`) results in the daemon
never auto-starting, falling back to in-process graph construction, or
erroring out entirely.
## Expected Behavior
The global bin should not set up a workspace context when it's about to
hand off to a local Nx installation. The local Nx will handle workspace
context setup itself.
`setupWorkspaceContext()` is now only called in the `isLocalInstall` and
`isNxCloudCommand` branches — skipped in the handoff path. This matches
the existing pattern established in #34914 for analytics and DB
connections.
## Related Issue(s)
<!-- No specific issue filed — discovered during development in the nx
repo -->
## Current Behavior
The command name (e.g., `build`, `test`, `generate`) is only sent as the
`dt` (document title) parameter on `page_view` events. All other
telemetry events like `run_completed` and `project_graph_computed` have
no command context, making it impossible to correlate them with the
command that triggered them in Google Analytics.
## Expected Behavior
All telemetry events include the `dt` parameter with the command name.
The Rust telemetry background thread stores the page title when a
`page_view` is received and automatically injects it into all subsequent
events in the same process.
## Related Issue(s)
N/A — internal analytics improvement
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
The native Rust import scanner mishandles TypeScript's `import X = Y.Z`
namespace alias syntax. When it encounters this pattern, it fails to
recognize it as a non-module statement and continues scanning forward
for string literals, treating the next one it finds as a module
specifier.
On case-insensitive filesystems (macOS default HFS+/APFS), this causes
phantom npm dependencies in the project graph. For example, `import
MyBar = Foo.Bar` followed by `case 'Open':` causes `npm:open` to appear
as a static dependency because `node_modules/Open` resolves to
`node_modules/open`.
This makes `@nx/dependency-checks` impossible to satisfy across
platforms — macOS and Linux disagree on whether the phantom package is
used.
## Expected Behavior
`import X = Y.Z` is a TypeScript namespace alias, not a module import.
The scanner should skip it entirely. Only actual module imports (`import
X = require('module')`, `import ... from 'module'`, etc.) should produce
dependency entries.
After this fix:
- `import X = Y.Z` is correctly identified as a namespace alias and
skipped
- `import X = require('module')` continues to work as a valid CommonJS
import
- String literals in switch/case statements and type aliases are no
longer misidentified as imports
## Related Issue(s)
Fixes#34644
Fix duplicate migrations keys as only the last migration was getting
picked up.
## Current Behavior
When running nx migrate only the module federation migration is picked
up and not updating the core angular packages.
## Expected Behavior
Both migrations should be added to migrations.json.
## Current Behavior
The TUI sidebar scrollbar thumb position is driven by the selected
task's index among tasks (`selected_task_index`). This means the
scrollbar can appear offset from the top even when `scroll_offset=0`
(i.e., the viewport is showing the very beginning of the list), because
the selected task might not be the first one. This makes it look like
there is content above that cannot be scrolled to.
<img width="1165" height="833" alt="Screenshot 2026-03-03 at 8 53 00 PM"
src="https://github.com/user-attachments/assets/43c695ad-c4a8-40c9-85b0-7677a8b83d12"
/>
Here, it looks scrolled down...but actually all the content is in view.
## Expected Behavior
The scrollbar should accurately represent which portion of the entry
list is currently in view. When at the top of the list
(`scroll_offset=0`), the thumb should be at the top. When scrolled to
the bottom, the thumb should be at the bottom.
## Related Issue(s)
N/A (discovered via visual inspection)
## Details
Switch the scrollbar from selection-based metrics to scroll-offset-based
metrics:
- `content_length` = total entries (including spacer rows)
- `viewport_content_length` = viewport height
- `position` = scroll offset
This ensures the scrollbar reflects the actual viewport position rather
than the selected task's position within the task list.
Also adds `total_entries` and `viewport_height` fields to
`ScrollMetrics` to make these values available without additional lock
acquisitions.
Co-authored-by: Amp <amp@ampcode.com>
## Current Behavior
`--parallel=N` doesn't cap discrete task concurrency when continuous
tasks exist. `getThreadCount` inflates the thread count to `N +
continuousCount`, and all threads are fungible — any thread picks up any
task. With `--parallel=1` and 2 continuous tasks, 3 threads run,
allowing up to 3 discrete tasks concurrently.
## Expected Behavior
`--parallel=N` caps discrete task concurrency to N. Continuous tasks get
dedicated threads that don't inflate the discrete limit.
### Changes
- **Two-pool thread model**: Split the unified thread pool into discrete
and continuous pools. Each pool runs its own loop and only picks up
matching tasks.
- **`getThreadPoolSize`** (renamed from `getThreadCount`): Returns `{
discrete, continuous, total }`. Discrete pool = `options.parallel`,
continuous pool = number of continuous tasks.
- **`executeDiscreteTaskLoop`**: Handles batches + discrete tasks only.
Uses slot-based groupIds via `closeGroup`/`openGroup`.
- **`executeContinuousTaskLoop`**: Handles continuous tasks only. Uses
counter-based groupIds (`parallel + n++`). Exits once all continuous
tasks have been started.
- **`nextTask(filter?)`** on `TasksSchedule`: Optional filter param to
dequeue only matching tasks. Scheduler stays unaware of pools/limits.
## Related Issue(s)
Fixes#34117Fixes#31494
## Current Behavior
`nx run-many` with TUI enabled crashes with `TypeError: Cannot read
properties of undefined (reading 'push')` in `tui-summary-life-cycle.ts`
when `appendTaskOutput` is called for a task whose output entry was
already cleaned up by `endTasks`.
## Expected Behavior
Late-arriving task output after `endTasks` has finalized the task is
silently discarded instead of crashing. The output is redundant since it
was already captured when the task completed.
## Related Issue(s)
Fixes#34677
## Current Behavior
- `splitTarget` causes issues when a project name has more than one
colon
- Project name substitution is triggered when a project depends on the
orginal name of a renamed project, even if the renamed project was
renamed before the dep was registered
That second one is hard to understand. Imagine the real scenario below:
- @nx/gradle was reading settings.gradle.kts and naming the root project
`nx`
- The package.json inference plugin renames it to `@nx/nx-source`
- The package.json inference plugin infers the `nx` project in
`packages/nx`
- The package.json inference plugin reads a config that has `dependsOn:
[nx:build]`
- The substitutors run, and update the dependsOn to
`@nx/nx-source:build`
## Expected Behavior
splitTarget follows the below precedence:
- If splitting a target string thats embedded in a specific project
configuration, targets on that project are preferred
- Targets belonging to the project with the longest name that is valid
from joining segments left to right
- Targets that have the longest name from joining segments are preferred
- Configuration is the remaining segments after picking off the longest
valid project containing a valid target.
Substitutors prefer registering by root if a project with a given name
exists, and only register by name if no project with that root exists
(e.g. if a plugin adds a dependsOn to a project that was inferred by a
later plugin, this would be common if a custom plugin is reading
project.json to get a name or smth).
## AI Summary
> # Branch Analysis: `fix/split-target-fixes` vs `master`
>
> ## Summary
>
> | Metric | Lines |
> |--------|------:|
> | **Total raw diff (added+removed, non-test)** | 2,840 + 1,221 = 4,061
|
> | **Lines that were just moved (file split)** | ~960 |
> | **Truly new/changed lines (non-test)** | ~570 |
> | **Test file changes (raw diff)** | 3,109 added / 2,242 removed |
>
> ## Commits
>
> | SHA | Message |
> |-----|---------|
> | `57ec87b3c4` | fix(core): split-target should handle projects with
colons in name better |
> | `af1ebc90ab` | fix(core): avoid renaming projects based on former
name if they were rooted when dep is drawn |
> | `b2fffc60c7` | cleanup(core): split project-configuration-utils into
focused modules |
> | `3bae3048d8` | fix(core): fixup name substitution manager |
>
> ## File Split: `project-configuration-utils.ts` -> 4 modules
>
> The original `project-configuration-utils.ts` (1,407 lines) was split
into focused modules. **~960 lines were moved as-is** (identical minus
whitespace/formatting) into the new files. The remaining changes are
actual logic modifications.
>
> | File | Total Lines | Moved from original | Truly new/changed |
> |------|------------:|--------------------:|------------------:|
> | `project-configuration-utils.ts` (remaining) | 444 | ~395 | ~49 |
> | `target-merging.ts` | 494 | ~430 | } |
> | `target-normalization.ts` | 282 | ~250 | } ~111 combined |
> | `project-nodes-manager.ts` | 365 | ~285 | } |
> | **Subtotal** | 1,585 | ~1,360 | ~160 |
>
> Additionally, ~39 lines were removed from the original and not moved
anywhere (dead code removal or refactored away).
>
> ## Actual Code Changes (non-test, excluding moved lines)
>
> ### Major changes
>
> | File | New | Removed | Net | Description |
> |------|----:|--------:|----:|-------------|
> | `split-target.ts` | ~211 | ~38 | +173 | New logic for handling
projects with colons in names |
> | `name-substitution-manager.ts` | ~152 | ~85 | +67 | Fix: avoid
renaming rooted projects based on former name |
> | Split files (combined, new logic only) | ~111 | — | +111 | New code
introduced during the split refactor |
> | `project-configuration-utils.ts` (new logic only) | ~49 | ~39 | +10
| Residual new code after split |
>
> ### Minor edits (import path updates, small fixes)
>
> | File | Added | Removed |
> |------|------:|--------:|
> | `parse-target-string.ts` | 7 | 1 |
> | `command-line/show/target.ts` | 11 | 7 |
> | `devkit-internals.ts` | 3 | 5 |
> | `tasks-runner/utils.ts` | 5 | 3 |
> | `build-project-graph.ts` | 2 | 4 |
> | `error-types.ts` | 2 | 4 |
> | `command-line/run/run-one.ts` | 2 | 1 |
> | `ngcli-adapter.ts` | 1 | 1 |
> | `convert-nx-executor.ts` | 1 | 1 |
> | `project-configuration.ts` (generators) | 1 | 1 |
> | `package-json.ts` | 1 | 1 |
> | `settings.gradle.kts` | 1 | 1 |
> | **Minor edits subtotal** | **37** | **30** |
>
> ### Other new files
>
> | File | Lines | Description |
> |------|------:|-------------|
> | `packages/devkit/CLAUDE.md` | 62 | Dev documentation |
> | `__fixtures__/merge-create-nodes-args.json` | 100 | Test fixture
data |
>
> ## Final Tally: True Non-Test Changes
>
> | Category | Lines |
> |----------|------:|
> | New logic in `split-target.ts` | ~211 |
> | New logic in `name-substitution-manager.ts` | ~152 |
> | New logic in split module files | ~111 |
> | New logic in remaining `project-configuration-utils.ts` | ~49 |
> | Minor import/path updates across 12 files | ~37 added / ~30 removed
|
> | New non-code files (CLAUDE.md, fixture JSON) | 162 |
> | **Total truly new/changed lines** | **~570 added, ~160 removed** |
> | Moved lines (file split, not real changes) | **~960** |
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The `nx` package compiles its TypeScript output to
`../../dist/packages/nx/` (relative to the package root), which places
build artifacts outside the package directory at the repo root level
(`dist/packages/nx/`). This makes the package structure harder to reason
about, complicates the build pipeline, and doesn't align with how most
packages organize their output.
The package uses `"module": "commonjs"` with basic module resolution,
which limits future migration paths toward ESM.
## Expected Behavior
The `nx` package now builds to a local `dist/` directory within the
package itself (`packages/nx/dist/`). This is a cleaner, more standard
layout — like having your tools in your own toolbox instead of scattered
across the workshop.
### Key changes:
**Build configuration (`packages/nx/tsconfig.lib.json`):**
- `outDir` changed from `../../dist/packages/nx` to `dist` (local to
package)
- `module` changed to `nodenext` with `moduleResolution: nodenext`
- Updated `include` patterns to explicitly list source directories
**Package entry points (`packages/nx/package.json`):**
- `bin` paths updated: `./bin/nx.js` → `./dist/bin/nx.js`
- Added `"type": "commonjs"` explicitly
- Added comprehensive `exports` map with `@nx/nx-source` condition for
dev/test resolution back to TS source
- Added `postinstall` path update to `./dist/bin/post-install`
**Module resolution fixes:**
- Created `src/utils/handle-import.ts` — a CJS-first import utility that
falls back to ESM `import()` for ESM-only packages, providing a single
migration point for future ESM work
- Converted dynamic `await import()` calls to
`require(require.resolve())` pattern where needed to satisfy `nodenext`
extension requirements
- Plugin worker spawn path now uses correct `.ts`/`.js` extension based
on runtime context (source vs compiled)
**Test infrastructure:**
- Added custom `jest-resolver.js` for the `nx` package that resolves
`nx/...` imports using the `@nx/nx-source` exports condition, so tests
run against TS source
- Updated `jest.preset.js` with SWC transformer configuration
- Added chalk mock for test compatibility
**CI and tooling:**
- Conformance check updated to build `workspace-plugin` first (the Nx
Cloud runner lacks `@swc-node/register` for TS resolution)
- Conformance rule paths in `nx.json` now point to compiled
`dist/workspace-plugin/src/...` output
- Added `dist` to eslint ignore patterns to prevent linting compiled
output
- Added workspace-plugin build target and updated its dependencies
**Other fixes:**
- Various import path fixes across `create-nx-workspace`, gradle, and
other packages to work with `nodenext` resolution
- Updated e2e test paths to reference the new dist location
- Fixed `.gitignore` and `.npmignore` for the new output structure
## Related Issue(s)
Internal infrastructure improvement — no external issue.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Coly010 <Coly010@users.noreply.github.com>
Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
## Current Behavior
Framework generators append predefined configs (`flat/react`,
`flat/angular`, etc.) after `baseConfig` in the flat config array. In
flat config, later entries override earlier ones, so framework rules
override user root rules.
Additionally, the parser/plugins config entries in `flat/typescript`,
`flat/javascript`, and `flat/angular` have no `files` restriction,
applying the TypeScript parser to all files globally — including
`.html`, `.json`, and other non-TS/JS files. This was partially fixed in
PR #28381 (which scoped rules/extends) but the parser entries were
missed.
## Expected Behavior
Framework configs are inserted before `baseConfig`, giving user root
config higher priority. Root standalone projects (no `baseConfig`) are
unaffected.
Parser/plugins entries are scoped to their respective file types, so
`.html` files get the correct parser (Angular template parser, or ESLint
default) instead of the TypeScript parser.
## Changes
- Implement `checkBaseConfig` option in `addBlockToFlatConfigExport` to
insert before `...baseConfig` when present
- Framework generators (`@nx/react`, `@nx/angular`, `@nx/next`, etc.)
pass `checkBaseConfig: true`
- Scope parser entries in `flat/typescript` to `**/*.ts, **/*.tsx,
**/*.cts, **/*.mts`
- Scope parser entries in `flat/javascript` to `**/*.js, **/*.jsx,
**/*.cjs, **/*.mjs`
- Scope processor/plugins entry in `flat/angular` to `**/*.ts`
- Refactor `addPredefinedConfigToFlatLintConfig` to use an options
object for optional params
- Fix regex patterns in react-native and expo jest config templates to
use `[.]` instead of `\.` — avoids `no-useless-escape` lint errors and
fixes a latent bug where `\.` in a string literal matched any character
instead of a literal dot
## Related Issue(s)
Fixes#32923
## Current Behavior
When using Angular + Rspack + Module Federation, the
`NxModuleFederationPlugin` (Angular variant) sets `library: { type:
'module' }` on the Module Federation plugin config, which causes
`remoteEntry.js` to emit ESM `export` statements. However, the
compiler's `experiments.outputModule` and `output.module` flags are not
set, so the MF runtime tries to load the remote entry as a classic
script. This results in:
```
Uncaught SyntaxError: Unexpected token 'export' (at remoteEntry.js:48774:1)
```
This is especially broken during `nx serve` (dev server), because
`@nx/angular-rspack`'s `createConfig` only sets
`experiments.outputModule = true` for production builds, not dev server
builds.
## Expected Behavior
The Angular rspack `NxModuleFederationPlugin` should ensure
`experiments.outputModule = true` and `output.module = true` are set on
the compiler when using `library: { type: 'module' }`, so that Module
Federation works out of the box in both dev and production modes.
This aligns with how the Angular **webpack** MF config already handles
it (in `with-module-federation/angular/with-module-federation.ts`).
## Related Issue(s)
Fixes#34584Fixes#33992
## Current Behavior
The default `@nx/next:build` for nx-dev outputs to `dist/nx-dev/nx-dev`.
At runtime, Node.js can't resolve the `ai` package from that path
because `node_modules` is at the workspace root — causing
`ERR_MODULE_NOT_FOUND` on `/ai-chat`.
Additionally, `ui-video-courses` is missing `@nx/nx-dev-ui-icons` as a
dependency (introduced by #34669), causing a webpack compilation
failure.
## Expected Behavior
Build in-place to `nx-dev/nx-dev` (matching the existing Netlify config)
so `.next` stays close to `node_modules` and package resolution works.
This also simplifies config by removing the Netlify vs Vercel branching
in sitemap scripts and project.json configurations.
## Other Notes
This PR also:
- Redirects `/` to `/blog` since almost everything else, including
homepage, are in Framer.
- Uses `@nx/next/plugin` to infer targets now so we no longer use any
`@nx/next:*` executors.
- Cleans up `nx-dev` project config so it no longer has `serve-docs` and
other weird setup. It is purely just `nx dev nx-dev` or `nx build
nx-dev` or `nx start nx-dev` like a normal Next.js app.
- Removes extra `NETLIFY` env var checks to point outputs in different
places.
## Related Issue(s)
Closes DOC-418
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
## Current Behavior
Tutorials direct users to cloud.nx.app CTAs requiring GitHub account +
full cloud onboarding before the tutorial starts. CNW starts dropped to
~1,800/day weekday (target ~2,700). Self-healing CI content buried at
bottom of tutorials where only 15-20% of users scroll.
## Expected Behavior
- Tutorials use npx create-nx-workspace as primary path, cloud link kept
as secondary text link
- llm_only tags added to tell AI agents to use CLI only (they can't
handle the browser OAuth flow)
- Tutorial file trees, app names, and scopes updated to match what CNW
actually generates
- Self-healing CI extracted to standalone "Setting up CI" tutorial
- Nx Cloud page moved from Getting Started to Orchestration & CI
overview (redirect added)
- Sidebar labels converted to sentence case per style guide
- AI integrations moved before Editor setup in sidebar (higher traffic)
- Tutorials expanded by default in sidebar
- Intro page now shows CNW and nx init commands directly
## Notes
A follow-up to break tutorials down into smaller, more focused topics
will be the next step.
<img width="547" height="400" alt="image"
src="https://github.com/user-attachments/assets/b717ee54-0dc6-4c08-b1d3-0d80c9dce1df"
/>
## Related Issue(s)
Closes DOC-448
## Current Behavior
In TS solution workspaces, library generators pass `alwaysRun=true` to
`installPackagesTask` to ensure symlinks are created for new packages.
However, when the init generator already triggered an install (via
`addDependenciesToPackageJson`), the `alwaysRun` flag bypasses the cache
and forces a redundant second install.
## Expected Behavior
Only a single install should run per generator invocation. If install
already ran this cycle, subsequent `ensureInstall` calls should be
skipped since the previous install already picked up all filesystem
changes (including `pnpm-workspace.yaml` updates for symlinks).
## Fix
Renamed `alwaysRun` to `ensureInstall` in `installPackagesTask` and
simplified the install condition:
```typescript
if (packageJsonDiffers || (ensureInstall && !installAlreadyRan))
```
This handles both cases:
- **First library** (deps change): init callback runs install →
`ensureInstall` callback sees install already ran → skips. One install.
- **Second+ library** (no dep changes): init callbacks are no-ops →
`ensureInstall` callback sees no prior install → runs. One install.
## Related Issue(s)
<!-- No existing issue — discovered during investigation -->
## Current Behavior
The `dev.nx.gradle.project-graph` plugin is at version `0.1.15`.
## Expected Behavior
The `dev.nx.gradle.project-graph` plugin is bumped to version `0.1.16`,
with a corresponding Nx migration so users are automatically updated
when they migrate to `22.7.0-beta.2`.
## Related Issue(s)
N/A - routine version bump
---
### Changes
- Updated `gradleProjectGraphVersion` in
`packages/gradle/src/utils/versions.ts`
- Updated `version` in `packages/gradle/project-graph/build.gradle.kts`
- Created migration TS and MD files in
`packages/gradle/src/migrations/22-7-0/`
- Added migration entry in `packages/gradle/migrations.json`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Current Behavior
Workspace uses nx 22.7.0-beta.0 and related @nx/* packages at
22.7.0-beta.0.
## Expected Behavior
Workspace uses nx 22.7.0-beta.1 and related @nx/* packages at
22.7.0-beta.1.
## Related Issue(s)
N/A — routine version bump.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
On Windows, the Nx daemon runs as a detached background process with no
console. When child processes are spawned without `windowsHide: true`
(Node.js) or `CREATE_NO_WINDOW` (Rust/Win32), Windows allocates a new
visible console window for each subprocess. This causes command prompt
windows to flash on screen during:
- Project graph creation (daemon spawn, plugin workers)
- Task hashing (runtime hashers)
- NX Console extension detection (`code.cmd --list-extensions`, etc.)
- AI agent configuration checks (`git ls-remote`, npm install)
- Machine ID retrieval, package manager version detection, git
operations, and more
The issue is especially noticeable "after a little bit" following daemon
startup, because background operations like the NX Console status check
and AI agents configuration check kick off after the initial project
graph is computed.
## Expected Behavior
No console windows should flash on Windows. All child process spawns use
`windowsHide: true` (Node.js) or `CREATE_NO_WINDOW` (Rust) to suppress
console windows.
## Root Causes Found
Investigation using a child_process interceptor in the daemon revealed
multiple sources:
1. **Rust native `ide/install.rs`** — `Command::new("code.cmd")` calls
for NX Console extension detection/installation were missing
`CREATE_NO_WINDOW`
2. **Rust native `hash_runtime.rs`** — Had its own `CREATE_NO_WINDOW`
handling but was duplicated
3. **`nx@latest` temp install** — The daemon downloads `nx@latest` to a
temp directory for NX Console and AI agent checks. The install process
(`pnpm add -D nx@latest`) and the downloaded code's `git ls-remote`
calls run without `windowsHide: true`
4. **~120 Node.js `child_process` calls** — Various
`spawn`/`exec`/`execSync` calls across the codebase were missing
`windowsHide: true`
## Changes
### Node.js child_process fixes
- Set `windowsHide: true` on all `spawn`/`exec`/`execSync`/`spawnSync`
calls across the codebase (~120 files)
- Added custom ESLint rule `@nx/workspace/require-windows-hide` that
errors when any spawn/exec call is missing `windowsHide: true`
### Rust native fixes
- **New shared util `native/utils/command.rs`** with `create_command()`
and `create_shell_command()` that set `CREATE_NO_WINDOW` on Windows —
centralizes the pattern so future Rust code gets it right by default
- **`ide/install.rs`** — Use `create_command()` for `code.cmd` calls
(list-extensions, install-extension, version check)
- **`hash_runtime.rs`** — Replaced local `create_command_builder()` with
shared `create_shell_command()`
- **`machine_id/mod.rs`** — Updated to use shared
`create_shell_command()`
### Daemon background operation fixes
- **`handle-configure-ai-agents.ts`** — Now respects `NX_USE_LOCAL` env
var to skip downloading `nx@latest`, avoiding the pnpm install that
opens windows. Once these fixes ship in a release, the downloaded
`nx@latest` will also have the fixes.
### Inlined node-machine-id
- Replaced the `node-machine-id` npm package with an inlined
implementation in `machine-id-cache.ts` that uses `windowsHide: true`
- The original package used `exec`/`execSync` without `windowsHide`
## Related Issue(s)
Supersedes #34455
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
The `replace-removed-matcher-aliases` migration crashes the entire
migration runner when a project has a broken or misconfigured
`jest.config.ts`. This was discovered while upgrading nx-labs from Nx 21
to 22 via `nx migrate --run-migrations`.
The migration uses Jest's `readConfig()` and `Runtime.createContext()`
to resolve jest configs, but has no error handling around these calls.
If any project has issues like:
- A missing file referenced in the config (e.g. `.lib.swcrc` that was
renamed to `.swcrc`)
- A missing transform module (e.g. `@swc/jest` not installed)
- A missing preset file (e.g. `jest.preset.ts` instead of
`jest.preset.js`)
...the entire migration fails with an opaque error:
```
Error: Command failed: /var/folders/.../node_modules/.bin/nx _migrate --run-migrations
at checkExecSyncError (node:child_process:925:11)
status: 1,
stdout: null,
stderr: null
```
The actual errors are swallowed by the nested `execSync` call, making it
very hard to debug.
## Expected Behavior
The migration should skip projects with broken jest configs and continue
processing the rest of the workspace.
## Fix
Wrapped the `readConfig` / `Runtime.createContext` /
`SearchSource.getTestPaths` block in a try-catch that skips the failing
project. This matches the defensive pattern already used in
`packages/jest/src/plugins/plugin.ts` for similar jest config
resolution.
## Test Plan
- Added 3 tests covering: missing file reference, missing preset, and
verifying valid projects are still processed when a sibling project has
a broken config
- All existing tests continue to pass
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
The Gradle plugin's test class atomizer was incorrectly treating Kotlin
annotation class declarations as test targets, generating invalid test
tasks for them. This caused issues when projects defined custom
annotations alongside their test classes—the atomizer would attempt to
run annotation classes as tests.
## Expected Behavior
Both the AST-based parser and the regex fallback parser now skip
annotation classes, matching the existing behavior for data classes,
enum classes, sealed classes, and abstract classes. A new test suite
covers all annotation class exclusion scenarios for both parsers,
including files with multiple annotation classes and mixed
annotation/test class files.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: lourw <lourw@users.noreply.github.com>
## Current Behavior
When multiple processes call `connectToNxDb()` concurrently (plugin
workers via `startAnalytics()`, daemon, main CLI), two bugs can corrupt
the workspace database:
**Bug 1: Lock file inode race.** `unlock_file()` deletes the lock file
after unlocking, allowing a subsequent `File::create()` to produce a new
file with a different inode. Two processes can hold "the lock"
simultaneously on different file objects, breaking mutual exclusion.
**Bug 2: Partial file cleanup on version mismatch/connection failure.**
The `reason` arm and `Err` arm in `initialize_db` call
`remove_file(db_path)` which only deletes `.db`, leaving stale `.db-wal`
and `.db-shm` on disk. The recursive `initialize_db` creates a fresh
`.db`, but SQLite detects the stale WAL (different inode salt) and
deletes it — destroying all data that existed only in the WAL.
Both bugs lead to:
```
Database file exists but has no metadata table.
```
## Expected Behavior
1. Lock file persists across lock/unlock cycles — all processes
serialize through the same inode
2. When DB recreation is needed, all auxiliary files (`.db`, `.db-wal`,
`.db-shm`) are cleaned up together via `remove_all_database_files`
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
When the daemon handles task hashing, the `NativeTaskHasherImpl` checked
`hasTaskInputSubscribers()` in the daemon process where no subscribers
exist, causing the native Rust hasher to skip input collection entirely.
This resulted in empty input arrays in IO tracing signals.
The fix passes collectInputs from the client process (where subscribers
are registered) through the daemon IPC to the hasher, so the daemon uses
the client's subscriber state instead of its own.
## Current Behavior
`nx init` has a special code path for Create React App (CRA) projects
that installs Vite-related dependencies incompatible with `@nx/vite`
when Vite 8 is resolved, causing failures for npm workspaces.
CRA is essentially unused for years so there's no point to keep it
around.
## Expected Behavior
CRA projects are no longer special-cased in `nx init`. They flow through
the normal npm repo path, which detects plugins like `@nx/vite` via the
standard `detectPlugins()` mechanism.
<img width="1279" height="448" alt="Screenshot 2026-03-18 at 2 34 56 PM"
src="https://github.com/user-attachments/assets/17da3c6d-8aef-450f-baaa-6014e03cf41f"
/>
<img width="1280" height="432" alt="Screenshot 2026-03-18 at 2 35 01 PM"
src="https://github.com/user-attachments/assets/e02ccda5-9faf-428f-878d-348e4cc44d74"
/>
<img width="1270" height="821" alt="Screenshot 2026-03-18 at 2 35 15 PM"
src="https://github.com/user-attachments/assets/fdd99af5-db7e-422b-be42-a817c9dcefa2"
/>
## Related Issue(s)
Closes NXC-4107
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Adds references to [Yarn catalog](https://yarnpkg.com/features/catalogs)
to dependency management documentation.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Related #34377
---------
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
## Current Behavior
Several issues in `nx migrate` registry fetching cause confusing errors:
- `packageRegistryPack` uses `pnpm pack` for pnpm users, but `pnpm pack`
only packs the local project (unlike `npm pack` which downloads remote
packages). This silently fails for all pnpm users, always falling back
to the slower install path.
- On Windows, `extractFileFromTarball` fails because `join('package',
migrationsFilePath)` produces backslash paths that don't match
forward-slash tarball entries.
- When the install fallback also fails,
`getPackageMigrationsUsingInstall` returns `{}` instead of throwing. The
missing `version` property (`undefined`) propagates through
`packageUpdates` and `collectedVersions`, producing `Fetching
nx@undefined`.
- The install fallback can fail on peer dependency conflicts since
`legacy-peer-deps` was removed as a default (PR #33014), even though the
install only needs files on disk (not a valid dependency tree).
- The `.catch()` in `fetchMigrations` swallows errors without logging,
making it impossible to diagnose registry fetch failures.
## Expected Behavior
- `packageRegistryPack` always uses `npm pack` since it's the only
package manager that supports downloading remote packages
- Tarball entry matching uses `joinPathFragments` to normalize paths
(forward slashes) for cross-platform compatibility
- `getPackageMigrationsUsingInstall` throws on failure with a
descriptive error instead of returning an empty object
- Install fallback sets `npm_config_legacy_peer_deps=true` since it only
needs files on disk, not a valid dependency tree
- Registry fetch errors are logged at verbose level
(`NX_VERBOSE_LOGGING=true`) for debuggability
## Related Issue(s)
Fixes#33135
## Current Behavior
When pressing Ctrl+C on continuous tasks, `postTasksExecution` never
fires because `process.exit()` in `running-tasks.ts` SIGINT handlers
kills the process before the orchestrator can run async cleanup and
lifecycle hooks.
## Expected Behavior
`postTasksExecution` fires reliably on Ctrl+C with complete task
results, without causing stale `RunningTasksService` DB entries that
block subsequent `nx` invocations with "Waiting for ... in another nx
process" messages.
## Technical Details
Re-applies #34623 (reverted in #34869) with fixes for the issues that
caused CI failures.
**Root cause of the revert**: Removing `process.exit()` from SIGINT
handlers left the nx process alive during async cleanup. The
`running_tasks` DB entry persisted with a still-alive PID, so new nx
processes saw it as a running task and hung.
**Fix 1 — Early DB cleanup**: Synchronously remove all owned DB entries
in the SIGINT handler *before* starting async cleanup. This replicates
the cleanup that `process.exit()` + Rust `Drop` previously provided —
from any external observer's perspective, the tasks are gone
immediately.
**Fix 2 — Always register onExit in startContinuousTask**: The original
PR added a guard that skipped `onExit` registration for initiating
tasks, assuming `executeNextBatchOfTasksUsingTaskSchedule` would handle
it. But `runContinuousTasks()` (used by DTE agents and Playwright) calls
`startContinuousTask` directly without going through `run()`. The
missing handler meant task exit was never processed — no DB cleanup, no
lifecycle hooks. This caused verdaccio (local-registry) to become
unreachable on DTE agents. The fix always registers `onExit` in
`startContinuousTask` and simplifies the
`executeNextBatchOfTasksUsingTaskSchedule` handler to only unblock the
thread.
Changes:
- Remove `process.exit()` from `running-tasks.ts` SIGINT handlers (lets
orchestrator run)
- Replace `cleanupDone` boolean with `cleanupPromise` (fixes
SIGINT/SIGTERM race)
- Set `stopRequested = true` for SIGTERM/SIGHUP (correct task
classification)
- Early synchronous DB cleanup in non-TUI SIGINT handler
- Always register `onExit` handler in `startContinuousTask` for both
initiating and non-initiating tasks
- Simplify initiating task handler in
`executeNextBatchOfTasksUsingTaskSchedule` to only call `res()`
## Current Behavior
The Angular devkit adapter (`ngcli-adapter.ts`) calls
`createProjectGraphAsync()` in multiple places, even though callers
(executors, generate command, migrate command) already have the project
graph available. This results in redundant requests to create the graph.
## Expected Behavior
Reuse the project graph from callers when available, falling back to
reading from cache (`readCachedProjectGraph()`) or
`createProjectGraphAsync()` only when necessary.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
There's not a great way to attach metadata to CLI commands to influence
rendering the markdown docs for them
## Expected Behavior
There's a metadata system currently used to express minimum support
version for the show target command
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
When calling `nxE2EPreset(__filename)` without passing the optional
`options` parameter, the function throws a runtime error because
`options.openHtmlReport` accesses a property on `undefined`. Other
property accesses in the function already use optional chaining
(`options?.testDir`, `options?.generateBlobReports`), but this one was
missed.
Additionally, the `openHtmlReport` property documents a default value of
`'on-failure'` via JSDoc, but that default was never actually applied in
the code.
## Expected Behavior
Calling `nxE2EPreset(__filename)` without the `options` argument works
without errors. The `openHtmlReport` option correctly falls back to
`'on-failure'` when not specified, matching the documented `@default` in
the interface.
## Related Issue(s)
N/A - Discovered when upgrading NX and we didn't pass options
## Current Behavior
The "Migrating Multiple Angular CLI Workspaces" page is a 26-line stub
with only a YouTube video. It doesn't mention `nx import` and contains
outdated guidance. The single-workspace migration guide (`angular.mdoc`)
also has outdated sections: a list of supported builders, a full CI
setup walkthrough, an irrelevant "From Nx Console" section, and a
reference to `karma.conf.js`.
## Expected Behavior
- The `angular-multiple.mdoc` page is removed with a redirect to the
main Angular migration guide.
- The main migration guide mentions `nx import` for consolidating
multiple Angular CLI projects.
- Outdated sections (Modified folder structure, Set up CI, From Nx
Console) are removed and replaced with concise links.
- The `nx-and-angular.mdoc` guide links to `nx import` docs instead of
the deleted page.
Preview:
https://deploy-preview-34913--nx-docs.netlify.app/docs/technologies/angular/migration/angular-multiple
(redirects to the updated page)
## Related Issue(s)
Closes DOC-419
## Current Behavior
The `@nx/eslint/plugin` uses `eslintConfigFiles[0]` to decide between
`FlatESLint` and `LegacyESLint`. Due to glob pattern ordering,
`.eslintrc.*` files sort before `eslint.config.*`, so a stray nested
`.eslintrc.json` (e.g., in `eslint-local-rules/`) causes the plugin to
pick `LegacyESLint` even when the root has a flat config — crashing with
"No ESLint configuration found" during project graph creation.
## Expected Behavior
The ESLint class is determined from the root config, matching ESLint's
own behavior where `find-up` from cwd decides the mode. Nested legacy
config files are irrelevant when a root flat config exists. When both
flat and legacy configs exist at root (mid-migration), flat config is
preferred.
## Related Issue(s)
Fixes#32110
## Current Behavior
The `convert-to-flat-config` generator silently skips project-level
`.eslintrc.json` files when `projectConfig.targets` is undefined (e.g.,
package.json-only projects in pnpm workspaces). The `@nx/eslint/plugin`
check is gated behind the targets check and never reached.
## Expected Behavior
Projects with `.eslintrc.json` are converted when `@nx/eslint/plugin` is
registered, even without explicit targets. When a project is skipped
because no ESLint lint target is detected, a warning is logged so users
know which projects were not converted and why.
## Related Issue(s)
Fixes#29458
## Current Behavior
The `convert-to-flat-config` generator wraps all plugin extends with
`FlatCompat`, including Nx-specific ones like `plugin:@nx/typescript`.
The output doesn't match what freshly generated projects produce
(`nx.configs['flat/typescript']`). The `@nx` plugin registration uses a
manual `{ plugins: { '@nx': nxEslintPlugin } }` block instead of
`flat/base`.
## Expected Behavior
Nx plugin extends are converted to native `nx.configs['flat/X']`
entries. The `@nx` plugin registration uses `flat/base`. `FlatCompat` is
only used for third-party plugins. The import variable is normalized to
`nx` to match fresh generation.
## Related Issue(s)
Fixes#31736
## Current Behavior
The `enforce-module-boundaries` rule only visits ESM AST nodes
(`ImportDeclaration`, `ImportExpression`, `ExportAllDeclaration`,
`ExportNamedDeclaration`). CommonJS `require()` calls bypass all
boundary checks entirely.
## Expected Behavior
`require()` and `require.resolve()` calls are detected and validated
against the same module boundary rules as ESM imports. Auto-fix is
skipped for `require()` nodes since they have no `specifiers`.
## Related Issue(s)
Fixes#34096
## Current Behavior
When CLI arguments containing shell metacharacters (like `|`, `&`, `$`,
`;`, `*`, etc.) are passed through Nx to underlying tasks, they are not
properly quoted, causing shell interpretation errors.
For example, running:
```bash
nx test app --grep="@tag1|@tag2"
```
Would fail with `/bin/sh: @smoke: command not found` because the pipe
character `|` was interpreted by the shell as a pipe operator instead of
being passed as a literal string to the underlying command.
Users had to use awkward double-quoting workarounds like
`--grep='"@tag1|@tag2"'` to get the expected behavior.
## Expected Behavior
CLI arguments containing shell metacharacters should be automatically
quoted before being passed to underlying commands, so that:
```bash
nx test app --grep="@tag1|@tag2"
```
Works correctly and passes `--grep="@tag1|@tag2"` to the underlying test
runner without shell interpretation.
## Related Issue(s)
Fixes#32305Fixes#26682
## Implementation Details
- Created a shared `needsShellQuoting()` utility in
`packages/nx/src/utils/shell-quoting.ts` that detects shell
metacharacters
- Updated `wrapArgIntoQuotesIfNeeded()` in `run-commands.impl.ts` to use
the shared utility
- Updated `stringShouldBeWrappedIntoQuotes()` in
`serialize-overrides-into-command-line.ts` to use the shared utility
- Fixed a bug where `arg.split('=')` would incorrectly split values
containing `=` (e.g., `--define=FOO=bar|baz`)
- Added proper escaping of embedded double quotes when wrapping values
- Added comprehensive test coverage
### Shell metacharacters now handled:
`|` `&` `;` `<` `>` `(` `)` `$` `` ` `` `\` `"` `'` `*` `?` `[` `]` `{`
`}` `~` `#` `!` and whitespace
---------
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
## Current Behavior
When the globally installed Nx binary (`bin/nx.ts`) runs, it goes
through the following flow before determining whether to hand off to a
local Nx installation:
1. `ensureAnalyticsPreferenceSet()` — prompts user if analytics
preference not set
2. `startAnalytics()` — which calls `getDbConnection()` →
`connectToNxDb(directory, NX_VERSION)` using the **global** Nx version
and native bindings, then calls `initializeTelemetry(dbConnection, ...)`
to initialize telemetry
3. Sets `NX_ANALYTICS_SESSION_ID` env var
Then it checks which execution path to take:
- **`isNxCloudCommand`** — executes commands directly (no handoff)
- **`isLocalInstall`** — the global IS the local, calls `initLocal()`
(no handoff)
- **`localNx` exists** — hands off to the local Nx via
`require(localNx)`
In the handoff case, the local `bin/nx.ts` runs `main()` from scratch,
which calls `startAnalytics()` again. It sees `NX_ANALYTICS_SESSION_ID`
is already set and takes the "reuse session" shortcut — but telemetry
was already initialized with the wrong version's native bindings and DB
connection.
**Problems:**
- The global bin opens a DB connection with the **wrong `NX_VERSION`**
(global version, not local version) and **wrong native bindings**
- Analytics is initialized twice — once from global (incorrect), once
from local (correct but using session reuse path)
- The DB connection opened by the global bin is unnecessary since the
local Nx handles everything
## Expected Behavior
When the global bin is about to hand off to a local Nx installation, it
should **not** initialize analytics or open any DB connections. The
local Nx will handle analytics initialization correctly with its own
version and native bindings.
Analytics and DB connections should only be initialized in the two
branches where the global bin actually executes commands itself:
- `isNxCloudCommand` — needs analytics because it runs commands directly
- `isLocalInstall` — needs analytics because `initLocal()` doesn't
re-enter `bin/nx.ts`
## Related Issue(s)
<!-- Internal discovery during analytics work -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Visiting [nx.dev/changelog](https://nx.dev/changelog) returns an HTTP
500 Internal Server Error. The page builds successfully during
deployment (prerendered as SSG), but the Netlify server handler fails at
runtime when serving the page.
## Expected Behavior
The changelog page loads correctly, displaying Nx release history with
version timelines and any manually authored changelog content.
## Related Issue(s)
Fixes#34909
## Changes
- **`next.config.js`**: Add `outputFileTracingIncludes` for
`public/documentation/changelog/**` so the changelog content directory
is included in the Netlify serverless function bundle (Next.js file
tracing can't detect `readdirSync` with string paths)
- **`pages/changelog.tsx`**: Wrap `changeLogApi.getChangelogEntries()`
in try/catch for graceful degradation if the directory is unavailable
during on-demand rendering
- **`rewrite-framer-urls.ts`**: Add `/changelog` to the edge function
`excludedPath` config so the Framer proxy edge function is bypassed
entirely for changelog requests
- **`_redirects`**: Fix malformed redirect on line 55 — destination path
was missing a leading `/`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Current Behavior
After certain cache state transitions (e.g., tsconfig parsing cache warm
but targets cache cold), the `@nx/js/typescript` plugin falls back to
the `production` named input instead of deriving precise inputs from
tsconfig `include`/`exclude` paths. This results in broader cache
invalidation than necessary, and in projects with `allowJs` or
`resolveJsonModule`, the wrong file extensions are tracked as inputs.
Output inference is also affected when `emitDeclarationOnly` or
`declarationMap` are set.
Running `nx reset` restores correct behavior, but the issue recurs.
## Expected Behavior
Inferred inputs and outputs are consistent regardless of cache state —
always matching what the tsconfig actually specifies.
The root cause was the tsconfig parsing cache serialization
(`toAbsolutePaths`/`toRelativePaths`) dropping fields the plugin relies
on: `raw.include`, `raw.exclude`, `raw.files`, and `options.allowJs`,
`options.resolveJsonModule`, `options.emitDeclarationOnly`,
`options.declarationMap`. These are now preserved, and
`TSCONFIG_CACHE_VERSION` is bumped to invalidate stale caches.
## Current Behavior
On Windows, the TypeScript plugin produces `cwd` with backslash
separators (e.g., `cwd: "packages\\nx"`) for both the typecheck and
build targets. All paths in Nx targets should use `/` separators.
## Expected Behavior
The `cwd` option uses forward slashes on all platforms (e.g., `cwd:
"packages/nx"`).
## Related Issue(s)
Fixes NXC-4105
## Summary
- Self-healing auto-apply writes fix context to `.nx/self-healing/`, but
that directory was not in `.gitignore`. This adds it via a migration and
in the CAIA generator, following the same pattern as `.nx/polygraph`.
## Current Behavior
The `.nx/self-healing` directory is not gitignored. Users who run
self-healing auto-apply may accidentally commit generated fix context
files.
## Expected Behavior
`.nx/self-healing` is added to `.gitignore` automatically during `nx
migrate` (via a new migration) and when running the CAIA setup
generator.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Add X-Frame-Options and Content-Security-Policy frame-ancestors headers
to prevent clickjacking on nx.dev marketing and docs sites.
Fixes DOC-449
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Current Behavior
#34799 has some method names that could have been better
## Expected Behavior
The methods are named to signify they are notifications, not
instructions
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Summary
- The upstream config repo (nx-ai-agents-config) now generates skills
into a shared `.agents/skills/` directory instead of separate per-agent
skills directories for codex, cursor, and gemini.
- Updated the `agentDirs` mapping so `.agents` is copied when any of
codex, cursor, or gemini are enabled, not just codex.
- Widened the `agent` field type from `Agent` to `Agent | Agent[]` to
support mapping a single directory to multiple agents.
## Key decisions
- Used `Agent | Agent[]` union type rather than always-array to minimize
changes to existing single-agent entries. The loop normalizes with
`Array.isArray` before checking.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
We want to capture more error and cancel events so that start events
match complete/error/cancel events. This PR ensures that more errors are
properly captured as `CnwError` rather than just `output.error` +
`process.exit(1)`;
- Invalid workpspace name (i.e. starting with a number) now throws
`CnwError` so we record them correctly
- Missing package manager is now captured as `CnwError` correctly
- SIGINT when workpspace is already created now send "cancel" event
Closes NXC-4095
## Current Behavior
Nx packages are on a previous version.
## Expected Behavior
All Nx packages updated to 22.6.0-rc.2.
## Related Issue(s)
N/A - routine version bump for RC testing.
This reverts commit 8d71d5b57b.
## Current Behavior
<!-- This is the behavior we have today -->
This generator causes unnecessary changes
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
The generator is removed for now and will be reintroduced when it is
refined.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
Batch mode documentation is scattered across individual technology pages
(TypeScript, Gradle, Maven, Jest) with no central guide explaining the
concept, how it works, or which executors support it.
## Expected Behavior
A new guide page at `guides/tasks--caching/batch-mode` provides a
technology-agnostic overview of batch mode, covering:
- What batch mode is and why it's faster
- How to enable it (`NX_BATCH_MODE=true` env var and `--batch` CLI arg)
- Which executors support it (with notes that Gradle/Maven have it on by
default)
- Caching and CI compatibility
Each technology-specific page now links back to the central guide for
the full explanation.
## Related Issue(s)
Fixes #DOC-420
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: barbados-clemens <barbados-clemens@users.noreply.github.com>
## Current Behavior
CNW flow matches v22.1.3 (restored in #34671): no template prompt is
shown, the cloud prompt uses simplified "Would you like remote caching?"
wording, banner variant is locked to 0, and preset flow connects to
cloud during workspace creation.
## Expected Behavior
Restore the template prompt and cloud prompts that were removed in
#34671:
- **Template prompt**: "Which starter do you want to use?" with 5
choices (Minimal, React, Angular, NPM Packages, Custom)
- **Cloud prompt**: `determineNxCloudV2` ("Connect to Nx Cloud?") for
preset flow when no `--nxCloud` CLI arg; existing CI provider prompt
when `--nxCloud` is explicitly provided
- **Banner**: Variant 2 (box banner) for standard Nx Cloud URLs, variant
0 for enterprise
- **Preset flow**: Deferred cloud connection (`nxCloud: 'skip'`) instead
of connecting during workspace creation
- **Push logic**: Push to GitHub for both `nxCloud === 'github'` and
`nxCloud === 'yes'`
- **Messages**: "Try the full Nx platform?" wording, GitHub repo link in
push messages, "Your remote cache setup is almost complete." title
Preserves all subsequent changes (analytics prompt from #34818,
SANDBOX_FAILED fix, .gitignore updates).
## Related Issue(s)
Fixes NXC-4096
## Current Behavior
Every process that initializes analytics opens its own DB connection to
get/create a session ID. This includes the CLI, the daemon, and **every
plugin worker**. In workspaces with many plugins, dozens of plugin
workers spawn simultaneously, each opening a DB connection and
querying/writing session metadata. This overwhelms the SQLite database
with concurrent connections and causes failures.
## How This Fixes It
The root cause is that plugin workers each independently open a DB
connection just to read the session ID. The fix eliminates this by
having the parent process (CLI or daemon) fetch the session ID once and
pass it to child processes via the `NX_ANALYTICS_SESSION_ID` environment
variable. Plugin workers inherit this env var and initialize telemetry
without touching the DB at all.
| Process | Before | After |
|---------|--------|-------|
| CLI | Opens DB connection | Opens DB connection (1x) |
| Daemon | Opens DB connection | Opens DB connection (1x) |
| Plugin worker (×N) | Each opens DB connection | Reads env var, **no DB
connection** |
In a workspace with 20 plugins, this reduces DB connections from 22 (CLI
+ daemon + 20 workers) down to 2 (CLI + daemon only).
## Two Initialization Paths
- **`initializeTelemetry(dbConnection, ...)`** — Used by CLI and daemon.
Gets/creates the session ID from the DB via a transaction, stores the
connection for persisting session refreshes on flush, and returns the
session ID so the caller can set it as an env var for child processes.
- **`initializeTelemetryWithSessionId(sessionId, ...)`** — Used by
plugin workers. Takes the session ID inherited from the parent process
env var. No DB connection, no DB queries.
## Session Refresh for Long-Lived Processes
The daemon is long-lived and could hold a stale session ID for hours.
The telemetry background thread now tracks activity and generates a new
session ID after 30 minutes of inactivity (matching the existing GA4
session timeout). When a session refreshes, the background thread
notifies the main thread via a channel, which persists the new session
to the DB in a transaction on flush.
## Other Changes
- Extracted `TelemetryOptions` struct to replace the long parameter list
in `TelemetryService::new`
- Moved `SESSION_TIMEOUT_SECS` to `constants.rs` so it can be shared
between modules
- Extracted `init_service` helper to deduplicate between the two init
paths
- Extracted `persist_session_to_db` helper that wraps both metadata
writes in a transaction
- Separated session ID retrieval (`get_or_create_session_id`) from
telemetry service initialization
## Related Issue(s)
Fixes database connection exhaustion when many plugin workers initialize
analytics simultaneously.
## Current Behavior
There is no documentation for Nx CLI telemetry, which was added in Nx
22.6.0. Users who are prompted to opt in have no reference page to learn
what data is collected or how to opt out.
## Expected Behavior
A dedicated telemetry reference page at `/reference/telemetry` explains
what is collected, what is not collected, and how to disable telemetry
via `nx.json`. The `nx.json` reference page also documents the
`analytics` property.
Changes:
- New page: `astro-docs/src/content/docs/reference/telemetry.mdoc`
- Sidebar entry added under Reference
- `analytics` property added to nx.json reference (expanded example +
new section)
## Related Issue(s)
Fixes DOC-446
## Current Behavior
The CODEOWNERS setup is getting in the way as core maintainers have
moved around and timezones and such...
## Expected Behavior
Anyone on the CLI reviewers team should be capable of recognizing PRs
they are capable of assessing, this eases the burden of increased PRs
from AI chatbots and timezones.
## Current Behavior
Only the astro-docs (nx-docs) Netlify app tracks server-side page views
via edge functions. AI tool and bot detection relies on fragile
User-Agent regex matching against a hardcoded list of known bot strings.
## Expected Behavior
Both nx-dev and nx-docs page views are tracked server-side via GA4,
using Netlify's built-in `Netlify-Agent-Category` header to distinguish
`ai-agent` from `crawler` traffic.
Tracking edge functions are consolidated into the nx-dev app
(`netlify/edge-functions/`) since all traffic flows through it before
being rewritten to nx-docs. Framer-proxied pages are tracked inline in
`rewrite-framer-urls.ts` because the proxy short-circuits
`context.next()`, preventing downstream edge functions from firing.
**Changes:**
- Moved `track-page-requests.ts` and `track-asset-requests.ts` from
`astro-docs/netlify/edge-functions/` to `netlify/edge-functions/`
- Replaced User-Agent regex with `Netlify-Agent-Category` header checks
in all tracking functions
- Added GA4 tracking to `rewrite-framer-urls.ts` for Framer-proxied
pages
## Related Issue(s)
Fixes DOC-445
## Current Behavior
New logic in the daemon can cancel an active graph creation, resulting
in worker shutdown not behaving as intended
## Expected Behavior
When the daemon aborts graph construction, the plugins still shut down
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
Projects scaffolded with `@nx/vite` or `@nx/vitest` generators use
`^4.0.0` for vitest v4 dependencies. Since vitest 4.1.0 (released Mar
12), its vite peer dep expanded to `^6.0.0 || ^7.0.0 || ^8.0.0-0`. Yarn
Classic's linker fails with `Invariant Violation: could not find a copy
of vite to link` when encountering this expanded OR range.
This breaks all e2e tests running with Yarn Classic (e2e-cypress,
e2e-eslint, e2e-jest, e2e-playwright, e2e-web, e2e-webpack on
Linux/yarn/20 combos).
## Expected Behavior
Scaffolded projects use `~4.0.x` tilde ranges for vitest v4, restricting
to patch updates only. This avoids pulling in vitest 4.1.0+ and the
problematic peer dep range, keeping Yarn Classic compatibility intact.
## Related Issue(s)
Fixes failing CI runs on yarn combos since Mar 12-13.
## Current Behavior
We accumulate `inputs`/`outputs`/`pids` and store them even if no
subscriber is subscribed, and then they hang around to notify late
subscribers that may never come
## Expected Behavior
`inputs`/`outputs`/`pids` are only sent to current subscribers
## AI Summary
This pull request optimizes how task input notifications are handled in
Nx by ensuring that expensive input collection and storage only occur
when there are active subscribers. This change prevents unnecessary
memory growth in long-lived processes, such as the Nx daemon, and
improves performance by avoiding redundant work.
Notification and input collection optimization:
* Added a `hasTaskInputSubscribers()` method to the `TaskIOService`
class, allowing the hasher to check if any input subscribers are
registered before collecting and notifying task inputs.
* Updated all hashing functions in `hash-task.ts` to only notify task
inputs if there are active subscribers, reducing unnecessary work and
memory usage.
[[1]](diffhunk://#diff-d061dc5551f692abad009b8284c719466cff2f0d3d19bd52e81b7921a9e543d6R57-R63)
[[2]](diffhunk://#diff-d061dc5551f692abad009b8284c719466cff2f0d3d19bd52e81b7921a9e543d6L114-R117)
[[3]](diffhunk://#diff-d061dc5551f692abad009b8284c719466cff2f0d3d19bd52e81b7921a9e543d6R172)
[[4]](diffhunk://#diff-d061dc5551f692abad009b8284c719466cff2f0d3d19bd52e81b7921a9e543d6L185-R188)
[[5]](diffhunk://#diff-d061dc5551f692abad009b8284c719466cff2f0d3d19bd52e81b7921a9e543d6L201-R204)
* Modified the native task hasher implementation
(`native-task-hasher-impl.ts` and Rust code) to conditionally collect
and return input data only when requested, minimizing overhead and
memory allocation.
[[1]](diffhunk://#diff-ddf992f97afbcd8b2206b8d1faab7e7f9571ecfe50e2ab8e3da104001f39f0c3L73-R80)
[[2]](diffhunk://#diff-ddf992f97afbcd8b2206b8d1faab7e7f9571ecfe50e2ab8e3da104001f39f0c3L88-R101)
[[3]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR194-R200)
[[4]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL226-R233)
[[5]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR256)
[[6]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL262-R273)
[[7]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL288-R299)
[[8]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR330-R352)
[[9]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL348-R383)
[[10]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL368-R407)
[[11]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR432)
[[12]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL417-R475)
[[13]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL452-R489)
[[14]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL461-R503)
[[15]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR518)
Code cleanup and refactoring:
* Removed unused state and redundant code from `TaskIOService` related
to storing task-to-PID, task-to-input, and task-to-output mappings, as
well as unnecessary graph references and late subscriber emission logic.
[[1]](diffhunk://#diff-fc051b28c1ac25926033d157ec2278cc1cf2fcf7e16626eeedaf876c29fc9d62R42-R66)
[[2]](diffhunk://#diff-fc051b28c1ac25926033d157ec2278cc1cf2fcf7e16626eeedaf876c29fc9d62L91-L95)
[[3]](diffhunk://#diff-fc051b28c1ac25926033d157ec2278cc1cf2fcf7e16626eeedaf876c29fc9d62L104-L111)
* Updated imports and cleaned up constructor logic in
`task-io-service.ts` and `native-task-hasher-impl.ts` for clarity and
maintainability.
[[1]](diffhunk://#diff-ddf992f97afbcd8b2206b8d1faab7e7f9571ecfe50e2ab8e3da104001f39f0c3R16)
[[2]](diffhunk://#diff-fc051b28c1ac25926033d157ec2278cc1cf2fcf7e16626eeedaf876c29fc9d62L1-L2)
These changes collectively make task input tracking more efficient and
robust, especially in scenarios where Nx runs as a daemon or in
environments with no listeners for input notifications.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
## Current Behavior
When `detectPackageManager()` finds no lock file for bun, yarn, or pnpm,
it falls back to `detectInvokedPackageManager()` which checks
`npm_config_user_agent`. This causes misdetection when a workspace is
created with npm (has `package-lock.json`) but the parent process uses
pnpm — the detection picks up pnpm from the user agent instead of npm
from the lock file.
This has been causing **every nightly E2E run to fail since March 4th**
(when #34691 was merged), particularly in e2e-angular where `ng-add`
tests create npm workspaces but `installPackagesTask` incorrectly
invokes `pnpm install --no-frozen-lockfile`.
## Expected Behavior
`detectPackageManager()` should check for `package-lock.json` (npm's
lock file) before falling back to the invoking package manager
detection. This ensures that workspaces with a `package-lock.json` are
correctly identified as npm workspaces regardless of what package
manager invoked the current process.
## Related Issue(s)
Fixes the nightly E2E matrix failures in e2e-angular (`ng-add.test.ts`,
`plugin.test.ts`), e2e-nx-init, and e2e-workspace-create that have been
consistently failing since #34691 was merged.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This PR introduces the `deps-sync` generator which pairs with
`typescript-sync`generator to ensure internal dependencies are correctly
mapped via `devDependencies` of corresponding `package.json`.
## Current Behavior
When dependency to local package is created (via import for example) the
existing typescript-sync generator updates the tsconfig but package.json
is left untouched which might cause issues when referencing transitive
dependencies.
## Expected Behavior
The typescript-sync should be accompanied by deps-sync generator that
would update dependencies in package.json for workspaces.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Add the continuous property to the nx show target output. Previously the
command showed cache and parallelism but not whether a target is
continuous.
Fixes NXC-4084
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
## Current Behavior
The Gradle plugin uses an in-memory `gradleCurrentConfigHash` variable
to decide whether to skip re-running `./gradlew nxProjectGraph`. Since
plugin workers shut down between graph computations, this variable
resets to `undefined` each time. The `??=` operator on the disk cache
read means it's only read once per worker lifetime, and the
`!gradleCurrentConfigHash` check always evaluates to `true` on fresh
workers — making the in-memory hash comparison dead code.
This means the caching logic works despite itself (via disk cache), but
is fragile and could cause unnecessary Gradle invocations or stale cache
hits if worker lifecycle assumptions change.
There was also another issue with hashing options when calculating the
project graph for nodes and dependencies. Options were not normalized
when hashing which you did not get deterministic hashing when calling
dependencies after createNodes.
## Expected Behavior
Always read the disk cache and compare hashes directly. If the hash
matches, skip running Gradle. If not, run Gradle and update the disk
cache. No in-memory state needed between worker restarts.
Options are normalized before hashing to ensure that regardless of
createNodes or dependencies, we will get a deterministic hash.
## Related Issue(s)
<!-- No specific issue — discovered during code review -->
---------
Co-authored-by: lourw <56288712+lourw@users.noreply.github.com>
## Current Behavior
When configuring AI agents via `nx polygraph`, a
`.claude/settings.local.json` file is created containing user-specific
settings. This file is not gitignored by Nx, so it can accidentally be
committed to the repository.
## Expected Behavior
`.claude/settings.local.json` should be gitignored by default, similar
to how `.claude/worktrees` is already handled.
## Changes
- Added a migration that adds `.claude/settings.local.json` to existing
workspaces' `.gitignore`
- Updated all three `.gitignore` templates so new workspaces include it
from the start
- Follows the same pattern as the existing
`add-claude-worktrees-to-git-ignore` migration
## Current Behavior
After #34623, when continuous tasks are killed (Ctrl+C), the nx process
stays alive during async cleanup instead of exiting immediately. This
causes the `RunningTasksService` SQLite entry to persist with a
still-alive PID.
When a new `nx` process starts during this cleanup window,
`is_task_running()` finds the stale entry, confirms the PID is still
alive (old process cleaning up), and creates a `SharedRunningTask` —
incorrectly printing:
```
Waiting for @nrwl/ocean:local-registry in another nx process
```
The root cause: #34623 removed `process.exit()` from SIGINT handlers in
`running-tasks.ts`, widening the window where the old process is alive
but the task is no longer actually running.
## Expected Behavior
After killing continuous tasks, a new `nx` invocation should not see
stale "Waiting for ... in another nx process" messages.
This reverts #34623 while a proper fix is designed that achieves both
goals: reliable `postTasksExecution` firing AND prompt DB cleanup.
## Related Issue(s)
Reverts #34623
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
When running nx commands outside of an Nx workspace (no nx.json), the
light client is downloaded to .nx/cache/cloud relative to process.cwd().
This creates an unwanted .nx folder in whatever directory the user
happens to be in.
## Expected Behavior
When outside an Nx workspace, the light client is downloaded to a temp
directory (os.tmpdir()/nx-cloud-client/hash) where hash is derived from
the NX_CLOUD_API URL. This avoids polluting arbitrary directories with
.nx folders while ensuring different cloud instances get separate
directories.
When inside an Nx workspace, behavior is unchanged.
## Current Behavior
The Maven plugin version is at 0.0.15.
## Expected Behavior
The Maven plugin version is bumped to 0.0.16 with a corresponding
migration entry for Nx 22.6.0-beta.14.
## Related Issue(s)
N/A - routine version bump.
### Changes
- Updated all pom.xml files to version 0.0.16
- Updated `mavenPluginVersion` constant in `versions.ts`
- Added migration entry in `migrations.json`
- Created migration file `update-pom-xml-version.ts` for 0.0.16
## Current Behavior
The `@nx/js/typescript` plugin adds dependency project `tsconfig.json`
files as inputs but doesn't resolve nested `projectReferences` within
those files. When `tsc -b` runs, it follows the full reference chain —
reading `tsconfig.lib.json`, `tsconfig.spec.json`,
`tsconfig.storybook.json`, etc. from dependencies — but these files
aren't declared as inputs, causing potential cache correctness issues.
## Expected Behavior
The plugin now walks the full project reference chain from external
dependencies and collects all distinct tsconfig relative paths (e.g.,
`tsconfig.lib.json`, `tsconfig.spec.json`, `cypress/tsconfig.json`).
These are emitted as `^{projectRoot}/...` input patterns, ensuring that
any tsconfig file read by `tsc -b` through the reference chain is
tracked as an input.
Key changes:
- Renamed `getExternalProjectReferenceConfigFiles` →
`getExternalProjectReferenceTsconfigPatterns` to reflect it now returns
input patterns instead of absolute paths
- Uses a worklist algorithm to traverse the reference chain
(breadth-first, cycle-safe)
- Collects relative paths per dependency project root, deduplicates, and
emits `^{projectRoot}/...` patterns
- For build targets, only tsconfig files reachable from the build
reference chain are included (not all refs from the solution tsconfig)
These flags were added while we tested changes to the task API and
streaming. They are no longer needed.
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
The Jest plugin infers `test` target inputs from the preset file path
but ignores other config properties that reference files — transforms,
setup files, module name mappers, reporters, watch plugins, etc. Changes
to those files don't invalidate the test cache.
## Expected Behavior
All Jest config properties that reference files are resolved and
included as task inputs, matching Jest's merge semantics:
- **Replaced** (config wins over preset): `resolver`, `globalSetup`,
`globalTeardown`, `snapshotResolver`, `snapshotSerializers`,
`testResultsProcessor`, `runner`, `reporters`, `watchPlugins`
- **Concatenated** (preset + config): `setupFiles`, `setupFilesAfterEnv`
- **Deep merged** (config keys win): `moduleNameMapper`, `transform`
Also handles `jest-runner-`/`jest-watch-` prefix resolution, `<rootDir>`
in preset values, and Windows path normalization.
Adds a `useJestResolver` option to control whether jest-resolve is used
for input resolution, decoupled from `disableJestRuntime`. By default,
inputs are resolved using path-based classification (fast, no filesystem
calls beyond what's already done). When `useJestResolver` is enabled,
jest-resolve is used instead, which follows symlinks and honors custom
`moduleDirectories`/`modulePaths` — more accurate for workspace-linked
packages but slower due to filesystem probing per resolved path.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
When pressing Ctrl+C on continuous tasks (e.g., `nx run app:serve`),
`postTasksExecution` never fires or fires with incomplete task results.
This affects any plugin or custom lifecycle hook relying on
`postTasksExecution` to perform cleanup, reporting, or post-run logic.
The issue manifests in non-TUI mode (`NX_TUI=false`) and in setups where
nx runs as a child of a package manager (pnpm/npm), which sends SIGTERM
shortly after SIGINT.
## Expected Behavior
`postTasksExecution` fires reliably on Ctrl+C with complete task
results, regardless of whether TUI is enabled or how the nx process is
invoked.
## Technical Details
Three independent issues caused the broken behavior:
**1. Initiating continuous task exits before orchestrator cleanup**
When the initiating task is continuous and exits with a signal code
(e.g., 130 from SIGINT), the `onExit` handler called
`process.exit(code)` synchronously — before the orchestrator's SIGINT
handler could run. Cleanup and `postTasksExecution` never executed.
Fixed by replacing `process.exit()` with proper task completion via
`handleContinuousTaskExit`, and ensuring initiating tasks are the sole
`onExit` callback (to avoid floating promises from
`exitCallbacks.forEach` not awaiting async callbacks).
**2. run-commands SIGINT handlers call `process.exit(130)`**
Every `nx:run-commands` task running in the main process registered a
SIGINT handler that called `process.exit(signalToCode('SIGINT'))`,
killing the process before the orchestrator's async cleanup could run.
Fixed by removing `process.exit()` from both SIGINT handlers in
`running-tasks.ts`. The `this.kill('SIGTERM')` call is kept for prompt
child termination. Cache-write safety is already guaranteed by
`postRunSteps` guards (`stopRequested`, `status !== 'stopped'`).
**3. `cleanup()` resolves prematurely on concurrent signals**
When pnpm/npm sends SIGTERM ~30ms after SIGINT, the SIGTERM handler saw
`cleanupDone = true` (set at the start of cleanup, before async work),
returned immediately, and its `.finally()` called `resolveStopPromise()`
before SIGINT's cleanup finished. `run()` returned with incomplete task
results.
Fixed by replacing the `cleanupDone` boolean with a stored promise.
Concurrent callers now await the same in-progress cleanup.
**Additional fixes:**
- SIGTERM/SIGHUP handlers now set `stopRequested = true` so externally
terminated tasks are correctly classified as `'interrupted'` rather than
`'fulfilled'`.
- Extracted shared exit-handling logic into `handleContinuousTaskExit`
to consolidate reason-determination between initiating and
non-initiating continuous tasks.
## Current Behavior
The analytics prompt (`ensureAnalyticsPreferenceSet()`) and
`startAnalytics()` run for all commands, including cloud commands like
`download-cloud-client`. When a cloud command runs outside an Nx
workspace, `saveAnalyticsPreference()` creates an almost-empty `nx.json`
(`{ "analytics": true }`) in whatever directory you happen to be in.
## Expected Behavior
Cloud commands skip the analytics prompt and `startAnalytics()`
entirely, since they may run without a workspace and there is no
appropriate `nx.json` to write to.
## Related Issue(s)
Fixes the issue where running `nx download-cloud-client` outside a
workspace creates a spurious `nx.json`.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Removes the `.nx/workflows/sandboxing-config.yaml` file, which is no
longer needed in the repository.
## Changes
- **Deleted** `.nx/workflows/sandboxing-config.yaml` — no CI pipelines
or workflow files reference this file directly
- The only existing mention is in `astro-docs/.../sandboxing.mdoc`,
which is purely instructional documentation guiding users to create
their own config; no update needed
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
> ## Goal
> Remove the workflow configuration file
`.nx/workflows/sandboxing-config.yaml` from the `nrwl/nx` repository.
>
> ## Context
> - Repository: `nrwl/nx`
> - Base branch: `master`
> - File to remove: `.nx/workflows/sandboxing-config.yaml`
> - Source URL:
https://github.com/nrwl/nx/blob/master/.nx/workflows/sandboxing-config.yaml
> - Commit OID (reference provided by user):
`5dd12046c3d5bc5919abdb176be2d11f16c8f943`
> - Current contents:
> ```yaml
> exclude-reads:
> - '**/node_modules/**'
> - .nx/**/*
> - .git/**/*
> - '**/package.json'
> - pnpm-workspace.yaml
> exclude-writes:
> - nx/**/*
> ```
>
> ## Requirements
> 1. Delete `.nx/workflows/sandboxing-config.yaml`.
> 2. Ensure there are no other required references to this file (e.g.,
in CI/workflows) that would break due to removal. If references exist,
update them accordingly.
> 3. Run the full validation suite prior to finalizing (`nx prepush`).
> 4. If validation fails, fix the issues by amending the current commit
(do not create additional commits solely for validation fixes).
>
> ## Deliverable
> Open a pull request that removes
`.nx/workflows/sandboxing-config.yaml` (and any necessary reference
updates), including a clear description of the change and validation
performed.
>
> The following is the prior conversation context from the user's chat
exploration (may be truncated):
>
> User: please open a pr to remove this file
>
> Assistant: I’m paused waiting for you to accept or reject the pull
request action in the confirmation dialog—once you respond there, I can
continue.
>
>
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
*This pull request was created from Copilot chat.*
>
<!-- START COPILOT CODING AGENT TIPS -->
---
💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: FrozenPandaz <8104246+FrozenPandaz@users.noreply.github.com>
## Current Behavior
When using Maven plugins that fork executions (e.g. `maven-pmd-plugin`),
the Nx Maven batch executor crashes with
`java.lang.UnsupportedOperationException`. This happens because Kotlin's
`listOfNotNull()`, `mapNotNull()`, and `filter()` return immutable
lists, but Maven's `MojoExecutor.executeForkedExecutions` calls
`list.set()` on `session.projects`, which requires a mutable list.
## Expected Behavior
Maven plugins that fork executions (PMD, Checkstyle, etc.) should work
correctly with the Nx Maven batch executor. All lists assigned to
`session.projects` and `session.allProjects` are now wrapped with
`toMutableList()` to ensure Maven can mutate them as needed.
The fix is applied to both the Maven 3 and Maven 4 adapters.
## Related Issue(s)
Fixes#34758
Document how nx.dev is deployed on Netlify and how requests are routed
between Framer (marketing), Next.js (blog/courses), and Astro (docs).
Includes:
- Request flow diagram and explanation
- Edge function configuration
- _redirects file organization
- Environment variables reference
- Common debugging tasks
- How to add redirects and rewrites
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
## Current Behavior
The `@nx/gradle` plugin fails to parse the project graph when a Gradle
project name contains `.json` as a substring (e.g.
`org.acme.util.jsonutils`). The parsing logic in `processNxProjectGraph`
checks `includes('.json')` to find the JSON file path, but starts from
the task line itself. When the project name contains `.json`, the task
line matches immediately and gets used as a file path, causing an
`ENOENT` error.
## Expected Behavior
The plugin correctly skips the task line and finds the actual JSON file
path on the following line, regardless of whether the project name
contains `.json`.
Two changes:
1. Increment `index` after matching the task line to skip it before
searching for the JSON path
2. Use `endsWith('.json')` instead of `includes('.json')` for a more
precise match
## Related Issue(s)
Fixes#34768
## Summary
- Bumps `fork-ts-checker-webpack-plugin` from `7.2.13` to `9.1.0` in
root `package.json` and `packages/webpack/package.json`
- v7 depends on `memfs` v3 which uses a deprecated `fs.stat` API,
causing console warnings on Node 22+
- v9 uses updated `memfs` and has no breaking API changes for the
constructor pattern used by Nx
## Test plan
- [ ] Run Angular webpack app and verify no `fstat` deprecation warnings
- [ ] Run Node webpack build and verify no regressions
- [ ] Verify type checking still works via the plugin
Fixes#34404
## Summary
- `pathToKey` was incorrectly assigned
`this._options.devServerConfig.sslCert` instead of `sslKey` in all 4
Module Federation dev server plugins (Rspack, Rspack SSR, Angular,
Angular SSR)
- This caused SSL to break when using separate cert and key files
## Files Changed
-
`packages/module-federation/src/plugins/nx-module-federation-plugin/rspack/nx-module-federation-dev-server-plugin.ts`
-
`packages/module-federation/src/plugins/nx-module-federation-plugin/rspack/nx-module-federation-ssr-dev-server-plugin.ts`
-
`packages/module-federation/src/plugins/nx-module-federation-plugin/angular/nx-module-federation-dev-server-plugin.ts`
-
`packages/module-federation/src/plugins/nx-module-federation-plugin/angular/nx-module-federation-ssr-dev-server-plugin.ts`
## Test plan
- [ ] Verify SSL works with separate cert/key files in Module Federation
dev server (Rspack)
- [ ] Verify SSL works with separate cert/key files in Module Federation
dev server (Angular)
Fixes#34811
## Summary
- When native bindings fail to load (`IS_WASM = true`),
`runningTasksService` is `null`
- Two call sites in `task-orchestrator.ts` accessed it without null
checks, causing `Cannot read properties of null (reading
'addRunningTask')` during `nx serve`
- Added optional chaining (`?.`) at both sites, matching the existing
guard pattern already used elsewhere in the same file
## Test plan
- [ ] Run `nx serve` in an environment where native bindings are
unavailable (e.g. missing `@nx/nx-<platform>`)
- [ ] Verify no crash on `addRunningTask` or `removeRunningTask`
Fixes#34573
## Current Behavior
The Playwright plugin does not include `tsconfig*.json` files from
dependency projects as inputs. Playwright resolves these files when
running tests, leading to unexpected file reads that aren't tracked for
caching.
## Expected Behavior
When the `production` named input is used, the Playwright plugin infers
`^{projectRoot}/tsconfig*.json` as an input so dependency tsconfig files
are properly tracked. This is not needed for the `^default` branch since
it already includes all dependency files.
## Current Behavior
Running `npx nx@next download-cloud-client` outside an Nx workspace
fails with "The current directory isn't part of an Nx workspace" because
`download-cloud-client` is not in the `isNxCloudCommand` list in
`packages/nx/bin/nx.ts`. This means the process exits at the
`handleNoWorkspace` guard before ever reaching the handler fixed in
#34746.
## Expected Behavior
`download-cloud-client` runs successfully outside an Nx workspace, like
`login`, `logout`, `polygraph`, and other cloud commands that don't need
workspace context.
## Related Issue(s)
Follows up on #34728 and #34746 — `download-cloud-client` was added
before the cloud command bypass existed and was missed when the bypass
was introduced.
## Summary
- `expandWildcardTargetConfiguration` was explicitly copying only
`projects` and `dependencies` when expanding glob patterns in
`dependsOn` target names, dropping `params` and `options`
- This meant `"params": "forward"` (and `"options": "forward"`) had no
effect when the `dependsOn` target used a wildcard like `"target":
"build*"`
- Fix uses object spread (`...dependencyConfig`) to preserve all
properties from the original dependency config
## Bug
Given this `project.json`:
```json
{
"targets": {
"build-all": {
"executor": "nx:noop",
"dependsOn": [
{
"target": "build*",
"params": "forward"
}
]
}
}
}
```
Running `nx run project:build-all --myParam=value` would correctly
resolve the wildcard to match targets like `build`, `build:test`,
`build:prod`, etc. — but `--myParam=value` was never forwarded to those
targets because `params: "forward"` was dropped during expansion.
## Root Cause
In `expandWildcardTargetConfiguration`
(`packages/nx/src/tasks-runner/utils.ts`), the matched targets were
mapped with only `target`, `projects`, and `dependencies`:
```typescript
return matchingTargets.map((t) => ({
target: t,
projects: dependencyConfig.projects,
dependencies: dependencyConfig.dependencies,
// params and options were missing!
}));
```
## Fix
Use object spread to carry over all properties:
```typescript
return matchingTargets.map((t) => ({
...dependencyConfig,
target: t,
}));
```
## Test plan
- [x] Added test case verifying `params: "forward"` is preserved after
wildcard expansion
- [x] All 45 existing tests in `utils.spec.ts` continue to pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The change removes Nx Cloud routes from the list of excluded URL rewrite
paths in the Framer edge rewrite configuration. This means requests to
those Nx Cloud paths will now be eligible for the rewrite behavior
instead of being skipped.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Telemetry only reports event duration via the `measureAndTrack` helper,
which uses a `[track] ` prefix convention. No task-level metrics (count,
cache hits, project count) are reported.
## Expected Behavior
- New event dimensions: `taskCount`, `projectCount`, `cachedTaskCount`
available for telemetry events
- `TaskTelemetryLifeCycle` reports task execution metrics (duration,
task count, project count, cached task count) — only runs on the main
CLI process, not on DTE agents
- `performance.measure()` with `detail: { track: true, ... }` replaces
the `measureAndTrack` / `[track] ` prefix pattern
- Perf observer automatically forwards detail entries matching known GA4
dimension keys
- `reportEvent` simplified to a pass-through — callers use
`customDimensions` keys directly
- `customDimensions` and `EventParameters` exported for use by callers
## Current Behavior
The TUI help text format is currently malformed.
<img width="1181" height="191" alt="Screenshot 2026-03-07 at 11 13 40"
src="https://github.com/user-attachments/assets/e66316eb-600d-4456-920e-c9538c10e7e7"
/>
- The links overlap the plaintext
- Some of the plaintext has the link color
- The bullet points are on the same line
## Expected Behavior
Separation of links and plaintext with bullet points on separate lines.
### Changes
The text output of the TUI help section looks like this:
```txt
│ │
│ Thanks for using Nx! To get the most out of this terminal UI, please check out the docs: https://nx.dev/terminal-uiIf you would prefer to not use the TUI, you can disable it by: - Adding the `--no-tui` flag to your │
│ command.- Setting NX_TUI=false in your environment. │
│ If you are finding Nx useful, please consider giving it a star on GitHub, it means a lot: https://github.com/nrwl/nx │
│ │
```
This PR corrects the layout to be more readable:
```txt
│ │
│ Thanks for using Nx! To get the most out of this terminal UI, please check out the docs: https://nx.dev/terminal-ui │
│ │
│ If you would prefer to not use the TUI, you can disable it by: │
│ - Adding the `--no-tui` flag to your command. │
│ - Setting `NX_TUI=false` in your environment. │
│ │
│ If you are finding Nx useful, please consider giving it a star on GitHub, it means a lot: https://github.com/nrwl/nx │
│ │
```
### Notes
The help link `https://nx.dev/terminal-ui` doesn't exist at the moment.
## Current Behavior
When a user creates a new workspace with `create-nx-workspace`, they are
not asked about analytics. The analytics prompt only appears later on
the first `nx` command run, or via the 22.6.0 migration.
## Expected Behavior
Users are prompted to opt in or out of usage analytics during
`create-nx-workspace`, so the preference is set from the start. The
prompt matches the style of other prompts in the flow (autocomplete with
Yes/No choices).
- **Preset flow**: The `analytics` property is set via the workspace
generator's `createNxJson`, so `nx.json` is properly formatted by
prettier through `formatFiles(tree)`
- **Template flow**: The `analytics` property is written directly to
`nx.json` (matching the pattern used by `setNeverConnectToCloud`)
- Supports `--analytics` CLI flag for non-interactive usage
- Skips the prompt in CI and non-interactive environments (defaults to
`false`)
## Current Behavior
The Nx vs Turborepo comparison doc mixed setup complexity with advanced
features without a clear progression. Some sections were missing (code
generation comparison, cross-repo coordination, project graph
visualization). Images were in PNG format.
## Expected Behavior
- Progressive structure: starts with basics (onboarding, running tasks)
and moves to advanced capabilities (CI, AI, cross-repo); beats the
misconception that starting with Nx is more complex or you need to go
full in
- Overview table at top for quick comparison with links to sections (we
could collapse it if it gets too much 🤔)
- New sections: Running tasks (to emphasize how simple it is to run
tasks in an existing repo, again beating the misconceptions that are
around), Cross-repo coordination, CI throughput, Project graph
- Improved sections: Onboarding (incremental adoption story), Caching
(real benchmark configs), Code generation (acknowledges turbo gen)
- All images converted to AVIF
- Migration doc updated: removed beta/Nx 21 language for continuous
tasks
New docs:
- [comparison
doc](https://deploy-preview-34792--nx-docs.netlify.app/docs/guides/adopting-nx/nx-vs-turborepo)
- [migration doc (mostly
untouched)](https://deploy-preview-34792--nx-docs.netlify.app/docs/guides/adopting-nx/from-turborepo)
## Related Issue(s)
N/A - documentation improvement
## Current Behavior
- `hashBatchTasks` hashes tasks one-by-one via `Promise.all` +
`hashTask` (singular), making N separate native hasher calls.
- Batch cache resolution rebuilds the entire remaining task graph each
wave via `removeTasksFromTaskGraph`, which is O(tasks) per wave and can
crash if `graph.dependencies[id]` is undefined.
- `postRunSteps` for cached batch results is deferred until all cache
resolution completes.
- TUI batch groups appear below "Waiting for task..." placeholders
because their status is based on nested task statuses, which may not
have transitioned yet.
## Expected Behavior
- `hashBatchTasks` uses `hashTasks` (plural) to batch all tasks into a
single native hasher call.
- Batch cache resolution uses a new `walkTaskGraph` util that walks
topologically via in-degree counters — only visits direct dependents per
wave instead of rebuilding the graph.
- `postRunSteps` runs incrementally per wave during cache resolution.
- TUI batch groups are treated as in-progress by existence (since
`start_batch` was called), only moving to completed when all nested
tasks are done.
## Related Issue(s)
Fixes crash: `Cannot read properties of undefined (reading 'filter')` in
`removeIdsFromTaskGraph` during batch cache resolution.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
- Performance tracking is scattered across multiple files with separate
`PerformanceObserver` instances (daemon server, plugin worker, nx.ts)
- Only `createProjectGraphAsync` duration is reported to analytics via a
dedicated `reportProjectGraphCreationEvent` function
- The Rust telemetry `flush()` has a race condition where closing event
channels before sending the flush request can cause the background
thread to exit before processing the flush
- The `default(timeout)` branch in the background sender does nothing
(`continue`)
## Expected Behavior
- A single centralized `PerformanceObserver` in `perf-logging.ts`
handles all performance measure reporting
- Any `performance.measure()` call prefixed with `[track]` is
automatically reported to telemetry (prefix stripped from display/event
name)
- `reportPerfEvent(name, duration)` is a generic function that works for
any perf measure
- Daemon-aware logging: uses `serverLogger` when running in the daemon,
`console.log` otherwise
- The telemetry flush race condition is fixed by sending the flush
request before closing channels
- The background sender's `default(timeout)` branch properly drains and
sends batches
- Duplicate drain logic is extracted into `enqueue_event`,
`enqueue_page_view`, and `drain_channels` helpers
### Currently tracked measures:
- `createProjectGraphAsync` — full project graph build time
- `{plugin}:createNodes` — per-plugin node creation time
- `{plugin}:createDependencies` — per-plugin dependency creation time
## Related Issue(s)
N/A
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Nuxt and rspack E2E tests fail due to multiple issues:
1. `NODE_ENV=test` leaking from Jest into build subprocesses, causing
nuxt to skip type-checking
2. ESLint errors on compiled `.vue.js` files containing `__VLS_*`
identifiers
3. Rspack multi-compiler array config tests fail without `NODE_ENV`
4. Remix vite version incompatible with vitest
Nuxt build E2E still fails due to TS6304 (`composite: true` +
`declaration: false` conflict in non-TS-solution workspaces) — that test
is skipped pending a proper fix.
## Expected Behavior
Nuxt lint, rspack, and remix E2E tests pass. Nuxt build test is skipped
with a TODO until the composite tsconfig issue is resolved.
## Related Issue(s)
Fixes CI E2E failures for nuxt, rspack, and remix.
## Changes
### Environment fixes
- **Strip `NODE_ENV` from E2E subprocess env** — Jest sets
`NODE_ENV=test` which leaked into nuxt build, causing it to skip
type-checking. Stripped globally in `getStrippedEnvironmentVariables()`.
- **Strip AI agent env vars** (`CLAUDECODE`, `CURSOR_TRACE_ID`, etc.) —
prevents the test runner's environment from leaking into e2e
subprocesses.
- **Pass `NODE_ENV` explicitly for rspack array config tests** — rspack
multi-compiler builds need `NODE_ENV` to determine build mode; pass it
via `runCLI` env option.
### Nuxt fixes
- **Add `**/*.vue.js` to ESLint flat config ignores** — compiled Vue
files contain `__VLS_*` identifiers that trigger lint errors.
- **Skip nuxt build e2e test** — pending fix for TS6304
composite/declaration conflict in non-TS-solution workspaces.
### Remix fix
- **Bump vite from `^5.0.0` to `^6.0.0`** — vitest dropped vite 5
support.
### E2E infra
- **Always print E2E workspace directory** — removed `isVerbose()` gate
so the path is always logged.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The Okta SAML doc ends with a vague "Contact your developer productivity
engineer" message. It doesn't specify what information needs to be
exchanged between the customer and DPE to enable SAML and SCIM. This
section was present in the old combined `auth-saml.md` but was lost
during the migration to separate Okta/Azure docs in astro-docs.
## Expected Behavior
The doc now has a clear "Information to exchange with your DPE" section
that outlines:
- **From your DPE** (provided up front): Nx Cloud App URL, JWT token,
and Organization ID for SCIM setup
- **Send back to your DPE**: SAML certificate and SAML entry point URL
This matches the pattern already used in the Azure SAML doc and assumes
SCIM will be configured.
## Related Issue(s)
N/A
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: llwt <llwt@users.noreply.github.com>
## Current Behavior
The TUI debug pane (F12) opens but shows no log output. This is because
PR #34426 gated `tui_logger::init_logger()` behind `NX_TUI=true` in
`initialize_logger()`. However, `enable_logger()` uses `Once::call_once`
and is called from many places (WorkspaceContext, PseudoTerminal, etc.)
before yargs sets `process.env.NX_TUI = 'true'`. Since the first call
wins, the tui-logger layer is never registered and the debug pane stays
empty.
## Expected Behavior
The TUI debug pane (F12) displays log output when the TUI is active,
regardless of which code path calls `enable_logger()` first.
## Changes
- Always register `TuiTracingSubscriberLayer` in the global tracing
subscriber — it just buffers events with no thread overhead
- Defer `tui_logger::init_logger()` (which spawns the mover thread) to
the TUI lifecycle `__init`, where we know the TUI is actually in use
- Non-TUI contexts still avoid the background thread cost
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
We do not track changes to `libs.versions.toml` files
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Include `libs.versions.toml` files in the list of files that we track
when we hash. This ensures that changes to that file will trigger a
regeneration of the gradle project graph.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
- Remove relevant native files entries from `.nxignore`, causing them to
be missing inputs.
- Remove stale entry from `.nxignore` used to workaround an issue that
was already solved
## Current Behavior
The npm audit CI job fails with a critical vulnerability
(GHSA-r275-fr43-pm7q) in `simple-git < 3.29.0`, pulled in transitively
via `nuxt@3.17.6` → `@nuxt/devtools@2.6.2` → `simple-git@3.28.0`.
## Expected Behavior
The audit passes. Bumping `nuxt` from `^3.10.0` to `^3.21.1` pulls in
`@nuxt/devtools@3.2.3` → `simple-git@3.33.0`, which includes the fix.
## Related Issue(s)
Fixes the failing audit job:
https://github.com/nrwl/nx/actions/runs/22930179319/job/66549735267
## Current Behavior
The Maven plugin is at a previous version and needs to be updated.
## Expected Behavior
The Maven plugin version is bumped to `0.0.15`, with a corresponding
migration for users upgrading to Nx `22.6.0-beta.12`.
## Related Issue(s)
N/A
## Current Behavior
The Maven plugin only reports dependencies between workspace projects.
External dependencies (Spring, JUnit, etc.) from Maven Central are
invisible — `nx graph` doesn't show the full dependency picture, and Nx
can't invalidate cache when an external dependency changes.
## Expected Behavior
External Maven dependencies now appear as full-fidelity external nodes
in the Nx project graph, with dependency edges between them, hashes for
cache correctness, and `externalDependencies` inputs on targets.
### External Nodes
External nodes use the naming convention `maven:groupId:artifactId`
(e.g., `maven:org.springframework:spring-core`) with:
- Type `"maven"`
- `groupId` and `artifactId` as separate fields
- Declared version (or `"managed"` if inherited from a parent POM)
- SHA-1 hash read from Maven's `.sha1` sidecar files in
`~/.m2/repository`
### External-to-External Edges
Edges between external nodes are derived by parsing POMs from the local
Maven repository. For example, `spring-boot-starter-web` → `spring-web`
→ `spring-core`. This gives `nx graph` a complete picture of the
transitive dependency tree.
### Cache Invalidation via externalDependencies Inputs
Every cacheable target now includes `{"externalDependencies":
["maven:groupId:artifactId", ...]}` in its inputs. This means Nx
invalidates cache when a resolved artifact changes — important for
SNAPSHOTs and version ranges where the dependency can change without
`pom.xml` changing.
### Changes
**Kotlin (maven-plugin)**
- `NxProjectAnalyzerMojo.kt`: Changed ResolutionScope to COMPILE for
full transitive resolution. Added `generateExternalNodes()` with
deduplication and SHA-1 hash reading. Added `generateExternalEdges()`
via POM parsing from ~/.m2. Embedded external nodes in createNodesResult
tuples, added project-to-external and external-to-external dependency
edges.
- `NxProjectAnalyzer.kt`: Uses `project.artifacts` (transitive) instead
of `project.dependencies` (direct only) for external deps. Added
`artifactFile` field for hash lookup. Collects external dep names and
passes them to target factory.
- `NxTargetFactory.kt`: Accepts externalDependencies list, threads it
through all target creation methods, and adds `{"externalDependencies":
[...]}` to every cacheable target's inputs.
**TypeScript**
- `dependencies.ts`: External sources/targets with `maven:` prefix pass
through as-is instead of rootToProjectMap lookup.
- `types.ts`: Added `externalNodes` field to `MavenAnalysisData`.
## Related Issue(s)
<!-- Feature parity with Gradle plugin for external dependency support
-->
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
`nx show *` doesn't default to JSON
## Expected Behavior
For agents, it defaults to JSON
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
The `@nx/js/typescript` plugin only infers `dependentTasksOutputFiles`
for `tsc --build` targets when external project references are detected.
Otherwise it falls back to `^production`, which tracks dependency source
files.
This is semantically incorrect — `tsc --build` resolves dependencies
through build artifacts (`.d.ts` and `.tsbuildinfo`), never source
files, regardless of reference type (external refs, internal refs, or
ad-hoc task dependencies).
## Expected Behavior
`dependentTasksOutputFiles` is always inferred for `tsc --build` targets
since that's what tsc actually reads. The `^production` fallback is
removed as it was a proxy that's no longer needed.
## Current Behavior
After #34446, batch tasks with `depsOutputs` inputs had their hashing
deferred until after execution. This meant the streaming `endTasks`
callback fired with `task.hash = undefined`, which Cloud/DTE rejects.
## Expected Behavior
All batch tasks always have a valid hash when `endTasks` is called.
Tasks with `depsOutputs` get a preliminary hash upfront (based on
whatever outputs are on disk), then are re-hashed after execution with
fresh outputs for correct cache storage.
### How it works
1. **Phase 1** now hashes ALL root tasks at each level (not just
cache-eligible ones). Ineligible tasks get a preliminary hash so the
streaming callback always has something valid to send.
2. **Phase 2** runs the batch, then clears and re-hashes all tasks that
ran — outputs are fresh on disk, so depsOutputs tasks get correct final
hashes.
3. The re-hash logic is consolidated into a single block after both code
paths (cache-enabled and cache-skipped).
## Related Issue(s)
Fixes the undefined hash regression from #34446
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
`@nx/webpack` specifies `"less": "^4.1.3"` as a dependency, which allows
`less@4.6.0` to be installed. However, `less@4.6.0` switched to ESM
(`"type": "module"`), and `less-loader@11.x` uses `require()` to load
it. This causes a runtime error:
```
class WebpackFileManager extends implementation.FileManager {
^
TypeError: Class extends value undefined is not a constructor or null
```
This breaks any React/webpack project that uses Less stylesheets (e.g.
the "should support global and css modules" e2e test).
## Expected Behavior
The `less` version range is capped to `>=4.1.3 <4.6.0`, preventing the
incompatible ESM-only version from being installed. Less stylesheets
compile correctly with webpack and less-loader.
## Related Issue(s)
N/A - discovered via e2e test failure
## Current Behavior
The gradle project graph plugin version is 0.1.14.
## Expected Behavior
The gradle project graph plugin version is bumped to 0.1.15 with a
corresponding migration.
## Related Issue(s)
N/A - Routine version bump.
Co-authored-by: Claude <noreply@anthropic.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
Gradle task inputs in Nx do not includ configuration files like
gradle.properties, gradle/wrapper/gradle-wrapper.jar, and
gradle/wrapper/gradle-wrapper.properties. When these files change, Nx's
cache does not invalidate, potentially causing builds to use stale
cached results despite configuration changes that affect build behavior.
## Expected Behavior
Gradle wrapper and properties files are now automatically included as
inputs for all Gradle tasks when they exist in the workspace. This
ensures that changes to Gradle version (via wrapper files) or build
configuration (via gradle.properties) properly invalidate Nx's task
cache, guaranteeing accurate incremental builds and cache hits.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: lourw <lourw@users.noreply.github.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
When generating atomized CI test targets for Gradle projects, the
dependsOn entries do not respect targetNameOverrides or targetNamePrefix
configuration. This means that if a test task depends on other tasks
(like compileTestKotlin or classes), and those tasks have been renamed
via overrides or prefixed (e.g., gradle-compileTestKotlin), the
generated CI test targets reference the original, non-transformed task
names. This creates broken dependencies in the project graph.
## Expected Behavior
Atomized CI test targets now correctly apply both targetNameOverrides
and targetNamePrefix to their dependsOn entries. When a test task
depends on other tasks, the generated CI targets will reference the
properly transformed target names, ensuring dependency integrity
throughout the project graph. The fix passes these configuration
parameters through the entire CI target generation pipeline in
CiTargetsUtils.kt.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: lourw <lourw@users.noreply.github.com>
## Current Behavior
When `nx.json` exists but contains a syntax error (e.g. invalid JSON),
the nx wrapper shows:
```
[NX]: The "nx.json" file is required when running the nx wrapper.
```
This is misleading because the file exists — it's just malformed.
## Expected Behavior
The nx wrapper now distinguishes between a missing `nx.json` and a
malformed one:
- **Missing file**: `[NX]: The "nx.json" file is required when running
the nx wrapper.`
- **Parse error**: `[NX]: Failed to parse "nx.json": <actual error>. See
...`
The existence check is done upfront before attempting to parse, so the
`catch` block only handles parse errors.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Current Behavior
The `@nx/js/typescript` plugin's `getInputs()` doesn't include tsconfig
files from external project references as task inputs. When `tsc
--build` follows a project reference to another Nx project (e.g.,
`../ui-common/tsconfig.lib.json`), it reads that tsconfig file, but the
dependency isn't declared. This means changes to an external reference's
tsconfig won't invalidate the dependent task's cache.
## Expected Behavior
External project reference config files (and their extended configs) are
declared as inputs for correct cache invalidation.
## Changes
- Replace `hasExternalProjectReferences` boolean check with
`getExternalProjectReferenceConfigFiles` that collects external ref
config file paths in a single traversal
- Add collected paths as inputs alongside `dependentTasksOutputFiles`
- Remove now-unused `hasExternalProjectReferences` (one traversal
instead of two when external refs exist)
## Current Behavior
The `@nx/js/typescript` plugin infers `dependentTasksOutputFiles:
'**/*.{d.ts,tsbuildinfo}'` as inputs for typecheck tasks, but only
tracks `.d.ts` outputs from **direct** dependencies. TypeScript project
references transitively read `.d.ts` files from the full dependency
chain, so when a project typechecks, it reads `.d.ts` outputs from
transitive deps too. These undeclared inputs can cause incorrect cache
hits.
## Expected Behavior
The inferred inputs for typecheck tasks include `.d.ts` and
`.tsbuildinfo` outputs from the entire transitive dependency graph by
setting `transitive: true` on the `dependentTasksOutputFiles` input.
## Current Behavior
Each `nx` CLI invocation generates a new random session ID for GA4
analytics. This means GA4 cannot correlate multiple commands from the
same user working session, making active user tracking inaccurate.
## Expected Behavior
Session IDs are persisted in the SQLite metadata table with a 30-minute
timeout. Consecutive `nx` commands within that window share the same
session ID, enabling accurate GA4 active user tracking. After 30 minutes
of inactivity, a new session is automatically created.
## Related Issue(s)
N/A — internal analytics improvement
## Current Behavior
The webpack legacy e2e test
(`e2e-webpack:e2e-ci--src/webpack.legacy.test.ts`) fails with a snapshot
mismatch because the `reportsDirectory` value changed from
`../coverage/app3224373` to `coverage/app3224373`.
## Expected Behavior
The snapshot should match the new `reportsDirectory` path format
introduced by #34720.
## Related Issue(s)
Fixes the e2e test breakage introduced by #34720 (`fix(vitest)!: resolve
reportsDirectory against workspace root`).
## Current Behavior
Nx CLI has no mechanism for collecting usage analytics, and users are
not prompted about their analytics preferences. There is no way for the
Nx team to understand which commands, generators, and features are most
used.
## Expected Behavior
This PR adds opt-in analytics collection to the Nx CLI with two main
components:
### 1. Analytics Prompt
Users are prompted for their analytics preference on first interactive
CLI run when `analytics` is not yet configured in `nx.json`:
- Only appears when `analytics` is undefined in `nx.json`
- Skipped in CI environments
- Skipped in non-interactive terminals (piped input/output)
- Stores the user's choice as a boolean (`true`/`false`) in the
`analytics` field of `nx.json`
- Defaults to `false` if the user cancels (Ctrl+C)
- Includes a migration (`update-22-6-0/enable-analytics-prompt`) for
existing workspaces
### 2. Analytics Collector
When analytics is enabled, the CLI collects usage data via a Rust-based
telemetry service and sends it to GA4. Data collected includes:
- **Commands run** (e.g. `build`, `test`, `generate`, `add`) — tracked
as page views
- **Command arguments** — with aggressive sanitization of sensitive
values (project names, file paths, URLs, credentials, free-form text are
all redacted; only boolean flags and safe enum values are preserved)
- **Generator and package names** for `nx add` and `nx generate` (as
custom dimensions)
- **Project graph creation duration**
- **Environment metadata**: Nx version, Node version, package manager,
OS, architecture, CI detection
### Workspace Identification
Each workspace is identified by a deterministic ID (used as the GA4
client ID) with the following priority:
1. **`nxCloudId`** (or `nxCloudAccessToken`) from `nx.json` — used
directly, most stable
2. **Git remote URL** (`git remote get-url origin`) — SHA-256 hashed for
privacy
3. **First commit SHA** (`git rev-list --max-parents=0 HEAD`) — used
directly as a fallback
Each user/machine is identified separately via `node-machine-id`.
### Privacy & Safety
- No project names, file paths, or other PII is collected
- Sensitive CLI arguments are redacted (see `SENSITIVE_ARGS_KEYS` list)
- Analytics is strictly opt-in (must be `true` in `nx.json`)
- Telemetry failures are silently ignored — never blocks or crashes the
CLI
- WASM builds are excluded (no native telemetry module available)
- The native telemetry functions are loaded with optional chaining to
prevent crashes when running against published Nx binaries that don't
include them yet
### Key Files
- `packages/nx/src/utils/analytics-prompt.ts` — Prompt logic and
workspace ID generation
- `packages/nx/src/analytics/analytics.ts` — Analytics collector, event
tracking, argument sanitization
- `packages/nx/src/native/telemetry/` — Rust telemetry service
(constants, service, mod)
- `packages/nx/src/utils/machine-id-cache.ts` — Machine ID for user
identification
- `packages/nx/src/migrations/update-22-6-0/enable-analytics-prompt.ts`
— Migration for existing workspaces
## Related Issue(s)
Closes NXC-3731
Closes NXC-3732
Closes NXC-3733
Closes NXC-3734
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Coly010 <Coly010@users.noreply.github.com>
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
https://claude.ai/code/session_01BMRoUBzqhTQL6WrQFiBxnr
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
Co-authored-by: Claude <noreply@anthropic.com>
## Current Behavior
When `reportsDirectory` is configured with `{workspaceRoot}` token in
`nx.json` targetDefaults:
```json
"@nx/vitest:test": {
"options": {
"reportsDirectory": "{workspaceRoot}/coverage/{projectRoot}"
}
}
```
Coverage output lands in the wrong location. For example, with a project
at `apps/my-app`, coverage goes to `apps/my-app/coverage/apps/my-app/`
instead of the intended `coverage/apps/my-app/`.
## Expected Behavior
Coverage output should be written to
`<workspaceRoot>/coverage/apps/my-app/`.
## Root Cause
Nx's `resolveNxTokensInOptions` strips `{workspaceRoot}/` from option
values and replaces `{projectRoot}`, producing a workspace-root-relative
path (e.g. `coverage/apps/my-app`). The vitest executor then passed this
directly to vitest, which resolved it relative to the **project root** —
not the workspace root.
## Fix
Resolve non-absolute `reportsDirectory` paths against the workspace root
before passing them to vitest, so vitest writes coverage to the correct
location.
## Test Plan
- Added unit tests for the new `resolveReportsDirectory` helper
- Verified with a reproduction workspace that coverage now lands at the
correct path
## Current Behavior
The `@nx/eslint/plugin` infers a `lint` target for
`gradle-project-graph` because it contains a single `.ts` file
(`publish-maven.ts`), even though it's a Kotlin/Gradle project.
Similarly, the parent `gradle` project's `eslint .` scans into non-JS
sub-project directories unnecessarily.
## Expected Behavior
Non-JS Gradle sub-projects (`project-graph`, `batch-runner`) should not
have lint targets inferred by the ESLint plugin, and the parent `gradle`
project's lint should not scan into those directories.
## Changes
- Add `project-graph` and `batch-runner` to ESLint `ignorePatterns` in
`packages/gradle/.eslintrc.json`
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: leosvelperez <leosvelperez@users.noreply.github.com>
## Current Behavior
The Gradle plugin generates `dependsOn` entries using the shorthand
string format (e.g., `"projectName:taskName"`). This doesn't leverage
the full object syntax that Nx supports.
## Expected Behavior
`dependsOn` entries now use the object format:
- Same-project dependencies: `{ "target": "taskName" }`
- Cross-project dependencies: `{ "target": "taskName", "projects":
["proj1", "proj2"] }` with projects grouped by target name
This is more explicit, consistent with the CI targets code (which
already used object format), and enables the `projects` array for
grouping multiple project dependencies under a single target.
## Related Issue(s)
N/A - internal improvement
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
The `@nx/js/typescript` plugin sets `dependentTasksOutputFiles:
'**/*.d.ts'` for tsc tasks with external project references. This misses
`.tsbuildinfo` files that `tsc --build` reads from referenced projects
for incremental compilation, which can lead to incorrect cache hits.
## Expected Behavior
`dependentTasksOutputFiles` uses the glob `**/*.{d.ts,tsbuildinfo}`,
ensuring all files read by `tsc --build` from dependencies are tracked
as inputs for correct cache invalidation.
## Current Behavior
When the `dependency-checks` ESLint rule auto-fixes missing dependencies
in a project's `package.json`, it resolves the version from the root
`package.json` or falls back to the installed version from the project
graph. This inserts explicit version strings even when the workspace
uses catalogs, breaking same-version policy.
## Expected Behavior
The fixer now checks catalogs before falling back to installed versions.
When exactly one catalog entry for a missing dependency satisfies the
installed version, the fixer inserts `catalog:` (default catalog) or
`catalog:<name>` (named catalog) instead of an explicit version.
Fallback chain: root `package.json` → catalog lookup → installed
version.
Edge cases handled:
- Package in multiple catalogs but only one satisfies → uses that one
- Package in multiple catalogs and multiple satisfy → falls back to
installed version
- `file:` and other protocol-based versions → exact string comparison
instead of semver
- No catalog manager or no catalog definitions → falls back to installed
version
Also caches the catalog manager instance and catalog definitions per
lint run instead of re-creating them per function call.
## Current Behavior
In batch mode (Maven/Gradle), all task hashes are computed upfront in
`processScheduledBatch` before the batch executor runs any tasks. Tasks
with `dependentTasksOutputFiles` (aka `depsOutputs`) get hashed using
whatever dependency outputs happen to be on disk from a previous run.
This leads to:
- **False cache hits**: If a dependency's sources changed but its old
outputs are still on disk, the dependent task's hash matches a stale
cache entry and wrong results are served.
- **False cache misses**: On cold runs with no outputs on disk, the hash
is computed without dependency output content and never matches any
stored cache entry.
Non-batch mode doesn't have this problem because it uses lazy hashing —
tasks with `depsOutputs` are only hashed after their dependencies
complete and fresh outputs exist on disk.
## Expected Behavior
Batch mode hashes tasks topologically — each task is hashed only after
its dependencies have run and their outputs are on disk. This means
hashes are always computed against fresh outputs, eliminating both false
cache hits and false cache misses.
### How it works
`applyFromCacheOrRunBatch` now has two phases:
1. **Topological cache resolution** — Walk the **entire** batch task
graph level by level. At each level, partition root tasks into
cache-eligible vs ineligible. A task is **ineligible** for cache if it
has `depsOutputs` inputs AND any of its dependencies were not cached
(their outputs aren't on disk, so the hash would be wrong). Hash and
check cache for eligible tasks, then remove **all** roots from the graph
to expose the next level — even when some tasks are cache misses. This
ensures the walk continues past cache misses to find deeper cache hits.
2. **Run remaining tasks, then hash** — Rebuild a run graph from all
non-cached task IDs and run them through the batch executor. After the
batch completes, hash all tasks that ran. Since all outputs (including
from sibling batch tasks) are now fresh on disk, tasks with
`depsOutputs` get correct hashes on the first pass — no re-hash needed.
### Task history lifecycle fix
The batch streaming callback calls `endTasks` as tasks finish mid-batch,
but tasks haven't been hashed yet at that point (hash is deferred to
post-execution). Previously, `TaskHistoryLifeCycle` and
`LegacyTaskHistoryLifeCycle` eagerly snapshotted `task.hash` in
`endTasks`, which sent `undefined` to the native Rust layer causing a
"Missing field `hash`" crash.
**Fix:** Both lifecycles now store `TaskResult` references in `endTasks`
and defer building `TaskRun` objects until `endCommand`, when
`task.hash` is guaranteed to be set by the post-batch re-hash. The
streaming callback and `runBatch` return value also now use the original
task object reference (instead of spread copies) so that the hash
mutation from `hashBatchTasks` flows through to all stored references.
### Example: 3 tasks over 3 runs
Consider a batch with three tasks in a linear chain: **A → B → C**
- **Task A** — `lib:compile`. Inputs: source files only. No
`depsOutputs`.
- **Task B** — `app:compile`. Depends on A. Inputs: only `depsOutputs`
from Task A (e.g., `target/classes/**`). No source file inputs.
- **Task C** — `app:checkstyle`. Depends on B. Not cacheable.
Hash notation: `H(inputs…)` means the hash is a function of those
inputs.
---
#### Run 1 — Fresh (no outputs on disk, empty cache)
| Step | What happens |
|------|-------------|
| **Phase 1** | Roots = `[A]` (B depends on A, C depends on B). Hash A →
**H_A** |
| | Check cache: A = miss. A is added to `nonCachedTaskIds`. Remove all
roots. |
| **Phase 1, iter 2** | Roots = `[B]`. B has `depsOutputs` and A is
non-cached → B is **ineligible**. Added to `nonCachedTaskIds`. Remove
all roots. |
| **Phase 1, iter 3** | Roots = `[C]`. C has no `depsOutputs` →
eligible. Hash C → **H_C**. Cache miss. Added to `nonCachedTaskIds`.
Remove all roots. |
| **Phase 2** | Rebuild run graph from `nonCachedTaskIds` = {A, B, C}.
Run batch: all 3 tasks execute. A produces `target/classes/`. |
| | Hash all tasks post-execution: A → **H_A**, B → `H(A_outputs)` =
**H_B**, C → **H_C** |
| **Cache** | Store A as **H_A**, store B as **H_B**. C is not cached. |
> **Key:** B is hashed *after* the batch, when A's outputs already exist
on disk. The hash is correct on the first pass. C was still checked
against cache even though A and B were misses.
---
#### Run 2 — Warm (nothing changed, cache populated from Run 1)
| Step | What happens |
|------|-------------|
| **Phase 1, iter 1** | Roots = `[A]`. Hash A → **H_A** |
| | Check cache: A = **HIT** ✅ → restore `target/classes/` to disk. |
| **Phase 1, iter 2** | Roots = `[B]`. B has `depsOutputs` but A is
cached (not in `nonCachedTaskIds`) → B is **eligible**. Hash B →
`H(A_outputs)` = **H_B** (A's outputs just restored!) |
| | Check cache: B = **HIT** ✅ → restore B's outputs. |
| **Phase 1, iter 3** | Roots = `[C]`. Hash C → **H_C**. Not cacheable →
no hit. Added to `nonCachedTaskIds`. |
| **Phase 2** | Rebuild run graph from `nonCachedTaskIds` = {C}. Run
batch with just C. Hash C post-execution. |
> **Key:** Phase 1 restored A's outputs from cache *before* hashing B.
So B's hash matches Run 1's value → cache hit. The topological walk
peels the chain one level at a time: A → B → C.
---
#### Run 3 — Source changed (A's source modified, stale outputs from Run
2 still on disk)
| Step | What happens |
|------|-------------|
| **Phase 1, iter 1** | Roots = `[A]`. Hash A → `H(A_src')` = **H_A'**
(new hash!) |
| | Check cache: A = **miss** (H_A' not in cache). A added to
`nonCachedTaskIds`. |
| **Phase 1, iter 2** | Roots = `[B]`. B has `depsOutputs` and A is
non-cached → B is **ineligible**. Added to `nonCachedTaskIds`. |
| **Phase 1, iter 3** | Roots = `[C]`. C has no `depsOutputs` →
eligible. Hash C → **H_C**. Cache miss. Added to `nonCachedTaskIds`. |
| **Phase 2** | Run batch: all 3 tasks execute. A produces *new*
outputs. |
| | Hash all tasks post-execution: A → **H_A'**, B → `H(A_new_outputs)`
= **H_B'**, C → **H_C** |
| **Cache** | Store A as **H_A'**, store B as **H_B'**. C is not cached.
|
> **Key:** Because hashing happens after execution, B is always hashed
against A's *fresh* outputs. No stale hash, no re-hash needed. And C is
still checked against cache at every level, even when upstream tasks
miss.
---
#### Summary of hashes across runs
| Task | Run 1 (fresh) | Run 2 (warm) | Run 3 (src changed) |
|------|--------------|-------------|-------------------|
| **A** | miss → cache **H_A** | hit **H_A** ✅ | miss → cache **H_A'** |
| **B** | miss → post-exec hash & cache **H_B** | hit **H_B** ✅ | miss →
post-exec hash & cache **H_B'** |
| **C** | not cacheable → runs | not cacheable → runs | not cacheable →
runs |
### Maven plugin fixes
Several fixes to the Maven plugin to ensure correct batch behavior:
- **Propagate batch runner exit code failures**: Batch runner process
exit codes are now correctly propagated so task failures are reported
properly.
- **Use glob patterns for gitignored dependent task outputs**:
`depsOutputs` patterns like `target/classes` are now resolved using glob
patterns, fixing issues with `.gitignore`d output directories.
- **Fix inputs for `maven:test`**: Test task inputs now correctly
include test source files so hash changes when tests are modified.
- **Include test sources in `testCompile` task hash**: The `testCompile`
target now includes `src/test/java` in its inputs.
## Related Issue(s)
Related to #30949
This PR makes it much easier for everyone to contribute to our docs.
1. [Vale](https://vale.sh/) is installed via `mise` - This is our
automated editor.
2. Claude skill to invoke Vale and also follow
`astro-docs/STYLE_GUIDE.md` for things that Vale cannot pick up.
3. `CLAUDE.md` instruction to invoke the skill (2) whenever someone is
updating docs.
Demo: https://www.loom.com/share/415a9da056d3483da297fda61f7e7382
Remove the isNxCloudUsed guard that prevented the command from running
without an nx.json. Now gracefully falls back to default cloud URL when
not in an Nx workspace.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Remove NX_CLOUD_IO_TRACING_DIRECTORY environment variable.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
Running `nx migrate latest` updates all `@nx/*` packages except
`@nx/angular-rspack` and `@nx/angular-rspack-compiler`.
## Expected Behavior
`nx migrate latest` updates `@nx/angular-rspack` and
`@nx/angular-rspack-compiler` along with all other `@nx/*` packages.
## Related Issue(s)
Fixes#32772
## Current Behavior
The `getOutputs()` function in the `@nx/js/typescript` plugin derives
`.tsbuildinfo` filenames from `config.basenameNoExt` (the outer
`ConfigContext`, which always corresponds to `tsconfig.json`) instead of
the currently iterated internal project reference. This causes all
internal references to produce `dist/tsconfig.tsbuildinfo` as the output
path instead of the correct filenames like
`dist/tsconfig.lib.tsbuildinfo` and `dist/tsconfig.spec.tsbuildinfo`.
This leads to:
- Cache misses because Nx looks for `dist/tsconfig.tsbuildinfo` which
doesn't exist
- Missing actual `.tsbuildinfo` files from cache outputs
- Potential race conditions in parallel `tsc --build` invocations
## Expected Behavior
The `.tsbuildinfo` filename should be derived from each individual
tsconfig's file path. For a project with:
- `tsconfig.lib.json` → `dist/tsconfig.lib.tsbuildinfo`
- `tsconfig.spec.json` → `dist/tsconfig.spec.tsbuildinfo`
- `cypress/tsconfig.json` → `cypress/dist/tsconfig.tsbuildinfo`
The fix changes the loop in `getOutputs()` to iterate over entries (path
+ data pairs) so the basename can be correctly derived from each
tsconfig's file path. Also fixes the `outFile` and no-outDir branches
which had similar issues using the outer config parameter instead of the
loop variable.
## Related Issue(s)
Fixes#34737
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: vsavkin <vsavkin@users.noreply.github.com>
Rewrite README to reflect Nx's current capabilities: zero-config caching,
polyglot plugin system, CI distribution, AI-native tooling, and self-healing CI.
Remove stale badges (Gitter, Semantic Release). Point courses banner to nx.dev/courses.
## Current Behavior
When running `@nx/vitest:configuration` on an Angular project, the
generator always generates `import
'@analogjs/vitest-angular/setup-zone'` regardless of whether the app is
zoneless. Additionally, the setup file generation logic for Angular 21+
(which uses `setupTestBed()`) only exists in
`packages/angular/src/generators/utils/add-vitest.ts`
(`createAnalogSetupFile`), making the vitest generator not
self-contained.
## Expected Behavior
The vitest configuration generator should:
- Auto-detect whether an Angular project is zoneless (by checking
polyfills for apps, or zone.js dependency for libraries)
- Generate `setup-snapshots` instead of `setup-zone` for zoneless
projects
- Handle Angular 21+ `setupTestBed()` setup directly, without requiring
a separate function in the angular package
- Accept an explicit `zoneless` option to override auto-detection
## Related Issue(s)
Fixes#33983
## Current Behavior
The `resolveId` hook in `nxViteTsPaths` processes all import paths,
including Vite root-relative paths (e.g. `/src/test-setup.ts`). In Vite,
`/foo` means "relative to project root," but `nxViteTsPaths` resolves it
via tsconfig's `baseUrl` (workspace root), producing wrong paths.
In an Angular standalone workspace with Vitest + Analog, generating a
library and running `nx test test-lib` fails with `Error: Need to call
TestBed.initTestEnvironment() first` because the library's
`src/test-setup.ts` resolves to the root app's file instead. The Analog
Angular compiler never compiled that file, so the transform returns
empty content and `setupTestBed()` never runs.
## Expected Behavior
`nxViteTsPaths` should skip `/`-prefixed paths and let Vite's built-in
resolver handle them. These are filesystem paths (absolute or
root-relative), not TypeScript import specifiers. Library tests should
work correctly with their own `test-setup.ts`.
## Related Issue(s)
Fixes#34300
## Current Behavior
The TypeScript plugin's typecheck target inference was tied to
`tsconfig.json`, so users could not configure a different tsconfig file
name for typecheck inference.
## Expected Behavior
The plugin supports configuring `typecheck.configName`, which defaults
to `tsconfig.json`.
## Current Behavior
Running Nx Cloud commands (login, logout, polygraph, etc.) from outside
an Nx workspace fails with 'The current directory is not part of an Nx
workspace' because handleNoWorkspace exits before reaching the cloud
command handler. Additionally, polygraph was not in the isNxCloudCommand
list.
## Expected Behavior
Nx Cloud commands work regardless of whether you are inside an Nx
workspace, since they only delegate to the cloud client.
## Related Issue(s)
N/A
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Current Behavior
Plugin cache writes (`writeJsonFile`, `writeFileSync`) across the
codebase have inconsistent error handling:
- Some throw on failure, aborting project graph calculation entirely
- Some silently swallow errors, leaving corrupted cache files on disk
- No mechanism exists to recover from oversized or corrupted caches
- `nx-deps-cache.ts` retries 5 times then throws, crashing the graph
## Expected Behavior
- Plugin cache write failures **never** abort graph calculation — they
warn and continue
- A centralized `safeWritePluginCache` utility handles all hash-map
plugin caches with a 3-step strategy:
1. Attempt full write
2. On failure: evict oldest 50% of entries (LRU) and retry
3. On second failure: wipe cache file, log warning, return without
throwing
- `PluginCache<T>` class wraps cache data with a Proxy that
transparently tracks access order, enabling true LRU
eviction (not just insertion order)
- Access order is stored as a simple `string[]` array — front is oldest,
back is most recent
- Backward-compatible with 3 on-disk formats: legacy plain `Record`,
timestamp-based `{ entries, accessedAt }`, and
current `{ entries, accessOrder }`
- All plugin cache consumers migrated: package-json, js/lockfile,
dotnet, cypress, playwright, gradle, maven
- `nx-deps-cache.ts` changed from throw-after-retries to warn + cleanup
- New utilities exported via `@nx/devkit` internals for plugin authors
## Related Issue(s)
Fixes NXC-3833"
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
## Current Behavior
Several security-related issues reported:
1. **copy-webpack-plugin** (#34632): `@nx/webpack` and `@nx/next` pin
`copy-webpack-plugin@^10.2.4` which transitively depends on
`serialize-javascript@^6.0.1` (vulnerable) and `fast-glob` (supply-chain
risk).
2. **koa** (#34621): `@nx/module-federation` transitively pulls
`koa@3.0.3` via `@module-federation/dts-plugin`, which is vulnerable to
CVE-2026-27959 (Host Header Injection, fixed in koa 3.1.2).
3. **css-minimizer-webpack-plugin**: `@nx/webpack` pins `^5.0.0` which
also depends on vulnerable `serialize-javascript@^6.0.1`.
4. **@module-federation/enhanced**: Versions `<2.1.0` transitively
install vulnerable `koa` via `dts-plugin`.
5. **Next.js**: Versions `16.0.x` are vulnerable to GHSA-9g9p-9gw9-jx7f
(Image Optimizer DoS) and GHSA-5f7q-jpqc-wp7h (PPR Resume memory
consumption).
6. **minimatch** (#34701): User reports minimatch vulnerability, but the
Nx pnpm catalog already pins the patched version `10.2.4`. No code
change needed — users should delete their lockfile and reinstall.
## Expected Behavior
1. **copy-webpack-plugin** bumped to `^14.0.0` which uses
`serialize-javascript@^7.0.3` (patched). Added `noErrorOnMissing: true`
to all 3 copy-webpack-plugin usage sites to handle the v14 breaking
change where missing glob patterns now throw errors by default.
2. **css-minimizer-webpack-plugin** bumped to `^8.0.0` which uses
`serialize-javascript@^7.0.3` (patched).
3. **koa** bumped to `^3.1.2` in `@nx/node` versions.ts.
`@module-federation/dts-plugin@2.1.0` completely removes koa dependency.
4. **@module-federation/enhanced**, **runtime**, **sdk** bumped to
`^2.1.0` across all packages (`@nx/module-federation`, `@nx/react`,
`@nx/angular`, `@nx/rspack`). Added `noErrorOnMissing` fix for
`@module-federation/enhanced` 2.x `runtime-library-control.plugin.ts`
compatibility.
5. **Next.js** bumped to `~16.1.6` and `eslint-config-next` to
`^16.1.6`.
6. **minimatch** — no change needed, already resolved.
### Migrations added (22.6.0-beta.10)
- `@nx/module-federation`: Bump MF packages to `^2.1.0`
- `@nx/react`: Bump `@module-federation/enhanced` to `^2.1.0`
- `@nx/angular`: Bump `@module-federation/enhanced` to `^2.1.0`
- `@nx/node`: Bump `koa` to `^3.1.2`
- `@nx/next`: Bump `next` to `~16.1.6`
### Skipped
- **esbuild** (`<=0.24.2`, moderate severity, dev server only): Fix
requires breaking change jump from `^0.19.2` to `0.25+`. Will address
separately.
## Testing
Created a fresh Nx workspace with all affected plugins to verify `npm
audit` is clean after changes:
- `@nx/next` (nextapp)
- `@nx/webpack` (webpackapp)
- `@nx/rspack` (shell, remote1, remote2 via Module Federation)
- `@nx/module-federation` (shell + remotes with MF config)
- `@nx/react` (MF host/remotes)
- `@nx/node` + koa (api)
```
├── apps
│ ├── api # @nx/node (koa)
│ ├── nextapp # @nx/next
│ ├── remote1 # @nx/react + rspack + MF
│ ├── remote2 # @nx/react + rspack + MF
│ ├── shell # @nx/react + rspack + MF (host)
│ └── webpackapp # @nx/webpack
```
Post-change audit result — only remaining issue is esbuild (moderate,
skipped intentionally):
```
# npm audit report
esbuild <=0.24.2
Severity: moderate
esbuild enables any website to send any requests to the development server
and read the response - https://github.com/advisories/GHSA-67mh-4wv8-2f99
fix available via `npm audit fix --force`
Will install esbuild@0.27.3, which is a breaking change
1 moderate severity vulnerability
```
## Related Issue(s)
Fixes#34632Fixes#34621Fixes#34701
This addresses feedback on PR #34726 - .netlify directories are build
artifacts and should be in .gitignore rather than .nxignore. This also
generalizes the pattern from `astro-docs/.netlify` to `.netlify` to
cover the root-level `.netlify/static/documentation` directory that was
causing duplicate project detection.
Co-authored-by: Claude <noreply@anthropic.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
This PR surfaces install errors instead of silently failing without
anything useful printed. It also records `needs_input` in the AX flow as
`cancelled` so we account for cases where AI agents exit without
recording either success, complete, or cancelled.
BEFORE:
<img width="1288" height="381" alt="image"
src="https://github.com/user-attachments/assets/44fe9642-764e-452b-90be-f30c4a230be5"
/>
AFTER:
<img width="1279" height="850" alt="Screenshot 2026-03-05 at 12 30
25 PM"
src="https://github.com/user-attachments/assets/24c96584-2790-4dcb-8404-7e221c50876c"
/>
## Notes
1. **Remove `--silent` from all PM install commands** — since
`execAndWait` uses `exec()` (captures output in memory, never shown to
terminal), `--silent` just suppressed error info for no benefit
2. **Increase `maxBuffer`** from default 1MB to 10MB to prevent process
being killed when PMs emit verbose output
3. **Fallback error message** when both stderr and stdout are empty —
includes exit code and log file path
4. **Structured sandbox error** with exit code, log file, and actionable
hint
5. **Record telemetry stat** for AI agent `needs_input` flow (was
previously missing)
6. **Migrate from deprecated `CreateNxWorkspaceError`** to `CnwError` in
`execAndWait`
## Related Issue(s)
Fixes NXC-4035
## Current Behavior
[The courses
page](https://deploy-preview-34669--nx-dev.netlify.app/courses) has a
hero section with title and subtitle but no mention of the YouTube
channel for one-off educational videos.
## Expected Behavior
A subtle callout link with a YouTube icon appears below the hero
subtitle, directing users to the Nx YouTube channel for one-off
educational videos.
## Related Issue(s)
N/A
## Current Behavior
No blog post covering the AX principles behind recent Nx CLI
improvements.
## Expected Behavior
New draft blog post (`docs/blog/2026-03-05-making-nx-agent-ready.md`)
covering:
- Why agentic experience (AX) matters for developer tools
- AX principles: context management, structured feedback, idempotency,
informative output
- The open question of human vs agent experience divergence
- A forward look at `nx connect` going agentic
The post is set to `draft: true` and scheduled for Thursday March 5th.
## Related Issue(s)
N/A — new content
---
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Juri <juri.strumpflohner@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: MaxKless <MaxKless@users.noreply.github.com>
## Current Behavior
When Claude Code creates worktrees under `.claude/worktrees/`, Nx picks
them up as workspace projects. This causes duplicate project errors that
break the project graph.
## Expected Behavior
`.claude/worktrees` is gitignored so worktree copies of the repo don't
interfere with Nx's project detection.
## Current Behavior
After the napi v2→v3 migration, running tasks with TUI enabled while Nx
Console is connected causes a panic: `there is no reactor running, must
be called from the context of a Tokio 1.x runtime`. This happens because
`end_command` is a sync NAPI callback that calls `end_running_tasks()`
which uses `tokio::spawn`, but napi v3 no longer wraps sync callbacks
with Tokio runtime context by default.
## Expected Behavior
Tasks complete without panic when Nx Console is connected and TUI is
enabled.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Bump Nx version to 22.6.0-beta.8
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
On Windows, the `@nx/js` TypeScript plugin fails during project graph
creation with a path mismatch assertion error. The plugin uses
`path.join()` and `path.relative()` from Node's `path` module, which
produce backslash-separated paths on Windows. These are passed to
TypeScript's `readConfigFile` API, which has an internal inconsistency:
its parser normalizes paths to forward slashes for diagnostics but
retains the original (backslash) path on the source file, causing an
assertion failure when parse diagnostics are present.
Additionally, `posix.normalize()` was incorrectly relied upon to convert
backslashes to forward slashes — it only normalizes paths already in
POSIX format.
## Expected Behavior
The TypeScript plugin normalizes paths to POSIX format (forward slashes)
for TypeScript API compatibility and cache key consistency, working
correctly on both Windows and Unix systems.
## Related Issue(s)
Fixes#31232
## Current Behavior
When creating a new Angular standalone project with Vitest
(`create-nx-workspace` → Angular → Standalone), running tests fails
with:
```
✘ [ERROR] TS2304: Cannot find name 'Disposable'. [plugin angular-compiler]
node_modules/@vitest/spy/dist/index.d.ts:158:80:
158 │ ...tends Procedure | Constructable = Procedure> extends Disposable {
```
For standalone (root) projects, `getRootTsConfigFileName` returns
`tsconfig.json` — the same file as the project tsconfig.
`getNeededCompilerOptionOverrides` compares the file against itself,
sees `skipLibCheck: true` already matches, and strips it. The result:
`skipLibCheck` disappears from the final `tsconfig.json`.
## Expected Behavior
Standalone Angular projects should have `skipLibCheck: true` in their
`tsconfig.json` (matching what Angular CLI generates), preventing type
errors from third-party `.d.ts` files like `@vitest/spy`.
## Related Issue(s)
Fixes#34164
## Current Behavior
When using `generatePackageJson: true`, Nx generates a pruned
`pnpm-lock.yaml` that includes the `catalogs:` section from the root
lockfile. Since the dist folder doesn't include `pnpm-workspace.yaml`,
pnpm 10.24.0+ throws `ERR_PNPM_LOCKFILE_CONFIG_MISMATCH` during `pnpm
install --frozen-lockfile`.
## Expected Behavior
The pruned lockfile strips the `catalogs` section since the dist folder
has no `pnpm-workspace.yaml` to define them.
## Related Issue(s)
Fixes#34337
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
When `NX_PREFIX_OUTPUT=true` (e.g. `--output-style=stream`),
`nx:run-commands` tasks are forced out of the fast direct execution path
and into a slower forked process, because the direct path had no
mechanism to prefix output with project names.
## Expected Behavior
`nx:run-commands` tasks stay on the direct execution path even when
output prefixing is needed. The PTY's `quiet` mode suppresses direct
stdout writes, and the orchestrator intercepts output via `onOutput`
callbacks to prefix each line with the colored project name before
writing to stdout.
### Changes
- Allow `nx:run-commands` to use the direct execution path regardless of
prefix output setting
- When prefixing is enabled, suppress direct stream output and intercept
it via `onOutput` to add colored project-name prefixes
- Extract a shared `writePrefixedLines` utility used by both the direct
path and the existing `addPrefixTransformer` stream — eliminates
duplicated split/filter/prefix logic
- Use `os.EOL` instead of manual platform newline detection
- Hoist formatted prefix string outside the per-line callback for
efficiency
- Simplify boolean expressions (`streamOutput && !shouldPrefix` instead
of ternary, remove redundant guard)
## Related Issue(s)
N/A — performance improvement for streaming output mode.
## Current Behavior
Requests to `/.netlify/images?url=...` are intercepted by the Framer
proxy edge function and forwarded to Framer, returning broken images on
docs pages (e.g. sandboxing page).
## Expected Behavior
`/.netlify/*` requests pass through to Next.js, which rewrites them to
the astro-docs site where the actual images are hosted.
## Related Issue(s)
Fixes DOC-436
This PR adds a new feature page for sandboxing.
- What task sandboxing is (hermetic task execution with IO tracing)
- Why hermeticity matters for caching correctness, with concrete Vite
examples
- How to investigate sandbox violations in the Nx Cloud UI (with
annotated screenshots)
- How to inspect declared inputs/outputs with `nx show target` and `nx
show project`
- How to enable sandboxing (`NX_CLOUD_IO_TRACING_DIRECTORY`) and
configure path exclusions
The page is listed under Orchestration & CI in the sidebar.
Preview:
https://deploy-preview-34686--nx-docs.netlify.app/docs/features/ci-features/sandboxing
<img width="396" height="658" alt="image"
src="https://github.com/user-attachments/assets/96dc802a-eb1a-4b4a-9df4-bde846ed2775"
/>
## Related Issue(s)
Closes DOC-429
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The sandboxing config only excludes reads for `node_modules`, `.nx`,
`.git`, and `package.json` paths. No write exclusions are configured,
and `pnpm-workspace.yaml` is not excluded from reads.
## Expected Behavior
- The `nx/**/*` directory is excluded from write sandboxing, allowing
write operations to the nx directory during CI workflows.
- `pnpm-workspace.yaml` is excluded from read sandboxing, similar to
other workspace config files.
## Related Issue(s)
N/A
This PR brings our CNW experience back to previous state.
## Current Behavior
The CNW prompt flow diverges from v22.1.3 in several ways:
- Shows "Which starter do you want to use?" template prompt
- Cloud prompt says "Try the full Nx platform?" instead of caching
question
- Preset flow uses simplified cloud prompt instead of CI provider →
caching fallback
- Completion message shows box banner with `accessToken=undefined`
manual URL
- Setup message includes `github.com/new` link not present in v22.1.3
## Expected Behavior
Human-visible CNW flow identical to v22.1.3 while preserving:
- NDJSON AI output (agentic experience)
- `--template` flag (works via CLI, not surfaced in prompts)
- Telemetry, error handling, AI agent detection
Changes:
- Skip template prompt, go straight to preset/stack flow
- Restore "Would you like remote caching?" with v22.1.3 wording
- Restore CI provider → caching fallback prompt chain for preset flow
- Pass actual nxCloud to createEmptyWorkspace so cloud token is real
- Restore v22.1.3 completion message ("Your remote cache is almost
complete.")
- Restore v22.1.3 getNxCloudInfo signature with rawNxCloud URL
visibility
- Restore "Nx Cloud has been set up successfully" spinner text
- Remove github.com/new link from push message
## Related Issue(s)
Closes NXC-4020
## Current Behavior
when you run things like `pnpm nx@latest init`, there might not be a
pnpm lockfile yet. So nx commands will detect the PM as `npm` by
default... even though the fact that the user is invoking the command
via `pnpm` is a strong signal that that's the PM they want to use.
## Expected Behavior
We fall back to detecting the invoking PM via env var if no lockfile
exists.
## Summary
- Read generated `config.toml` from `nx-ai-agents-config` repo as single
source of truth for Codex config format
- Deep-merge into user's existing `.codex/config.toml` using
`@ltd/j-toml` (already in monorepo)
- Adjust MCP args dynamically for Nx version (`["nx", "mcp"]` for ≥22,
`["nx-mcp"]` for <22), preserving extra user args
- Respect `multi_agent = false` if explicitly set by user
- Copy `.codex/agents/` subagent TOML files alongside existing
`.agents/skills/`
- Add unit tests (7) and e2e tests (4) for the new functionality
## Test plan
- [ ] Unit tests: `nx test nx -- --testPathPatterns set-up-ai-agents`
- [ ] E2e tests: `nx e2e e2e-nx -- --testPathPatterns
configure-ai-agents` (codex agent describe block)
- [ ] Verify codex config merges correctly with existing user config
- [ ] Verify `multi_agent = false` is not overwritten on re-run
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Current Behavior
The `extras.test.ts` e2e test is flaking on master because the snapshot
file expects `.github/workflows/ci.yml` in the "general" task inputs,
but `create-nx-workspace --no-interactive` no longer generates it.
PR #34332 accidentally updated the snapshot to include this file — the
author likely ran tests against a cached workspace from before PR #34616
fixed the CI workflow generation bug.
## Expected Behavior
The snapshot should not include `.github/workflows/ci.yml` since
`create-nx-workspace` correctly skips CI workflow generation in
non-interactive mode (fixed in PR #34616).
## Related Issue(s)
Fixes the `e2e-nx:e2e-ci--src/extras.test.ts` CI flakiness on master.
## Current Behavior
There's a bug currently where is a plugin returns a dependsOn dependency
or input that directly references another project by name, and a later
plugin renames that project, the dependsOn or input entry is left stale
and pointing at a now non-existent project.
## Expected Behavior
The old refs are kept up to date as the nodes get merged together
## AI Summary
This pull request introduces a new mechanism to handle project name
substitutions in the Nx project graph, ensuring that references to
project names in `inputs` and `dependsOn` blocks remain accurate even if
a plugin changes a project's name during graph construction. The main
addition is the `ProjectNameInNodePropsManager`, which tracks and
updates references when project names change. Several related
refactorings and improvements were made to integrate this manager into
the project configuration merging process.
**Project name substitution and consistency:**
* Added a new `ProjectNameInNodePropsManager` class to manage and apply
project name substitutions when project names change, ensuring that all
references in `inputs` and `dependsOn` blocks remain consistent.
* Integrated the `ProjectNameInNodePropsManager` into the
`mergeCreateNodesResults` function, registering substitutors for node
results, marking roots as dirty when names change, and applying
substitutions after merging.
[[1]](diffhunk://#diff-774ce1a4fd8ec0c898c989fb75e2d8c87a0055549bca680945bce50f717db117R518)
[[2]](diffhunk://#diff-774ce1a4fd8ec0c898c989fb75e2d8c87a0055549bca680945bce50f717db117L521-R551)
[[3]](diffhunk://#diff-774ce1a4fd8ec0c898c989fb75e2d8c87a0055549bca680945bce50f717db117R563-R571)
**API and function changes:**
* Modified `mergeProjectConfigurationIntoRootMap` to return an object
indicating whether a project name was changed, instead of just returning
void.
[[1]](diffhunk://#diff-774ce1a4fd8ec0c898c989fb75e2d8c87a0055549bca680945bce50f717db117L59-R62)
[[2]](diffhunk://#diff-774ce1a4fd8ec0c898c989fb75e2d8c87a0055549bca680945bce50f717db117R238-R244)
**Code organization and import cleanup:**
* Refactored imports in `project-configuration-utils.ts` for better
organization and to accommodate the new manager.
[[1]](diffhunk://#diff-774ce1a4fd8ec0c898c989fb75e2d8c87a0055549bca680945bce50f717db117L9-R25)
[[2]](diffhunk://#diff-774ce1a4fd8ec0c898c989fb75e2d8c87a0055549bca680945bce50f717db117R43-L43)
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
The clean-up PR inverted the reverse proxy logic such that only the
defined paths are passed to Next.js. The default is to return Framer
pages. Since we also remove the Nextjs redirect, we lose the `/docs` one
that was missing from `_redirects` file.
https://github.com/nrwl/nx/pull/34672
This PR adds the redirect to fix it again.
Bun includes extra consecutive async frames for async functions in call
stacks, causing `preventRecursionInGraphConstruction` to falsely detect
a recursive loop during normal project graph construction.
## Root Cause
`preventRecursionInGraphConstruction` uses `getCallSites().slice(2)` to
skip the top 2 frames (itself +
`buildProjectGraphAndSourceMapsWithoutDaemon`), then checks if
`buildProjectGraphAndSourceMapsWithoutDaemon` appears again in the
remaining frames.
In Bun, `buildProjectGraphAndSourceMapsWithoutDaemon` appears **twice
consecutively** due to async frame duplication — leaving one occurrence
after the slice, which incorrectly triggers the loop error.
**Node call stack (after `slice(2)`):**
```
#0 createProjectGraphAndSourceMapsAsync ← clean
#1 createProjectGraphAsync
#2 runOne
```
**Bun call stack (after `slice(2)`):**
```
#0 buildProjectGraphAndSourceMapsWithoutDaemon ← false positive!
#1 createProjectGraphAndSourceMapsAsync
#2 createProjectGraphAndSourceMapsAsync ← Bun duplicates async frames
#3 createProjectGraphAsync
```
## Fix
Since the call stack recursion check does not work reliably under Bun,
`preventRecursionInGraphConstruction` now returns early when running
under Bun, detected via `'Bun' in globalThis` — consistent with the
existing Bun runtime detection pattern used elsewhere in the codebase
(e.g., `isolated-plugin.ts`). The original `slice(2)` logic is preserved
unchanged for Node.js and other runtimes.
```ts
export function preventRecursionInGraphConstruction() {
// Bun's async stack traces include extra frames that cause false positives in the
// recursion check below, so we skip the check when running under Bun.
if ('Bun' in globalThis) {
return;
}
// ... existing Node.js check ...
}
```
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
----
*This section details on the original issue you should resolve*
<issue_title>getCallSites output differs between Node and Bun triggering
loop detection</issue_title>
<issue_description>### Current Behavior
Hey team,
I am trying to use Bun (1.3.5) instead of Node (v22.17.0) for running Nx
(v22.1.3) and bumped into the following error:
Command:
```bash
bunx --bun nx run api:build
```
Error:
```
NX Project graph construction cannot be performed due to a loop detected in the call stack. This can happen if 'createProjectGraphAsync' is called directly or indirectly during project graph construction.
To avoid this, you can add a check against "global.NX_GRAPH_CREATION" before calling "createProjectGraphAsync".
Call stack:
buildProjectGraphAndSourceMapsWithoutDaemon (/app/node_modules/.pnpm/nx@22.1.3_@swc-node+register@1.10.9_@swc+core@1.11.1_@swc+helpers@0.5.15__@swc+types@0.1.18_t_twtgkxomntuzxcyp4ewkmtxn2q/node_modules/nx/src/project-graph/project-graph.js:81:62)
createProjectGraphAndSourceMapsAsync (/app/node_modules/.pnpm/nx@22.1.3_@swc-node+register@1.10.9_@swc+core@1.11.1_@swc+helpers@0.5.15__@swc+types@0.1.18_t_twtgkxomntuzxcyp4ewkmtxn2q/node_modules/nx/src/project-graph/project-graph.js:274:31)
createProjectGraphAndSourceMapsAsync (/app/node_modules/.pnpm/nx@22.1.3_@swc-node+register@1.10.9_@swc+core@1.11.1_@swc+helpers@0.5.15__@swc+types@0.1.18_t_twtgkxomntuzxcyp4ewkmtxn2q/node_modules/nx/src/project-graph/project-graph.js:225:53)
createProjectGraphAsync (/app/node_modules/.pnpm/nx@22.1.3_@swc-node+register@1.10.9_@swc+core@1.11.1_@swc+helpers@0.5.15__@swc+types@0.1.18_t_twtgkxomntuzxcyp4ewkmtxn2q/node_modules/nx/src/project-graph/project-graph.js:222:45)
createProjectGraphAsync (/app/node_modules/.pnpm/nx@22.1.3_@swc-node+register@1.10.9_@swc+core@1.11.1_@swc+helpers@0.5.15__@swc+types@0.1.18_t_twtgkxomntuzxcyp4ewkmtxn2q/node_modules/nx/src/project-graph/project-graph.js:205:40)
runOne (/app/node_modules/.pnpm/nx@22.1.3_@swc-node+register@1.10.9_@swc+core@1.11.1_@swc+helpers@0.5.15__@swc+types@0.1.18_t_twtgkxomntuzxcyp4ewkmtxn2q/node_modules/nx/src/command-line/run/run-one.js:23:52)
runOne (/app/node_modules/.pnpm/nx@22.1.3_@swc-node+register@1.10.9_@swc+core@1.11.1_@swc+helpers@0.5.15__@swc+types@0.1.18_t_twtgkxomntuzxcyp4ewkmtxn2q/node_modules/nx/src/command-line/run/run-one.js:16:23)
Pass --verbose to see the stacktrace.
```
After some digging I found that the stack trace produced by
https://github.com/nrwl/nx/blob/691bb320ce1e9cc2872e1a1b364d3fdeb9e1ad0e/packages/nx/src/utils/call-sites.ts
differs between Node and Bun. When
https://github.com/nrwl/nx/blob/691bb320ce1e9cc2872e1a1b364d3fdeb9e1ad0e/packages/nx/src/project-graph/project-graph.ts#L422
is run, the produced function call tracing is:
Node (v22.17.0):
```
nrwl/nx#0 createProjectGraphAndSourceMapsAsync
nrwl/nx#1 createProjectGraphAsync
nrwl/nx#2 runOne
nrwl/nx#3 <anonymous>
nrwl/nx#4 <anonymous>
nrwl/nx#5 handleErrors
nrwl/nx#6 handler
```
Bun (1.3.5):
```
nrwl/nx#0 buildProjectGraphAndSourceMapsWithoutDaemon <- This entry causes Nx to detect a loop
nrwl/nx#1 createProjectGraphAndSourceMapsAsync
nrwl/nx#2 createProjectGraphAndSourceMapsAsync
nrwl/nx#3 createProjectGraphAsync
nrwl/nx#4 createProjectGraphAsync
nrwl/nx#5 runOne
nrwl/nx#6 runOne
```
This is not a Nx bug per-se, but wondering if this falls into the
efforts of supporting Bun into Nx (i.e.
https://nx.dev/blog/nx-19-5-adds-stackblitz-new-features-and-more#bun-and-pnpm-v9-support)?
I will cross post the above into the Bun repo too for input.
### Expected Behavior
Able to execute Nx commands with Bun
### GitHub Repo
_No response_
### Steps to Reproduce
1. Run bunx --bun nx run api:build
### Nx Report
```shell
NX_DAEMON=true bunx --bun nx --disableNxCache --disableRemoteCache --outputStyle dynamic-legacy report 1 ✘ 16:18:00
NX Report complete - copy this into the issue template
Node : 24.3.0
OS : darwin-arm64
Native Target : aarch64-macos
pnpm : 9.6.0
nx : 22.1.3
@nx/js : 22.1.3
@nx/jest : 22.1.3
@nx/eslint : 22.1.3
@nx/workspace : 22.1.3
@nx/cypress : 22.1.3
@nx/devkit : 22.1.3
@nx/esbuild : 22.1.3
@nx/eslint-plugin : 22.1.3
@nx/module-federation : 22.1.3
@nx/nest : 22.1.3
@nx/next : 22.1.3
@nx/node : 22.1.3
@nx/playwright : 22.1.3
@nx/plugin : 22.1.3
@nx/react : 22.1.3
@nx/rollup : 22.1.3
@nx/storybook : 22.1.3
@nx/vite : 22.1.3
@nx/vitest : 22.1.3
@nx/web : 22.1.3
@nx/webpack : 22.1.3
@nx/docker : 22.1.3
nx-cloud : 19.1.0
@nrwl/nx-cloud : 19.1.0
typescript : 5.7.3
---------------------------------------
Registered Plugins:
@nxlv/python
---------------------------------------
Community plu...
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
- Fixesnrwl/nx#33997
<!-- START COPILOT CODING AGENT TIPS -->
---
✨ Let Copilot coding agent [set things up for you](https://github.com/nrwl/nx/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AgentEnder <6933928+AgentEnder@users.noreply.github.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Copilot <Copilot@users.noreply.github.com>
## Current Behavior
Frequent write cache calls during tasks hashing results in delays
## Expected Behavior
Cache is validated but only written when changed
## AI Summary
This pull request introduces an optimization to the project graph cache
writing logic, reducing unnecessary disk writes when serving repeated
requests with unchanged graphs. The main change is the addition of a
mechanism to track the cache file's modification time and only write to
disk if the file has been externally modified or not written yet by the
current process.
Optimizations to cache writing:
* Added `writeCacheIfStale` function in `nx-deps-cache.ts` to prevent
redundant cache writes by checking the cache file's modification time
before writing. This function is now used in the daemon's graph
recomputation logic, replacing the previous unconditional write.
[[1]](diffhunk://#diff-82bd1a5a7b7320ffc3233470f191782c054bf69a696dc16001d2c4b1d0b04963R285-R312)
[[2]](diffhunk://#diff-d5bf3c66e62cac1884a071bf07fd1991320a3e62b07bfc03af3b9557b714c892L17-R17)
[[3]](diffhunk://#diff-d5bf3c66e62cac1884a071bf07fd1991320a3e62b07bfc03af3b9557b714c892L136-R149)
* Introduced `lastWrittenCacheMtimeMs` variable to track the last
successful write's modification time, updated after each cache write.
[[1]](diffhunk://#diff-82bd1a5a7b7320ffc3233470f191782c054bf69a696dc16001d2c4b1d0b04963R202-R208)
[[2]](diffhunk://#diff-82bd1a5a7b7320ffc3233470f191782c054bf69a696dc16001d2c4b1d0b04963R256-R261)
Codebase updates:
* Updated imports in `nx-deps-cache.ts` to include `statSync` for file
modification time checks.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
`{projectRoot}` and `{projectName}` tokens inside `{workspaceRoot}/...`
input patterns were silently never substituted in the native Rust
hasher, causing the glob to match zero files and those inputs to be
entirely excluded from the cache hash — a silent cache correctness bug.
## Root Cause
In `gather_self_inputs` (`hash_planner.rs`), workspace filesets (those
starting with `{workspaceRoot}/`) were forwarded as-is to
`HashInstruction::WorkspaceFileSet`. When `globs_from_workspace_globs`
later stripped `{workspaceRoot}/`, the remaining `{projectRoot}/**/*.go`
was matched literally — which never exists on disk.
## Changes
- **`hash_planner.rs`**: Before storing workspace filesets in
`HashInstruction::WorkspaceFileSet`, replace `{projectRoot}` and
`{projectName}` with their actual values from the project graph node.
- **`planner.spec.ts`**: Added a test verifying that a pattern like
`{workspaceRoot}/{projectRoot}/**/*.go` correctly resolves to
`{workspaceRoot}/libs/parent/**/*.go` in the hash plan.
```json
// nx.json — this pattern now works correctly
"namedInputs": {
"goSource": ["{workspaceRoot}/{projectRoot}/**/*.go"]
}
```
This pattern is documented as valid per the [Nx inputs
reference](https://nx.dev/docs/reference/inputs): `{projectRoot}` and
`{projectName}` can appear anywhere after the leading `{workspaceRoot}`.
> [!WARNING]
>
> <details>
> <summary>Firewall rules blocked me from connecting to one or more
addresses (expand for details)</summary>
>
> #### I tried to connect to the following addresses, but was blocked by
firewall rules:
>
> - `repo.gradle.org`
> - Triggering command: `/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
--add-opens=java.base/java.lang=ALL-UNNAMED
--add-opens=java.base/java.lang.invoke=ALL-UNNAMED
--add-opens=java.base/java.util=ALL-UNNAMED
--add-opens=java.prefs/java.util.prefs=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
--add-opens=java.base/java.util=ALL-UNNAMED
--add-opens=java.prefs/java.util.prefs=ALL-UNNAMED
--add-opens=java.base/java.nio.charset=ALL-UNNAMED
--add-opens=java.base/java.net=ALL-UNNAMED
--add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED
--add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED
-XX:MaxMetaspaceSize=384m -XX:+HeapDumpOnOutOfMemoryError -Xms256m
-Xmx512m -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en` (dns
block)
> - `staging.nx.app`
> - Triggering command:
`/home/REDACTED/work/_temp/ghcca-node/node/bin/node node
./bin/post-install` (dns block)
> - Triggering command: `/opt/hostedtoolcache/node/24.13.1/x64/bin/node
/opt/hostedtoolcache/node/24.13.1/x64/bin/node
/home/REDACTED/work/nx/nx/node_modules/.pnpm/nx@22.6.0-beta.5_@swc-node+register@1.11.1_@swc+core@1.15.10_@swc+helpers@0.5.18__@swc+_a827ebc424be037fc154d90301143d4e/node_modules/nx/src/project-graph/plugins/isolation/plugin-worker
/tmp/plugin4368-16-420.890618.sock @nx/enterprise-cloud` (dns block)
> - Triggering command: `/opt/hostedtoolcache/node/24.13.1/x64/bin/node
/opt/hostedtoolcache/node/24.13.1/x64/bin/node
/home/REDACTED/work/nx/nx/node_modules/.pnpm/nx@22.6.0-beta.5_@swc-node+register@1.11.1_@swc+core@1.15.10_@swc+helpers@0.5.18__@swc+_a827ebc424be037fc154d90301143d4e/node_modules/nx/src/project-graph/plugins/isolation/plugin-worker
/tmp/plugin5770-16-420.874901.sock @nx/enterprise-cloud` (dns block)
>
> If you need me to access, download, or install something from one of
these locations, you can either:
>
> - Configure [Actions setup
steps](https://gh.io/copilot/actions-setup-steps) to set up my
environment, which run before the firewall is enabled
> - Add the appropriate URLs or hosts to the custom allowlist in this
repository's [Copilot coding agent
settings](https://github.com/nrwl/nx/settings/copilot/coding_agent)
(admins only)
>
> </details>
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
>
> ----
>
> *This section details on the original issue you should resolve*
>
> <issue_title>{projectRoot} not interpolated inside {workspaceRoot}
input patterns in native hasher</issue_title>
> <issue_description>## Current Behavior
>
> When using a `{workspaceRoot}/{projectRoot}/**/*.go` pattern in target
inputs, the `{projectRoot}` token is **not interpolated** by the native
Rust hash planner. The pattern silently matches zero files, causing the
cache hash to exclude those files entirely.
>
> ```json
> // nx.json
> "namedInputs": {
> "gosourceUnfiltered": ["{workspaceRoot}/{projectRoot}/**/*.go"]
> }
> ```
>
> ```json
> // target config
> "format": {
> "inputs": ["gosourceUnfiltered", { "externalDependencies": [] }]
> }
> ```
>
> The hash plan for this target contains **zero `.go` file inputs**:
>
> ```
> Task: my-project:format:write
> Inputs:
> my-project:ProjectConfiguration
> my-project:TsConfig
> env:NX_CLOUD_ENCRYPTION_KEY
> file:nx.json
> file:.gitignore
> // no .go files!
> ```
>
> ## Root Cause
>
> In the Rust hash planner (`hash_planner.rs`), fileset inputs are
partitioned based on their prefix:
>
> ```rust
> .partition(|file_set| {
> file_set.starts_with("{projectRoot}/") ||
file_set.starts_with("!{projectRoot}/")
> });
> ```
>
> A pattern starting with `{workspaceRoot}/` is classified as a
**workspace fileset**. It then flows to `hash_workspace_files.rs` where
`{workspaceRoot}/` is stripped via `strip_prefix("{workspaceRoot}/")`,
leaving `{projectRoot}/**/*.go`. But `{projectRoot}` is **never
substituted** with the actual project root, so the glob literally tries
to match paths starting with `{projectRoot}/` — which don't exist.
>
> ## Expected Behavior
>
> Per the [Nx docs on inputs](https://nx.dev/docs/reference/inputs):
>
> > `{workspaceRoot}` should only appear in the beginning of an input
but **`{projectRoot}` and `{projectName}` can be specified later in the
input to interpolate the root or name of the project** into the input
location.
>
> The native hasher should interpolate `{projectRoot}` (and
`{projectName}`) within `{workspaceRoot}` patterns before glob matching.
After stripping `{workspaceRoot}/` and interpolating `{projectRoot}`,
the pattern should become e.g. `packages/shared/go/middleware/**/*.go`
and correctly match files.
>
> ## Impact
>
> This is a **silent cache correctness issue**: targets using this
pattern appear to work but their cache hash doesn't include the matched
files. Changes to those files won't invalidate the cache. There's no
warning or error emitted.
>
> ## Workaround
>
> Use the literal path instead of `{projectRoot}` inside workspace-level
patterns:
>
> ```js
> // In a createNodes plugin, instead of:
> inputs: ["{workspaceRoot}/{projectRoot}/**/*.go"]
> // Use:
> inputs: [`{workspaceRoot}/${actualProjectRoot}/**/*.go`]
> ```
>
> ## Related
>
> - nrwl/nx#34225 — Nested Project Files Excluded from Parent Project
Inputs (the reason `{workspaceRoot}/{projectRoot}` patterns are used in
the first place: to bypass project file filtering and include files from
nested sub-projects)
>
> ## Environment
>
> - **Nx version**: 22.6.0-beta.3
> - **OS**: macOS (Darwin 24.6.0)
> - **Package manager**: pnpm</issue_description>
>
> ## Comments on the Issue (you are @copilot in this section)
>
> <comments>
> </comments>
>
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
- Fixesnrwl/nx#34595
<!-- START COPILOT CODING AGENT TIPS -->
---
🔒 GitHub Advanced Security automatically protects Copilot coding agent
pull requests. You can protect all pull requests by enabling Advanced
Security for your repositories. [Learn more about Advanced
Security.](https://gh.io/cca-advanced-security)
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AgentEnder <6933928+AgentEnder@users.noreply.github.com>
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Copilot <Copilot@users.noreply.github.com>
`nx show target inputs` returns empty results for any target with
`defaultConfiguration` set because
`HashPlanInspector.inspectTaskInputs()` keys results as
`project:target:config` (e.g. `my-app:build:local`), but the lookup was
using `project:target` — a guaranteed miss.
## Changes
- **`packages/nx/src/command-line/show/target.ts`**: In
`resolveInputFiles`, construct the plan lookup key using the explicitly
passed `configuration` first, then fall back to the target's
`defaultConfiguration`:
```ts
const targetConfig = graph.nodes[projectName]?.data?.targets?.[targetName];
const effectiveConfig = configuration ?? targetConfig?.defaultConfiguration;
const taskId = effectiveConfig
? `${projectName}:${targetName}:${effectiveConfig}`
: `${projectName}:${targetName}`;
```
If the computed `taskId` is not found in the hash plan, an error is
thrown instead of silently returning empty results.
- **`showTargetInputsHandler`**: Now extracts the configuration from the
target string (`project:target:config`) or the `-c`/`--configuration`
flag and forwards it to `resolveInputFiles`.
- **`ShowTargetInputsOptions`** (`command-object.ts`): Added
`configuration?: string` so the type correctly reflects the prop.
- **`packages/nx/src/command-line/show/target.spec.ts`**: Added tests
covering:
- Resolving input files when `defaultConfiguration` is set
- Preferring an explicit `configuration` over `defaultConfiguration`
- Throwing when the task ID is not found in the hash plan
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
>
> ----
>
> *This section details on the original issue you should resolve*
>
> <issue_title>nx show target inputs returns empty when target has
defaultConfiguration</issue_title>
> <issue_description>## Current Behavior
>
> `nx show target inputs <project>:<target> --json` returns no files for
any target that has a `defaultConfiguration` set (directly or via
`targetDefaults`).
>
> ```bash
> $ nx show target inputs card-api-lambda:build --json
> {
> "project": "card-api-lambda",
> "target": "build"
> }
> # Expected: files array with resolved input files
> ```
>
> The `--check` flag also reports files as not being inputs when they
should be:
>
> ```bash
> $ nx show target inputs card-api-lambda:build --check
packages/card/api/lambda/main.go
> ✗ packages/card/api/lambda/main.go is not an input for
card-api-lambda:build
> ```
>
> Targets **without** `defaultConfiguration` on the same project resolve
correctly:
>
> ```bash
> $ nx show target inputs card-api-lambda:generate-docs --json
> {
> "project": "card-api-lambda",
> "target": "generate-docs",
> "files": [ ".gitignore", "nx.json",
"packages/card/api/lambda/main.go", ... ]
> }
> ```
>
> ## Root Cause
>
> In `packages/nx/src/command-line/show/target.ts`, the
`resolveInputFiles` function constructs the lookup key as:
>
> ```js
> const taskId = `${projectName}:${targetName}`;
> ```
>
> But the native `HashPlanInspector.inspectTaskInputs()` returns results
keyed by the **full task ID including the default configuration**, e.g.
`card-api-lambda:build:local`.
>
> When a target has `defaultConfiguration: "local"`, the plan result is
keyed as `project:target:local`, but the lookup searches for
`project:target` — which doesn't exist — so it falls through to the
empty default `{ files: [], ... }`.
>
> Verified by calling `inspectTaskInputs` directly:
>
> ```
> === build ===
> Task IDs in result: card-api-lambda:generate-docs,
card-api-lambda:build:local
> # lookup for "card-api-lambda:build" → miss → empty
>
> === generate-docs ===
> Task IDs in result: card-api-lambda:generate-docs
> # lookup for "card-api-lambda:generate-docs" → hit → 142 files
> ```
>
> ## Suggested Fix
>
> The lookup key should account for the default configuration:
>
> ```js
> const defaultConfig =
graph.nodes[projectName]?.data?.targets?.[targetName]?.defaultConfiguration;
> const taskId = defaultConfig
> ? `${projectName}:${targetName}:${defaultConfig}`
> : `${projectName}:${targetName}`;
> ```
>
> ## Expected Behavior
>
> `nx show target inputs` should resolve and display input files
regardless of whether the target has a `defaultConfiguration`.
>
> ## Environment
>
> - **Nx version**: 22.6.0-beta.3
> - **OS**: macOS (Darwin 24.6.0)
> - **Node**: v22
> - **Package manager**: pnpm</issue_description>
>
> ## Comments on the Issue (you are @copilot in this section)
>
> <comments>
> </comments>
>
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
- Fixesnrwl/nx#34594
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AgentEnder <6933928+AgentEnder@users.noreply.github.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Copilot <Copilot@users.noreply.github.com>
## Current Behavior
napi-derive v2 has a bug where the type definition temp file doesn't get
cleaned between builds with different profiles (release vs dev), causing
duplicate class declarations in `index.d.ts`.
## Expected Behavior
With napi-rs v3, the temp file handling is fixed — each crate gets its
own folder, preventing duplicate declarations across build profiles.
## Related Issue(s)
N/A — internal build tooling improvement.
## Changes
### Dependency upgrades
- **Cargo.toml**: `napi` 2.x → 3.8.3, `napi-derive` 2.x → 3.5.2,
`napi-build` 1.x → 2.3.1
- **package.json**: `@napi-rs/cli` → 3.5.1 (was 3.0.0-alpha.56)
### External data sharing (Arc pattern)
- Replaced raw-pointer `StoredExternal<T>` wrapper with napi-recommended
`Arc<T>` pattern for shared ownership
- Read-only data: `External::new(Arc::new(data))` at creation, `Arc<T>`
in struct fields
- Mutable data (DB): `External::new(Arc::new(Mutex::new(data)))`,
`Arc<Mutex<T>>` in struct fields
- Constructors take `&External<Arc<T>>`, clone the Arc;
`#[napi(ts_arg_type)]` preserves TS types
- Deleted `types/external_compat.rs` (86 lines of unsafe code removed)
### API migrations
- `ThreadsafeFunction`: Accept directly as napi parameters instead of
building from `Function<'_>`
- Return-only structs: Use `#[napi(object, object_from_js = false)]` for
structs with `External<T>` fields
- `env.create_object()` → `Object::new(&env)`, `JsObject` → `PromiseRaw`
for async returns
### Watcher fix
- Deferred `Watchexec::default()` creation from constructor to `watch()`
inside napi's `spawn()` block
- `Watchexec::default()` internally calls `tokio::spawn()` which
requires a Tokio runtime in TLS — the `#[napi(constructor)]` runs on the
JS main thread with no runtime context
- Changed `napi::tokio::spawn(...)` to `spawn(...)` (napi's
`bindgen_prelude::spawn` which uses a static runtime, works from any
thread)
- Store `watch_exec` as `Arc<Mutex<Option<Arc<Watchexec>>>>` for lazy
initialization
### TUI fix
- Same Tokio runtime issue: `tokio::spawn(async {})` placeholder in
`Tui::new()` panicked on JS main thread
- Changed `task` field from `JoinHandle<()>` to
`Option<JoinHandle<()>>`, initialized as `None`
- Moved `tui.start()` (which calls `tokio::spawn`) inside napi's
`spawn()` async block
- Changed `napi::tokio::spawn(...)` to `spawn(...)` in lifecycle.rs
### Platform-specific watcher improvements
- Gated non-macOS watcher functions with `#[cfg(not(target_os =
"macos"))]`
- Added `rlib` to `crate-type` in Cargo.toml for cargo test
compatibility
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
Several Rust native modules and one TS utility have unnecessary
allocations:
- `hash_planner.rs`: visited set uses `HashSet<String>` with
`to_string()` per insert; unnecessary `.collect::<Vec<_>>()` creating
temp vecs; no pre-allocation for dependency inputs
- `context.rs`: `update_files` allocates a String per map entry inside
retain loop
- `hash_workspace_files.rs`: `.clone()` before `.as_bytes()` — 2
needless heap allocs per file
- `find_matching_projects.rs`: collects HashMap keys into Vec + linear
search instead of O(1) lookup
- `validate_outputs.rs`: compiles regex on every call
- `project-configuration-utils.ts`: uses `JSON.parse(JSON.stringify())`
for deep cloning
## Expected Behavior
All unnecessary allocations removed:
- Borrow `&str` from project graph instead of cloning Strings into
visited set
- Use `Path::new()` once outside retain closure (also more correct —
component-boundary-aware matching)
- Call `.as_bytes()` directly without clone
- Use `HashMap::get_key_value()` for O(1) lookup
- Cache compiled regex with `LazyLock<Regex>`
- Use `structuredClone` (Node 18+) instead of JSON round-trip
See individual commits for detailed rationale per change.
## Current Behavior
When using `@nx/angular-rspack` with SCSS imports that reference
external assets (e.g., `flag-icon-css`), the `postcss-cli-resources`
plugin generates absolute filesystem paths in CSS `url()` values:
```css
.flag-icon-us{background-image:url(/some/project/path/dist/bug-demo/browser/media/us.2d0a1dd6.svg)}
```
This happens because `normalizeOutputPath()` makes `outputPath.media`
absolute, and this absolute path is passed directly as
`resourcesOutputPath` to `postcss-cli-resources`.
## Expected Behavior
CSS `url()` values contain relative paths:
```css
.flag-icon-us{background-image:url(media/us.2d0a1dd6.svg)}
```
## Related Issue(s)
Fixes#34092
## Current Behavior
CSS `url()` references to fonts and assets fail to resolve on Windows
with webpack, rspack, and angular-rspack builds. The
`postcss-cli-resources` plugin passes Windows absolute paths (e.g.
`E:\dev\project\font.woff2`) to `new URL(path, 'file:///')`, which
misinterprets the drive letter (`E:`) as a URL protocol, stripping it
from `pathname` and producing unresolvable paths like
`\dev\project\font.woff2`.
## Expected Behavior
CSS `url()` references to fonts, images, and other assets resolve
correctly on all platforms.
## Related Issue(s)
Fixes#33052
## Current Behavior
The Netlify edge function reads `NEXT_PUBLIC_FRAMER_REWRITES` env var to
determine which paths to proxy to Framer. As more pages move to Framer,
this growing allowlist becomes cumbersome to maintain.
## Expected Behavior
The edge function now proxies all requests to Framer by default. Only
paths explicitly listed in `nextjsPaths` and `excludedPath` are served
by Next.js. This removes the need for the `NEXT_PUBLIC_FRAMER_REWRITES`
env var (should be removed from Netlify dashboard manually).
Deleted 25 page files (pages router + app router) that are now served by
Framer: homepage, 404, enterprise/*, contact/*, solutions/*, community,
company, customers, nx-cloud, partners, brands, careers, java, react,
remote-cache, resources, webinar.
**Next.js paths kept:** `/blog/*`, `/courses/*`, `/pricing`, `/podcast`,
`/ai-chat`, `/changelog`, `/resources-library`, `/whitepaper-fast-ci`,
`/500`, `/api/*`, `/docs/*`
**Manual follow-up:** Remove `NEXT_PUBLIC_FRAMER_REWRITES` env var from
Netlify dashboard.
## Related Issue(s)
Closes DOC-431
## Current Behavior
10 `nx.dev` URLs return 404 — broken links in CLI output, graph UI, and
cloud UI. Additionally:
- ~20 wildcard redirect rules silently broken because `:slug*` was never
converted to Netlify's `*`/`:splat` syntax
- Specific `/getting-started/` rules ordered after the wildcard
catch-all, sending users to the generic intro page instead of the
correct page (e.g., `/getting-started/editor-setup`)
- Redirect chain breaks where intermediate targets (e.g.,
`/nx-api/powerpack-*-cache` → `/nx-api/*-cache`) had no rule
## Expected Behavior
All documented `nx.dev` URLs resolve correctly. `_redirects` is the sole
source of truth — no more JS generator pipeline.
### Changes
1. **Fixed `_redirects`**:
- Converted all `:slug*` to `*`/`:splat` (Netlify syntax)
- Reordered specific `/getting-started/` rules before the wildcard
catch-all
- Added 13 new redirect rules for broken 404 URLs
2. **Fixed `astro-docs/netlify.toml`**:
- Added 3 redirects for `/docs/` path typos (trailing-s, wrong path)
3. **Deleted legacy files** (-2,248 lines):
- `redirect-rules.js`
- `redirect-rules-docs-to-astro.js`
- `redirect-rules.spec.js`
- `scripts/generate-netlify-redirects.mjs`
## Related Issue(s)
Fixes DOC-428
## Current Behavior
When running Gradle batch tasks (e.g. `nx build my-gradle-project
--no-tui`), the terminal shows no task output. The Gradle build output
is captured into `ByteArrayOutputStream` for JSON results but never
forwarded to the terminal. Users can only see the output in Nx Cloud
task results.
## Expected Behavior
Gradle batch task output (build logs, test results, etc.) should be
visible in the terminal in real-time as the batch executes.
## Changes
- **`TeeOutputStream.kt`** (new): An `OutputStream` that writes to two
destinations simultaneously — captures output for JSON results while
also forwarding to `System.err` for terminal display.
- **`GradleRunner.kt`**: Wrap `setStandardOutput` and `setStandardError`
with `TeeOutputStream` to tee into `System.err` in both
`runBuildLauncher` and `runTestLauncher`.
- **`gradle-batch.impl.ts`**: Replace `PseudoTerminal` (which swallowed
all output with `quiet: true`) with `execSync` using `stdio: ['pipe',
'pipe', 'inherit']` so stderr flows directly to the terminal.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
The `build-base` target in `nx.json` has a manual `inputs` override of
`["production", "^production"]` which shadows the more accurate inputs
inferred by the `@nx/js/typescript` plugin.
## Expected Behavior
Let the `@nx/js/typescript` plugin infer the correct inputs for
`build-base` tasks, providing more accurate cache invalidation based on
the actual tsconfig project references.
## Lint Rule Changes
Removing the broad `inputs` override causes the `@nx/dependency-checks`
lint rule to flag a few dependencies as "unused" across three packages.
This happens because the rule uses the build target's inputs to
determine which files to scan for imports, and the narrower tsc-inferred
inputs don't cover certain files:
- **`packages/nx`**: `@napi-rs/wasm-runtime` — used in
`nx.wasi-browser.js`, a `.js` file outside tsconfig scope
- **`packages/angular`**: `@angular-devkit/core` — only referenced as a
string (for `ensurePackage()`, migrations config) and is a
`peerDependency`, not directly imported at runtime
- **`packages/angular-rspack-compiler`**: `semver` — used in
`patch/patch-angular-build.js`, a `.js` patch file outside tsconfig
scope
These are all legitimate dependencies. Adding the `.js` files to
tsconfig would require `allowJs` and pull non-TS scripts into the build
pipeline unnecessarily. Adding them to `ignoredDependencies` in each
package's `.eslintrc.json` is the correct fix.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
The vitest executor ignores `reporter`/`reporters` set in target options
(project.json). Both forms leak through as passthrough CLI args, causing
vitest's `resolveConfig` to override the `reporters` array (which
includes NxReporter), making the executor hang indefinitely.
Additionally, when the vite config uses `reporter` (singular), the
config value always overrides the `reporters` array passed as inline
options — again removing NxReporter.
## Expected Behavior
Target-level `reporter`/`reporters` options take priority over
config-file values. NxReporter is always preserved in the final
reporters array regardless of configuration source.
Priority chain: target `reporter` (singular) > target `reporters`
(plural) > config `reporter` (singular) > config `reporters` (plural).
## Related Issue(s)
Fixes#34495
This PR updates the guide for integrated -> TS solution setup with
additional information. It also adds `% llm_copy_prompt %}` and `{%
llm_only %}` components to provide instructions to the AI agent to
perform the migration.
Preview:
https://deploy-preview-34646--nx-docs.netlify.app/docs/technologies/typescript/guides/switch-to-workspaces-project-references
## Current Behavior
The migration guide covers basic steps but is missing several nuances
discovered in the ocean repo's convert-ts-solution generator and through
end-to-end validation against a real workspace.
## Expected Behavior
The guide covers all steps needed for a successful migration, including:
- .gitignore updates for out-tsc/dist/test-output artifacts
- Order of operations (install before removing paths)
- Root tsconfig cleanup (remove paths entirely, baseUrl, rootDir)
- Package.json self-export and nested path alias strategies
- Update build targets (remove @nx/js:tsc for non-buildable libs)
- Import path updates after package renaming
- Bundler config updates (webpack auto-detect, Jest resolver,
Vite/Vitest)
- Edge cases: circular deps, e2e projects, non-standard locations,
tsconfig include/exclude patterns
Validated end-to-end against a test workspace with 5 libs and 2 React
apps (webpack+jest, vite+vitest) across 2 clean iterations.
## Screenshots
<img width="810" height="458" alt="Screenshot 2026-02-27 at 7 31 16 AM"
src="https://github.com/user-attachments/assets/80ad87db-6ebe-4f54-9995-65f2ee2c46f1"
/>
<img width="784" height="735" alt="Screenshot 2026-02-27 at 7 31 20 AM"
src="https://github.com/user-attachments/assets/a0a7fe26-7d7d-4225-94bb-f82e5fc49178"
/>
<img width="897" height="161" alt="Screenshot 2026-02-27 at 7 31 39 AM"
src="https://github.com/user-attachments/assets/3b410092-5fb4-420d-9909-ee83c34ff0d6"
/>
## Related Issue(s)
NXC-2950
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
## Current Behavior
Searching for CLI commands like `nx watch` on the docs site does not
surface the [Nx Commands reference
page](https://nx.dev/docs/reference/nx-commands) on the first page of
results, even when filtering by "References."
Root causes:
- **Term saturation**: "nx" appears 150+ times on the CLI reference page
(in every heading, usage block, and example), causing it to saturate and
contribute almost nothing to ranking differentiation.
- **Page length penalty**: The current `pageLength: 0.5` setting
actively penalizes long pages—the CLI reference is one of the longest on
the site.
- **No weight boost**: The CLI page had no `weight` set, while
generators/executors pages already get `weight: 2.0`.
## Expected Behavior
Searching for `nx watch`, `nx run-many`, or other CLI commands should
surface the Nx Commands reference page prominently in results.
This PR applies two quick-win tuning changes:
1. **`weight: 4`** on the CLI commands page entry — gives body text ~16×
impact (quadratic scaling), making it competitive with shorter pages
that mention commands incidentally. include the command name in the sub
headers for more improvement in relevancy search without impacting other
pages
2. **`termSaturation: 1.2`** (down from default 1.4) — makes highly
repeated terms like "nx" saturate faster so that the differentiating
term (e.g. "watch") carries more relative weight.
## Related Issue(s)
Addresses
[DOC-401](https://linear.app/nxdev/issue/DOC-401/investigate-boosting-cli-command-reference-pages-in-search)
## Current Behavior
When running `pnpm nx-release --local false`, the `nx release version`
step modifies `package.json` files for `angular-rspack`,
`angular-rspack-compiler`, `dotnet`, and `maven` (bumping versions and
resolving `workspace:*` protocols). The local release path (`--local
false`, non-CI) exits early before reaching the reset logic that the CI
path uses, leaving unstaged changes behind.
## Expected Behavior
After the release script completes (or is interrupted), the source
`package.json` files should be restored to their original state. The
version bumps are only needed in `dist/` for publishing — the source
files should stay at `0.0.1` with `workspace:*` protocols.
This PR:
- Extracts a shared `resetPackageJsons()` function used by both the
local and CI code paths
- Wraps the local release steps in `try/finally` so files are restored
even on errors
- Adds a `SIGINT` handler so files are restored on ctrl+C
Add sandboxing-config.yaml to exclude node_modules from reads in Nx
Cloud workflow sandboxing.
## Current Behavior
No sandboxing configuration exists for Nx Cloud workflows.
## Expected Behavior
Nx Cloud workflows use sandboxing config that excludes `node_modules/**`
from reads.
## Related Issue(s)
N/A
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
## Current Behavior
In `internalCreateNodesV2`, the ESLint plugin creates a **new `ESLint`
instance per child project root** to check whether the project has
non-ignored lintable files via `isPathIgnored`:
```ts
const eslint = new ESLint({
cwd: join(context.workspaceRoot, projectRoot),
});
for (const file of lintableFilesPerProjectRoot.get(projectRoot) ?? []) {
if (!(await eslint.isPathIgnored(...))) { ... }
}
```
In large monorepos, this means hundreds or thousands of ESLint
instantiations during graph calculation. Each instantiation involves
loading and resolving the ESLint configuration, which is the dominant
cost in the plugin.
## Expected Behavior
A single ESLint instance should be shared across all child projects
under the same ESLint config directory. Since `isPathIgnored` receives
**absolute paths**, the instance's `cwd` does not affect the result — it
only needs to resolve the correct config, which is the same for all
children of a given config root.
## Changes
* In `internalCreateNodesV2`: create one lazily-initialized shared
ESLint instance per config directory (the `configDir`), reused across
all child project roots
* Projects that have their own `.eslintignore` file fall back to a
per-project ESLint instance, preserving correct behavior for ESLint v8
(which resolves `.eslintignore` relative to `cwd`)
## Benchmark
Measured on a real monorepo with **1,609 projects** and a single root
ESLint config.
| | Before | After | Change |
|---|---|---|---|
| Cold run (avg of 3) | **~18,700ms** | **~7,300ms** | **-61%** |
| ESLint instances created | 1,457 | 8 | **-99.5%** |
### Breakdown
| Version | Run 1 | Run 2 | Run 3 |
|---|---|---|---|
| Baseline (upstream) | 23,482ms | 16,238ms | 16,389ms |
| With shared instance | 7,473ms | 7,012ms | 7,515ms |
### Methodology
* `NX_DAEMON=false` to prevent daemon caching
* ESLint plugin hash cache cleared between each cold run
* `performance.now()` instrumentation around `internalCreateNodesV2`
* Each measurement repeated 3 times
* **Verified same number of lint targets** (1,473) before and after — no
behavior change
### Why `configDir === '.'` is not excluded
The earlier revision excluded root-level ESLint configs (`configDir ===
'.'`) from the optimization as a conservative safety measure. However,
benchmarking showed this **completely disables the optimization** for
the most common monorepo setup (single root ESLint config), dropping
1,456 of 1,457 instances back to per-project instantiation with zero
measurable improvement.
The per-project `.eslintignore` check already handles the ESLint v8
concern: projects with their own `.eslintignore` still get dedicated
instances, while all others share one instance whose `cwd` correctly
resolves the root config and root `.eslintignore`.
## Related
This is the `createNodesV2` (Nx plugin) code path. The change does not
affect ESLint execution itself — only the graph calculation phase.
When latest >= next, the build-migrations script now preserves the
existing x-prompt and requires fields from the pre-release entry, rather
than skipping them, and adjusts the requires upper bound to the stable
version.
## Current Behavior
The Maven batch runner uses a parallel thread pool to execute tasks.
When multiple tasks are independent roots in the task graph, they get
picked up by separate threads simultaneously. Both threads call
`invoke()` on the same shared `CachingResidentMavenInvoker` (Maven 4) or
`CachingMaven3Invoker` (Maven 3) instance concurrently.
Maven 4's `LookupInvoker.invoke()` is not thread-safe — it
snapshots/restores `System.getProperties()` and the thread context
classloader in a `finally` block, and all concurrent invocations share
the same resident `MavenContext`. This can cause deadlocks in Maven's
internal session machinery.
## Expected Behavior
Maven executions through the shared invoker are serialized via
`@Synchronized`, preventing concurrent access to non-thread-safe Maven
internals. The parallel thread pool still handles task graph management,
build state recording, and result emission concurrently.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Current Behavior
When running `nx release` with Git remotes configured as **canonical SSH
URLs**, the repository slug is extracted incorrectly.
Examples of affected remotes include:
- `ssh://git@ssh.github.com:443/org/repo.git`
- `ssh://git@gitlab.company.com:2222/group/subgroup/repo.git`
In these cases, the existing regex-based logic misinterprets the port
number as part of the repository path (for example: `443/org`). This
causes the release creation request to be built with an invalid
repository slug and results in `404 Not Found` errors from the GitHub or
GitLab APIs.
## Expected Behavior
`nx release` should correctly extract the repository slug from all valid
Git remote URL formats, regardless of whether the remote uses HTTPS,
SCP-style SSH, or fully qualified SSH URLs with explicit ports.
Valid examples should resolve to the correct slug:
- `ssh://git@ssh.github.com:443/org/repo.git` => `org/repo`
- `ssh://git@gitlab.company.com:2222/group/subgroup/repo.git`=>
`group/subgroup/repo`
Users should not need to modify their Git remote configuration in order
for `nx release` to work correctly.
## What’s Changed
- Introduced a shared utility
([`extractRepoSlug`](https://github.com/nrwl/nx/pull/31684/changes#diff-799188c178b8a084e86dbe9063ec88a7c324212a8ef79729777f56e4bf7f455cR29-R60))
to consistently extract repository slugs from Git remote URLs.
- Replaced provider-specific, regex-based parsing in:
- `GithubRemoteReleaseClient`
- `GitLabRemoteReleaseClient`
- Added support for:
- HTTPS remotes
- SCP-style SSH remotes (`git@host:org/repo.git`)
- Fully qualified SSH URLs with custom ports
- Arbitrarily nested GitLab group and subgroup paths
- Self-hosted GitHub and GitLab instances via hostname matching
- Added comprehensive unit tests covering valid and invalid URL formats
for both providers.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes#31682
## Current Behavior
The Nx native module (Rust cdylib loaded by Node.js) uses the system
allocator. The daemon process retains a large RSS footprint after the
initial project graph build, even though most of that memory is no
longer in use. On macOS and Linux, the system allocator doesn't
aggressively return freed pages to the OS.
## Expected Behavior
The daemon's steady-state RSS drops significantly after graph build by
using jemalloc with tuned page purge timers. Peak RSS and wall time are
unaffected.
## Changes
Adds [tikv-jemallocator](https://github.com/tikv/jemallocator) as the
global allocator on Linux and macOS, with two compile-time settings:
- **`dirty_decay_ms:1000`** — returns freed pages to the OS after 1s
instead of the default 10s. Tuned to Nx's phase-separated workload
(graph build → idle → task execution), where transitions happen every
~30-60s. Benchmarked against 5s and 10s — both too slow to purge between
phases.
- **`muzzy_decay_ms:0`** — skips the lazy purge phase (`MADV_FREE`) and
goes straight to `MADV_DONTNEED`. Required on macOS and Linux ≥ 4.5
where `MADV_FREE` doesn't actually reduce RSS.
Windows and WASI continue using the system allocator. Windows is
excluded because `tikv-jemalloc-sys` fails to build with MSVC (spaces in
`cl.exe` path break the autoconf configure script). Tracked upstream in
[tikv/jemallocator#99](https://github.com/tikv/jemallocator/pull/99).
### Other Settings Considered
Tested narenas reduction, tcache_max, extent fit tuning, background
threads, and decay timer values. Only the decay timer configuration
improved steady-state RSS without wall time regression.
closed#32190
## Current Behavior
eslint crashes when tsconfig.base.json path includes a * and you have an
import going to that project
## Expected Behavior
The plugin shouldn't crash and it should auto fix to a working import
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
https://github.com/nrwl/nx/issues/32190Fixes#32190
## Current Behavior
The `replace-removed-matcher-aliases` migration uses `tsquery.replace()`
which reprints the entire AST through TypeScript's Printer. This causes
two problems:
1. **Syntax corruption**: Valid TypeScript files are mangled:
- Destructuring patterns: `{ result }` becomes `{result}:`
- Arrow functions: missing opening braces
- Nested callbacks: collapsed/merged code blocks
2. **Unnecessary file changes**: Every test file is written back to disk
even when no matchers are replaced. This triggers `formatFiles()` to
reformat unchanged files, creating large whitespace-only diffs. In large
codebases, this can result in hundreds or thousands of files being
modified unnecessarily, making the migration PR difficult to review.
**Why I care a Lot**
I was running this on a multi-million-LOC monorepo and ran into two
issues:
* I got ~10k modified files with whitespace-only changes from the
removal of newlines. These changes couldn't be fixed with Prettier
because it didn't care about the number of newlines, so the diff was
unmergeable.
* I got ~8 files with malformed Typescript, causing commit hooks, CI,
etc. to fail without manual intervention.
## Expected Behavior
The migration should:
1. Only replace the deprecated matcher names (e.g., `toBeCalled` →
`toHaveBeenCalled`)
2. Preserve all surrounding code exactly as written
5. Only touch files that actually contain deprecated matchers
## Solution
Replace AST-reprinting with surgical text replacement:
- Use `tsquery.query()` to find matching AST nodes
- Collect text positions (start/end) for each node to replace
- Apply replacements in reverse order using string slicing
- Only write files that actually changed
This pattern is already used successfully in other Nx migrations (e.g.,
`rename-cy-exec-code-property.ts` in the Cypress package).
**Additional improvements:**
- Single AST parse with regex selector vs. 11 separate passes
- Quick string check skips parsing files without deprecated matchers
- New regression test covers complex patterns that triggered corruption
## Related Issue(s)
Fixes#32062
---------
Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
## Current Behavior
E2E tests may timeout without clear error messages, making it difficult
to diagnose test failures.
## Expected Behavior
E2E test utilities should provide better timeout handling and logging to
help diagnose test failures.
## Changes
- Add timeout handling to e2e test utilities (`runCLI` and
`runLernaCLI`)
- Add command logging to track execution time
- Improve timeout error messages with process output
- Bump cache bust value
## Related Issue(s)
CI stability and debugging improvements
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
`@nx/eslint` relies on ESLint internals that changed in ESLint v10
(`use-at-your-own-risk`), which causes failures.
It looks like https://github.com/nrwl/nx/pull/24632 originally attempted
to use `loadESLint()` which would've been forward compatible with v10,
but it was later removed in https://github.com/nrwl/nx/pull/27404 in
favor of the `use-at-your-own-risk` import.
## Expected Behavior
`@nx/eslint` supports ESLint v10.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes#34415
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
If you specify both the `all` and `initialRun` options when running `nx
watch`, `initialRun` have no effect.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
The command should be called once at the beginning even if there are no
file changes.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes#32281
## Current Behavior
nx-maven-plugin 0.0.12 is also changing the base directory along with
the pom file when the plugins like flatten-maven-plugin /
maven-shade-plugin produces pomFile in different directory other than
the base directory
## Expected Behavior
plugin should only update the pom file and not the base directory
location
## Related Issue(s)
Fixes#34181https://github.com/mojohaus/flatten-maven-plugin/issues/50
Co-authored-by: anurag.ag <anuragagarwal561994@users.noreply.github.com>
## Current Behavior
Technology introduction pages have inconsistent or missing version
requirements information. Some pages have no Requirements section,
others use ad-hoc formats (asides, bullet lists under Prerequisites),
and page titles follow different naming conventions ("Overview of the Nx
X Plugin", "Nx X Plugin Overview", "Introduction - X", etc.).
## Expected Behavior
Every technology introduction page now has a standardized Requirements
section with:
- A version support table using consistent semver range format
- Code-formatted package names in the `Package` column
- An intro sentence identifying the Nx plugin (e.g. "The `@nx/react`
plugin supports the following package versions.")
- A note linking to [code generation docs](/docs/features/generate-code)
for auto-installed packages
- Consistent **"X Plugin for Nx"** page title pattern across all intro
pages
Additional changes:
- Deleted the standalone Node.js/TypeScript compatibility page, inlining
its content into the respective plugin intro pages
- Created a proper introduction page for Angular Rsbuild (previously
linked directly to `createConfig` API reference)
- Added Requirements tables to Java, Gradle, and Maven pages with system
dependency versions
- Updated sidebar links and redirects for removed/moved pages
- Applied style guide fixes across all edited pages (removed "allows you
to", "easily", product possessives, etc.)
## Related Issue(s)
Fixes DOC-423
## Current Behavior
After #34580, `determineNxCloudV2()` returns `'skip'` in non-interactive
mode, but the caller remaps it to `nxCloud = 'yes'` with
`skipCloudConnect = true`. This causes `setupCI()` to run and generate
`.github/workflows/ci.yml` in new workspaces — which didn't happen
before.
This breaks the `extras.test.ts` e2e snapshot test because
`.github/workflows/ci.yml` now appears in the expanded default task
inputs.
## Expected Behavior
Keep `nxCloud = 'skip'` when the cloud choice is `'skip'`, which
prevents CI file generation in non-interactive mode. This restores the
behavior prior to #34580.
## Related Issue(s)
Fixes the `extras.test.ts` e2e snapshot failure introduced by #34580.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Bump project graph plugin version.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Current Behavior
`build_glob_set` recompiles identical glob pattern sets on every call,
even when the same set of patterns has been compiled before.
## Expected Behavior
Compiled `NxGlobSet` instances are cached in a static `DashMap` keyed by
sorted glob strings. Repeated calls with the same patterns return a
shared `Arc<NxGlobSet>` instead of recompiling. Profiling `nx run-many
-t build lint test --parallel 8` in the Nx repo measured 95.6% cache hit
rate (9,758 of 10,202 calls) with only 444 unique pattern sets, reducing
hashing-phase CPU by ~40%.
## Current Behavior
All 1,200+ redirect rules are processed by the Next.js serverless
function via the `redirects()` config in `next.config.js`. Every
redirect request requires a cold start of the serverless function, which
contributed to the 10-minute outage reported in DOC-415.
## Expected Behavior
Redirects are handled at the Netlify CDN edge via a plain `_redirects`
file, which is faster and doesn't depend on the Next.js serverless
function being healthy.
- Converted all redirect rules from `redirect-rules.js` and
`redirect-rules-docs-to-astro.js` into Netlify `_redirects` format
(1,231 rules)
- Expanded Next.js regex group patterns (e.g. `/(l|latest)/...`) into
individual Netlify rules since Netlify doesn't support regex
- Converted `:path*` wildcards to Netlify `*`/`:splat` syntax
- Rewrites (Astro docs proxy) remain in `next.config.js` as they require
server-side processing
- Original JS redirect files kept for reference (can be removed in
follow-up)
## Related Issue(s)
Fixes DOC-415
## Current Behavior
`TargetProjectLocator.findProjectFromImport` stores `null` for
unresolved imports using a bare `importExpr` key, but
`findNpmProjectFromImport` looks up cache entries using
`${packageName}__${dirPath}`. The key mismatch means repeated lookups
for the same import+directory re-run the full resolution waterfall
(typescript + require.resolve) instead of returning the cached `null`.
## Expected Behavior
Store `null` for unresolved imports using the same
`${packageName}__${dirPath}` key that `findNpmProjectFromImport` uses
for lookups. Repeated lookups for already-failed imports skip the
expensive resolution steps.
Also removes an unused cache write for builtin module imports as a minor
cleanup.
## Current Behavior
After migrating from chalk to picocolors (#34305), `FORCE_COLOR=0` no
longer disables colors. picocolors checks `!!env.FORCE_COLOR`, and since
`!!"0"` is `true` in JavaScript, it treats `FORCE_COLOR=0` as "enable
colors."
This breaks CI environments and tools like Homebrew that set
`FORCE_COLOR=0` to get plain text output.
## Expected Behavior
`FORCE_COLOR=0` should disable ANSI color output, matching the previous
chalk behavior and the [FORCE_COLOR spec](https://force-color.org/).
## Related Issue(s)
Fixes#34387
Upstream issue filed:
https://github.com/alexeyraspopov/picocolors/issues/100
## Current Behavior
When processing Gradle tasks, Nx tracks dependent task output files by
recording individual file paths for each output. This can lead to
incorrect cache invalidation behavior since we are prefixing the paths
unnecessarily. We will therefore never match.
## Expected Behavior
Nx now consolidates dependent task output files using glob patterns
based on file extensions (e.g., **/*.jar, **/*.class). This focuses on
the types of files produced rather than their specific paths. The
approach groups all output files by extension and generates a single
glob pattern per extension, reducing the complexity of input tracking
while maintaining correctness.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #Q-247
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
The dependsOn of atomized tasks should match the base non-atomized task.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes Q-174
## Current Behavior
The CNW cloud prompt is locked to auto-select deferred connection
(CLOUD-4255), always generating a short URL but never writing nxCloudId
to nx.json. Users have no explicit choice.
## Expected Behavior
The cloud prompt now offers three explicit choices:
- Yes: connect now, generate nxCloudId in nx.json, show strong
completion message
- Skip for now: deferred connection (no nxCloudId), still show short URL
and update README
- No: full opt-out, set neverConnectToCloud: true in nx.json, no URL, no
README update, no cloud messaging
**CLI args:** `--nxCloud=skip`, `--nxCloud=never` (new),
`--nxCloud=yes`. Non-interactive defaults to skip.
Closes CLOUD-4242
## Current Behavior
PR #34523 fixed the macOS file watcher issue (#34522) but did not
include comprehensive test coverage.
## Expected Behavior
Tests should verify that the fix works correctly and catch any future
regressions.
## Related Issue(s)
Adds test coverage for #34523 and #34522
---
## Changes
**TypeScript integration test**
(`packages/nx/src/native/tests/watcher.spec.ts`):
Added **"should detect file changes in large directory structures"** - a
comprehensive integration test that:
1. Creates 10,000+ directories simulating a monorepo-scale workspace
2. Starts a real `Watcher` instance
3. Creates and modifies files deep in the directory tree
4. Verifies that file change events are actually delivered
This test validates the actual behavior users care about - that file
watching works reliably in large repos - rather than testing
implementation details. It would catch any regression where events fail
to be delivered at scale.
## Testing
TypeScript integration test validates the actual bug fix - that file
events are delivered reliably in large directory structures with 10,000+
directories.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
Given the following Vite config:
```ts
import { defineConfig } from 'vite';
export default defineConfig((config) => {
console.log(config);
return {};
});
```
`npx nx preview` logs:
```ts
{
mode: 'development',
command: 'build',
isSsrBuild: false,
isPreview: false
}
```
## Expected Behavior
`npx nx preview` should log:
```ts
{
mode: 'development',
command: 'build',
isSsrBuild: false,
isPreview: true
}
```
## Related Issue(s)
https://github.com/vitejs/vite/issues/15694
Fixes #
## Current Behavior
`BatchProcess` accumulates all stdout/stderr output in
`terminalOutputChunks` and exposes it via `getTerminalOutput()`, but
nothing ever calls `getTerminalOutput()`. The accumulated strings are
unique allocations (created via `chunk.toString()`), not shared with the
output callbacks or `process.stdout.write`.
For verbose batched tasks (e.g., Maven/Gradle with hundreds of tasks),
this can hold tens to hundreds of MB for the entire batch duration.
## Expected Behavior
Remove the dead accumulation code. stdout/stderr chunks are still
forwarded to `process.stdout`/`process.stderr` and output callbacks as
before — only the unused storage is removed.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
When generating a library with vitest as the test runner and a non-vite
bundler (e.g. `tsc`), the js library generator creates two config files:
- `vitest.config.mts` (from the vitest `configurationGenerator`) with
`root: __dirname`
- `vite.config.ts` (from a second `createOrEditViteConfig` call) with
`root: import.meta.dirname`
The redundant `vite.config.ts` uses ESM-only `import.meta.dirname`
syntax, which causes TS1470 when the project targets CommonJS output:
```
vite.config.ts:5:9 - error TS1470: The 'import.meta' meta-property is not allowed in files which will build into CommonJS output.
5 root: import.meta.dirname,
~~~~~~~~~~~
```
## Expected Behavior
Only `vitest.config.mts` should be generated. The vitest
`configurationGenerator` already handles creating the correct config
file with `root: __dirname`. The second `createOrEditViteConfig` call
from `@nx/vite` was redundant and produced the conflicting file.
## Related Issue(s)
Fixes#34399
## Current Behavior
Since Nx 22.5.0, the daemon's native file watcher silently drops all
file change events on macOS in large monorepos (~5,250+ watched
directories). `nx watch`, `nx serve`, and any daemon-dependent file
watching is broken.
The root cause is that #34329 switched all watched paths to
`WatchedPath::non_recursive()`. On macOS, the `notify` crate uses
**kqueue** for non-recursive watches instead of **FSEvents**. kqueue
silently fails at scale due to vnode table pressure (`kern.num_vnodes ==
kern.maxvnodes`), causing the daemon to never detect file changes.
This is a **scale-dependent** bug: it works fine in small workspaces
(~30 directories) but breaks silently in large ones.
| | **Nx 22.4.5** | **Nx 22.5.0+** |
|---|---|---|
| **Small repo (~30 dirs)** | Works (FSEvents) | Works (~30 kqueue
watches) |
| **Large repo (~5,250+ dirs)** | Works (FSEvents) | **Broken** (kqueue
silently drops all events) |
## Expected Behavior
The macOS file watcher should detect file creates, modifications, and
deletions at any scale, matching the behavior of Nx 22.4.x.
## Fix
Use platform-conditional watch modes:
- **macOS:** Single recursive watch on the workspace root (uses FSEvents
natively)
- **Linux/Windows:** Non-recursive per-directory watches (preserves the
#33781 inotify fix)
On macOS, FSEvents handles recursive watching from a single root path,
so directory enumeration and dynamic registration are skipped entirely.
This also improves daemon startup time on macOS from ~10 minutes to <1
second in a 354-project monorepo.
### What changed in `watcher.rs`
1. **Initial pathset:** On macOS, watch only the root directory
recursively via FSEvents instead of enumerating all directories for
non-recursive kqueue watches.
2. **Dynamic directory registration (`on_action`):** Wrapped in
`#[cfg(not(target_os = "macos"))]` since FSEvents already watches the
full tree.
Linux/Windows behavior is completely unchanged.
### Why the event filter is fine as-is
We verified that with recursive FSEvents watches, macOS emits specific
`FileEventKind` variants (`Create(File)`, `Modify(Data(Content))`,
`Remove(File)`, `Modify(Name(Any))`) that the current
`watch_filterer.rs` already handles correctly. Zero events were rejected
by the catch-all. The `Modify(Any)` / `Create(Any)` variants are kqueue
artifacts that are not needed with FSEvents.
### Why kqueue fails silently
Apple's [File System Events Programming
Guide](https://developer.apple.com/library/archive/documentation/Darwin/Conceptual/FSEvents_ProgGuide/KernelQueues/KernelQueues.html)
explicitly recommends FSEvents over kqueue for large hierarchies: *"If
you are monitoring a large hierarchy of content, you should use file
system events instead."* kqueue requires `open(path, O_EVTONLY)` per
watched directory. Under vnode table pressure, the kernel recycles
vnodes with kqueue watches attached without notifying the watcher. There
is no error, no partial delivery, and no diagnostic signal.
## Tested on
- macOS 26.3 (Tahoe), Apple Silicon (arm64), APFS
- 354-project pnpm monorepo (~19,865 non-ignored directories)
- Verified: file modifications, file creates, and file deletes all
detected
- Daemon init time: ~10 min (with enumeration) -> <1s (with root-only
FSEvents watch)
## Related Issue(s)
Fixes#34522
Co-authored-by: Amp <amp@ampcode.com>
## Current Behavior
When nx release runs with docker-configured projects (either via
explicit config or
@nx/docker plugin inference), git tags are created with the literal
string {version}
instead of the actual version number (e.g., v{version} instead of
v1.0.6, or
app-3@{version} instead of app-3@1.0.0).
This happens because:
1. If ANY project in a release group has docker config,
preferDockerVersion is auto-set to
true for the ENTIRE group
2. createGitTagValues() then blindly selects
projectVersionData.dockerVersion, which is
null for non-docker projects (or projects with no changes)
3. The interpolate() function receives null for {version} and returns
the literal
placeholder unchanged
Commit messages are unaffected because createCommitMessageValues() only
uses newVersion and
already guards against null. The changelog code (changelog.ts:1117-1121)
also already has
the correct null-safe pattern.
## Expected Behavior
When preferDockerVersion is true but dockerVersion is null, git tags
should fall back to
using newVersion instead of producing literal {version} placeholders.
When both versions
are null, no tag should be created.
For mixed release groups (some projects have docker config, some don't),
the auto-enable
logic should use 'both' mode instead of true, which already has proper
null-safe checks for
each version type.
## Changes
- shared.ts: Added null-safe fallback (??) in createGitTagValues() for
both independent and
fixed group code paths, plus a guard to skip tag creation when both
versions are null
- config.ts: Refined auto-enable logic to check whether ALL or only SOME
projects have
docker config — mixed groups now get 'both' mode instead of true
- shared.spec.ts: Added 5 test cases covering null version fallback
scenarios for fixed
groups, independent groups, both-null, reverse fallback, and mixed
groups
## Related Issue(s)
Fixes#34382Fixes#33890Fixes#34391
## Current Behavior
The "7. Thriving Community" section on
`nx.dev/docs/guides/adopting-nx/from-turborepo` displayed an outdated Nx
download statistic (~5 million downloads per week).
## Expected Behavior
The "7. Thriving Community" section on
`nx.dev/docs/guides/adopting-nx/from-turborepo` now reflects the latest
Nx download statistic (~9 million downloads per week).
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: MaxKless <MaxKless@users.noreply.github.com>
When we optimized the `process.env` values to not embed the full object
unnecessarily, we also regressed in cases where users do use
`process.env` instead of `process.env["NX_PUBLIC_FOO"]`.
## Current behavior
Users cannot use `process.env` and must access each key individuall.
Although the serializing the full object can bloat bundle sizes, we also
don't want to break existing apps unnecessarily.
## Expected behavior
Existing apps should continue to work as usual.
## Related issues
Fixes#34279
## Current Behavior
When running `@nx/js:prune-lockfile` on a monorepo with transitive
dependencies that have multiple versions where neither version is
reachable from a direct dependency in package.json, the executor throws:
```
NX An error occurred while creating pruned lockfile
Original error: Cannot read properties of undefined (reading 'name')
TypeError: Cannot read properties of undefined (reading 'name')
at switchNodeToHoisted (node_modules/nx/src/plugins/js/lock-file/project-graph-pruning.js:165:31)
```
## Expected Behavior
The lockfile pruning should complete without crashing, even when some
transitive dependencies cannot be traced back to a direct dependency.
## Root Cause
In `rehoistNodes()`, when there are multiple nested nodes for a package,
the code finds the "closest" node by computing `pathLengthToIncoming()`
for each. However, when none of the nested nodes have a path to any
direct dependency in package.json, `pathLengthToIncoming()` returns
`undefined` for all of them. Since `undefined < Infinity` is `false` in
JavaScript, `closest` remains `undefined`, and then
`switchNodeToHoisted(undefined, ...)` crashes.
## Fix
Add a guard to only call `switchNodeToHoisted()` when a closest node was
actually found:
```typescript
if (closest) {
switchNodeToHoisted(closest, builder, invBuilder);
}
```
This allows the pruning to continue - the nested nodes simply won't be
rehoisted if no closest node can be determined.
## Related Issue
Fixes#34322
## Test Added
Added a unit test that verifies `rehoistNodes()` doesn't crash when
nested nodes have no path to package.json dependencies.
## Current Behavior
When importing a `package.json` file in an Angular application built
with `@nx/angular-rspack`, the build fails with a Babel syntax error if
the `package.json` contains `@angular/*` dependencies:
```
SyntaxError: /path/to/package.json: Missing semicolon. (2:10)
1 | {
> 2 | "name": "@org/app",
| ^
3 | "version": "4.0.2",
4 | "dependencies": {
5 | "@angular/platform-browser": "20.3.7",
```
This happens because the `JS_ALL_EXT_REGEX` pattern
`/\.[cm]?(js)[^x]?\??/` incorrectly matches `.json` files. When the JSON
file content contains `@angular` strings, the
`angular-partial-transform-loader` attempts to process it through Babel,
which fails because JSON is not valid JavaScript.
**Root cause:** The regex `[^x]?` (optional character that is NOT 'x')
allows `.json` to match because 'o' is not 'x'.
## Expected Behavior
- `.json` files should NOT match `JS_ALL_EXT_REGEX` or
`TS_ALL_EXT_REGEX`
- Importing `package.json` in Angular applications should work correctly
- All existing matches for `.js`, `.jsx`, `.mjs`, `.cjs` (and TypeScript
equivalents) should continue to work
## Related Issue(s)
https://github.com/nrwl/nx/issues/32649
## Current Behavior
Documentation bugfix on
https://nx.dev/docs/concepts/typescript-project-linking#set-up-typescript-project-references
In the section about setting up TypeScript project references, the
documentation currently states:
"Each project's tsconfig.lib.json file extends the project's
tsconfig.json file and adds references to the tsconfig.lib.json files of
project dependencies."
## Expected Behavior
In a standard Nx workspace configuration, tsconfig.lib.json extends the
workspace-level tsconfig.base.json, not the project-level tsconfig.json
(and the example provided just after is correct).
Suggested correction:
"Each project's tsconfig.lib.json file extends the workspace
tsconfig.base.json file and adds references to the tsconfig.lib.json
files of project dependencies."
## Related Issue(s)
Fixes #34118
---------
Co-authored-by: Aude Planchamp <aude.planchamp@ekino.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
When a plugin worker process exits unexpectedly, the exit handler
previously sent synthetic `loadResult` messages to all pending response
handlers. If any handler was waiting for a different result type (e.g.
`createNodesResult`), the type validation would reject with a confusing
"Expected createNodesResult, got loadResult" error instead of surfacing
the actual cause.
Split response handlers into `onMessage` / `onError` callbacks so the
exit handler can reject each pending promise directly with a clear
"Plugin worker exited unexpectedly" error.
Also use unique transaction IDs for `load` messages (via `generateTxId`)
to avoid potential handler overwrites during worker restarts.
Fixes#34564
## Summary
Testing CI behavior with continuous assignment disabled and cache bust
set to 4.
This is part of investigating flakiness potentially related to
continuous assignment in CI.
## Test plan
- Monitor CI execution behavior
- Compare with other test branches (bust=2, bust=3)
## Current Behavior
Documentation pages across Getting Started, How Nx Works, and Platform
Features sections contain:
1. Duplicated content — mental-model.mdoc has a ~70-line caching section
and a ~20-line DTE section that are near-verbatim
copies of how-caching-works.mdoc and distribute-task-execution.mdoc
respectively. remote-cache.mdoc re-explains local caching
in its intro instead of linking to the canonical page.
2. Missing cross-reference links — Key concepts like "affected command",
"remote cache", "project graph", and "task pipeline
configuration" are mentioned without linking to their dedicated pages.
3. Style guide violations — Trust-undermining words ("simply", "just",
"straightforward"), anti-AI phrases ("Let's take",
"Whether you're..."), product possessives ("Nx's"), customer perspective
issues ("allows you to"), and em dashes appear
across Getting Started and How Nx Works pages.
## Expected Behavior
1. Content consolidation — mental-model.mdoc is trimmed by ~85 lines,
keeping the concept + images and linking to dedicated
pages for details. remote-cache.mdoc intro references the canonical
caching page. publish-conformance-rules-to-nx-cloud.mdoc
deduplicates its intro. maintain-typescript-monorepos.mdoc shortens its
inferred tasks re-explanation.
2. Cross-reference links added — First-mention links for affected,
remote cache, computation caching, task pipeline
configuration (in mental-model) and project graph (in self-healing-ci).
3. Style guide compliance — 18 fixes across 10 Getting Started and How
Nx Works pages, removing banned phrases and aligning
with the new STYLE_GUIDE.md.
4. Sidebar improvements — Cache Task Results added after Run Tasks in
Platform Features; Maintain TypeScript Monorepos moved
to first in KB > TypeScript.
## Pages changed
```
┌─────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Section │ Pages │
├─────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Getting Started │ intro, index, nx-cloud, ai-setup, start-with-existing-project │
├─────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤
│ How Nx Works │ mental-model, how-caching-works, task-pipeline-configuration, nx-plugins, nx-daemon │
├─────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Features │ remote-cache, self-healing-ci, maintain-typescript-monorepos, cache-task-results (sidebar only) │
├─────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Enterprise │ publish-conformance-rules-to-nx-cloud │
└─────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────┘
```
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
### 1. Continuous tasks missing from `postTasksExecution` hook
When running continuous tasks (e.g., `nx serve app`) and stopping them
with Ctrl+C, the `postTasksExecution` lifecycle hook does not include
them in `taskResults`. This breaks plugins that rely on post-run
statistics (e.g., uploading task stats to DataDog).
### 2. Confusing TUI status when sibling continuous task exits
When multiple continuous tasks run together and one exits unexpectedly,
its sibling is marked as "failed" even though it was intentionally
terminated/stopped by the task orchestrator.
## Expected Behavior
1. All tasks, including continuous ones, are included in `taskResults`
for the `postTasksExecution` hook
2. Continuous tasks that are intentionally stopped (because dependent
tasks completed or during graceful shutdown) report as `success` with
`Stopped` display status
3. Continuous tasks that exit unexpectedly (crash) report as `failure`
4. TUI summary shows correct status: success when all tasks completed
successfully, square icon for stopped tasks
## Related Issue(s)
Fixes https://github.com/nrwl/nx/issues/33561
Supersedes:
- https://github.com/nrwl/nx/pull/33562
- https://github.com/nrwl/nx/pull/34132
Add NX_CLOUD_IO_TRACING_DIRECTORY environment variable.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes Q-245
## Current Behavior
No documentation exists explaining the concept of synthetic monorepos —
how they bridge polyrepo and monorepo setups by connecting separate
repositories into a unified dependency graph.
https://deploy-preview-34565--nx-docs.netlify.app/docs/concepts/synthetic-monorepos
## Expected Behavior
New concept page under "How Nx Works" that explains:
- What synthetic monorepos are (unified graph across separate repos
without moving code)
- Why they matter for humans (visibility, cross-repo coordination) and
AI agents (seeing beyond repo boundaries)
- What they provide (cross-repo graph, actionable tooling, AI agent
enablement)
- How they serve as a gradual entry point toward deeper monorepo
adoption
## Related Issue(s)
N/A — new documentation page based on existing content from webinars and
internal knowledge.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: juristr <juristr@users.noreply.github.com>
## Current Behavior
When users hit the `generatePackageJson: true` error with TS Solution
Setup, the error tells them to "unset the option" but gives no guidance
on the replacement workflow.
## Expected Behavior
The error message now includes a link to the pruning guide at
https://nx.dev/docs/technologies/node/guides/deploying-node-projects so
users can immediately find the migration steps.
## Related Issue(s)
Related #30146
When plugin isolation is off, concurrent createNodesV2 invocations share
the same module instance. The module-level mutable `cache` variable
caused
invocation A's `finally` block to null it out while invocation B was
still
reading from it, resulting in "Cannot read properties of null (reading
'configContexts')".
Replace the shared mutable `cache` with a Symbol-keyed Map so each
invocation gets its own isolated cache. The tsconfig disk cache is
shared
across invocations with an idempotent initialization guard.
CLOSES NXC-3971
## Summary
- Updated version of the classic "Misconceptions about Monorepos"
article
- New sections on AI compatibility, scaling strategies (affected,
caching, distribution, atomization), and `@nx/owners`
- Custom SVG diagrams for project graph illustrations (replacing old
Medium images)
- Authors: Victor Savkin, Juri Strumpflohner
## Test plan
- [ ] Verify blog post renders correctly on preview
- [ ] Check all images load (SVGs + avif)
- [ ] Verify internal doc links resolve
- [ ] Check TOC renders properly
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: juristr <juristr@users.noreply.github.com>
## Current Behavior
folks had to type in `nx-cloud apply-locally`
## Expected Behavior
now `nx apply-locally` works
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
### Current Behavior
nx list <plugin> shows generator/executor names and descriptions in text
format only. It does not show where the plugin or its
generators/executors are located on disk, and there is no
machine-readable output option.
### Expected Behavior
- nx list --json outputs all local and installed plugins with their
paths and capability types
- nx list <plugin> --json outputs detailed structured JSON including
resolved paths to each generator/executor implementation and schema
- nx list <plugin> (text mode) now also shows the plugin's root path
## Current Behavior
In TS Solution Setup, the esbuild executor forces `runTypeCheck` even
when `skipTypeCheck: true` and `declaration: false`, due to the `||
options.isTsSolutionSetup` condition in `esbuild.impl.ts` (lines 139-140
and 195).
When `declaration: false`, this type check runs in `noEmit` mode with
`ignoreDiagnostics: true` — making it **completely pointless** (no
declarations emitted, no diagnostics reported). Its only observable
effect is writing a poisoned 19-byte tsbuildinfo file that causes race
conditions with `tsc --build`.
## Expected Behavior
When `skipTypeCheck: true` and `declaration: false`, the esbuild
executor should not run type checking at all. The `isTsSolutionSetup`
override should only force type checking when declarations actually need
to be generated.
## Fix
### Primary: Skip unnecessary type check (`esbuild.impl.ts`)
```diff
// Non-watch mode (line 195)
- if (!options.skipTypeCheck || options.isTsSolutionSetup) {
+ if (!options.skipTypeCheck || (options.isTsSolutionSetup && options.declaration)) {
// Watch mode (lines 139-140)
- options.isTsSolutionSetup
+ (options.isTsSolutionSetup && options.declaration)
```
Only force type checking in TS Solution Setup when declarations need to
be generated. This eliminates the pointless type check entirely.
### Defense-in-depth: Prevent tsbuildinfo in `noEmit` mode
(`run-type-check.ts`)
```diff
- : { noEmit: true };
+ : { noEmit: true, composite: false };
```
Setting `composite: false` alongside `noEmit: true` prevents TypeScript
from writing tsbuildinfo files, protecting against this class of bug
from any caller of `runTypeCheck`.
## Why This is Safe
| Scenario | Before | After |
|----------|--------|-------|
| `skipTypeCheck: false`, `declaration: false`, `isTsSolutionSetup:
true` | Runs type check (noEmit) | Still runs (`!false \|\| ...` = true)
|
| `skipTypeCheck: false`, `declaration: true`, `isTsSolutionSetup: true`
| Runs type check (emitDeclarationOnly) | Still runs |
| `skipTypeCheck: true`, `declaration: true`, `isTsSolutionSetup: true`
| normalize.ts overrides skipTypeCheck to false; runs type check | Still
runs (same normalization) |
| **`skipTypeCheck: true`, `declaration: false`, `isTsSolutionSetup:
true`** | **Runs pointless type check (noEmit + ignoreDiagnostics),
writes poisoned tsbuildinfo** | **Skipped entirely** |
The only behavior change is in the last row — the case where the type
check was doing nothing useful but causing harm.
## Related Issue(s)
Fixes#34492
---------
Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
## Current Behavior
Users migrating to Nx 20's TS Solution Setup lose `generatePackageJson`
support and have no documentation on the replacement prune workflow
(`prune-lockfile`, `copy-workspace-modules`). The error message tells
them to "unset the option" but doesn't explain what to do instead.
## Expected Behavior
A dedicated guide at
`/docs/technologies/node/guides/deploying-node-projects` covers the full
prune workflow: when to use pruning vs bundling, target configuration,
Dockerfile setup, and step-by-step migration from `generatePackageJson`.
Also updated the existing bundling guide to match the same structure
(intro table, cross-links, style guide compliance). The two articles are
sister guides covering the two ways to deploy Node.js apps: bundle
everything into a single file, or prune dependencies for a
`node_modules`-based install.
Cross-links added from the bundling guide and ci-deployment guide.
## Related Issue(s)
Closes#30146
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
## Current Behavior
The `checkAllBranchesWhen` option is documented as type `string` with a
minimal description, which does not match the actual implementation.
## Expected Behavior
Document the correct type (`boolean | string[]`) and explain the default
branch resolution behavior, the three value modes (true, false,
string[]), and when this option is useful.
## Related Issue(s)
Closes DOC-414
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
## Current Behavior
When multiple Nx processes (task hasher, daemon, workers) access the
SQLite database concurrently, the `NxDbConnection::transaction()` method
only retries the BEGIN step using the `retry_db_operation_when_busy!`
macro. Operations executed inside the transaction and the COMMIT are not
retried, so if the database is busy during those steps, the task crashes
with:
```
Error: DB transaction operation error: SqliteFailure(Error { code: DatabaseBusy, extended_code: 5 }, Some("database is locked"))
```
This is particularly common during parallel task hashing with continuous
tasks, where `TaskDetails.recordTaskDetails()` and `RunningTasksService`
compete for write access.
## Expected Behavior
The entire transaction (begin, execute, commit) is retried as a single
unit when any step encounters a `DatabaseBusy` error. If the database is
busy during the operation or commit, the transaction is automatically
rolled back (via drop) and retried with the same exponential backoff
used everywhere else.
## Related Issue(s)
<!-- No public issue linked -->
## Current Behavior
The NPM audit CI job is failing due to a critical XSS vulnerability
(CVE-2026-25896) in `fast-xml-parser` version 4.5.3.
## Expected Behavior
The NPM audit should pass with no critical vulnerabilities.
## Related Issue(s)
Fixes the failing NPM audit CI run:
https://github.com/nrwl/nx/actions/runs/22288455713
---
This PR upgrades `fast-xml-parser` from `^4.2.7` to `^5.3.7` to address
GHSA-m7jm-9gc2-mpf2, a critical XSS vulnerability that allows entity
encoding bypass via regex injection in DOCTYPE entity names.
The package is only used in
`scripts/documentation/internal-link-checker.ts` for parsing XML
sitemaps, so the risk of this upgrade is low.
## Current Behavior
Batch mode is binary:
- `--batch` flag → batch ALL executors that support it
- No flag → batch NOTHING
This means users of gradle/maven must always remember to pass `--batch`
to get the performance benefits.
## Expected Behavior
Plugin authors can now set `preferBatch: true` in their executor config
to indicate batch mode should be used by default. Users can still
opt-out with `--no-batch`.
Three states:
- `--batch` → batch everything
- `--no-batch` → batch nothing
- (not specified) → use each executor's `preferBatch` preference
| `--batch` flag | `preferBatch` | Result |
|----------------|---------------|--------|
| `true` | any | Batch |
| `false` | any | No batch |
| not set | `true` | Batch |
| not set | `false`/undefined | No batch |
## Changes
- Added `preferBatch?: boolean` to `ExecutorJsonEntryConfig` and
`ExecutorConfig` interfaces
- Updated `--batch` default from `false` to `undefined` to allow
`preferBatch` to decide
- Modified batch scheduling logic to respect `preferBatch`
- Enabled `preferBatch: true` for gradle and maven executors
- Added 5 unit tests covering all `preferBatch` scenarios
## Related Issue(s)
<!-- Link any related issues here -->
PR #30826 introduced a fallback definition for `process.env`:
```ts
{ 'process.env': '{}' }
```
Since `DefinePlugin` performs raw textual replacement, this can generate
invalid JavaScript when user code accesses environment variables via dot
notation:
```ts
process.env.SOME_KEY
```
becomes:
```js
{}.SOME_KEY
```
`{}` is parsed as a block statement (not an object literal), resulting
in:
> Unexpected token: punc (.)
This PR updates the fallback to a parenthesized object literal:
```ts
{ 'process.env': '({})' }
```
which produces valid output:
```js
({}).SOME_KEY
```
This preserves the intended bundle-size optimization while ensuring
syntactically correct output for standard `process.env.X` access
patterns.
## Related Issue(s)
Refs #30826Fixes#34460
//CC @Coly010 @coolassassin
## Current Behavior
`nx import` relies on interactive prompts (enquirer) and spinners (ora)
for user interaction. AI agents cannot parse this output or respond to
prompts, making `nx import` unusable in agent workflows.
## Expected Behavior
When `isAiAgent()` is true, `nx import` now:
- Skips all interactive prompts and spinners
- Emits NDJSON progress to stdout (`starting`, `cloning`, `filtering`,
`merging`, `detecting-plugins`, `complete`)
- Returns structured `needs_input` when required args are missing (all
at once to minimize round-trips)
- Returns structured `needs_input` for plugin selection when `--plugins`
flag is not provided
- Returns structured success/error results with hints and next steps
- Supports new `--plugins` flag (`skip`/`all`/comma-separated list)
Shared AI output types extracted from `init` into
`packages/nx/src/command-line/ai/ai-output.ts` for reuse across
commands.
## Current Behavior
Running `configure-ai-agents` overwrites the entire `source` object in
`extraKnownMarketplaces['nx-claude-plugins']`, removing any user-added
properties like `ref`.
## Expected Behavior
User-added source properties (e.g. `ref`) are preserved, while `source`
and `repo` are always set to the correct values.
The nx-welcome component inline styles were always using CSS/SCSS syntax
(with braces and semicolons) regardless of the selected style option.
When --style=sass is chosen, the component now correctly uses SASS
indented syntax (no braces or semicolons) matching the expected
behavior for the .sass file format.
Fixes#33489
## Current Behavior
When `configure-ai-agents` is invoked from within an AI agent (e.g.
Claude Code), it either shows an interactive multi-select prompt (which
the agent can't interact with) or requires `--agents` and
`--no-interactive` flags to work correctly. This makes the experience
awkward when AI agents call the command as part of workspace setup.
## Expected Behavior
When an AI agent is detected (via environment variables like
`CLAUDECODE`), the command now:
1. **Auto-configures the detected agent** if it's not yet configured,
partially configured, or outdated — no prompts needed
2. **Auto-updates any other outdated agents** alongside the detected one
3. **Reports non-configured agents** with a suggested `nx
configure-ai-agents --agents ...` command
4. **Reports up-to-date status** if the detected agent is already fully
configured
When `--agents` is explicitly passed, detection is ignored entirely
(existing behavior preserved). `--check` mode also works with detection
— it checks the detected agent plus all other configured agents.
Additionally:
- Strips AI agent detection env vars (`CLAUDECODE`, `CLAUDE_CODE`,
`OPENCODE`, `GEMINI_CLI`, etc.) from e2e subprocess environments to
prevent the test runner's environment from leaking into tests
- Fixes e2e tests to use `AGENTS.md` (not `GEMINI.md`) for gemini
assertions, matching what the gemini generator actually creates for
fresh installations
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
- Cypress `start-dev-server.ts` file creates a port lock file next to
the source code
- Native temp DB files created by tests are not properly ignored
- Astro config timestamp file is not ignored
These all trigger watch file change events, which cause the project
graph to be recomputed unnecessarily.
## Expected Behavior
Output files should not trigger watch file change events. The project
graph should not be recomputed unnecessarily.
## Current Behavior
Several Nx packages directly depend on a minimatch version with a
high-severity vulnerability
(https://github.com/advisories/GHSA-3ppc-4f35-3m26).
## Expected Behavior
Several Nx packages should depend directly on a minimatch version that
does not include the reported high-severity vulnerability.
Note: unsafe `minimatch` versions can still be pulled in transitively.
Upstream deps need to be updated, and then we need to update the Nx
packages to newer versions.
## Related Issue(s)
Fixes#34507
---------
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
## Current Behavior
Users specifying directory paths in `inputs` without a trailing slash or
glob pattern find that files are not matched. For example,
`{projectRoot}/src` does not match any files, while `outputs` allows
naked directory paths without issue.
## Expected Behavior
Documentation clearly explains that directory paths in `inputs` require
a trailing slash or glob pattern:
```jsonc
{
"inputs": [
"{projectRoot}/src/", // ✓ Works (trailing slash)
"{projectRoot}/src/**/*", // ✓ Works (glob pattern)
"{projectRoot}/src" // ✗ Does NOT work
]
}
```
### Changes
- **Reference doc** (`reference/inputs.mdoc`): Added "Directory Paths"
section explaining the requirement with examples
- **Guide** (`configure-inputs.mdoc`): Added callout warning at top
alerting users to this behavior
- Both docs note the difference from `outputs`, which do support naked
directory paths
## Related Issue(s)
Fixes
https://linear.app/nxdev/issue/NXC-2102/clarify-trailing-slash-requirement-for-inputs-in-directory-paths
Co-authored-by: Steven Nance <steven@nrwl.io>
## Current Behavior
When creating a new workspace using `create-nx-workspace` with the
"custom" preset flow, an `nxCloudId` is generated and added to
`nx.json`. This happens even though the onboarding flow is supposed to
handle Cloud setup separately via a short URL.
## Expected Behavior
New workspaces created via `create-nx-workspace` should not have
`nxCloudId` set in `nx.json`. Instead, a short URL is provided for users
to finish Cloud onboarding on their own. The `nxCloud: 'skip'` option is
now passed for the custom flow to prevent the ID from being generated.
E2E tests are updated to verify that `nxCloudId` is undefined in the
generated `nx.json` across all workspace presets.
## Related Issue(s)
N/A - internal fix for workspace creation behavior.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
`--help` on commands that hit yargs help are hanging
## Expected Behavior
It doesn't hang. This contains a quick fix in adding the process.exit
call, but also adds the unref needed to maintain previous working
behavior. We'll need to investigate long term if additional areas keep
commands alive, but adding this unref theoretically allows removing the
process.exit calls from `nx show`
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
## Current Behavior
14 e2e test suites were disabled (`xdescribe`) due to an ESM import
issue in `@microsoft/api-extractor@7.57.0` (see
https://github.com/qmhc/unplugin-dts/issues/461).
## Expected Behavior
With the upstream issue resolved, all 14 e2e test suites are re-enabled
(`describe`) and should pass normally.
## Related Issue(s)
Reverts #34516
## Current Behavior
Running `nx test` for Next.js projects causes Jest to hang with:
```
Jest did not exit one second after the test run has completed.
```
This happens because `next/jest` loads `next.config.js`, which calls
`withNx` → `createProjectGraphAsync()`. The daemon client socket
connection is left open, keeping the Node.js event loop alive and
preventing Jest from exiting. Non-Next.js projects are unaffected since
they don't trigger this code path.
## Expected Behavior
Jest exits cleanly after tests complete for Next.js projects, without
needing `forceExit: true`.
## Fix
Pass `resetDaemonClient: true` to `createProjectGraphAsync()` in
`packages/next/plugins/with-nx.ts`. This tells the project graph
function to call `daemonClient.reset()` after fetching the graph, which
closes the socket and allows Jest to exit.
### Verification
| Scenario | Before | After |
|----------|--------|-------|
| `nx test next-app` | Hangs | Exits cleanly |
| `NX_DAEMON=false nx test next-app` | Exits cleanly | Exits cleanly |
| Direct `npx jest` | Exits cleanly | Exits cleanly |
| Non-Next.js `nx test react-lib` | Exits cleanly | Exits cleanly |
## Related Issue(s)
Fixes#32880
## Current Behavior
The Maven plugin is on version `0.0.13`.
## Expected Behavior
The Maven plugin is bumped to version `0.0.14`, with a migration
generated for Nx `22.6.0-beta.1`.
## Current Behavior
Agents are silent and hard to diagnose
## Expected Behavior
Agents should print debug logs without making all of Nx print debug logs
## Current Behavior
14 e2e tests are failing across master with "Failed to process project
graph" errors. The root cause is `@microsoft/api-extractor@7.57.0` which
has a broken ESM export (`ConsoleMessageId`). When `@nx/vite/plugin` or
`@nx/vitest` plugins load `vite.config.mts` files, they transitively
import api-extractor which crashes.
## Expected Behavior
Broken e2e tests are disabled via `xdescribe` so they no longer block
CI. Tests should be re-enabled once the upstream api-extractor ESM issue
is fixed.
## Disabled Tests
| Project | Test File |
|---------|-----------|
| e2e-vite | `vite.test.ts`, `vite-legacy.test.ts`,
`vite-ts-solution.test.ts` |
| e2e-vue | `vue.test.ts`, `vue-legacy.test.ts`,
`vue-ts-solution.test.ts` |
| e2e-js | `js-ts-solution.test.ts` |
| e2e-web | `web-vite.test.ts` |
| e2e-react | `react-vite.test.ts`, `react-ts-solution.test.ts` |
| e2e-next | `next-ts-solutions.test.ts` |
| e2e-release | `release-publishable-libraries.test.ts`,
`release-publishable-libraries-ts-solution.test.ts` |
| e2e-storybook | `storybook-nested.test.ts` |
## Related Issue(s)
Upstream: https://github.com/qmhc/unplugin-dts/issues/461
## Current Behavior
The `maven-shade-plugin` at version 3.5.0 intermittently fails on CI
with:
```
Could not replace original artifact with shaded artifact!
```
This is a file-locking race condition where the plugin fails to
atomically replace the original JAR with the shaded JAR.
## Expected Behavior
Upgrading to 3.6.0 resolves the intermittent CI failures by using
improved file-handling logic with better retry behavior during the
artifact replacement step.
## Related Issue(s)
N/A - fixes intermittent CI flakiness in `maven-batch-runner` builds.
Closes#19779
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
## Current Behavior
When targeting Windows the resulting binary (nx.dll) dynamically links
against Microsoft Visual C++ runtime (msvcrt140.dll). This means nx
won't be able to run on Windows systems without this runtime installed.
## Expected Behavior
I'd like to avoid this dependency by linking the runtime statically into
the nx binary. (This is also how e.g. cargo.exe for Windows is built.)
## Related Issue(s)
Fixes#19779
---------
Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
## Current Behavior
Terminal output in the task runner is accumulated via repeated string
concatenation (`terminalOutput += chunk`). Each `+=` on a growing string
causes V8 to allocate a new, larger string and copy the old contents,
resulting in O(n²) allocation behavior for tasks with large output.
Additionally, `PseudoTtyProcess.onExit` didn't pass `terminalOutput` to
its callbacks, forcing callers like `TaskOrchestrator` to duplicate
output accumulation logic with a separate `onOutput` listener.
## Expected Behavior
- Terminal output is collected in `string[]` arrays and joined once at
the end, reducing intermediate allocations from O(n²) to O(n)
- `PseudoTtyProcess.onExit` now passes `terminalOutput` as a second
argument, matching the signature of other `RunningTask` implementations
- `TaskOrchestrator` no longer needs a special code path for
`PseudoTtyProcess` — unified `onExit` handling for all task types
- `tui-summary-life-cycle` accumulates output in chunks during execution
and stores the finalized string on task completion, allowing chunk
arrays to be GC'd
- `SeriallyRunningTasks` and `RunningNodeProcess` similarly switched to
chunk-based accumulation
- `BatchProcess` and `NodeChildProcessWithNonDirectOutput` lazily join
and cache their terminal output
Work on making a tech intro pages more consistent with each other and
focus on "answering the 80%" for the given technology.
Focusing on
- Angular
- Maven/Gradle
- react
- TS
- Vite
- Vitest/Jest
The changes are based around answering the following, where each
"category" of page might have a different set of depth for the answer.
1. Why do I want to use this plugin?
- Plugins are considered fully optional and are aimed at providing
better DX for a technology, such as inferred setup, generators,
migrations.
- some plugins (like TSC) might have special call outs in some of this,
but generally the same for all plugins.
2. How do I use this plugin in my workspace?
- also pretty commonly the "same" for all plugins in terms of "setup"
- where they differ is mostly for frameworks, e.g. Angular, React.
You're looking at setting up a project to use these tools
- For Build/Test tools you're looking at adding to an existing project,
or converting from one to another.
- Build/Test tools are "means to an end", so should callout if the goal
is tool + framework in a "new" context point to the framework based
plugin page. Otherwise, show adding to an existing project like React
project.
3. What do I need to know about using this plugin?
- understanding finer details of a plugin options, e.g. buildable &
publishable
- extra generators for the plugin. e.g. "convert-to-swc"
- generally I like the idea of having a "CI considerations" where we
talk about CI setups that can help, e.g. options or batch mode etc.
closes DOC-407
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: barbados-clemens <barbados-clemens@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
navigation of the breadcrumbs could lead to confusing state since they
were based around the folder structure.
Breadcrumbs are now based around the sidebar structure so they match the
hierarchy of content.
Note: I left the existing index file based route pages in place in case
there are any links people have booked marked/linked to in other
locations. these will get cleaned up when we finally rewrite all the
URLs to their new content locations
## Current Behavior
When file changes arrive rapidly, the daemon triggers multiple
concurrent project graph recomputations that all run to completion —
wasting CPU/memory on redundant work and returning stale results.
Additionally, after processing file changes, the daemon clears all
tracked files indiscriminately. Files that changed mid-recomputation are
silently lost and never reflected in the project graph until another
unrelated file change arrives.
## Expected Behavior
Stale recomputations detect when a newer one has started and exit early,
chaining to the newer promise so callers always get the freshest result.
File change tracking now uses versioned maps. Each batch of file watcher
events gets a unique version, and only files matching the snapshotted
version are cleared after processing. Files that changed
mid-recomputation are preserved and picked up by the next cycle.
## Current Behavior
Two categories of e2e CI failures were observed in run
https://github.com/nrwl/nx/actions/runs/22127884259:
1. **`e2e-nx-init` and `e2e-js` fail on Node 22.12.0** with:
```
error eslint-visitor-keys@5.0.0: The engine "node" is incompatible with
this module.
Expected version "^20.19.0 || ^22.13.0 || >=24". Got "22.12.0"
```
Node 22.12.0 is one minor version short of the `^22.13.0` range required
by `eslint-visitor-keys@5.0.0`.
2. **`e2e-nx` tests fail because `[isolated-plugin]` / `[plugin-worker]`
verbose messages leak into captured stdout**, causing:
- `JSON.parse(runCLI('show project --json'))` to throw `SyntaxError:
Unexpected token 'i', "[isolated-p"...`
- `expect(runCLI('show projects')).toEqual('')` to fail with worker
spawn noise
- The `@nx/workspace:infer-targets` test to unexpectedly find
`@nx/remix` in output (from a worker spawn message)
Root cause: in `isolated-plugin.ts`, the plugin worker's stdout was
piped directly to `process.stdout`, so `[plugin-worker]` verbose
messages written by the worker ended up in the stdout captured by
`runCLI` in e2e tests.
## Expected Behavior
1. The CI matrix uses a Node 22.x version that satisfies `^22.13.0`.
2. Plugin worker verbose/diagnostic messages go to `process.stderr` (not
`process.stdout`), so they don't contaminate output captured by `runCLI`
in e2e tests. Both worker stdout and stderr now pipe to
`process.stderr`, and the max listener bump is consolidated to `+2` on
stderr.
## Related Issue(s)
N/A — identified from CI run
https://github.com/nrwl/nx/actions/runs/22127884259
## Current Behavior
When running in maven 4 batch mode, the build state is recorded only
after the full batch is done.
This means that nx caching records the state of a task before build
state is recorded to disk.
When running another maven task that depends on this partially recorded
cache, the build state file is missing and we get errors.
## Expected Behavior
build state should be recorded after every task is done and before nx
caching can kick in. This way we can ensure that nx cache is correct.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Current Behavior
When running generators or migrations, Nx automatically skips Prettier
formatting if no root Prettier config is detected (added in #30426).
However, there's no way to explicitly skip Prettier formatting when a
config IS present but the user wants to bypass it for specific
operations.
This can be needed when:
- When running something like Oxfmt that may treat prettier slightly
differently, even with the same config (this is the main thing I ran
into)
- Running migrations where Prettier reformatting causes unintended side
effects (e.g., breaking `eslint-disable` comments)
- Temporarily disabling formatting for debugging purposes
- Using a formatter that coexists with Prettier in the workspace but
should take precedence for certain files
## Expected Behavior
Users can set `NX_SKIP_FORMAT=true` to explicitly skip Prettier
formatting in generators and migrations, regardless of whether Prettier
is configured. TSConfig path sorting (controlled by
`sortRootTsconfigPaths` or `NX_FORMAT_SORT_TSCONFIG_PATHS`) continues to
work independently.
```bash
NX_SKIP_FORMAT=true nx migrate --run-migrations
NX_SKIP_FORMAT=true nx g @nx/react:app my-app
```
## Related Issue(s)
Related to #30403 and #30426. This enhancement adds explicit user
control for cases where auto-detection of Prettier configuration isn't
sufficient.
## Current Behavior
When publish fails due to missing OTP code, its not clear as a user who
is using the top level command what to do next.
## Expected Behavior
Add the --otp flag to the top-level `nx release` command so users can
provide a one-time password for 2FA-enabled registries when running the
full release orchestration (version + changelog + publish).
When publish fails due to an expired or missing OTP (EOTP error),
display a helpful warning listing affected projects and the exact
command to re-run the publish step in isolation with a new OTP.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
Codex has only basic MCP/AGENTS.md support right now. Also, because it
used to have only global-level config files, we had some extra logic
around configuring that.
We want Codex to get the latest skills too (they don't support custom
subagents yet, though) and use project-level config files that they now
support.
After running nx build (or any task), the daemon now shows a hint if
your AI agent configuration is outdated: "Your AI agent configuration is
outdated. Run nx configure-ai-agents to update."
The daemon computes and caches the full agent configuration status
(fully configured, outdated, partially configured, non-configured) using
latest Nx from npm, so the check is always against the newest available
configuration. Running nx configure-ai-agents resets the daemon's cache
so the message disappears on the next build.
Key changes
- Daemon agent status endpoint: New GET_CONFIGURE_AI_AGENTS_STATUS /
RESET_CONFIGURE_AI_AGENTS_STATUS message types. The handler fires off
computation in the background and returns immediately (never blocks the
request). Results are cached for the daemon's lifetime.
- Shared latest-nx module: Extracted the "install nx@latest to tmp"
logic from nx-console-operations into daemon/server/latest-nx.ts so both
Nx Console and AI agents handlers share a single cached installation.
Includes a race-condition guard (in-flight promise deduplication).
- Post-task outdated hint: run-command.ts queries the daemon after task
execution and prints a single dim line if agents are outdated.
- Daemon reset from configure-ai-agents: The CLI sets NX_DAEMON=false
for configure-ai-agents, so we bypass daemonClient.enabled() and use
isServerAvailable() directly to reach an already-running daemon. The
socket is closed in a finally block so the process exits cleanly.
- Async editor detection (Rust): Made isEditorInstalled,
canInstallNxConsoleForEditor, installNxConsole, and related napi
functions async so they run on the libuv thread pool instead of blocking
Node's event loop. This prevents the daemon from stalling for ~3.5s when
checking editor extensions.
- output.logRawLine: New helper that prints a single line without the NX
prefix.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
We want to minimize prompts for people and agents. But we also want to
help them by setting up nx config for them so their agents can work
optimally.
If they're executing `nx init` or `create-nx-workspace` from within an
agent, it's a reasonable assumption that they'll want the best AI config
for that specific agent - so we set it up for them.
## Current Behavior
The `nx watch` file watcher uses the `ignore-files` and
`watchexec-filterer-ignore` crates to handle `.gitignore` matching.
These crates use a trie-based approach that has a bug with
path-component matching — certain gitignore patterns (e.g., prefix
patterns) don't match correctly, causing files that should be ignored to
trigger unnecessary watch events.
## Expected Behavior
Gitignore patterns are matched correctly using per-directory `Gitignore`
instances from the `ignore` crate — the same crate already used by the
file walker. Each `.gitignore` file is scoped to its directory, and
matching is done deepest-first so that nested gitignores take priority —
matching standard git behavior.
### What changed
- Replaced `ignore-files` + `watchexec-filterer-ignore` with direct use
of `ignore::gitignore::{Gitignore, GitignoreBuilder}`, aligning the
watcher with the approach already used by the file walker
- Each `.gitignore` is now compiled as a standalone instance tied to its
parent directory
- Gitignore evaluation walks deepest-first; first match wins
- `.nxignore` matching now uses `matched_path_or_any_parents` for
correct ancestor checking
- `create_filter` is now synchronous (no longer `async`) since the new
approach doesn't need async I/O
- Removed 2 crate dependencies (`ignore-files`,
`watchexec-filterer-ignore`)
## Current Behavior
When a project-level `tsconfig.json` (e.g., `apps/aurora/tsconfig.json`)
inherits `paths` via `extends` from `tsconfig.base.json` at the
workspace root and no explicit `baseUrl` is set, Nx incorrectly resolves
`./`-prefixed path mappings relative to the project tsconfig directory
instead of the workspace root where the paths were defined.
This causes errors when loading TypeScript config files (e.g.,
`rspack.config.ts`) that import workspace libraries using path aliases:
NX Cannot find module './libs/plugins/rspack/src'
`@swc-node/register`'s `readDefaultTsConfig` auto-sets `baseUrl` to
`dirname(tsConfigPath)` (the project directory) when not explicitly
configured, causing SWC to rewrite imports to incorrect relative paths
during transpilation.
## Expected Behavior
Path aliases defined in `tsconfig.base.json` (e.g.,
`"@trellis/plugins/rspack": ["./libs/plugins/rspack/src/index.ts"]`)
should resolve relative to the workspace root when no `baseUrl` is
configured.
This is needed so that when using `tsgo` and needing to prefix all paths
with `./` (no more `baseUrl` allowed) the paths are still resolved from
the right spot.
I tested this fix against our codebase on the branch I was trying to
switch to tsgo on and it seemed to work.
## Related Issue(s)
Fixes
https://discord.com/channels/1143497901675401286/1471627045694865581
## Current Behavior
CONTRIBUTING.md contains an outdated "How to Get Started Video" section
and references Stack Overflow for general questions.
## Expected Behavior
Remove outdated video section and point users to the Discord community
instead of Stack Overflow for general questions.
## Related Issue(s)
N/A
When switching from inline mode to full-screen TUI (or during window
resize), the PTY resize operation reparsed ALL raw terminal output
through a new vt100 parser synchronously on the event loop. For tasks
with large output, this caused a noticeable hang.
Add `resize_async()` which moves the expensive reparse to a background
thread using a snapshot-and-replay pattern:
1. Quick snapshot of raw output (brief read lock)
2. Expensive reparse on background thread (no locks held)
3. Quick swap with replay of any new output (brief write lock)
A generation counter prevents stale resizes from overwriting newer ones.
Also combine two separate O(n) scrollback processing calls in inline
mode into a single pass.
## Current Behavior
Copyright year shows 2017-2025 and README uses older tagline.
## Expected Behavior
Copyright reflects current year 2026 and README uses updated repository
description.
## Changes
- **LICENSE**: Updated copyright year from `2017-2025` to `2017-2026`
- **README.md**: Replaced heading and description
- New heading: "The Monorepo Platform that amplifies both developers and
AI agents. Nx optimizes your builds, scales your CI, and fixes failed
PRs automatically. Ship in half the time."
- Removed redundant description line below heading
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
> ## Update License and README
>
> Please make the following changes:
>
> 1. **Update LICENSE file**: Change the copyright year from `2017-2025`
to `2017-2026`
> - File: `LICENSE`
> - Line 3: Update `Copyright (c) 2017-2025 Narwhal Technologies Inc.`
to `Copyright (c) 2017-2026 Narwhal Technologies Inc.`
>
> 2. **Update README.md description**: Replace the current description
with the repository's official description
> - File: `README.md`
> - Line 22: Change the heading from `# Smart Monorepos · Fast Builds`
to `# The Monorepo Platform that amplifies both developers and AI
agents. Nx optimizes your builds, scales your CI, and fixes failed PRs
automatically. Ship in half the time.`
> - Line 24: Remove the current description line: `Get to green PRs in
half the time. Nx optimizes your builds, scales your CI, and fixes
failed PRs. Built for developers and AI agents.`
>
> The new README should have the repository description as the main
heading, followed immediately by the "Create a new Nx workspace with"
section.
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
*This pull request was created from Copilot chat.*
>
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: FrozenPandaz <8104246+FrozenPandaz@users.noreply.github.com>
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
## Current Behavior
`tui_logger::init_logger()` and `TuiTracingSubscriberLayer` are
initialized unconditionally in `initialize_logger()`. This spawns a
background thread, causing unnecessary allocation churn and increased
processing.
## Expected Behavior
`tui_logger` is only initialized when `NX_TUI=true`, avoiding the
background thread and allocation overhead for all non-TUI contexts.
## Current Behavior
We have ~1,657 redirect rules across `redirect-rules.js` and
`redirect-rules-docs-to-astro.js`, getting close to Netlify's limit and
we need room for more as the Astro migration continues.
## Expected Behavior
Reduced to **1,139 rules** (~31% reduction) by:
- Resolving duplicate/conflicting source paths across sections
- Flattening multi-hop redirect chains to point directly to final
destinations
- Consolidating groups of individual rules into wildcard patterns
(tutorials, CLI, helm, concepts, recipes, etc.)
- Removing old 2022-era sections (`schemaUrls`, `overviewUrls`,
`packagesIndexes`, `packagesDocuments`) whose destinations chain 3-5
hops deep and are long superseded by newer redirects
- made sure old links in nx code base still have redirects (will update
in future PR)
Build, tests, and internal link check all pass with no issues.
## Related Issue(s)
Fixes DOC-403
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: barbados-clemens <barbados-clemens@users.noreply.github.com>
## Current Behavior
When AI agents (Claude Code, Cursor, Windsurf, etc.) run `nx init`, the
command works but:
- Uses interactive prompts that AI agents can't handle
- Outputs human-readable text that AI agents must parse
- Doesn't provide structured progress or error information
## Expected Behavior
When `nx init` detects an AI agent (via environment variables like
`CLAUDE_CODE=1`), it should:
- Skip interactive prompts and use sensible defaults
- Output structured NDJSON for progress updates, success, and errors
- Include detailed context for AI agents to understand and fix issues
## Changes
This PR adds agentic mode to `nx init`:
### `nx init` Changes
- Detect AI agents via `isAiAgent()` native function
- Auto-defaults: `interactive=false`, `nxCloud=false`, auto-detect `.nx`
installation
- NDJSON output with `type: progress|success|error`
- Error logs written to `.nx/ai-errors/` with full context
- Cursor restoration escape sequence skipped for AI agents (prevents
NDJSON corruption)
### Output Format
```jsonl
{"type":"progress","step":"starting","message":"Initializing Nx..."}
{"type":"success","nxVersion":"22.5.0","projectsDetected":1,"pluginsInstalled":["@nx/vite"]}
```
Or on error:
```jsonl
{"type":"error","message":"Failed to install","code":"INSTALL_ERROR","errorLogPath":".nx/ai-errors/nx-init-error-2025-01-15T10-30-00.log"}
```
## Current Behavior
we pull from latest all the time even if the current version is already
latest
## Expected Behavior
we can skip this extra work sometimes
## Current Behavior
Pages proxied from Framer contain canonical URLs pointing to the Framer
domain (`ready-knowledge-238309.framer.app`), causing duplicate indexing
issues in search engines.
## Expected Behavior
Canonical URLs and other references in Framer-proxied pages now point to
`nx.dev`, ensuring proper SEO indexing.
### Implementation
Consolidated all Framer logic into a single Netlify edge function
(`rewrite-framer-urls.ts`) that:
1. Checks if the request path matches a Framer-proxied path (using
`FRAMER_REWRITES` env var)
2. Fetches directly from Framer (using `FRAMER_URL` env var)
3. Rewrites all Framer URLs to `nx.dev` in the HTML response (handles
`<link rel="canonical">`, `og:url`, etc.)
4. For non-Framer paths, passes through to Next.js
The edge function uses the `accept: ['text/html']` config to only run on
HTML requests, matching the pattern from `track-page-requests.ts` in
astro-docs.
The Next.js middleware has been removed since all Framer routing is now
handled by the edge function.
### Environment Variables
The edge function expects these env vars in Netlify (already added
previously):
- `NEXT_PUBLIC_FRAMER_URL`: e.g.,
`https://ready-knowledge-238309.framer.app`
- `NEXT_PUBLIC_FRAMER_REWRITES`: comma-separated list of paths, e.g.,
`/pricing,/enterprise`
## Demo
1. Go to https://deploy-preview-34445--nx-dev.netlify.app/
2. View source and look for canonical
3. See it is nx.dev not framer domain
<img width="1347" height="161" alt="image"
src="https://github.com/user-attachments/assets/d4a515da-e39f-41cb-a7b4-668fe0bedbbd"
/>
## Related Issue(s)
Closes CLOUD-4148
## Current Behavior
The `release` function returned by `createAPI` has a return type of
`Promise<NxReleaseVersionResult | number>`. The `| number` union member
is inaccurate since the function always returns
`NxReleaseVersionResult`, which can mislead consumers of the
programmatic API.
## Expected Behavior
The return type is narrowed to `Promise<NxReleaseVersionResult>`,
accurately reflecting what the function actually returns and giving API
consumers correct type information.
Co-authored-by: Andreas Hörnicke <andreas.hoernicke@contentful.com>
Blog post draft about the evolution from MCP tools to agent skills.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: MaxKless <MaxKless@users.noreply.github.com>
Co-authored-by: Juri Strumpflohner <juri.strumpflohner@gmail.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
## Current Behavior
`^` and `dependencies: true` only work for fileset inputs
## Expected Behavior
Adds support for inputs of the form `^{projectRoot}/**/*.ts` as
syntactic sugar for specifying a fileset input that should be collected
from dependency projects.
Previously, only named inputs could use the `^` prefix to include
dependencies (e.g., `^production`). Now filesets can also use this
syntax directly without needing to define a named input first.
Examples:
- `^{projectRoot}/**/*.ts` - include .ts files from all dependencies
- `^{workspaceRoot}/tools/**/*` - include workspace tools from
dependencies
- `{ fileset: '{projectRoot}/**/*.ts', dependencies: true }` - object
form
Detection is deterministic: if the string after `^` starts with
`{projectRoot}` or `{workspaceRoot}`, it's treated as a dependency
fileset; otherwise, it's treated as a named input reference.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
- In some `nx run-many` executions, terminal output can appear staggered
or visually misaligned instead of updating cleanly in place.
- For `run-many` cases that end up running a single task (especially
when TUI is not active), the spinner/status line can be rendered twice.
## Expected Behavior
- Dynamic terminal output updates should remain stable and aligned, with
clean in-place refreshes.
- Single-task `run-many` should display a single spinner/status line
with no duplicate rendering.
Add uncaughtException handler for ERR_USE_AFTER_CLOSE to prevent
ugly stack trace when pressing Ctrl+C during enquirer prompts
(Node 24 stricter readline behavior). Matches existing pattern
used in nx init and create-nx-workspace.
## Current Behavior
The publish workflow uses `sudo npm install -g npm@11.5.2` which was
added in #34409. This causes issues with OIDC token permissions in the
release pipeline since `sudo` runs as a different user context.
## Expected Behavior
The publish workflow should use `npm install -g npm@11.5.2` without
`sudo`, matching the standard approach used elsewhere and avoiding
permission context issues during release.
## Related Issue(s)
Reverts #34409
## Current Behavior
The publish workflow uses `addnab/docker-run-action@v3` which is based
on `docker:20.10` (Docker API 1.41). GitHub's `ubuntu-24.04` runners now
ship Docker Engine 28.x which requires minimum API version 1.44, causing
all 4 Linux Docker builds to fail:
```
docker: Error response from daemon: client version 1.41 is too old. Minimum supported API version is 1.44, please upgrade your client to a newer version.
```
Failed run: https://github.com/nrwl/nx/actions/runs/21961139962
## Expected Behavior
Linux Docker builds (x86_64-gnu, x86_64-musl, aarch64-gnu, aarch64-musl)
complete successfully using the host's modern Docker CLI.
https://github.com/nrwl/nx/actions/runs/21996143819
## Related Issue(s)
The `addnab/docker-run-action` repo is abandoned (last release March
2021, last commit May 2021) with open issues about this exact problem.
## Current Behavior
There's a chance that windows can falsely flag our native binaries as a
threat. We do not use the shellapi feature from winapi.
## Expected Behavior
We hope that removing this API doesn't break things, and the threat
messaging goes away
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #https://github.com/nrwl/nx/issues/34186
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Pressing `[1]` or `[2]` on a task that's already pinned to that slot
**focuses** the pane instead of unpinning it. This means there's no way
to unpin a single pane via keyboard — you can only nuke everything with
`[0]`.
This regression was introduced in #34175, which fixed a real problem:
pressing Enter on an already-pinned task would unpin it, leaving focus
on an invisible pane (a ghost pane — you're staring at nothing but the
TUI thinks you're looking at output). The fix was to make the "already
pinned to this pane" branch focus instead of unpin. The problem is that
`[1]`, `[2]`, and Enter all flowed through the same function
(`assign_current_task_to_pane`), so changing behavior for Enter changed
it for everyone. One lock got swapped out, and every door started
behaving the same way.
## Expected Behavior
`[1]` and `[2]` are **toggles**: press once to pin, press again to
unpin. Enter is a **display** action: show me this task's output and put
my cursor there — if it's already visible somewhere, just take me to it.
After this change:
- **`[1]` / `[2]`** on an already-pinned task → unpins it (pane
disappears, layout adjusts, focus returns to task list if no panes
remain)
- **Enter** on an already-pinned task → focuses whichever pane it's in
(even if it's in pane 2 and you'd normally expect pane 1)
- **Init** (startup restore of pinned tasks) → pure assignment, no
toggling, no focusing
## Approach
The old code had one function trying to serve three masters. Rather than
adding a flag parameter (`should_toggle: bool`) — which would just be a
boolean that lies about its intentions at every call site — the function
was split along the actual semantic boundaries:
| Function | Used by | "Already pinned here" behavior |
|---|---|---|
| `toggle_current_task_in_pane` | `[1]` / `[2]` keys | **Unpin** (toggle
off) |
| `assign_current_task_to_pane` | `init()` | No-op (task is where it
should be) |
| `display_and_focus_current_task_in_terminal_pane` | Enter key |
**Focus** the existing pane |
The shared logic — exiting spacebar mode, moving a task between panes,
fresh-pinning — lives in two small helpers (`exit_spacebar_and_pin`,
`move_or_pin_selection`) that both `toggle` and `assign` delegate to.
The only code that differs is the "what do we do when it's already
here?" branch, which is exactly the part that *should* differ.
**Why not keep one function with a mode parameter?** Because the three
behaviors aren't variations of the same action — they're genuinely
different user intents. A toggle is "I changed my mind." A focus is
"Take me there." An assignment is "Put this here." Encoding that as an
enum parameter just moves the branching somewhere less obvious and makes
the call sites harder to read. The function names now document the
intent at the point of use, and there's no shared state to accidentally
couple.
**Why does Enter check all panes, not just pane 0?** Because if you
pinned a task to pane 2 via `[2]` and then press Enter on it, the least
surprising thing is to jump to where it already lives — not to silently
duplicate it into pane 1 or ignore you. The task is already on screen;
Enter means "show me."
## Related Issue(s)
Fixes the regression introduced by #34175.
## Current Behavior
CNW (Create Nx Workspace) has A/B testing logic that randomly selects
between variants 0, 1, and 2 for the Nx Cloud connection flow. Each
variant shows different prompts and banners.
## Expected Behavior
Lock in variant 2 as the permanent behavior:
- **No cloud prompt** - users are not asked about Nx Cloud during
workspace creation
- **Deferred connection** - no `nxCloudId` is written to `nx.json` (uses
`skipCloudConnect: true`)
- **Variant 2 banner** - shows "Enable remote caching and automatic
fixes when CI fails" with a link to complete setup later
### Changes
- Simplified `ab-testing.ts` - removed caching, random selection;
`getFlowVariant()` always returns `'2'`
- `shouldShowCloudPrompt()` always returns `false`
- `determineNxCloudV2()` returns `'github'` with `skipCloudConnect:
true` for deferred connection
- Removed variant 1 banner logic from `messages.ts`
- Updated tests to reflect the locked-in behavior
## Demo
https://www.loom.com/share/7f688eed6052428cbe91dd9db837cbbd
## Related Issue(s)
Closes CLOUD-4255
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Simplified form targeting by replacing the global `reactHubspotForm` ID
with a deterministic `targetId` that incorporates portal, form, and
calendly IDs. This improves scalability and avoids potential ID
collisions.
## Current Behavior
When banner content changes in Framer, the nx-docs and nx-dev sites need
to be manually redeployed to pick up the new content.
## Expected Behavior
A scheduled workflow monitors the banner URL every 15 minutes and
automatically triggers Netlify production deploys when content changes.
## How it works
1. Fetches `BANNER_URL` content (from repository variable)
2. Computes SHA256 hash
3. Compares to cached hash from previous run
4. If different → triggers both Netlify deploys, updates cache
5. If unchanged → no-op
## Required Setup
1. **Repository variable** (`Settings → Secrets and variables → Actions
→ Variables`):
- `BANNER_URL` = Framer banner API URL
2. **Repository secret** (`Settings → Secrets and variables → Actions →
Secrets`):
- `NETLIFY_AUTH_TOKEN` = Netlify personal access token
## Related Issue(s)
Fixes DOC-405
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
Negation patterns are ignored in plugin configuration for the `include`
and `exclude` properties.
## Expected Behavior
- Negation patterns should work in the same way that they do for other
`include`/`exclude` configurations
**Example: Excluding all e2e projects except one**
```jsonc
// nx.json
{
"plugins": [
{
"plugin": "@nx/jest/plugin",
"exclude": ["**/*-e2e/**/*", "!**/toolkit-workspace-e2e/**/*"],
},
],
}
```
This will exclude all e2e projects except `toolkit-workspace-e2e`.
**Example: Including packages except legacy ones**
```jsonc
// nx.json
{
"plugins": [
{
"plugin": "@nx/vite/plugin",
"include": ["packages/**/*", "!packages/legacy/**/*"],
},
],
}
```
**How negation patterns work:**
- Patterns are processed in order from first to last
- A pattern starting with `!` removes files from the match set
- A pattern without `!` adds files to the match set
- The last matching pattern determines if a file is included
- If the first pattern is a negation, all files are matched initially
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
## Current Behavior
There is currently no plugin.md file for Gradle.
Other plugin.md files can be improved
## Expected Behavior
Add plugin.md file for Gradle to aid with verification with Agents.
Add plugin.md file for Vite for workspaces that have not migrated to
@nx/vitest.
## Related Issue(s)
CLOSES NXC-3843
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Coly010 <Coly010@users.noreply.github.com>
## Current Behavior
The Playwright executor does not support configuring a custom cache
directory for Playwright's internal cache (browser binaries, etc.).
Users who need to control where Playwright stores its cache, for example
in CI environments with specific disk constraints like not being able to
DTE tasks or shared caching setups, have no way to set this through the
executor configuration.
## Expected Behavior
A new `cacheDir` option is available on the Playwright executor. When
provided, it sets the `PWTEST_CACHE_DIR` environment variable on the
forked Playwright process, allowing users to control where Playwright
stores its internal cache.
```json
{
"targets": {
"e2e": {
"executor": "@nx/playwright:playwright",
"options": {
"cacheDir": "/tmp/playwright-cache"
}
}
}
}
```
## Related Issue(s)
Replaces #34397
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Current Behavior
The SECURITY.md file does not clarify what types of reports should be
sent to the security email, leading to reports about outdated
dependencies and vulnerability scanner output.
## Expected Behavior
The file now clarifies that the security email is for demonstrable,
verified vulnerabilities in the Nx codebase itself, not for:
- Outdated dependency reports
- Dependencies with CVEs that don't directly affect Nx
- General vulnerability scanner output
## Related Issue(s)
Fixes NXC-3898
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Current Behavior
The `npm install -g npm@11.5.2` step in the publish workflow fails with
`EACCES: permission denied, mkdir '/usr/local/share/man/man5'` on newer
GitHub Actions runner images.
## Expected Behavior
The global npm install step completes successfully regardless of runner
image permissions on `/usr/local/share/man/`.
## Related Issue(s)
This is a known issue with GitHub Actions runners:
https://github.com/actions/runner-images/issues/9644
Add isSandbox() utility that checks for sandbox environment variables
(SANDBOX_RUNTIME, GEMINI_SANDBOX, CODEX_SANDBOX, CURSOR_SANDBOX) and
use it to disable the daemon and plugin isolation in sandbox
environments.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: meeroslav <meeroslav@users.noreply.github.com>
## Current Behavior
Running ai agents in sandbox mode causes issues with Nx's daemon and
plugin isolation
## Expected Behavior
Running ai agents in sandbox mode should work
## Related Issue(s)
CLOSES NXA-828
## Current Behavior
We do not include NxVersion when creating short urls.
## Expected Behavior
Include NxVersion when creating short urls.
## Related Issue(s)
CLOSES NXC-3879
When cache outputs include both glob patterns and directory patterns
containing symlinks, the cache restore fails with EEXIST (os error 17).
This happens because `fs_extra::remove_items` silently skips dangling
symlinks (since `is_dir()`/`is_file()` follow links and return false),
leaving stale symlinks that cause `symlink()` to fail.
The fix makes symlink creation idempotent by checking for and removing
any existing symlink at the destination before creating a new one, using
`symlink_metadata()` which correctly detects dangling symlinks.
Fixes#34013
## Current Behavior
The Maven plugin's `createNodes` and `createDependencies` functions both
independently compute a hash of all pom.xml directories, then use that
hash to look up cached Maven analysis data from disk. When Maven
projects have `<includes>` or `<excludes>` in their plugin config, the
hash can differ between the two calls, causing `createDependencies` to
fail to find the data that `createNodes` stored.
## Expected Behavior
`createDependencies` reliably receives the Maven analysis data from
`createNodes` regardless of hash differences, by reading it from a
module-level variable instead of re-hashing and looking it up from disk.
This matches the pattern already used by the Gradle plugin
(`getCurrentGradleReport`).
## Related Issue(s)
## Current Behavior
Runtime cache keys could be nondeterministic because the order of
environment variables varied, leading to inconsistent cache hits across
runs.
## Expected Behavior
Runtime cache keys are deterministic regardless of the insertion order
of env variables, improving cache stability.
## Current Behavior
Cycles in the task graph could remove unrelated `continuousDependencies`
when the cycle exists only in `dependencies`, leading to missing
continuous task edges.
## Expected Behavior
Cycle removal only removes the specific cyclic edge from the list where
it appears, preserving unrelated continuous dependencies.
## Current Behavior
When running Nx tasks in CI environments (e.g., Buildkite) where the
host's /tmp is mounted to containers, intermittent EADDRINUSE errors
occur in PseudoIPCServer.init(). This happens because:
1. PseudoIPCServer doesn't clean up its Unix socket file before calling
listen()
2. ForkedProcessTaskRunner.createPseudoTerminal() instantiates
PseudoTerminal directly instead of using the createPseudoTerminal()
helper, bypassing shutdown callback registration
When a new container starts with the same PID as a previous run (PID
recycling), it generates the same socket path and hits EADDRINUSE
because the stale socket file still exists.
## Expected Behavior
No EADDRINUSE errors should occur. The PseudoIPCServer should
defensively remove any stale socket file before attempting to listen,
similar to how the daemon server handles this.
## Related Issue(s)
Fixes#34233
Streamlined analytics tracking by removing Cookiebot and direct GA
(gtag.js) integrations. Consolidated event logging through GTM's
dataLayer for consistency and maintenance simplicity.
## Current Behavior
Batch IDs are generated in two places: the task scheduler uses an
incremental counter (`executorName N`) while the forked process task
runner generates its own using the process PID (`executorName-pid`).
This means the batch ID registered in metrics doesn't match the one used
everywhere else.
## Expected Behavior
Batch IDs are only created by the task scheduler. The forked process
task runner uses the scheduler-assigned ID to ensure consistency across
the system.
## Current Behavior
right now if users modify their mcp params like `--minimal`, we will
override them on `configure-ai-agents`
## Expected Behavior
We want to bring users up to latest without overriding their valid
configurations
## Current Behavior
The Bun package manager config uses `--frozen-lockfile` for
`updateLockFile`:
```typescript
updateLockFile: 'bun install --frozen-lockfile',
```
However, `--frozen-lockfile` **prevents** changes to the lockfile,
causing `nx release` to fail when trying to update the lockfile after
version bumps.
## Expected Behavior
Use `--lockfile-only` which generates/updates the lockfile without
installing dependencies:
```typescript
updateLockFile: 'bun install --lockfile-only',
```
This is consistent with other package managers:
- npm: `npm install --package-lock-only`
- pnpm: `pnpm install --lockfile-only`
- yarn berry: `yarn install --mode update-lockfile`
## Background
When Bun support was added in PR #22602 (April 2024), `--lockfile-only`
didn't exist in Bun. Bun has since added this flag.
Closes#34344
Co-authored-by: Kai Gritun <kai@kaigritun.com>
## Current Behavior
Several GitHub Actions workflows hardcode pnpm version `10.11.1`, while
`package.json` specifies `pnpm@10.28.2` in the `packageManager` field.
This causes CI failures with:
```
Error: Multiple versions of pnpm specified:
- version 10.11.1 in the GitHub Action config with the key "version"
- version pnpm@10.28.2 in the package.json with the key "packageManager"
```
## Expected Behavior
All pnpm version references across CI workflows should match the
`packageManager` field in `package.json` (`10.28.2`).
## Related Issue(s)
N/A — Fixing CI breakage from version mismatch.
## Changes
Updated pnpm version from `10.11.1` → `10.28.2` in:
- `.github/workflows/npm-audit.yml` — `pnpm/action-setup` version
- `.github/workflows/publish.yml` — `PNPM_VERSION` env var and FreeBSD
install
- `.github/workflows/issue-notifier.yml` — `pnpm/action-setup` version
- `.github/workflows/generate-embeddings.yml` — `pnpm/action-setup`
version
## Current Behavior
The Windows build (`aarch64-pc-windows-msvc`) fails to compile with:
```
error[E0433]: failed to resolve: use of undeclared type `FileType`
--> packages\nx\src\native\watch\types.rs:128:55
```
The `FileType` type is used inside a `#[cfg(target_os = "windows")]`
block but was not imported.
## Expected Behavior
The Windows build compiles successfully. The `FileType` import is scoped
inside the `#[cfg(target_os = "windows")]` block (matching the existing
pattern in the macOS block) so there are no unused imports on any
platform.
## Related Issue(s)
N/A — build breakage discovered during CI publish workflow.
## Current Behavior
The daemon's file watcher uses watchexec 3.0.1 which hardcodes
`RecursiveMode::Recursive` when registering inotify watches. This means
**every** directory gets an inotify watch — including all of
`node_modules`, `.git`, and other ignored trees.
On a typical workspace with a large `node_modules`, this can consume
thousands of inotify watches, eating kernel memory and CPU. The
`WatchFilterer` only filters **events** after watches are already
registered — the watches themselves are never prevented.
## Expected Behavior
Only non-ignored directories (workspace source code) get inotify
watches. Ignored directories like `node_modules`, `.git`, `.nx/cache`,
`.nx/workspace-data`, and `.yarn/cache` are skipped entirely at the
watch registration level.
This dramatically reduces:
- **inotify watch count** (from thousands to hundreds)
- **Memory usage** (each watch consumes kernel memory)
- **CPU overhead** (fewer watches = less kernel bookkeeping)
### How it works
- Upgraded watchexec 3.0.1 → 8.0.1 which supports
`WatchedPath::non_recursive()`
- Added `create_watch_walker()` using `ignore::WalkBuilder` (same
pattern as `walker.rs`) to enumerate only non-ignored directories
- Each directory is watched with `NonRecursive` mode — like putting
security cameras only in the rooms you care about instead of every room
in the building
- New directories created at runtime are dynamically added to the watch
set via the `on_action` handler
- Event-level filtering via `WatchFilterer` is unchanged — same behavior
for gitignore/nxignore patterns
### macOS Support for Dynamic Directory Registration
The initial implementation worked on Linux and Windows but failed tests
on macOS because macOS FSEvents doesn't always provide the same
`FileEventKind` tags as Linux inotify or Windows ReadDirectoryChangesW.
**Three changes to support macOS:**
1. **watcher.rs**: On macOS, check all events for directory creation
(not just events with specific FileEventKind tags) and verify via
filesystem
2. **types.rs**: Filter directory events from JavaScript callbacks on
macOS (similar to Windows behavior)
3. **watch_filterer.rs**: Allow macOS directory events (`Create(Folder)`
and `Modify(Metadata)`) through the filter so the action handler can
register them
All changes use `#[cfg(target_os = "macos")]` for compile-time
conditional compilation, so Linux/Windows behavior is completely
unchanged and there's zero runtime overhead.
### Additional notes
- Pinned `serde` to `<1.0.220` because serde 1.0.220+ moved `__private`
to `serde_core`, breaking `swc_common 0.31.22`
- No TypeScript changes — the napi interface is identical
- `watch_filterer.rs`, `types.rs`, `utils.rs` required no changes (APIs
are compatible)
## Related Issue(s)
Fixes https://github.com/nrwl/nx/issues/33781
Fixes https://github.com/nrwl/nx-console/issues/2468
<!-- No specific issue linked yet -->
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Re-enabling tests and putting back kotlin e2e tests for gradle.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
Flaky task detection warns about all tasks that have different exit
codes for the same hash, including non-cacheable tasks. This is
misleading because the flaky task warning message points users to Nx
Cloud's flaky task retry feature, which is only relevant for cached
tasks.
## Expected Behavior
Flaky task detection should only consider tasks where `task.cache ===
true`, making the warning more meaningful and avoiding noise for
non-cacheable tasks.
## Related Issue(s)
N/A - Internal improvement
## Changes Made
###
`packages/nx/src/tasks-runner/life-cycles/task-history-life-cycle.ts`
1. Added `cacheable: boolean` to the `TaskRun` interface
2. In `endTasks`, now tracks `cacheable: taskResult.task.cache === true`
for each task
3. In `endCommand`, filters to only check flaky tasks among cacheable
tasks
###
`packages/nx/src/tasks-runner/life-cycles/task-history-life-cycle-old.ts`
1. Added `cacheableHashes: Set<string>` to track which task hashes are
cacheable
2. In `endTasks`, tracks cacheable tasks by adding their hash to the set
3. In `endCommand`, only checks for flaky tasks among cacheable task
hashes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
A new version compatibility table is added to help users understand
which versions of the Gradle plugin work with which versions of the Nx
plugin. The targetNamePrefix configuration option is now documented with
an explanation of its use case in polyglot workspaces where target name
collisions may occur.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Gradle plugin to 0.1.12
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
- No way to run the batch executor in debug mode
- Any flags passed into the nx gradle batch command get forwarded into
the `gradlew` command.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
- Allow for an env variable to be set for debug flags so that the batch
runner jar can be run with a debugger hooked in.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #NXC-3797
---------
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
## Current Behavior
In some resize/background scenarios, the TUI could crash while rendering
output panes, displaying a "Scrollbar area is empty" message.
## Expected Behavior
The TUI remains stable during resizes and backgrounding. Output panes no
longer crash when a scrollbar would render in an invalid area.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
When Nx processes Gradle subprojects, task dependencies reference the
wrong project names. For a subproject structure like :app or :lib:core,
the generated task dependencies use only the simple project name (app or
core) instead of the full build tree path (:app or :lib:core). This
causes dependent tasks to be generated with incorrect project
references, breaking the task graph for multi-project Gradle builds.
## Expected Behavior
Task dependencies should use the full Gradle build tree path for
subprojects. When a task in :app depends on a task in :lib, the
dependency should be correctly referenced as :lib:taskName.
The fix introduces a getNxProjectName() utility function that correctly
resolves the Nx project name based on the Gradle project's
buildTreePath, and applies it consistently across all dependency
resolution logic in ProjectUtils.kt and
TaskUtils.kt. New tests verify the fix works for both single and nested
subproject structures.
Also removed --rerun-tasks from the batch and non batch runners, we
found that during parallel task executions with the non batch runner,
the flag would cause cache conflicts that would fail tasks.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
When a task has outputs at different path depths, some outputs may not
be tracked. This causes:
- Deleted output files not being detected
- Cache restoration being skipped with message "existing outputs match
the cache, left as is"
- Files not being restored even though they exist in cache
## Expected Behavior
All task outputs are tracked regardless of their path depth, ensuring:
- Deleted outputs are correctly detected
- Cache restoration happens when outputs are missing
## Current Behavior
When a task output directory contains a nested `.gitignore` that hides
its contents, Nx can treat the outputs as already present and skip
restoring them from cache. This can result in generated files being
missing from disk, even though the cache entry is valid.
## Expected Behavior
Nx should restore cached outputs regardless of ignore rules inside the
output directory.
## Related Issue(s)
Fixes#32620
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Disabling Gradle e2e tests until foojay toolchain service back online.
Ensures that gradle within the Nx repo uses mise to download java
toolchain, but gradle workspaces within the e2e environments download
their own toolchain.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #Q-175
AI agents are detected via environment variables (CLAUDECODE, OPENCODE)
and receive NDJSON streaming output, non-interactive mode, structured
JSON results with explicit GitHub setup instructions.
Related NXC-3628
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Add documentation for batch mode. Remove references to removed custom
overrides for intTest.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
The only way to download the cloud client is to run a specific cloud
command or a task with cloud configured.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
A new command is added where we only download the cloud client with:
```
nx download-cloud-client
```
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
add guide to clarify various ways to bundle a node app for different
bundlers
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: barbados-clemens <barbados-clemens@users.noreply.github.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
## Current Behavior
In the TUI, when any standalone task finishes,
`handle_standalone_task_finished` unconditionally switches the user's
selection to another in-progress task — even if the finished task wasn't
the one the user had selected. This causes the selection to jump
unexpectedly while the user is watching a different task.
## Expected Behavior
Selection should only change when the task the user is actively viewing
finishes. If an unrelated background task finishes, the user's selection
should remain on whatever they chose.
## Related Issue(s)
N/A — discovered during development testing.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
NX_NATIVE_LOGGING is hardcoded and can't be customized on the daemon
server
## Expected Behavior
Log settings can be customized by changing them in the env of the first
command to spawn the daemon
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
we were including native binaries in the final netlify function build
for nextjs which was fine until we reach the limit of 250mb causing a
failure to upload the function (AWS imposed lambda limit)
Now we strip out any deps we know we don't need for the app which are
dev deps and not runtime required.
## Current Behavior
- Canary releases calculate their base version by incrementing the minor
of `nx@latest`, or using `nx@next` major when majors differ
- PR releases always use `0.0.0` as their base version (e.g.
`0.0.0-pr-1234-abc1234`)
- This means canary and PR versions don't clearly relate to the current
beta release line
## Expected Behavior
- Both canary and PR releases derive their base version directly from
`nx@next`
- If next is `22.5.0-beta.5`, then:
- Canary: `22.5.0-canary.20260204-abc1234`
- PR: `22.5.0-pr.1234.abc1234`
- All prerelease channels now share the same base version, making it
clear which release line they belong to
## Related Issue(s)
N/A - internal improvement to release infrastructure
## Current Behavior
The Maven plugin version is `0.0.12` across all pom.xml files and the
versions.ts constant. The `bump-maven-version` generator does not update
the `batch-runner-adapters` pom files, causing version mismatches.
## Expected Behavior
The Maven plugin version is bumped to `0.0.13` in **all** pom.xml files
(including batch-runner-adapters), with a migration created for users
upgrading to Nx `22.5.0-beta.4`. The bump generator now includes the
batch-runner-adapters pom files so future bumps won't miss them.
### Changes
- Updated version from `0.0.12` to `0.0.13` in all pom.xml files (root,
maven, maven-plugin, shared, batch-runner, batch-runner-adapters,
maven3-adapter, maven4-adapter)
- Updated `mavenPluginVersion` constant in
`packages/maven/src/utils/versions.ts`
- Added `update-0-0-13` migration entry in
`packages/maven/migrations.json` targeting Nx `22.5.0-beta.4`
- Created migration file
`packages/maven/src/migrations/0-0-13/update-pom-xml-version.ts`
- Fixed `bump-maven-version` generator to include
`batch-runner-adapters` pom files
## Current Behavior
The FreeBSD build in CI can fail silently or with unclear error messages
when:
- Disk space runs low during the build process
- The build command fails without proper error propagation
- Unnecessary files consume valuable disk space
## Expected Behavior
With these changes:
- Additional disk space is freed by removing docs/astro-docs/nx-dev
directories before building
- Build exit codes are properly captured and propagated
- Disk usage is logged after the build completes for debugging purposes
- Build failures are clearly reported with explicit error messages
This improves reliability and makes it easier to diagnose issues when
they occur.
## Related Issue(s)
<!-- No specific issue, general CI improvement -->
We're getting requests to `favicon.svg.md` that are being tracked,
ignore these. Also for the server page views, we should only count them
if `text/html` is in the accept header. Browsers will send these, and AI
agents, curl, etc. do not. This allows us to compare browser traffic vs
AI/curl traffic more accurately.
## Current Behavior
When a client disconnects while the daemon is writing a response, a
`socket.write` call triggers an EPIPE error. The old error handler used
`console.error`, which caused the error to propagate through
`respondWithErrorAndExit` and crash the daemon process via
`process.exit(1)`. The client would then see an `internalDaemonError`
and permanently disable the daemon via `markDaemonAsDisabled`, requiring
`nx reset` to recover.
Additionally, disconnected sockets were not cleaned up from the file
watcher and project graph listener registries on socket error events,
only on `close` events. This left a window where the daemon could
attempt to write to dead sockets during notifications.
## Expected Behavior
When a client disconnects mid-response:
- The `socket.write` callback logs the error gracefully via
`serverLogger` instead of `console.error`
- The daemon process stays alive and continues serving other clients
- The `socket.on('error')` handler cleans up registered file watcher and
project graph listener sockets immediately, matching the existing
`close` handler behavior
- The daemon is never permanently disabled due to EPIPE errors
## Related Issue(s)
<!-- No linked issue -->
### Current Behavior
The nx configure-ai-agents command output is minimal - just "AI agents
set up successfully" with a
bullet list of agent names. Users don't understand what was actually
configured (plugin vs MCP,
skills, which files were created/modified).
### Expected Behavior
Clearer feedback about what gets configured for each agent:
Selection prompt improvements:
- Agents needing updates show (update available) tag
- Footer always shows result state: what will be configured
- Agent-specific descriptions (e.g., "Installs Nx plugin (MCP + skills +
agents). Updates
CLAUDE.md.")
Post-configuration output:
- Compact summary per agent showing what was set up
- Example: Claude Code: Nx plugin (MCP + skills + agents) + CLAUDE.md
Claude .mcp.json cleanup:
- When configuring Claude, removes nx-mcp from .mcp.json since it's now
handled by the plugin
- Deletes the file entirely if nx-mcp was the only entry
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
This PR fixes two issues:
1. When the README changes are amended, there's an edge case where we
don't have a commit to amend (e.g. `--skipGit`), and this fails the
entire CNW flow.
2. When user opts out of Cloud, we strip the entire `<!-- BEGIN:
nx-cloud -->` block in README rather than just the comments and leaving
the content.
Closes NXC-3812
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Current Behavior
There's not an easy to use service to track PIDs being registered to nx
tasks
## Expected Behavior
There's a service to track this stuff
## Related Issue(s)
## Current Behavior
The `getNpmPackageVersion` function in
`packages/workspace/src/generators/utils/get-npm-package-version.ts`
uses `execSync` with direct string interpolation of the `packageName`
parameter. When a user runs `create-nx-workspace` with a custom
`--preset` value that doesn't match a known preset, the value flows
unsanitized into a shell command:
```js
execSync(`npm view ${packageName}... version --json`)
```
This allows arbitrary command execution via shell metacharacters (e.g.,
`--preset='pkg$(malicious command)'`).
## Expected Behavior
User-supplied package names are validated against a strict npm package
name regex before being passed to any shell command. The function now
uses `execFileSync` with an args array instead of `execSync` with string
interpolation, providing defense in depth:
1. **Input validation** — rejects anything that isn't a valid npm
package name
2. **Safe execution** — arguments are passed as an array so Node.js
handles escaping, rather than concatenating into a raw shell string
## Current Behavior
Given a project name like `:foo`, you can run tasks like `nx test foo`
(note `foo` vs `:foo`), but passing --help throws an error
## Expected Behavior
`--help` works the same with the shortname vs full name
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
- `pom.xml` is only included as an input when a mojo uses default inputs
- Mojos with specific input configurations (like
`maven-compiler-plugin:compile`) don't get `pom.xml` in their inputs
- Parent `pom.xml` files aren't tracked as inputs
This can lead to stale cache hits when:
1. `pom.xml` changes but a mojo has specific input config
2. A parent `pom.xml` changes (affecting inherited properties,
dependency versions, plugin config)
## Expected Behavior
- Every target should include its own `pom.xml` as an input
- Every target should include ancestor `pom.xml` files (within the
workspace) as inputs
- Cache should invalidate when any relevant `pom.xml` changes
## Related Issue(s)
N/A - discovered during code review
## Changes
- **CacheConfig.kt**: Removed `pom.xml` from `defaultInputs` (now always
added explicitly)
- **MojoAnalyzer.kt**: Added `workspaceRoot` parameter and logic to walk
up the parent chain, adding all in-workspace ancestor `pom.xml` files as
inputs
- **NxProjectAnalyzerMojo.kt**: Pass `workspaceRoot` to `MojoAnalyzer`
## Current Behavior
When the daemon dies while processing a request, the reconnect logic
adds the retry back to the promise-based queue. However, the original
request is still blocked in the queue waiting for a response that will
never come (the socket is dead). This creates a deadlock:
1. Original request (`fn1`) is blocked awaiting a promise that will
never resolve
2. Retry request (`fn2`) is queued but can't execute until `fn1`
completes
3. `fn1` can't complete because it's waiting on the dead socket
## Expected Behavior
When reconnecting after daemon death, the retry should resolve the
pending promise that the original queue entry is waiting on, allowing
the queue to proceed normally.
## Related Issue(s)
<!-- No specific issue, discovered during development -->
## Solution
Instead of re-queuing the retry through `sendToDaemonViaQueue` (which
adds to the end of the queue), we now call `sendMessageToDaemon`
directly. This resolves the pending promise that `fn1` is waiting on,
allowing it to complete naturally and the queue to proceed.
Also removed the now-unused `decrementQueueCounter` method from
`PromisedBasedQueue`.
## Current Behavior
Only markdown and text file requests are tracked server-side via the
`track-asset-requests` edge function. HTML page views are not tracked on
the server, missing requests from AI tools and curl.
## Expected Behavior
Track all doc page views server-side with a new edge function that:
- Sends `server_page_view` events to GA4 with `content_type` param to
differentiate HTML/markdown/text
- Uses Netlify's `excludedPath` config for efficient path filtering
(zero compute for excluded paths)
- Skips non-HTML requests via Accept header check
### Changes
| File | Change |
|------|--------|
| `track-page-requests.ts` | **NEW** - Edge function for HTML page view
tracking on `/docs/*` |
| `track-asset-requests.ts` | Changed event name to `server_page_view`,
added `content_type` param |
| `add-link-headers.ts` | Refactored to use `excludedPath` config
instead of runtime path checks |
| `netlify.toml` | Added edge function declaration for
`track-page-requests` |
### GA Event Schema
```javascript
{
name: 'server_page_view',
params: {
content_type: 'text/html' | 'text/markdown' | 'text/plain',
file_extension: '.html' | '.md' | '.txt',
is_ai_tool: 'true' | 'false',
// ... other params
}
}
```
## Other Notes
This PR also removes the edge function entries from `netlify.toml` since
it's auto detected from `astro-docs/netlify/edge-functions`. This makes
all the configuration in the actual `.ts` file, not duplicated in the
`netlify.toml` file.
## Related Issue(s)
Closes DOC-395
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
The Gradle executor accepts a taskName option that *should not* contain
multiple space-separated tasks. When multiple tasks are provided, the
batch runner misinterprets the space-separated string as containing
project names rather than treating it as a single task argument, leading
to execution errors and confusion.
This only occurs if the taskName is manually overridden and should not
occur when task names are generated by the project graph plugin.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
The Gradle executor now validates that taskName contains only a single
task without spaces. If multiple tasks are passed, it throws a clear
error message: "Task '[taskName]' contains spaces. Only a single Gradle
task is allowed per executor invocation." This prevents the batch runner
from misinterpreting the task name and provides immediate feedback to
users about the correct usage.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
When running Gradle tasks in batch mode with atomized targets, if the
same task appears multiple times in the output (which happens when tasks
are atomized and executed separately), the batch runner only captures
the output from the last execution. Previous executions' output gets
overwritten because the splitOutputPerTask function replaces the entire
output for each task name it encounters.
## Expected Behavior
All output from a task should be preserved, even when the task appears
multiple times in the batch output. When the same task name is
encountered multiple times, the outputs should be concatenated rather
than replaced, ensuring developers can see the complete execution
history for atomized targets.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Plan for fixing .NET incremental builds documentation
- [x] Update the "Target dependencies" section to remove `restore` from
the `dependsOn` array
- [x] Add explanation about why `restore` cannot be run through Nx for
solutions using custom frameworks
- [x] Improve explanation clarity based on review feedback
- [x] Verify the "Target configuration" section is consistent with the
changes
- [x] Complete code review and address feedback
- [x] Run security checks (no issues found)
- [x] Address PR feedback: Use JSX `<Aside>` component with import
statement instead of Markdoc tag
## Summary
Successfully fixed the documentation issue in the .NET incremental
builds guide. The changes made:
1. **Removed `restore` from the build target's `dependsOn` array** - The
documentation now correctly shows `"dependsOn": ["^build"]` instead of
`"dependsOn": ["restore", "^build"]`, matching the actual implementation
in the plugin code.
2. **Added a clear explanation** - Included an aside box explaining why
`restore` is not in the `dependsOn` array: because Nx requires NuGet
package restoration to be completed before running any tasks, and
including it would create a circular dependency.
3. **Verified consistency** - Checked that the "Target configuration"
section already showed the correct configuration, ensuring all
documentation is now consistent.
4. **Used correct Starlight component** - Changed from Markdoc `{% aside
%}` tag to JSX `<Aside>` component with proper import statement per
Starlight documentation standards.
The changes align with the actual implementation in
`packages/dotnet/analyzer/Utilities/TargetBuilder.Build.cs` where the
build target's `dependsOn` is set to `[$"^{targetName}"]` (line 56).
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
>
> ----
>
> *This section details on the original issue you should resolve*
>
> <issue_title>docs(dotnet): implied conflict in dependsOn of inferred
build task</issue_title>
> <issue_description>### Documentation issue
>
> <!-- (Update "[ ]" to "[x]" to check a box) -->
>
> - [ ] Reporting a typo
> - [ ] Reporting a documentation bug
> - [ ] Documentation improvement
> - [x] Documentation feedback
>
> <!--
> If your issue is not regarding the documentation, please choose an
issue type:
> https://github.com/nrwl/nx/issues/new/choose
> -->
>
> ### Is there a specific documentation page you are reporting?
>
>
https://nx.dev/docs/technologies/dotnet/guides/incremental-builds#target-dependencies
>
> ### Additional context or description
>
> The code sample provides in this doc includes `"dependsOn":
["restore", "^build"]`, but the automatically inferred `build` target
from this plugin does not actually include the `restore` target in the
dependsOn array. I assume this is by design? The docs seem to confuse it
a bit.
> </issue_description>
>
> <agent_instructions>Remove the `restore` target from the dependsOn
block. Add a small explanation that we can't run restore through Nx
because Nx requires restore to have been completed prior to running
tasks if the solution uses a custom framework</agent_instructions>
>
> ## Comments on the Issue (you are @copilot in this section)
>
> <comments>
> </comments>
>
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
- Fixesnrwl/nx#34150
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AgentEnder <6933928+AgentEnder@users.noreply.github.com>
## Current Behavior
When socket data chunks split a multibyte UTF-8 character (e.g., CJK
characters like Korean, Chinese, Japanese) at an arbitrary byte
boundary, `Buffer.toString()` decodes incomplete byte sequences as
replacement characters (�), causing message corruption.
This can occur when:
- File paths contain non-ASCII characters
- Project names include multibyte characters
- Any JSON message contains international text
## Expected Behavior
Multibyte UTF-8 characters should be properly decoded even when split
across multiple socket data chunks. The fix uses Node.js `StringDecoder`
which buffers incomplete multibyte sequences until the remaining bytes
arrive.
## Related Issue(s)
Fixes socket message corruption for paths/names containing multibyte
characters.
- fix(nx-dev): always link headers regardless of mdoc or markdown
content source (generated vs static file)
- fix(nx-dev): make option/property columns in table linkable
- the table column header is matched on `options`, `option`,
`properties`, and property` (case insensitive)
https://github.com/user-attachments/assets/7250b9d5-1030-4ebc-9e21-0a05f295bbf5
Note bc mdoc and `renderMarkdown` go through 2 different rendering
pipelines, this logic must bc within the markdoc config and rehype
(markdown) processing logic. tried to shared logic where I could
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Current Behavior
After completing the CNW (Create Nx Workspace) flow with Nx Cloud, users
see a plain text completion message with a link to finish setup.
## Expected Behavior
Users now see one of four completion message variants controlled by
`NX_CNW_FLOW_VARIANT`:
- **Variant 0**: Plain link (control) - always used for enterprise URLs
- **Variant 1**: "Try the full Nx platform" decorative ASCII banner
- **Variant 2**: "Unlock 70% faster CI" decorative ASCII banner
- **Variant 3**: "Reclaim your team's focus" decorative ASCII banner
Key changes:
- Added enterprise URL detection (non-standard Nx Cloud URLs always get
variant 0)
- Locked the cloud prompt to always show "Try the full Nx platform?" (no
longer varies by flow variant)
- Flow variant now only affects the completion banner, not the prompt
- Added `snapshot.nx.app` to standard Nx Cloud hosts
- Removed variant 2 auto-connect behavior (all variants now prompt)
## Screenshots
Variant 0:
<img width="1392" height="1065" alt="variant0"
src="https://github.com/user-attachments/assets/0b18686e-1481-4fc0-995e-1577052887ff"
/>
Variant 1:
<img width="1392" height="1065" alt="variant1"
src="https://github.com/user-attachments/assets/e5909e2e-e1d8-4d04-9721-ba6186d06891"
/>
Variant 2:
<img width="1392" height="1065" alt="variant2"
src="https://github.com/user-attachments/assets/7e9f819f-e3c0-44d7-9760-0cab1d5dd9ac"
/>
Variant 3:
<img width="1392" height="1065" alt="variant3"
src="https://github.com/user-attachments/assets/83f0499f-d807-4dc8-9390-c7eec93590a9"
/>
## Related Issue(s)
Closes CLOUD-4147
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Enable Gradle executor to run tasks in batch mode in CI.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
We're showing over 8 GB of memory usage on Netlify, and 11+ GB on
Agents. Let's test out a few ways to reduce the memory footprint.
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Preload vitest/node ESM module early in
buildViteTargets/buildVitestTargets
functions before parallel processing occurs. This prevents the
ERR_INTERNAL_ASSERTION error that occurs when multiple vitest.config
files
are processed in parallel on Node 24+.
Fixes#34028Fixes#33091
## Current Behavior
The `runCommandUntil` e2e utility function waits indefinitely for the
expected output to appear. If the output never appears (e.g., server
fails to start, different output format, port conflict), the test hangs
forever, causing CI jobs to run for hours before being killed.
## Expected Behavior
The function should timeout after a configurable duration and fail with
a clear error message showing what output was received.
## Related Issue(s)
Fixes hanging e2e tests observed in CI (e.g.,
`e2e-node:e2e-ci--src/node-server.test.ts` hung for 1h 21m).
## Changes
- Added optional `timeout` parameter to `runCommandUntil` opts (default:
5 seconds)
- On timeout: kills the process, logs the collected output, and rejects
with a clear error
- Existing call sites work unchanged; tests needing more startup time
can pass `{ timeout: 30000 }`
---------
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: FrozenPandaz <8104246+FrozenPandaz@users.noreply.github.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
1. The `check-links` task cache inputs only included `sitemap.xml` (the
index
file) and `sitemap-index.xml`, but not the actual `sitemap-0.xml` files
that
contain the URL data. This meant that when pages were added or removed,
the
cache wasn't properly invalidated - the check-links task would return a
cached "passing" result even when broken links existed.
2. The `/launch-nx` page was removed in #34183 but one link in
`astro-docs/src/content/docs/reference/Nx Cloud/release-notes.mdoc`
still
pointed to it. This link was masked by being in the `validate-links.ts`
ignore list.
## Expected Behavior
1. The `check-links` task cache is invalidated when sitemap URLs change
by
using glob patterns (`sitemap*.xml`) to include all sitemap files.
2. All links point to valid pages. The `/launch-nx` link now redirects
to
`/blog/launch-nx-week-recap`.
## Changes
- **astro-docs/release-notes.mdoc**: Updated `/launch-nx` link to
`/blog/launch-nx-week-recap`
- **astro-docs/validate-links.ts**: Removed `/launch-nx` from ignore
list (no longer needed)
- **nx-dev/project.json**: Fixed cache inputs to use `sitemap*.xml` glob
patterns
## Related Issue(s)
Fixes DOC-385
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Current Behavior
With Vite now providing additional options (environments etc) for
framework authors, vite.config files can be much more simple for the
user.
However, this often assumes that the `root` property will be set and
provided during `Vite CLI` invocation.
When we run `resolveConfig` to determine inputs and outputs, we do not
set this `root` and expect the user to have it in their vite config
file.
For some plugins/frameworks such as Tanstack Start - this causes the
plugin to error.
The `isBuildable` conditions is also not inclusive enough and can skip
projects that should be marked as buildable.
## Expected Behavior
Ensure that sophisticated vite plugins are supported with Nx
## Related Issue(s)
CLOSES NXC-3637
## Current Behavior
Template-generated workspaces use a generic link in the README instead
of a per-workspace short link for Nx Cloud setup.
## Expected Behavior
When users opt into Nx Cloud (or are auto-connected via variant 2), the
template README is updated with a personalized connect URL section that
helps them finish setting up their workspace.
---
BEFORE:
<img width="762" height="460" alt="image"
src="https://github.com/user-attachments/assets/58900071-1727-49d1-aa19-279c488b5037"
/>
AFTER:
<img width="1032" height="599" alt="image"
src="https://github.com/user-attachments/assets/a6e3a122-5807-4ba2-90dd-441e41a3280e"
/>
---
## Related Issue(s)
Closes NXC-3783
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
deno worker.stdout is a Readable/Writeable. To provide better deno
support an error should not be thrown
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
If `worker.stdout` or `worker.stderr` are not instanceof Socket then nx
throws an error. This is problematic in Deno where `stdout` and `stderr`
are Readable/Writable and not Socket.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
`startupPluginWorker` function should work in Deno runtime
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
- https://github.com/denoland/deno/issues/31961
- https://github.com/oven-sh/bun/issues/26505
---------
Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
## Current Behavior
Plugin workers occasionally fall over during the start up steps.
## Expected Behavior
Improves some issues with the error handling when loading plugin workers
and adds some more logs to help understand what's went wrong here.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Remove redundantly placed period and make error message construction
more readable when facing AggregateError
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes NXC-3766
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
--rerun is not tooling api compatible and therefore will break usage of
the batch executor. Replaced the flag with --rerun-tasks.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
The Maven batch runner bundles Maven 4 classes at compile time, which:
- Creates a large JAR file (~50+ MB)
- Only works with Maven 4
- Has classloader conflicts with Maven's own SLF4J
## Expected Behavior
The batch runner loads Maven classes at runtime from `MAVEN_HOME`,
which:
- Creates a small JAR (~2 MB) with no bundled Maven dependencies
- Works with both Maven 3.x and Maven 4.x
- Avoids classloader conflicts by isolating Maven in its own ClassRealm
- Outputs clean Maven-style logs (`[INFO]`, `[WARNING]`, etc.)
## Implementation
### Architecture
```
batch-runner.jar (NO Maven dependencies)
├── MavenClassRealm → Loads Maven JARs from MAVEN_HOME at runtime
├── ResidentMavenExecutor → Maven 4 executor (reflection-based)
├── CachingMaven3Invoker → Maven 3 executor (reflection-based)
└── nx-maven-adapters/ → Pre-compiled adapter JARs (embedded as resources)
├── batch-runner-adapters-maven3.jar
└── batch-runner-adapter-maven4.jar
```
### Key Changes
1. **Removed compile-time Maven dependencies** from batch-runner module
2. **Created batch-runner-adapters** modules for Maven 3 and Maven 4
specific code
3. **Implemented MavenClassRealm** to load Maven JARs from MAVEN_HOME at
runtime
4. **Implemented reflection-based executors** that load adapter JARs
into ClassRealm
5. **Fixed SLF4J logging** with System.out redirection for clean
Maven-style output
6. **Added shared module** for BuildStateManager, BuildStateApplier, and
BuildStateRecorder
### Benefits
- **Version agnostic**: Same JAR works with Maven 3.x and 4.x
- **Graph caching**: Project dependency graph built once, reused across
tasks
- **Build state persistence**: compile → package → install works
correctly
- **No classloader conflicts**: Maven's classes isolated in their own
ClassRealm
- **Clean output**: Standard Maven log format without SLF4J noise
## Related Issue(s)
N/A - Internal refactoring for better Maven version support
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
LLMs and CLI tools must explicitly request the `.md` URL suffix to get
raw markdown content from documentation pages.
## Expected Behavior
When a client requests a docs page with `Accept: text/markdown` header,
the edge function rewrites to serve the `.md` version directly (no
redirect). This enables LLM tools to get markdown content by requesting
the standard URL.
Behavior:
- `Accept: text/markdown` → serves .md content (via rewrite, no
redirect)
- Default (browsers) → serves HTML with Link headers (unchanged)
Examples:
```
curl -H 'Accept: text/markdown' https://deploy-preview-34239--nx-docs.netlify.app/docs/getting-started/intro
curl -H 'Accept: text/markdown' https://deploy-preview-34239--nx-docs.netlify.app/docs/getting-started/tutorials/angular-monorepo-tutorial
```
Uses Netlify Edge Function rewrite (returns URL object) instead of
redirect for single-request response that works with all HTTP clients.
## Related Issue(s)
Closes DOC-389
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Current Behavior
broken links
## Expected Behavior
links aren't broken any more and are updated to expected pages from docs
page.
## Related Issue(s)
Fixes DOC-391
This PR adds:
1. `llms-full.txt` that is a full copy of our docs in markdown.
2. HTTP `Link` headers to our docs HTML pages so that they point to the
`.md` (markdown) version, and also to `llms.txt` and `llms-full.txt`.
The `llms-full.txt` is currently at 2.7 MB, which is much less than
other sites that are up to 5MB or more.
<img width="569" height="35" alt="Screenshot 2026-01-27 at 12 11 10 PM"
src="https://github.com/user-attachments/assets/61be02b4-2813-4c39-951c-d831af83e823"
/>
First 100 lines of `llms-full.txt`:
````
# Nx Documentation
> Complete Nx documentation compiled into a single file for LLM consumption.
Nx is a powerful, open source, technology-agnostic build platform designed to efficiently manage codebases of any scale. From small single projects to large enterprise monorepos, Nx provides intelligent task execution, caching, and CI optimization.
This file was generated from 503 documentation pages.
Individual pages are available at: https://nx.dev/docs/{slug}.md
# Quickstart
---
<!-- source: https://nx.dev/docs/quickstart.md -->
## Quickstart with Nx
Get up and running with Nx in just a few minutes by following these simple steps.
{% steps %}
1. Install the Nx CLI
Installing Nx globally is **optional** - you can use `npx` to run Nx commands without installing it globally, especially if you're working with Node.js projects.
{% tabs syncKey="install-method" %}
{% tabitem label="npm" %}
```shell
npm add --global nx
```
**Note:** You can also use Yarn, pnpm, or Bun
{% /tabitem %}
{% tabitem label="Homebrew (macOS, Linux)" %}
```shell
brew install nx
```
{% /tabitem %}
{% tabitem label="Chocolatey (Windows)" %}
```shell
choco install nx
```
{% /tabitem %}
{% tabitem label="apt (Ubuntu)" %}
```shell
sudo add-apt-repository ppa:nrwl/nx
sudo apt update
sudo apt install nx
```
{% /tabitem %}
{% /tabs %}
2. Start fresh or add to existing project
For JavaScript-based projects you can **start with a new workspace** using the following command:
```shell
npx create-nx-workspace@latest
```
**Add to an existing project: (recommended also for non-JS projects)**
```shell
npx nx@latest init
```
**Get the complete experience:**
For a fully integrated development workflow with AI-powered CI features, [start directly from Nx Cloud](https://cloud.nx.app/get-started).
Learn more: [Start New Project](/docs/getting-started/start-new-project) • [Add to Existing](/docs/getting-started/start-with-existing-project) • [Complete Nx Experience](https://cloud.nx.app/get-started)
3. Run Your First Commands
Nx provides powerful task execution with built-in caching. Here are some essential commands:
**Run a task for a single project:**
```shell
nx build my-app
nx test my-lib
```
**Run tasks for multiple projects:**
```shell
nx run-many -t build test lint
```
Learn more: [Run Tasks](/docs/features/run-tasks) • [Cache Task Results](/docs/features/cache-task-results)
4. What's next?
Now that you've experienced the Nx basics, choose how you want to continue:
````
## Related Issue(s)
Closes DOC-236
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
This design refresh emphasizes the contrast variant aesthetic across all
hero sections, pricing cards, and primary call-to-actions.
- Change 47 instances of variant="primary" to variant="contrast"
- Update ui-courses to use variant="secondary" for GitHub link
- Prefer high-contrast inverted style for primary CTAs
- Maintain proper visual hierarchy with secondary actions
- Replace all slate-* classes with zinc-* equivalents (1,158 instances)
- Replace all sky-* classes with blue-* equivalents (210 instances)
- Update opacity variants, gradients, rings, and borders
- Maintain full dark mode compatibility
## Current Behavior
If something is in `package.json#dependencies`, we still suggest it to
be `nx add`-ed during `nx import`
## Expected Behavior
If a plugin is already installed, we don't suggest it anymore
`@nx/web:app` generator is incorrectly calling `createOrEditViteConfig`
when bundler != vite and unitTestRunner = vitest.
Ensure it is using the correct file
## Current Behavior
The migration for svgr requires using file-loader which is unmaintained.
## Expected Behavior
Use asset/resource instead of file-loader
## Related Issue(s)
CLOSES NXC-3667
## Summary
- When `/tmp` is mounted with `noexec`, loading native modules from the
cache fails silently and causes Nx to hang indefinitely
- This adds a fallback to load from `node_modules` when permission
errors occur
## Problem
Users with `/tmp` mounted with `noexec` (a common security hardening
practice) experience Nx hanging forever, even for simple commands like
`nx --version`.
The root cause:
1. Nx copies native `.node` files to `/tmp` to avoid Windows file
locking issues
2. On `noexec` mounts, execution fails with `EACCES`/`EPERM`
3. The error wasn't caught, leading to broken native bindings and
infinite loops
## Solution
Catch permission errors when loading from the cache and fall back to the
original `node_modules` location. This:
- Works automatically without user config
- Preserves Windows file locking fix (only falls back when needed)
- No error messages for users
Closes#33991
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
Schema validation (done, for instance, when calling an executor) fails
when an option has value "null" and schema accepts null values. I had it
in a custom executor for `nx-release-publish`, that understands that
`nxReleaseVersionData` is implicitly passed, so I define its schema:
```json
"newVersion": {
"type": ["string", "null"],
"description": "The new version of the project, null if no changes detected"
}
```
My code calls `getReleaseClient().releaseVersion(options)`, which gets
me a `projectsVersionData` object with version info. It contains `null`
values (allowed). I then pass it, and ends up in:
```typescript
// nx/src/tasks-runner/task-orchestrator.ts:531-539
const combinedOptions = combineOptionsForExecutor(
task.overrides, // ← Contains nxReleaseVersionData with null values
task.target.configuration,
targetConfiguration,
schema, // ← Schema from executor
task.target.project,
relativeCwd,
isVerbose
);
```
which fails inside:
```typescript
// nx/src/utils/params.js:126-201
function validateObject(opts, schema, definitions) {
// Line 191-200: Iterate through all properties
Object.keys(opts).forEach((p) => {
validateProperty(
p, // "nxReleaseVersionData"
opts[p], // { foo: { newVersion: null, ... }}
(schema.properties ?? {})[p], // schema for nxReleaseVersionData
definitions
);
});
}
```
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR -->
`null` values should be considered, as they are valid in JSON schemas. It was probably not considered, because we never think that `typeof null === "object"`, but it's unfortunately the case.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is merged. -->
I will create one
Fixes https://github.com/nrwl/nx/issues/34169
Transpiling through SWC or ts-node is slow compared to the native type
stripping that Node.js provides.
This PR adds `NX_PREFER_NODE_STRIP_TYPES` to allow users to use Node.js
built-in TypeScript support. There are some features that need
transpilation that won't work with type stripping:
- Enum declarations
- namespace with runtime code
- legacy module with runtime code
- parameter properties
- path aliases
See: https://nodejs.org/api/typescript.html#full-typescript-support
The speed-up is significant. My test workspace went from 22s to ~2s to
compute from cold cache.
Demo: https://www.loom.com/share/ce1db29e501b46d58109ffeec8a7a649
In the future we should enable this by default, and users have to turn
it off to use SWC/ts-node.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
Right now GA only collects page views. This PR allow us to see which
`.md` files are being used. There are mostly useful for AI agents to
fetch without using too many tokens. We want to track usage so we can
see what techniques to guide agents actually work.
## Related Issue(s)
Closes DOC-386
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This PR makes it so Cloud commands like `npx nx record` and `npx nx
fix-ci` still work without `nxCloudId`. We'll log a warning so that
`ci.yml` using these commands will still work. The warning let's users
know that these do not work without being connected.
Closes #NXC-3753
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
Commit linting does not mention requirement for commit message to be
lowercase.
<!-- This is the behavior we have today -->
## Expected Behavior
Hook message should instruct user to use all lowercase for commit
message.
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
Self-healing docs only reference being supported for GitHub, Azure, and
GitLab.
<!-- This is the behavior we have today -->
## Expected Behavior
We should show instructions for all currently supported vcs providers,
including Bitbucket.
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
Resizing the TUI while in inline view kinda breaks things. Its
unfortunate, I'm not sure there's a ton to be done, but this PR explores
some solutions
## Expected Behavior
The TUI is less sensitive to resize events with inline mode
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
The create-nx-workspace (CNW) command includes A/B testing variant 1
which skips the cloud prompt under certain conditions.
## Expected Behavior
Revert to the previous behavior where the cloud prompt flow is
consistent without the A/B testing variant.
## Related Issue(s)
This reverts commit 2039a5e119 from PR
#34106.
## Current Behavior
The internal link checker reports 3 broken links pointing to
`/launch-nx`:
- `/blog/2024-02-05-nx-18-project-crystal.md`
- `/blog/2024-02-15-launch-week-recap.md`
- `/changelog/18_0_0.md`
The `/launch-nx` page was a temporary page for the Nx 18 launch event in
February 2024 and no longer exists.
## Expected Behavior
All internal links should point to valid pages. Links to the old launch
page now redirect to the Launch Nx Week recap blog post.
## Related Issue(s)
Fixes the internal link checker errors.
## Current Behavior
There's a hard to reproduce hang that happens occasionally when running
the TUI
## Expected Behavior
We think this should fix it
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
The `banner.json` was committed as a fallback if we're not using Framer
to control the banner yet on nx.dev. Now that it is verified we can
remove the committed file.
Closes DOC-381
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Previously, `nx reset --onlyDaemon` would only stop the daemon process
but not clean up the daemon files in `.nx/workspace-data/d`. This change
ensures the daemon workspace data directory is also removed when using
the `--onlyDaemon` flag, consistent with the behavior of a full reset.
Co-authored-by: MaxKless <MaxKless@users.noreply.github.com>
This PR updates the releases page so v22 is included.
Closes #DOC-382
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
1. When running a task with dependencies (e.g., `nx serve app` where app
depends on app2:serve), the initiating task might not be selected on
startup. Additionally, the auto-select logic could switch selection to
the initiating task at any time when it started running - even minutes
later - which felt "random" to the user.
2. When pressing Enter on an already-pinned task, it would unpin the
task, causing the pane to disappear while focus remained on it
(invisible-but-focused state).
## Expected Behavior
1. The initiating task (the one the user actually requested) should be
selected during init, and selection should never unexpectedly change
later when tasks start.
2. Pressing Enter on an already-pinned task should focus the pane, not
unpin it.
## Changes
- **Select initiating task during init**: Moved initiating task
selection to `init()` in app.rs. This only applies in `RunOne` mode
since in `RunMany` there's no single initiating task to prioritize.
Removed the "switch to initiating task" logic from `start_tasks()` in
tasks_list.rs.
- **Focus pane on Enter**: Changed behavior so pressing Enter on an
already-pinned task focuses the pane instead of unpinning it.
## Related Issue(s)
N/A - discovered during TUI testing
# Current Behavior
The `beforeCompile` hook is registered inside the `watchRun` hook,
causing a new handler to be added on every recompilation. This leads to
handler accumulation, where setup operations (building static remotes,
starting file server, starting proxies) are triggered multiple times
during watch mode.
# Expected Behavior
Hooks should be registered once, outside of other hooks, to prevent
accumulation. Setup operations should only run once, not on every
recompilation.
# Related Issue(s)
Fixes https://github.com/nrwl/nx/issues/34141
# Current Behavior
1. **Performance bottleneck**: `statsValue.toJson()` is called with no
options, causing full serialization of all stats data on every build.
This is expensive and unnecessary when only budget checking is needed.
2. **Redundant work**: Budget checking code runs even when no budgets
are configured or when targeting server platform.
3. **User stats config ignored**: Custom stats configuration provided
via `rspackConfigOverrides` is not respected by the stats logger.
4. **Double serialization**: `rspackStatsLogger` calls `stats.toJson()`
without passing the stats options, ignoring user preferences.
# Expected Behavior
1. Only serialize what's needed for budget checking (`assets` and
`chunks`), significantly reducing overhead.
2. Early exit when budgets are not configured or on server platform,
skipping expensive `toJson()` entirely.
3. User's stats configuration is merged with defaults and respected
throughout the build output.
4. `rspackStatsLogger` uses the provided `statOptions` when serializing
stats.
# Related Issue(s)
Fixes https://github.com/nrwl/nx/issues/34145
## Current Behavior
CNW always shows the "Try the full Nx platform?" prompt and connects to
Nx Cloud to generate an onboarding URL with a token.
## Expected Behavior
For A/B testing variant 1:
- Skip cloud prompt
- Skip connectToNxCloudForTemplate() - no nxCloudId in nx.json
- Skip readNxCloudToken() - no misleading spinner
- Use GitHub flow for URL generation (accessToken: null)
- Show github.com/new hint when user hasn't pushed
Also fixes:
- Expired cache file bug: now deletes with unlinkSync() instead of
ignoring, which caused 50-50 randomization after 1-week expiry
- Adds variant-X to short URL meta property for cloud analytics
## Related Issue(s)
Closes NXC-3628
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Current Behavior
`getNxRequirePaths` returns paths in the order `[root,
getNxInstallationPath(root)]`, which means the workspace root is checked
first when resolving modules.
## Expected Behavior
The nx installation path (`.nx/installation`) should be prioritized and
checked first before falling back to the workspace root. This ensures
that modules from the nx installation directory take precedence.
## Related Issue(s)
N/A
# Current Behavior
The Angular Rspack compiler's `ComponentStylesheetBundler` does not
receive Tailwind or PostCSS configuration. This means Tailwind
directives (like `@apply`, `@tailwind`) in component stylesheets are not
processed, resulting in broken styles.
# Expected Behavior
Component stylesheets should support Tailwind CSS and PostCSS
configurations, matching the behavior of the standard Angular CLI build
process.
# Related Issue(s)
https://github.com/nrwl/nx/issues/34098
# Current Behavior
1. **Handler accumulation**: The `compilation`, `beforeCompile`, and
`done` hooks are registered inside `watchRun`, causing new handlers to
be added on every rebuild cycle. This leads to performance degradation
and duplicate operations during watch mode.
2. **Double rebuilds**: Rapid filesystem events (e.g., editor
backup/swap files) trigger multiple rebuilds because there's no
aggregation timeout configured.
3. **No watchOptions configuration**: Users cannot customize watcher
behavior (aggregateTimeout, ignored patterns, etc.).
# Expected Behavior
1. Hooks should be registered once outside of `watchRun` to prevent
accumulation. Shared state is used to pass data between watch cycles and
compilation hooks.
2. A default `aggregateTimeout: 50` batches rapid filesystem events to
prevent double rebuilds.
3. Users can provide custom `watchOptions` to configure watcher
behavior, with user options taking precedence over defaults.
# Related Issue(s)
https://github.com/nrwl/nx/issues/34142#issuecomment-3767571208
## Current Behavior
New task processes show 0% CPU on their first measurement because no
baseline exists. Accurate readings only appear on the second collection
cycle (~1s later).
## Expected Behavior
New task processes get accurate CPU readings on their first measurement.
The collector establishes CPU baselines for newly registered processes
~250ms before collection, giving `sysinfo` enough time to calculate
accurate CPU deltas.
## Technical Details: Baselining & Collection Flow
The collection loop runs in 4 phases:
```
T=0ms T=750ms T=1000ms T=1750ms T=2000ms
| | | | |
v v v v v
Collect → Sleep(750ms) → Baseline → Sleep(250ms) → Collect → ...
```
1. **Collect**: Refresh all processes and gather metrics
2. **Post-collection sleep**: Wait until baseline time (interval -
250ms)
3. **Baseline**: Bulk CPU refresh for newly registered PIDs (if any)
4. **Pre-collection sleep**: Wait 250ms for accurate CPU delta
calculation
## Current Behavior
if inline tui init fails, we panic
## Expected Behavior
If inline tui init fails, inline mode is disabled. We show the reason
its disabled when someone tries to use it.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
When exiting the TUI (especially via Ctrl+C), escape sequences leak to
the terminal:
```
^[]11;rgb:2121/2121/2121^[\^[[55;1R^[[?62;22;52c
```
This happens because the TUI queries terminal background color via OSC
11 to detect dark/light mode. The terminal responds with an escape
sequence, but if the program exits before fully consuming the response,
it appears in the terminal output.
## Expected Behavior
Clean terminal state after TUI exits, with no escape sequence artifacts.
## Related Issue(s)
N/A - discovered during development
## Solution
Added `drain_stdin()` function that polls and consumes any pending
terminal events before disabling raw mode. This clears any lingering OSC
responses (like the background color query response) before the terminal
is restored.
```rust
fn drain_stdin() {
use std::time::Duration;
while crossterm::event::poll(Duration::from_millis(5)).unwrap_or(false) {
let _ = crossterm::event::read();
}
}
```
The 5ms timeout is long enough to catch pending responses but short
enough not to noticeably delay exit.
## Current Behavior
When the daemon encounters a project graph error during task hashing, it
extracts the partial project graph from the error and continues hashing
tasks. This can produce incorrect hashes since the graph is incomplete.
## Expected Behavior
The error should be thrown immediately, preventing any hashing attempts
with an invalid project graph. This ensures we don't produce incorrect
task hashes that could lead to cache issues.
## Related Issue(s)
N/A - Bug fix discovered during development
## Current Behavior
The `rollup-plugin-postcss` has not released a new version in 4 years.
The deps it depends on are outdated and starting to cause problems with
peer-dep conflicts.
## Expected Behavior
Recreate the plugin within the `@nx/rollup` package to maintain the
functionality/behaviour and manage dependencies ourselves.
## Related Issue(s)
Closes NXC-3644
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Coly010 <Coly010@users.noreply.github.com>
## Current Behavior
Running `nx test` with Vitest browser mode fails with error:
> "The browser configuration must have a 'name' property"
Array configs like `browser.instances` and `reporters` get duplicated,
breaking tests.
## Expected Behavior
Vitest browser mode and array configurations work correctly without
duplication.
## Related Issue(s)
Fixes#33591
## Current Behavior
Nx's AI agent detection currently identifies Claude Code, Repl.it, and
Cursor AI agents via environment variables, but does not detect
OpenCode.
## Expected Behavior
Nx should also detect when running under OpenCode AI agent by checking
for the `OPENCODE` environment variable, which OpenCode sets to `1` when
active.
## Related Issue(s)
N/A - Feature addition to improve AI agent detection coverage.
## Changes
- Added `is_opencode_ai()` function in
`packages/nx/src/native/utils/ai.rs`
- Updated `is_ai_agent()` to include OpenCode detection
- Added corresponding unit tests
## Current Behavior
For optional packages that are not installed when using yarn, we
currently add the package version to the key for the hash. NPM and PNPM
do not do this.
The results in inconsistent hashes for package dependencies when running
in different environments. For example, trying to use the cache created
in CI on linux on a mac where native dependencies are used.
**yarn on arm mac**
_note how the key has the version in it for the linux and x64 versions
that are not installed_
```
$ nx test foo | grep @nx/nx-
...
"npm:@nx/nx-darwin-x64@22.3.3": "14042642002999097748",
"npm:@nx/nx-linux-x64-gnu@22.3.3": "12169496858981304476",
"npm:@nx/nx-darwin-arm64": "1683411334940043113",
```
**npm on arm mac**
```
$ nx test foo | grep @nx/nx-
...
"npm:@nx/nx-darwin-x64": "14042642002999097748",
"npm:@nx/nx-darwin-arm64": "1683411334940043113",
"9980946580833020728",
"npm:@nx/nx-linux-x64-gnu": "12169496858981304476",
```
**pnpm on arm mac**
```
$ nx test foo | grep @nx/nx-
...
"npm:@nx/nx-darwin-arm64": "1683411334940043113",
"npm:@nx/nx-darwin-x64": "14042642002999097748",
"npm:@nx/nx-linux-x64-gnu": "12169496858981304476",
```
## Expected Behavior
Optional dependencies should be handled the same way from a hashing
perspective as installed dependencies.
**yarn on arm mac**
```
$ nx test foo | grep @nx/nx-
...
"npm:@nx/nx-darwin-x64": "14042642002999097748",
"npm:@nx/nx-linux-x64-gnu": "12169496858981304476",
"npm:@nx/nx-darwin-arm64": "1683411334940043113",
```
On test repo reduces the
`nx/js/dependencies-and-lockfile:createDependencies`:
- from `10506ms`
- to `3665ms`
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
CPU metrics collection can report inaccurate values where:
- Individual processes show inflated CPU usage
- Total CPU aggregation across all processes exceeds the system's
maximum available CPU
- This leads to confusing and misleading metrics data
## Expected Behavior
CPU metrics accurately reflect actual resource usage:
- Process CPU values are accurate
- Total CPU aggregation stays within system limits
- Metrics data is reliable and trustworthy
### Additional Notes
- **Root cause**: When registering a new process, we established a CPU
baseline by refreshing only that single process via `sysinfo`.
Internally, `sysinfo` calculates CPU% as `(process_cpu_time_delta /
wall_time_delta) * 100`. Refreshing a single process updates the wall
time reference but leaves the CPU time baselines of other processes
unchanged. In the next metrics collection, these other processes appear
to have consumed their CPU time over a shorter wall time period (based
on the last baseline), resulting in inflated percentages (e.g., 200%+
for single-threaded processes).
- This PR also improves initialization performance by only loading
necessary system data (processes, CPU, memory) instead of all system
information
- Upgrades `sysinfo` dependency to v0.37.2, which includes upstream CPU
measurement improvements.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Marketing pages (homepage, /react, /java, etc.) do not track scroll
depth. Only docs pages have scroll tracking via the ScrollableContent
component.
## Expected Behavior
Marketing pages now track scroll depth and fire scroll_0, scroll_25,
scroll_50, scroll_75, scroll_90 events to Google Analytics, matching the
existing docs page behavior.
## Related Issue(s)
Closes DOC-376
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Current Behavior
The inline tui runs some terminal escape codes to check cursor position,
these break when stdin isn't a tty (like in a git hook)
## Expected Behavior
The inline tui is disabled if stdin isn't a tty
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The `migrations.json` file in the `@nx/maven` package is not included in
the `files` array in `package.json`. This means when the package is
published to npm, the migrations file is not included, preventing users
from running migrations.
## Expected Behavior
The `migrations.json` file should be included in the published package
so that Nx can discover and run migrations when users upgrade.
## Related Issue(s)
N/A - discovered during development
Exclude some handwritten files from the native build outputs. When those
files are updated in isolation, the build can replace them with stale
cached outputs. This is because they are not inputs of the native
builds, but are incorrectly stored as outputs of the native builds.
## Current Behavior
When using pnpm aliases, the project graph may miss the actual
dependency node (the aliased package overwrites it), resulting in
incomplete or confusing dependency graphs.
## Expected Behavior
The project graph should include both the alias and the actual
dependency node, matching the behavior of other package managers.
## Current Behavior
No daemon info is provided in `nx report`, but some issues are
exasperated by the daemon or may only show if the daemon is disabled.
This is useful context that we currently lack.
## Expected Behavior
`nx report` includes if the daemon is available, enabled but not
started, or disabled.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Running `nx show project` without specifying a project name fails, even
when inside a project directory:
```bash
cd packages/my-lib
nx show project
# Error: requires projectName argument
```
## Expected Behavior
When run from within a project directory, `nx show project` infers the
project from the current working directory:
```bash
cd packages/my-lib
nx show project # Shows my-lib configuration and targets
nx show project --json # Outputs JSON for my-lib
```
Explicit project name still works:
```bash
nx show project other-project
```
If cwd is not within any project, shows a helpful error with usage
instructions.
### Changes
- `command-object.ts`: Make `projectName` positional arg optional
(`[projectName]`)
- `project.ts`: Infer project from cwd using `findProjectForPath` when
no project specified
- `project.spec.ts`: Add unit tests for cwd inference, nested
directories, root projects, and error cases
## Related Issue(s)
Fixes#31055
> [!WARNING]
>
> <details>
> <summary>Firewall rules blocked me from connecting to one or more
addresses (expand for details)</summary>
>
> #### I tried to connect to the following addresses, but was blocked by
firewall rules:
>
> - `repo.gradle.org`
> - Triggering command: `/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
--add-opens=java.base/java.lang=ALL-UNNAMED
--add-opens=java.base/java.lang.invoke=ALL-UNNAMED
--add-opens=java.base/java.util=ALL-UNNAMED
--add-opens=java.prefs/java.util.prefs=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
--add-opens=java.base/java.util=ALL-UNNAMED
--add-opens=java.prefs/java.util.prefs=ALL-UNNAMED
--add-opens=java.base/java.nio.charset=ALL-UNNAMED
--add-opens=java.base/java.net=ALL-UNNAMED
--add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED
--add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED
-XX:MaxMetaspaceSize=384m -XX:+HeapDumpOnOutOfMemoryError -Xms256m
-Xmx512m -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en` (dns
block)
> - `staging.nx.app`
> - Triggering command: `/usr/local/bin/node node ./bin/post-install`
(dns block)
> - Triggering command: `/usr/local/bin/node /usr/local/bin/node
/home/REDACTED/work/nx/nx/node_modules/.pnpm/nx@22.2.0-beta.1_@swc-node+register@1.9.1_@swc+core@1.5.7_@swc+helpers@0.5.11__@swc+typ_e0638a3d25d549ce0cdd3a7d8bad3b61/node_modules/nx/src/project-graph/plugins/isolation/plugin-worker
/tmp/plugin3517-16-424.353971.sock @nx/enterprise-cloud` (dns block)
> - Triggering command: `/usr/local/bin/node /usr/local/bin/node
/home/REDACTED/work/nx/nx/node_modules/.pnpm/nx@22.2.0-beta.1_@swc-node+register@1.9.1_@swc+core@1.5.7_@swc+helpers@0.5.11__@swc+typ_e0638a3d25d549ce0cdd3a7d8bad3b61/node_modules/nx/src/project-graph/plugins/isolation/plugin-worker
/tmp/plugin4274-16-431.526748.sock @nx/enterprise-cloud` (dns block)
>
> If you need me to access, download, or install something from one of
these locations, you can either:
>
> - Configure [Actions setup
steps](https://gh.io/copilot/actions-setup-steps) to set up my
environment, which run before the firewall is enabled
> - Add the appropriate URLs or hosts to the custom allowlist in this
repository's [Copilot coding agent
settings](https://github.com/nrwl/nx/settings/copilot/coding_agent)
(admins only)
>
> </details>
<!-- START COPILOT CODING AGENT SUFFIX -->
<details>
<summary>Original prompt</summary>
>
> ----
>
> *This section details on the original issue you should resolve*
>
> <issue_title>Feature: allow `nx show targets` to infer project from
the cwd</issue_title>
> <issue_description>### Description
> When I'm already working inside a project's folder (for example
`github/abapify/packages/adk` that has its own `project.json`), I would
love to ask Nx to list the targets that belong to that project without
having to remember or type the project name. Ideally `npx nx show
targets` could detect the `project.json` in the current working
directory and operate on that project automatically.
>
> ### Current behavior
> Running the command from inside a project directory fails because the
CLI insists on a `--project` argument:
>
> ```
> $ cd github/abapify/packages/adk
> $ npx nx show targets
> Please provide a project name via --project=<name>
> ```
>
> ### Expected behavior
> If the current directory (or one of its ancestors) contains a
`project.json`, Nx should infer the project from that file and list the
targets without extra flags. This would make it much easier to explore
available targets while staying focused on a single project, especially
in large workspaces with many similarly named packages.
>
> ### Environment
> - Nx: 21.6.3
> - Node: 24.10.0
> - Package manager: bun 1.3.1
> - OS: WSL2 (Linux 6.6.87.2-microsoft-standard-WSL2)
> </issue_description>
>
> ## Comments on the Issue (you are @copilot in this section)
>
> <comments>
> </comments>
>
</details>
- Fixesnrwl/nx#33503
<!-- START COPILOT CODING AGENT TIPS -->
---
✨ Let Copilot coding agent [set things up for
you](https://github.com/nrwl/nx/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AgentEnder <6933928+AgentEnder@users.noreply.github.com>
Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Current Behavior
The `prettier` module is being resolved twice during the `format`
command execution and using a mix of `import`/`require`.
## Expected Behavior
The `prettier` module should be resolved once and reused.
## Current Behavior
The `central-publishing-maven-plugin` with `extensions=true` does not
work with Maven 4. Running `./mvnw deploy -Prelease` completes the build
but never triggers the deploy/publish phase - it stops at install.
This is a known issue tracked as
[MNG-8584](https://issues.apache.org/jira/browse/MNG-8584).
## Expected Behavior
Running `./mvnw deploy -Prelease -pl packages/maven/maven-plugin -am`
should publish the nx-maven-plugin to Maven Central.
## Related Issue(s)
Related to Maven 4 compatibility:
https://issues.apache.org/jira/browse/MNG-8584
## Changes
- Add explicit execution binding for `central-publishing-maven-plugin`
to the deploy phase (workaround for broken extensions mechanism in Maven
4)
- Remove `extensions=true` that doesn't work with Maven 4
- Update `central-publishing-maven-plugin` from 0.9.0 to 0.10.0
- Skip standard `maven-deploy-plugin` for nx-maven-plugin module (we use
central-publishing instead)
## Current Behavior
the plugins are disabled on vercel and netlify but there's no easy way
to disable them otherwise.
## Expected Behavior
there's an env var to disable each of the plugins: `NX_GRADLE_DISABLE` /
`NX_MAVEN_DISABLE` just like there is one for dotnet.
## Current Behavior
Maven e2e tests fail with two issues:
1. Spring Initializr rejects Spring Boot 3.4.0 (now requires >=3.5.0)
2. Parent POMs (`nx-parent` and `nx-maven-parent`) aren't installed
locally, causing Maven to fail when resolving `nx-maven-plugin:0.0.12`
since it's not yet published to Maven Central
## Expected Behavior
Maven e2e tests pass successfully by:
1. Using Spring Boot 4.0.0 which is supported by Spring Initializr
2. Installing parent POMs to local Maven repository so the plugin can
resolve its dependencies
## Related Issue(s)
Fixes the CI failures in maven e2e tests after the 0.0.12 version bump.
## Current Behavior
The prebuild-banner script requires an `enabled` boolean field that the
Framer API does not return, causing validation failures.
## Expected Behavior
Validation matches the actual API response format which uses
`activeUntil` for determining banner visibility instead of `enabled`.
Closes #CLOUD-4071
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
After running commands which spawn plugin workers on the main process
(`nx show project`, or any command with the daemon disabled) some users
(and notably @FrozenPandaz) experienced terminal issues that resulted in
↑ / ↓ printing escape codes instead of scrolling command history.
## Expected Behavior
This pull request updates how plugin worker processes handle their
input/output streams to improve terminal behavior and debugging
capabilities. The main change is switching the worker's stdio from
`inherit` to `pipe`, and then manually piping the worker's stdout and
stderr to the main process. This avoids terminal state issues and
enables better debugging.
**Plugin worker process I/O handling:**
* Changed the worker process `stdio` option from `'inherit'` to `'pipe'`
in `startPluginWorker`, preventing terminal state issues (such as broken
arrow key functionality) after Nx execution.
* Added logic to pipe the worker's `stdout` and `stderr` to the main
process, making it easier to debug and allowing plugins to communicate
metrics. Increased the max listener count on `process.stdout` and
`process.stderr` to avoid warnings from multiple listeners.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
We dont validate the size of the native file cache after copying it,
which sometimes fails and corrupts the data. This failure results in a
different size, so we can detect it. In certain situations, the
corruption causes node to hang instead of throw.
## Expected Behavior
We detect the corruption
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes#30653Fixes#31300
I found regression in `replaceOverride` behavior that isn't guarded
against. There is migration from v17
(https://github.com/nrwl/nx/blob/20.8.x/packages/next/src/migrations/update-17-2-7/remove-eslint-rules-patch.ts#L17)
that shows that when `update` function returns `undefined`, then that
override entry should be removed.
We had no other unit tests to cover this case previously, so it was
missed until I tried cherry-picking the fix to `20.8.x` branch.
There was also a bug with flat config in v20 and v21 where not passing
`update` function for flat config leads to an error due to
`update(data)` being called without checking if `update` exists (since
it's optional). We never actually skip on passing `update` (which makes
`replaceOverride` useless since it's noop), so I marked it as required
arg now.
## Current Behavior
When `replaceOverride` is called with an update function that returns
`undefined`, the override block is not removed. This is inconsistent
with the JSON code path which uses `splice` to remove the override.
## Expected Behavior
When the update function returns `undefined`, the entire override block
should be deleted from the flat config, matching the JSON path behavior.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Current Behavior
When running a task graph with continuous tasks and one of them is
terminated, the reverse continuous task deps that have no other tasks
that depend on them are also automatically terminated. This is fine in a
non-DTE context, but when running in a DTE context, the DTE task runner
must own the lifecycle of the continuous tasks, and Nx shouldn't
automatically terminate them.
## Expected Behavior
Nx shouldn't automatically terminate continuous tasks when running in a
DTE context.
## Current Behavior
The `create-nx-workspace` incorrectly offers `Vitest & Angular` as a
valid unit test runner choice when the bundler is something other than
`esbuild`.
## Expected Behavior
The `create-nx-workspace` should only offer `Vitest & Angular` as a
valid unit test runner choice when the bundler is `esbuild`.
## Related Issue(s)
Fixes#34014
## Current Behavior
On systems under heavy load, plugin loading can fail with:
```bash
Plugin Worker exited because no plugin was loaded within 10 seconds of starting up.
```
The initialization of the process metrics collection could potentially
cause this by blocking the loading of the plugin.
## Expected Behavior
Process metrics initialization no longer blocks critical startup paths,
allowing plugin loading to succeed regardless of system load.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Shared running tasks (tasks running in another process) do not appear in
the In Progress section of the TUI. They show the throbber as being in
progress, but are located in the Pending/Completed section.
## Expected Behavior
Shared running tasks (tasks running in another process) should appear in
the In Progress section of the TUI.
## Current Behavior
package installation does not set `windowsHide` so it flashes a terminal
window
## Expected Behavior
there should be no flashing terminal window
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
Adds a sm utility script to ease quickly checking changes against local
repos. Not a replacement for local registry + install, but good for
quick checks
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Bump gradle project graph plugin version to 0.1.11
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
We have a hard coded list of task targets to not exclude depends on.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
We resolve a gradle task such that we can identify if there are provider
dependency relationships involved. If there are, then do not exclude
depends on since Gradle needs the dependsOn tasks to fulfill providers.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
When processing Kotlin Multiplatform (KMP) projects, the Nx Gradle
plugin encounters ConcurrentModificationException errors because KMP
dynamically modifies the Gradle project's task and configuration
containers during dependency resolution. The plugin was resolving
configuration dependencies before processing tasks, which triggered
KMP's hierarchy finalization and dynamic task creation while the plugin
was still iterating over these collections.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
The Gradle plugin should handle Kotlin Multiplatform projects without
errors by:
1. Processing tasks before resolving configuration dependencies,
preventing KMP from modifying task containers during iteration
2. Creating immutable snapshots of task and configuration collections
before iteration to avoid concurrent modification issues
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes NXC-3633
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
The Nx Plugin uses Nx as the backing mechanism, but Gradle still does
some caching behind the scenes. When using Nx and Gradle's caching at
the same time, there can be times where Gradle does not recognize input
changes and will not execute tasks that it mistakenly deems unchanged.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Ensure that the batch executor always reruns tasks and is not impacted
by the Gradle build cache.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes NXC-3649
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: Caleb Ukle <caleb@nrwl.io>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
For 0.x versions, shift semver bump types down to follow the common
convention where breaking changes bump minor, and new features bump
patch:
- major -> minor
- premajor -> preminor
- minor -> patch
- preminor -> prepatch
- patch -> patch (unchanged)
This ensures that `nx release` with a breaking change on a 0.x package
(e.g., 0.1.0) bumps to 0.2.0 instead of 1.0.0.
Fixes NXC-3638
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Coly010 <Coly010@users.noreply.github.com>
The migration generator (`@nx/plugin:migration`) fails when due to
ESLint flat config not being parsed correctly, leading to an error.
This happens because `replaceOverride` uses `parseTextToJson` to parse
the config, which fails for non-JSON-serializable JavaScript
expressions.
This PR fixes the issue by using AST parsing, like we did for
`hasOverrides` here https://github.com/nrwl/nx/pull/33548.
Fixes#34010
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Current Behavior
When adding a non-buildable JS library (`bundler: 'none'`) to a
workspace with an existing `@nx/js/typescript` plugin registration that
has build options configured, a duplicate plugin entry is unnecessarily
created in `nx.json`.
## Expected Behavior
Non-buildable libraries reuse the existing plugin registration when
`skipBuildCheck: true` is not specified, thereby avoiding duplicate
entries in `nx.json`. The `@nx/js/typescript` plugin will infer the
project as non-buildable because the library's `package.json` will have
entry points pointing to source files, so there's no need for a separate
plugin registration.
## Related Issue(s)
Fixes#33981
## Current Behavior
Rspack 1.7.0 is failing to create factories for internals with Module
Federation.
## Expected Behavior
Pin Rspack to 1.6.8 for now to ensure continued functioning of Module
Federation
## Current Behavior
PR #32915 changed how conventional commits determine version, making
them rely on commit scope:
commits with types configured to bump minor / major version bumps only
patch if commit scope exists and it does not include project name
## Expected Behavior
In our project we do not use projectName as commit scope, so we would
like to bring back old behavior, this can be achieved by adding option
to opt-out such behavior
---------
Co-authored-by: Colum Ferry <cferry09@gmail.com>
## Current Behavior
- The `skipTypeChecking` option is a simple boolean that only allows
enabling/disabling type checking
- Rspack executor is missing the `runtimeDependencies` option that
webpack has
- The `cache` configuration is hardcoded and cannot be overridden by
users
## Expected Behavior
- New `typeCheckOptions` option allows configuring type checking with `{
async: true }` to run type checking in a separate process without
blocking the build
- The deprecated `skipTypeChecking` option is maintained for backward
compatibility
- Rspack now supports `runtimeDependencies` option for adding runtime
dependencies to generated `package.json` (useful for Docker installs)
- New `cache` option allows users to override webpack/rspack caching
behavior while maintaining backward-compatible defaults
## Related Issue(s)
N/A - Standalone feature
## Changes Made
### typeCheckOptions (webpack & rspack)
- Added `TypeCheckOptions` interface with `async` property
- Added `typeCheckOptions` option to plugin options interfaces
- Updated `apply-base-config.ts` to normalize `typeCheckOptions` from
deprecated `skipTypeChecking` for backward compatibility
- Added schema definitions to `schema.json` and `schema.d.ts`
### runtimeDependencies (webpack & rspack)
- Added `runtimeDependencies` option to `NxAppRspackPluginOptions`
- Updated `GeneratePackageJsonPlugin` to resolve and include runtime
dependencies
- Added schema definitions to `schema.json` and `schema.d.ts`
### cache (webpack & rspack)
- Added `cache` option to plugin options interfaces
- Updated `apply-base-config.ts` to check `'cache' in options` before
applying defaults
- Allows explicit `cache: undefined` to force cache to be disabled
- Maintains backward-compatible defaults:
- Webpack: `{ type: 'memory' }` for Node targets in watch mode
- Rspack: `true` for Node targets in watch mode, `true` in dependent
config
## Files Changed
**Webpack:**
-
`packages/webpack/src/plugins/nx-webpack-plugin/nx-app-webpack-plugin-options.ts`
-
`packages/webpack/src/plugins/nx-webpack-plugin/lib/apply-base-config.ts`
- `packages/webpack/src/executors/webpack/schema.json`
- `packages/webpack/src/executors/webpack/schema.d.ts`
- `packages/webpack/src/plugins/generate-package-json-plugin.ts`
**Rspack:**
- `packages/rspack/src/plugins/utils/models.ts`
- `packages/rspack/src/plugins/utils/apply-base-config.ts`
-
`packages/rspack/src/plugins/utils/plugins/generate-package-json-plugin.ts`
- `packages/rspack/src/executors/rspack/schema.json`
- `packages/rspack/src/executors/rspack/schema.d.ts`
**Documentation:**
- `astro-docs/.../webpack/Guides/webpack-plugins.mdoc`
- `packages/webpack/docs/webpack-build-executor-examples.md`
---------
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
When generating Cypress component testing in Angular workspaces, the
base tsconfig sets moduleResolution to 'bundler' which causes TS5095
errors because 'bundler' requires module to be 'preserve' or 'es2015+'.
Cypress runs in Node.js and should use Node.js module resolution
instead. This fix sets moduleResolution to 'node' for Cypress
tsconfig.json templates.
## Current Behavior
When generating Cypress component testing configuration in Angular
workspaces, the generated `cypress/tsconfig.json` inherits
`moduleResolution: "bundler"` from the workspace base config. Since
Cypress uses `module: "commonjs"` for Node.js runtime, this causes
TypeScript compiler error TS5095: "Option 'bundler' can only be used
when 'module' is set to 'preserve' or to 'es2015' or later."
## Expected Behavior
The generated `cypress/tsconfig.json` should explicitly set
`moduleResolution: "node"` to match the `module: "commonjs"` setting,
preventing TS5095 errors. This aligns with how NestJS applications
handle the same issue (see #33607).
---------
Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
## Summary
Adds comprehensive Docker development guidance to the Nx Daemon
documentation.
Related to #33263, #30359, #14126
## What's included
This PR expands the existing Nx Daemon docs with a new "Running Nx in
Docker" section that covers:
- **Why the daemon often fails in Docker** - ephemeral filesystems,
inode/mtime changes from volume mounts, container restarts, IPC issues
- **Recommended approach** - disable daemon with `NX_DAEMON=false`
- **Example docker-compose setup** - minimal reproducible configuration
for local development
- **CI/CD best practices** - when to prefer stateless builds over daemon
caching
## Context
Several issues have been opened around daemon behavior in containers,
but the existing docs only briefly mention socket location
customization. Users are left wondering:
- Why doesn't the daemon work reliably in Docker?
- What's the recommended workflow for containerized development?
- How should CI pipelines handle this?
Issue #33263 specifically describes daemon crashes when running nx
between Docker and non-docker environments - this PR documents the
recommended workaround (`NX_DAEMON=false`) and explains why.
## Preview
The new section appears under the existing "Customizing the socket
location" heading and includes:
- Explanation of Docker-specific challenges
- Code examples for Dockerfile, docker-compose, and CLI
- Rule of thumb callout for quick reference
Open to feedback on structure or placement.
---------
Co-authored-by: kazuki <kazuki@kazukinoMacBook-Air.local>
Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
Adds a new peerDepsVersionStrategy option that allows configuring how
peer dependencies versions are set when auto-fixing. When set to
'workspace', peer dependencies will use 'workspace:*' instead of the
installed or root package version. Defaults to 'installed' to maintain
backward compatibility.
## Current Behavior
A concrete peer dependency version is being fixed.
## Expected Behavior
User can choose between `installed` and `workspace` version strategies.
---------
Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
## Plan to Fix Inputs Documentation
Based on the issue, I need to correct the inputs documentation at
`/astro-docs/src/content/docs/reference/inputs.mdoc`. The problems are:
- [x] Fix the invalid syntax in "Source Files" section - currently shows
inputs as an object instead of an array
- [x] Document the `{ input: someNamedInput, projects: [] }` format for
referencing named inputs from specific projects
- [x] Document the `{ input: someNamedInput, dependencies: true }`
format for referencing named inputs from dependencies
- [x] Document the object form for fileset inputs (e.g., `{ fileset:
string }`)
- [x] Ensure all documented formats match the TypeScript type definition
- [x] Fix typo found in code review (to to -> to)
- [x] Remove targetDefaults wrapper from examples (per review feedback)
- [x] Use string syntactic sugar forms in examples and explain
equivalence to object forms
- [x] Fix comment to show object form equivalence instead of repeating
string form
## Changes Made
1. **Fixed Source Files section syntax**:
- Changed from invalid object syntax to correct array syntax
- Removed unnecessary targetDefaults wrapper per review feedback
- Added documentation for the object format with fileset property
2. **Added new section "Named Inputs from Other Projects"**:
- Documents `{ input: "production", projects: "mylib" }` format
- Documents `{ input: "production", projects: ["mylib", "myapp"] }`
format for multiple projects
- Uses string syntactic sugar `"production"` and `"^production"` in
examples
- Explains equivalence: `"production"` is shorthand for `{ "input":
"production" }`, `"^production"` is shorthand for `{ "input":
"production", "dependencies": true }`
3. **Fixed typo**: Changed "to to not invalidate" to "to not invalidate"
All changes validated with prettier formatting checks.
<!-- START COPILOT CODING AGENT SUFFIX -->
<details>
<summary>Original prompt</summary>
> Issue Title: Docs: correct inputs syntax and document missing formats
> Issue Description: The inputs docs appear inaccurate at
[nx.dev/docs/reference/inputs#source-files](https://nx.dev/docs/reference/inputs#source-files).
>
> * The shown inputs syntax isn’t valid; it should be an array.
> * The page doesn’t mention the `{ input: someNamedInput, projects: [\]
}` format.
> * It also doesn’t cover the object form for `deps`/`self` inputs.
>
> Valid types reference:
[https://github.com/nrwl/nx/blob/master/packages/nx/src/config/workspace-json-project-json.ts#L206](https://github.com/nrwl/nx/blob/master/packages/nx/src/config/workspace-json-project-json.ts#L206)
>
> Working with \[GitHub
Copilot\](User:d484ef82-7f7d-4a95-be09-9d82ca3905dc) on this.
> Fixes
https://linear.app/nxdev/issue/NXC-3369/docs-correct-inputs-syntax-and-document-missing-formats
>
>
> Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6:
> [https://github.com/nrwl/nx](https://github.com/nrwl/nx)
>
> Comment by User d484ef82-7f7d-4a95-be09-9d82ca3905dc:
> 📋 I wasn't able to determine which GitHub repository to work in.
>
> I think it's one of these, but can you tell me which one is right?
>
> Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6:
> Side not, copilot assignment didn't work. Would have been neat 🙂
>
> Comment by User d484ef82-7f7d-4a95-be09-9d82ca3905dc:
> Waiting for https://linear.app/nxdev/profiles/craigory to link their
GitHub account. [Click to authorize
→](https://linear.business.githubcopilot.com/linear/auth)
>
> Comment by User :
> This thread is for an agent session with githubcopilot.
>
> Comment by User :
> Created issue
[NXC-3369](https://linear.app/nxdev/issue/NXC-3369/docs-correct-inputs-syntax-and-document-missing-formats)
>
> Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6:
> This comment thread is synced to a corresponding [thread in
Slack](https://nrwl.slack.com/archives/CT3CQ2F0D/p1761762896195989?thread_ts=1761762896.195989&cid=CT3CQ2F0D).
All replies are displayed in both locations.
>
> Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6:
> @Linear create a docs issue, assign it to me and copilot
>
> Comment by User f5ae6d50-28e9-4ee7-ad51-3da8208d5914:
> Send a PR? 🙏
>
> Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6:
> See these valid types:
[https://github.com/nrwl/nx/blob/master/packages/nx/src/config/workspace-json-project-json.ts#L206](https://github.com/nrwl/nx/blob/master/packages/nx/src/config/workspace-json-project-json.ts#L206)
>
> Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6:
> Also on the same page, I don't see any mention of the `{ input:
someNamedInput, projects: [] }` format, nor the object form for deps /
self inputs
>
>
</details>
<!-- START COPILOT CODING AGENT TIPS -->
---
💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AgentEnder <6933928+AgentEnder@users.noreply.github.com>
## Current Behavior
The GitHub Actions DTE example includes a redundant conditional checkout
step that has separate configurations for pull request and default
branch events, both performing the same checkout operation.
## Expected Behavior
The GitHub Actions example should use a single, simpler checkout
configuration that works for both pull request and default branch
events, removing redundant code.
## Related Issue(s)
This change simplifies the documentation example by removing redundant
checkout steps while maintaining the same functionality.
Updated Turborepo section to include robust browser-based graph
visualizations and Graphviz image exports.
---------
Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
TypeScript’s module resolution stop at project's root when resolving
modules.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
TypeScript’s module resolution will walk up to the workspace root when
resolving modules
## Changes Made
Convert the filePath to an absolute path inside findProjectFromImport
before calling resolveImportWithTypescript, because TypeScript’s module
resolution will not correctly traverse up the directory tree toward the
workspace root when given a relative path.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes#33985
When a vitest config file is at the workspace root and contains a
`projects` property in the test configuration, the plugin now skips
inferring test targets for that config. This is because root workspace
configs act as orchestrators - the actual tests live in the individual
project configs referenced by `projects`.
Fixes#32471
## Current Behavior
The dev-server builder throws an error about the "define" option not
being supported in Angular < 21, even when users don't configure it. The
validation uses a truthy check that treats empty objects `{}` as true.
## Expected Behavior
The error should only throw when users explicitly configure define with
actual keys.
## Related Issues
Fixes#33964
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: leosvelperez <12051310+leosvelperez@users.noreply.github.com>
## Current Behavior
There is no straight-forward way to use the cwd as part of a tasks hash
## Expected Behavior
You can use `{workingDirectory: 'absolute'}` to factor the working
directory into the hash
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes#33684
On test repo, the
`package-json:createNodes:isInPackageManagerWorkspacesTime` takes:
- Before the PR: 2356ms
- With PR: 23ms
This is achieved by avoiding unnecessary use of `minimatch` when a
direct string comparison is sufficient.
It also makes creation and logging of entire graph come down from
`23.6s` down to `18.5s`
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: meeroslav <meeroslav@users.noreply.github.com>
Add support for ESLint's new bulk suppression features introduced in
v9.24.0.
- Add `suppressAll` option to suppress all existing violations
- Add `suppressRule` option to suppress specific rule(s)
- Added `suppressionsLocation` option to specify custom location for the
suppressions file (defaults to eslint-suppressions.json)
- Include proper version checking for ESLint v9.24.0+
- Add related tests and documentation
- Update schema and TypeScript types
This allows teams to incrementally adopt stricter lint rules without
being overwhelmed by legacy violations.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
The `@nx/eslint` package currently doesn't support ESLint's bulk
suppression features. When teams want to enable new lint rules, they
must fix all existing violations first, which can be a significant
barrier to adopting stricter linting standards.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
The @nx/eslint package now supports ESLint v9.24.0+'s bulk suppression
features through new flags:
- `suppressAll`: Suppresses all existing violations
- `suppressRule`: Suppresses specific rules
- `suppressionsLocation`: specifies a custom location for the
suppressions file (defaults to eslint-suppressions.json)
```json
{
"lint": {
"executor": "@nx/eslint:lint",
"options": {
"suppressAll": true
}
}
}
```
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
https://github.com/nrwl/nx/discussions/30620
This PR removes the variant check for deciding whether to use GitHub
templates for CNW. Moving forward, the first-level options are all
`nrwl/*` templates. The `Custom` option allows users to go back to the
previous presets.
Explains how to configure Tailwind CSS so that classes used in remote
applications are properly compiled by the host application. Covers both
Tailwind v3 (content array) and v4 (@source directive) configurations.
This PR fixes the `/changelog` page.
With `getStaticProps`, files were read at build time when copy-docs had
already copied them. With `getServerSideProps`, files are read at
request time on serverless functions where those files don't exist.
This PR reverts the changes to use `getServerSideProps` and uses a
middleware instead. This also has the benefit of keeping the pages
static, so we do not need edge functions to run their server function.
Fixed: https://nx-dev-git-doc-372-nrwl.vercel.app/changelog
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
## Current Behavior
Cypress Component Testing for zoneless Angular projects is not
supported.
## Expected Behavior
Cypress Component Testing for zoneless Angular projects should be
supported.
## Current Behavior
When generating Angular apps/libs with `setParserOptionsProject`, the
ESLint flat config output did not include the project-level
`parserOptions.project`, so type-aware lint rules still fail unless
users edit the config manually.
## Expected Behavior
Enabling `setParserOptionsProject` produces the appropriate
project-level `parserOptions.project` configuration in both flat ESLint
config and legacy `.eslintrc.json`, so type-aware linting works out of
the box.
## Related Issue(s)
Fixes#33944
## Current Behavior
When migrating Angular packages the `@angular/cli` package is not
updated as part of the `nx migrate` initial package updates to the
`package.json` file. Instead, it's updated at a later stage with a
migration generator. This happens for a couple of reasons:
- Angular CLI package group will update all the packages using a `^`,
which can result in workspaces getting a minor version of the packages
installed before Nx adds support for that minor version.
- Angular CLI migrations can error due to some assumptions that are not
always correct in Nx workspaces.
- The `nx migrate` command currently doesn't have the ability to ignore
the package group or migrations of a given package.
This is why the `@angular/cli` package is migrated "manually" in a
migration generator.
## Expected Behavior
The `@angular/cli` package should be updated as part of the package
updates performed by the `nx migrate` command while ignoring its package
group and migrations. The `@nx/angular` package already provides the
same set of migrations and more.
Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
## Current Behavior
Creating a new Angular workspace with Vitest results in no test setup
being generated. This happens because the `vitest` option is no longer
available, and instead, there are two options: `vitest-angular` and
`vitest-analog`.
## Expected Behavior
Creating a new Angular workspace should prompt for Vitest with Angular
or Vitest with Analog to set up the tests.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: leosvelperez <leosvelperez@users.noreply.github.com>
Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
The getDependentPackagesForProject function was crashing when processing
projects with non-npm external nodes (e.g., cargo: prefixed nodes from
@monodon/rust plugin). The code only handled npm: prefixed externals and
treated everything else as workspace libraries, causing undefined access
errors when cargo externals were encountered.
This fix adds a check to skip external nodes that aren't npm-prefixed by
detecting the presence of a colon in the dependency target. Only npm:
prefixed externals are processed as npm packages, and other external
prefixes (cargo:, maven:, etc.) are now properly skipped.
Fixes#32819
## Current Behaviour
The ignorePatternsForPlanCheck configuration option in nx.json for
version plans lacks
documentation about the pattern syntax. Users attempting to use negation
patterns (e.g.,
["*", "!src/"]) may experience unexpected behavior because gitignore
semantics don't work
as intuitively expected with such patterns.
## Expected Behaviour
The documentation and JSDoc comments now clearly explain:
- That ignorePatternsForPlanCheck follows gitignore semantics
- Working patterns like ["**/*.spec.ts"] and ["**/*.ts", "!**/src/**"]
- Non-working patterns like ["*", "!src/"] and why they don't work as
expected
- Recommended approach of using file extension patterns instead of
wildcards when trying to
ignore all files except those in specific directories
## Related Issues
Fixes#30324
---------
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Coly010 <Coly010@users.noreply.github.com>
## Current Behavior
When running single tasks we have a "minimal" tui, but that minimal tui
still makes it really hard / impossible to use some of the terminals
built in features... like:
- Find (can only find what's currently rendered by tui)
- Text select + copy (can only select what's rendered to screen, copied
text includes the frame around the tui / scrollbar)
## Expected Behavior
When running single tasks we can use an inline viewport to render some
tui widgets at the bottom of the viewport, and terminal output can be
printed above.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Disabling Maven e2e tests since something with e2e setup breaks when we
try to update the spring boot version.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
make sure canonical always points to nx.dev and not a subdomain preview
incase they're index.
<img width="1728" height="863" alt="image"
src="https://github.com/user-attachments/assets/8e7d91c1-277b-4a2a-91ec-732f7f4f37ae"
/>
also confirm that robots.txt still has deny for non prod builds.
this is done via middleware since canonical url is controlled via astro
config 'site' property. which is used to control other aspects that we
do want to be preview URL domains (like navigation). so we have a
middleware to always override the url to match prod.
also nextjs side was already overriding this in `_app.tsx`
This PR adds the ability to consumer banner data for nx.dev (both astro
and next.js) from a remote JSON file. This is only enabled if
`BANNER_URL` environment variable is set.
The `banner-config.json` files are committed for both astro and next.js,
so before we consume banner JSON from Framer, we can use this as the
source of truth rather than update the component code.
Once we switch completely to Framer CMS, we can remove the committed
JSON files.
Note: A redeploy is required for banner changes to take effect. In the
future we may be able to do this dynamically in Astro.
Co-authored-by: Claude <noreply@anthropic.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Maven plugin was complaining about Spring-boot version being behind 3.5
when running e2e suite. Also bumped down maven plugin version since
version 0.0.12 could not be found at the time of this PR.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
The inferred tasks documentation at
https://nx.dev/docs/concepts/inferred-tasks does not mention the
`include` or `exclude` options on plugins, even though this is a common
and useful feature for scoping plugins to specific projects.
## Expected Behavior
The inferred tasks documentation now includes a new section called
"Scope Plugins to Specific Projects" that explains:
- How to use `include` and `exclude` properties in plugin configuration
- What the glob patterns match against
- Use cases for this feature (scoping plugins, applying different
options to different projects)
This aligns with the existing documentation in the nx.json reference
guide.
## Related Issue(s)
Fixes DOC-367
## Current Behavior
When generating a React Module Federation remote with webpack bundler in
a TypeScript
Solution setup, the generator incorrectly sets the production webpack
config path in the
project configuration. Additionally, the sourceRoot property is not
being set in
package.json for TS Solution setups, which causes issues with module
federation's ability
to locate source files correctly.
## Expected Behavior
When using a TypeScript Solution setup:
- The production webpack config should not be explicitly set in the
build target's
production configuration (it will be inferred correctly)
- The sourceRoot property should be set in package.json under the nx
configuration to
properly identify the project's source directory
- The typecheck target should be added as a dependency for both build
and serve targets
## Related Issue(s)
Fixes#31029
## Current Behavior
The `replace-removed-matcher-aliases` migration from `@nx/jest` is not
run when migrating to Angular v21 (and updating Jest to v30). That
migration targets the original package update for Jest v30 (Nx 21.3.0),
which was incompatible with Angular < 21, so it wouldn't have run for
Angular workspaces at the time. Now that Angular is being updated to
v21, Jest is updated to v30, but the migration generator is not running.
## Expected Behavior
The `replace-removed-matcher-aliases` migration from `@nx/jest` should
run when migrating to Angular v21 (and updating Jest to v30).
## Current Behavior
The Maven version used for development dependencies is currently set to
3.9.11 in both pom.xml and mise.toml.
## Expected Behavior
The Maven version should be updated to 4.0.0-rc-5 to align with the
Maven 4 version already used in the batch-runner component.
## Related Issue(s)
N/A - This is a dependency version update to ensure consistency across
the Maven plugin ecosystem.
## Changes
- Updated `maven.version` property in pom.xml from 3.9.11 to 4.0.0-rc-5
- Updated maven tool version in mise.toml from 3.9.11 to 4.0.0-rc-5
This ensures that developers working on the Nx Maven plugin use the same
Maven 4.0.0-rc-5 version across all components.
## Current Behavior
The Maven plugin version is currently at 0.0.11.
## Expected Behavior
This PR bumps the Maven plugin version to 0.0.12 and creates a migration
for Nx 22.4.0-beta.0. This allows users to automatically update their
pom.xml files when they upgrade to the next version of Nx.
## Related Issue(s)
N/A - Version bump
## Current Behavior
The daemon currently depends on client requests failing with a
`LOCK_FILES_CHANGED` error to trigger a restart. This creates several
issues:
- The daemon may stay running with stale dependencies if no requests
come in
- The client must wait for a request to fail to trigger reconnection
- Special-case error handling is scattered across the client code
## Expected Behavior
The daemon should proactively restart itself when lock files change, and
the client should gracefully reconnect with exponential backoff for any
server shutdown scenario.
## Changes
### Client-Side: Exponential Backoff Reconnection
- Add `handleConnectionError()` method that retries with exponential
backoff (10ms → 5000ms, 30 attempts max)
- Preserve pending messages during reconnection and resend them once the
new daemon is available
- Remove special-case handling for `LOCK_FILES_CHANGED` and
`NX_VERSION_CHANGED` errors
- Remove `retryMessageAfterNewDaemonStarts()` method as it's no longer
needed
### Server-Side: Self-Restart on Lock File Changes
- Add `startNewDaemonInBackground()` to spawn a replacement daemon
before shutdown
- Add `handleServerProcessTerminationWithRestart()` for restartable
shutdown scenarios
- Detect lock file changes and proactively start a new daemon before
responding with an error
- Keep version change handling simple (just exit, no restart)
## Benefits
- **More Resilient**: Client recovers from any server shutdown, not just
specific errors
- **Cleaner Architecture**: Server manages its own lifecycle, client
doesn't need special cases
- **No Request Dependency**: Daemon doesn't wait for requests to detect
changes
- **Reduced Error Spam**: Exponential backoff prevents connection error
floods
- **Future-Proof**: Foundation for other restart scenarios (plugins,
config changes)
## Related Issue(s)
Fixes https://github.com/nrwl/nx/issues/30514
Fixes https://github.com/nrwl/nx/issues/29118
## Summary
Adds a batch executor for Nx Maven that enables parallel multi-task
execution with significant performance improvements. The batch runner
keeps Maven resident in memory, avoiding cold start overhead for each
task.
## Changes
### 1. Batch Runner JAR (`packages/maven/batch-runner`)
- **ResidentMavenExecutor**: Uses Maven 4.x's `ResidentMavenInvoker` to
keep Maven in memory
- **NxMaven**: Custom Maven wrapper that caches project graphs and
sessions across invocations
- **CachingResidentMavenInvoker**: Preserves session state so artifacts
from `jar:jar` are visible to `install:install`
- **BuildStateManager**: Applies/records build states for
cross-invocation caching
- Maven 4.x dependencies are shaded into the JAR for standalone
execution
### 2. TypeScript Executors (`packages/maven/src/executors/maven`)
- **maven.impl.ts**: Single-task executor using `mvnw`/`mvn`
- **maven-batch.impl.ts**: Batch executor that invokes the batch runner
JAR
- Automatic Maven version detection and executable resolution
### 3. Shared Utilities (`packages/maven/shared`)
- `BuildState`, `BuildStateApplier`, `BuildStateRecorder` for
cross-invocation state
- `MavenCommandResolver` for detecting Maven executable
- Reusable across batch-runner and maven-plugin modules
### 4. Maven Plugin Updates (`packages/maven/maven-plugin`)
- Updated to use `@nx/maven:maven` executor (batch-aware)
- Improved `GitIgnoreClassifier` for nested .gitignore handling
- Cache config tweaks for compiler inputs
## Performance
| Scenario | Before | After |
|----------|--------|-------|
| Cold start per task | 100-500ms | N/A (one-time init) |
| Per-task execution | 100-500ms | ~1.3ms (cached) |
| Improvement | - | **75-385x faster** |
## Version Support
- **Maven 4.x**: Full support with ResidentMavenExecutor (optimized)
- **Maven 3.x**: Falls back to ProcessBasedMavenExecutor (subprocess)
## Testing
- E2E tests for Maven 4.0.0-rc-4, 4.0.0-rc-5
- Unit tests for TypeScript executors
- Tests for GitIgnoreClassifier
## Current Behavior
Uses subprocess execution via `mvnw`/`mvn` for each task.
## Expected Behavior
Batch execution keeps Maven resident, dramatically reducing per-task
overhead.
## Related Issue(s)
Part of Maven integration improvements.
---------
Co-authored-by: Max Kless <maxk@nrwl.io>
## Current Behavior
The tui shouldn't show for single tasks
## Expected Behavior
The tui isn't shown for single tasks
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
Two issues with the .NET dependency graph:
1. Issue #33653: When a .NET project uses multi-targeting
(<TargetFrameworks> plural), its project reference dependencies
disappear from the Nx graph.
2. Issue #33397: Transitive dependencies are incorrectly shown as direct
dependencies. For example, if A → B → C, the graph shows A depending on
both B and C, when it should only show A → B.
## Expected Behavior
1. Multi-targeting projects should correctly show their dependencies in
the graph.
2. Only direct dependencies should be shown, not transitive ones. Nx
handles transitive dependencies through the dependency chain.
### Solution
Multi-targeting fix (#33653)
MSBuild creates multiple nodes for multi-targeting projects:
- An "outer build" with TargetFrameworks set but TargetFramework empty
- "Inner builds" for each target framework with TargetFramework set
The fix groups nodes by project file path and prefers inner builds
(which have properly resolved references) over outer builds.
### Transitive dependency fix (#33397)
Changed from using ProjectGraphNode.ProjectReferences (which includes
transitive dependencies in multi-targeting scenarios) to
ProjectInstance.GetItems("ProjectReference") which returns only direct
references defined in the project file.
## Related Issue(s)
Fixes#33653Fixes#33397
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Templates use `"*"` for workspace dependencies in individual
package.json files. This works for npm but breaks pnpm, yarn, and bun
which require the `workspace:` protocol for proper symlinking.
For pnpm, yarn, and bun: automatically convert `"*"` dependencies to
`"workspace:*"` in all workspace package.json files. npm is left
unchanged since it handles `"*"` natively.
Also adds support for 2-level nested projects (e.g.,
`libs/shared/models/package.json`).
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Current Behavior
Sync generators are processed in the background by the daemon server.
Their results are cached and reprocessed when the project graph is
recomputed. There are currently two issues:
- The cache is only invalidated after the project graph finishes
recomputing, which means that there's an interval between files changed
(triggering the project graph recomputation) and the recomputation
finishes, where the cache is not invalidated, and it's stale. During
that interval, any request to get the sync generator changes will use
the stale cache.
- Sync generators are scheduled to be processed after the project graph
is recomputed, so a quick succession of recomputations can be coalesced.
The problem is that the scheduled closure uses the project graph from
the initial scheduling, rather than the latest available project graph
at the time it runs. This results in the usage of stale data to process
the sync generators.
## Expected Behavior
Getting sync generators changes should always return up-to-date
information.
## Current Behavior
When database initialization fails due to filesystem issues, permission
problems, or environment incompatibilities (like WAL mode not being
supported), Nx shows generic error messages that don't provide
actionable guidance to users. For example:
- "Unable to create db lock file: PermissionDenied"
- "Unable to set journal_mode: <sqlite error>"
This makes it difficult for users to diagnose and resolve issues,
especially in restricted environments like Docker containers, network
filesystems, or WSL1.
## Expected Behavior
With this PR, database initialization errors now provide:
1. **Context-specific error messages** - Different guidance based on the
error type:
- Permission denied: Instructions about file ownership, Docker volume
permissions, and read-only filesystems
- Storage full: Suggestions to free disk space
- Already exists: Guidance about stale files from crashed processes
2. **Automatic WAL mode fallback** - When WAL journal mode is not
supported by the filesystem, Nx now automatically falls back to DELETE
journal mode instead of failing. This improves compatibility with:
- Network filesystems (NFS, CIFS)
- Some Docker volume configurations
- Other environments with limited locking support
3. **WSL1 detection** - Proactively detects WSL1 environments (which
have known WAL incompatibilities) and uses DELETE mode from the start,
avoiding failed attempts and retries.
4. **Better cleanup on retry** - When database initialization fails and
needs to retry, all auxiliary files (WAL and SHM files) are also cleaned
up, not just the main database file.
5. **Actionable reporting instructions** - All error messages now
include:
- How to capture detailed logs
(`NX_NATIVE_FILE_LOGGING=nx::native::db=trace`)
- Link to create an issue
- Suggestion to run `nx reset`
## Current Behavior
When users press unhandled keys in the TUI (e.g., pressing `i` on a
completed task, or typing in a non-interactive terminal pane), nothing
happens and there's no feedback explaining why.
Similarly, when users press certain key bindings like `c` to copy
output, the action succeeds but there's no visual confirmation.
## Expected Behavior
### Hint Popups for Unhandled Keys
Users now see helpful hint popups when pressing keys that don't work in
the current context:
- Pressing `i`, `c`, or `Ctrl+A` in the dependency view (task hasn't
started yet)
- Pressing `i` on a task that doesn't support interactive mode
- Pressing character keys in a terminal pane that's not in interactive
mode
The hints explain what's happening and guide users on how to proceed.
### Status Messages for "Invisible" Actions
When users perform actions without obvious visual feedback, a status
message now appears in the terminal pane's bottom border:
- `Output copied` when pressing `c` to copy
- `Sent to assistant` when pressing `Ctrl+A`
### Configuration Option
Users who prefer not to see hint popups can disable them in `nx.json`:
```json
{
"tui": {
"suppressHints": true
}
}
```
Current Behavior
When using pnpm catalogs to manage dependency versions, the Storybook
utilities and ESLint version-utils read package.json directly using
readJson() or readJsonFile(). This approach doesn't resolve catalog
references like catalog:default, causing version detection to fail or
return incorrect values.
For example, if package.json contains:
{
"devDependencies": {
"storybook": "catalog:default"
}
}
The current code would return "catalog:default" as the version string
instead of resolving it to the actual version (e.g., "8.5.0").
Expected Behavior
Use the getDependencyVersionFromPackageJson() helper from @nx/devkit
which properly handles pnpm catalog resolution. This ensures that
version detection works correctly regardless of whether dependencies are
specified directly or via pnpm catalogs.
The helper:
- Resolves catalog: references to their actual versions
- Falls back gracefully when catalogs aren't in use
- Maintains consistent behavior across different package managers
Related Issue(s)
Fixes issues with pnpm catalog compatibility in Storybook generators and
ESLint utilities.
Related to #29772
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Coly010 <Coly010@users.noreply.github.com>
## Current Behavior
Nx doesn't generate projects with Prettier v3.
## Expected Behavior
Nx should generate projects with Prettier v3.
## Related Issue(s)
Fixes#30801
## Current Behavior
There's a deadlock between the Angular and Jest package updates
requirements (`requires` and `incompatibleWith`) that prevents updating
Jest to v30 and `jest-preset-angular` to v16.
## Expected Behavior
Updating to Angular v21 should result in updating Jest to v30 and
`jest-preset-angular` to v16.
This is ensured by moving the `jest-preset-angular` package update
definition to the `@nx/jest` package and processing the `@nx/angular`
package updates before `@nx/jest`. That way, by the time `@nx/jest` is
processed, the migrator would have collected the Angular v21 updates,
and the requirements will be met.
## Current Behavior
The nx-dev Next.js app can only serve pages from its own codebase or
proxy to Astro docs.
## Expected Behavior
Support proxying specific pages to a Framer site via environment
variables:
- `NEXT_PUBLIC_FRAMER_URL`: Base URL of the Framer site
- `NEXT_PUBLIC_FRAMER_REWRITES`: Comma-separated paths for new pages
For existing pages like `/ai`, the proxy is handled in
`getServerSideProps`.
## Related Issue(s)
Closes DOC-349
Co-authored-by: Claude <noreply@anthropic.com>
Removed 'node_modules' from the ignore list for asset copying.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
Updating Nx to v22 breaks my app. Some projects use generated Prisma
clients which, because of how Prisma binaries work, have to be generated
to `node_modules` in order to work both locally and in Docker context.
With Nx v22 Prisma client is not copied when project is built using
`@nx/esbuild:esbuild` (or any other executor supporting `assets`
property) without any error or warning. It took mi couple of hours to
pinpoint the exact line of code responsible for this. `node_modules` dir
is hardcoded there without any possibility to be overridden.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Prisma client can be copied from `node_modules` to project output
directory when building.
Ignoring `node_modules` is removed **or can be overridden**. I'm open to
any solution which will let me update Nx in my repository.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This PR adds a validation to the directory prompt, so users should not
hit the error as often. Only possible now if they pass the arg from CLI
_and_ they are in non-interactive mode.
We also added some more data to help debug problems, such as node
version, template/preset chosen, etc. And updated the message in
"custom" preset prompt to align with the more effective "full platform"
prompt.
Note: Also update the error message (if it hits) to be the same in new
flow and old flow.
## Related Issue(s)
Closes NXC-3624
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The `argv` field was already implemented in `PreTasksExecutionContext`
and `PostTasksExecutionContext` but lacked tests and documentation for
plugin authors to discover and use it.
## Changes
- **Added unit tests**
(`packages/nx/src/daemon/server/handle-tasks-execution-hooks.spec.ts`)
validating that `argv` flows correctly through hook handlers for
different command patterns (direct, affected, run-many)
- **Enhanced existing documentation** in
`astro-docs/src/content/docs/extending-nx/task-running-lifecycle.mdoc`
with a new section covering:
- Context property definitions showing the `argv` field
- Examples showing how to detect command types (direct execution,
affected, run-many)
- Example demonstrating conditional analytics based on the original
command
- Common command patterns reference
- Best practices for defensive argv parsing
## Usage
```typescript
import type { NxPlugin, PostTasksExecutionContext } from '@nx/devkit';
export const myPlugin: NxPlugin = {
name: 'my-plugin',
postTasksExecution: async (options, context: PostTasksExecutionContext) => {
// Distinguish between nx build my-app vs nx affected -t build
if (context.argv.includes('affected')) {
console.log('Running in affected mode');
}
}
};
```
Fixes
https://linear.app/nxdev/issue/NXC-3382/add-contextargv-to-task-execution-hook-contexts
<!-- START COPILOT CODING AGENT SUFFIX -->
<details>
<summary>Original prompt</summary>
> Issue Title: Add context.argv to task execution hook contexts
> Issue Description: Expose the original CLI arguments on the plugin
worker so hooks can distinguish how execution was started (e.g., `nx
build nx-api` vs `nx affected -t build`). Proposal: include the invoking
argv on the hook context (e.g., `context.argv`).
> Fixes
https://linear.app/nxdev/issue/NXC-3382/add-contextargv-to-task-execution-hook-contexts
>
>
> Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6:
> [https://github.com/nrwl/nx](https://github.com/nrwl/nx)
>
> Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6:
> Aha! that worked - so you can tell it to assign to copilot instead of
"me and copilot"
>
> Comment by User d484ef82-7f7d-4a95-be09-9d82ca3905dc:
> 📋 I wasn't able to determine which GitHub repository to work in.
>
> I think it's one of these, but can you tell me which one is right?
>
> Comment by User :
> Created issue
[NXC-3382](https://linear.app/nxdev/issue/NXC-3382/add-contextargv-to-task-execution-hook-contexts)
>
> Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6:
> This comment thread is synced to a corresponding [thread in
Slack](https://nrwl.slack.com/archives/C070BJ2JYLW/p1761928859857989?thread_ts=1761928859.857989&cid=C070BJ2JYLW).
All replies are displayed in both locations.
>
> Comment by User :
> This thread is for an agent session with githubcopilot.
>
> Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6:
> @Linear make a ticket and assign it to copilot
>
> Comment by User f5ae6d50-28e9-4ee7-ad51-3da8208d5914:
> Makes sense to me
>
> Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6:
> Sure, context.argv?
>
> Comment by User f5ae6d50-28e9-4ee7-ad51-3da8208d5914:
> We can add them as `argv`?
>
> Comment by User f5ae6d50-28e9-4ee7-ad51-3da8208d5914:
> Yeah they would run on the plugin worker so it's not there
>
> Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6:
> @jason we could add `originalArgv` to the contexts?
>
> Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6:
> Eh, probably not... they run on the plugin worker
>
> Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6:
> Yeah, I don't think the hooks know.... I'd be curious if process.argv
would just have that info though
>
> Comment by User 439b15a6-827b-4258-971a-d86133ad59de:
> payfit does
>
> Comment by User 74901385-a023-4825-8470-fe68b1b55664:
> I can’t see anything about that in the docs - so I would assume the
hooks are agnostic to how the tasks were triggered?
>
> Comment by User 74901385-a023-4825-8470-fe68b1b55664:
> so they’re asking is there’s a way to tell the difference between `nx
build nx-api` or `nx affected -t build` in the task hook?
>
> Comment by User 74901385-a023-4825-8470-fe68b1b55664:
> > I’ve been playing around with the Task Execution Hooks, specifically
the postTasksExecution hook, and I think it will be really useful for me
to grab some detailed metrics for our specific use cases.
> > What I feel like it’s missing is a way to see what command actually
started the task execution, whether it was a specific target or an
affected command. As long as it was a specific target, I think the tasks
are sorted in order so the last taskResult will probably be the actual
target of the command but for affected it seems a bit more random what
the last result will be.
> > Is there a way to know exactly which command kicked off the ‘task
execution’?
>
>
</details>
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AgentEnder <6933928+AgentEnder@users.noreply.github.com>
Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
Co-authored-by: Caleb Ukle <caleb@nrwl.io>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Copilot <Copilot@users.noreply.github.com>
## Current Behavior
The same task can be pinned in multiple terminal panes of the TUI. This
causes one pane to scroll the other because they share the same data.
## Expected Behavior
The same task can only be pinned in a single terminal pane of the TUI.
## Current Behavior
The e2e test `should emit decorator metadata when using --compiler=swc`
fails because the regex `/Foo=.*?_decorate/` expects the old transpiled
output format where classes were assigned to variables.
## Expected Behavior
The test should pass by matching the current SWC output format which
uses native ES class syntax.
## Solution
Updated the regex from `/Foo=.*?_decorate/` to `/class
Foo.*_ts_metadata/` which:
- Matches `class Foo` (native ES class syntax)
- Verifies `_ts_metadata` is present (decorator metadata)
## Current Behavior
When `NX_NATIVE_COMMAND_RUNNER=false` is set, tasks running in the TUI
don't display any output in the terminal pane. The pane remains empty
even though the task is running.
## Expected Behavior
Task output is displayed in the TUI terminal pane regardless of the
`NX_NATIVE_COMMAND_RUNNER` setting.
## Related Issues
Fixes#32803
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
When upgrading to Angular 21 with Jest, users may encounter TypeScript
compilation issues because their `tsconfig.spec.json` files don't have
`isolatedModules: true` set, which is required for compatibility with
Jest and `jest-preset-angular`.
## Expected Behavior
After running `nx migrate`, Angular projects using Jest will
automatically have `isolatedModules: true` added to their
`tsconfig.spec.json` files (or custom test tsconfig files referenced by
`@nx/jest:jest` tasks) if not already set or inherited from a parent
tsconfig.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Update references to disablement variable for metrics collection for Nx
Cloud enterprise users.
---------
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
When using `@nx/node:library` generator with `--compiler=swc`, the
generator
was incorrectly adding `tslib` as a dependency instead of
`@swc/helpers`.
This change fixes two issues:
1. Pass the correct bundler (matching the compiler) to
jsLibraryGenerator
so it adds the correct helper dependency to the project's package.json
2. Update ensureDependencies to only add tslib when compiler is tsc
Fixes#31202
Fixes#32069
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: Colum Ferry <cferry09@gmail.com>
The Azure Pipelines schema requires fetchDepth to be a string, but the
generator was outputting a number.
This fix changes the value to a string to match the official schema
specification.
## Current Behavior
The generator creates `azure-pipelines.yml` with `fetchDepth: 0`
(number), which causes YAML schema validation error because the Azure
Pipelines schema expects `fetchDepth` to be a string.
According to the [Azure Pipelines
schema](https://raw.githubusercontent.com/microsoft/azure-pipelines-vscode/master/service-schema.json),
`fetchDepth` is defined as:
```json
"fetchDepth": {
"description": "Depth of Git graph to fetch",
"$ref": "#/definitions/string"
}
```
## Expected Behavior
The generator should output `fetchDepth: '0'` (string) to match the
schema specification and prevent validation errors.
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
## Current Behavior
If a workspace uses pnpm and adds a local patch to a dependency (e.g.
`vitest`), this patch is not taken into account when computing that
dependency's hash for purposes of determining cache changes. In
practice, you could patch vitest locally, and tests would pull from the
cache.
## Expected Behavior
Patches can alter behavior in the same way that updating the version
could, it's just that the version is not created and the patch is
applied locally.
This updates the pnpm lockfile parsing functionality to read the patches
field into a map and then combine the patch hash with the integrity
hash. The integrity hash ONLY represents the remote / tarball
intergrity, so these have to be combined in order to create a proper
key.
## Related Issue(s)
(Could not find any, but saw this in my work today)
---------
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
Due to this PR https://github.com/swc-project/pkgs/pull/53 at SWC, it is
not possible to use the latest version of @swc/cli in NX.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
stdout and stderr are now handled more precisely, allowing you to update
to the latest version of @swc/cli.
---------
Co-authored-by: Colum Ferry <cferry09@gmail.com>
## Current Behavior
When a project has a `project.json` file but no `name` field, the
`addBuildAndWatchDepsTargets` function in
`packages/js/src/plugins/typescript/util.ts` returns early without
creating build and watch deps targets, even if the project has a valid
name in its `package.json`.
## Expected Behavior
The function should fall back to checking `package.json` for the project
name when `project.json` exists but has no `name` field, allowing the
build and watch deps targets to be created properly.
## Related Issue(s)
This fixes an issue where projects with `project.json` files missing the
`name` field would not get proper build and watch dependency targets
generated.
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Colum Ferry <cferry09@gmail.com>
## Current Behavior
`getProjectPathsAffectedByDependencyUpdates` in
`@packages/nx/src/plugins/js/project-graph/affected/lock-file-changes.ts`
doesn't return projects affected when updating `pnpm.overrides` or
`overrides` in package.json.
## Expected Behavior
When `overrides`, `resolutions`, or `pnpm.overrides` fields are changed
in package.json, the affected projects should be properly detected and
returned.
## Related Issue(s)
This addresses reports that affected project detection isn't working
properly when package manager override configurations are changed.
## Changes Made
- Enhanced `getTouchedNpmPackages` function to detect changes to
`overrides`, `resolutions`, and `pnpm.overrides` fields
- When a known package is changed in overrides, only that specific
package is marked as affected
- When an unknown package is changed in overrides, all projects are
marked as affected (since overrides can affect transitive dependencies)
- Added comprehensive tests for all override scenarios
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
### Summary:
This PR updates the Nx documentation for the Storybook migration
generator to ensure clear and accurate guidance for migrating to
Storybook version 9. It replaces outdated references to version 8,
corrects migration links, and updates example configurations for Angular
and React (Vite) projects to reflect Storybook v9. These improvements
help users follow the correct steps and avoid confusion when upgrading
their workspace to the latest major release.
### Key Updates:
- Updated all migration documentation links and text to target Storybook
v9 resources.
- Corrected example .storybook/main.js|ts file descriptions for Angular
and React projects to reference version 9.
[[1]](diffhunk://#diff-2bd0403b5cc6e0c92d83a89400b36d55ad50eb0f82688aa976ecb3c93ff69eceL44-R44)
[[2]](diffhunk://#diff-2bd0403b5cc6e0c92d83a89400b36d55ad50eb0f82688aa976ecb3c93ff69eceL61-R61)
- Ensured users will be directed to the right guides and migration steps
for a smoother upgrade experience.
### Type of Change:
Documentation only; no changes to code or functionality.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
N/A - Docs update
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
N/A - Docs update
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
N/A
## Current Behavior
The `pageGenerator` has the option `fileName`, but it is not affecting
anything.
## Expected Behavior
The `pageGenerator` has the option `fileName`, it will define the page
file name.
## Current Behavior
The `recordStat` telemetry for `create-nx-workspace` and
`create-nx-plugin` completion events does not include the Nx Cloud
connect URL.
## Expected Behavior
The connect URL (e.g., `https://cloud.nx.app/connect/{shortlinkId}`) is
now included in the completion metadata sent to `/nx-cloud/stats`.
## Related Issue(s)
N/A - Internal telemetry enhancement
NODE_VERSION was set only on the mise-action step, so subsequent steps
defaulted to the wrong node version and corepack couldn't create the
pnpm shim.
Also update corepack first, otherwise you might run into intermittent
integrity check issues:
- https://github.com/nodejs/corepack/issues/612
- https://vercel.com/kb/guide/corepack-errors-github-actions
---
Before (NODE_VERSION at step-level):
1. mise-action runs with NODE_VERSION=20
- mise installs Node 20
- mise adds /home/runner/.local/share/mise/installs/node/20.x.x/bin/ to
PATH
2. corepack enable runs - NODE_VERSION is NOT set anymore (step env is
gone)
- corepack shim calls mise
- mise reads mise.toml template: node = "{{ env['NODE_VERSION'] |
default(value='24.11.0') }}"
- NODE_VERSION is unset → defaults to 24.11.0
- mise runs corepack from Node 24's install
- corepack creates pnpm shim in Node 24's bin directory
3. pnpm install runs
- PATH has Node 20's bin (from step 1)
- pnpm is in Node 24's bin (from step 2)
- pnpm not found!
After (NODE_VERSION at job-level):
1. mise-action runs with NODE_VERSION=20 (from job env)
- mise installs Node 20, adds its bin to PATH
2. corepack enable runs - NODE_VERSION=20 is still set
- corepack shim calls mise
- mise sees NODE_VERSION=20
- corepack runs from Node 20's install
- pnpm shim created in Node 20's bin
3. pnpm install runs
- PATH has Node 20's bin ✓
- pnpm is in Node 20's bin ✓
- Works!
---
Closes NXC-3620
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Current Behavior
The `linkTaskDetails` parameter was removed from the `NxCache`
constructor in #33843, which broke Nx Cloud since it still passes this
parameter.
## Expected Behavior
The `NxCache` constructor should accept the `linkTaskDetails` parameter
(even if unused) to maintain backwards compatibility with Nx Cloud.
## Related Issue(s)
Fixes
https://github.com/nrwl/nx/commit/ed09ee1daed597b7be60255f0cebe52efdd1ae69#r172894152
Updates CNW messages and removes `cancel` event from being recorded in
SIGINT handler, since it didn't work.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
When multiple MF dev servers run concurrently and share the same remote,
they would both attempt to start proxies on the same port, causing
EADDRINUSE errors.
This fix checks if a port is already in use before attempting to start
a proxy. If the port is occupied (likely by another MF dev server that
started earlier), the proxy is skipped for that remote since it's
already being served.
Uses the existing `waitForPortOpen` utility with retries: 0 to perform
an immediate check.
Fixes#33470
Update the programmatic API documentation to clarify that the dryRun
option for releasePublish does not prevent the underlying commands
from being executed. Instead, it forwards the flag to the executor
and sets the NX_DRY_RUN environment variable.
This makes it clear that:
- The built-in @nx/js:release-publish executor handles dryRun correctly
- Custom nx-release-publish executors must implement dryRun support
themselves
Fixes#33443
The publish workflow template was designed for fixed versioning strategy
(where all packages share the same version). This commit adds:
- A callout explaining that the template works best with fixed
versioning
- A new section covering considerations for independent versioning
- Documentation of GitHub's 3-tag event limitation
- Alternative approaches: workflow_dispatch, branch-based triggers, or
batch tag pushing
Fixes#33502
## Current Behavior
The `printTaskTerminalOutput` callback in the TUI summary life cycle
overwrites terminal output for all tasks when output is provided. This
causes the command line information (the actual command that was run) to
be lost for non-cached tasks because non-cached tasks stream their
output via `appendTaskOutput`, which includes the command information.
## Expected Behavior
For non-cached tasks (those with 'failure' or 'success' status), the
output should be preserved from the streaming via `appendTaskOutput`
which includes the complete command line that was executed. Only cached
tasks should have their output overwritten by `printTaskTerminalOutput`
since they don't go through the streaming path.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
## Current Behavior
NestJs libraries are generated with output path pointing to workspace
level dist folder. With TS Soln setups, we expect the dist folder to be
local to the project.
## Expected Behavior
Ensure the outputPath generated is correct
## Related Issue(s)
Fixes#32060
## Current Behavior
`nx format:write` fails with Prettier 4+ (and Prettier 3.6+ with the
experimental CLI enabled) with the error:
```bash
Incompatible options: "write" and "list-different" cannot be used together
```
## Expected Behavior
`nx format:write` works seamlessly across all Prettier versions (2.x,
3.x, and 4.x).
## Related Issue(s)
Fixes#33658Fixes#31951
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Root Maven and Gradle build files (pom.xml, build.gradle.kts,
settings.gradle.kts) are not explicitly assigned to the Java reviewers
in CODEOWNERS.
## Expected Behavior
With this PR, the Java reviewers (@FrozenPandaz @MaxKless @lourw) will
be automatically added as reviewers when changes are made to:
- `/pom.xml` - Root Maven build file
- `/build.gradle.kts` - Root Gradle build file
- `/settings.gradle.kts` - Gradle settings file
This ensures the Java team has visibility into changes affecting the
root Java build configuration.
## Related Issue(s)
N/A - Maintenance improvement to CODEOWNERS
## Current Behavior
The `/ci/recipes/enterprise/on-premise/auth-single-admin` and
`/ci/recipes/on-premise/auth-single-admin` paths redirect to
`https://github.com/nrwl/nx-cloud-helm`, but the documentation that
should exist at these paths no longer has a target location.
## Expected Behavior
These redirect rules are removed so the broken links don't mislead users
with incorrect redirects.
## Related Issue(s)
Fixes CLOUD-4007
#### Current Behavior
When creating a new Nx workspace with Angular v20 and selecting Vitest
as the unit test runner, the installation fails with an npm peer
dependency conflict:
```bash
npm error ERESOLVE could not resolve
npm error peerOptional vitest@"^3.1.1" from @angular/build@20.3.13
npm error Found: vitest@4.0.15
```
## Expected Behavior
Workspace creation completes successfully when selecting Angular with
Vitest as the unit test runner.
## Related Issue(s)
Fixes#33770
## Current Behavior
The publish workflow was installing `aarch64-apple-darwin` Rust target
but attempting to build for `x86_64-apple-darwin`, causing the build to
fail with:
```
error[E0463]: can't find crate for `core`
= note: the `x86_64-apple-darwin` target may not be installed
= help: consider downloading the target with `rustup target add x86_64-apple-darwin`
```
## Expected Behavior
The workflow should install the correct Rust target
(`x86_64-apple-darwin`) before attempting to build for it.
## Related Issue(s)
N/A - Bug found during publish workflow debugging
## Current Behavior
When running `nx g @nx/angular:cypress-component-configuration` on an
Angular project that uses esbuild (the default bundler since Angular
17), users receive a confusing error message:
```bash
Unable to find a valid build configuration. Try passing in a target for an Angular app.
```
This doesn't explain why the configuration fails or what the actual
limitation is.
## Expected Behavior
Users now receive a clear, informative error message that explains:
- Cypress Component Testing for Angular requires a webpack-based build
target
- Their project uses an esbuild-based executor (and which one)
- Cypress only supports webpack as the bundler for Angular component
testing
This helps users understand the limitation and make informed decisions
about how to proceed.
## Related Issue(s)
Fixes#33329
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
The `//` comment in the styled template [is not valid
css](https://stackoverflow.com/questions/12298890/is-it-bad-practice-to-prefix-single-lines-of-css-with-as-a-personal-comment-s/20192639#20192639)
and is causing [stylelint](https://stylelint.io/) to throw errors upon
creating new apps
## Expected Behavior
It should be valid css
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
https://github.com/nrwl/nx/issues/33579
---------
Co-authored-by: Colum Ferry <cferry09@gmail.com>
## Current Behavior
When running `nx serve` with a NestJS project (and other node apps using
`runBuildTargetDependencies`), the node executor attempts to resolve the
`nx` binary using `require.resolve('nx')`. This fails with because
`nx/package.json` does no longer has a `main` field.
## Expected Behavior
The node executor should correctly resolve and use the `nx` binary from
the workspace where it's always installed.
This is fixed by using `nx/bin/nx.js` instead of just `nx` -- as we do
in other places.
## Related Issue(s)
Fixes#33776
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Current Behavior
<!-- This is the behavior we have today -->
When a workspace library has a `name` field set in its `nx`
configuration (e.g., `"nx": { "name": "buildable" }`), the
`@nx/js:prune-lockfile` executor fails to include transitive
dependencies from that library in the pruned lockfile.
The issue occurs because `addNodesAndDependencies` attempts to retrieve
workspace nodes using `graph.nodes[name]` where `name` is the package
name from `package.json`, but `graph.nodes` is keyed by the project name
(from `nx.name`). When these differ, the lookup fails and transitive
dependencies are not traversed.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
The `@nx/js:prune-lockfile` executor should include all transitive
dependencies from workspace libraries regardless of whether the library
has a `name` field set in its `nx` configuration.
The fix uses the workspace node from the `workspacePackages` map (which
is keyed by package name) instead of attempting to look it up in
`graph.nodes` (which is keyed by project name).
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes#33582
## Current Behavior
When terminating a task using the `@nx/js:swc` executor before it ever
got to execute its post-compilation logic, a `TypeError: disposeFn is
not a function` error is thrown.
## Expected Behavior
Terminating a task using the `@nx/js:swc` executor should not error.
## Related Issue(s)
Fixes#31938
## Current Behavior
The `update-21-2-0/update-module-resolution migration` doesn't process
any tsconfig files of non-buildable libraries.
## Expected Behavior
The `update-21-2-0/update-module-resolution migration` should process
common/known tsconfig files of non-buildable libraries.
## Related Issue(s)
Fixes#33705
Currently, when SWC compilation fails, Nx logs only a generic message:
"SWC compilation failed"
There is no `error.message`, no `stderr` and no `stdout` printed.
In many cases the actual cause of failure is completely hidden, which
makes debugging very difficult, especially in CI.
This PR improves the logging by printing:
- error.message (or the error itself)
- stderr if available
- stdout if available
This makes SWC failures visible and debuggable again.
## Current Behavior
Only a generic "SWC compilation failed" message is logged. No error
message or stdout are shown.
## Expected Behavior
Include error.message, stderr, and stdout (when available) so developers
can understand and debug failures.
We're missing messages and variant when errors happen during CNW. This
will help us track down potential problems.
We also want to know when CNW is cancelled in order to know that we're
not missing any events.
Description update for @berenddeboer/nx-aws-cdk plugin: this has become
self-inferring.
---------
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
## Current Behavior
The `getModuleFederationConfig` function is implemented separately in 4
places with 70-80% duplicate code:
- `with-module-federation/webpack/utils.ts` (161 lines)
- `with-module-federation/rspack/utils.ts` (150 lines)
- `with-module-federation/angular/utils.ts` - async version (~80 lines)
- `with-module-federation/angular/utils.ts` - sync version (~80 lines)
Each implementation repeats the same core logic:
1. Get project from graph
2. Get and filter dependencies
3. Share workspace libraries and npm packages
4. Apply eager packages
5. Map remotes
## Expected Behavior
A single shared implementation with framework-specific configuration via
a `FrameworkConfig` interface. Each bundler utility becomes a thin
wrapper that provides its specific configuration.
### Changes
| File | Before | After | Change |
|------|--------|-------|--------|
| `webpack/utils.ts` | 161 lines | 54 lines | -107 lines |
| `rspack/utils.ts` | 150 lines | 51 lines | -99 lines |
| `angular/utils.ts` | 273 lines | 119 lines | -154 lines |
| **NEW** `module-federation-config.ts` | - | 289 lines | +289 lines |
**Net reduction**: 41 lines, with significantly improved maintainability
### New Shared Utility
Created
`packages/module-federation/src/utils/module-federation-config.ts` with:
- `FrameworkConfig` interface for bundler-specific customization
- `ModuleFederationConfigResult` interface for type-safe return values
- `getModuleFederationConfigAsync()` - for webpack/angular async configs
- `getModuleFederationConfigSync()` - for rspack/angular sync configs
- `createDefaultRemoteUrlResolver()` - shared remote URL generation
- Caching for `NX_MF_DEV_SERVER_STATIC_REMOTES` env variable parsing
(performance)
### Benefits
1. **Single source of truth**: Bug fixes and improvements only need to
be made once
2. **Better maintainability**: Framework-specific behavior is clearly
separated via config
3. **Performance**: Added caching for env variable parsing
4. **Type safety**: New interfaces provide better IntelliSense and
compile-time checks
5. **Backward compatible**: All existing exports and behavior preserved
## Related Issue(s)
N/A - This is a refactoring for improved code maintainability and
performance.
## Merge Dependencies
**Must be merged AFTER:** #33734
---
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
With the new tsgo `baseUrl` has been completely removed, this breaks
compilation of plugins using swc when calling them with nx due to swc
needing the baseUrl. I patched my company workspace with this fix and it
did resolve it, not sure if its the right fix or what the ramifications
are so happy to discuss that more. Since `baseUrl` is removed in tsgo
(and recommended against in general), we need to find a way to provide
it to swc (potentially through an `.swcrc` alternatively, I tried adding
that to my workspace though and it didn't do anything).
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
Expo CLI 54.0.12+ changed how originModulePath is determined in Metro
resolvers - from workspace root to project root. This caused the Nx
custom resolver to double-path modules when resolving workspace
libraries.
This fix:
- Adds projectRoot: workspaceRoot to the Metro config to ensure
originModulePath remains workspace-relative
- Adds defensive path normalization in pnpmResolver to handle edge cases
Fixes#33597
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
Self-healing docs only reference being supported for GitHub.
<!-- This is the behavior we have today -->
## Expected Behavior
We should show instructions for all currently supported vcs providers,
including GitLab and Azure Devops.
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
Replace process.exit(1) calls with typed CnwError exceptions for
structured error reporting and telemetry tracking. Update recordStat
meta to use typed JSON objects with named keys instead of arrays.
Examples of what's sent as `meta`.
```
{"type":"start","flowVariant":"1"}
{"type":"complete","flowVariant":"1","setupCIPrompt":"which-ci-provider","setupCloudPrompt":"cloud-v2-remote-cache-visit","nxCloudArg":"skip","nxCloudArgRaw":"","pushedToVcs":"SkippedGit","template":"nrwl/empty-template"}
{"type":"start","flowVariant":"1"}
{"type":"start","flowVariant":"0"}
{"type":"complete","flowVariant":"0","setupCIPrompt":"which-ci-provider","setupCloudPrompt":"enable-caching2","nxCloudArg":"skip","nxCloudArgRaw":"","pushedToVcs":"SkippedGit","template":"custom"}
{"type":"start","flowVariant":"1"}
{"type":"error","errorCode":"DIRECTORY_EXISTS"}
{"type":"start","flowVariant":"1"}
{"type":"error","errorCode":"DIRECTORY_EXISTS"}
{"type":"start","flowVariant":"1"}
{"type":"complete","flowVariant":"1","setupCIPrompt":"which-ci-provider","setupCloudPrompt":"cloud-v2-green-prs-visit","nxCloudArg":"yes","nxCloudArgRaw":"","pushedToVcs":"FailedToPushToVcs","template":"nrwl/empty-template"}
{"type":"start","flowVariant":"1"}
{"type":"complete","flowVariant":"1","setupCIPrompt":"which-ci-provider","setupCloudPrompt":"cloud-v2-fast-ci-visit","nxCloudArg":"yes","nxCloudArgRaw":"","pushedToVcs":"FailedToPushToVcs","template":"nrwl/empty-template"}
{"type":"start","flowVariant":"1"}
{"type":"start","flowVariant":"1"}
{"type":"complete","flowVariant":"1","setupCIPrompt":"which-ci-provider","setupCloudPrompt":"cloud-v2-green-prs-visit","nxCloudArg":"skip","nxCloudArgRaw":"","pushedToVcs":"SkippedGit","template":"nrwl/typescript-template"}
{"type":"start","flowVariant":"1"}
{"type":"error","errorCode":"WORKSPACE_CREATION_FAILED"}
```
Known errors like "directory exists" does not print stack trace:
<img width="1061" height="362" alt="image"
src="https://github.com/user-attachments/assets/8f29f303-3839-4297-b789-d23ac3af6d52"
/>
Another known error (invalid custom preset):
<img width="1091" height="391" alt="image"
src="https://github.com/user-attachments/assets/c7f6e586-42a8-493b-b595-f7d77743683f"
/>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
When using Nx React Module Federation with Rspack, running `nx run-many
-t e2e` before `nx run-many -t typecheck`, it causes typecheck to fail.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
`nx run-many -t typecheck` should succeed regardless of whether Rspack
(via `nx preview`) was executed before it.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes https://github.com/nrwl/nx/issues/33445
The CopyAssetsHandler was logging every copied file to the console,
causing noisy output when a project copies many files. This could
cause build errors to be cut off in terminals with scroll limits.
Change logger.log() to logger.verbose() so per-file logging only
appears when --verbose is passed or NX_VERBOSE_LOGGING=true.
Fixes#33521
The release-publish executor was only displaying npm-style errors
(error.summary
and error.detail), but pnpm returns errors with a different format
(error.code
and error.message). This caused pnpm publish errors to be invisible
unless users
passed the --verbose flag.
This fix adds handling for pnpm's error format so that error messages
are
properly displayed to users without requiring --verbose.
Fixes 33537
Adds a 'compiler' option to the @nx/js/typescript plugin configuration,
with options 'tsc' and 'tsgo'. Affects both typecheck and build targets.
## Current Behavior
The `@nx/js/typescript` plugin always uses `tsc` as the compiler, with
no way to use the native `tsgo` preview.
## Expected Behavior
The `@nx/js/typescript` plugin can be configured to use `tsgo` for
building and typechecking.
## Related Issue(s)
Related discussion #32591.
This pull request introduces a new option to the Nx Webpack plugin that
allows users to control whether the plugin should merge its external
dependencies configuration with any existing Webpack externals
configuration. This provides greater flexibility when customizing how
external dependencies are handled during the build process.
Configuration enhancements:
* Added a new `mergeExternals` boolean option to the
`NxAppWebpackPluginOptions` interface, allowing users to specify whether
to combine the plugin's externals configuration with the existing
Webpack config.
* Updated the logic in `apply-base-config.ts` so that the `externals`
array is set based on the new `mergeExternals` option, defaulting to not
merging unless specified.
---------
Co-authored-by: David Antoon <davidmantoon@gmail.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Currently the template flow will only set up `npm`, even if you run
`yarn create` or `pnpx create-nx-workspace`. This PR adds support back
for other package managers.
## Current Behavior
When we prepare ENVs for the DefinePlugin, we are creating the
`process.env` object.
For example:
```
// .env
NX_PUBLIC_VALUE1=1
NX_PUBLIC_VALUE2=2
NX_PUBLIC_VALUE3=3
```
As result we will have:
```js
{
'process.env': {
"NX_PUBLIC_VALUE1": "1",
"NX_PUBLIC_VALUE2": "2",
"NX_PUBLIC_VALUE3": "3"
}
}
```
As a result, in the final bundle, we will replace process.env with this
object.
The issue:
If I use all 3 values in my application DefinePlugin will inject this
object 3 times, instead of injecting it once.
It will look like that:
```js
const a = {
"NX_PUBLIC_VALUE1": "1",
"NX_PUBLIC_VALUE2": "2",
"NX_PUBLIC_VALUE3": "3"
}.NX_PUBLIC_VALUE1
const b = {
"NX_PUBLIC_VALUE1": "1",
"NX_PUBLIC_VALUE2": "2",
"NX_PUBLIC_VALUE3": "3"
}.NX_PUBLIC_VALUE2
const c = {
"NX_PUBLIC_VALUE1": "1",
"NX_PUBLIC_VALUE2": "2",
"NX_PUBLIC_VALUE3": "3"
}.NX_PUBLIC_VALUE3
```
## Expected Behavior
DefinePlugin injects values instead of env object in each place
```js
const a = "1"
const b = "2"
const c = "3"
```
## Fixes
- fixed this issue for webpack
- fixed this issue for storybook
- fixed this issue for rspack
TLDR:
now we have object like so:
```js
{
"process.env.NX_PUBLIC_VALUE1": "1",
"process.env.NX_PUBLIC_VALUE2": "2",
"process.env.NX_PUBLIC_VALUE3": "3"
}
```
---------
Co-authored-by: Colum Ferry <cferry09@gmail.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
NodeNext is not recognized as ESM. This causes this warning message to
be logged, even when you have `"type": "module",` in the `package.json`
file and are compile TypeScript to `"module": "NodeNext"`.
```
Package type is set to "module" but "cjs" format is included. Going to use "esm" format instead. You can change the package type to "commonjs" or remove type in the package.json file.
```
## Expected Behavior
Don't log this message. It is incorrect.
---------
Co-authored-by: Colum Ferry <cferry09@gmail.com>
This pull request refactors the `dependsOn` configuration for Jest
targets in the Nx plugin to improve flexibility and maintainability. The
changes replace string-based dependencies with structured objects,
ensuring better alignment with Nx's target configuration standards.
This PR updates the `dependsOn` configuration for Jest `ciTarget` to
ensure that top-level args are passed on if the parent target has a
dependsOn for other targets.
For example if i pass `nx run-many e2e-ci -- --json
--outputFile=my-test-results.json` the options:
- `--json`
- `--outputFile`
Should be forwarded to the dependent targets.
---------
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
The check should be for `GITHUB_ACTIONS` since that is the intention.
The token may not be set due to Trusted Publisher flow.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
…new convention
## Current Behavior
Currently, the @nx/vite plugin generates a `vite.config.ts` file where
the worker configuration is commented out, but uses the old format:
```ts
// worker: {
// plugins: [ nxViteTsPaths() ],
// }
```
If uncomment, this format triggers a warning from Vite, as the worker
configuration should now be a function that returns an array of plugins.
While Vite automatically converts the old format for compatibility, it
is not ideal to rely on this behavior.
## Expected Behavior
With the changes in this PR, the @nx/vite plugin will generate a Vite
configuration where the worker configuration follows the new convention,
avoiding warnings and ensuring compatibility with future versions of
Vite. The updated configuration will look like this:
```ts
// worker: {
// plugins: () => [ nxViteTsPaths() ],
// }
```
This change ensures that the generated configuration aligns with Vite's
recommended practices and eliminates unnecessary warnings.
---------
Co-authored-by: Colum Ferry <cferry09@gmail.com>
The NxAppWebpackPlugin option 'verbose' should show chunk output during
webpack build when true, and hide them when false. It's currently the
reverse, causing a lot of console spam during dev, and the hiding the
info during ci/cd.
## Current Behavior
Setting the NxAppWebpackPlugin option 'verbose' to false shows chunk
output.
<img width="848" alt="chunky"
src="https://github.com/user-attachments/assets/4cd5502d-d059-4ace-9e42-28eb160bc1d0"
/>
## Expected Behavior
Setting the NxAppWebpackPlugin option 'verbose' to false hides chunk
output.
Resolve issue when
projectRoot was created from
join workspaceRoot and projectNode.data.root
but method createTmpTsConfig also make join
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
```
method withNx create
const projectRoot = join(workspaceRoot, projectNode.data.root);
```
method createTmpTsConfig
apply also join
```
const tmpTsConfigPath = join(
workspaceRoot,
'tmp',
projectRoot,
process.env.NX_TASK_TARGET_TARGET ?? 'build',
`tsconfig.generated.${randomUUID()}.json`
);
```
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes#31522
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
No option to manage opening html report after run.
## Expected Behavior
Added option to manage opening html report after run.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
## Current Behavior
The nightly-2025-12-10 Rust version removed `mtim()` from the WASI
`MetadataExt` trait, causing the WASM build to fail with:
```
error[E0599]: no method named `mtim` found for reference `&std::fs::Metadata` in the current scope
```
## Expected Behavior
WASM builds should compile successfully.
## Solution
Revert to `nightly-2025-05-09` which still has the `mtim()` API
available in the WASI `MetadataExt` trait.
## Current Behavior
The `build:wasm` script uses `rustup override set nightly-2025-12-10` to
set the Rust toolchain. However, when mise is configured to manage Rust
(e.g., `rust = "1.90.0"` in `mise.toml`), it sets the `RUSTUP_TOOLCHAIN`
environment variable which has higher precedence than directory
overrides.
This causes the WASM build to fail with:
```
error[E0554]: `#![feature]` may not be used on the stable release channel
```
## Expected Behavior
WASM builds should use nightly Rust regardless of mise configuration.
## Solution
Set `RUSTUP_TOOLCHAIN=nightly-2025-12-10` directly in the script, which
overrides any existing env var from mise or other sources.
## Current Behavior
The Nx Console settings reference lacks explanation of VSCode's user vs
workspace settings and incorrectly states that Project Viewing Style is
unavailable in JetBrains IDEs.
## Expected Behavior
Nx consoles are documented for vscode/jetbrains editors
https://deploy-preview-33363--nx-docs.netlify.app/docs/reference/nx-console-settings
fixes: DOC-315
---------
Co-authored-by: Claude <noreply@anthropic.com>
## Current Behavior
After the mise migration (#33772), the `publish.yml` workflow fails
during the "Build Wasm" step with:
```
error[E0554]: `#![feature]` may not be used on the stable release channel
--> packages/nx/src/lib.rs:2:33
|
2 | #![cfg_attr(target_os = "wasi", feature(wasi_ext))]
```
The WASM build requires nightly Rust because it uses the unstable
`wasi_ext` feature. The `build:wasm` script attempts to switch to
nightly via `rustup override set`, but after the mise migration, the
nightly toolchain is no longer pre-installed, causing the build to fail
with the stable compiler.
## Expected Behavior
The WASM build should successfully compile using nightly Rust with the
`wasi_ext` feature.
## Related Issue(s)
Fixes the publish workflow regression introduced in #33772
| | Before | After |
| ---- | ----- | ---- |
| Total createNodes | 812 | 104 |
| Total matchPropValue | 672 | 2 |
## Current Behavior
The `createNode` function is slow for pnpm due to suboptimal
`matchPropValue` function.
## Expected Behavior
The `createNode` function should be fast and not slowdown the graph
creation.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
When some tasks fail during execution, it can result in a cryptic:
```bash
Failed to convert JavaScript value 'Undefined' into rust type 'String'
```
This happens because the reported `terminalOutput` for the errored tasks
can be `undefined`, and it hides the actual error that occurred.
## Expected Behavior
Error handling paths should always provide a valid string for
`terminalOutput`, using the error stack/message, or an empty string as a
fallback.
## Related Issue(s)
Fixes#32675
## Current Behavior
The inline `mise_toml` config in `e2e-matrix.yml` was overwriting the
entire `mise.toml` file, causing rust, dotnet, bun, and java to NOT be
installed by mise. This resulted in slow package installs as these tools
were downloaded during `pnpm install` instead.
## Expected Behavior
All tools from `mise.toml` (rust, dotnet, bun, java) should be installed
by mise, with only the node version varying based on the matrix.
## Solution
Use mise's template syntax to make node version configurable via
`NODE_VERSION` env var while preserving all other tools from
`mise.toml`:
```toml
node = "{{ env['NODE_VERSION'] | default(value='24') }}"
```
Then in the workflow, set the env var instead of overriding the entire
config:
```yaml
- name: Setup dev tools with mise
uses: jdx/mise-action@v3
env:
NODE_VERSION: ${{ matrix.node_version }}
```
## Related Issue(s)
Fixes slow install times in nightly e2e-matrix workflow after #33772.
## Current Behavior
The mise vfox-dotnet plugin fails on Windows with:
```
mise ERROR Failed to install vfox:mise-plugins/vfox-dotnet@9:
0: error converting Lua table to PreInstall (no version returned from vfox plugin)
```
This prevents dotnet from being installed via mise on Windows CI
runners.
## Expected Behavior
Dotnet should install successfully on all platforms including Windows.
## Related Issue(s)
Related upstream issue: https://github.com/jdx/mise/discussions/4738
## Solution
1. **mise.toml** - Made dotnet installation conditional on Linux/macOS
only using the `os` option
2. **.github/workflows/publish.yml** - Added `winget install` to install
.NET SDK 9 on Windows CI runners
This approach works around the buggy vfox plugin by using the native
Windows package manager instead.
## Current Behavior
When building Angular libraries with `ng-packagr` >20.3.0, the build
fails with:
```bash
TypeError: Cannot read properties of undefined (reading 'outputCache')
```
This occurs because `ng-packagr` v20.3.1 introduced a memory
optimization
([ng-packagr#3172](https://github.com/ng-packagr/ng-packagr/pull/3172))
that calls `dispose()` on entry points after they're processed, setting
`entry.cache = undefined`.
Nx's custom `writeBundlesTransform` was iterating over **all** entries
in the graph, including already-disposed entry points, causing the crash
when accessing their cache.
## Expected Behavior
Angular library builds should succeed with `ng-packagr` >20.3.0,
including libraries with secondary entry points. Workspaces using lower
versions of `ng-packagr` should remain unaffected.
## Solution
Align with `ng-packagr`'s own pattern by using
`graph.find(isEntryPointInProgress())` to process only the currently
in-progress entry point, rather than iterating over all graph entries.
**Key changes:**
- Use `isEntryPointInProgress()` instead of iterating all entries with
`isEntryPoint()`
- Remove unused `BuildGraph` import (no longer creating a new graph)
- Update package node only when processing the primary entry point (more
efficient)
- Return nothing from the transform (original graph passes through, same
as ng-packagr)
This approach:
- Matches `ng-packagr`'s `writeBundlesTransform` implementation pattern
- Only accesses cache of the in-progress entry point (guaranteed not to
be disposed)
- Works with all supported `ng-packagr` versions (v19+) since
`isEntryPointInProgress()` has been available since v19
## Related Issue(s)
Fixes#33560
This PR fixes an issue with CNW where the initial `recordStat` call is
not working due to a logic error on passing `directory` that isn't
initialized yet.
## Current Behavior
If there are issues with values passed to generators via prompt, we
still see a green output and exit code 0.
A colleague found out about this by pressing `Ctrl+C` when being
prompted for parameters for a generator, and this led to the CLI simply
continuing execution and showing no issue.
## Expected Behavior
CLI fails.
Allows setting a default output style instead of having to include it on
every command
Closes#27490
## Current Behavior
We must specify --outputStyle on every command
## Expected Behavior
Should allow overriding the default with an environment variable
## Related Issue(s)
#27490Fixes#27490
When 8+ dependent tasks exist, each adds an `exit` listener to track
completion. This listener attaches to `process`. So we apply the same
fix that worked for `stdout` and `stderr`, and was merged through [PR
16693](https://github.com/nrwl/nx/pull/16993)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
For `nx` targets with 8+ dependent targets, we encounter
`maxListenersExceededWarning`:
```
(node: 22553) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 SIGINT listeners added to [process]. MaxListeners is 10. Use emitter.setMaxListeners() to increase limit.
(node: 22553) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 SIGTERM listeners added to [process]. MaxListeners is 10. Use emitter.setMaxListeners() to increase limit.
(node: 22553) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 SIGHUP listeners added to [process]. MaxListeners is 10. Use emitter.setMaxListeners() to increase limit.
```
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
No `MaxListenersExceededWarning` should be thrown on account of the nx
run.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes#32439
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
## Current Behavior
In `filterAffectedProjects`, a **new visited Set is created for each
touched project**:
```typescript
ctx.touchedProjects.forEach((p) => {
addAffectedNodes(p, reversed, result, new Set()); // NEW Set per project!
});
ctx.touchedProjects.forEach((p) => {
addAffectedDependencies(p, reversed, result, new Set()); // NEW Set per project!
});
```
This defeats the purpose of the visited Set for deduplication. If
projects A and B both depend on shared project C, then C gets visited
**twice**.
## Expected Behavior
Share a single visited Set across all touched projects:
```typescript
const visitedNodes = new Set<string>();
const visitedDeps = new Set<string>();
for (const p of ctx.touchedProjects) {
addAffectedNodes(p, reversed, result, visitedNodes); // SHARED Set
}
for (const p of ctx.touchedProjects) {
addAffectedDependencies(p, reversed, result, visitedDeps); // SHARED Set
}
```
## Performance Impact
```
Before (separate Sets): After (shared Sets):
┌─────────────────────────┐ ┌─────────────────────────┐
│ touchedProjects: [A,B] │ │ touchedProjects: [A,B] │
└───────────┬─────────────┘ └───────────┬─────────────┘
│ │
┌───────┴───────┐ ┌───────┴───────┐
▼ ▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐
│ A │ │ B │ │ A │ │ B │
│visited│ │visited│ │ │ │ │
│= {} │ │= {} │ │ shared visitedNodes │
└───┬───┘ └───┬───┘ └───┬───┘ └───┬───┘
│ │ │ │
▼ ▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────────────────────┐
│visit C│ │visit C│ │ visit C (ONCE) │
│(dup!) │ │(dup!) │ │ skip C from B │
└───────┘ └───────┘ └───────────────────────┘
Complexity: Complexity:
O(touched × shared_deps) O(total_nodes)
```
**Example**: With 50 touched projects sharing 100 common dependencies:
- Before: 50 × 100 = 5,000 node visits
- After: ~150 node visits (each node visited once)
## Why Accept This PR
1. **Bug-like behavior**: The current code defeats the purpose of the
visited Set
2. **Significant impact**: Affects every `nx affected` command
3. **Zero risk**: Same traversal logic, just shared deduplication
4. **Common scenario**: Monorepos often have shared dependencies (utils,
types, etc.)
## Related Issue(s)
Contributes to #32265
## Merge Dependencies
This PR has no dependencies and can be merged independently.
---
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
Co-authored-by: khalilou88 <32600911+khalilou88@users.noreply.github.com>
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
## Summary
Migrates GitHub workflows to use [mise](https://mise.jdx.dev/) for
managing development tool installations instead of individual setup
actions.
## Changes
- **mise.toml**: Added pnpm@10.11.1 to tool configuration
- **ci.yml**: Replaced pnpm, node, rust, and java setup actions with
`mise-action@v3` in both Linux and macOS jobs
- **e2e-matrix.yml**: Replaced all tool setup actions with mise-action
in preinstall and e2e jobs
- **publish.yml**: Replaced tool setup actions with mise-action in build
and publish jobs
- **codeql workflows**: Updated to use `mise-action@v3`
## Benefits
- **Single source of truth**: All tool versions defined in `mise.toml`
- **Faster CI setup**: Mise provides better caching than individual
actions
- **Consistency**: Same tool versions across local dev and CI
- **Easier maintenance**: Update versions in one place
## Test Plan
- [ ] CI workflow passes on Linux
- [ ] CI workflow passes on macOS
- [ ] E2E matrix builds successfully
- [ ] Publish workflow can run (test with dry-run if possible)
- [ ] CodeQL scans complete successfully
Fixes #ISSUE_NUMBER
## Current Behavior
We do not have a guide showing how to use Vitest with custom conditions
## Expected Behavior
Add a guide showing how to use Vitest with custom conditions
The Plugin Registry page in astro-docs displays a static grid of plugins
without any search or filtering capabilities, making it hard to find
specific plugins.
Users can search plugins by name or description, and sort by release
date, npm downloads, GitHub stars, or Nx version compatibility.
<img width="975" height="1059" alt="image"
src="https://github.com/user-attachments/assets/a08e2a12-697e-4e3b-b9c0-2983d50fdad8"
/>
Closes DOC-343
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
## Current Behavior
The fileURLToPath and dirname imports are only used in the
getAbsolutePath function, which is only generated for non-Angular
Storybook projects.
## Expected Behavior
This change prevents unnecessary imports from being added to Angular
Storybook configurations.
## Current Behavior
When using pnpm with workspace protocol (`workspace:*`, `workspace:^`,
etc.), module federation sets `requiredVersion` to the raw protocol
string (e.g., `workspace:*`) instead of resolving it to the actual
semver version from the library's package.json.
This causes issues like:
- `requiredVersion: "workspace:*"` which is not a valid semver
- Module federation failing to properly share workspace libraries
- Warnings about unable to find required versions
Example of the broken output:
```json
{
"version": "*",
"singleton": true,
"requiredVersion": "^*"
}
```
## Expected Behavior
When a workspace protocol version is detected, it should be resolved to
the actual version from the library's package.json:
```json
{
"version": "2.0.0",
"singleton": true,
"requiredVersion": "2.0.0"
}
```
## Changes Made
1. **Added helper functions** in `share.ts`:
- `isWorkspaceProtocolVersion()` - Detects workspace protocol versions
(`workspace:*`, `workspace:^`, `*`, `file:`)
- `normalizeWorkspaceProtocolVersion()` - Resolves protocol versions to
actual semver by looking up the library's package.json
2. **Applied normalization in `shareWorkspaceLibraries()`**:
- After getting version from `getDependencyVersionFromPackageJson`,
normalize it if it's a workspace protocol
- Simplified the `workspaceLibrariesAsDeps` loop by using the helper
function (removed duplicated logic)
3. **Updated `getNpmPackageSharedConfig()`**:
- Added a check to warn and return undefined when workspace protocol
versions are passed
- Helps users understand that workspace libraries should be configured
properly
4. **Added comprehensive tests** (13 new tests):
- 6 tests for workspace protocol version normalization in
`shareWorkspaceLibraries`
- 7 tests for `getNpmPackageSharedConfig` handling workspace protocol
versions
## Related Issue(s)
Fixes#31397
## Merge Dependencies
This PR has no dependencies and can be merged independently.
**Must be merged BEFORE:** #33734
---
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
We would add the gradle project graph plugin to your build.gradle files
if we did not already detect it. However, this did mechanism did not
recognize aliases for the project graph plugin that came from version
catalogs.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
When adding the project graph plugin to build.gradle.kts, we check if a
version catalogue exists, and if it does we add the alias for the
project graph plugin.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
## Current Behavior
The `hasPath` function in `graph.ts` uses an array with `indexOf()` for
tracking visited nodes during recursive graph traversal:
```typescript
function hasPath(graph, target, node, visited: string[]) {
for (let d of graph.dependencies[node] || []) {
if (visited.indexOf(d.target) > -1) continue; // O(n) lookup
visited.push(d.target);
// recursive call...
}
}
```
This results in O(n) lookups per node visited, making worst-case
traversal O(n²).
## Expected Behavior
Use `Set` for O(1) visited node tracking:
```typescript
function hasPath(graph, target, node, visited: Set<string>) {
for (const d of graph.dependencies[node] || []) {
if (visited.has(d.target)) continue; // O(1) lookup
visited.add(d.target);
// recursive call...
}
}
```
## Performance Impact
```
Before (Array + indexOf): After (Set + has):
┌─────────────────────────┐ ┌─────────────────────────┐
│ hasPath() called │ │ hasPath() called │
└───────────┬─────────────┘ └───────────┬─────────────┘
│ │
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐
│ visited.indexOf(target) │ │ visited.has(target) │
│ O(n) lookup │ │ O(1) lookup │
└───────────┬─────────────┘ └───────────┬─────────────┘
│ │
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐
│ visited.push(target) │ │ visited.add(target) │
│ O(1) │ │ O(1) │
└───────────┬─────────────┘ └───────────┬─────────────┘
│ │
▼ ▼
Complexity: O(n²) Complexity: O(n)
for full traversal for full traversal
```
**Example with 500 nodes:**
- Before: 500 nodes × avg 250 indexOf lookups = ~125,000 comparisons
- After: 500 nodes × 1 Set lookup each = 500 operations
## Why Accept This PR
1. **Zero risk**: Same semantics, just faster data structure
2. **Standard pattern**: Set is the idiomatic choice for visited
tracking in graph algorithms
3. **Measurable impact**: Graph filtering with `--focus` flag will be
significantly faster on large monorepos
## Related Issue(s)
Contributes to #32265
## Merge Dependencies
This PR has no dependencies and can be merged independently.
---
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Correcting docs to mention that resource collection on Nx Cloud will be
availabe from 22.2 onwards.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
When using `nx release version --preid=alpha`, the version determination
now correctly considers both prerelease tags AND stable release tags to
determine the "latest" version. Previously, it would only look at
prerelease tags matching the preid, ignoring stable releases that should
have become the new baseline.
For example, with tags: 1.1.0, 1.1.0-alpha.0, 1.1.0-alpha.1, 1.1.1
- Before: Would return 1.1.0-alpha.1 → bump to 1.1.0-alpha.2
- After: Returns 1.1.1 (stable >= preid base) → bump to 1.1.2-alpha.0
Fixes#33343
## Current Behavior
The Maven plugin version is currently at 0.0.10 across all pom.xml files
in the repository.
## Expected Behavior
With this PR, the Maven plugin version will be updated to 0.0.11. This
includes:
- Updating the version in the root pom.xml
- Updating the version in packages/maven/maven-plugin/pom.xml
- Updating the mavenPluginVersion constant in
packages/maven/src/utils/versions.ts
- Adding a migration script to automatically update user pom.xml files
from 0.0.10 to 0.0.11
## Related Issue(s)
N/A - Version bump for the Maven plugin
Webpack e2e tests occasionally hang in CI without clear indication of
where the test gets stuck.
Debug logs will help identify which step the test hangs at, making it
easier to diagnose and fix the root cause.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
Current gradle plugin version is at 0.1.9
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Take gradle plugin to 0.1.10
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
Pressing Cmd+K (or Ctrl+K) on non-docs pages does nothing. The Pagefind
search is only accessible when already on the documentation pages.
Pressing Cmd+K on any non-docs page redirects to the docs and
automatically opens the search modal with focus on the input field.
https://www.loom.com/share/72fcee74620640cab28639e9ad33d962
Closes DOC-314
Co-authored-by: Claude <noreply@anthropic.com>
## Current Behavior
The `tools/update-repos/config/repos.json` configuration has the ocean
repository set to use `npm` as its package manager.
## Expected Behavior
The ocean repository should be configured to use `pnpm` as its package
manager, reflecting the actual package manager used by the repository.
## Related Issue(s)
N/A - Configuration update to reflect actual repository state.
This PR adds `SIGINT` handling when user kills the process via `Ctrl+C`
during CNW. This only prints when the workspace setup is complete, and
we also print the Cloud onboarding URL if it has been set up.
Also fixes an issue where `selectedRepositoryName` is never sent during
CNW.
---------
Co-authored-by: Claude <noreply@anthropic.com>
## Current Behavior
you can mark some targets generated by gradle by specifying options.
## Expected Behavior
you can apply a prefix to all targets generated by gradle. This is
useful for targetDefaults, for example.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Adds a Gradle option to prefix all generated Nx target names
(including dependencies and CI targets) with tests to avoid
double-prefixing.
>
> - **Gradle project graph**:
> - Apply optional `targetNamePrefix` to all target names and dependency
rewrites in `createNodeForProject`, `processTargetsForProject`, and
`getDependsOnForTask`.
> - Wire prefix through plugin/task: read in
`NxProjectGraphReportPlugin`, expose on `NxProjectReportTask`, and pass
to processing functions.
> - Ensure CI targets (`ciTestTargetName`, `check-ci`, `build-ci`) are
correctly prefixed and not double-prefixed; update dependency
replacement logic accordingly.
> - Add logging for prefix usage.
> - **Plugin options (TS)**:
> - Extend `GradlePluginOptions` with `targetNamePrefix` in
`packages/gradle/src/plugin/utils/gradle-plugin-options.ts`.
> - **E2E tests**:
> - Add tests validating prefixed targets exist and run, and that CI
test targets are not double-prefixed in `e2e/gradle/src/gradle.test.ts`.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
9a5160b8855a32d6c13c33c97379e7aa143a0737. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Key optimisations:
1. Pre-computed PackageIndex - Built once during lockfile parsing:
- byName: Map from package name → array of versions (O(1) lookup)
- workspaceNames: Set of workspace package names (O(1) lookup)
- workspacePaths: Set of workspace paths (O(1) lookup)
- packagesWithWorkspaceVariants: Set of packages with workspace-specific
variants (O(1) lookup)
- patchedPackages: Set of patched package names (O(1) lookup)
2. findResolvedVersion: Changed from O(n) scan through all packages to
O(1) map lookup + O(k) where k = number of versions for that package
(typically 1-3)
3. isWorkspacePackage: Changed from O(n) scan to O(1) set lookup
4. hasWorkspaceSpecificVariant: Changed from O(n) scan to O(1) set
lookup
5. isNestedPackageKey: Now uses pre-computed workspace paths/names sets
instead of computing them each call
On my 40 project typescript monorepo my time goes from about 30 seconds
to 4.5s, a speed-up of 6-7x.
---------
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
This PR simplifies the CNW process so we only prompt for a starter (TS,
NPM Packages, React, Angular) and we clone a full example to showcase Nx
monorepo for the given starter. This speeds up CNW drastically and
allows users to get the workspace in 5-10 seconds vs 1-3 minutes.
Users can choose `Custom` to fall back to the previous prompts, which
will ask framework, unit test runner, e2e runner, etc.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Gradle multi-module projects have project names with colons (e.g.,
`:common:iam-client`)
which are invalid in git tag names. This adds a
`sanitizeProjectNameForGitTag()` function
that replaces colons with slashes and other invalid git ref characters
with hyphens.
The sanitization is applied when:
- Creating git tags in `createGitTagValues()`
- Creating the `ReleaseVersion` class gitTag property
- Matching existing tags in `getLatestGitTagForPattern()`
Fixes#33262
## Current Behavior
We only support Expo 53
## Expected Behavior
Add support for Expo 54
Allow existing workspaces wishing to remain on Expo 53 to continue to be
supported
Add migrations allowing LLMs to handle migrating from Expo 53 to Expo 54
## Related Issue(s)
Closes NXC-3526
---------
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Coly010 <Coly010@users.noreply.github.com>
## Current Behavior
Rebuild logs during serve of Angular Rspack applications are logged an
exponential number of times with each change.
## Expected Behavior
Log only once
## Current Behavior
We currently install an outdated version of `@emotion/styled` that
causes Typecheck issues.
## Expected Behavior
Use latest version of Emotion
## Related Issue(s)
Fixes#31252
## Current Behavior
Angular Rspack outputs ESM for build and serve. However, with serve, it
causes issue for HMR.
## Expected Behavior
Use CJS for serve to allow HMR to work correctly
## Related Issue(s)
Fixes#33106
## Expected Behavior
Generate only `vitest.config.mts` file when not bundling with Vite
Use `vite.config` file if it exists already
Add `testMode` option to the `@nx/vitest` Inference Plugin to allow
easier switching between `vitest` and `vitest run`.
## Related Issue(s)
Fixes NXC-3334
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Removes legacy additions of `.cursor/rules/nx-rules.mdc` and
`.github/instructions/nx.instructions.md` to `.gitignore` across code,
templates, and migrations.
>
> - **.gitignore behavior**:
> - Remove logic in
`packages/nx/src/command-line/init/implementation/utils.ts` that
appended `.cursor/rules/nx-rules.mdc` and
`.github/instructions/nx.instructions.md`.
> - Clean up root `.gitignore` to exclude those entries.
> - Update new workspace templates
(`packages/workspace/.../__dot__gitignore`) to omit those entries.
> - **Migrations**:
> - Remove migration `21-1-0-add-ignore-entries-for-nx-rule-files` from
`packages/nx/migrations.json` and delete its implementation and spec.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
7863fa5beee127777f819a5155ae17239c5b16cd. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## Current Behavior
Trying to create a new workspace is failing on npm peer dep conflicts
when trying to use Vitest with Angular.
The version of `@analogjs/vitest-angular` only supports Vitest <4.
## Expected Behavior
Update to latest version of `@analogjs/vitest-angular` to support Vitest
4
## Related Issue(s)
Fixes#33602
## Current Behavior
`buildLibsFromSource` defaults to true only in the `@nx/rollup:rollup`
executor.
In the normalize options helper for the executor, it is not handled at
all. Programmatic usage would therefore result in `undefined`.
For pure Inference Plugin usage, `buildLibsFromSource` is also not
handled in normalize options.
Therefore, it always defaults to `undefined`.
## Expected Behavior
To reduce breaking changes, force `buildLibsFromSource` to be false for
Inference Plugin usage.
Explicitly set it in executor's normalize options helper to true to
match the schema default.
## Related Issue(s)
Fixes NXC-3537
# Fix: Pass releaseGroupName to getLatestGitTagForPattern for proper tag
resolution
## Problem
When using `releaseTag.pattern: "{releaseGroupName}@{version}"`, Nx
fails to resolve versions from git tags because the `{releaseGroupName}`
placeholder is not interpolated.
For example, with git tag `my-group@2.9.0`:
- **Expected**: Extract version `2.9.0`
- **Actual**: Extracts `"my-group"` → Error: `Invalid semver version
'my-group' provided`
## Root Cause
In `release-graph.ts:627-631`, only `projectName` is passed to
`getLatestGitTagForPattern()`, missing `releaseGroupName` needed for
interpolation.
## Solution
Pass `releaseGroupName` to the interpolation data (1-line change at
`release-graph.ts:631`):
```typescript
latestMatchingGitTag = await getLatestGitTagForPattern(
releaseTagPattern,
{
projectName: projectGraphNode.name,
releaseGroupName: releaseGroupNode.group.name, // ✅ Added
},
{ ... }
);
```
## Backward Compatibility
✅ Fully backward compatible:
- `releaseGroupNode.group.name` is always defined (user-defined or
`"__default__"`)
- Unused interpolation data is safely ignored
- Existing patterns (`v{version}`, `{projectName}@{version}`) continue
to work
## Tests
Added test case in `git.spec.ts` for `{releaseGroupName}@{version}`
pattern that verifies correct tag matching and version extraction.
## Files Changed
- `packages/nx/src/command-line/release/utils/release-graph.ts` (1 line)
- `packages/nx/src/command-line/release/utils/git.spec.ts` (1 test case)
Co-authored-by: James Henry <james@henry.sc>
## Current Behavior
The arboard crate is used without the `wayland-data-control` feature,
which means clipboard operations may not work properly on Wayland-based
Linux systems.
## Expected Behavior
With the `wayland-data-control` feature enabled, arboard can interact
with the clipboard on Wayland systems using the wlr-data-control
protocol.
## Related Issue(s)
N/A - Enhancement for better Wayland support
## Current Behavior
The Maven plugin incorrectly maps Maven's `isThreadSafe` mojo property
to Nx's `parallelism` target property. Maven's `isThreadSafe` indicates
whether a mojo can safely run in parallel with other mojos of the same
type within the same Maven build (multi-threaded Maven builds).
## Expected Behavior
Nx's `parallelism` controls whether the target can run in parallel
alongside anything else - a fundamentally different concept. All Maven
targets now default to `parallelism: true`, letting Nx handle
parallelism based on its own task graph analysis rather than using
Maven's unrelated thread-safety concept.
## Related Issue(s)
N/A - Internal cleanup based on code review feedback.
## Current Behavior
On Node.js v24+, the `@nx/jest/plugin` sets
`--no-experimental-strip-types` in NODE_OPTIONS which causes an error:
"node: --no-experimental-strip-types is not allowed in NODE_OPTIONS".
Additionally, `jest.config.ts` files using ESM syntax (`export default`,
`import`) fail to load correctly under Node.js type-stripping when the
project is configured for CommonJS.
## Expected Behavior
- Remove the NODE_OPTIONS manipulation that adds
`--no-experimental-strip-types`
- Add a migration (22.2.0-beta.2) that converts `jest.config.ts` files
from ESM to CJS syntax for projects using `@nx/jest/plugin`
- The migration only runs when `@nx/jest/plugin` is registered in
`nx.json`
- Projects with `type: module` are warned as they're incompatible with
the plugin
- Files using ESM-only features (import.meta, top-level await) are
skipped with a warning for manual conversion
## Demo
https://www.loom.com/share/8a157a0b01d144ae8d6ae48b9b0cd0e4
## Related Issue(s)
Closes NXC-3541
---------
Co-authored-by: Claude <noreply@anthropic.com>
## Current Behavior
We don't have a migration to migrate users to use the new @nx/vitest
package if they're currently using @nx/vite
Given that we have Vitest-related operations marked as deprecated when
used via @nx/vite, we should have a migration.
## Expected Behavior
Add a migration that:
1. Installs @nx/vitest
2. Switches @nx/vite:test executor usage to use @nx/vitest:test executor
usage
3. Splits `@nx/vite/plugin` in nx.json that sets up vitest test targets
to use `@nx/vitest` instead
## Current Behavior
The `@nx/vite:test` executor is not returning the async iterable. This
causes a destructuring issue.
## Expected Behavior
Ensure the `@nx/vite:test` executor returns the async iterable.
## Related Issue(s)
Fixes#33588
This PR updates the `workspace-rule` generator so use ESLint v9 by
default. It currently forces the unsupported ESLint v8.
In theory this is only useful if not using workspaces and you need
tsconfig paths to be mapped and resolved correctly. For workspaces, you
can easily just generate any library to be used to contain custom rules.
Closes NXC-3500
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Blog posts contain broken links pointing to old `/powerpack` and
`/docs/enterprise/powerpack/*` paths that no longer exist.
## Expected Behavior
Links should point to the new enterprise documentation paths:
- `/powerpack` → `/enterprise`
- `/docs/enterprise/powerpack/*` → `/docs/enterprise/*`
- `/docs/reference/powerpack/*` → `/docs/reference/*`
## Related Issue(s)
Closes DOC-354
Co-authored-by: Claude <noreply@anthropic.com>
## Current Behavior
When running `NX_PERF_LOGGING=true nx graph --file graph.json`, many
performance logs are missing (e.g., `create-project-graph-async`,
`retrieve-project-configurations`).
Only plugin-specific logs like `createDependencies` appear.
## Expected Behavior
All performance timing logs should appear, matching the output of
`NX_PERF_LOGGING=true nx show projects`.
## Related Issue(s)
N/A - Internal improvement for debugging/profiling.
## Solution
The `graph.ts` file had `process.exit(0)` calls that terminated the
process immediately, not giving the async `PerformanceObserver` callback
time to fire.
Added `await new Promise((res) => setImmediate(res))` before
`process.exit(0)` to give the event loop one tick to process pending
callbacks. This follows the existing
pattern in `show/projects.ts` and `show/project.ts`.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The Nx Console installation prompt blocks commands when run in
non-interactive contexts such as:
- CI environments
- AI agents
- Piped commands
The prompt only checked `process.stdout.isTTY` but not
`process.stdin.isTTY`, causing it to wait indefinitely for input that
would never arrive.
## Expected Behavior
Commands should complete without prompting when run in non-interactive
environments.
## Related Issue(s)
Fixes#33552
## Solution
Updated the check to verify:
1. Both `stdin` and `stdout` are TTY (truly interactive terminal)
2. Not running in a CI environment (using existing `isCI()` utility)
This ensures the prompt only appears when the user can actually provide
input.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
Since Nx 21.6.1, running `nx graph` or other commands that calculate
affected projects prints git errors to stderr when the default branch is
not fetched:
```
fatal: ambiguous argument 'main': unknown revision or path not in the working tree.
```
This causes CI pipelines with strict stderr checking to fail, even
though the nx commands succeed.
## Expected Behavior
Git error messages should not be printed to stderr. The errors are
already caught and handled gracefully - they just shouldn't be visible
to the user.
## Related Issue(s)
Fixes#33330
## Solution
Added `stdio: 'pipe'` to the `execSync` call in `parseGitOutput()`. This
suppresses stderr output while still allowing the command to throw on
failure (which is already caught by the try-catch in `graph.ts`).
This matches the pattern used in `getMergeBase()` which already uses
`stdio: 'pipe'`.
## Current Behavior
When running `nx migrate latest`, the `create-nx-workspace` package is
not updated along with other Nx packages, even though it's a core part
of the Nx ecosystem that users may have as a dependency (especially when
extending the install package pattern).
## Expected Behavior
The `create-nx-workspace` package should be updated to the same version
as `nx` and other `@nx/*` packages when running migrations.
## Related Issue(s)
Fixes#33585
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The nuxt preset test was checking for `apps/${name}/src/app.vue`, but
since the generator now defaults to Nuxt v4 which uses the app directory
structure, this file is no longer created at that path.
## Expected Behavior
Tests should pass on master.
## Solution
Updated the test to check for `apps/${name}/app/app.vue` which is the
correct path for Nuxt v4's app directory structure.
## Current Behavior
When Nx commands finish or receive termination signals (SIGINT, SIGTERM,
SIGHUP), child processes spawned by continuous tasks (such as `nx
serve`) can remain orphaned in certain scenarios. This happens because
only the direct child process is killed using `childProcess.kill()`,
leaving grandchild processes running.
## Expected Behavior
When Nx terminates, all processes in the spawned process tree should be
properly terminated and no orphaned processes should remain.
## Related Issue(s)
Fixes#32438Fixes#33460
## Changes
- Updated signal handlers in `RunningNodeProcess` to use `this.kill()`
instead of `this.childProcess.kill()`, leveraging the existing
`tree-kill` implementation
- Added `tree-kill` to `NodeChildProcessWithNonDirectOutput` and
`NodeChildProcessWithDirectOutput` kill methods to ensure entire process
trees are terminated
We missed a peer dep error in the Nuxt 4 PR since CI allowed install to
go through, but in a real repo it would have failed.
We _may_ need to still have the `prefer-frozen-lockfile=false` option,
but let's let CI run with this first.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
We currently do not support Nuxt 4. We still generate Nuxt 3.
## Expected Behavior
Support Nuxt 4.
New workspaces will get Nuxt 4.
Existing Workspaces that use Nuxt 3 intentionally will continue to use
Nuxt 3.
Add a migration to update users to Nuxt 4
Handle ESLint flat config
## Related Issue(s)
Closes NXC-3525
Closes NXC-3497
---------
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Coly010 <Coly010@users.noreply.github.com>
## Current Behavior
The base `eslint` config will ignore `**/dist` but not `**/out-tsc`.
This can cause issues if lint is run after a `typecheck` which has
placed `.d.ts` files into an `out-tsc` directory.
## Expected Behavior
Base eslint config should ignore `**/out-tsc`
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Coly010 <Coly010@users.noreply.github.com>
## Current Behavior
Storybook support is not explicitly set to 10.1 which has just been
released.
## Expected Behavior
Explicitly set storybook version to 10.1 to ensure support for Angular
21
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Coly010 <Coly010@users.noreply.github.com>
- E2E nightly tests only run on Node 20 and 22.
- E2E nightly tests now also run on Node 24, 22, and 20 for linux. For
Windows and Mac run only 24.
Did a test run from this branch, and Node 24 passes where other versions
pass, and fails where other versions fail. It shouldn't make any golden
tests fail just due to Node 24, but it's possible that things will flake
more since the matrix has expanded. We just have to make them more
robust.
<img width="920" height="1089" alt="image"
src="https://github.com/user-attachments/assets/90ee4d04-68d5-4d0d-9729-60cacfbc99db"
/>
Close NXC-3491
## Current Behavior
The Next 16 AI Migration Instructions are always created, regardless of
existing Next version
## Expected Behavior
Make the Next 16 Migration optional
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Coly010 <Coly010@users.noreply.github.com>
There is no documentation on how to create and use custom ESLint rules
in Nx workspaces.
Users can follow a guide to create custom ESLint rules using either:
1. Package Manager Workspaces (npm/yarn/pnpm/bun) - create a dedicated
ESLint plugin package that's symlinked via the package manager
2. `loadWorkspaceRules` utility from `@nx/eslint-plugin` - load rules
from any directory with automatic TypeScript transpilation
The guide includes:
- Comparison table for choosing the right approach
- Step-by-step instructions for both approaches
- TypeScript execution options (build first, Node.js native support,
tsx)
- Rule testing with `@typescript-eslint/rule-tester`
- Best practices and troubleshooting tips
Page:
https://deploy-preview-33618--nx-docs.netlify.app/docs/technologies/eslint/guides/custom-workspace-rules
Closes DOC-339
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
## Current Behavior
Despite @nx/vite depending on @nx/vitest it only does so in the same
version that introduces the migration for Vitest 4.
This means the user does not get any migrations for Vitest 4
## Expected Behavior
Add the Vitest 4 migrations to the @nx/vite package to allow users to
migrate.
There's a problem when `@nx/rollup` is installed with yarn@1.22, and
typechecks.
This is caused by the transitive dependency chain:
```
rollup-plugin-copy@3.5.0 -> globby@10.0.1 -> @types/glob@7.2.0 -> @types/minimatch
```
When users run tsc without explicit `types` configuration, TypeScript
auto-discovers `@types/minimatch` from `node_modules` but can't properly
resolve it.
Note: This doesn't happen with NPM and PNPM, nor newer yarn versions.
Verified fix with this repro: https://github.com/jaysoo/rollup-251124Fixes#32398
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
## Current Behavior
- Storybook generator docs banner says "Nx uses Storybook 7"
- Separate storybook-9-setup.mdoc page exists
- No documentation for migrate-10 generator
## Expected Behavior
- Update banner to "Nx uses Storybook 10"
- Consolidate storybook-9-setup into upgrading-storybook guide
- Add migrate-10-generator-examples.md with AI-assisted migration docs
- Update all references to point to new consolidated guide
---
Main content is here:
https://deploy-preview-33619--nx-docs.netlify.app/docs/technologies/test-tools/storybook/guides/upgrading-storybook
Need to follow-up on the sidebar ordering as it doesn't seem to take it
into account for technologies section.
Fixes DOC-347
## Current Behavior
When a continuous task depends on another continuous task and the
dependent task exits with an error, the parent task continues running
indefinitely. The task execution never terminates, leaving processes
running in the background.
For example, if task `a` (continuous) depends on task `b` (continuous),
and task `b` exits with error code 1, task `a` will continue running
even though its dependency failed.
## Expected Behavior
When a continuous task exits (with any exit code), the failure should be
propagated to dependent tasks:
1. The failed continuous task should be marked as failed
2. Dependent continuous tasks should be marked as skipped
3. All affected continuous tasks should be killed
4. Task execution should terminate with an error
---
## Changes Made
### 1. Restore Error Handling in Continuous Task Exit Handlers
- Re-added the `cleaningUp` flag that was removed in a previous
TUI-related commit
- Modified `onExit` handlers for both regular and shared continuous
tasks to:
- Check if the task exited during normal cleanup vs. unexpectedly
- Call `complete()` with 'failure' status for unexpected exits
- Log error messages for debugging
### 2. Fix `cleanUpUnneededContinuousTasks()` Logic
The previous implementation always added `initializingTaskIds` to the
needed set, even when those tasks were already completed. This prevented
dependency tasks from being killed when the top-level task exited.
Fixed by:
- Only adding tasks from `initializingTaskIds` if they are still
incomplete
- Keeping dependencies of incomplete tasks alive
- This ensures continuous tasks are killed when no longer needed,
whether a dependency fails or a top-level task exits
### 3. Prevent Status Overwrites
Added a check in `onExit` handlers to only set status to `Stopped` if
the task hasn't already been completed. This prevents the async `onExit`
callback from overwriting the correct status (like 'skipped' or
'failure') with 'Stopped'.
### 4. Fix Signal Handling in `PseudoTtyProcess.kill()`
The Rust pseudo-terminal defaults to SIGINT when no signal is provided,
which does not reliably terminate child processes in PTY sessions.
Fixed by:
- Defaulting to SIGTERM in the JavaScript wrapper (rather than changing
the Rust default)
- The JS wrapper is the API boundary that should match Node.js
semantics, where `childProcess.kill(undefined)` defaults to SIGTERM
- The Rust default of SIGINT is appropriate for interactive use
(Ctrl-C), while programmatic cleanup needs SIGTERM
- This ensures child processes are properly killed when
`runningTask.kill()` is called
**File:** `packages/nx/src/tasks-runner/pseudo-terminal.ts`
## Technical Details
The fix leverages the existing task failure propagation mechanism in
`complete()` instead of using `process.exit(1)`, which:
- Allows proper cleanup through normal execution flow
- Respects the `--bail` flag configuration
- Works correctly with both TUI and non-TUI modes
- Maintains consistency with how other task failures are handled
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
Older versions don't support the `nx mcp` command yet - but they CAN use
the `nx-mcp` package via npx
## Expected Behavior
We generatee the proper command into their MCP config by matching on
their version
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
When multiple versions of the `nx` package are installed in a workspace
(e.g., due to a third-party package incorrectly depending on nx), users
have no visibility into
this issue through `nx report`.
## Expected Behavior
The `nx report` command now detects when other packages depend on a
different version of nx than the workspace version and reports this
clearly:
⚠️ Multiple Nx versions detected
Your workspace uses nx@20.0.0, but other packages depend on a different
version:
- some-package → @scope/tool → nx@19.0.0
These packages should not have nx as a dependency. Please report this
issue to the package maintainers.
Run pnpm why nx@19.0.0 for more details.
This helps users identify and report problematic packages that bundle
their own version of nx.
## Related Issue(s)
N/A - This is a proactive improvement to help users diagnose workspace
issues.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
nightly tests fail on gradle because of gradle parsing
## Expected Behavior
nightly tests should pass
## Current Behavior
We do not generate AI Instructions to aid with upgrading from Next 15 to
Next 16
## Expected Behavior
Add a migration generator to create a file containing instructions for
an LLM to upgrade Next 15 to Next 16
## Related Issue(s)
Closes NXC-3418
---------
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
## Current Behavior
In TS Solution setups, we generate webpack config with
`generatePackageJson: true`. This is confusing and unneeded.
It should be set to false in TS Solution repos.
## Expected Behavior
Set `generatePackageJson: false` in webpack config for TS Solution
Setups
Closes NXC-3521
When generating NestJS applications in Angular workspaces, the base
tsconfig sets moduleResolution to 'bundler' which causes TS5095 errors
because 'bundler' requires module to be 'preserve' or 'es2015+'.
NestJS applications should use Node.js module resolution instead. This
fix sets moduleResolution to 'node' for NestJS applications (except when
using TS solution setup, which uses 'nodenext').
Fixes#33589
## Current Behavior
The ⚠️ emoji at the beginning of bc commits is duplicated in the bc
section of the changelog.
This is unneeded.
## Expected Behavior
Ensure the ⚠️ is not repeated in the bc section of the changelog
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Coly010 <Coly010@users.noreply.github.com>
Close NXC-3516
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
1. **Socket Race Condition**: All daemon servers listen on the same
socket path, causing a race condition where shutting down daemons remove
sockets that newly started
daemons are listening on.
2. **Daemon Console Check Blocks**: The daemon availability check runs
synchronously and blocks the main thread.
3. **Version Mismatch Issues**: Packages using a different nx version
than what's installed in the workspace could still use the daemon,
leading to potential issues.
## Expected Behavior
1. Each daemon server creates a unique socket path based on its process
ID, preventing race conditions.
2. The daemon console check runs in the background without blocking.
3. The daemon is disabled when there's a version mismatch between the
running nx and the workspace's installed version.
## Changes
### 1. Unique Daemon Socket Paths
- Include `process.pid` in the socket directory hash to make each
daemon's path unique
- Store the socket path in `server-process.json` so clients know where
to connect
- Clients read the socket path from the file instead of calculating it
### 2. Backgroundable Daemon Check
- Reapplied #33491 which makes the Nx Console install check run on the
daemon in the background
- This was previously reverted due to the socket race condition (now
fixed by change 1.)
- Running in background also allows pulling the latest check logic from
npm
### 3. Version Mismatch Check
- Added `isNxVersionMismatch()` check in `DaemonClient.enabled()`
- Created shared utility `is-nx-version-mismatch.ts` for version
comparison
- Refactored server.ts to use the shared utility
- Uses `require.resolve('nx/package.json', { paths: [workspaceRoot] })`
to properly resolve the workspace's installed nx version
## Related Issue(s)
Fixes daemon socket path race condition and improves daemon reliability.
## Current Behavior
`copy-workspace-modules` executor only copies workspace dependencies 1
level deep.
If that workspace library depends on another workspace library, it is
not copied correctly.
## Expected Behavior
Copy transitive workspace modules
## Related Issue(s)
Fixes NXC-3466
When adding Cypress to a library, the generated commands.ts causes
TS2669 error because declare global requires the file to be a module.
Changed to use declare namespace Cypress directly.
Fixes#32930
## Current Behavior
PR #33491 introduced a daemon call into every command and caused
unexpected issues...
## Expected Behavior
the change is reverted while we investigate a proper fix
## Changes
This reverts commit 9471207767 from PR
#33491.
## Related Issue(s)
Fixes#33472
## Current Behavior
The manual DTE workflow explicitly sets the `ref` parameter to `${{
github.event.pull_request.head.sha }}` to checkout the actual branch
HEAD instead of the merge commit.
## Expected Behavior
Use GitHub's default ref behavior instead of explicitly overriding it,
as the default behavior now handles this correctly.
Co-authored-by: Claude <noreply@anthropic.com>
## Current Behavior
When generating a React component with --style=tailwind, the component
template incorrectly includes `className={styles['container']}` and
attempts to import CSS modules.
## Expected Behavior
Components generated with --style=tailwind should not include CSS
modules imports or `styles['container']` references, since Tailwind
doesn't use CSS modules.
## Related Issue(s)
Closes NXC-3511
Co-authored-by: Claude <noreply@anthropic.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
Stats don't come along with versions which makes it hard to see if it
was specific changes that make a difference.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Stats are tagged with the version so we can compare stats between
versions
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
This PR fixes an issue where migrations can hang due to out invocation
of `storybook automigrate`. We are passing both `--config-dir` and
`STORYBOOK_PROJECT_ROOT`, the latter causes hanging with Storybook v9.
https://www.loom.com/share/39bbb350595c4a13aef86ec29f4b748f
## Current Behavior
Hangs
## Expected Behavior
Does not hang
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes#32492
This PR cleans up the markdown files under `packages/`. We previously
had to support Next.js docs and translate it for astro docs with proper
markdown syntax. This applies to generators, executors, and migrations.
Also removes the function to do the translation in astro app since it's
no longer needed.
## Code block (migrations)
<img width="1086" height="800" alt="Screenshot 2025-11-20 at 1 23 53 PM"
src="https://github.com/user-attachments/assets/bd9acb9b-7960-4e41-9d26-22d29da6658e"
/>
## Aside (generators)
<img width="802" height="443" alt="Screenshot 2025-11-20 at 1 43 17 PM"
src="https://github.com/user-attachments/assets/e2999821-8783-46ed-a984-2193f6f8eafa"
/>
## Current Behavior
During createNodes, if a file is imported and a function in said file
invokes the Nx project graph creation process, there's an infinite loop
that results in all nx commands hanging with little feedback.
Theoretically this loop would terminate at around the 10 minute mark,
but throughout the loop we would be digging deeper and deeper into
recursive territory so its possible that the node process could become
overwhelmed and hang.
## Expected Behavior
If recursive graph creation is detected, Nx terminates and logs the call
stack so it can be investigated properly.
## Related Issue(s)
Fixes#29618
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
This PR adds the ability for users to import ESLint rules from arbitrary
location in the workspace rather than storing them in
`tools/eslint-rules`. This is useful for monorepo not using
npm/yarn/pnpm workspaces and need a mechanism to load from any custom
rules location without them being installed/symlinked.
It also handles TS files automatically.
Demo: https://www.loom.com/share/3c32af4555614eeab4f81fce8db0c955
Example:
```js
import baseConfig from "../../eslint.config.mjs";
import { loadWorkspaceRules } from "@nx/eslint-plugin";
const customRules = await loadWorkspaceRules("foo/bar/eslint-rules");
export default [
...baseConfig,
{
ignores: ["**/out-tsc"],
},
{
files: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
plugins: {
custom: { rules: customRules },
},
rules: {
"custom/valid-command-object": "error",
},
},
];
```
---------
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
## Current Behavior
Daemon command hangs at end instead of exiting
## Expected Behavior
Daemon command exits
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
There's some issues with the jest configs in the repo, CJS files having
`import` in them, etc.
## Expected Behavior
CJS files don't have `import`
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
`cache: true` is overridden if task name is dev
## Expected Behavior
`cache: true` has priority
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes#32610
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Using .mts extension forces files to always be treated as ESM modules,
ensuring consistent behavior regardless of package.json or tsconfig
settings.
This matters for Node 24 because by default Node will strip types from
`.ts` files and then they are resolved through normal Node resolution.
In the past we can control CJS/ESM through tsconfig options, but now
only extension or `type` in `package.json` matters.
Changes:
- Updated all createOrEditViteConfig calls to pass useEsmExtension: true
- Updated normalizeViteConfigFilePathWithTree to check for .mts files
first
- Updated test files to expect .mts config files
- Updated snapshots to reflect new .mts extension
Closes NXC-3446
Flat config overrides util may fail when it isn't a plain JS object.
This PR makes the `hasOverrides` function more robust against these
cases.
Fixes#31796
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
the JSON parsing is broken because in verbose mode, the gradle plugin
returns more than just JSON.
## Expected Behavior
JSON parsing in the test should work
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
We naively mark WholeFileChange for commits when getting the relevant
commits for projects.
There is already logic to perform better diff checking for lock files,
especially in the case of pnpm catalog usage
## Expected Behavior
Reuse existing logic to determine file changes more accurately
## Related Issue(s)
Fixes#33413
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
If no inputs or named inputs are defined, this area of code was hit:
```
const DEFAULT_INPUTS: ReadonlyArray<InputDefinition> = [
{
fileset: '{projectRoot}/**/*',
},
{
dependencies: true,
input: 'default',
},
];
export function getNamedInputs(
nxJson: NxJsonConfiguration,
project: ProjectGraphProjectNode
) {
return {
default: [{ fileset: '{projectRoot}/**/*' }],
...nxJson.namedInputs,
...project.data.namedInputs,
};
}
```
This resulted in weird behavior when the user would define `default` in
named inputs, but it would seemingly only be applied to the project's
deps and not the project itself
## Expected Behavior
The `default` input is the default for both
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #https://github.com/nrwl/nx/issues/32924
## Current Behavior
Every time a daemon connection is opened, we create a new interval and
unref it. This results in the daemon checking its process termination at
an ever increasing rate, which presents as increased memory usage and
practically means the daemon is just doing a lot more work than it needs
to in this area.
## Expected Behavior
The interval is registered only on initial server startup.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes#29836
This PR updates the guide here to also include type entries (which we
generate by default):
https://nx.dev/docs/technologies/typescript/guides/compile-multiple-formats
Update the content to account for the new inferred `@nx/rollup/plugin`
setup, but still mentions the executor. And also link to a tool that can
be used to check for types correctness.
Closes#33258
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
if there is an error parsing a schema file for generators/executors then
we should fully error out to prevent caching a "bad" build even if the
site techincally works with missing plugin info.
## Current Behavior
Metrics collection is currently disabled in the CI workflow via the
`NX_CLOUD_ENABLE_METRICS_COLLECTION` environment variable set to
'false'.
## Expected Behavior
Metrics collection should be enabled to gather build and performance
data from CI runs.
## Related Issue(s)
Reverts the change from #33497
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
The `@nx/js:typescript-sync` generator never syncs external project
references to `tsconfig.json` files if a runtime tsconfig file exists.
## Expected Behavior
The `@nx/js:typescript-sync` generator should sync external project
references to `tsconfig.json` files if it includes any files or a
runtime tsconfig file doesn't exist.
## Summary
This PR fixes a critical deadlock issue in the metrics collector that
occurred due to inconsistent lock acquisition order between the
collection thread and registration threads. The fix involved
restructuring lock scopes across multiple functions to maintain a
consistent lock hierarchy.
## Changes
- Fixed lock acquisition order in 4 registration functions
(register_main_cli_process, register_main_cli_subprocess,
register_task_process, register_batch)
- Restructured collect_metrics() to minimize system lock scope and
release it before acquiring other locks
- Fixed collection helper methods to read PIDs in scoped blocks without
holding system lock
- Added comprehensive trace logging for debugging lock contentions
- Added concurrent test case to verify no deadlocks occur under stress
## Testing
- All 12 metrics tests pass
- Comprehensive concurrent stress test added:
test_concurrent_group_creation_with_subprocess_updates
- Lock ordering consistency test added:
test_lock_order_consistency_across_registration_threads
## Lock Ordering Rule
Established and enforced this hierarchy across all threads:
1. Acquire system lock first
2. Release system lock
3. Then acquire registration/PID locks
This prevents circular wait conditions (A→B / B→A) that cause deadlocks.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
When Storybook is installed with a version range like `^10.0.0` or
`~8.5.3`, the configuration generator fails with error:
```
NX Invalid Version: ^10.0.0
TypeError: Invalid Version: ^10.0.0
```
This occurs because `gte()` from semver doesn't accept version ranges on
the left-hand side - only valid semver versions are allowed there.
The Storybook configuration generator should work with version ranges by
extracting the actual version number before comparison.
Fixes#33514
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
Env variables from the parent target are not propagated to the atomized
target e.g.
`.test-ci.env` will be only applied to **no-op** `test-ci` but not to
`test-ci--path/to/test/file`.
## Expected Behavior
Running an atomized target will load from the parent's env files.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
## Current Behavior
Gradle projects in Nx workspaces can only configure metadata through
Gradle's built-in mechanisms. There's no way to specify Nx-specific
project metadata (like tags) or customize task target configurations
other than overriding with `project.json`
## Expected Behavior
Developers can now configure Nx-specific metadata for both projects and
tasks using a type-safe Kotlin/Groovy DSL:
### Project-level metadata (in build.gradle.kts):
```
nx {
set("name", "my-service")
array("tags", "scope:backend", "type:api")
set("description", "Payment processing service")
}
```
### Task-level metadata (in build.gradle.kts):
```
tasks.named("integrationTest") {
nx {
set("cache", false)
array("tags", "integration", "slow")
}
}
```
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: MaxKless <MaxKless@users.noreply.github.com>
## Current Behavior
In certain scenarios, Nx commands would hang between command completion
and process exit. This was caused by inefficient message end detection
in the
daemon socket communication, where the check for `MESSAGE_END_SEQ` could
fail when TCP packets were fragmented.
## Expected Behavior
With this PR, the message end detection is more robust and handles TCP
packet fragmentation correctly, preventing the hanging issue. The
changes also
add better performance tracking and logging to help diagnose similar
issues in the future.
## Changes Made
- **Improved message end detection**
(`consume-messages-from-socket.ts`): Added a preliminary check of the
last character's code point before checking
the full MESSAGE_END_SEQ, which prevents false negatives when TCP
packets are fragmented
- **Enhanced performance tracking** (`daemon/client/client.ts`,
`daemon-socket-messenger.ts`): Added message-type-specific performance
marks and
measures for better debugging
- **Added server-side logging** (`daemon/server/server.ts`): Added
logging for message receipt, serialization, and response to help
diagnose
communication issues
## Related Issue(s)
This fix addresses hanging issues observed in daemon communication when
commands complete but the process doesn't exit.
Add a skipDefaultTag option to the DockerTargetOptions interface that
allows users to opt out of the automatic default tag that is prepended
to build targets.
This is useful for multi-platform builds that need to push during build
(e.g., using --platform linux/amd64,linux/arm64 --push), where the
default tag causes build failures because it attempts to push a tag that
was not configured.
- Add skipDefaultTag?: boolean to DockerTargetOptions interface
- Modify buildTargetOptions to conditionally skip default tag when
skipDefaultTag is true
- Inherit skipDefaultTag from parent target in configurations
- Add comprehensive unit tests for skipDefaultTag functionality
- Add e2e test to verify skipDefaultTag works end-to-end
- Maintains backward compatibility (default behavior unchanged when
option not specified)
Fixes#33477
## Current Behavior
Loading local plugins that are relative paths from workspace root, that
point to a JS file, still require preliminary data from default plugins
despite being resolvable. This is because we aren't passing `paths` to
`require.resolve`, so it is trying to resolve relative to the nx package
instead of the workspace.
## Expected Behavior
The straight JS path resolves
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
The metrics collector has several inefficiencies and architectural
issues:
- Complex hierarchical data structure (`ProcessTreeMetrics`) that
doesn't align with how the data is consumed
- Separate `MetadataStore` struct with unnecessary indirection
- Full metadata resent to all subscribers on every collection cycle
- Mutable parameters passed through collection functions instead of
functional return values
- Repeated allocations and clones across collection cycles
- `CollectionRunner` tightly coupled to NAPI, making it untestable in
pure Rust
## Expected Behavior
This PR restructures the metrics collector for better performance,
testability, and maintainability:
### Architectural Changes
1. **Flat Process Model**: Replaced hierarchical `ProcessTreeMetrics`
with a flat `Vec<ProcessMetrics>`, simplifying data flow
2. **Group-Based Organization**: Introduced `GroupInfo` and `GroupType`
to logically organize processes:
- `MainCLI` - Nx CLI process and its subprocesses
- `Daemon` - Nx daemon and its children
- `Task` - Individual task execution processes
- `Batch` - Batch execution with multiple tasks
3. **Incremental Metadata Updates**:
- Track which groups and processes have been sent using
`Arc<DashMap<String, GroupInfo>>` and `Arc<DashMap<String,
ProcessMetadata>>`
- Only send new metadata to subscribers instead of full state every
cycle
- New subscribers receive full metadata on first update via
`needs_full_metadata` flag
- Automatic cleanup of dead process/group metadata
4. **Shared State with Arc**:
- Metadata maps shared between `ProcessMetricsCollector` and
`CollectionRunner` using `Arc<DashMap>`
- Eliminated duplicate metadata storage
- Single source of truth for all metadata
5. **Functional Programming Pattern**:
- Collection functions now return `Result<MetricsCollectionResult>`
instead of mutating parameters
- Cleaner error handling with `inspect_err` and `map`
- Easier to reason about data flow
- Removed ~100 lines of code by consolidating logic
6. **Channel-Based Communication**:
- Decoupled `CollectionRunner` from NAPI using `crossbeam_channel`
- Collection thread sends metrics via channel to listener thread
- Listener thread receives metrics and notifies NAPI subscribers
- `CollectionRunner` is now NAPI-free and fully testable in pure Rust
- Non-blocking collection (subscriber callbacks don't block metrics
collection)
### Performance Optimizations
- Pre-allocated `Vec` capacity when combining metrics from different
sources
- Eliminated unnecessary `HashMap` clones during metadata updates
- Single-pass insertion into `DashMap` during string key conversion
- Reduced memory allocations in hot paths
- Collection thread never blocks on JavaScript callbacks
### Code Quality Improvements
- **Testability**: Added 7 pure Rust unit tests for `CollectionRunner`:
- Group creation with different registration types
- Incremental metadata updates
- Dead group cleanup
- All tests pass without requiring NAPI/Node.js runtime
- Clearer separation of concerns between collection and notification
- Better comments explaining incremental update strategy
- More idiomatic Rust patterns throughout
- Updated TypeScript type exports to match new structure
### Threading Model
**Before:**
```
CollectionRunner (mixed collection + NAPI notification)
```
**After:**
```
CollectionRunner (pure Rust, testable)
└─> Channel
└─> Listener Thread
└─> NAPI ThreadsafeFunction
└─> Subscribers
```
## Testing
- ✅ 241 Rust tests passing (including 7 new `CollectionRunner` tests)
- ✅ Native module builds successfully
- ✅ TypeScript types updated and exports verified
## Related Issue(s)
Part of ongoing metrics collector optimization work.
## Current Behavior
The Nx Console install check for the prompt happens in the main process,
adding some overhead to each invocation of nx.
## Expected Behavior
We want this check to happen on the daemon so that it's running in the
background. If it's still running when nx is invoked, we can just skip
the prompt since it's non-critical.
Running it in the background also allows us to pull the latest version
of the logic from npm when executing - that way we can keep the logic
older versions up-to-date even when ppl don't migrate to latest.
## Current Behavior
The Maven plugin is currently at version 0.0.9.
## Expected Behavior
This PR bumps the Maven plugin to version 0.0.10 and creates the
necessary migration for users to automatically update their pom.xml
files.
## Related Issue(s)
N/A - Routine version bump
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
https://www.loom.com/share/560ceccdad45462e9fd3e3f185fc9fa5
Node 24 has stricter readline behavior, and enquirer is not checking for
closed state when invoking operations, resulting in an
ERR_USE_AFTER_CLOSE error when users press Ctrl+C during interactive
prompts.
This commit fixes the issue by adding uncaughtException handler to
ignore ERR_USE_AFTER_CLOSE errors.
When users press Ctrl+C, the process now exits cleanly without showing
an ugly error stack trace.
Fixes NXC-3412
Co-authored-by: Claude <noreply@anthropic.com>
Updates the Nx Powerpack docs and marketing page to make it clear that
Powerpack packages are included with Nx Enterprise, and cannot be
purchased separately, and remove references to the Nx Powerpack trial.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
---------
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: Caleb Ukle <caleb@nrwl.io>
The recipe for switching to TS project references states "If you
reference a local library project with its own `build` task" which
caused confusion. Users thought only buildable libraries should be
included in devDependencies, leading them to create unnecessary path
aliases for non-buildable libraries.
Closes DOC-149
Co-authored-by: Claude <noreply@anthropic.com>
## Current Behavior
Currently, telemetry stats are only recorded when `create-nx-workspace`
completes successfully. This doesn't capture how many times the command
is invoked vs completed.
## Expected Behavior
Record a stat when `create-nx-workspace` is first invoked (before any
prompts), enabling analysis of drop-off between initial invocation and
workspace completion.
## Changes Made
### Telemetry
- Added `recordStat()` call in `normalizeArgsMiddleware()` immediately
after welcome message and before any user prompts
- Records with command name `create-nx-workspace` and metadata
`['start']` to distinguish from completion stat
- Allows correlation of invocation and completion events for drop-off
analysis
### AI Agents Prompt
- This is being temporarily disabled because we noticed a dip in
create-nx-workspace completions that lines up when this was released.
Disabling it temporarily to see if the dip is recovered by disabling the
prompt.
### React Framework Selection
- Added early returns in `determineReactFramework()` for cases where
framework is already provided or interactive mode is disabled
- Improves performance by avoiding unnecessary prompt interactions
### Code Quality
- Reorganized imports in alphabetical order for better maintainability
- Removed unused import (`printSocialInformation`)
## Related Issue(s)
WIP - Draft for discussion
## Current Behavior
When `nx add @nx/s3-cache` fails due to incompatible peer dependencies
or other installation errors, users see only a generic error message
without the actual error details from the package manager.
## Expected Behavior
The command should display complete error messages from the package
manager (both stdout and stderr), including peer dependency conflicts
and other important diagnostic information.
## Changes
Fixed the exec callback in the `installPackage` function to:
1. Capture the `stderr` parameter (was previously ignored)
2. Log both stdout and stderr with a newline separator for clarity
3. Ensure users see all error information from package managers
## Why It Matters
Package managers write installation errors and peer dependency warnings
to stderr. By ignoring stderr, users had no visibility into what
actually failed, making it difficult to diagnose and fix issues.
Fixes issue with `nx add` not showing proper error messages.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
The `remove-redundant-ts-project-references` migration fails with an
error when run on workspaces that don't have a root `tsconfig.json`
file, such as nx-examples.
## Expected Behavior
The migration should skip workspaces that are not using TypeScript
solution setup instead of throwing an error.
## Related Issue(s)
Fixes the issue encountered when running the migration on
nrwl/nx-examples repo.
## Changes
- Added check to skip migration if workspace is not using TS solution
setup
- Updated test setup to properly configure TS solution for existing
tests
- Added new test cases to verify skip behavior
The migration now uses `isUsingTsSolutionSetup()` to detect if:
- `tsconfig.base.json` exists
- `tsconfig.json` exists and extends the base
- Package manager workspaces are configured
- Proper TS solution structure is in place
Workspaces missing any of these requirements will have the migration
skip silently.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
## Current Behavior
The migration for removing redundant TypeScript project references is
set to version `22.1.0-beta.8`.
## Expected Behavior
The migration should be updated to version `22.1.0-rc.1` to align with
the release candidate version.
## Related Issue(s)
N/A - Version update as requested.
## Current Behavior
The `@nx/js:typescript-sync` generator adds project dependencies as
TypeScript project references to each project's `tsconfig.json` and
runtime tsconfig file (e.g., `tsconfig.app.json`, `tsconfig.lib.json`,
etc.). This is redundant since projects' `tsconfig.json` files already
reference the runtime tsconfig file, which would reference the
dependencies.
## Expected Behavior
The `@nx/js:typescript-sync` generator should add project dependencies
as TypeScript project references to each project's runtime tsconfig file
(e.g., `tsconfig.app.json`, `tsconfig.lib.json`, etc.). If the project
only has a `tsconfig.json` file, it should add them to it.
We've observed some performance improvement with this change while
running the `typecheck` tasks.
## Summary
This PR combines two critical performance optimizations for batch task
scheduling and task hashing:
### 1. Batch Scheduling Fix
Fixed a correctness bug in the batch scheduling optimization where the
`visitedInBatch` tracking happened too early. Now tasks are only marked
as visited AFTER all scheduling checks pass, ensuring tasks can be added
to the batch from any valid dependency path.
**Files Modified:**
- `packages/nx/src/tasks-runner/tasks-schedule.ts`
**Impact:**
- Prevents task splitting across unnecessary batch boundaries
- Maintains correctness while optimizing performance
- All batch scheduling tests pass
### 2. Task Output Hashing Optimization
Added a DashMap-based cache to the Rust TaskHasher to prevent redundant
hashing when multiple tasks depend on the same outputs.
**Files Modified:**
- `packages/nx/src/native/tasks/task_hasher.rs` - Added
task_output_cache field
- `packages/nx/src/native/tasks/hashers/hash_task_output.rs` - Implement
cache logic
## Root Cause of Issue #33366
Large project graphs with high dependency fanout exhibit slow
`hashMultipleTasks` because:
- 100+ tasks may depend on the same 10 build tasks' outputs
- This generates 1,000+ separate `TaskOutput` hash instructions
- Each instruction independently:
- Lists output files from disk (filesystem I/O)
- Builds glob patterns
- Hashes the same files
- Result: Same files hashed 100× redundantly
## Solution: Task Output Cache
The cache key combines glob pattern and sorted output paths. When
identical outputs are hashed with the same pattern:
- First task: computes hash (~milliseconds) and stores in cache
- Remaining 99 tasks: cache hits (~nanoseconds each)
### Cache Lifetime
- `TaskHasher` instantiated once per `hashMultipleTasks` call
- Cache persists for entire hashing session
- Discarded when `hashMultipleTasks` completes
- Optimal scope for cache hit maximization
## Performance Impact
**Expected Improvements:**
- Best case (100 tasks, 10 shared deps): 100× speedup
- Typical monorepo: 10-20× speedup
- No shared deps: No regression (cache lookup negligible)
**Issue #33366 Analysis:**
- Current: `Time for 'hashMultipleTasks' 49351.488518` (49 seconds)
- Expected with optimization: ~5-10 seconds (depending on actual
dependency structure)
## Testing
- ✅ All task scheduling tests pass (2262 passed, 0 new failures)
- ✅ Batch mode tests pass (6/6)
- ✅ Native build completed successfully
- ✅ No regressions in existing functionality
## Design Notes
- Task output cache follows same pattern as `workspace_files_cache`,
`external_cache`, `runtime_cache`
- Thread-safe using DashMap with Arc for Rayon parallel processing
- Instrumented with trace-level logging for cache hits/misses
- Cache automatically cleaned up when TaskHasher is dropped
Updates the `ng-packagr` executors to support a breaking change in v21.
This needs to be done in advance because we use the published
`@nx/angular` executors to build the `@nx/angular` source code. To
update to Angular v21, we need this change to be merged, released, and
installed in the Nx repository so that we can build the `@nx/angular`
package containing the support for Angular v21.
Users cannot import TypeScript schema definitions or schema.json files
from Nx packages due to strict package exports introduced in Nx 21.0.0.
Users can now import both TypeScript definitions and JSON schemas from
generator/executor/builder paths using wildcard export patterns. Both
first-level (`*`) and second-level (`*/*`) patterns are supported to
handle different import depths.
Note: For old plugins that had `src/...` deep imports we keep those in
`exports`, but for newer plugins that always used `exports` we skip
`src/` in the export path. So `@nx/nuxt/generators` instead of
`@nx/nuxt/src/generators`.
Fixes#33336
## Current Behavior
nx init is executed with whatever version is installed globally by npm
(might be outdated)
## Expected Behavior
we pull down the latest version from npm (if it has provenance) and run
that when executing.
Also introduced a new `NX_USE_LOCAL` env var that will be respected by
all commands that have this pulling-from-latest behaviour.
Update CI to Node 24. Note that the e2e-release changes are pulled from
the original PR.
Note: There are changes to NPM 11 (Node 24) to make it run slower than
NPM 10 (Node 20/22) for publish. (https://github.com/nrwl/nx/pull/31934)
e.g. https://github.com/npm/cli/releases/tag/v11.0.0-pre.1 (`Upon
publishing, in order to apply a default "latest" dist tag, the command
now retrieves all prior versions of the package.` which incurs more
network cost)
## Current Behavior
The typecheck target in graph/client/project.json only depends on
^typecheck targets from other projects.
## Expected Behavior
The typecheck target now includes nx:build-native as a dependency to
ensure native dependencies are built before type checking.
## Related Issue(s)
This fix resolves typecheck failures caused by missing native
dependencies.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
## Current Behavior
there is no way to generally influence how maven goals/phases are
represented as targets in nx. This could be useful though for organizing
targets in nx, for example through nx.json `targetDefaults`
## Expected Behavior
There's a `targetNamePrefix` plugin option that can be passed.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: MaxKless <MaxKless@users.noreply.github.com>
After PR #33256 split lockfile parsing into separate node and dependency
caches, a regression occurred where dependencies could be regenerated
without nodes, leaving a shared module-level variable (`keyMap`) empty
and causing incorrect dependency resolution.
Changes:
- Serialize `keyMap` with nodes cache to maintain state between phases
- Remove module-level shared state from pnpm, npm, and yarn parsers
- Move `keyMap` creation inside `getNodes` functions for better
encapsulation
- Update `readCachedExternalNodes` to deserialize `keyMap` internally
Update from the macos-13 is in brownout and will be gone soon.
- macos-15-intel for x86_64-apple-darwin (Intel) builds
- macos-latest for aarch64-apple-darwin (ARM64) builds
Closes NXC-3444
## Current Behavior
We currently only generate vitest projects using Vitest 3
## Expected Behavior
Use Vitest 4 when generating new projects
## Related Issue(s)
Closes NXC-3343
Closes NXC-3379
---------
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Clarifies some details of the Nx Cloud GitHub integration, such as which
features are available when you use the base integration vs a GitHub
powered organization.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
## Current Behavior
We do not render the commit title in the BC section of generated
changelogs.
## Expected Behavior
We should render the commit title in the BC section of generated
changelogs along with the Remote Release Client reference to the commit.
## Related Issue(s)
Closes NXC-3290
## Current Behavior
There may be a scenario when `cleanedAngularVersion` is of type `object`
rather than `string` which proceeds to error when passed to
`semver.major()`.
## Expected Behavior
If `cleanedAngularVersion` is not `string`, assume the latest version
will be installed. Similar to how no found angular version is handled.
## Related Issue(s)
Fixes#33347
- Fix for a bug with the external project references cache
- Add more caching for repeated operations
- Skip tsconfig files processing based on which targets should be
inferred
Fixes#33076
## Current Behavior
The library generator has an unused import and incorrect import pattern
for the vitest generator.
## Expected Behavior
Clean imports with the correct way to access the configurationGenerator
from @nx/vitest.
## Changes
- Removed unused imports (logger, readJson)
- Updated vitest generator import to use direct require instead of
ensurePackage pattern for accessing the generator
Fixes #
## Current Behavior
The Storybook 10 migration was originally intended to be optional.
## Expected Behavior
Make Storybook 10 migration add the migration generator to
migrations.json always. Users can remove it from here if they do not
want it, or they can use the Migrate UI from Nx Console to choose not to
run it.
Split entry point of `@nx/vitest` into `index.ts` for the Inference
Plugin, `generators.ts` for Generators, `executors.ts` for the
Executors.
Ensure the `README.md` is copied to the correct output directory
---------
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Coly010 <Coly010@users.noreply.github.com>
Split `vitest` out of `@nx/vite` and create a new `@nx/vitest`.
This allows for each plugin to have a single responsibility. One for
bundling, one for testing.
It should allow for lighter setups where Vite for Bundling is not
required, but users still want to use vitest for testing.
---------
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Coly010 <Coly010@users.noreply.github.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
Storybook was accidentally downgraded to 9.1.9
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Storybook is upgraded back to 10.0
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
Cypress v15 is not supported.
## Expected Behavior
Cypress v15 should be supported.
## Related Issue(s)
Fixes#33304
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: leosvelperez <leosvelperez@users.noreply.github.com>
CI and nightlies can flake out during expo test if cypress or playwright
times out waiting for webserver. This makes is to the `export` that
powers `static-serve` is run before running e2e.
This is not usually a problem in new workspaces with inference, but this
legacy test uses executors so continuous task dependency isn't set up.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
Nx plugin for Gradle is at version 0.1.8
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Nx plugin for Gradle is at version 0.1.9
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
- Track relevant CPU and memory system metrics.
- Link plugins to workers when possible.
- Refactor collector code by splitting it into two separate structs.
## Current Behavior
Maven plugin is currently at version 0.0.8.
## Expected Behavior
Maven plugin should be bumped to version 0.0.9 with a corresponding
migration for users.
## Changes Made
- Updated parent POM version to 0.0.9
- Updated package.json version to 0.0.9
- Updated mavenPluginVersion constant to 0.0.9
- Added new migration (0-0-9) to update user pom.xml files from 0.0.8 to
0.0.9
- Scheduled migration for Nx v22.1.0-beta.6
## Testing
- ✅ Maven package tests pass
- ✅ Build succeeds
- ✅ Linting passes
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
Because `./nx` is passing args with `$@`, args are being split by
spaces. Reference for this behaviour:
https://www.gnu.org/software/bash/manual/html_node/Special-Parameters.html#Special-Parameters-1
This results in this set of process args when running `./nx start-ci-run
--distribute-on="3 linux-medium-jvm"`
```
[
"<user path>/.nvm/versions/node/v22.19.0/bin/node",
"<user path>/Projects/OTW/mm-test/.nx/nxw.js",
"start-ci-run",
"--distribute-on=3",
"linux-medium-jvm",
]
```
## Expected Behavior
the arg with whitespace should not be split before being passed to nx:
```
[
"<user path>/.nvm/versions/node/v22.19.0/bin/node",
"<user path>/.nx/nxw.js",
"start-ci-run",
"--distribute-on=3 linux-medium-jvm",
]
```
This PR is just for the repo itself to use `.cts` to explicitly use CJS
for jest config. Node 24 strip types so having `.ts` files with ESM
syntax even though we're previously transpiling them to CJS is a
problem.
Make run-one task terminal outputs in the TUI non-interactive by
default. Most tasks don't need interactivity, and it causes TUI to
ignore all its key bindings because it forwards them to the underlying
program. If interactivity is needed, users can press `i` to enable it.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
Currently we detect if inputs are found in the build directory to
determine if they are considered dependent task output files. We can use
a simpler heuristic instead.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
If an input is gitignored (and therefore not hashed by Nx), then we
consider it a dependant task output file. This gitignore classifier will
be a mirror of the class used within the Maven plugin. There will be
another PR to move this classifier into a shared kotlin project that can
be used between both projects.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
Maven target dependencies are defined as simple strings that reference
other targets. Currently, parameters are not forwarded through these
dependencies when Maven goals are executed.
## Expected Behavior
Target dependencies in Maven should forward parameters (args) to their
dependency targets, enabling better parameter propagation through the
build pipeline.
## Changes Made
Modified `NxTargetFactory.kt` to transform simple string dependency
references into structured dependency objects with explicit parameter
forwarding:
- Install dependencies now forward parameters
- Phase dependencies now forward parameters
- Test dependencies now forward parameters
- CI target dependencies now forward parameters
This ensures that when a Maven goal executes, any parameters passed to
it are properly forwarded to all transitive target dependencies.
## Related Issue(s)
This change enables parameter passing through Maven target dependencies
via the new dependency object format with `"params": "forward"`.
When using Jest 30 with SWC, users are seeing an error where `__dirname`
is not defined for ESM modules. This is because the `.ts` extension is
type-stripped by Node 22.17/24+ via checking for
[`process.features.typescript`](https://github.com/jestjs/jest/blob/fe7f28c9d1941f5c2726831cd9d9e479b401610e/packages/jest-config/src/readConfigFileAndSetRootDir.ts#L46).
This means that instead of using the `commonjs` we set for `ts-node`,
normal Node resolution kicks in, and is now treating `jest.config.ts` as
ESM.
This PR fixes this issue by using an explicit `.cts` extension, which
forces CommonJS that we assume for Jest configs.
Note: For Jest 29 or earlier we need to keep `jest.config.ts` since the
`.cts` extension is not supported prior to Jest 30.
There's also a fix for an existing issue where using `module.exports` of
anything else from `@types/node` will error out if `tsconfig.json` has a
`types` field but does not include `node`. See:
https://www.loom.com/share/7ebe3c90a70e4ec7bfa53cbcccaaea7dFixes#32236
---------
Co-authored-by: Claude <noreply@anthropic.com>
## Current Behavior
`@nx/vite` has a peerDep range of only `1 | 2 | 3` for `vitest`.
This will cause peer dep conflicts for using wishing to use Vitest 4.
## Expected Behavior
Add Vitest 4 to the peerDep range of `@nx/vite` to prevent conflicts.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
when trying to format with prettier while outside of the workspace (for
example in a `tmp` nx installation like is created during
`configure-ai-agents`), resolving prettier could fail even though it's
present in the actual workspace.
## Expected Behavior
resolving prettier works if it's available in either the proper
workspace or the tmp one by specifying `paths` in `require.resolve`
## Current Behavior
We currently support Next 14 and 15.
## Expected Behavior
Add support for Next 16, bringing support to 14, 15, 16.
Existing workspaces will continue to use the version they are on.
New workspaces will use Next 16.
Refer to Next 16 Migration Guide for migrating from Next 15 to 16
## Related Issue(s)
Fixes#33207
---------
Co-authored-by: Eric Büttner <eric.buettner@tuffz.com>
This pull request makes a minor update to the CI workflow configuration.
The change simplifies the `Start CI Run` step by removing the
`--fix-tasks="!*check-commit*"` option from the `npx nx-cloud@next
start-ci-run` command.
Updates @module-federation packages from 0.18.0 to 0.21.2 and
@module-federation/node from 2.7.11 to 2.7.21 to address Koa Open
Redirect vulnerability (CVE: GHSA-g8mr-fgfg-5qpc).
The vulnerability was in transitive dependencies:
@nx/react → @nx/module-federation → @module-federation/enhanced →
@module-federation/dts-plugin → koa@3.0.1-3.0.2
Changes:
- Updated package.json dependencies in @nx/module-federation and
@nx/rspack
- Updated version constants in Angular and React utils/versions.ts
- Added 22.2.0 migrations to all affected packages
Fixes#33285
This PR clarifies that Nx 22 removed SVGR support from Next.js and React
(Webpack/Rspack).
<img width="942" height="1185" alt="image"
src="https://github.com/user-attachments/assets/6ea8393f-b037-489f-804b-eb06d8d07e4c"
/>
Previously we had this option `svgr: true` but was configurable and
didn't align with current best practices like `import Logo from
'./logo.svg?react'`. We removed it from Nx 22, provided a migration, but
users will be confused by the docs.
Fixes DOC-326
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
`createVitest` is looking at the full workspace every time. It should
only look at the directory with the config file.
## Expected Behavior
Pass the root as the projectRoot of the config file found.
## Current Behavior
The Nx TUI doesn't provide any visual feedback about task progress
outside of the terminal window itself. Users need to keep the terminal
visible to see task completion status.
## Expected Behavior
With this PR, the TUI now displays real-time task completion progress in
the terminal window chrome (tabs, title bars, dock icons) using the OSC
9;4 escape sequence. This provides at-a-glance progress feedback even
when the terminal is minimized or in the background.
## Related Issue(s)
N/A - This is a new feature enhancement
## Implementation Details
### What is OSC 9;4?
OSC 9;4 is a terminal escape sequence for displaying progress
indicators, originally from ConEmu and now supported by multiple modern
terminals.
### Key Changes
- Added `update_ghostty_progress()` method to calculate and display task
completion percentage
- Added `clear_ghostty_progress()` method to hide progress when done
- Integrated progress updates into task lifecycle (start, status update,
exit)
- Uses ST terminator (`\x1b\\`) for maximum terminal compatibility
- Writes to stderr to avoid conflicts with TUI rendering on stdout
### Supported Terminals
- **Ghostty** - Full support
- **Windows Terminal** (v1.6+) - Full support
- **VTE-based terminals** (GNOME Terminal, Ptyxis) - Full support
- **Other terminals** - Gracefully ignore sequences (no errors)
### Terminal Compatibility Note
The implementation uses the ST (String Terminator) escape sequence
rather than BEL, as this is preferred by Ghostty and required by
VTE-based terminals, while remaining compatible with Windows Terminal.
## Testing
Tested with:
- Building the native module successfully
- Rust formatting and linting passes
- Running nx commands with the TUI active
The progress indicator updates in real-time as tasks complete and clears
automatically when the TUI exits.
## Current Behavior
When processing scheduled batches, tasks are hashed one at a time even
though the hasher has a `hashTasks` method for batch hashing.
Results from a batch with 109 tasks.
```
hash batch: 8.297s
```
## Expected Behavior
Tasks without custom hashers should be batch-hashed using the hasher's
`hashTasks` method for better performance.
Results from a batch with 109 tasks.
```
hash batch: 991.725ms
```
## Related Issue(s)
This is a performance optimization for task hashing.
---
**Changes:**
- Added a new `hashTasks` function that intelligently separates tasks
with custom hashers from those without
- Tasks with custom hashers are hashed individually using `Promise.all`
- Tasks without custom hashers are batch-hashed using the hasher's
`hashTasks` method
- The function automatically filters out tasks that already have a hash
- Updated `processScheduledBatch` in task-orchestrator to use this new
function
## Current Behavior
Vitest's `createVitest` can be over-eager and include other projects in
the workspace when finding relevant test specifications.
This leads atomizer to look at relative paths outside the project root.
## Expected Behavior
Ensure that the plugin does not look at projects outside the project
root.
## Current Behavior
The Maven migration version was incorrectly set to `0.0.8-beta.0` in the
migrations.json file.
## Expected Behavior
The migration version should be set to `22.1.0-beta.4` to align with the
Nx release version.
## Related Issue(s)
Fixes the migration version discrepancy in the Maven plugin.
Embeds videos about self-healing CI into the corresponding feature doc
---------
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
## Current Behavior
The Maven plugin is at version 0.0.7 with no automated migration path
for users to upgrade their pom.xml files.
## Expected Behavior
Users can upgrade to Maven plugin 0.0.8 and have their pom.xml files
automatically updated via the Nx migration system.
## Changes Made
- Updated Maven plugin version from 0.0.7 to 0.0.8 in:
- `packages/maven/package.json`
- `packages/maven/src/utils/versions.ts`
- `packages/maven/maven-plugin/pom.xml`
- Created `updateNxMavenPluginVersion()` utility function with:
- Proper XML parsing using `@xmldom/xmldom` DOM API
- Targeted updates only for `dev.nx.maven:nx-maven-plugin` elements
- Safe handling of all other version elements (dependencies, parent,
project, etc.)
- Comprehensive error handling
- Added migration `0-0-8/update-pom-xml-version.ts` that:
- Automatically runs when users upgrade to 0.0.8
- Updates root pom.xml files via the utility function
- Logs status of migrations performed
- Registered migration in `migrations.json`
- Added comprehensive unit tests (9 test cases):
- Updates only the nx-maven-plugin version
- Does not update other plugin versions
- Handles missing files gracefully
- Preserves XML formatting and structure
- Handles multiple plugin references
- Handles whitespace correctly
- Only updates plugins, not parent/project versions
- All tests passing (27/27)
## Test Plan
- [x] Unit tests pass (27 passing tests)
- [x] XML parsing correctly identifies and updates only
`dev.nx.maven:nx-maven-plugin`
- [x] Other version elements remain untouched
- [x] Migration registration validated
## Current Behavior:
Tui is working, but only able to be enabled on windows via env vars or
explicit command line config
## Expected Behavior:
Tui is default on
## Current Behavior
Maven and .NET use a bespoke script to handle skipping an optional
native target in case a system is not setup on a dev's machine.
.NET isn't in codeowners
## Expected Behavior
This pull request introduces improvements to the build and formatting
workflows for the `.NET` and Maven plugins, streamlining the execution
of native targets and updating code ownership assignments. The changes
focus on refactoring build scripts to use a unified runner, adding new
formatting capabilities for .NET projects, and updating the CODEOWNERS
file for clearer team responsibilities.
**Build and Format Workflow Improvements**
* Refactored the `.NET` analyzer build command in
`packages/dotnet/project.json` to use the new `run-native-target.js`
script, replacing the previous direct script invocation.
[[1]](diffhunk://#diff-036c1a7f2e7d98f5a3207441f2ff1cb25b5b5a03343672ce473f4ff0189a5946L25-R25)
[[2]](diffhunk://#diff-42d990ddcbf8d3585553503097ee8b8d1fff8cb6e25e0cd545a87e11d64c03a8L1-L7)
* Added new `format-native` and `_format-native` targets to
`packages/dotnet/project.json`, enabling verification and fixing of code
formatting for the .NET analyzer using `dotnet format`.
**Unified Native Target Runner**
* Introduced the `scripts/run-native-target.js` script to standardize
running native build and install targets, controlled by environment
variables for skipping builds.
**Maven Plugin Build Refactor**
* Updated the Maven plugin's install workflow in
`packages/maven/maven-plugin/project.json` to use the new native target
runner, and removed the obsolete `scripts/build-maven-analyzer.js`
script.
[[1]](diffhunk://#diff-2763fe8a7c2989643f53370a14a08c06616e85c29340fd5bcfa1a67d0deaee7aL11-R11)
[[2]](diffhunk://#diff-972edab06956ad35145cbc20b8e250e7067fbb288b1f99c92661c24f64d3e69dL1-L7)
**Ownership Updates**
* Updated the `CODEOWNERS` file to assign `.NET`-related directories to
`@FrozenPandaz` and `@AgentEnder`, clarifying team responsibilities.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
We support Storybook 8 and 9
## Expected Behavior
Add support for Storybook 10, bringing Storybook support to 8, 9 and 10
## Related Issue(s)
Fixes#33141
## Current Behavior
Maven dependencies were not being resolved correctly from project roots,
which affected the dependency analysis in monorepos with Maven projects.
## Expected Behavior
Maven dependencies should be properly resolved from each project's root,
allowing Nx to correctly understand the project graph for Maven-based
projects.
## Changes Made
- Updated Maven plugin Kotlin code to properly resolve dependencies from
project roots
- Fixed devkit internal utilities to properly handle Maven dependency
resolution
- Updated TypeScript dependencies plugin to align with the new
resolution logic
## Files Changed
-
`packages/maven/maven-plugin/src/main/kotlin/dev/nx/maven/NxProjectAnalyzer.kt`
-
`packages/maven/maven-plugin/src/main/kotlin/dev/nx/maven/NxProjectAnalyzerMojo.kt`
- `packages/maven/src/plugins/dependencies.ts`
- `packages/dotnet/src/plugins/create-dependencies.ts`
- `packages/devkit/internal.ts`
- `packages/nx/src/devkit-internals.ts`
Fixes #XXXXX
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
CodeQL is not enabled for C#, but C# is in the nx repo
## Expected Behavior
CodeQL is enabled on C#
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: Claude <noreply@anthropic.com>
This PR primes the cache for `@vitejs/plugin-vue`, similar to how we
already do for `esbuild`. When `vite.config.ts` is compiled into CJS,
then doing `require('@vitejs/plugin-vue')` may error with a race
condition if an `import` of the same module is in progress.
Fixes #NXC-3289
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
there are recommendations for using the CIPE fixing tools but they are
being removed
## Expected Behavior
removed tools should not be mentioned anymore.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
Completed Maven documentation updates:
- [x] Add `nx show projects` command to display Maven projects after
initialization
- [x] Change example from `nx build` to `nx verify`
- [x] Simplify configuration section to use `plugins: ["@nx/maven"]`
with defaults
- [x] Update configuration description to accurately explain how targets
are created
## Summary
Successfully updated the Maven plugin documentation:
1. **Added `nx show projects` command** - Shows users how to list
discovered Maven projects after running `nx init`
2. **Changed example to `nx verify`** - Uses a more appropriate Maven
lifecycle phase that includes tests and verification
3. **Simplified configuration** - Updated the configuration section to
show the simpler string array syntax `plugins: ["@nx/maven"]`
4. **Corrected target creation description** - Updated the text to
accurately explain that `@nx/maven` automatically retrieves information
about projects from Maven and creates targets for each phase, goal, and
some additional targets for CI
<!-- START COPILOT CODING AGENT SUFFIX -->
<details>
<summary>Original prompt</summary>
> Expand this section
https://nx.dev/docs/technologies/java/maven/introduction#add-nx-to-a-maven-workspace
>
> With instructions to run the following
>
> nx show projects
>
> To see a list of Maven projects
>
> And change nx build Maven project to nx verify Maven project
>
</details>
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: FrozenPandaz <8104246+FrozenPandaz@users.noreply.github.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
devcontainer wasn't working reliably
## Expected Behavior
devcontainer should work reliably and reuse the configuration setup we
have for all these different tools that are needed
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
in a `.nx` installation, `require.resolve` won't find the `extends`
preset nx.json file because there are no root `node_modules`
## Expected Behavior
in a `.nx` installation, the nested `.nx/installation/node_modules` are
also used to try and resolve the preset.
This PR builds on the previous fix that ensured `eslint-config-next` was
correctly installed when using Next.js 15. #30258
Now, the logic has been further refined to dynamically determine the
installed Next.js version and install the corresponding
`eslint-config-next` version accordingly.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
### Current Behavior
- `next@~15.1.4` is installed
- `eslint-config-next@14.2.16` is installed (incorrect for Next.js 15)
### Expected Behavior
If Next.js 15 is detected -> eslint-config-next@15.1.4 is installed
If Next.js 14 is detected -> eslint-config-next@14.2.16 is installed
### GitHub Repo
https://github.com/tuffz/new-nx-with-preset-nextjs
### Steps to Reproduce
1. Run the following command from the official documentation:
`npx create-nx-workspace@latest --preset=next`
3. Open `package.json` and check the installed dependencies
- `next@~15.1.4` is installed
- `eslint-config-next@14.2.16` is installed
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes#30257 (& #30258)
guides couldn't be merged into one page w/ tabs and keep consistent
headers. so we're back to splitting them out. but all under a
source-control-integration parent route. so we don't need to do any
redirects and such as the index route will contain the links to each one
Removed duplicate information about installing Nx globally.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
Duplicate line.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Removed duplicate line.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
the implementation is brittle and will fail if `contextFileName` is not
a string
## Expected Behavior
we should just not handle other things that folks are putting in there.
but not fail
---------
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Dogfoods the Pnpm Catalogs feature in the Nx repo. This is the first
step to move all dependencies to Pnpm Catalogs definitions. More work
will be done incrementally later as we consolidate package versions
across the repository.
The initial list of dependencies moved to Pnpm Catalogs is:
- Angular packages
- React packages
- TypeScript packages
- Jest packages
- Rspack packages
- Some common utilities
## Current Behavior
We have tried to enable v8 serialization again... but it still seems
problematic. We don't want to revert again... so we evaluated some
options:
1. Disable by default
2. Disable for the single client method we think may be problematic
3. Fall back to JSON if v8 fails
4. Disable by default and still fall back if JSON fails
## Expected Behavior
We decided to update Nx such that the default behavior will be a
combination of #4, and #2. So by default we use JSON, if that
serialization fails we'll try v8... but there's an exception so the
method we know to be an issue will never try v8.
If you opt in to v8 by default, the combo changes to #3 + #2. So, by
default we'd use v8... if it fails try json... never try v8 for
processInBackground
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
Docker plugin assumed `commitSha` was always non-null; when `null`,
`shortCommitSha.slice` caused a runtime error during target
interpolation.
## Expected Behavior
Plugin should succeed even if latest commit SHA cannot be resolved,
simply omitting shortCommitSha-based substitutions.
## Changes
- Added null guard: `shortCommitSha` now set to `commitSha ?
commitSha.slice(0,7) : null`.
- Added test "should not throw when commitSha is null" verifying node /
target creation succeeds.
- No breaking changes; only broadens safe input surface.
## Additional notes
Logic only executes when `commitSha` was previously null (error case);
normal paths unchanged. If consumers interpolate `{shortCommitSha}`,
they should handle possible null (unchanged if interpolation is already
optional).
This PR removes the need to check links during deploy and instead
enforces it in CI. This removes the need to build astro-docs when
building next.js app.
Reduces Vercel build from 10-11 mins to 6.5 mins.
<img width="1231" height="94" alt="image"
src="https://github.com/user-attachments/assets/ad5d4459-f917-4609-8c00-151f61dc29d6"
/>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Update release notes entry tor 2025.07.3 - short link support for DTE
summary
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
You can see the issue here:
https://staging.nx.app/cipes/68fba96042d3126ee8ec0d19/analysis?runGroup=18785923031-1-linux
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes BAC-1387
This PR wraps one of the exports of `@nx/webpack` within a dynamic
function that ultimately requires `tsquery`. This causes an issue in
yarn v1 where `typescript` cannot be resolved thus causing an error when
`@nx/webpack` is imported.
The errors happens on every new workspace that starts from empty:
```
npx create-nx-workspace --preset=ts --pm=yarn
nx add @nx/web
nx g @nx/web:app apps/demo --bundler=webpack
```
Results in:
```
NX Cannot find module 'typescript'
Require stack:
- /private/var/folders/p4/6tvkdn_11xlc_2j999ybhbkr0000gn/T/tmp-59809-4kedkj5a1XsO/node_modules/@phenomnomnominal/tsquery/dist/src/ast.js
- /private/var/folders/p4/6tvkdn_11xlc_2j999ybhbkr0000gn/T/tmp-59809-4kedkj5a1XsO/node_modules/@phenomnomnominal/tsquery/dist/src/index.js
- /private/var/folders/p4/6tvkdn_11xlc_2j999ybhbkr0000gn/T/tmp-59809-4kedkj5a1XsO/node_modules/@nx/webpack/src/generators/convert-config-to-webpack-plugin/lib/extract-webpack-options.js
- /private/var/folders/p4/6tvkdn_11xlc_2j999ybhbkr0000gn/T/tmp-59809-4kedkj5a1XsO/node_modules/@nx/webpack/src/generators/convert-config-to-webpack-plugin/convert-config-to-webpack-plugin.js
- /private/var/folders/p4/6tvkdn_11xlc_2j999ybhbkr0000gn/T/tmp-59809-4kedkj5a1XsO/node_modules/@nx/webpack/index.js
- /private/tmp/web4/node_modules/@nx/devkit/src/utils/package-json.js
- /private/tmp/web4/node_modules/@nx/devkit/src/generators/to-js.js
- /private/tmp/web4/node_modules/@nx/devkit/public-api.js
- /private/tmp/web4/node_modules/@nx/devkit/index.js
- /private/tmp/web4/node_modules/@nx/web/src/generators/application/application.js
- /private/tmp/web4/node_modules/nx/src/config/schema-utils.js
- /private/tmp/web4/node_modules/nx/src/command-line/run/executor-utils.js
- /private/tmp/web4/node_modules/nx/src/project-graph/utils/project-configuration-utils.js
- /private/tmp/web4/node_modules/nx/src/utils/package-json.js
- /private/tmp/web4/node_modules/nx/bin/nx.js
Pass --verbose to see the stacktrace.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```
Note: Other generators like React/Vue are fine since they have
dependency on tsquery, which installs typescript.
---------
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
## Summary
This PR includes three performance and correctness improvements:
1. **Split lockfile cache into separate node and dependency caches** -
Previously, both createNodes and createDependencies would read/write the
entire cache. Now each manages its own cache independently:
- `parsed-lock-file.nodes.json` for external nodes
- `parsed-lock-file.dependencies.json` for dependencies
2. **Prevent duplicate plugin resolution calls with promise cache** -
Added a promise cache to prevent concurrent duplicate calls to
`retrieveProjectConfigurationsWithoutPluginInference` when multiple
plugins fail to resolve simultaneously
3. **Normalize targets in separate loop after validation** - Moved
target normalization out of the validation loop to ensure proper
sequencing
## Test plan
- [ ] Tests pass (currently failing in CI - needs investigation)
- [ ] Build succeeds
- [ ] Lint passes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
When stringifying a pnpm v9 lockfile with workspace packages, Nx crashes
with: "Cannot destructure property 'specifiers' of 'projectSnapshot' as
it is undefined."
This occurs when:
- The lockfile has a root importer with `link:` references to workspace
packages
- But the lockfile is missing the workspace package importer entries
- The code tries to access `importers[importerPath]` which returns
undefined
- This undefined value gets added to the output lockfile
- During serialization, it crashes when trying to destructure undefined
Workspace packages with missing importers are now silently skipped
during lockfile serialization. This prevents the crash and allows Nx to
continue operating with out-of-sync lockfiles.
The fix adds a null check before adding workspace dependency importers
to the output lockfile.
Closes NXC-3244
This reverts commit 54db861b72.
## Current Behavior
Daemon messaging is all done over JSON messages
## Expected Behavior
Daemon messages use v8 serialization to avoid string length issues
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
With the Accept header in place during the retrieval of cache, the
client always expects an octet-stream from the server.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
When the `Accept` header is missing, you might not get the correct data
depending on the underlying implementation of a self hosted cache
solution. We use AWS API Gateway which has a hard requirement for
`Accept` to determine how it should convert the data.
Fixes#33092
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
We search for the gradle wrapper of a project by first looking at the
project root, then traversing upwards to the workspace root. If a
workspace has a separate gradle project defined that does not contain a
wrapper, then Nx will error.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Add a field to the gradle plugin that will allow users to specify a
custom gradle installation within their workspace. Nx will check for a
gradle wrapper that the specified location when executing gradle tasks.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes NXC-3147
## Current Behavior
When the Nx daemon returns a cached project graph from memory (without
recomputing), it does not write the graph to disk. This creates a
cache/daemon mismatch scenario:
1. Daemon has valid project graph in memory
2. A non-daemon process (fallback when daemon fails) encounters errors
and writes cache to disk with those errors
3. Parent process gets clean graph from daemon
4. Forked executor processes read from disk cache which contains errors
5. The errors cause `readProjectGraphCache()` to return `null` (when no
`minimumComputedAt` is provided)
6. This triggers a misleading "No cached ProjectGraph is available"
error instead of surfacing the actual errors
This issue manifests intermittently in CI environments, especially when:
- Daemon connection timeouts occur
- Multiple concurrent processes are running (DTE scenarios)
- File system latency is high
## Expected Behavior
The daemon should always write its current project graph to disk
whenever it returns it, ensuring the disk cache stays synchronized with
the daemon's in-memory cache. This prevents stale or errored caches from
persisting when the daemon has a valid graph.
## Related Issue(s)
Fixes NXC-3030
## Implementation Details
Modified `getCachedSerializedProjectGraphPromise()` in
`packages/nx/src/daemon/server/project-graph-incremental-recomputation.ts`
to write the project graph cache to disk after retrieving the result,
even when reusing the in-memory cached graph.
The fix ensures that:
- Any errored cache written by a non-daemon process gets overwritten by
the daemon's valid graph
- Forked executor processes always read a consistent cache that matches
what the daemon served to the parent process
- Real errors are properly surfaced instead of being hidden by a generic
"no cache available" message
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
Local `tsconfig.app.json` is never picked up due to wrong resolved path.
## Expected Behavior
Local `tsconfig.app.json` is picked up and the path aliases are added to
Vite.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes#33231
Removed "Explain with AI" feature documentation and redirected all URLs
to Self-Healing CI. Added sunset notice to blog posts, updated
configuration files, and preserved historical content for reference.
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
## Current Behavior
When `Dockerfile` is at project root, we attempt to append `--tag .`
which is invalid for docker.
## Expected Behavior
Ensure that if `Dockerfile` is at project root, we use `workspaceRoot`
to determine the `--tag`.
Note, this tag is primarily used as a deterministic method for Nx to
find the correct docker image when calling `docker run` and `nx release`
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
When using `--output-style=stream-without-prefixes` nx incorrectly shows
the TUI instead of streaming
## Expected Behavior
When using `--output-style=stream-without-prefixes`, nx should stream
with no prefixes just like it does with `--output-style=stream`
## Related Issue(s)
Fixes#32535
Co-authored-by: Hugo Burton <hugo.burton@westpac.com.au>
## Current Behavior
`node_modules` are being copied during copy-local-native
## Expected Behavior
`node_modules` are not being copied during `copy-local-native`
## Current Behavior
Maven is installed as a global dependency for all e2e targets
(`e2e-local`, `e2e-ci--**/**`, and `e2e-macos-ci--**/*`), even when only
the Maven e2e tests need it.
## Expected Behavior
Maven should only be installed as a dependency for the Maven e2e tests
that actually use it, avoiding unnecessary installations for other e2e
test projects.
## Changes Made
- Removed `nx-maven-plugin:install` from the global e2e target defaults
in `nx.json`
- Added `nx-maven-plugin:install` as a specific dependency to the Maven
e2e project targets in `e2e/maven/project.json`
This optimization ensures Maven is only installed when needed, reducing
unnecessary build overhead for other e2e tests.
## Current Behavior
`nx init` does not search for `Dockerfile` patterns to suggest adding
the `@nx/docker` plugin.
## Expected Behavior
`nx init` finds and suggests `@nx/docker` plugin
Fixes NXC-3319
Fixes the order of the arguments in invocations to
`resolveCatalogReference` when resolving catalog references from the
filesystem (not using a `Tree`).
## Current Behavior
In some scenarios, when some processes terminate unexpectedly (e.g.
crashed due to OOM), the task runner will incorrectly determine their
exit code to be 0. This results in Nx storing the task results as a
success, which can cause cache hits with false positive successes.
## Expected Behavior
When processes terminate unexpectedly (e.g. crashed due to OOM), the
task runner should correctly determine their exit code from the signal,
and it should never be 0. The stored task result should not be marked as
successful.
## Related Issue(s)
Fixes#29204
## Current Behavior
When `@nx/docker` is registered via string only (`nxJson.plugins:
["@nx/docker"]`, project graph creation fails because we try to access
`options.buildTarget`.
## Expected Behavior
Handle undefined `options` gracefully, and still create the default
target.
## Related Issue(s)
Fixes NXC-3320
## Current Behavior
Dependencies are outdated and Rust tooling needs to be updated.
## Expected Behavior
Rust dependencies are updated to their latest compatible versions.
## Changes
- Updated Rust dependencies
- Rebased with latest `origin/master`
- Updated pnpm lock file
## Related Issue(s)
None
## Current Behavior
No docs describing migration path
## Expected Behavior
Docs describing migration path
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Caleb Ukle <caleb@nrwl.io>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
## Current Behavior
.NET sometimes bails on a failed mutex in e2e
## Expected Behavior
it runs only 1 at a time
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
It was removed when the scripts were cleaned up during next.js->astro
migration. Adding this back since blog posts need to be synced first
before serving.
## Current Behavior
When project configuration errors occur due to invalid token usage
(e.g., `{workspaceRoot}` in the middle of a path), error messages don't
provide sufficient context about where the error occurred.
## Expected Behavior
Error messages should include:
- For project-level errors: the project and target context (e.g.,
"libs/my-app:build")
- For nx.json targetDefaults errors: the nx.json context (e.g.,
"nx.json[targetDefaults]:test")
This makes it much easier for users to locate and fix the configuration
issue.
## Changes
This PR adds comprehensive integration tests to verify the improved
error messaging:
- Test for project-level invalid token usage showing project:target
context
- Test for nx.json targetDefaults invalid token usage showing nx.json
context
Tests use mock plugins to simulate realistic scenarios where invalid
`{workspaceRoot}` token usage would occur, ensuring the error messages
contain the expected context information.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
by default code blocks will get text as the lang if not set or the lang
is not supported
but we should be so I don't have to look at the warnings in the terminal
output 😅
also remove the frame=none since the team preferred to have the terminal
frame
Example of invalid codeblock messages

Also added conformance rule for validating image pages for public and
src/assets/ directory
examples of incorrect absolute path ref from "public" folder and
incorrect absolute path:

fixes DOC-242
fixes DOC-259
## Current Behavior
Currently, when configuring the `@nx/docker` plugin, we only set the
target name and a basic command.
The intention was that targetDefaults might be able to be used to
configure additional args, but this falls short in some places.
## Expected Behavior
Allow setting additional args when configuring the `@nx/docker` plugin
that supports interpolated values, similar to `versionSchemes`.
This will allow additional flexibility when setting up the docker build
command such as:
```json
{
"plugin": "@nx/docker",
"options": {
"buildTarget": {
"name": "docker:build",
"args": ["-t {projectName}"]
}
}
}
```
This means that we can use `nx run-many -t docker:build` and it will
successfully add the name of the projects into the tag.
This is one example, other examples include being able to set individual
Docker Layer Caching where each registry needs a unique name.
## Current Behavior
The Maven plugin currently checks if a path is absolute by using
`outputFile.startsWith("/")`. This only works on Unix-like systems and
fails on Windows where absolute paths start with a drive letter (e.g.,
`C:\`).
## Expected Behavior
The Maven plugin should correctly identify absolute paths on all
platforms (Windows, macOS, Linux) using the platform-agnostic
`File.isAbsolute()` method.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
N/A - This is a proactive bug fix for cross-platform compatibility.
---
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
## Current Behavior
When Nx Cloud is used, but the Nx Cloud client is unavailable, an error
is thrown and commands are not run.
## Expected Behavior
When Nx Cloud is used, but the client is unavailable, continue execution
without Nx Cloud.
## Related Issue(s)
Fixes NXC-3175
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
Tests either cause agents to run out of memory:
<img width="956" height="358" alt="image"
src="https://github.com/user-attachments/assets/e55bae05-7757-46e7-88ed-158f72411195"
/>
<img width="1278" height="336" alt="image"
src="https://github.com/user-attachments/assets/40dc8dd6-4409-461e-b75a-5c1c36551da5"
/>
Or setup tasks fail due to network flakiness:
https://staging.nx.app/runs/J1qWVZA7K5
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
A fully affected, cache busted task graph should run without any
failures.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
This pull request updates the documentation for configuring .NET target
types in the `nx.json` file to provide clearer instructions and
examples. The changes make it easier to understand how to customize
target names and configurations for the `@nx/dotnet` plugin.
Improvements to configuration documentation:
* Added a section describing how each target type can be configured,
including renaming targets, customizing options, disabling targets, and
specifying additional properties.
* Updated the example configuration to show how to rename targets (e.g.,
"build" to "compile"), add configurations (e.g., production
optimization), set dependencies between targets, and disable targets
(e.g., disabling "pack").
* Clarified that targets are created with the configuration specified in
the `nx.json` `plugins` array, rather than just with custom names.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
The first-class programmatic API of nx release is only documented within
the manage releases introduction and is incomplete (`ReleaseClient` is
not covered at all).
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
The programmatic API has its own in depth guide, which is then
cross-referenced from the manage releases guide. `ReleaseClient` is now
documented including its new Nx 22 features.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
## Current Behavior
`run`/`watch` are not mentioned in docs
## Expected Behavior
`run`/`watch` docs are accurate
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
---------
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
## Current Behavior
Maven plugin targets are only created for goals explicitly bound to
executions in the POM. Goals defined in a plugin but not bound to any
execution are not available as Nx targets.
## Expected Behavior
All available Maven goals should be accessible as Nx targets, including:
- Goals bound to executions (existing behavior)
- Unbound goals defined in the plugin (new behavior)
## Changes Made
1. **Added unbound goal support**: The `NxTargetFactory` now creates
targets for goals defined in a plugin but not bound to any execution.
These targets are created with the format `goalPrefix:goalName` without
an execution ID.
2. **Added continuous build tracking**:
- Added `continuous` property to `NxTarget` data class to track whether
a goal supports continuous builds
- Updated `MojoAnalyzer` to detect continuous goals from the cache
configuration
- All targets now properly propagate continuous mode information
3. **Code improvements**:
- Improved formatting and indentation for consistency
- Made `execution` parameter optional in `createSimpleGoalTarget` to
support both bound and unbound goals
- Updated command generation to work with or without execution IDs
## Related Issue(s)
This change enables better Maven goal discovery and execution in Nx
monorepos.
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
When an invalid output exists we report:
```
NX The following outputs are invalid:
- foo.txt
Run `nx repair` to fix this.
```
Without specifying the reason, we tell to run `nx repair`.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
More feedback provided to the user as to the error with their output:
```
NX The following outputs are invalid:
- foo.txt
** Reason: Outputs must start with either "{workspaceRoot}/" or "{projectRoot}/".
Run `nx repair` to fix this.
```
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
This PR fixes some pages that don't use the proper markdoc syntax for
line lighting in code blocks. The `{% meta %}` tag is needed and it is
missing in some places.
Closes DOC-2790
## Current Behavior
When the daemon is disabled due to an error, the reason isn't captured,
making it harder to debug why the daemon was disabled.
## Expected Behavior
The error message/reason is now stored when marking the daemon as
disabled, allowing better visibility into what caused the daemon to be
disabled.
## Related Issue(s)
Node tool executions (non-watch) were exiting before completing. This
change ensures they complete before exit.
closes#32385
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
Node tool executions are not completing since #32356
## Expected Behavior
Node tool executions complete before exit
## Related Issue(s)
#32385Fixes#32385
## Current Behavior
When errors happen in earlier stages of compilation process, such as
processing global styles, these errors are not printed and the rspack
build process hangs at the Sealing phase.
## Expected Behavior
Ensure that errors that have occurred that would cause the process to
hang are printed and process exits correctly.
Part of this happens because the usual printing of errors happens in the
`afterDone` hook, which is never reached when the above occurs.
## Related Issue(s)
Fixes NXC-3268
## Current Behavior
Readme template files are being published to npm with inconsistent
naming conventions.
## Expected Behavior
Readme template files should be excluded from npm publication and
consistently named as `readme-template.md`.
## Changes
- Renamed all readme template files to `readme-template.md` across
angular-rspack-compiler, angular-rspack, dotnet, and maven packages
- Updated .npmignore files to exclude readme-template.md from npm
publication
- Updated package.json build commands to reference the new
readme-template.md paths
- Removed `!README.md__tpl__` exceptions from package.json files array
entries
add new page for the createNodes api compat
add callouts to extending project graph and tooling plugin page
fixes DOC-255
---------
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Fixes the pnpm caching setup for the `main-macos` job. It can currently
fail when the pnpm cache directory doesn't exist. We need to handle the
pnpm cache conditionally and separately from the node setup.
…as testing projects<!-- Please make sure you have read the submission
guidelines before posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
- Microsoft.Testing.Platform projects are not detected as having a test
target
- `serve` does not have an equivalent
## Expected Behavior
- Test projects are properly detected
- `serve` has been split into 2 targets:
- `watch`
- `run`
The split of `serve` mirrors the`dotnet` cli in the same way that we
mirrored `vite` when adding `preview` and `dev` targets when we moved
with project crystal. `watch` can be used for a variety of cases, but
provides hot reload + run a 'la `dev` / `serve`. `run` is more of a fire
and forget target that starts up the app. Both targets would only really
be used in local dev.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
…as testing projects
---------
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-21 16:31:55 -04:00
9392 changed files with 373087 additions and 311049 deletions
description: 'Link workspace packages in monorepos (npm, yarn, pnpm, bun). USE WHEN: (1) you just created or generated new packages and need to wire up their dependencies, (2) user imports from a sibling package and needs to add it as a dependency, (3) you get resolution errors for workspace packages (@org/*) like "cannot find module", "failed to resolve import", "TS2307", or "cannot resolve". DO NOT patch around with tsconfig paths or manual package.json edits - use the package manager''s workspace commands to fix actual linking.'
---
# Link Workspace Packages
Add dependencies between packages in a monorepo. All package managers support workspaces but with different syntax.
## Detect Package Manager
Check whether there's a `packageManager` field in the root-level `package.json`.
Alternatively check lockfile in repo root:
-`pnpm-lock.yaml` → pnpm
-`yarn.lock` → yarn
-`bun.lock` / `bun.lockb` → bun
-`package-lock.json` → npm
## Workflow
1. Identify consumer package (the one importing)
2. Identify provider package(s) (being imported)
3. Add dependency using package manager's workspace syntax
4. Verify symlinks created in consumer's `node_modules/`
---
## pnpm
Uses `workspace:` protocol - symlinks only created when explicitly declared.
```bash
# From consumer directory
pnpm add @org/ui --workspace
# Or with --filter from anywhere
pnpm add @org/ui --filter @org/app --workspace
```
Result in `package.json`:
```json
{"dependencies":{"@org/ui":"workspace:*"}}
```
---
## yarn (v2+/berry)
Also uses `workspace:` protocol.
```bash
yarn workspace @org/app add @org/ui
```
Result in `package.json`:
```json
{"dependencies":{"@org/ui":"workspace:^"}}
```
---
## npm
No `workspace:` protocol. npm auto-symlinks workspace packages.
```bash
npm install @org/ui --workspace @org/app
```
Result in `package.json`:
```json
{"dependencies":{"@org/ui":"*"}}
```
npm resolves to local workspace automatically during install.
description: Monitor Nx Cloud CI pipeline and handle self-healing fixes. USE WHEN user says "monitor ci", "watch ci", "ci monitor", "watch ci for this branch", "track ci", "check ci status", wants to track CI status, or needs help with self-healing CI fixes. Prefer this skill over native CI provider tools (gh, glab, etc.) for CI monitoring — it integrates with Nx Cloud self-healing which those tools cannot access.
---
# Monitor CI Command
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn subagents to interact with Nx Cloud, run deterministic decision scripts, and take action based on the results.
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CI-Attempt failures (e.g., lockfile updates) |
| `--new-cipe-timeout` | 10 | Minutes to wait for new CI Attempt after action |
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
Parse any overrides from `$ARGUMENTS` and merge with defaults.
## Nx Cloud Connection Check
Before starting the monitoring loop, verify the workspace is connected to Nx Cloud. Without this connection, no CI data is available and the entire skill is inoperable.
### Step 0: Verify Nx Cloud Connection
1.**Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
2.**If `nx.json` missing OR neither property exists** → exit with:
```
Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
```
3. **If connected** → continue to main loop
## Architecture Overview
1. **This skill (orchestrator)**: spawns subagents, runs scripts, prints status, does local coding work
2. **ci-monitor-subagent (haiku)**: calls one MCP tool (ci_information or update_self_healing_fix), returns structured result, exits
3. **ci-poll-decide.mjs (deterministic script)**: takes ci_information result + state, returns action + status message
4. **ci-state-update.mjs (deterministic script)**: manages budget gates, post-action state transitions, and cycle classification
## Status Reporting
The decision script handles message formatting based on verbosity. When printing messages to the user:
- Prepend `[monitor-ci]` to every message from the script's `message` field
- For your own action messages (e.g. "Applying fix via MCP..."), also prepend `[monitor-ci]`
## Anti-Patterns
These behaviors cause real problems — racing with self-healing, losing CI progress, or wasting context:
| Using CI provider CLIs with `--watch` flags (e.g., `gh pr checks --watch`, `glab ci status -w`) | Bypasses Nx Cloud self-healing entirely |
| Writing custom CI polling scripts | Unreliable, pollutes context, no self-healing |
| Cancelling CI workflows/pipelines | Destructive, loses CI progress |
| Running CI checks on main agent | Wastes main agent context tokens |
| Independently analyzing/fixing CI failures while polling | Races with self-healing, causes duplicate fixes and confused state |
**If this skill fails to activate**, the fallback is:
1. Use CI provider CLI for a one-time, read-only status check (single call, no watch/polling flags)
2. Immediately delegate to this skill with gathered context
3. Do not continue polling on main agent — it wastes context tokens and bypasses self-healing
## Session Context Behavior
If the user previously ran `/monitor-ci` in this session, you may have prior state (poll counts, last CI Attempt URL, etc.). Resume from that state unless `--fresh` is set, in which case discard it and start from Step 1.
## MCP Tool Reference
Three field sets control polling efficiency — use the lightest set that gives you what you need:
The `ci_information` tool accepts `branch` (optional, defaults to current git branch), `select` (comma-separated field names), and `pageToken` (0-based pagination for long strings).
The `update_self_healing_fix` tool accepts a `shortLink` and an action: `APPLY`, `REJECT`, or `RERUN_ENVIRONMENT_STATE`.
## Default Behaviors by Status
The decision script returns one of the following statuses. This table defines the **default behavior** for each. User instructions can override any of these.
| `self_healing_throttled` | Reject old fixes, attempt local fix. |
| `no_new_cipe` | CI Attempt never spawned. Auto-fix workflow or exit with guidance. |
| `cipe_no_tasks` | CI failed with no tasks. Retry once with empty commit. |
**Key rules (always apply):**
- **Git safety**: Stage specific files by name — `git add -A` or `git add .` risks committing the user's unrelated work-in-progress or secrets
- **Environment failures** (OOM, command not found, permission denied): bail immediately. These aren't code bugs, so spending local-fix budget on them is wasteful
- **Gate check**: Run `ci-state-update.mjs gate` before local fix attempts — if budget exhausted, print message and exit
## Main Loop
### Step 1: Initialize Tracking
```
cycle_count = 0 # Only incremented for agent-initiated cycles (counted against --max-cycles)
start_time = now()
no_progress_count = 0
local_verify_count = 0
env_rerun_count = 0
last_cipe_url = null
expected_commit_sha = null
agent_triggered = false # Set true after monitor takes an action that triggers new CI Attempt
poll_count = 0
wait_mode = false
prev_status = null
prev_cipe_status = null
prev_sh_status = null
prev_verification_status = null
prev_failure_classification = null
```
### Step 2: Polling Loop
Repeat until done:
#### 2a. Spawn subagent (FETCH_STATUS)
Determine select fields based on mode:
- **Wait mode**: use WAIT_FIELDS (`cipeUrl,commitSha,cipeStatus`)
- **Normal mode (first poll or after newCipeDetected)**: use LIGHT_FIELDS
Call the `ci_information` tool with the determined `select` fields for the current branch. Wait for the result before proceeding.
- **`action == "poll"`**: Print `output.message`, sleep `output.delay` seconds, go to 2a
- If `output.newCipeDetected`: clear wait mode, reset `wait_mode = false`
- **`action == "wait"`**: Print `output.message`, sleep `output.delay` seconds, go to 2a
- **`action == "done"`**: Proceed to Step 3 with `output.code`
### Step 3: Handle Actionable Status
When decision script returns `action == "done"`:
1. Run cycle-check (Step 4) **before** handling the code
2. Check the returned `code`
3. Look up default behavior in the table above
4. Check if user instructions override the default
5. Execute the appropriate action
6. **If action expects new CI Attempt**, update tracking (see Step 3a)
7. If action results in looping, go to Step 2
#### Tool calls for actions
Several statuses require fetching additional data or calling tools:
- **fix_apply_ready**: Call `update_self_healing_fix` with action `APPLY`
- **fix_needs_local_verify**: Call `ci_information` with HEAVY_FIELDS for fix details before local verification
- **fix_needs_review**: Call `ci_information` with HEAVY_FIELDS → get `suggestedFixDescription`, `suggestedFixSummary`, `taskFailureSummaries`
- **fix_failed / no_fix**: Call `ci_information` with HEAVY_FIELDS → get `taskFailureSummaries` for local fix context
- **environment_issue**: Call `update_self_healing_fix` with action `RERUN_ENVIRONMENT_STATE`
- **self_healing_throttled**: Call `ci_information` with HEAVY_FIELDS → get `selfHealingSkipMessage`; then call `update_self_healing_fix` for each old fix
### Step 3a: Track State for New-CI-Attempt Detection
After actions that should trigger a new CI Attempt, run:
The script returns `{ waitMode, pollCount, lastCipeUrl, expectedCommitSha, agentTriggered }`. Update all tracking state from the output, then go to Step 2.
### Step 4: Cycle Classification and Progress Tracking
When the decision script returns `action == "done"`, run cycle-check **before** handling the code:
The script returns `{ cycleCount, agentTriggered, envRerunCount, approachingLimit, message }`. Update tracking state from the output.
- If `approachingLimit` → ask user whether to continue (with 5 or 10 more cycles) or stop monitoring
- If previous cycle was NOT agent-triggered (human pushed), log that human-initiated push was detected
#### Progress Tracking
- `no_progress_count`, circuit breaker (5 polls), and backoff reset are handled by ci-poll-decide.mjs (progress = any change in cipeStatus, selfHealingStatus, verificationStatus, or failureClassification)
- `env_rerun_count` reset on non-environment status is handled by ci-state-update.mjs cycle-check
- On new CI Attempt detected (poll script returns `newCipeDetected`) → reset `local_verify_count = 0`, `env_rerun_count = 0`
- If fix is wrong → run `ci-state-update.mjs gate --gate-type local-fix`. If not allowed, print message and exit. Otherwise → Reject + Fix From Scratch Flow
### fix_failed / no_fix
Spawn FETCH_HEAVY subagent for `taskFailureSummaries`. Run `ci-state-update.mjs gate --gate-type local-fix` — if not allowed, print message and exit. Otherwise attempt local fix (counter already incremented by gate). If successful → commit, push, enter wait mode. If not → exit with failure.
### environment_issue
1. Run `ci-state-update.mjs gate --gate-type env-rerun`. If not allowed, print message and exit.
2. Spawn UPDATE_FIX subagent with `RERUN_ENVIRONMENT_STATE`
3. Enter wait mode with `last_cipe_url` set
### self_healing_throttled
Spawn FETCH_HEAVY subagent for `selfHealingSkipMessage`.
1.**Parse throttle message** for CI Attempt URLs (regex: `/cipes/{id}`)
2.**Reject previous fixes** — for each URL: spawn FETCH_THROTTLE_INFO to get `shortLink`, then UPDATE_FIX with `REJECT`
3.**Attempt local fix**: Run `ci-state-update.mjs gate --gate-type local-fix`. If not allowed → skip to step 4. Otherwise use `failedTaskIds` and `taskFailureSummaries` for context.
4.**Fallback if local fix not possible or budget exhausted**: push empty commit (`git commit --allow-empty -m "ci: rerun after rejecting throttled fixes"`), enter wait mode
### no_new_cipe
1. Report to user: no CI attempt found, suggest checking CI provider
2. If `--auto-fix-workflow`: detect package manager, run install, commit lockfile if changed, enter wait mode
3. Otherwise: exit with guidance
### cipe_no_tasks
1. Report to user: CI failed with no tasks recorded
2. Retry: `git commit --allow-empty -m "chore: retry ci [monitor-ci]"` + push, enter wait mode
3. If retry also returns `cipe_no_tasks`: exit with failure
## Fix Action Flows
### Apply via MCP
Spawn UPDATE_FIX subagent with `APPLY`. New CI Attempt spawns automatically. No local git ops.
### Apply Locally + Enhance Flow
1.`nx-cloud apply-locally <shortLink>` (sets state to `APPLIED_LOCALLY`)
2. Enhance code to fix failing tasks
3. Run failing tasks to verify
4. If still failing → run `ci-state-update.mjs gate --gate-type local-fix`. If not allowed, commit current state and push (let CI be final judge). Otherwise loop back to enhance.
5. If passing → commit and push, enter wait mode
### Reject + Fix From Scratch Flow
1. Run `ci-state-update.mjs gate --gate-type local-fix`. If not allowed, print message and exit.
2. Spawn UPDATE_FIX subagent with `REJECT`
3. Fix from scratch locally
4. Commit and push, enter wait mode
## Environment vs Code Failure Recognition
When any local fix path runs a task and it fails, assess whether the failure is a **code issue** or an **environment/tooling issue** before running the gate script.
**Indicators of environment/tooling failures** (non-exhaustive): command not found / binary missing, OOM / heap allocation failures, permission denied, network timeouts / DNS failures, missing system libraries, Docker/container issues, disk space exhaustion.
When detected → bail immediately without running gate (no budget consumed). Report that the failure is an environment/tooling issue, not a code bug.
**Code failures** (compilation errors, test assertion failures, lint violations, type errors) are genuine candidates for local fix attempts and proceed normally through the gate.
## Git Safety
- Stage specific files by name — `git add -A` or `git add .` risks committing the user's unrelated work-in-progress or secrets
description: Generate code using nx generators. INVOKE IMMEDIATELY when user mentions scaffolding, setup, structure, creating apps/libs, or setting up project structure. Trigger words - scaffold, setup, create a new app, create a new lib, project structure, generate, add a new project. ALWAYS use this BEFORE calling nx_docs or exploring - this skill handles discovery internally.
---
# Run Nx Generator
Nx generators are powerful tools that scaffold projects, make automated code migrations or automate repetitive tasks in a monorepo. They ensure consistency across the codebase and reduce boilerplate work.
This skill applies when the user wants to:
- Create new projects like libraries or applications
- Scaffold features or boilerplate code
- Run workspace-specific or custom generators
- Do anything else that an nx generator exists for
## Key Principles
1.**Always use `--no-interactive`** - Prevents prompts that would hang execution
2.**Read the generator source code** - The schema alone is not enough; understand what the generator actually does
3.**Match existing repo patterns** - Study similar artifacts in the repo and follow their conventions
4.**Verify with lint/test/build/typecheck etc.** - Generated code must pass verification. The listed targets are just an example, use what's appropriate for this workspace.
## Steps
### 1. Discover Available Generators
Use the Nx CLI to discover available generators:
- List all generators for a plugin: `npx nx list @nx/react`
- View available plugins: `npx nx list`
This includes plugin generators (e.g., `@nx/react:library`) and local workspace generators.
### 2. Match Generator to User Request
Identify which generator(s) could fulfill the user's needs. Consider what artifact type they want, which framework is relevant, and any specific generator names mentioned.
**IMPORTANT**: When both a local workspace generator and an external plugin generator could satisfy the request, **always prefer the local workspace generator**. Local generators are customized for the specific repo's patterns.
If no suitable generator exists, you can stop using this skill. However, the burden of proof is high—carefully consider all available generators before deciding none apply.
### 3. Get Generator Options
Use the `--help` flag to understand available options:
```bash
npx nx g @nx/react:library --help
```
Pay attention to required options, defaults that might need overriding, and options relevant to the user's request.
### Library Buildability
**Default to non-buildable libraries** unless there's a specific reason for buildable.
| **Non-buildable** (default) | Internal monorepo libs consumed by apps | No `--bundler` flag |
| **Buildable** | Publishing to npm, cross-repo sharing, stable libs for cache hits | `--bundler=vite` or `--bundler=swc` |
Non-buildable libs:
- Export `.ts`/`.tsx` source directly
- Consumer's bundler compiles them
- Faster dev experience, less config
Buildable libs:
- Have their own build target
- Useful for stable libs that rarely change (cache hits)
- Required for npm publishing
**If unclear, ask the user:** "Should this library be buildable (own build step, better caching) or non-buildable (source consumed directly, simpler setup)?"
### 4. Read Generator Source Code
**This step is critical.** The schema alone does not tell you everything. Reading the source code helps you:
- Know exactly what files will be created/modified and where
- Understand side effects (updating configs, installing deps, etc.)
- Identify behaviors and options not obvious from the schema
- Understand how options interact with each other
To find generator source code:
- For plugin generators: Use `node -e "console.log(require.resolve('@nx/<plugin>/generators.json'));"` to find the generators.json, then locate the source from there
- If that fails, read directly from `node_modules/<plugin>/generators.json`
- For local generators: Typically in `tools/generators/` or a local plugin directory. Search the repo for the generator name.
After reading the source, reconsider: Is this the right generator? If not, go back to step 2.
> **⚠️ `--directory` flag behavior can be misleading.**
> It should specify the full path of the generated library or component, not the parent path that it will be generated in.
>
> ```bash
> # ✅ Correct - directory is the full path for the library
> nx g @nx/react:library --directory=libs/my-lib
> # generates libs/my-lib/package.json and more
>
> # ❌ Wrong - this will create files at libs and libs/src/...
> nx g @nx/react:library --name=my-lib --directory=libs
> # generates libs/package.json and more
> ```
### 5. Examine Existing Patterns
Before generating, examine the target area of the codebase:
- Look at similar existing artifacts (other libraries, applications, etc.)
- Identify naming conventions, file structures, and configuration patterns
- Note which test runners, build tools, and linters are used
- Configure the generator to match these patterns
### 6. Dry-Run to Verify File Placement
**Always run with `--dry-run` first** to verify files will be created in the correct location:
```bash
npx nx g @nx/react:library --name=my-lib --dry-run --no-interactive
```
Review the output carefully. If files would be created in the wrong location, adjust your options based on what you learned from the generator source code.
Note: Some generators don't support dry-run (e.g., if they install npm packages). If dry-run fails for this reason, proceed to running the generator for real.
> **Tip:** New packages often need workspace dependencies wired up (e.g., importing shared types, being consumed by apps). The `link-workspace-packages` skill can help add these correctly.
### 8. Modify Generated Code (If Needed)
Generators provide a starting point. Modify the output as needed to:
- Add or modify functionality as requested
- Adjust imports, exports, or configurations
- Integrate with existing code patterns
**Important:** If you replace or delete generated test files (e.g., `*.spec.ts`), either write meaningful replacement tests or remove the `test` target from the project configuration. Empty test suites will cause `nx test` to fail.
### 9. Format and Verify
Format all generated/modified files:
```bash
nx format --fix
```
This example is for built-in nx formatting with prettier. There might be other formatting tools for this workspace, use these when appropriate.
Then verify the generated code works. Keep in mind that the changes you make with a generator or subsequent modifications might impact various projects so it's usually not enough to only run targets for the artifact you just created.
```bash
# these targets are just an example!
nx run-many -t build,lint,test,typecheck
```
These targets are common examples used across many workspaces. You should do research into other targets available for this workspace and its projects. CI configuration is usually a good guide for what the critical targets are that have to pass.
If verification fails with manageable issues (a few lint errors, minor type issues), fix them. If issues are extensive, attempt obvious fixes first, then escalate to the user with details about what was generated, what's failing, and what you've attempted.
description: Import, merge, or combine repositories into an Nx workspace using nx import. USE WHEN the user asks to adopt Nx across repos, move projects into a monorepo, or bring code/history from another repository.
---
## Quick Start
-`nx import` brings code from a source repository or folder into the current workspace, preserving commit history.
- After nx `22.6.0`, `nx import` responds with .ndjson outputs and follow-up questions. For earlier versions, always run with `--no-interactive` and specify all flags directly.
- Run `nx import --help` for available options.
- Make sure the destination directory is empty before importing.
EXAMPLE: target has `libs/utils` and `libs/models`; source has `libs/ui` and `libs/data-access` — you cannot import `libs/` into `libs/` directly. Import each source library individually.
- **Recommended for monorepo sources** — files land at top level, no redundant config
- Caveats: multiple import commands (separate merge commits each); dest must not have conflicting directories; root configs (deps, plugins, targetDefaults) not imported
- **Directory conflicts**: Import into alternate-named dir (e.g. `imported-apps/`), then rename
- **Only for non-monorepo sources** (single-project repos)
- For monorepos, creates messy nested config (`imported/nx.json`, `imported/tsconfig.base.json`, etc.)
- If you must: keep imported `tsconfig.base.json` (projects extend it), prefix workspace globs and executor paths
### Directory Conventions
- **Always prefer the destination's existing conventions.** Source uses `libs/`but dest uses `packages/`? Import into `packages/` (`nx import <source> packages/foo --source=libs/foo`).
- If dest has no convention (empty workspace), ask the user.
### Application vs Library Detection
Before importing, identify whether the source is an **application** or a **library**:
- **Applications**: Deployable end products. Common indicators:
- _Frontend_: `next.config.*`, `vite.config.*` with a build entry point, framework-specific app scaffolding (CRA, Angular CLI app, etc.)
- _Backend (Node.js)_: Express/Fastify/NestJS server entrypoint, no `"exports"` field in `package.json`
- _JVM_: Maven `pom.xml` with `<packaging>jar</packaging>` or `<packaging>war</packaging>` and a `main` class; Gradle `application` plugin or `mainClass` setting
- _.NET_: `.csproj`/`.fsproj` with `<OutputType>Exe</OutputType>` or `<OutputType>WinExe</OutputType>`
- _General_: Dockerfile, a runnable entrypoint, no public API surface intended for import by other projects
- **Libraries**: Reusable packages consumed by other projects. Common indicators: `"main"`/`"exports"` in `package.json`, Maven/Gradle packaging as a library jar, .NET `<OutputType>Library</OutputType>`, named exports intended for import by other packages.
**Destination directory rules**:
- Applications → `apps/<name>`. Check workspace globs (e.g. `pnpm-workspace.yaml`, `workspaces` in root `package.json`) for an existing `apps/*` entry.
- If `apps/*` is **not** present, add it before importing: update the workspace glob config and commit (or stage) the change.
`nx import` adds the imported directory itself (e.g. `apps`) to `pnpm-workspace.yaml`, **NOT** glob patterns for packages within it. Cross-package imports will fail with `Cannot find module`.
**Fix**: Replace with proper globs from the source config (e.g. `apps/*`, `libs/shared/*`), then `pnpm install`.
### Root Dependencies and Config Not Imported (Critical)
`nx import` does **NOT** merge from the source's root:
-`dependencies`/`devDependencies` from `package.json`
-`targetDefaults` from `nx.json` (e.g. `"@nx/esbuild:esbuild": { "dependsOn": ["^build"] }` — critical for build ordering)
-`namedInputs` from `nx.json` (e.g. `production` exclusion patterns for test files)
- Plugin configurations from `nx.json`
**Fix**: Diff source and dest `package.json` + `nx.json`. Add missing deps, merge relevant `targetDefaults` and `namedInputs`.
### TypeScript Project References
After import, run `nx sync --yes`. If it reports nothing but typecheck still fails, `nx reset` first, then `nx sync --yes` again.
### Explicit Executor Path Fixups
Inferred targets (via Nx plugins) resolve config relative to project root — no changes needed. Explicit executor targets (e.g. `@nx/esbuild:esbuild`) have workspace-root-relative paths (`main`, `outputPath`, `tsConfig`, `assets`, `sourceRoot`) that must be prefixed with the import destination directory.
### Plugin Detection
- **Whole-repo import**: `nx import` detects and offers to install plugins. Accept them.
- **Subdirectory import**: Plugins NOT auto-detected. Manually add with `npx nx add @nx/PLUGIN`. Check `include`/`exclude` patterns — defaults won't match alternate directories (e.g. `apps-beta/`).
- Run `npx nx reset` after any plugin config changes.
### Redundant Root Files (Whole-Repo Only)
Whole-repo import brings ALL source root files into the dest subdirectory. Clean up:
-`pnpm-lock.yaml` — stale; dest has its own lockfile
-`pnpm-workspace.yaml` — source workspace config; conflicts with dest
-`node_modules/` — stale symlinks pointing to source filesystem
-`.gitignore` — redundant with dest root `.gitignore`
-`nx.json` — source Nx config; dest has its own
-`README.md` — optional; keep or remove
**Don't blindly delete**`tsconfig.base.json` — imported projects may extend it via relative paths.
2. Create root `eslint.config.mjs` (copy from source or create with `@nx/eslint-plugin` base rules)
3. Then `npx nx add @nx/eslint` to register the plugin in `nx.json`
Install `typescript-eslint` explicitly — pnpm's strict hoisting won't auto-resolve this transitive dep of `@nx/eslint-plugin`.
### ESLint Version Pinning (Critical)
**Pin ESLint to v9** (`eslint@^9.0.0`). ESLint 10 breaks `@nx/eslint` and many plugins with cryptic errors like `Cannot read properties of undefined (reading 'version')`.
`@nx/eslint` may peer-depend on ESLint 8, causing the wrong version to resolve. If lint fails with `Cannot read properties of undefined (reading 'allow')`, add `pnpm.overrides`:
```json
{"pnpm":{"overrides":{"eslint":"^9.0.0"}}}
```
### Dependency Version Conflicts
After import, compare key deps (`typescript`, `eslint`, framework-specific). If dest uses newer versions, upgrade imported packages to match (usually safe). If source is newer, may need to upgrade dest first. Use `pnpm.overrides` to enforce single-version policy if desired.
### Module Boundaries
Imported projects may lack `tags`. Add tags or update `@nx/enforce-module-boundaries` rules.
### Project Name Collisions (Multi-Import)
Same `name` in `package.json` across source and dest causes `MultipleProjectsWithSameNameError`. **Fix**: Rename conflicting names (e.g. `@org/api` → `@org/teama-api`), update all dep references and import statements, `pnpm install`. The root `package.json` of each imported repo also becomes a project — rename those too.
### Workspace Dep Import Ordering
`pnpm install` fails during `nx import` if a `"workspace:*"` dependency hasn't been imported yet. File operations still succeed. **Fix**: Import all projects first, then `pnpm install --no-frozen-lockfile`.
### `.gitkeep` Blocking Subdirectory Import
The TS preset creates `packages/.gitkeep`. Remove it and commit before importing.
### Frontend tsconfig Base Settings (Critical)
The TS preset defaults (`module: "nodenext"`, `moduleResolution: "nodenext"`, `lib: ["es2022"]`) are incompatible with frontend frameworks (React, Next.js, Vue, Vite). After importing frontend projects, verify the dest root `tsconfig.base.json`:
- **`moduleResolution`**: Must be `"bundler"` (not `"nodenext"`)
- **`module`**: Must be `"esnext"` (not `"nodenext"`)
- **`lib`**: Must include `"dom"` and `"dom.iterable"` (frontend projects need these)
- **`jsx`**: `"react-jsx"` for React-only workspaces, per-project for mixed frameworks
For **subdirectory imports**, the dest root tsconfig is authoritative — update it. For **whole-repo imports**, imported projects may extend their own nested `tsconfig.base.json`, making this less critical.
If the dest also has backend projects needing `nodenext`, use per-project overrides instead of changing the root.
**Gotcha**: TypeScript does NOT merge `lib` arrays — a project-level override **replaces** the base array entirely. Always include all needed entries (e.g. `es2022`, `dom`, `dom.iterable`) in any project-level `lib`.
### `@nx/react` Typings for Libraries
React libraries generated with `@nx/react:library` reference `@nx/react/typings/cssmodule.d.ts` and `@nx/react/typings/image.d.ts` in their tsconfig `types`. These fail with `Cannot find type definition file` unless `@nx/react` is installed in the dest workspace.
**Fix**: `pnpm add -wD @nx/react`
### Jest Preset Missing (Subdirectory Import)
Nx presets create `jest.preset.js` at the workspace root, and project jest configs reference it (e.g. `../../jest.preset.js`). Subdirectory import does NOT bring this file.
**Fix**:
1. Run `npx nx add @nx/jest` — registers `@nx/jest/plugin` in `nx.json` and updates `namedInputs`
2. Create `jest.preset.js` at workspace root (see `references/JEST.md` for content) — `nx add` only creates this when a generator runs, not on bare `nx add`
3. Install test runner deps: `pnpm add -wD jest jest-environment-jsdom ts-jest @types/jest`
4. Install framework-specific test deps as needed (see `references/JEST.md`)
For deeper Jest issues (tsconfig.spec.json, Babel transforms, CI atomization, Jest vs Vitest coexistence), see `references/JEST.md`.
### Target Name Prefixing (Whole-Repo Import)
When importing a project with existing npm scripts (`build`, `dev`, `start`, `lint`), Nx plugins auto-prefix inferred target names to avoid conflicts: e.g. `next:build`, `vite:build`, `eslint:lint`.
**Fix**: Remove the Nx-rewritten npm scripts from the imported `package.json`, then either:
- Accept the prefixed names (e.g. `nx run app:next:build`)
- Rename plugin target names in `nx.json` to use unprefixed names
## Non-Nx Source Issues
When the source is a plain pnpm/npm workspace without `nx.json`.
### npm Script Rewriting (Critical)
Nx rewrites `package.json` scripts during init, creating broken commands (e.g. `vitest run` → `nx test run`). **Fix**: Remove all rewritten scripts — Nx plugins infer targets from config files.
- **Flat config (`eslint.config.js`)**: Self-contained configs can often be left as-is.
- **No ESLint**: Create both root and project-level configs from scratch.
### TypeScript `paths` Aliases
Nx uses `package.json``"exports"` + pnpm workspace linking instead of tsconfig `"paths"`. If packages have proper `"exports"`, paths are redundant. Otherwise, update paths for the new directory structure.
## Technology-specific Guidance
Identify technologies in the source repo, then read and apply the matching reference file(s).
Available references:
-`references/ESLINT.md` — ESLint projects: duplicate `lint`/`eslint:lint` targets, legacy `.eslintrc.*` linting generated files, flat config `.cjs` self-linting, `typescript-eslint` v7/v9 peer dep conflict, mixed ESLint v8+v9 in one workspace.
-`references/GRADLE.md`
-`references/JEST.md` — Jest testing: `@nx/jest/plugin` setup, jest.preset.js, testing deps by framework, tsconfig.spec.json, Jest vs Vitest coexistence, Babel transforms, CI atomization.
ESLint-specific guidance for `nx import`. For generic import issues (root deps, pnpm globs, project references), see `SKILL.md`.
---
### How `@nx/eslint/plugin` Works
`@nx/eslint/plugin` scans for ESLint config files and creates a lint target for each project. It detects **both** flat config files (`eslint.config.{js,mjs,cjs,ts,mts,cts}`) and legacy config files (`.eslintrc.{json,js,cjs,mjs,yml,yaml}`).
**Plugin options (set during `nx add @nx/eslint`):**
```json
{
"plugin":"@nx/eslint/plugin",
"options":{
"targetName":"eslint:lint"
}
}
```
**Auto-installation**: `nx import` auto-detects ESLint config files and offers to install `@nx/eslint`. Accept the offer — it registers the plugin and updates `namedInputs.production` to exclude ESLint config files.
---
### Duplicate `lint` and `eslint:lint` Targets
After import, projects will have **two** lint-related targets if the source `package.json` has a `"lint"` npm script:
-`eslint:lint` — inferred by `@nx/eslint/plugin`; has proper caching and input/output tracking
-`lint` — created by Nx from the npm script via `nx:run-script`; no caching intelligence, just wraps `npm run lint`
**Fix**: Remove the `"lint"` script from each project's `package.json`. Keep `"lint:fix"` if present — there is no plugin-inferred equivalent for auto-fixing.
When `@nx/eslint/plugin` runs `eslint .` on a project with a legacy `.eslintrc.*` config that uses `parserOptions.project`, it tries to lint **all** files in the project directory including:
- Generated `dist/**/*.d.ts` files (not in tsconfig `include`)
- The `.eslintrc.js` config file itself (not in tsconfig `include`)
This causes `Parsing error: ESLint was configured to run on X using parserOptions.project, however that TSConfig does not include this file`.
**Fix**: Add `ignorePatterns` to the `.eslintrc.*` config:
```json
// .eslintrc.json
{
"ignorePatterns":["dist/**"]
}
```
```js
// .eslintrc.js — also ignore the config file itself since module.exports isn't in tsconfig
module.exports={
ignorePatterns:['dist/**','.eslintrc.js'],
// ...
};
```
---
### Flat Config `.cjs` Files Self-Linting
When a project uses `eslint.config.cjs` (CJS flat config), `eslint .` lints the config file itself. The `require()` call on line 1 triggers `@typescript-eslint/no-require-imports`.
**Fix**: Add the config filename to the top-level `ignores` array:
The same applies to `eslint.config.js` in a CJS project (no `"type": "module"`) if it uses `require()`.
---
### `typescript-eslint` Version Conflict With ESLint 9
`typescript-eslint@7.x` declares `peerDependencies: { "eslint": "^8.56.0" }`, but it is commonly used alongside `"eslint": "^9.0.0"`. npm treats this as a hard peer dep conflict and refuses to install.
**Root cause**: `@nx/eslint` init adds `eslint@~8.57.0` at the workspace root (for its own peer deps). Workspace packages that request `eslint@^9.0.0` + `typescript-eslint@^7.0.0` trigger the conflict when npm resolves their deps.
**Fix**: Upgrade `typescript-eslint` from `^7.0.0` to `^8.0.0` directly in the affected workspace package's `package.json`. The `tseslint.config()` API and `tseslint.configs.recommended` are identical between v7 and v8 — no config changes needed.
```json
// packages/my-package/package.json
{
"devDependencies":{
"typescript-eslint":"^8.0.0"
}
}
```
**Note**: npm's root-level `"overrides"` field does not force versions for workspace packages' direct dependencies — update each package.json individually.
---
### Mixed ESLint v8 and v9 in One Workspace
Legacy v8 and flat-config v9 packages can coexist in the same workspace. Each package resolves its own `eslint` version. The root `eslint@~8.57.0` (added by `@nx/eslint` init) is used by legacy v8 packages; v9 packages get their own hoisted `eslint@9`.
`@nx/eslint/plugin` infers `eslint:lint` targets for **both** config formats. Legacy packages run ESLint v8 with `.eslintrc.*`; flat-config packages run ESLint v9 with `eslint.config.*`. No special nx.json configuration is needed to support both simultaneously.
- If you import an entire Gradle repository into a subfolder, files like `gradlew`, `gradlew.bat`, and `gradle/wrapper` will end up inside that imported subfolder.
- The `@nx/gradle` plugin expects those files at the workspace root to infer Gradle projects/tasks automatically.
- If the target workspace has no Gradle setup yet, consider moving those files to the root (especially when using `@nx/gradle`).
- If the target workspace already has Gradle configured, avoid duplicate wrappers: remove imported duplicates from the subfolder or merge carefully.
- Because the import lands in a subfolder, Gradle project references can break; review settings and project path references, then fix any errors.
- If `@nx/gradle` is installed, run `nx show projects` to verify that Gradle projects are being inferred.
Jest-specific guidance for `nx import`. For the basic "Jest Preset Missing" fix (create `jest.preset.js`, install deps), see `SKILL.md`. This file covers deeper Jest integration issues.
---
### How `@nx/jest` Works
`@nx/jest/plugin` scans for `jest.config.{ts,js,cjs,mjs,cts,mts}` and creates a `test` target for each project.
**Plugin options:**
```json
{
"plugin":"@nx/jest/plugin",
"options":{
"targetName":"test"
}
}
```
`npx nx add @nx/jest` does two things:
1.**Registers `@nx/jest/plugin` in `nx.json`** — without this, no `test` targets are inferred
2. Updates `namedInputs.production` to exclude test files
**Gotcha**: `nx add @nx/jest` does NOT create `jest.preset.js` — that file is only generated when you run a generator (e.g. `@nx/jest:configuration`). For imports, you must create it manually (see "Jest Preset" section below).
**Other gotcha**: If you create `jest.preset.js` manually but skip `npx nx add @nx/jest`, the plugin won't be registered and `nx run PROJECT:test` will fail with "Cannot find target 'test'". You need both.
---
### Jest Preset
The preset provides shared Jest configuration (test patterns, ts-jest transform, resolver, jsdom environment).
**Root `jest.preset.js`:**
```js
constnxPreset=require('@nx/jest/preset').default;
module.exports={...nxPreset};
```
**Project `jest.config.ts`:**
```ts
exportdefault{
displayName:'my-lib',
preset:'../../jest.preset.js',
// project-specific overrides
};
```
The `preset` path is relative from the project root to the workspace root. Subdirectory imports preserve the original relative path (e.g. `../../jest.preset.js`), which resolves correctly if the import destination matches the source directory depth.
Both `@nx/jest/plugin` and `@nx/vite/plugin` (which infers Vitest targets) coexist without conflicts — they detect different config files (`jest.config.*` vs `vite.config.*`).
**Target naming**: Both default to `test`. If a project somehow has both config files, rename one:
```json
{
"plugin":"@nx/jest/plugin",
"options":{"targetName":"jest-test"}
}
```
---
### `@testing-library/jest-dom` — Jest vs Vitest
Projects migrating from Jest to Vitest (or workspaces with both) need different imports:
**Jest** (in `test-setup.ts`):
```ts
import'@testing-library/jest-dom';
```
**Vitest** (in `test-setup.ts`):
```ts
import'@testing-library/jest-dom/vitest';
```
If the source used Jest but the dest workspace uses Vitest for that project type, update the import path. Also add `@testing-library/jest-dom` to tsconfig `types` array.
---
### Non-Nx Source: Test Script Rewriting
Nx rewrites `package.json` scripts during init. Test scripts get broken:
-`"test": "jest"` → `"test": "nx test"` (circular if no executor configured)
-`"test": "vitest run"` → `"test": "nx test run"` (broken — `run` becomes an argument)
**Fix**: Remove all rewritten test scripts. `@nx/jest/plugin` and `@nx/vite/plugin` infer test targets from config files.
---
### CI Atomization
`@nx/jest/plugin` supports splitting tests per-file for CI parallelism:
```json
{
"plugin":"@nx/jest/plugin",
"options":{
"targetName":"test",
"ciTargetName":"test-ci"
}
}
```
This creates `test-ci--src/lib/foo.spec.ts` targets for each test file, enabling Nx Cloud distribution. Not relevant during import, but useful for post-import CI setup.
---
### Common Post-Import Issues
1.**"Cannot find target 'test'"**: `@nx/jest/plugin` not registered in `nx.json`. Run `npx nx add @nx/jest` or manually add the plugin entry.
2.**"Cannot find module 'jest-preset'"**: `jest.preset.js` missing at workspace root. Create it (see SKILL.md).
3.**"Cannot find type definition file for 'jest'"**: Missing `@types/jest` or `tsconfig.spec.json` doesn't have `"types": ["jest", "node"]`.
4.**Tests fail with "Cannot use import statement outside a module"**: `ts-jest` not installed or not configured as transform. Check `jest.config.ts` transform section.
5.**Snapshot path mismatches**: After import, `__snapshots__` directories may have paths baked in. Run tests once with `--updateSnapshot` to regenerate.
---
## Fix Order
### Subdirectory Import (Nx Source)
1.`npx nx add @nx/jest` — registers plugin in `nx.json` (does NOT create `jest.preset.js`)
2. Create `jest.preset.js` manually (see "Jest Preset" section above)
3. Install deps: `pnpm add -wD jest jest-environment-jsdom ts-jest @types/jest`
4. Install framework test deps: `@testing-library/react @testing-library/jest-dom` (React), `@vue/test-utils` (Vue)
5. Verify `tsconfig.spec.json` has `"types": ["jest", "node"]`
6.`nx run-many -t test`
### Whole-Repo Import (Non-Nx Source)
1. Remove rewritten test scripts from `package.json`
Next.js-specific guidance for `nx import`. For generic import issues (pnpm globs, root deps, project references, name collisions, ESLint, frontend tsconfig base settings, `@nx/react` typings, Jest preset, target name prefixing, non-Nx source handling), see `SKILL.md`.
---
### `@nx/next/plugin` Inferred Targets
`@nx/next/plugin` detects `next.config.{ts,js,cjs,mjs}` and creates these targets:
-`build-deps` / `watch-deps` — for TS solution setup
**No separate typecheck target** — Next.js runs TypeScript checking as part of `next build`. The `@nx/js/typescript` plugin provides a standalone `typecheck` target for non-Next libraries in the workspace.
**Build target conflict**: Both `@nx/next/plugin` and `@nx/js/typescript` define a `build` target. `@nx/next/plugin` wins for Next.js projects (it detects `next.config.*`), while `@nx/js/typescript` handles libraries with `tsconfig.lib.json`. No rename needed — they coexist.
### `withNx` in `next.config.js`
Nx-generated Next.js projects use `composePlugins(withNx)` from `@nx/next`. This wrapper is optional for `next build` via the inferred plugin (which just runs `next build`), but it provides Nx-specific configuration. Keep it if present.
### Root Dependencies for Next.js
Beyond the generic root deps issue (see SKILL.md), Next.js projects typically need:
**Core**: `react`, `react-dom`, `@types/react`, `@types/react-dom`, `@types/node`, `@nx/react` (see SKILL.md for `@nx/react` typings)
**Nx plugins**: `@nx/next` (auto-installed by import), `@nx/eslint`, `@nx/jest`
**Testing**: see SKILL.md "Jest Preset Missing" section
**ESLint**: `@next/eslint-plugin-next` (in addition to generic ESLint deps from SKILL.md)
### Next.js Auto-Installing Dependencies via Wrong Package Manager
Next.js detects missing `@types/react` during `next build` and tries to install it using `yarn add` regardless of the actual package manager. In a pnpm workspace, this fails with a "nearest package directory isn't part of the project" error.
**Root cause**: `@types/react` is missing from root devDependencies.
**Fix**: Install deps at the root before building: `pnpm add -wD @types/react @types/react-dom`
### Next.js TypeScript Config Specifics
Next.js app tsconfigs have unique patterns compared to Vite:
- **`noEmit: true`** with `emitDeclarationOnly: false` — Next.js handles emit, TS just checks types. This conflicts with `composite: true` from the TS solution setup.
- **`"types": ["jest", "node"]`** — includes test types in the main tsconfig (no separate `tsconfig.app.json`)
- **`"plugins": [{ "name": "next" }]`** — for IDE integration
- **`include`** references `.next/types/**/*.ts` for Next.js auto-generated types
- **`"jsx": "preserve"`** — Next.js uses its own JSX transform, not React's
**Gotcha**: The Next.js tsconfig sets `"noEmit": true` which disables `composite` mode. This is fine because Next.js projects use `next build` for building, not `tsc`. The `@nx/js/typescript` plugin's `typecheck` target is not needed for Next.js apps.
### `next.config.js` Lint Warning
Imported Next.js configs may have `// eslint-disable-next-line @typescript-eslint/no-var-requires` but the project ESLint config enables different rule sets. This produces `Unused eslint-disable directive` warnings. Harmless — remove the comment or ignore.
### `@nx/next:init` Rewrites All npm Scripts (Whole-Repo Import)
When `@nx/next:init` runs during a whole-repo import, it rewrites the project's `package.json` scripts to prefixed `nx` calls:
```json
{
"dev":"nx next:dev",
"build":"nx next:build",
"start":"nx next:start"
}
```
This is the standard "npm Script Rewriting" issue from SKILL.md, but triggered by `@nx/next:init` rather than Nx init. **Fix**: Remove all rewritten scripts from `package.json` — `@nx/next/plugin` infers all targets from `next.config.*`.
---
## Non-Nx Source (create-next-app)
### Whole-Repo Import Recommended
For single-project `create-next-app` repos, use whole-repo import into a subdirectory:
`next build` auto-generates `next-env.d.ts` at the project root. Add `next-env.d.ts` to the dest root `.gitignore` — it is framework-generated and should not be committed.
### ESLint: Self-Contained `eslint-config-next`
`create-next-app` generates a flat ESLint config using `eslint-config-next` (which bundles its own plugins). This is **self-contained** — no root `eslint.config.mjs` needed, no `@nx/eslint-plugin` dependency. The `@nx/eslint/plugin` detects it and creates a lint target.
### TypeScript: No Changes Needed
Non-Nx Next.js projects have self-contained tsconfigs with `noEmit: true`, their own `lib`, `module`, `moduleResolution`, and `jsx` settings. Since `next build` handles type checking internally, no tsconfig modifications are needed. The project does NOT need to extend `tsconfig.base.json`.
**Gotcha**: The `@nx/js/typescript` plugin won't create a `typecheck` target because there's no `tsconfig.lib.json`. This is fine — use `next:build` for type checking.
### `noEmit: true` and TS Solution Setup
Non-Nx Next.js projects use `noEmit: true`, which conflicts with Nx's TS solution setup (`composite: true`). If the dest workspace uses project references and you want the Next.js app to participate:
**However**, this is optional for standalone Next.js apps that don't export types consumed by other workspace projects.
### Tailwind / PostCSS
`create-next-app` with Tailwind generates `postcss.config.mjs`. This works as-is after import — no path changes needed since PostCSS resolves relative to the project root.
---
## Mixed Next.js + Vite Coexistence
When both Next.js and Vite projects exist in the same workspace.
### Plugin Coexistence
Both `@nx/next/plugin` and `@nx/vite/plugin` can coexist in `nx.json`. They detect different config files (`next.config.*` vs `vite.config.*`) so there are no conflicts. The `@nx/js/typescript` plugin handles libraries.
### Vite Standalone Project tsconfig Fixes
Vite standalone projects (imported as whole-repo) have self-contained tsconfigs without `composite: true`. The `@nx/js/typescript` plugin's typecheck target runs `tsc --build --emitDeclarationOnly` which requires `composite`.
**Fix**:
1. Add `extends: "../../tsconfig.base.json"` to the root project tsconfig
2. Add `composite: true`, `declaration: true`, `declarationMap: true`, `tsBuildInfoFile` to `tsconfig.app.json` and `tsconfig.spec.json`
3. Set `moduleResolution: "bundler"` (replace `"node"`)
4. Add source files to `tsconfig.spec.json``include` — specs import app code, and `composite` mode requires all files to be listed
### Typecheck Target Names
-`@nx/vite/plugin` defaults `typecheckTargetName` to `"vite:typecheck"`
-`@nx/js/typescript` uses `"typecheck"`
- Next.js projects have NO standalone typecheck target — Next.js runs type checking during `next build`
No naming conflicts between frameworks.
---
## Fix Order — Nx Source (Subdirectory Import)
1. Import Next.js apps into `apps/<name>` (see SKILL.md: "Application vs Library Detection")
2. Generic fixes from SKILL.md (pnpm globs, root deps, `.gitkeep` removal, frontend tsconfig base settings, `@nx/react` typings)
- **`@repo/eslint-config`** (or similar) — ESLint config files and all ESLint plugin dependencies
These are not code libraries. They distribute config via Node module resolution (e.g., `"extends": "@repo/typescript-config/nextjs.json"`). This is the **default** Turborepo pattern — expect it in virtually every Turborepo import. Package names vary — check `package.json` files to identify the actual names.
## Check for Root Config Files First
**Before doing any config merging, check whether the destination workspace uses shared root configuration.** This decides how to handle the config packages.
- If the workspace has a root `tsconfig.base.json` and/or root `eslint.config.mjs` that projects extend, merge the config packages into these root configs (see steps below).
- If the workspace does NOT have root config files — each project manages its own configuration independently (similar to Turborepo). In this case, **do not create root config files or merge into them**. Just remove turbo-specific parts (`turbo.json`, `eslint-plugin-turbo`) and leave the config packages in place, or ask the user how they want to handle them.
If unclear, check for the presence of `tsconfig.base.json` at the root or ask the user.
## Merging TypeScript Config (Only When Root tsconfig.base.json Exists)
The config package contains a hierarchy of tsconfig files. Each project extends one via package name.
1.**Read the config package** — trace the full inheritance chain (e.g., `nextjs.json` extends `base.json`).
2.**Update root `tsconfig.base.json`** — absorb `compilerOptions` from the base config. Add Nx `paths` for cross-project imports (Turborepo doesn't use path aliases, Nx relies on them).
3.**Update each project's `tsconfig.json`**:
- Change `"extends"` from `"@repo/typescript-config/<variant>.json"` to the relative path to root `tsconfig.base.json`.
4.**Delete the config package** and remove it from all `devDependencies`.
## Merging ESLint Config (Only When Root eslint.config Exists)
The config package centralizes ESLint plugin dependencies and exports composable flat configs.
1.**Read the config package** — identify exported configs, plugin dependencies, and inheritance.
2.**Update root `eslint.config.mjs`** — absorb base rules (JS recommended, TypeScript-ESLint, Prettier, etc.). Drop `eslint-plugin-turbo`.
3.**Update each project's `eslint.config.mjs`** — switch from importing `@repo/eslint-config/<variant>` to extending the root config, adding framework-specific plugins inline.
4.**Move ESLint plugin dependencies** from the config package to root `devDependencies`.
5. If `@nx/eslint` plugin is configured with inferred targets, remove `"lint"` scripts from project `package.json` files.
6.**Delete the config package** and remove it from all `devDependencies`.
Vite-specific guidance for `nx import`. For generic import issues (pnpm globs, root deps, project references, name collisions, ESLint, frontend tsconfig base settings, `@nx/react` typings, Jest preset, non-Nx source handling), see `SKILL.md`.
---
### `@nx/vite/plugin` Typecheck Target
`@nx/vite/plugin` defaults `typecheckTargetName` to `"vite:typecheck"`. If the workspace expects `"typecheck"`, set it explicitly in `nx.json`. If `@nx/js/typescript` is also registered, rename one target to avoid conflicts (e.g. `"tsc-typecheck"` for the JS plugin).
Keep both plugins only if the workspace has non-Vite pure TS libraries — `@nx/js/typescript` handles those while `@nx/vite/plugin` handles Vite projects.
### @nx/vite Plugin Install Failure
Plugin init loads `vite.config.ts` before deps are available. **Fix**: `pnpm add -wD vite @vitejs/plugin-react` (or `@vitejs/plugin-vue`) first, then `pnpm exec nx add @nx/vite`.
### Vite `resolve.alias` and `__dirname` (Non-Nx Sources)
**`__dirname` undefined** (CJS-only): Replace with `fileURLToPath(new URL('./src', import.meta.url))` from `'node:url'`.
**`@/` path alias**: Vite's `resolve.alias` works at runtime but TS needs matching `"paths"`. Set `"baseUrl": "."` in project tsconfig.
**PostCSS/Tailwind**: Verify `content` globs resolve correctly after import.
### Missing TypeScript `types` (Non-Nx Sources)
Non-Nx tsconfigs may not declare all needed types. Ensure Vite projects include `"types": ["node", "vite/client"]` in their tsconfig.
### `noEmit` Fix: Vite-Specific Notes
See SKILL.md for the generic noEmit→composite fix. Vite-specific additions:
- Non-Nx Vite projects often have **both**`tsconfig.app.json` and `tsconfig.node.json` with `noEmit` — fix both
- Solution-style tsconfigs (`"files": [], "references": [...]`) may lack `extends`. Add `extends` pointing to the dest root `tsconfig.base.json` so base settings (`moduleResolution`, `lib`) apply.
- This is safe — Vite/Vitest ignore TypeScript emit settings.
### Dependency Version Conflicts
**Shared Vite deps (both frameworks):**`vite`, `vitest`, `jsdom`, `@types/node`, `typescript` (dev)
**Vite 6→7**: Typecheck fails (`Plugin<any>` type mismatch); build/serve still works. Fix: align versions.
**Vitest 3→4**: Usually works; type conflicts may surface in shared test utils.
---
## React Router 7 (Vite-Based)
React Router 7 (`@react-router/dev`) uses Vite under the hood with a `vite.config.ts` and a `react-router.config.ts`. The `@nx/vite/plugin` detects `vite.config.ts` and creates inferred targets.
### Targets
`@nx/vite/plugin` creates `build`, `dev`, `serve` targets. The `build` target invokes the script defined in `package.json` (usually `react-router build`), not `vite build` directly.
**No separate typecheck target from `@nx/vite/plugin`** — React Router 7 typegen is run as part of `typecheck` (e.g. `react-router typegen && tsc`). The `typecheck` target is inferred from the tsconfig. Keep the `typecheck` script in `package.json` if present; it is not rewritten.
### tsconfig Notes
React Router 7 uses a single `tsconfig.json` (no `tsconfig.app.json`/`tsconfig.node.json` split). It includes:
-`"rootDirs": [".", "./.react-router/types"]` — for generated type files; keep as-is
-`"noEmit": true` — replace with composite settings per SKILL.md
### Build Output
React Router 7 outputs to `build/` (not `dist/`). Add `build` to the dest root `.gitignore`.
### Generated Types Directory
React Router 7 generates `.react-router/` at the project root for route type generation. Add `.react-router` to the dest root `.gitignore`.
---
## TanStack Start (Vite-Based)
TanStack Start uses Vinxi under the hood, which wraps Vite. Projects have a standard `vite.config.ts` that `@nx/vite/plugin` detects normally.
### Targets
`@nx/vite/plugin` creates `build`, `dev`, `preview`, `serve-static`, `typecheck` targets. The `build` target runs `vite build` which invokes the TanStack Start Vinxi pipeline (produces both client and SSR bundles).
### tsconfig Notes
TanStack Start uses a single `tsconfig.json` with `"allowImportingTsExtensions": true` and `"noEmit": true`. Apply the standard noEmit → composite fix. `allowImportingTsExtensions` is compatible with `emitDeclarationOnly: true` — no change needed.
### `paths` Aliases
TanStack Start commonly uses `"#/*": ["./src/*"]` and `"@/*": ["./src/*"]`. These are self-referential — keep as-is for a single-project app.
### Uncommitted Source Repo
`create-tan-stack` initializes a git repo but does NOT make an initial commit. Before importing, commit first:
Both `@nx/js/typescript` and `@nx/vite/plugin` auto-detect `vue-tsc` when installed — no manual config needed. Remove source scripts like `"typecheck": "vue-tsc --noEmit"`.
### ESLint Plugin Installation Order (Critical)
`@nx/eslint` init **crashes** if Vue ESLint deps aren't installed first (it loads all config files).
**Important**: `vue-eslint-parser` override must come **AFTER** base config — `flat/typescript` sets the TS parser globally without a `files` filter, breaking `.vue` parsing.
`vue-eslint-parser` must be an explicit pnpm dependency (strict resolution prevents transitive import).
**Known issue**: Some generated Vue ESLint configs omit `vue-eslint-parser`. Use the pattern above instead.
---
## Mixed React + Vue
When both frameworks coexist, several settings become per-project.
### tsconfig `jsx` — Per-Project Only
- React: `"jsx": "react-jsx"` in project tsconfig
- Vue: `"jsx": "preserve"`, `"jsxImportSource": "vue"` in project tsconfig
- Root: **NO**`jsx` setting
### Typecheck — Auto-Detects Framework
`@nx/vite/plugin` uses `vue-tsc` for Vue projects and `tsc` for React automatically.
`@nx/react`/`@nx/vue` are for generators only — no target conflicts.
---
## Redundant npm Scripts After Import
`nx import` copies `package.json` verbatim, so npm scripts come along. For Vite-based projects `@nx/vite/plugin` already infers the same targets from `vite.config.ts` — the npm scripts just shadow the plugin with weaker `nx:run-script` wrappers (no first-class caching inputs/outputs). Remove them after import.
### Standalone Vite App (`create-vite`)
Remove the following scripts — every one is redundant:
Remove `build`, `dev`, `preview`, and `test` scripts, but move any hardcoded `--port` flag to `vite.config.ts` first:
```ts
// vite.config.ts
exportdefaultdefineConfig({
server:{port: 3000},// replaces `vite dev --port 3000`
...
})
```
### React Router 7 — Keep ALL scripts
Do **not** remove React Router 7 scripts. They use the framework CLI (`react-router build`, `react-router dev`, `react-router-serve`) which is not interchangeable with plain `vite`:
-`typecheck` runs `react-router typegen && tsc` — typegen must precede `tsc` or it fails on missing route types
-`start` serves the SSR bundle — no plugin equivalent
---
## Fix Orders
### Nx Source
1. Generic fixes from SKILL.md (pnpm globs, root deps, executor paths, frontend tsconfig base settings, `@nx/react` typings)
2. Configure `@nx/vite/plugin` typecheck target
3.**React**: `jsx: "react-jsx"` (root or per-project)
0. Import into `apps/<name>` (see SKILL.md: "Application vs Library Detection")
1. Generic fixes from SKILL.md (stale files cleanup, pnpm globs, rewritten scripts, target name prefixing, noEmit→composite, ESLint handling)
2. Fix `noEmit` in **all** tsconfigs (app, node, etc. — non-Nx projects often have multiple)
3. Add `extends` to solution-style tsconfigs so root settings apply
4. Fix `resolve.alias` / `__dirname` / `baseUrl`
5. Ensure `types` include `vite/client` and `node`
6. Install `@nx/vite` manually if it failed during import
7. Remove redundant npm scripts so `@nx/vite/plugin` infers them natively (see "Redundant npm Scripts" section)
8.**Vue**: Add `outDir` + `**/*.vue.d.ts` to ESLint ignores
9. Full verification
### Multiple-Source Imports
See SKILL.md for generic multi-import (name collisions, dep refs). Vite-specific: fix tsconfig `references` paths for alternate directories (`../../libs/` → `../../libs-beta/`).
### Non-Nx Source: React Router 7
1. Ensure source has at least one commit (see SKILL.md: "Source Repo Has No Commits")
2.`nx import` whole-repo into `apps/<name>` (see SKILL.md: "Application vs Library Detection") → auto-installs `@nx/vite`, `@nx/react`
3. Stale file cleanup: `node_modules/`, `package-lock.json`, `.gitignore`
description: Find and add Nx plugins. USE WHEN user wants to discover available plugins, install a new plugin, or add support for a specific framework or technology to the workspace.
description: Helps with running tasks in an Nx workspace. USE WHEN the user wants to execute build, test, lint, serve, or run any other tasks defined in the workspace.
---
You can run tasks with Nx in the following way.
Keep in mind that you might have to prefix things with npx/pnpx/yarn if the user doesn't have nx installed globally. Look at the package.json or lockfile to determine which package manager is in use.
For more details on any command, run it with `--help` (e.g. `nx run-many --help`, `nx affected --help`).
## Understand which tasks can be run
You can check those via `nx show project <projectname> --json`, for example `nx show project myapp --json`. It contains a `targets` section which has information about targets that can be run. You can also just look at the `package.json` scripts or `project.json` targets, but you might miss out on inferred tasks by Nx plugins.
## Run a single task
```
nx run <project>:<task>
```
where `project` is the project name defined in `package.json` or `project.json` (if present).
## Run multiple tasks
```
nx run-many -t build test lint typecheck
```
You can pass a `-p` flag to filter to specific projects, otherwise it runs on all projects. You can also use `--exclude` to exclude projects, and `--parallel` to control the number of parallel processes (default is 3).
Examples:
-`nx run-many -t test -p proj1 proj2` — test specific projects
-`nx run-many -t test --projects=*-app --exclude=excluded-app` — test projects matching a pattern
-`nx run-many -t test --projects=tag:api-*` — test projects by tag
## Run tasks for affected projects
Use `nx affected` to only run tasks on projects that have been changed and projects that depend on changed projects. This is especially useful in CI and for large workspaces.
```
nx affected -t build test lint
```
By default it compares against the base branch. You can customize this:
-`nx affected -t test --base=main --head=HEAD` — compare against a specific base and head
-`nx affected -t test --files=libs/mylib/src/index.ts` — specify changed files directly
## Useful flags
These flags work with `run`, `run-many`, and `affected`:
-`--skipNxCache` — rerun tasks even when results are cached
-`--verbose` — print additional information such as stack traces
-`--nxBail` — stop execution after the first failed task
-`--configuration=<name>` — use a specific configuration (e.g. `production`)
description: "Explore and understand Nx workspaces. USE WHEN answering questions about the workspace, projects, or tasks. ALSO USE WHEN an nx command fails or you need to check available targets/configuration before running a task. EXAMPLES: 'What projects are in this workspace?', 'How is project X configured?', 'What depends on library Y?', 'What targets can I run?', 'Cannot find configuration for task', 'debug nx task failure'."
---
# Nx Workspace Exploration
This skill provides read-only exploration of Nx workspaces. Use it to understand workspace structure, project configuration, available targets, and dependencies.
Keep in mind that you might have to prefix commands with `npx`/`pnpx`/`yarn` if nx isn't installed globally. Check the lockfile to determine the package manager in use.
## Listing Projects
Use `nx show projects` to list projects in the workspace.
The project filtering syntax (`-p`/`--projects`) works across many Nx commands including `nx run-many`, `nx release`, `nx show projects`, and more. Filters support explicit names, glob patterns, tag references (e.g. `tag:name`), directories, and negation (e.g. `!project-name`).
```bash
# List all projects
nx show projects
# Filter by pattern (glob)
nx show projects --projects "apps/*"
nx show projects --projects "shared-*"
# Filter by tag
nx show projects --projects "tag:publishable"
nx show projects -p 'tag:publishable,!tag:internal'
# Filter by target (projects that have a specific target)
nx show projects --withTarget build
# Combine filters
nx show projects --type lib --withTarget test
nx show projects --affected --exclude="*-e2e"
nx show projects -p "tag:scope:client,packages/*"
# Negate patterns
nx show projects -p '!tag:private'
nx show projects -p '!*-e2e'
# Output as JSON
nx show projects --json
```
## Project Configuration
Use `nx show project <name> --json` to get the full resolved configuration for a project.
**Important**: Do NOT read `project.json` directly - it only contains partial configuration. The `nx show project --json` command returns the full resolved config including inferred targets from plugins.
You can read the full project schema at `node_modules/nx/schemas/project-schema.json` to understand nx project configuration options.
```bash
# Get full project configuration
nx show project my-app --json
# Extract specific parts from the JSON
nx show project my-app --json | jq '.targets'
nx show project my-app --json | jq '.targets.build'
nx show project my-app --json | jq '.targets | keys'
When processing nx CLI results, use command-line tools to compute the answer programmatically rather than counting or parsing output manually. Always use `--json` flags to get structured output that can be processed with `jq`, `grep`, or other tools you have installed locally.
- run:echo "We are in the process of transitioning from Circle CI to GitHub Actions. For details about your build results, consult github actions build logs."
"check violations", when given a sandbox report JSON file or URL to investigate,
or when the user pastes a staging.nx.app sandbox-report URL. Also trigger when
discussing unexpected reads/writes in Nx task execution. Guides structured
investigation of why tasks read/write undeclared files, determines root causes,
and recommends fixes.
argument-hint: '<sandbox-report.json or URL> [--filter <file|pattern|list>]'
allowed-tools: Bash, Read, Grep, Glob
---
# Diagnose Sandbox Report
## Overview
Sandbox violations occur when an Nx task reads files not declared as inputs or writes files not declared as outputs.
**Unexpected reads** are one of:
1.**Missing input** (most likely) — the process legitimately needs this file. Understand what the process does and why the access makes sense, then declare it as an input.
2.**Potential sandboxing gap** (last resort) — the access is irrelevant to correctness and should be filtered/ignored by the sandbox. Only conclude this after exhausting every possibility for it being a missing input.
**Unexpected writes** follow the same logic:
1.**Missing output** (most likely) — the process legitimately produces this file.
2.**Potential sandboxing gap** (last resort) — same as above.
The default assumption is that an unexpected access IS a missing declaration. The investigation's job is to understand WHY the process accesses the file — not to find reasons it shouldn't.
## Critical Rules
1.**NEVER read the sandbox report JSON directly** — these files are too large for the Read tool (50K+ tokens). Do NOT use `Read`, `cat`, `head`, `python3`, or `jq` on the raw report. All report parsing is handled by the script.
2.**ALWAYS run the context-gathering script as the very first step** — no manual parsing, no ad-hoc python/jq on the report file. The script does everything deterministically.
3. If the script fails, **report the error and stop**. Do not attempt manual parsing as a fallback.
4.**Identify the inferring plugin BEFORE proposing any fix** — check `inference.plugin` in the script output or run `jq '.targets.<target>.metadata' <detail-file>`. Fixing the wrong plugin wastes entire investigation rounds.
5.**Verify hypotheses empirically before committing to them** — see Principle 4 and the Phase 2 instrumentation guidance.
## Workflow
### Phase 0: Input
User provides one of:
- Path to a sandbox report JSON file
- A URL to a sandbox report — pass it directly to the script, it handles downloading
- A task ID + CIPE URL (fetch report via MCP if available)
- Inline violation data
If a task ID is provided but no report, ask the user for the report file.
**Filtering**: Most invocations will focus on specific files, not the entire report. The user may specify:
- A single file: `e2e.log`
- A comma-separated list: `apps/nx-cloud/e2e.log,apps/nx-cloud/build/client/assets/main.js`
- A glob pattern: `*.tsbuildinfo`, `apps/nx-cloud/build/**`
- A directory prefix: `apps/nx-cloud/build/client/assets`
When the user specifies files to focus on, pass them via `--filter` to the script. When they don't specify a filter and the report has many violations, summarize the groupings (by directory, extension) and ask which group(s) to investigate first rather than trying to investigate everything at once.
### Phase 1: Deterministic Pre-Processing
Run the context-gathering script **immediately** — this is the first tool call after reading the user's input.
Call it exactly as shown — do NOT append `2>&1` or `2>/dev/null` (the script manages its own stderr internally). Run in the **foreground** (no `run_in_background`) with a **3-minute timeout** — reports can be large and the script runs the task + multiple nx commands:
```bash
npx tsx ${CLAUDE_SKILL_DIR}/scripts/gather-sandbox-context.ts <report.json or URL> [--filter <pattern>][--workspace <path>]
```
Pass `--filter` when the user wants to focus on specific files or patterns. The script filters violations before all downstream processing (grouping, validation, classification), so the output only contains relevant data.
The script produces two outputs:
**stdout** (~3-5KB compact brief) — everything needed to start investigating:
-`summary`: violation counts (total, filtered, confirmed vs undeclared)
-`undeclaredFiles`: the actual file paths that are true violations
-`grouping`: violations grouped by directory and extension
-`commands`: processes with violations (pid, cmd, executable, arguments, counts) — no full file lists
-`classificationSummary`: counts per category (cross-project, build artifacts, config files, etc.)
-`crossProjectDependencyCheck`: whether cross-project file owners are in the task's dependency chain
-`staleDeclarations`: grouped analysis of expectedInputsNotRead / expectedOutputsNotWritten
-`dependentTasksOutputFiles`: extracted from target inputs config and named inputs — shows what dep output globs are declared (critical for cross-project violations)
-`executorInfo`: executor name and resolved source path in `node_modules` — read this file to understand how the tool is invoked
-`checkSample`: results of `--check` on up to 5 undeclared files (catches false positives early)
Read the brief output — it has everything to start. Use `jq` on the detail file only when you need to drill into specific sections. When querying the detail file, use the structure above — do not guess the schema. Do NOT use Python, ad-hoc scripts, or the Read tool on the detail file — only `jq`.
For reports with many violations, use `--filter` to narrow scope. When investigating without a filter, use the `grouping` data to identify patterns and prioritize — don't try to trace every file individually.
If `summary.undeclaredReads` and `summary.undeclaredWrites` are both 0, all violations were resolved by the script's validation against resolved inputs/outputs. Report this to the user — no further investigation needed.
The `commands` array pre-parses each process — use `executable` and `arguments` to identify the tool without re-parsing `cmd`. When many files share the same root cause, group them under one finding using a glob pattern or count (e.g., "88 `.d.ts` files matching `packages/nx/dist/**/*.d.ts`").
### Phase 2: Command Analysis — the core investigation
**This is the most important phase.** The goal is to determine with 100% certainty why each process reads or writes each violated file. Do not classify violations from file names or paths alone — trace the actual causal chain from command → config → file access.
#### Step 1: Understand the command
The brief's `commands` array pre-parses each process. Use the `executable` and `arguments` fields directly — don't re-parse `cmd`. Identify:
- The tool (from `executable`)
- The arguments (target files/dirs, config flags, extensions — from `arguments`)
- The working directory (from executor options or project root)
#### Step 2: Trace why the command accesses each violated file
For each violated file, establish the **exact causal chain** that leads the command to read or write it. The approach is the same regardless of tool:
1. Identify the tool's config file (usually in the project root or workspace root)
2. Read the config and trace file references: `includes`, `extends`, `presets`, entry points, plugins
3. Follow the reference chain until you can explain exactly why the violated file is accessed
- **Directory traversal**: tool scans a directory for matching files and reads everything, including files it won't process (e.g., jest-haste-map scanning `.next/`, eslint reading `.d.ts` alongside `.ts`)
- **Dependency resolution**: tool resolves imports/requires and follows the dependency graph to files outside the project (e.g., esbuild/vite/webpack resolving workspace packages to their dist outputs)
- **Plugin/transformer loading**: tool loads plugins or transformers that read additional files (e.g., ts-jest loading tsconfig for TypeScript compilation)
For any tool, read its source code in `node_modules` to understand its file discovery behavior. Don't assume — trace the actual code.
**You must be able to explain the full path:** e.g., "eslint loads `.eslintrc.json` → configures `@typescript-eslint/parser` → parser resolves `parserOptions.project` → walks up to find `tsconfig.json` → reads it." If you can't trace the full path, keep investigating — do not guess.
**When theoretical analysis is inconclusive, verify empirically.** For difficult cases, instrument `node_modules` with interceptors to capture real stack traces. For example, patch `fs.readFileSync` in the tool's entry point to log stack traces when the violated file is accessed. A confirmed stack trace is worth more than multiple rounds of code reading.
#### Step 3: Confirm the violation with `--check`
**This step is mandatory — do not skip it.** The script already runs `--check` on a sample of up to 5 undeclared files (see `checkSample` in the brief). Review those results first — if the sample files are confirmed as inputs/outputs, the corresponding violations are false positives.
For files not in the sample, use the pre-generated commands from `verificationCommands` in the brief:
```bash
npx nx show target inputs <project>:<target> --check <violated-read-files>
npx nx show target outputs <project>:<target> --check <violated-write-files>
```
If the commands fail because output files don't exist (e.g., the script's task run timed out), run the task first with `verificationCommands.runTask`.
If `--check` shows the file IS already an input/output, the violation is a false positive from the script's static analysis. If it confirms the file is NOT an input/output, proceed to classification.
#### Step 4: Classify
With the causal chain established and the violation confirmed, classify into one of these categories:
1.**Missing input/output** (most common) — the process legitimately needs this file. Understand why:
- **Direct dependency** — the tool needs this file to do its job (e.g., tsc reads referenced tsconfigs, eslint loads config chain)
- **Transitive dependency** — a config file references another file that references this one (e.g., jest preset → resolver → module). Trace the full chain.
- **Directory traversal side effect** — the tool reads all files in a directory even if it only processes some (e.g., eslint reads `.d.ts` files while linting `.ts`). Still a legitimate access from the tool's perspective.
2.**Bad tool configuration** — the tool accesses a file it shouldn't because its scope is too broad. The fix is fixing the tool's config, NOT adding an input. Investigate:
- Is the command targeting too broad a directory? (e.g., `eslint .` instead of `eslint src/`)
- Is a config file missing ignore/exclude rules? (e.g., eslint processing a file type it should skip)
- Is a plugin inferring a target for a project that doesn't match? (e.g., eslint target on a non-JS project)
- Is an env var causing the tool to behave differently?
3.**Potential sandboxing gap** (last resort) — the access is genuinely irrelevant to correctness (PID files, temp sockets, dev server logs that no task consumes). Only conclude this after exhausting categories 1 and 2.
### Phase 3: Deep Investigation
For violations that aren't immediately obvious, investigate further:
#### If the target is inferred by a plugin
1. Identify which plugin from `inference.plugin` in the brief output, or `nx show project --json` metadata
2. Read the plugin's `createNodesV2` implementation to understand inference logic
3. Determine if this project should have this target at all
4. Check if the plugin has `include`/`exclude` patterns in `nx.json` that should filter this project
5.**Check for input override layers** — `project.json`, `package.json`, or `nx.json``targetDefaults` may override plugin-inferred inputs, rendering plugin-level fixes invisible. Check all three before concluding a plugin fix is sufficient.
#### If violations come from a subprocess
1. Trace the process tree: which parent spawned the subprocess?
2. Why does the subprocess exist? (dev server for e2e, worker thread, build tool subprocess)
3. What environment does the subprocess inherit? (env vars, cwd)
4. Does the subprocess access files in a different project's directory?
#### If violations involve config file reference chains
1. Read the config file (jest.config, tsconfig, .eslintrc)
2. Trace all file references: `preset`, `extends`, `references`, `setupFiles`, `resolver`, `moduleNameMapper`, `transform`, etc.
4. Determine which referenced files are not declared as task inputs
#### If violations involve dependency task outputs
1. Check `dependsOn` to understand task dependency chain
2. Check `dependentTasksOutputFiles` glob pattern — is it too narrow?
3. Compare the glob against actual file types the tool reads from dependencies (e.g., `**/*.d.ts` missing `.tsbuildinfo`)
#### Generalizability analysis
After diagnosing the root cause, determine scope:
1. Is this violation specific to this project, or does it affect all projects using this tool/plugin?
2. What conditions trigger it? (specific config, specific tool version, specific project structure)
3. Should the fix be per-project (declarative input) or systemic (plugin improvement)?
4. If the plugin can be made smarter to infer the correct inputs, that's preferable to manual declarations.
### Phase 4: Output
**You MUST present findings using the structured format below before proceeding to any implementation discussion.** Do not use free-form narrative — the structure ensures completeness and makes findings reviewable.
Why: {why this access is irrelevant to correctness}
Evidence: {proof that categories 1-2 were exhausted}
### [INVESTIGATE] {short description}
Files: {file list or pattern}
Notes: {what's known, what needs more info}
Question: {what to ask the user or team}
## Stale Declarations
expectedInputsNotRead: {count and details if relevant}
expectedOutputsNotWritten: {count and details if relevant}
## Verification Plan
For each fix, provide the exact commands to verify:
1. Run the task so output files exist on disk: `npx nx <target> <project> --skip-nx-cache`
2. Check each violation file is now an input: `npx nx show target <project>:<target> inputs --check <space-separated files>`
3. For plugin-level fixes: build the plugin, patch node_modules, then verify with steps 1-2
```
## Principles
1.**Missing declaration is the default.** Most unexpected accesses are legitimate — the process needs the file, it just wasn't declared. Start from this assumption and investigate to understand WHY the access happens.
2.**The command is the unit of analysis.** Don't classify files in isolation. Understand what the command does and whether each file access makes sense given that command's purpose.
3.**Trace the full chain.** Plugin inference → target config → executor → command → file access. The root cause is often several layers removed from the symptom.
4.**Empirical over theoretical.** When code analysis produces a hypothesis, verify it before acting. Instrument `node_modules`, capture stack traces, run with debug flags. Wrong theories waste entire investigation rounds.
5.**Be thorough.** Read plugin source code, config files, executor implementations. Don't guess based on file names alone.
6.**Potential sandboxing gaps are last resort.** Only conclude this after exhausting missing declaration and bad tool config. The access must be genuinely irrelevant to correctness.
7.**Verify claims about Nx behavior in source code.** Any assertion about how Nx works must be traced to the actual implementation. Do not reason from theory or assumptions.
8.**Prefer systemic fixes over per-project declarations.** If a plugin can be improved to infer correct inputs for all projects, that's better than adding manual input declarations to each project.
## Delegating to Subagents
When the investigation is complex and requires parallel research, you can delegate to subagents. Follow this pattern:
1.**Run the context-gathering script yourself first.** The brief output (~3-5KB) is the shared context all subagents need.
2.**Include the brief output in each subagent prompt** along with the specific question to investigate. Subagents should NOT run the script again or try to parse the raw report.
3.**Give subagents the detail file path** so they can `jq` specific sections (process tree, resolved inputs, etc.) without re-running the script.
4.**Each subagent should answer one focused question**, e.g., "Why does PID 12345 (eslint) read `tsconfig.base.json`? Trace the full causal chain from the eslint config."
5.**Subagents must still follow the skill principles** — trace full causal chains, verify empirically, use `--check`, don't guess from file names. Include these instructions in the subagent prompt.
6.**Synthesize subagent results yourself** using the structured Phase 4 output format. Do not delegate the final classification.
## Reference
For the sandbox report data model and field definitions, see `references/data-model.md`.
You are migrating an Nx monorepo package from building to `../../dist/packages/<name>` to building locally to `packages/<name>/dist/`. This matches the pattern already used by `nx` and `devkit`.
## Argument
The user provides a package name (e.g., `js`, `webpack`, `angular`). The package lives at `packages/<name>/`.
## Steps
### 0. Preflight: check `workspace:*` deps for unmigrated packages
Read `packages/<name>/package.json` and list every `workspace:*` dep (in `dependencies`, `devDependencies`, `peerDependencies`).
For each such dep, look at the target package's `project.json`. If it does **not** override `release.version.manifestRootsToUpdate` to `["packages/{projectName}"]`, that target package is still on the old layout. You **must** migrate those packages too (apply this skill to each), in the same PR.
**Why:** With `preserveLocalDependencyProtocols: true` (the new pattern), `nx release version` does not substitute `workspace:*` in your manifest. At publish time, pnpm resolves `workspace:*` by reading the target's _source_`packages/<dep>/package.json`. The default `manifestRootsToUpdate: ["dist/packages/{projectName}"]` only bumps the dist copy, so pnpm picks up the unbumped source `0.0.1` and publishes your package with a dep on a version that does not exist in the registry. Local registry installs then fail with `ERR_PNPM_NO_MATCHING_VERSION`.
A `workspace:*` dep on a still-on-old-layout package is a hard blocker — migrate it before continuing.
### 1. Read current state
Read these files for the target package:
-`packages/<name>/package.json`
-`packages/<name>/project.json`
-`packages/<name>/tsconfig.lib.json`
-`packages/<name>/tsconfig.spec.json` (if exists)
-`packages/<name>/.eslintrc.json` (if exists)
-`packages/<name>/assets.json` (if exists)
-`packages/<name>/.npmignore` (if exists)
-`packages/<name>/.gitignore` (if exists)
Also read the reference implementations:
-`packages/devkit/tsconfig.lib.json`
-`packages/devkit/package.json`
-`packages/devkit/project.json`
-`packages/devkit/.npmignore`
Run `pnpm nx show target <name>:build-base` to see the inferred build target.
Run `pnpm nx show target <name>:build` to see the full build target.
### 2. Identify entry points
Look at the package's root `.ts` files and any existing `exports` field. Common entry points:
-`index.ts` (main)
-`testing.ts`
-`internal.ts`
-`ngcli-adapter.ts`
- Any other `.ts` files at the package root that re-export from `src/`
Also check for `migrations.json` and `generators.json`/`executors.json` — these need exports entries too.
### 3. Update `tsconfig.lib.json`
Transform from the old pattern to the new pattern:
**Important**: Adjust `include` based on the package's actual structure. If the package has directories like `bin/`, `plugins/`, etc. at the root level (like `nx` does), include those too.
### 4. Update `tsconfig.spec.json` (if exists)
Change `outDir` from `../../dist/packages/<name>/spec` to `dist/spec`.
### 5. Update `package.json`
Key changes:
- Add `"type": "commonjs"` near the top (after `private`)
- Change `"main"` to `"./dist/index.js"`
- Change `"types"` to `"./dist/index.d.ts"`
- Add `"typesVersions"` for backwards compatibility with `moduleResolution: "node"` consumers
- Add `"exports"` map with entries for each entry point
Each export entry follows this pattern:
```json
"./entry-name":{
"@nx/nx-source":"./entry-name.ts",
"types":"./entry-name.d.ts",
"default":"./dist/entry-name.js"
}
```
The main entry (`.`) uses `./index.ts`, `./index.d.ts`, `./dist/index.js`.
Always include:
```json
"./package.json":"./package.json"
```
Include `"./migrations.json": "./migrations.json"` if the package has migrations.
**Note**: The `@nx/nx-source` condition is a custom condition used for source-level resolution within the workspace (so other packages import from source, not dist).
Add a `typesVersions` field for consumers using `moduleResolution: "node"` (which doesn't read `exports`):
```json
"typesVersions":{
"*":{
"testing":["dist/testing.d.ts"],
"ngcli-adapter":["dist/ngcli-adapter.d.ts"]
}
}
```
Add an entry for each subpath export (excluding `.`, `./package.json`, and `./migrations.json`).
Do **not** override `build-base.outputs` in `project.json`. The `@nx/js/typescript` plugin reads `outDir` and `tsBuildInfoFile` from `tsconfig.lib.json` and infers the correct outputs (including the tsbuildinfo and the full set of file extensions). A hand-written override is almost always less complete than the inferred set.
If the package already has a hand-written `build-base.outputs` array, **delete it** — don't try to patch it. An incomplete override that omits `dist/tsconfig.tsbuildinfo` causes a sandbox violation in _every consumer_ that has a TypeScript project reference to this package: their `tsc --build` reads the referenced project's `.tsbuildinfo`, but `dependentTasksOutputFiles` can only collect it if this package declares it as an output.
Verify the inferred outputs include the tsbuildinfo:
```bash
pnpm nx show project <name> --json | jq '.targets["build-base"].outputs'
# Must include "{projectRoot}/dist/tsconfig.tsbuildinfo"
```
Update the existing `build` target's `outputs` if they reference `{workspaceRoot}/dist/packages/<name>` — they should now reference `{projectRoot}/dist/`.
Also update `dependsOn` in the `build` target: replace `"^build"` with `"^build"` if it isn't already, and make sure `"build-base"` is listed.
### 7. Update eslint config
Add `dist` to the ignores. For flat config (`eslint.config.mjs`):
```js
{ignores:['**/__fixtures__/**','dist']},
```
For legacy `.eslintrc.json`:
```json
"ignorePatterns":["!**/*","node_modules","dist"]
```
Do **not** add `*.d.ts` or `**/*.d.ts` — the base config already ignores `**/dist`, and `tsconfig.lib.json` (Step 4) sends all generated `.d.ts` files into `dist`, so they're already out of scope. Hand-authored `.d.ts` files in `src/` (e.g. `schema.d.ts`) generally don't need ignoring.
### 8. Update `assets.json` (if exists)
Change `outDir` from `"dist/packages/<name>"` to `"packages/<name>/dist"`.
### 9. Add `files` field to `package.json`
Instead of using `.npmignore`, add a `"files"` field to `package.json` (matching the `nx` package pattern). Remove `.npmignore` if it exists.
```json
"files":[
"dist",
"!dist/tsconfig.tsbuildinfo",
"migrations.json"
]
```
Adjust based on the package's needs:
- Add `"executors.json"` and/or `"generators.json"` if the package has them
- Add any other non-TS files that need to be published
- npm always includes `package.json` and `README.md` automatically — no need to list them
### 10. Rename README.md and update build command
If the package has a `README.md` at its root and uses the `copy-readme.js` script in its build target:
1. Rename `README.md` to `readme-template.md` (`git mv`)
2. Update the build command to pass explicit paths:
3. Update the build target `outputs` to `["{projectRoot}/README.md"]`
The script's default behavior reads `packages/<name>/README.md` and writes to `dist/packages/<name>/README.md` — both wrong for the new layout. Passing explicit args fixes both.
### 11. Update root `.gitignore`
Under the section that lists generated README files (look for `packages/nx/README.md`), add:
```
packages/<name>/README.md
```
The generated README is written next to source (not into `dist/`), so it needs its own ignore.
Do **not** add a `packages/<name>/**/*.d.ts` rule. The root `.gitignore` already has a top-level `dist` entry that ignores every `dist/` directory in the repo — and `tsconfig.lib.json` (Step 4) sets `declarationDir: "dist"`, so all generated `.d.ts` files land there. Adding a package-wide `**/*.d.ts` rule plus `!` re-includes for hand-authored `.d.ts` files (like committed `schema.d.ts` source files) is redundant defense-in-depth.
### 12. Update docs generation paths
Check `astro-docs/src/plugins/utils/` for any code that references `.d.ts` files from the package. The docs generation reads `.d.ts` entry points to build API reference pages. Paths that previously pointed to `dist/packages/<name>/foo.d.ts` (workspace root dist) or `packages/<name>/foo.d.ts` (package root) now need to point to `packages/<name>/dist/foo.d.ts`.
For example, `devkit-generation.ts` had to be updated to look for `packages/devkit/dist/index.d.ts` instead of `packages/devkit/index.d.ts`.
### 13. Update `scripts/nx-release.ts`
Two things to do here:
1. **Add the package to `packagesToReset`.** That array (around `scripts/nx-release.ts:76`) is the snapshot/restore list — every package whose source `package.json` gets mutated by `nx release` (because it now publishes from `packages/<name>/` directly, not `dist/packages/<name>/`) must be in this list. Otherwise the release will leave `packages/<name>/package.json` dirty in the working tree after running. **Easy to forget — and there is no test that catches it.**
2. **Update any package-specific paths.** If the package has special release handling (like devkit's `hackFixForDevkitPeerDependencies`), update any paths from `./dist/packages/<name>/` to `./packages/<name>/`.
### 14. Update imports across the workspace
Search for imports from `@nx/<name>/src/` across all other packages. These internal imports need to be updated:
- If the imported thing is re-exported through a public entry point (index.ts, internal.ts, etc.), update the import to use that entry point
- If not, consider adding it to `internal.ts` or the appropriate entry point
### 14b. (Optional) Lock down `./src/*` and route internal consumers through `./internal`
When you ship the migration, the package's `exports` map exposes everything under `./src/*` if you keep the wildcard. That's a 100s-of-symbols-wide semi-private surface that pins the implementation layout forever — consumers (first-party and external) can reach into any source file. The cleaner long-term shape, matching `@nx/devkit`/`@nx/workspace`, is to drop the wildcard and route internal consumers through a single curated `./internal` entry. Skip this step if you'd rather defer (e.g. the package has very heavy internal usage and you'd prefer a smaller PR), but plan a follow-up.
#### When to lock down vs defer
- **Lock down in the same PR** if internal subpath imports number in the low hundreds AND the package isn't `workspace:*`-pinned by other not-yet-migrated packages whose dist code would crash at runtime against the older published version (see "Published-version mismatch" below).
- **Defer to a follow-up PR** if the inventory is huge OR if dist-output code in other workspace packages depends on the OLD `./src/*` paths and those packages can't be migrated to local-dist yet. Lock down only once the immediate runtime-resolution surface is contained.
#### Step-by-step
**1. Inventory the subpath imports.** Scan for `from '@nx/<name>/src/...'`, plus runtime `require()`, dynamic `import()`, and `jest`/`vi.mock`-family calls:
Compile a `subpath → set-of-imported-symbols` map. About 30 distinct subpaths and 60 symbols is typical for a package the size of `@nx/js`.
**2. Identify runtime-string-resolved subpaths.** Some subpaths are referenced by _string default values_ the nx runtime resolves later (not static imports). The classic example: `packages/nx/src/command-line/release/config/config.ts` has `DEFAULT_VERSION_ACTIONS_PATH = '@nx/js/src/release/version-actions'`. These strings are also baked into pre-existing user `nx.json` files and you cannot rewrite them via a migration. **Keep those exact subpaths as explicit non-wildcard entries in the exports map** (not under `./internal`), and have the migration skip rewriting them.
```bash
# Search for string-default usages of the subpath in nx core
**3. Build `packages/<name>/internal.ts` at the package ROOT** (not inside `src/`, to mirror `@nx/devkit/internal`). Re-export every symbol callers reach for via `@nx/<name>/src/*`, BUT only symbols not already exported from `packages/<name>/src/index.ts`. Anything already public stays public — the migration sends those callers to `@nx/<name>`, not `@nx/<name>/internal`.
To compute the public set:
```bash
grep -E "^export " packages/<name>/src/index.ts
```
…and recursively expand any `export *` lines. The "public-export reachability" calculation is fiddly enough that a small Python script with a recursive expand is worth it (see PR #35538 commit history for an example).
Curate the new file:
```ts
// Semi-private surface for first-party Nx packages.
//
// External plugins should NOT import from here — this entry is curated for
// internal consumers and may change without semver protection. Mirrors
// `@nx/devkit/internal`.
// Re-exports of nx-source internals (need `no-restricted-imports` overrides).
Also strip `src/*` glob entries from `typesVersions`. Replace with explicit non-wildcard entries that mirror the kept exports.
**5. Codemod consumers in two passes.** Mechanical sed-style first, then a smarter split:
```bash
# Pass 1: every `from '@nx/<name>/src/...'` → `from '@nx/<name>/internal'`,
# except the preserved subpaths from step 2.
# (Use a Python/TS script — sed is fine for the simple cases too.)
```
```bash
# Pass 2: split mixed imports. Any line like
# import { libraryGenerator, ensureTypescript } from '@nx/<name>/internal';
# where `libraryGenerator` is publicly exported from `src/index.ts` becomes:
# import { libraryGenerator } from '@nx/<name>';
# import { ensureTypescript } from '@nx/<name>/internal';
```
Also handle these non-static cases:
- `jest.mock('@nx/<name>/src/...', ...)` and `jest.requireActual(...)` — same rewrite. The whole mock surface is now `@nx/<name>/internal`, so `...jest.requireActual('@nx/<name>/internal')` spreads more than the original site mocked, but that's fine in practice.
- Runtime `require('@nx/<name>/src/...')` — same rewrite.
- Template-string fixtures inside `.spec.ts` files — careful! Don't let your codemod rewrite literal `from "@nx/<name>/internal"` substrings that _test_ the migration (it'll flip quote style and break the test). Either skip `*.spec.ts` files containing fixtures, or operate at AST level.
**6. Collapse duplicate imports.** After the two-pass codemod, many files end up with two `import { ... } from '@nx/<name>/internal'` lines (or two `from '@nx/<name>'`). Run a third pass to merge same-source-same-`type`-prefix imports:
```python
# Match lines (anchored): `^import [type ]{ ... } from '@nx/<name>[/internal]';$`
# Group by (is_type_only, source). For each group with >1 entry: keep the first
# occurrence's position, merge the named bindings (dedupe), delete the others.
# Don't merge across type/non-type — the semantics differ.
```
**7. Public-symbol audit.** After splitting, `internal.ts` must not re-export anything already exported from `src/index.ts`. If it does, namespace consumers (`import * as shared from '@nx/<name>/internal'`) will see only the curated set and `shared.publiclyExportedSymbol` becomes `undefined`. Cross-check:
```bash
# Symbols in internal.ts that are ALSO in the recursive index.ts export set
# are a bug. Remove them from internal.ts. The codemod from step 5 should
# have already routed their callers to `@nx/<name>`, but verify nothing is
# left pointing at `@nx/<name>/internal` for these.
```
The three load-bearing patterns to verify:
- `import * as shared from '@nx/<name>/internal'` followed by `shared.publicSymbol` — fix by changing source to `@nx/<name>`.
- Runtime `const shared = require('@nx/<name>/internal')` followed by `shared.publicSymbol` — same fix.
- Named imports of public symbols from `@nx/<name>/internal` — already split by step 5; verify nothing slipped through.
**8. Ship a migration.** Add `packages/<name>/src/migrations/update-<version>/rewrite-<name>-internal-subpath-imports.ts` based on the workspace `move-typescript-compilation-import` template. It needs to handle:
- Static `import [type] { ... } from '@nx/<name>/src/<anything>'`
- `export [type] { ... } from '@nx/<name>/src/<anything>'`
- Dynamic `import('@nx/<name>/src/<anything>')`
- `require('@nx/<name>/src/<anything>')`
- `jest.mock|unmock|doMock|dontMock|requireActual|requireMock|importActual|importMock(...)` and the `vi.` equivalents
**Route by symbol, not blindly to `./internal`.** Some symbols reachable via `@nx/<name>/src/*` are _public_ — they're exported from `packages/<name>/src/index.ts` and ship on the main `@nx/<name>` entry. A migration that rewrites every `@nx/<name>/src/*` import to `@nx/<name>/internal` silently breaks any consumer importing a public symbol that way, because `internal.ts` deliberately does **not** re-export public symbols (step 7). Instead:
- Hard-code the public symbol set (the recursively-expanded `export`s of `src/index.ts`) in the migration.
- For a **named** `import`/`export` declaration, partition the named bindings: public symbols go to `@nx/<name>`, the rest to `@nx/<name>/internal`. Classify an `orig as alias` binding by `orig`. If both groups are non-empty, replace the single declaration with two — one per target — preserving any `import type` / `export type` modifier.
- A **namespace** import (`import * as ns`), a **default** import, `export *`, every **call expression** (`require`, dynamic `import`, `jest.mock` family), and `typeof import('...')`**type queries** (`ImportTypeNode`) reference the module as a whole and can't be symbol-split — route them to `@nx/<name>/internal`.
Skip the preserved subpaths from step 2 (e.g. `@nx/<name>/src/release/version-actions`). Use `ts.createSourceFile` for AST-based detection so you don't rewrite literals inside comments or template strings.
**Don't forget `typeof import('...')`.** It parses as an `ImportTypeNode`, not a `CallExpression`, so it's a separate AST branch from the `require`/dynamic-`import` handling. Real-world consumers use the idiom `const m = require('@nx/<name>/src/x') as typeof import('@nx/<name>/src/x')` to get a typed runtime `require` — if the codemod only rewrites the runtime arg, the type arg stays pointing at the now-removed `./src/*` wildcard and the consumer fails to type-check. Handle it explicitly: walk `ImportTypeNode`s and rewrite `node.argument.literal` when the string starts with `@nx/<name>/src/`.
Register in `packages/<name>/migrations.json` with `version: <current beta>`. The description should state the routing rule: named public-symbol imports/exports go to `@nx/<name>`, everything else to `@nx/<name>/internal`.
Add a spec covering: public-symbol import (→ `@nx/<name>`), internal-symbol import (→ `@nx/<name>/internal`), mixed import split into two, aliased bindings classified by original name, type-only split, `export { ... } from` (public / internal / mixed), `export *`, namespace import, **default import**, single-quoted, double-quoted, deep subpath, `.js` extension, `require()`, dynamic `import()`, **`typeof import()` type queries (→ `/internal`)**, **a `<typeof import()>require()` cast in tandem** (catches the regression where the runtime arg gets rewritten but the type arg doesn't), the **full** jest mock family (`it.each` over `MOCK_HELPER_METHODS`), the **full** vi mock family, **`jest.mock('...', factory)` with a factory argument**, a non-mock `jest.*` call left alone, an import + `jest.mock` in the same file, preserved subpaths, non-`@nx/<name>` imports, unrelated string literals inside comments. Make sure every entry in `PUBLIC_SYMBOLS` and every entry in `MOCK_HELPER_METHODS` is exercised at least once — drift from hardcoded sets is the most likely silent regression.
**9. Watch for the published-version-mismatch gotcha in example/test builds.**
The workspace's root `node_modules/@nx/<name>` is the _published_ version (root `package.json` pins it to a real release tag, not `workspace:*`). When code at `dist/packages/<X>/...` does `require('@nx/<name>/internal')` at runtime, Node walks up from `dist/` and finds workspace-root `node_modules/@nx/<name>` — the published copy. If that version was released BEFORE this PR, it has no `internal.js` and resolution fails.
This bites specifically for examples or e2e flows that load `dist/packages/<other-package>/...` artifacts (e.g. an angular-rspack module-federation example that monkey-patches `Module._resolveFilename` to redirect `@nx/<other-package>` to dist). If the other-package's dist code does `require('@nx/<name>/internal')`, you'll hit this.
Two fixes:
- **(Preferred, if applicable.)** Migrate the _other_ package to local-dist too. Then its built code lives at `packages/<other>/dist/...`, walks up to `packages/<other>/node_modules/@nx/<name>` (a workspace symlink to source), and resolution finds the new `internal.js` because workspace source has it.
- **(Band-aid for the in-between window.)** If migrating the other package is out of scope, extend the example's existing request-path patch to also redirect `@nx/<name>/internal` to the workspace source `packages/<name>/dist/internal`. Document it as a temporary measure tied to the same TODO that exists for the other-package redirect.
Search aggressively for this pattern after step 8:
If `nx affected` fails on a single example test with `Cannot find module '@nx/<name>/internal'`, that's step 9 — extend the example's request-path patch.
If `nx affected` fails on a package with `TS2339: Property 'foo' does not exist on type 'typeof import(".../internal")'`, that's step 7 — a `shared.publicSymbol` call survived. Find it (`grep -rn 'shared\.<symbol>' packages/`) and rewrite the namespace source to `@nx/<name>`.
### 15. Audit `require('../../package.json')` (or similar relative paths to the package.json)
Search for `require\(['"]\.\..*package\.json` inside `packages/<name>/src/`. Any TS source file that reads the package's own `package.json` via a relative path is a **layout-fragility bug** that this migration triggers:
- Before migration: source `packages/<name>/src/utils/versions.ts` → built `dist/packages/<name>/src/utils/versions.js`. `'../../package.json'` resolves to `dist/packages/<name>/package.json` (which the old build path copied there).
- After migration: source unchanged → built `packages/<name>/dist/src/utils/versions.js`. `'../../package.json'` now resolves to `packages/<name>/dist/package.json` — **doesn't exist**. Every consumer that pulls in `nxVersion`/`NX_VERSION`/etc. crashes at module-load time with `Cannot find module '../../package.json'`. This breaks e2e tests broadly because most generators load `versions.ts`.
**Fix**: replace the relative path with a **package-name self-reference**, using the dynamic `join()` form so eslint's `@nx/enforce-module-boundaries` doesn't trip on it:
A literal `require('@nx/<name>/package.json')` works at runtime but trips `enforce-module-boundaries`'s `noSelfCircularDependencies` check — the rule statically pattern-matches self-imports and fires before checking whether the import resolves to a non-main entry. The dynamic `join()` form is opaque to the static check, matches `@nx/devkit`'s established pattern, and resolves to the same path at runtime.
Node resolves `@nx/<name>/package.json` via `node_modules` (workspace symlink in dev, real install in published), and the package.json's `exports` map already declares `./package.json` (you ensured this in Step 5). Works identically in source and dist contexts.
Reference implementations:
-`packages/nx/src/utils/versions.ts` — `require('nx/package.json').version` (works because `nx` is the project's own name; the static rule's entry-point check is lenient for the top-level `nx` package specifically)
Search for `ensurePackage\(['"]@nx/` inside `packages/<name>/src/`. For every match, look at the next 5–20 lines for a `await import('@nx/<other>/...')` pulling from the same package. This pattern is **broken** under `nodenext`:
- Before migration: `module: commonjs` made TypeScript downlevel `await import('@nx/<other>')` to `Promise.resolve(require('@nx/<other>'))`. The synchronous `require()` honors `Module._initPaths`, which is exactly where `ensurePackage` registers the on-demand temp install. Resolution succeeds.
- After migration: `module: nodenext` preserves `import()` as a true ESM dynamic import. ESM resolution **ignores**`Module._initPaths` — it walks up `node_modules` from the importing file's location only. The temp install lives in a different temp dir, so the import fails with `Cannot find package '@nx/<other>'`.
**Fix**: replace the dynamic import with a synchronous `require()`. The `ensurePackage` side effect makes it findable via `_initPaths`, and `require()` honors that:
Collapse multiple successive `await import()`s of the same module into one `require()` destructuring while you're at it.
This was the source of the M2 e2e regressions (Playwright/Web/React generators crashed at `Cannot find package '@nx/eslint'` from `ignore-vite-temp-files.js` and `ignore-vitest-temp-files.js`). One-line failure mode, but it can sit hidden in any code path that the unit-test suite doesn't exercise — only the published-then-installed flow exposes it. Audit every `ensurePackage` callsite.
### 16. Preserve `add-extra-dependencies` if the package has one
`scripts/add-dependency-to-build.js` is a release-time hack that injects an extra dep into the **published**`package.json` (e.g., it adds `nx` to `@nx/workspace`'s `dependencies`). It is **not dead code** — without it the transitive resolution chain breaks for downstream consumers.
Concretely: created workspaces depend on `@nx/js`, which transitively depends on `@nx/workspace`. When the fork in `generate-preset.ts` runs `nx g @nx/workspace:preset`, Node's `require.resolve('@nx/workspace/package.json')` only finds the transitively-installed package because pnpm hoists `nx` along with `@nx/workspace` into `.pnpm/node_modules/` — and `nx` is hoisted there only because the **published**`@nx/workspace/package.json` declares it as a regular dependency. Drop that injection and the fork in the new workspace fails with `Unable to resolve @nx/workspace:preset` → `unable to find tsconfig.base.json`.
When migrating a package that has the `add-extra-dependencies` target:
1.**Keep** the target in `packages/<name>/project.json`.
2.**Update**`scripts/add-dependency-to-build.js`: change the `pkgPath` from `../dist/packages/<package>/package.json` to `../packages/<package>/package.json` (the source manifest is now the published manifest under the local-dist layout).
3.**Keep** the `pnpm nx run-many -t add-extra-dependencies --parallel 8` invocations in `scripts/nx-release.ts` (both the GitHub-release path and the local-publish path) — they fire between `runNxReleaseVersion` and `nx run nx:expand-deps`.
4. Confirm the snapshot/reset list (`packagesToReset`) covers this package so the injection is undone after publish.
If the package does not have the target, leave the script and the run-many calls alone — they no-op for any project without the target.
### 17. Verify
Run:
```bash
pnpm nx run-many -t test,build,lint -p <name>
```
Then:
```bash
pnpm nx affected -t build,test,lint
```
### Summary of the pattern
The core idea is simple: instead of building to a shared `dist/packages/<name>/` at the workspace root, each package builds to its own `packages/<name>/dist/`. The `exports` map with `@nx/nx-source` condition lets workspace packages resolve to `.ts` source files during development, while external consumers get the built `.js` from `dist/`. This is like giving each package its own "output mailbox" instead of sharing one big mailbox.
| Linear milestone "Multi-version supported across plugins" (project NXC-4072) | What's wrong per plugin, the resolved support window, open human decisions. Per-plugin tasks NXC-4381..NXC-4410 (P1–P29). |
| This skill | How to implement the canonical shape, code-level anti-patterns, gotchas, findings doc shape (no-task case). |
The skill is the gap-closer: it accepts a Linear task, parses it, drives
the fix. When no task exists for the plugin, fix mode runs discovery in
Phase 1–2 and produces a findings doc that mirrors a Linear task body —
so the user can file it as a new task before proceeding.
**Reference PRs (the canonical shape):**
-`#35587` — `@nx/angular` — merged. Set the precedent. Introduced
`throwForUnsupportedVersion`.
-`#35642` — `@nx/playwright` — merged. Generalized the shared helpers
into `@nx/devkit/internal`. Established executor / runtime feature-
gating.
-`#35670` — `@nx/cypress` — merged. Added `excludeGenerators` to the
parameterized test helper.
-`#35671` — `@nx/vitest` — open at time of writing. Demonstrates
"drop phantom peer-range claim" and "declared floor < effective floor"
patterns.
Before citing any PR by number, verify state — these go stale:
| `multi-version-compliance <NXC-XXXX>` | Fix (primary) | Fetch task, surface findings + decisions in Phase 2, wait for user OK before Phase 3 edits. |
| `multi-version-compliance` (no arg) | Ask for task ID | Prompt for NXC-XXXX. |
| `multi-version-compliance @nx/<plugin>` (bare plugin) | Fix (task lookup) | Look up the per-plugin task in milestone NXC-4072. If found, confirm with user and enter fix mode. If not found, run discovery in Phase 1–2 (rubric against code), present findings, suggest filing as a new task before any edits. |
| `multi-version-compliance review #<N>` | Review | Fetch PR, derive Linear task from branch name if possible, compare diff vs. task findings (or run pure code-level review if no task). |
**Stop-after-Phase-2 (audit-equivalent):** if you want findings without
edits, decline to approve at the end of Phase 2. The skill stops, no
branch, no commits.
**On a branch matching `nxc-NNNN` with no explicit arg:** before
asking the user, suggest "Use NXC-NNNN?" inferred from the branch name.
## Linear-fetching protocol
Before any code-level work in Linear-driven mode, the skill MUST:
1.**Check Linear MCP availability.** If `mcp__linear-server__get_issue`
isn't available (MCP server not installed / not connected), tell the
user and fall through to the no-task discovery path (fix mode Phase 1
step 2). Don't pretend to fetch.
2.**Fetch the task.**`mcp__linear-server__get_issue id="NXC-XXXX"`.
If the call errors (invalid ID, network), halt and ask the user to
verify the ID.
3.**Verify shape.** Confirm:
- Title matches `[multi-version][P##] \`@nx/<plugin>\` — multi-version support compliance`(per-plugin) or`[multi-version][W#] ...` (cross-cutting). If the pattern doesn't match, halt and ask the user to confirm this is the right task.
- Status. `Done` → ask whether re-audit or follow-up. `Canceled` → halt and ask.
4.**Read description sections.** Every per-plugin task has:
- **Needs human decision** — open items blocking implementation.
- **Findings** — `(high|medium|low)` items with `[file:line]` and a suggested fix per item.
- **Verification checklist** — Sections A (Support window declarations) / B (Generator inputs) / C (Generator outputs) / D (Migrations) / E (Runtime) / F (Out-of-window UX).
`chore-not-fix-non-prod.md`). Don't enforce or flag these from this
skill — defer to whatever the user's conventions resolve to at PR time.
## Findings doc template (Phase 2 output, used when no Linear task exists)
When fix mode hits the no-task case (Phase 1 step 2), produce
`tmp/<plugin>-findings.md` shaped to mirror a Linear task body so the
user can file it as a new task in milestone NXC-4072 before proceeding
to Phase 3.
For plugins managing multiple primary packages, repeat the install-map
/ decisions / findings bullets per primary.
```md
# @nx/<plugin> — multi-version support compliance findings
> No Linear task in milestone NXC-4072. This doc is filing-ready —
> create the task with this body before proceeding to fix.
## Plugin
- Path: packages/<plugin>
- Upstream support: <official policy if any, else "no formal LTS">
- peerDep declarations: <list>
- Per-major install (`<file>` branches on installed `<package>` major):
- v<N-1>: <constants>
- v<N>: <constants> (default)
- Paired secondaries: <list of ecosystem-locked siblings>
## Needs human decision
1. <decision 1 — e.g., raise floor to vN.0.0 vs keep current>
2. <decision 2 — e.g., drop ^1.0.0 from peer (no v1 install lane)>
## Findings
- **(high) <one-line summary>** [file:line]
_Suggested fix_: <one-line>
- **(medium) ...**
- **(low) ...**
## Verification checklist (A–F)
### A. Support window declarations
- [ ] peerDep ranges match the support window
- [ ] Version map / runtime branching covers every supported major
- [ ] Every third-party package the plugin **invokes at runtime** has a peerDep entry. "Invokes" = TS import/`require` OR executor spawns its CLI binary OR inferred plugin emits a target whose `command` invokes its CLI (look for `externalDependencies: ['<pkg>']` in emitted target inputs). Packages the generator installs for the user to consume independently (ESLint plugins loaded by the user's eslintrc, `@types/*`) don't need peer-declaration.
- [ ] Peers needed only when a user opts into a specific surface (executor opt-in, inferred plugin gated on config file presence, opt-in preset) are declared **optional** via `peerDependenciesMeta: { "<pkg>": { "optional": true } }`. Required-non-optional peers are reserved for packages every workspace using the plugin needs.
- [ ]`addDependenciesToPackageJson` passes `keepExistingVersions=true` or branches on detected version
- [ ] Fresh-install path installs the latest supported version
### C. Generator outputs
- [ ] Templates compile and run on every supported version
- [ ] Generated `project.json` target shape valid on every major
- [ ] Default option values valid on every major
- [ ] Version map covers every managed third-party dep — no gaps
- [ ] Schema accepts union of options; runtime throws when inapplicable
### D. Migrations (migrations.json + packageJsonUpdates)
- [ ] Cross-major `packageJsonUpdates` declare `requires` per bumped package
- [ ]`requires` ranges are bilateral (`>=N <M`) by default. One-sided ranges (`<N` with no lower, `>=N` with no upper) are intentional (legacy cleanup, undefined source major, v0→v1 bridge) — flagged in "Needs human decision" or noted in the Findings.
- [ ] Every migration declares `requires` against the touched package
- [ ] Nx-only migrations have no third-party `requires`
- [ ] No silent gap in `packageJsonUpdates` across the support window
### E. Runtime
- [ ] Executors branch on installed version where behavior diverges
- [ ] Inferred plugin (createNodes/V2) parses configs across every major
### F. Out-of-window UX
- [ ] Below-floor: throws via shared util naming package + installed + floor; no silent fall-through
## Out-of-scope (deferred follow-ups)
- <e.g., consolidate ... across plugins — separate PR>
```
## References
See "Which references to load" near the top. Don't pull all of them.
- Inline `clean(declared) ?? coerce(declared)` chain at a generator entry point — duplicates `getDeclaredPackageVersion`.
- Open-coded tree-branch in `getInstalled<Pkg>Version(tree?)`: `getDependencyVersionFromPackageJson(tree, pkg)` + an `installedVersion === 'latest' || installedVersion === 'next'` check + a `clean(...) ?? coerce(...)?.version ?? null` chain. The dist-tag list and the normalization chain are both centralized in `getDeclaredPackageVersion` (via `NON_SEMVER_DIST_TAGS` / `normalizeSemver`). A local copy silently misses new entries if `NON_SEMVER_DIST_TAGS` grows.
**Why wrong:** The shared helpers in `@nx/devkit/internal` and `@nx/devkit/internal-testing-utils` already exist. Duplicates create drift — one will get the `latest`/`next` handling, the other won't; one will use `getNxRequirePaths()` for pnpm-strict resolution, the other won't.
**Do instead:** Call `assertSupportedPackageVersion(tree, pkg, floor)` via the per-plugin wrapper (`assertSupportedXVersion`). For executor-side reads: `getInstalledPackageVersion(pkg)`. For tree-side normalization: `getDeclaredPackageVersion(tree, pkg, latestKnown)`. For raw semver cleaning: `normalizeSemver(v)`.
For the `getInstalled<Pkg>Version(tree?)` wrapper specifically:
**Omit the third arg by default.** It conflates "missing" with "dist tag" — both fall back to the supplied fresh-install constant. That's almost never what the caller wants; consumers that need a `?? latest` fallback should encode it at the call site, not globally in the helper (rspack/rsbuild precedent). See `canonical-shape.md` §"Dist-tag semantics — third arg".
**Reference:** Compliant — `packages/cypress/src/utils/assert-supported-cypress-version.ts` (7 lines); `packages/cypress/src/utils/versions.ts``getInstalledCypressVersion` (no third arg); `packages/rspack/src/utils/version-utils.ts` and `packages/rsbuild/src/utils/version-utils.ts` (same shape). Concrete anti-pattern — PR `#35676` introduces `function cleanVersion` (`packages/rsbuild/src/utils/versions.ts`) and `getInstalledRsbuildVersionRuntime` reading `require('@rsbuild/core/package.json')`. Both should call the shared helpers instead.
## 2. Above-ceiling throw or warn
**Looks like:**`if (major > maxKnown) throw …`, `if (major > maxKnown) logger.warn …`, `versions()` with a `switch + throw default:`, any `warnAboveCeiling` / `throwAboveWindow` helper.
**Why wrong:** Explicit policy. Above-ceiling falls through silently to `latestVersions`. Throwing breaks users on newer versions of third-party packages, which is the opposite of the initiative's intent. The angular reference implementation does not warn or branch above the highest known major, and every subsequent plugin compliance PR follows that convention.
**Do instead:**`versionMap[major] ?? latestVersions`. Below-floor is caught by the generator-level assert; the `versions()` function is just a lookup.
**Reference:** Compliant — `packages/cypress/src/utils/versions.ts` after `#35670` rewrite. Anti-pattern (before fix) — same file before `#35670` had `switch + throw default:`.
## 3. Hardcoded third-party version in generator body
**Why wrong:** Bypasses the `versions(tree)` routing and the install-lane logic. New majors will not be picked up; older workspaces get the wrong version.
**Do instead:** Route through `versions(tree)` and reference the per-major entry. If you genuinely have a version that's the same across all majors, still put it in the map for consistency.
## 4. Init generator overwriting pinned versions
**Looks like:**`addDependenciesToPackageJson(tree, …, …, undefined, options.keepExistingVersions)` where the schema default is `false`. Or no fifth argument at all (defaults to `false`).
**Known-incomplete reference:**`@nx/angular`'s `init/schema.json` currently has `default: false` and `init.ts` passes `options.keepExistingVersions` directly — PR `#35587` did not fix this. The angular init generator therefore still has this bug. Flagging it in a non-angular compliance PR is correct; fixing it in passing during another angular PR is also appropriate.
**Why wrong:** Generators bump packages = the user's pinned version is silently overwritten on re-run. Bumping is the job of migrations, not generators.
**Do instead:** Pass `keepExistingVersions: true` (positional 5th arg) or `options.keepExistingVersions ?? true`. Flip the schema default to `true`.
**Reference:** Compliant — `packages/cypress/src/generators/init/init.ts` and `init/schema.json` after `#35670`. Anti-pattern — the same files before `#35670` had schema default `false`.
## 5. `requires` gate on an Nx-only migration
**Looks like:**
```json
{
"update-unit-test-runner-option":{
"requires":{"@angular/core":">=21.0.0"},
"description":"Update 'vitest' unit test runner option to 'vitest-analog' in generator defaults."
}
}
```
when the migration only writes to `nx.json`.
**Why wrong:** The migration applies regardless of third-party version — it's rewriting an Nx-owned generator default. The gate causes pre-v21 workspaces with the stale default to silently skip the migration and stay broken.
**Do instead:** Remove the `requires` entry entirely. Nx-only migrations have no third-party gate.
## 6. Cross-major `packageJsonUpdates` with no `requires`
**Looks like:**
```json
{
"21.3.0":{
"packages":{
"jest":{"version":"^30.0.0"}
}
}
}
```
with no `requires` gate, when this is a v29 → v30 bump.
**Why wrong:** The bump fires for every workspace — including workspaces already on v30 (idempotent best case) or workspaces on v28 or below (which would silently land on v30 without going through any v29 → v30 codemods). Source-major gate ensures the bump only fires for workspaces actually in the source range.
**Do instead:**
```json
{
"21.3.0":{
"requires":{"jest":">=29.0.0 <30.0.0"},
"packages":{"jest":{"version":"^30.0.0"}}
}
}
```
**Reference:** Compliant — `packages/angular/migrations.json` MF entries after `#35587`. Anti-pattern examples on master at time of writing — `@nx/jest``21.3.0`, `@nx/eslint``20.7.0`, `@nx/vite``20.5.0` (verify by inspecting each plugin's `migrations.json` for cross-major `packageJsonUpdates` entries lacking `requires`).
## 7. Gating ecosystem-locked siblings on the primary's major alone
**Looks like:** A migration that bumps `@ngrx/store` from v18 to v19 with `requires: { "@angular/core": ">=19.0.0" }` only — no `@ngrx/store` entry.
**Why wrong:**`@ngrx/store` is independent of `@angular/core` versioning. A workspace can be on `@angular/core: 19` without having `@ngrx/store: 18` (might not use ngrx at all, or might be on v17). Gating on `@angular/core` fires the migration in workspaces where it has nothing to do.
**Do instead:** Add the sibling to `requires`: `{ "@angular/core": ">=19.0.0", "@ngrx/store": ">=18.0.0 <19.0.0" }`. For Angular ecosystem siblings: `@angular/cli`, `@angular/ssr`, `@angular-devkit/build-angular` (v20+) are peer-locked via `@angular/core` and don't need their own gate. `@ngrx/*`, `@angular-eslint/*`, `zone.js`, `jest-preset-angular` are independent and do.
**How to verify pairing:** read the sibling package's `peerDependencies` at the version range being bumped from. If it pins the primary's major, the primary's `requires` covers it. If it doesn't, the sibling is independent and needs its own gate.
## 8. Peer dep claiming a major with no install branch (phantom claim)
**Looks like:**
```json
{
"peerDependencies":{
"vitest":"^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0"
}
}
```
when `versions.ts` has no v1 entry and no `isVitestV1` branch anywhere.
**Why wrong:** The plugin advertises support for a version it doesn't honor. v1 workspaces silently fall through to v4 install constants.
**Do instead:** Drop the unsupported major from the peer. `vitest: "^2.0.0 || ^3.0.0 || ^4.0.0"`. If the support is desired, add the install lane.
**Reference:** Pattern demonstrated in open PR `#35671` (`@nx/vitest`) — drops `^1.0.0` from the `vitest` peer because there's no v1 install lane in the plugin's `versions.ts`. Inspect via `gh pr diff 35671 --repo nrwl/nx -- packages/vitest/package.json`. Verify state first.
**Related — drop EOL major (different reasoning, same action):** the major HAS an install lane but is EOL upstream (e.g., Storybook's official policy is "top 3 majors only"; v7 is EOL). Drop it from the peer because it's upstream-unsupported, not because the plugin doesn't honor it. Concrete example: NXC-4406 calls out dropping Storybook v7 from `@nx/storybook`'s peer per Storybook's top-3-majors policy.
## 8a. PeerDep range wider than the runtime dep pin
The plugin's own runtime dep pins ^8, but the peer claims ^6/^7/^8.
**Why wrong:** Distinct from §8 — here the install lane exists (in `dependencies`), but the lane only ships one major. The peer is over-promising relative to what the plugin actually runs against. A workspace on ^6 will satisfy the peer but won't get a compatible runtime once `@typescript-eslint/parser@^8` resolves.
**Do instead:** Tighten the peer to the actually-supported runtime range, or widen the runtime dep + add the install/branch lanes for the additional majors.
**Reference:** NXC-4388 (`@nx/eslint-plugin`).
## 9. Top-level `require()` of an optional peer in an executor
**Looks like:**
```ts
// at the top of executor.impl.ts
constcypress=require('cypress');
```
**Why wrong:** When cypress is absent (not yet installed, peer mismatch, etc.), the executor throws `MODULE_NOT_FOUND` at module load time, before any user-friendly error. Especially bad for deprecated executors that should fail with a deprecation message.
**Do instead:**`require` inside the function body, after the version detection / clear error.
## 10. Anything-but-`requires` as substitute for `requires`
**Looks like (variant A — `incompatibleWith` standing in):**
with no `requires` block on the migration entry in `migrations.json`.
**Why wrong:** Neither approach is a source-major gate.
-`incompatibleWith` blocks running on workspaces that have the matching version — it doesn't gate to a source-major range. A workspace on `@angular-devkit/build-angular: 22.0.0` will still pass the `incompatibleWith` check.
- A runtime per-package guard runs the migration _body_ on every workspace and skips internally. The migration record still appears as "executed" to the migrate runner, and any side effects (logging, partial work) leak. The `nx migrate` runner uses `requires` as the source-major filter; bypassing it means the migration isn't filtered at the right layer.
**Do instead:**`requires: { "<pkg>": ">=N.0.0 <(N+1).0.0" }` — the actual source-major gate at the migration-entry level. Drop the in-body guard once the `requires` is in place.
**Reference:** Anti-pattern (variant B) — `@nx/eslint``update-typescript-eslint-v8.13.0` (NXC-4387) has runtime `gte('8.0.0') + lt('8.13.0')` per-package guards but no `requires` block. `@nx/jest` similar with `incompatibleWith` (NXC-4391).
## 11. Naming a specific plugin in shared helper docstrings
**Looks like:** A JSDoc in `assert-generators-enforce-version-floor.ts` referencing `migrate-to-cypress-11` as the example use case for `excludeGenerators`.
**Why wrong:** The helper is shared across plugins. Naming one plugin in its docstring is leaky.
**Do instead:** Generic phrasing — "generators that must run below the floor by design (e.g., migrators that lift sub-floor workspaces onto a supported version)".
**Reference:** an early draft of `#35670`'s test helper had the plugin-specific JSDoc; the merged version uses generic phrasing.
## 12. Both schema `"default": true` AND `options.keepExistingVersions ?? true`
**Why wrong:** Two sources of truth. Either the schema default does the job (and `options.keepExistingVersions` will always be `true`) or the `?? true` fallback handles it (and the schema default is redundant).
**Do instead:** Pick one. Schema default is sufficient when the call site uses `options.keepExistingVersions` directly. The `?? true` fallback is only needed if the schema can be bypassed (programmatic invocation without schema validation).
## 13. Manual `RegExp` matching in tests instead of substring `toThrow`
**Looks like:**
```ts
.rejects.toThrow(newRegExp(`Unsupported version of \\\`${packageName}\\\` detected`));
```
**Why wrong:** Escape bugs. The backtick and the `${}` are easy to get wrong. The shared helper uses substring matching for a reason.
**Do instead:**
```ts
.rejects.toThrow(`Unsupported version of \`${packageName}\` detected`);
```
**Reference:** see how `assertGeneratorsEnforceVersionFloor` itself does the match in `packages/nx/src/internal-testing-utils/assert-generators-enforce-version-floor.ts` (grep for `Unsupported version of`).
## 14. Validating only at install sites instead of generator entry
**Looks like:** A `if (installedVersion < floor) throw …` check guarding only the `addDependenciesToPackageJson` call inside a generator, while the rest of the generator runs unconditionally.
**Why wrong:** The generator may write configuration or templates incompatible with the sub-floor third-party version before reaching the install branch. The assert must be at the entry point so nothing else runs.
**Do instead:**`assertSupportedXVersion(tree)` as the first statement in the generator's working function (`*Internal` for plugins with the wrapper/internal split; the function itself for single-function generators). The install branch can then assume the floor is met.
## 15. Per-major version aliases alongside the bundle map
**Why wrong:** The aliases (`vitestV3Version`, `vitestV3CoverageV8Version`, etc.) duplicate the `versionMap` entries. They drift over time — someone bumps the map but forgets the alias (or vice versa), and the plugin starts installing one version via generators and another via tests/runtime. Also: every dropped major (e.g., when raising the floor) becomes three or four delete lines instead of one map entry.
**Do instead:** Keep the bundle pattern — the per-major `versionMap` is the only place those values live. Stable (cross-version-identical) deps stay as top-level `export const`s; varying deps are accessed via `versions(tree).<key>` or directly from the top-level `latestVersions` bundle.
**Reference:** Open PR `#35671` initially carried `vitestV2Version` / `vitestV3Version` / `vitestV4Version` aliases. A follow-up commit (`chore(testing): adopt cypress version-resolution pattern in @nx/vitest`) dropped them in favor of the bundle pattern. Inspect via `gh pr view 35671 --repo nrwl/nx --json commits`.
## 16. Declared floor below the effective floor
**Looks like:**`peerDependencies` lists `"vitest": "^2.0.0 || ^3.0.0 || ^4.0.0"` and `versions.ts` has a `versionMap` entry for `2`, but somewhere in the plugin's executor / runtime / plugin code there's an import of a third-party API that only exists in v3+:
**Why wrong:** A workspace on v2 will pass the floor assert (peer + versionMap claim support), then crash at runtime with `getRelevantTestSpecifications is not a function`. The peer is lying.
**Do instead:** Raise the floor to the lowest major where every called third-party API exists. Drop the now-unsupported entries from `versionMap`, `peerDependencies`, and the per-major version aliases (if any). The `assert-supported-<pkg>-version.spec.ts` sub-floor test now covers the dropped major.
**Reference:** Open PR `#35671`'s second commit (`fix(testing): drop vitest v2 support from @nx/vitest`) — originally proposed a v2 floor matching the lowest install lane, then raised to v3 after audit caught the `getRelevantTestSpecifications` usage. Inspect via `gh pr view 35671 --repo nrwl/nx --json commits`.
## 17. Creating a branch during Phase 1–2 (discovery / read-only)
**Looks like:**`git checkout -b <some-branch>` before the user has approved Phase 3 edits.
**Why wrong:** Phase 1–2 produces findings, not commits. Creating a branch creates pressure to commit something. Any working artifact (e.g., `tmp/<plugin>-findings.md` for the no-task case) goes in `tmp/` (gitignored) — for the user to read and scope from, not to commit.
**Do instead:** Run Phase 1–2 on the current branch (typically `master`). Output to `tmp/<plugin>-findings.md` if you wrote one. Branch creation belongs in Phase 3, after explicit user approval to proceed with edits.
Behavior: reads `generators.json` from `packageRoot`, iterates every entry, loads its factory, calls it against a tree with `{ [packageName]: subFloorVersion }` in `package.json`, expects a throw matching `Unsupported version of \`${packageName}\` detected`.
`excludeGenerators` is only for intentional sub-floor migrators (e.g., `migrate-to-cypress-11`). Comment the reason next to the array.
## Finding the existing floor (audit input)
When auditing a plugin you haven't touched before, the floor may not be declared in one place. Check, in order of authority:
1. **`minSupported<Pkg>Version` constant in `versions.ts`** — if it exists, that's the declared floor.
2. **`peerDependencies` lowest range in `package.json`** — what the plugin advertises supporting.
3. **Lowest major in `versionMap` / `backwardCompatibleVersions` / `supportedVersions`** — what the plugin has install lanes for.
4. **Lowest `packageJsonUpdates` entry that touches the third-party package** — historical evidence of the supported range.
5. **Highest API requirement in the plugin's own code (the _effective_ floor).** Grep for every `import` / `require` from the third-party package and identify which APIs are called. Cross-reference each against the third-party's changelog. The plugin's effective floor is the lowest major where **all** called APIs exist. **This trumps the declared floor** — if `versions.ts` claims v2 but the plugin imports an API only available in v3+, the declared floor is wrong.
These should agree. When they don't, the disagreement is the finding (phantom peer claim, drifted versionMap, declared floor below effective floor).
**Worked example:** During open PR `#35671`, the audit initially landed on a `v2.0.0` floor (matching the lowest install lane). Then a follow-up commit dropped the floor to `v3.0.0` after noticing the plugin's atomization code calls `getRelevantTestSpecifications`, which is a vitest v3+ API. Lesson: step 5 above is not optional. Always check what APIs the plugin's own runtime code uses — `versions()` having a v2 lane doesn't mean the plugin actually works on v2.
Pick the shape that matches whether the plugin already has a `supportedVersions` list (angular does; cypress/playwright/vitest don't).
### Plugins managing multiple primary packages
`@nx/jest` manages `jest`, `ts-jest`, `@types/jest`. `@nx/eslint` manages `eslint`, `@typescript-eslint/parser`, `@typescript-eslint/eslint-plugin`, `eslint-config-prettier`. The canonical wrapper signature takes one package; with multiple, decisions are needed:
- **Gate on the primary only** when the others are peer-locked (e.g., angular's strategy with `@angular/core` covering `@angular/cli`, `@angular/ssr`, etc.). This is sufficient when the siblings' peer-deps tie them to the primary's major.
- **Gate on each independently** when the siblings can be installed at any major regardless of the primary (typescript-eslint pair vs eslint; ts-jest vs jest). In that case, the wrapper makes multiple `assertSupportedPackageVersion` calls in sequence:
```ts
export function assertSupportedJestVersion(tree: Tree): void {
When in doubt: read each sibling's `peerDependencies` block at the version range being supported. If it pins the primary, it's covered by the primary's gate. If it doesn't (or pins something else), it needs its own.
## Skip writing the install constant when the package is already detected
Init generators that add the third-party package to `package.json` should NOT overwrite an already-installed minor/patch. The `keepExistingVersions: true` flag handles this at the `addDependenciesToPackageJson` level. But for code paths that compute the version to write (e.g., picking the major-specific value from `versionMap`), the rule is the same: read what's installed first; only write the fresh-install constant when nothing is detected.
Reference: `packages/cypress/src/generators/init/init.ts` `updateDependencies` — calls `getInstalledCypressVersion(tree)` first, then routes through `versions(tree)` which short-circuits to existing-version paths. The `keepExistingVersions ?? true` flag at the `addDependenciesToPackageJson` call site is the final safety net.
## The versions module
Path: `packages/<plugin>/src/utils/versions.ts`.
### Required exports
```ts
// Plain string, no caret. Used as the floor for assertSupportedPackageVersion.
### Stable deps stay top-level; per-major-varying deps go in the bundle
A plugin typically manages one primary package whose version map drives several siblings. Deps that vary per major go into a typed bundle; deps that are version-stable stay as plain `export const`s.
```ts
// Stable across all supported majors of the primary → plain exports.
**Do not** keep per-major aliases like `vitestV3Version = '^3.0.0'` alongside the bundle — they duplicate the map entries and drift over time. See `anti-patterns.md` §15.
### The `versions(tree)` function
Falls through to latest on unknown majors — no `switch + throw default:`.
```ts
export function versions(tree: Tree): VitestVersions {
return versionMap[vitestMajorVersion as CompatVersions] ?? latestVersions;
}
```
### The `getInstalled<Pkg>Version(tree?)` helper
Optional `tree` parameter — with tree, reads declared from `package.json` via `getDeclaredPackageVersion` (handles dist-tag normalization, semver cleaning); without tree, routes through `getInstalledPackageVersion` from `@nx/devkit/internal` (FS resolution via `getNxRequirePaths()`).
**Do not open-code the tree-branch.** `getDeclaredPackageVersion` already centralizes the dist-tag list (`isNonSemverDistTag`) and the `clean(v) ?? coerce(v)?.version ?? null` chain (`normalizeSemver`). Local re-implementations drift when devkit's constants change. See `anti-patterns.md` §1.
```ts
import { type Tree } from '@nx/devkit';
import {
getDeclaredPackageVersion,
getInstalledPackageVersion,
} from '@nx/devkit/internal';
import { major } from 'semver';
export function getInstalledVitestVersion(tree?: Tree): string | null {
if (!tree) {
return getInstalledPackageVersion('vitest');
}
return getDeclaredPackageVersion(tree, 'vitest');
}
export function getInstalledVitestMajorVersion(tree?: Tree): number | null {
#### Dist-tag semantics — third arg (`latestKnownVersion`)
`getDeclaredPackageVersion(tree, pkg, latestKnownVersion?)`'s third arg falls back to `normalizeSemver(latestKnownVersion)` whenever the declared range can't be normalized to semver — both "package missing from `package.json`" AND "package declared as a dist tag (`latest` / `next`)". The helper does not distinguish the two cases.
**Default to omitting the third arg.** The wrapper returns `null` for both "missing" and "dist tag"; consumers that want `?? latestVersions` semantics should encode it at the call site (rspack/rsbuild precedent), not globally in the helper. Passing the third arg makes the helper claim the package is "installed at the fresh-install constant" even when nothing is declared — which silently disables init generators' "add the package" branches.
When a consumer specifically needs to distinguish "missing" from "dist tag", use `getDependencyVersionFromPackageJson` from `@nx/devkit` to inspect the raw declared string.
## Generator entry points
The assert goes in the function that does the actual work — first statement of the function body, before any other tree access or sub-generator call. (The assert itself reads the tree, of course; the rule is that nothing else in the generator runs against an unsupported version.)
### Plugins with a `<gen>` / `<gen>Internal` split (cypress, playwright)
The public wrapper merges defaults and delegates. Assert lives in `*Internal`:
```ts
// Public wrapper — no assert, just default merging.
export async function cypressInitGenerator(tree: Tree, options: Schema) {
### Double-asserts are established convention, not an edge case
When `configurationGenerator` calls `initGenerator` internally, both call their respective `assertSupportedXVersion`. This is the angular precedent (29 `generators.json` entries → 58 assert call sites). The assert is idempotent (one tree read + one semver comparison) and the parameterized floor spec treats every entry point as independent — both must throw on sub-floor. Don't refactor away.
## User-pin preservation
### `addDependenciesToPackageJson` call sites
Every call from a generator (NOT a migration) must pass `keepExistingVersions: true` as the fifth positional argument or via the `?? true` pattern.
Reference (on master): `packages/cypress/src/generators/init/schema.json`, `packages/playwright/src/generators/init/schema.json`. (`@nx/vitest` follows the same pattern in its open PR — verify via `gh pr diff 35671`.)
Ecosystem-locked siblings whose peer-on-the-primary covers them: no separate `requires`. Examples in Angular: `@angular/cli`, `@angular/ssr`, `@angular-devkit/build-angular` (from v20+, NOT v19).
Independent siblings: each needs its own `requires` entry. Examples in Angular: `@ngrx/*`, `@angular-eslint/*`, `zone.js`, `jest-preset-angular`.
### Reference examples
- `packages/angular/migrations.json` `20.2.0-module-federation`, `22.2.0`, `22.6.0-module-federation` — Module Federation entries gating on `@module-federation/enhanced` source range. Added in `#35587`.
- `@nx/vitest`'s `update-22-1-0` and `update-22-3-2` migrations gating on `vitest: ">=4.0.0"` (Vitest-4-specific AI-instructions) — pattern proposed in open PR `#35671`. Inspect via `gh pr diff 35671 --repo nrwl/nx -- packages/vitest/migrations.json`.
- `packages/angular/migrations.json` `update-unit-test-runner-option` — Nx-only migration with the over-gating `@angular/core` `requires` **removed** in `#35587`.
import { assertGeneratorsEnforceVersionFloor } from '@nx/devkit/internal-testing-utils';
import { join } from 'node:path';
describe('@nx/<plugin> generators enforce supported version floor', () => {
assertGeneratorsEnforceVersionFloor({
packageRoot: join(__dirname, '..', '..'),
packageName: '<pkg>',
subFloorVersion: '~<floor-minus-one>',
// Required only when a generator must run below the floor by design.
// excludeGenerators: ['migrate-to-cypress-11'],
});
});
```
Pick `subFloorVersion` such that `lt(coerce(it).version, floor)` is true. No pre-release identifiers. Reference values used in the repo: `~18.2.0` (angular, v19 floor), `~12.17.0` (cypress, v13 floor), `~1.35.0` (playwright, v1.36 floor).
`assertSupportedPackageVersion` is already fully tested in `@nx/devkit`,
so a per-plugin spec mostly re-tests the shared helper. The early
compliance PRs (`#35587` angular onward) ship one for symmetry, but it
isn't required. If you add one, five canonical cases is the shape used:
```ts
describe('assertSupportedCypressVersion', () => {
it('throws when cypress is below the supported floor');
it('does not throw when cypress is not installed (fresh-install path)');
it('does not throw when cypress is `latest`');
it('does not throw when cypress is `next`');
it('does not throw when cypress is within the supported window');
});
```
Reference: `packages/angular/src/utils/assert-supported-angular-version.spec.ts` (originally landed in `#35587`).
### Test message matching
Use substring match on the error message:
```ts
.rejects.toThrow(`Unsupported version of \`${packageName}\` detected`)
```
Not a hand-rolled RegExp (avoid escape bugs). Reference: see how the shared `assertGeneratorsEnforceVersionFloor` itself does the match — search for `Unsupported version of` in `packages/nx/src/internal-testing-utils/assert-generators-enforce-version-floor.ts`.
- The `Installed:` line preserves the **original declared range** (e.g., `~18.2.0`), not the cleaned semver. `assertSupportedPackageVersion` passes `declared` through to `throwForUnsupportedVersion`.
- Do not add an "above ceiling" branch to this message. Above-ceiling is silent fallthrough.
## Peer dep alignment
### What belongs in `peerDependencies`
The test: _would the plugin still work if this package were absent from the workspace, with the plugin's code paths unchanged?_
A package is required-peer if **any** of these is true:
- The plugin's TypeScript imports / `require`s it (executor, preset, runtime helper).
- The plugin's executor spawns its CLI binary (`spawn('cypress')`, etc.).
- The plugin's inferred plugin (`createNodes`/`createNodesV2`) **emits a target whose `command` invokes the package's CLI** (e.g., `command: 'rspack build'` → `@rspack/cli` is required). The `externalDependencies: ['<pkg>']` declaration in such targets is itself an admission of the runtime dependency.
A package is **not** required-peer when:
- The plugin's generator installs it into the user's workspace for the user to consume independently, and no plugin code (TypeScript, executor binary spawn, or inferred-plugin emitted command) ever invokes it. Example: ESLint plugins written into the user's eslintrc — `@nx/cypress` installs `eslint-plugin-cypress`, but its lint executor uses generic ESLint loading; the cypress plugin is loaded by ESLint per the user's config, not by `@nx/cypress`. Example: `@types/*` packages installed for the user's TS compilation but never imported by plugin code.
**Ecosystem-signal peer** (Angular's full `@angular/*` peer list) → judgment call, not a compliance requirement. Documents lockstep compatibility but isn't enforced by the multi-version rules.
### Required vs. optional peer
Most Nx plugin peers should be **optional** (`peerDependenciesMeta: { "<pkg>": { "optional": true } }`):
- Required peer: every user of the plugin needs this package, regardless of which surface they use. Example: `@angular-devkit/core`, `rxjs` in `@nx/angular` — every Angular Nx workspace uses them.
- **Optional peer (the common case for inferred-plugin / executor surfaces):** the package is only needed when the user opts into a specific surface — an executor they have to write into `project.json`, an inferred plugin gated on the presence of a config file, a preset that auto-injects. Users who don't use that surface shouldn't see an unmet-peer warning. Examples: `@playwright/test` in `@nx/playwright`, `cypress` in `@nx/cypress`, `vitest` / `vite` in `@nx/vitest`, `@angular/build` / `@angular-devkit/build-angular` / `ng-packagr` in `@nx/angular`.
For `@rspack/cli` / `@rspack/core` in `@nx/rspack`: both surfaces (executor, inferred plugin) are gated — executor opt-in via `project.json`, inferred plugin gated on `rspack.config.{ts,js}` presence. Compliance fix should peer-declare both with `optional: true`.
| `@rspack/cli` | `@nx/rspack` | yes — inferred plugin emits `command: 'rspack build'` (`packages/rspack/src/plugins/plugin.ts:182,196`). Not imported in TS, but invoked via emitted CLI target. | **yes** | optional (both surfaces gated) |
| `@angular-devkit/core` | `@nx/angular` | yes (used by every Angular Nx workspace) | **yes** | **not optional** |
| `@angular/build` | `@nx/angular` | yes (only when user uses the @angular/build builder) | **yes** | optional |
| `eslint-plugin-cypress` | `@nx/cypress` | no (generator writes it into user's eslintrc; ESLint loads it, not the plugin) | **no** | n/a |
| `@types/node` | various | no (generator install only; types are build-time) | **no** | n/a |
### Range / version alignment
For packages that ARE peer-declared: the range must match the install lanes the code ships. If the code has no `isV1Installed` branch and no v1 entry in `versionMap`, do not list `^1.0.0` in the peer range.
Reference: open PR `#35671` (`@nx/vitest`) drops `^1.0.0` from the `vitest` peer range because there is no v1 install lane. Inspect via `gh pr diff 35671 --repo nrwl/nx -- packages/vitest/package.json`. Verify state — may have merged or closed since.
## Executor / runtime feature gating
Features introduced after the floor must gate at call time on the installed version, not on the floor. Use `getInstalledPackageVersion` + `lt` from `semver`.
```ts
import { getInstalledPackageVersion } from '@nx/devkit/internal';
import { lt } from 'semver';
import { minPlaywrightVersionForBlobReports } from './versions';
if (installed && lt(installed, minPlaywrightVersionForBlobReports)) {
throw new Error(
`The "@nx/playwright:merge-reports" executor requires "@playwright/test" version ${minPlaywrightVersionForBlobReports} or greater (the version that introduced the "blob" reporter and the "merge-reports" CLI). You are currently using version ${installed}.`
);
}
```
Reference: `packages/playwright/src/executors/merge-reports/merge-reports.impl.ts`, `packages/playwright/src/utils/preset.ts`. Both added in `#35642`.
Two distinct cases:
- **Auto-injected feature** (preset's auto-blob in CI): skip injection silently when installed < threshold. Only throw when the user explicitly opted in (`generateBlobReports: true`) on an unsupported version.
- **Direct invocation** (executor CLI subcommand): throw immediately with a clear "requires >= X.Y.Z (the version that introduced …)" message.
## File-layout summary
For plugin `@nx/<plugin>` managing `<pkg>` with floor `X.Y.Z`:
```
packages/<plugin>/
src/
utils/
versions.ts # add minSupportedXVersion
assert-supported-<pkg>-version.ts # NEW — 7-line wrapper
all-generators-enforce-floor.spec.ts # NEW — parameterized
generators/
<each>/
<each>.ts # assert as first statement
init/
schema.json # keepExistingVersions default: true
init.ts # keepExistingVersions ?? true
executors/ # feature-gate via getInstalledPackageVersion
plugins/ # same
migrations.json # tighten / remove `requires` gates per audit
package.json # align peer; add "semver": "catalog:" if newly used
```
---
# Code-level verification (review-mode lens)
In review mode, walk these markers against the diff.
Output rules:
- **Inline categories are `[blocker]` and `[non-blocker]` only.** No "open question," "ask," or other ad-hoc tags. Author-directed questions emerge from non-blocker findings and surface in the closing "Open questions for author" block.
- **For each blocker / non-blocker:** anchor at `file:line` and cite which reference PR / file demonstrates the correct pattern. Cross-reference `anti-patterns.md` when the finding matches a numbered pattern.
- **Sections without findings get a single summary line**, not a per-file enumeration. `"Pass — all 7 generator entries assert at first statement"` is right; listing seven file:lines is wrong. Reviewer time is spent on actionable items; passing checks should not eat reading budget.
- **Produce the verdict block** at the end (see §"Verdict template"). The block is the skimmable index — produce it, don't substitute a free-form summary.
Scope:
- **In scope:** the diff's code, configs, schemas, migrations, and **in-codebase documentation that describes runtime behavior** (e.g., `.mdoc` / `.md` files under `astro-docs/` or `docs/` that claim how the plugin behaves). A docs claim that contradicts the code is a correctness issue and belongs here.
- **Out of scope:** PR title, PR body shape, commit message format, related-issues section, branch naming. Defer to the user's PR/commit conventions (loaded globally from `~/.claude/memory/workflow/git/`). Don't flag PR/commit shape in this skill's review output.
## 1. Peer dep & install constants
- [blocker] `package.json` peer dep range matches the install lanes implemented in `versions.ts`. No phantom version claims. → If `versionMap` has no v1 entry, peer must not list `^1.0.0`. Reference correction: `#35671` (`@nx/vitest`). Anti-pattern: §8.
- [blocker] **Declared floor matches the effective floor.** Grep every `import` / `require` from the third-party package in plugin code. If any imported API only exists at version >N, the declared floor must be >=N. Anti-pattern: §16. Reference correction: `#35671` second commit raised vitest from v2 to v3 after catching a `getRelevantTestSpecifications` import (v3+ only).
- [blocker] Fresh-install constant exposes the **full feature surface**, not just the peer floor. → Playwright peer stayed `^1.36.0` but fresh-install moved to `^1.37.0` because blob reporter + `merge-reports` CLI both require 1.37. Reference: `#35642` `packages/playwright/src/utils/versions.ts`.
- [blocker] No per-major version aliases (`<pkg>V3Version`, `<pkg>V4Version`, etc.) alongside a `versionMap` — pick one source of truth. Anti-pattern: §15. Reference: `#35671`'s third commit dropped these aliases.
- [blocker] `versions.ts` exports `minSupportedXVersion = 'X.Y.Z'` as a plain string (no caret, no range markers). The wrapper passes this verbatim to `assertSupportedPackageVersion`.
- [blocker] `versionMap[major]` lookup is `versionMap[major] ?? latestVersions`. No `switch + throw default:` or other above-ceiling throw. Anti-pattern: §2. Reference correction: `#35670` (`@nx/cypress` `versions()` rewrite).
- [blocker] Every third-party package the **plugin invokes at runtime** has a `peerDependencies` entry. "Invokes" covers: (a) TypeScript `import`/`require`, (b) executor spawning the package's CLI binary, (c) inferred-plugin (`createNodes`/`createNodesV2`) emitting a target whose `command` invokes the package's CLI (the `externalDependencies: ['<pkg>']` field on such targets confirms the dependency). See §"Peer dep alignment" for the full categorization.
- **Don't flag** packages the plugin's generator installs into the user's workspace for the user to consume independently, with no plugin codepath invoking them (e.g., ESLint plugins like `eslint-plugin-cypress` that ESLint loads from the user's eslintrc; `@types/*` packages).
- Plugins flagged at time of writing for actually-invoked packages without a peer entry: `@nx/webpack`, `@nx/rollup`, `@nx/angular-rspack-compiler` (primary listed under `dependencies`); `@nx/jest`, `@nx/nest`, `@nx/module-federation`, `@nx/react`, `@nx/vue`, `@nx/expo`, `@nx/react-native`, `@nx/node`, `@nx/js` (verify per plugin — TS imports, binary spawns, AND inferred-plugin emitted commands all count).
- [blocker] Peers that are only used when the user opts into a specific surface (executor opt-in, inferred plugin gated on config file presence, opt-in preset) are declared **optional** via `peerDependenciesMeta: { "<pkg>": { "optional": true } }`. Pattern is established across reference plugins — `@playwright/test`, `cypress`, `vitest`, `vite`, `@angular/build`, `ng-packagr` are all optional. Required-non-optional peers (`@angular-devkit/core`, `rxjs` in `@nx/angular`) are reserved for packages every workspace using the plugin needs. See §"Required vs. optional peer".
- [non-blocker] If the PR introduces `semver` usage in the plugin, `package.json` `dependencies` lists `"semver": "catalog:"`. Reference: `#35642` added it to `packages/playwright/package.json`.
## 2. Generator entry points
- [blocker] **Every** entry in `generators.json` has its working function calling `assertSupported<Pkg>Version(tree)` as the **first statement** — before any tree reads, writes, or sub-generator calls. For wrapper/internal-split plugins (cypress, playwright): assert is in `*Internal`. For single-function generators (angular): in the function itself. Anti-pattern: §14.
- [blocker] Plugin wrapper file `assert-supported-<pkg>-version.ts` imports `assertSupportedPackageVersion` from `@nx/devkit/internal`. No direct call to `throwForUnsupportedVersion`. No bespoke `throwBelowFloor` / `throwAboveWindow` / `assertVersion` / local `cleanVersion = clean(v) ?? coerce(v)?.version` helpers (use `normalizeSemver` / `getInstalledPackageVersion` / `getDeclaredPackageVersion`). Anti-pattern: §1. Concrete example: PR `#35676` introduces a local `cleanVersion` and `getInstalledRsbuildVersionRuntime` — both already exist as shared helpers.
- [blocker] If `all-generators-enforce-floor.spec.ts` uses `excludeGenerators`, each excluded name has a code comment explaining why the generator must run sub-floor (e.g., `migrate-to-cypress-11` lifts v8–v10 workspaces onto v11).
- [non-blocker] Double-assert chains (`configurationInternal` calls `initInternal`, both assert) are OK. Idempotent. Don't refactor away.
## 3. Generator outputs
- [blocker] Templates the generator writes (project files, configs, schemas) compile and run on every major in the support window. Verify with a quick mental walk: for each template referenced from the generator, identify any per-major-version conditional and confirm it's accurate.
- [blocker] Generated `project.json` target shape (executor, options, schema) is valid on every supported major. If the executor's option schema differs across the support window, the generator branches or uses the union shape.
- [blocker] Default option values are valid on every supported major. A default that's only valid above a specific major must be conditional.
- [blocker] Version map covers every managed third-party dep. If the runtime later branches on a sibling's version (e.g., `@vitest/ui`), the version map must have an entry for that sibling per major — no gaps where the generator picks a constant the runtime then can't reconcile.
- [blocker] Generator schema accepts the **union of options across the support window**. Options removed in a newer major still validate at schema level (with description-notice); runtime throws when inapplicable on the installed major. See `gotchas.md` §"Schema-level deprecated-option stubs with runtime throws".
- [blocker] Every `addDependenciesToPackageJson` call from a **generator** passes `keepExistingVersions: true` (positionally as the 5th arg) or `options.keepExistingVersions ?? true`.
- [blocker] `init/schema.json` has `"keepExistingVersions": { "default": true }`. Not `false`. Not absent. **Known gap:** `@nx/angular`'s init schema currently has `default: false` and was NOT addressed in `#35587` — flagging in a non-angular PR is correct; fixing in passing in an angular PR is also correct. Anti-pattern: §4.
- [non-blocker] If both schema `"default": true` AND `options.keepExistingVersions ?? true` are present, that's two sources of truth. Anti-pattern: §12.
- **Migration generators are exempt.** Do not flag missing flags in code under `src/migrations/`.
## 5. `migrations.json` gates
- [blocker] Every `packageJsonUpdates` entry that bumps across a major version has `requires: { "<pkg>": ">=N.0.0 <(N+1).0.0" }`. Source-major gate, not target. Anti-pattern: §6. Reference: `#35587` Module Federation entries.
- **Read the actual range strings; don't tick this by counting split entries.**
- [non-blocker / ask author] One-sided gates (`<X` with no lower bound, or `>=Y` with no upper bound) may be intentional or accidental. Legitimate cases: legacy-cleanup codemods that should apply on every source major below the target; a v0→v1 bridge where every v0.x workspace should migrate; bumping a package introduced at vN from `undefined`. Illegitimate cases: a v1→v2 bump expressed as `<2.x` would fire for v0 workspaces too; a `>=N` with no upper bound would fire for future majors. **When you see a one-sided gate, ask the author to confirm intent** — don't auto-flag as blocker.
- [blocker] Codemod migrations that only make sense at/above a specific third-party major have a `requires` entry. Open upper bound is intentional when the codemod cleans up legacy flags. Runtime per-package guards (`gte`/`lt` inside the migration body) are NOT a substitute for `requires`.
- [blocker] **Nx-only migrations have NO `requires` gate.** A migration that only writes to `nx.json`, executor options, or generator defaults applies regardless of third-party version. Anti-pattern: §5. Reference correction: `#35587` removed the over-gating `@angular/core: >=21.0.0` from `update-unit-test-runner-option`.
- [blocker] For independent siblings (Angular: `@ngrx/*`, `@angular-eslint/*`, `zone.js`, `jest-preset-angular`), gating on the primary's major is **not sufficient** — each needs its own `requires` entry. Anti-pattern: §7. Verify pairing by reading the sibling's `peerDependencies` at the version range being bumped from.
- [blocker] A single `packageJsonUpdates` entry must not mix mutually-exclusive cross-major bumps under one `requires` (AND-semantics). Split into separate entries each with its own gate. Concrete example: React PR's `22.3.4` entry mixed `react-router 7.12.0` (cross-major) with `react-router-dom 6.30.3` (v6 patch) — must split.
- [non-blocker] `incompatibleWith` is not a substitute for `requires`. Anti-pattern: §10. If you see `incompatibleWith` standing in for a source-major gate, ask for a `requires` instead.
- [non-blocker] Sibling `packageJsonUpdates` entries within the same block that depend on a peer's post-bump version are fine — tier-1 chaining evaluates against post-bump state. Reference: Storybook 21.2.0 chains on the prior 21.1.0 bump.
- [non-blocker] Pre-floor `packageJsonUpdates` entries targeting source majors below the current support floor are intentionally retained for users on older Nx versions. Don't _add_ a bridge entry without explicit decision, and don't _remove_ a legitimately-pre-floor entry mid-audit.
- [blocker] Executor code that invokes a CLI subcommand or uses an API introduced after the floor calls `getInstalledPackageVersion('<pkg>')` + `lt(installed, threshold)` from `semver` and throws a clear "requires >= X.Y.Z (the version that introduced …)" message. Reference: `packages/playwright/src/executors/merge-reports/merge-reports.impl.ts` (`#35642`).
- [blocker] Preset / config builders that auto-inject feature-version-coupled config skip injection silently when installed < threshold, and only throw when the user **explicitly** opted in on an unsupported version. Reference: `packages/playwright/src/utils/preset.ts` (`#35642` — `generateBlobReports` logic).
- [blocker] **Inferred plugins** (`createNodes`/`createNodesV2`) parse configs across every major in the support window. The plugin emits the same target shape regardless of the installed major (or branches if shapes diverge). Don't hardcode helper imports against one major.
- [blocker] **Above-ceiling is silent fallthrough.** No warn, no throw, no branch. Anti-pattern: §2.
- [non-blocker] Executors don't enforce the plugin floor. Floor enforcement is generator-only. Don't suggest adding an executor-level floor assert unless the user asks.
- [non-blocker] `require('<pkg>')` for optional peers should live inside the function body, after version detection. Anti-pattern: §9.
## 7. Tests
- [blocker] `all-generators-enforce-floor.spec.ts` exists at `packages/<plugin>/src/utils/all-generators-enforce-floor.spec.ts`, calls `assertGeneratorsEnforceVersionFloor` from `@nx/devkit/internal-testing-utils`. This is the parameterized spec that exercises every generator's floor assert.
- [blocker] `subFloorVersion` is a semver range where `lt(coerce(it).version, floor)` is true. No pre-release identifiers. Reference values: `~18.2.0` (angular, v19 floor), `~12.17.0` (cypress, v13 floor), `~1.35.0` (playwright, v1.36 floor).
- [non-blocker] Plugins establishing the pattern (`#35587` angular) ship a `assert-supported-<pkg>-version.spec.ts` with the 5 canonical cases (sub-floor / fresh-install / `latest` / `next` / in-range). The underlying `assertSupportedPackageVersion` already has full coverage in `@nx/devkit`, so the per-plugin spec largely re-tests the shared helper. Useful for symmetry across the PR series but not required — don't block on missing.
- [non-blocker] Runtime/executor feature-gate throw tests are nice-to-have, not required — reference PRs (`#35587`, `#35642`, `#35670`) do not have them today.
- [non-blocker] Error message matching uses substring (`toThrow('Unsupported version of \`<pkg>\` detected')`) instead of hand-rolled `RegExp`. Anti-pattern: §13.
- [non-blocker] FS-side helper migrated to `getInstalledPackageVersion`; tree-side helper may stay inline. The two helpers' `null` vs. fallback semantics differ. Reference: `#35670` `packages/cypress/src/utils/versions.ts` rewrite.
## Open questions to raise (when missing from the PR / Linear task)
1. **Floor:** deliberate raise from the previous declared peer, or matching the existing peer? If raise: do sub-floor users get a `packageJsonUpdates` bridge or manual bump?
2. **Peer-range tightening:** dropping a major because there's no install lane (legitimate, `#35671` pattern) or because tests fail (regression risk — investigate)?
3. **`requires` removals on Nx-only migrations:** genuinely Nx-only, or sneaking through a third-party-touching change?
4. **Pruned migrations gaps:** if floor is being raised by N+ majors and prior `packageJsonUpdates` entries were removed, do sub-floor users have any auto-bump path? `git log --all -- packages/<plugin>/migrations.json`.
5. **Runtime feature gates:** threshold verified against third-party release notes, or guessed?
6. **Sibling classification:** ecosystem-locked vs. independent. Read the sibling's `peerDependencies` at the bumped-from range. `@angular-devkit/build-angular` is the gotcha — peer-locked from v20+, NOT v19.
7. **Cross-plugin coordination:** if the plugin pins a third-party that another plugin also manages (e.g., `@nx/cypress` pinning vite for cypress v13/v14+; `@nx/vite` supporting vite v5–v8), confirm the windows stay aligned. If `@nx/vite` drops v5, `@nx/cypress` carries an orphaned install lane.
## Verdict template
```
Blockers: <N>
Non-blockers: <N>
1. Peer dep & install constants: [pass | <findings>]
| `#35587` | `@nx/angular` | `nxc-4381` | merged | First compliance PR. Established `throwForUnsupportedVersion`, the `assertSupported*Version` wrapper pattern, the `all-generators-enforce-floor.spec.ts` shape, the MF `requires`-gate pattern, the Nx-only-migration over-gate removal pattern. |
| `#35642` | `@nx/playwright` | `nxc-4398` | merged | Generalized the helpers into `version-floor.ts`/`installed-version.ts`. Added `assertGeneratorsEnforceVersionFloor` in `internal-testing-utils`. Established executor/runtime feature-gating pattern (blob reporter / `merge-reports`). Demonstrated the "fresh-install constant higher than peer floor" pattern. |
| `#35670` | `@nx/cypress` | `nxc-4384` | merged | Established `excludeGenerators` in the shared test helper for intentional sub-floor migrators (`migrate-to-cypress-11`). Demonstrated `versions()` switch-to-fallthrough rewrite. Demonstrated keeping the tree-side inline helper while migrating only the FS side to the shared helper. |
| `#35671` | `@nx/vitest` | `nxc-4408` | open at time of writing | Three commits. (1) Establishes "drop phantom peer-range claim" (removes `^1.0.0` from peer); migration `requires` tightening for Vitest-4-only AI-instructions migrations. (2) Raises floor v2 → v3 after audit catches `getRelevantTestSpecifications` import (v3+ API) — establishes the **effective-floor-vs-declared-floor** pattern (see `anti-patterns.md` §16). (3) Adopts the cypress version-resolution pattern (bundle-of-varying-deps `versions(tree)`, `getInstalled<Pkg>Version(tree?)`, no per-major aliases — see `anti-patterns.md` §15). To inspect: `gh pr view 35671 --repo nrwl/nx --json commits` then `gh pr diff 35671`. Verify state — may have merged or closed. |
Always verify state with `gh pr view <N> --repo nrwl/nx --json state` before citing — this table goes stale.
## Reference commits (for `git show` inspection)
When the same change exists as both a pre-squash branch commit AND a merged squash on master, prefer the merged squash — it's the authoritative final state. Pre-squash SHAs are listed because they're easier to read in isolation (smaller diffs) when investigating one specific aspect.
| `e2ef134645` | `fix(testing): multi-version support compliance for @nx/playwright (#35642)` |
| `5d8b1bab7e` | `cleanup(devkit): allow excluding generators from version floor test helper` |
| `bc35b484e3` | `fix(testing): multi-version support compliance for @nx/cypress (#35670)` |
Pre-squash branch SHAs are available via `gh pr view <N> --json commits` even after the branch is deleted; useful when inspecting one specific aspect of a merged PR in isolation. Example:
Cross-reference with the third-party packages each plugin manages (peer deps in `package.json`).
## How to use these examples
When auditing a new plugin, before writing anything:
1. Read the PR body of `#35642` (`@nx/playwright`) — it's the most comprehensive description of the canonical shape.
2. Read the four files from `@nx/playwright`: `versions.ts`, `assert-supported-playwright-version.ts`, `all-generators-enforce-floor.spec.ts`, and `preset.ts`. Five minutes.
3. If your plugin has a `migrations.json` of any complexity, also read `packages/angular/migrations.json` MF entries and the `update-unit-test-runner-option` entry for the gate patterns.
4. If the plugin has runtime feature gates, also read `packages/playwright/src/executors/merge-reports/merge-reports.impl.ts`.
When reviewing a compliance PR, the diff should look very similar to one of these reference PRs. Differences should be justifiable by the plugin's specifics (different floor, different feature gates, different migration shape) — not by departing from the canonical patterns.
Non-obvious behavior. Load these into your model before auditing or reviewing.
## `latest` / `next` dist-tags
When a workspace declares `"<pkg>": "latest"` or `"next"` in its `package.json`:
-`assertSupportedPackageVersion` no-ops via `isNonSemverDistTag` (NON_SEMVER_DIST_TAGS = `['latest', 'next']`). The floor check is skipped entirely.
-`getDeclaredPackageVersion` falls back to the cleaned `latestKnownVersion` argument (if provided) or returns `null`.
-`versions(tree)` returns `latestVersions` (the fresh-install path).
Tests must include `latest` and `next` cases. Both are no-ops; neither throws.
## pnpm `catalog:` references
Declared versions may be `"catalog:default"`, `"catalog:typescript"`, etc. (since pnpm 9.5):
-`getDependencyVersionFromPackageJson` (via the catalog manager in devkit) resolves these before the helper sees them. Don't call `clean`/`coerce` on raw values.
-`normalizeSemver` behavior on a raw `catalog:` string is not explicitly tested (open question — verify if you encounter it).
Reference: PR `#35459` (`fix(misc): resolve pnpm catalog: refs in version lookups`) — landed catalog ref handling.
## Fresh-install path (package not declared)
When `<pkg>` is missing from the workspace's `package.json` entirely:
-`assertSupportedPackageVersion` no-ops.
-`versions(tree)` returns `latestVersions`.
- The generator proceeds with the fresh-install constant (e.g., `playwrightVersion = '^1.37.0'`).
This is intentional — the generator is being run on a new workspace or one that's adding this package for the first time.
## Error message preserves declared range, not cleaned semver
```
Installed: ~18.2.0
Supported: >= 19.0.0
```
`Installed:` shows what's in `package.json` verbatim. Don't try to normalize it in the error message — it tells the user exactly what they typed, which helps them find it.
The argument flow: `assertSupportedPackageVersion` calls `throwForUnsupportedVersion(packageName, declared, minSupportedVersion)` with the raw `declared` value.
The shared `getDeclaredPackageVersion` falls back to `latestKnownVersion` when the declared value is `latest`/`next` or missing. Cypress's tree path returns `null` on missing. These semantics differ enough that the helper can't be consolidated without changing behavior.
The FS path (`getCypressVersionFromFileSystem`) was migrated to `getInstalledPackageVersion` (better resolution for pnpm strict / nested installs). The tree path stayed inline.
Reference: `packages/cypress/src/utils/versions.ts` after `#35670`.
## Double-asserts are fine
When `configurationInternal` calls `initInternal` (or any generator chain), both call their respective `assertSupportedXVersion(tree)`. The assert is idempotent and cheap (one tree read + one semver comparison). Don't refactor away.
The `assertGeneratorsEnforceVersionFloor` test treats both entry points as separate generators and asserts each throws — which is what we want.
## Angular ecosystem lockstep — what `@angular/core >=N` covers
Peer-locked to `@angular/core` (one `requires` on the primary is sufficient):
-`@angular/cli`
-`@angular/ssr`
-`@angular-devkit/build-angular`**from v20+** (NOT v19 — `@angular-devkit/build-angular@19` does not peer-on `@angular/core`)
-`@angular/material`, `@angular/cdk`, all `@angular/*` framework packages
-`@schematics/angular`
Independent (need their own `requires`):
-`@ngrx/*`
-`@angular-eslint/*`
-`zone.js`
-`jest-preset-angular`
-`karma`, `karma-*`
-`protractor` (deprecated)
-`tailwindcss` and CSS-tooling siblings
Always verify pairing at the actual version range being bumped from — `@angular-devkit/build-angular` is the classic gotcha (peers on `@angular/core` in some versions, not others).
## Pruned migrations leave no trace in `migrations.json`
Older `packageJsonUpdates` entries (e.g., `12.x` migrations) are removed during normal Nx version cleanup waves. They don't show up in the current `migrations.json` but their absence is meaningful — users on an old floor have no auto-bump path to the new floor.
Check via:
```sh
git log --all --oneline -p -- packages/<plugin>/migrations.json | head -200
When raising a floor by N+ majors, decide whether to:
1. Add a `packageJsonUpdates` entry bridging sub-floor → floor (the user gets auto-bumped on `nx migrate`).
2. Leave the gap (the user sees the floor-assert error and has to bump manually).
The Cypress v12 → v13 gap was left intentionally — users get the assert error and bump manually. Don't add a bridge entry without explicit agreement.
## Pre-floor `packageJsonUpdates` entries are intentionally retained
Distinct from the pruned-history case above: a plugin may carry `packageJsonUpdates` entries targeting source majors **below** the current support floor. Example: `@nx/react-native`'s entries `20.3.0` and `21.4.0` target RN versions below the current ~0.79.3 floor. These are intentionally retained for users on older Nx versions that supported older RN.
Don't _add_ a bridge entry without explicit decision. Don't _remove_ a legitimately-pre-floor entry as part of a compliance pass. The W1/W4 audit window only covers entries that target source majors **inside** the current support window.
## `subFloorVersion` must satisfy `lt(clean(it), floor)`
For the parameterized floor spec, pick a value that's actually below the floor after `clean()`. Examples:
| `19.0.0` | `~18.2.0`, `^18.0.0`, `18.2.0` | `~19.0.0-beta.0` (clean strips the pre-release; in some cases this still satisfies `lt`, but it's confusing — avoid pre-release) |
Use a stable minor-or-patch range below floor. Don't use pre-release identifiers.
## Declared floor vs. effective floor
The declared floor (peer dep + `minSupported<Pkg>Version` + `versionMap` lowest entry) is what the plugin advertises. The **effective floor** is the lowest major where every third-party API the plugin's code actually calls is available. When they diverge, the declared floor is lying.
How this happens: someone bumps the plugin to use a new API (e.g., `getRelevantTestSpecifications` introduced in vitest v3) without raising the floor. The plugin compiles, generators pass tests against the latest install lane, but workspaces on sub-effective-floor versions crash at runtime with `... is not a function`.
How to detect: in the audit's runtime/executor inventory step, every `import` / `require` from the third-party package goes into a list. Cross-reference each named export against the third-party's release notes / API docs. The effective floor is the highest "introduced in" version across that list.
How to fix: raise the declared floor to match the effective floor. Drop the now-unsupported entries from `versionMap`, peer, and any per-major aliases. The parameterized floor spec's `subFloorVersion` shifts up accordingly.
Reference: open PR `#35671` (`@nx/vitest`) — proposed v2 floor initially; raised to v3 in a follow-up commit after spotting `getRelevantTestSpecifications` usage. See `anti-patterns.md` §16.
## `versions()` fall-through above ceiling, not throw
Before `#35670`, cypress's `versions()` had a `switch + throw default:`. This is wrong for two reasons:
1. New majors that don't yet have a `versionMap` entry should silently use `latestVersions` — the plugin hasn't been updated to know about them, but the user should still be able to use them.
2. Below-floor is already caught by the generator-level assert. The `versions()` throw is redundant for the in-range/sub-floor case, and wrong for the above-ceiling case.
Within a single `packageJsonUpdates` entry (single block), if entry A bumps package X and entry B has `requires: { X: ">=N" }` that depends on the post-bump value of X, B's gate evaluates against the post-bump state. This is **deliberate design**, not a bug.
Concrete example: Storybook's `21.2.0-migrate-storybook-v9` migration is gated on `storybook >=9.0.0` even though the prior state was v8 — the sibling `packageJsonUpdates``21.1.0` bumps Storybook to v9 first, so the v9 gate evaluates against post-bump state.
This means you can have one block bump X then chain a sibling bump gated on X's new version, without splitting into separate `packageJsonUpdates` keys.
## Cross-plugin coordination of shared third-party windows
Some third-party packages are managed by multiple Nx plugins. Concrete example:
If `@nx/vite` drops v5 from its supported window, `@nx/cypress`'s v5 pin becomes an orphaned install lane — workspaces using both plugins are now in conflict.
When raising / lowering a third-party's support window in one plugin, check every other plugin that manages the same package. The Linear milestone tasks call this out per-plugin (e.g., NXC-4384 cypress flags vite coordination with NXC-4407 vite).
### Sibling declaration consistency
When the same third-party package appears in multiple plugins, the declaration _kind_ (peerDependencies vs dependencies vs devDependencies) should be consistent unless the plugins genuinely have different roles for the package. Concrete inconsistency on master at time of writing: `@module-federation/enhanced ^2.3.3` is in `dependencies` in `@nx/module-federation` but `peerDependencies` in `@nx/rspack`. Pick one rule per package across the plugin family and document the exception when one plugin must differ.
## Plugin must own its primary third-party's pin
A plugin's install constants for its primary third-party must live in the plugin's own `packages/<plugin>/src/utils/versions.ts` — not in another plugin. Cross-plugin imports of install constants create governance drift (the owning plugin can't change the pin without breaking the borrower).
Example anti-pattern: `@nx/esbuild`'s `esbuild` install constant living in `@nx/js`. Flagged in NXC-4386.
## Schema-level deprecated-option stubs with runtime throws
Established Angular pattern (also called out in NXC-4391 jest, NXC-4395 next, NXC-4408 vitest): when an option is deprecated/removed in a newer third-party major but the plugin still supports an older major where it's valid, **retain the option in the schema with a description-notice and throw at runtime when inapplicable to the installed major**.
This keeps the schema accepting the union of options across the support window. Runtime branches on installed version and throws a clear message if the user passes an option that's only valid on a major they're not running.
Reference: search the angular generators for `removed in Angular vN` style schema descriptions paired with `assertSupportedAngularVersion`-aware option handling.
## Known-incomplete plugins
These were touched by a compliance PR but the work is incomplete. Useful for review and for future PRs.
- **`@nx/angular` init `keepExistingVersions`**: `packages/angular/src/generators/init/schema.json` has `default: false` and `packages/angular/src/generators/init/init.ts` passes `options.keepExistingVersions` directly (no `?? true`). PR `#35587` fixed `add-linting` but NOT the init generator. Flag in non-angular PRs as a reference to the pattern; fix in passing in any future angular PR. (Verify state on current master before citing.)
- **`@nx/jest` peer-dep block missing entirely.** When adopting the floor assert, add `peerDependencies` first declaring `jest` / `ts-jest` / `@types/jest` ranges. Without the peer block, `getDependencyVersionFromPackageJson` for `jest` may return `undefined` on installed workspaces because pnpm catalog refs and certain other patterns rely on the peer being declared.
- **Cypress v12→v13 migration gap**: when `#35670` raised the floor to v13, prior v12-cleanup `packageJsonUpdates` entries were already pruned. Decision was to leave it — v12 workspaces see the assert error and bump manually. Reference for the "raise floor, no bridge" pattern.
- **`getInstalled<Pkg>Version` consolidation deferred**: each plugin still has its own near-identical helper (the FS-side has been migrated to the shared helper in some plugins, but a full unification across cypress/playwright/vitest/next/expo/angular is pending). Don't bundle that refactor into a compliance PR.
## `migrate-to-cypress-11` and other intentional sub-floor migrators
A generator whose purpose is to lift sub-floor workspaces onto a supported version must run on sub-floor workspaces. If it had the floor assert, it could never run.
For these generators:
- Do NOT add `assertSupportedXVersion(tree)` to them.
- Keep their existing version checks (e.g., `assertMinimumCypressVersion(8)` in `migrate-to-cypress-11`).
- Add them to `excludeGenerators` in `all-generators-enforce-floor.spec.ts` with a code comment explaining why.
There are usually 0 or 1 of these per plugin. Greater than 1 is suspicious — review carefully.
## `getInstalledPackageVersion` vs. `require('<pkg>/package.json')`
Bare `require('<pkg>/package.json')` resolves from the plugin's own install location, which in pnpm strict mode or nested installs may not match the workspace's resolved version. `readModulePackageJson` (used by `getInstalledPackageVersion`) goes through `getNxRequirePaths()` for correct workspace-rooted resolution.
Anywhere you read an installed version at runtime: prefer `getInstalledPackageVersion('<pkg>')`. Don't `require('<pkg>/package.json')`.
## "Above ceiling" is NOT in the task spec
Repeating because this gets re-introduced: above-ceiling handling is explicitly out of scope for these compliance tasks. If you find yourself adding it, you've drifted from the spec.
The behavior we want above the highest known major: silent fall-through to `latestVersions`. The plugin will be updated to add a `versionMap` entry for the new major in a future PR. Until then, the user gets the latest install constants and may run into incompatibilities, which is the existing pre-compliance behavior. We are NOT trying to detect future majors and warn — that's a different feature.
## Decisions you cannot make alone
Pause and ask when:
- **Peer-range drop:** dropping a major from the peer might be a regression if tests pass on that version. Verify whether the absence of an install lane reflects "we never supported it" (legitimate drop) or "we shipped support and quietly broke it" (regression — investigate before dropping).
- **Floor raise without a bridging migration:** raising the floor by N+ majors means users on the lowest sub-floor major see the assert error and must manually bump. Confirm with the user: acceptable, or add a `packageJsonUpdates` bridge?
- **`requires` removal on a borderline migration:** the diff says the migration is Nx-only (no third-party config touched), but it reads a config file that only exists at certain third-party versions. The third-party dependency is indirect but real. Don't remove the gate without verifying.
- **Peer floor and fresh-install constant diverge** (playwright pattern — peer `^1.36.0`, fresh-install `^1.37.0`). Confirm the gap is justified by feature surface (1.37 introduced the blob reporter + merge-reports CLI) and not an oversight.
- **Ecosystem-locked vs. independent sibling classification:** before adding or removing a sibling's `requires` entry, read its `peerDependencies` block at the version range being bumped from. `@angular-devkit/build-angular` is the gotcha — only peer-locked to `@angular/core` from v20+.
- **Pruned migration gap:** the lowest sub-floor major has no auto-bump path because prior `packageJsonUpdates` entries were removed during cleanup waves. Decide: add a bridge entry, or accept the manual bump? `git log --diff-filter=D -- packages/<plugin>/migrations.json` reveals the gap.
- **New plugin doesn't fit the canonical shape** (manages multiple primary packages with different floors, runs partially as a Nx-internal-only plugin, etc.). Ask before improvising — see `canonical-shape.md` §"Plugins managing multiple primary packages" for the established multi-primary pattern.
- **Test fails on `latest`/`next` despite the assert being a no-op.** The no-op behavior is intentional, but if the generator downstream of the assert can't handle the unresolved range, that's a real bug — not something to paper over by tightening the assert.
## Per-plugin decision log
These were decided once for the reference PRs (#35587, #35642, #35670) — apply them as defaults unless explicitly contradicted by the user for a new plugin:
- **Executors do NOT enforce the plugin floor.** Generator-only. Executors gate per-feature, not per-floor.
- **Above-ceiling: silent fall-through to `latestVersions`.** No warn, no throw, no branch.
- **Init generators preserve user pins** via `keepExistingVersions: true` (schema default) and the `?? true` safety net at the call site.
- **Skip writing the install constant when the package is already detected** (cypress + angular pattern — preserves the user's installed minor/patch).
- **Shared helpers stay in `@nx/devkit/internal`** — not part of the public devkit surface. (The W2 ticket originally proposed adding `throwForUnsupportedVersion` to the public devkit API; the implementation landed under `/internal` instead, matching how other version-related helpers ship.)
- **Consolidation of per-plugin `getInstalled<Pkg>Version` helpers is deferred** — don't bundle that refactor into a compliance PR.
Plugin-specific decisions that may be pending or have settled differently (check the live PR state via `gh pr list --repo nrwl/nx --search "multi-version compliance"`):
-`@nx/jest` — needs a `peerDependencies` block for `jest`/`ts-jest`/`@types/jest` before the floor assert can rely on `getDependencyVersionFromPackageJson`.
-`@nx/eslint` — historically gated on an ESLint v8 EOL decision. If you're touching it, confirm the decision is settled.
-`@nx/eslint-plugin` — historically coupled to the eslint v8 decision (typescript-eslint v6/v7 only support eslint v8). Confirm before proceeding.
-`@nx/rspack` / `@nx/rsbuild` — there is an open PR (`#35676` at time of writing). Inspect for the local-helper-duplication anti-pattern (`anti-patterns.md` §1).
description: Check modified Nx documentation pages against the astro-docs style guide. Auto-trigger after writing or editing docs content in the nx repo. Also trigger on "check style", "style guide", "docs review", "validate docs". Should run as a final step whenever docs files are modified. IMPORTANT: anytime astro-docs/**/*.mdoc files are modified, this should always run automatically without being asked.
allowed-tools: Read, Glob, Grep
---
# Nx docs style check
You are a documentation editor for Nx. Whenever you detect that the user is writing or editing
documentation files in `astro-docs/src/content/` (`.mdoc`, `.mdx`, `.md`), automatically run this
check and fix any issues. Do not wait to be asked.
## Phase 1: Information architecture audit
Read `astro-docs/STYLE_GUIDE.md` (the "Information architecture" section) and
`astro-docs/sidebar.mts` to understand where the page lives in the sidebar hierarchy.
For every new or moved page, evaluate against ALL FIVE principles. These are non-negotiable:
### 1. Progressive disclosure ("journey" rule)
- Is this for the first 30 minutes (Getting Started), first 30 days (Features), or forever (Reference)?
- Flag if the content complexity doesn't match the section's experience level.
### 2. Category homogeneity ("scan" rule)
- Look at sibling pages in the same sidebar section.
- Do they all share the same content type (concepts, tasks, or products)?
- Flag if this page mixes types that siblings don't.
### 3. Type-based navigation ("intent" rule)
- Is this a learning page (narrative/guide) or a lookup page (reference/API)?
- Flag if it's in the wrong category (e.g., a reference page in a guides section).
### 4. Pen and paper test ("theory" rule)
- Can the page be explained using only pen and paper (no terminal needed)?
- YES = belongs in "How Nx Works" (architecture/concepts)
- NO (needs terminal/code examples) = belongs in "Platform Features" or "Technologies"
- Flag if a concept page has terminal output, CLI commands, or code-heavy examples.
### 5. Universal vs. specific ("placement" rule)
- Does this feature apply to every Nx user?
- YES = "Platform Features"
- NO (only React/Angular/etc. users) = "Technologies"
- Flag if a technology-specific page is in Platform Features or vice versa.
## Phase 2: Style validation
### Step 1: Run Vale and fix errors
Run `nx run astro-docs:vale` to check the modified files.
- **errors** — fix these automatically. Edit the file to resolve the violation.
- **warnings** — fix these automatically when the fix is unambiguous (e.g., sentence case headings).
For ambiguous cases, suggest the fix and ask.
- **suggestions** — mention them to the user but do not auto-fix.
### Step 2: Fix issues Vale doesn't catch
Read `astro-docs/STYLE_GUIDE.md` and check for that things that Vale may have missed.
### Handling false positives
Use inline Vale comments to suppress legitimate exceptions:
description: Bump the dev.nx.gradle.project-graph plugin version. Use when updating the Gradle project graph plugin version across the codebase, creating the migration files, and updating migrations.json.
description: Migrate several repos to a target nx version (e.g. 23.0.0-beta.25) in one coordinated pass — delegates `nx migrate` + migrations to a Polygraph child agent per repo, then pushes branches and opens linked draft PRs. Use when asked to upgrade/migrate multiple repos to a specific nx version, or when working a Polygraph session whose goal is an nx version bump across repos.
Migrate a set of repos to one target nx version, then open linked draft PRs. Think of it like a pharmacist filling the same prescription for several patients: same drug (target version), but each patient (repo) has different allergies (package manager quirks) — get those wrong and the dose silently fails.
## Input
- **Target version** — e.g. `23.0.0-beta.25`. Verify it exists: `npm view nx@<version> version`.
- **Repos** — an explicit list, or the repos already in a Polygraph session. When none is given, the **default set** is `nx`, `ocean`, `nx-labs`, `nx-examples`, `nx-console` (all in the `nrwl` org).
## Procedure
### 1. Set up the session
Use the `polygraph` skill to discover repos, select the org, and start (or join) the session. It owns auth and session lifecycle — don't reimplement any of that here.
### 2. Delegate the migration to a child agent per repo
This is the Polygraph way: each repo's work runs in its own child agent (`spawn_agent`), not in the parent. Delegate to every repo in the session — in parallel — and poll with `show_agent` until each is terminal. Hand each child the migration instruction below (substitute the target version).
> Migrate this repository to nx `<VERSION>`.
>
> 1. **Branch from the current default branch, not the clone's checkout.** Fetch first so you don't inherit a stale clone or an in-place working-dir branch, then create the branch from `origin/<base>` (`master` or `main`): `git fetch origin <base> && git checkout -B migrate-nx-<VERSION> origin/<base>`.
> 2. Detect the package manager from the lockfile (`package-lock.json`=npm, `yarn.lock`=Yarn Berry, `pnpm-lock.yaml`=pnpm, `bun.lock`/`bun.lockb`=bun).
> 3. **Install first, so `node_modules` is at the repo's _current_ (pre-migrate) nx version.** `nx migrate` reads the "from" version from `node_modules`, not `package.json` — if `node_modules` is already at the target, it finds **zero migrations** and silently skips them. Verify with `node -p "require('./node_modules/nx/package.json').version"`.
> 4. Run `nx migrate <VERSION>` (updates `package.json`, writes `migrations.json`).
> 5. Install again — **mutable**. Do NOT set `CI=true` (it makes Yarn Berry immutable / pnpm frozen, so the install and migrations fail silently). pnpm needs `--config.confirm-modules-purge=false`; Yarn Berry needs `YARN_ENABLE_IMMUTABLE_INSTALLS=false`.
> 6. **Commit the version bump first** (before running migrations, so it stays isolated from the migration edits): stage `package.json` + the lockfile — NOT `migrations.json` — and commit `chore(repo): migrate to nx <VERSION>` (never mention AI/Claude).
> 7. If `migrations.json` exists, run it with commits + agentic review:
> - `--create-commits` lands each applied migration as its own commit, so migration-driven source edits stay isolated and reviewable.
> - The scoped `--commit-prefix` is **required**: nx's default `chore: [nx migration] ` has no scope and fails commitlint. (Pin the agent with `--agentic=claude-code` if auto-detection picks the wrong one.)
> - `--validate` (agent-driven validation) is **on by default** once `--agentic` is enabled, so you don't pass it separately.
> - Caveat: nx auto-skips the agentic flow when it detects it is already inside an AI agent (`Agentic flow skipped: …`), and `--validate` has **no effect inside an outer agent** (or non-interactively without an explicit agent) — so the review only truly runs when the migration executes outside the child-agent context.
> 8. Delete `migrations.json`; if migrations changed deps, re-install and commit the lockfile update.
> 9. Report: old→new version, packages bumped, migrations run (and their commits), and any errors — including type/name collisions (e.g. a repo that pins an older nx and keeps a `*V2` symbol). **Leave those for a human to resolve; do not invent workarounds.**
**Migrations can rewrite source:** a multi-beta jump (e.g. beta.23→beta.25) pulls migrations from every intervening version, so it may rewrite real code (e.g. `CreateNodesContextV2`→`CreateNodesContext`). The child should review the non-dep diff before committing. A single-beta jump on an already-current repo often legitimately has none.
### 3. Push + open a PR per repo, as each child finishes
Don't barrier on the slowest repo. The moment a child reports success, `push_branch` that repo (branch `migrate-nx-<VERSION>`) and `create_pr` for **that repo alone** — so its CI starts immediately and one slow repo (e.g. one stuck fighting the sandbox) doesn't gate the others:
```
for each repo, as its child reaches terminal success (not in a barrier):
push_branch(repo) → create_pr([repo])
```
The PRs stay **linked** because they all join the same Polygraph session — the link is the session, not the single batched call. Commit-message scope `repo` passes nx's commitlint. Print the Polygraph session URL once all are open.
> **Verify once:** a single batched `create_pr` writes every PR body with its sibling cross-references at creation time; with incremental creation, confirm Polygraph **back-fills** the earlier PRs' bodies with links to the later ones (vs. each PR only linking to the session). If it doesn't back-fill and you need the in-body cross-links, fall back to one batched `create_pr` after all children finish.
## Verification checklist (per repo, before opening PRs)
- [ ]`package.json` nx + `@nx/*` at the target version
- [ ] Migrations **ran** (not skipped because `node_modules` was already at target)
- [ ]`migrations.json` deleted
- [ ] Version-bump commit (`chore(repo): migrate to nx <VERSION>`) present on `migrate-nx-<VERSION>`, plus one `chore(repo): [nx migration] …` commit per applied migration (from `--create-commits`)
- [ ] Any collision/compile errors surfaced in the child's report for a human to resolve
## Gotchas from real runs
These each cost real time on a live 5-repo run. Plan for them up front.
**pnpm dies under the Bash sandbox; bun/yarn don't.** As of Claude Code 2.1.172 the Bash tool sandboxes by default. pnpm's content-addressed store + `clonefile()` reflink + `node_modules` purge trip macOS rules — `com.apple.provenance` xattr removal, creating `.vscode`/`.idea` dirs in the virtual store — plus outbound TLS, so pnpm `install` fails with `ERR_PNPM_EPERM` / reflink / `Operation not permitted`, while bun and yarn install cleanly. **Polygraph children carry their _own_ sandbox** (`~/.polygraph/config.json` → `agentOptions.claude.sandbox`), separate from `~/.claude/settings.json` → `sandbox.enabled`; either one only reaches already-spawned processes after a **restart**. If a pnpm child stops on a sandbox/EPERM error, do **not** let it invent workarounds (xattr stripping, TLS shims, store redirection). Instead, disable the sandbox + restart, or migrate that repo from the **unsandboxed parent**: the initiator repo is in-place, and clones live at `~/.polygraph/sessions/<id>/repos/<org>/<repo>` — run the same install→migrate→install steps there with the sandbox off, then push.
**The base can move after you start.** Step 1 (branch from `origin/<base>`) handles the _initial_ state, but the default branch can still advance **mid-run** — e.g. a separate version-bump PR merges underneath you, as happened when ocean's `main` jumped beta.23→beta.25 below an open migrate PR and turned it **conflicting**. Detect it with the behind-count (`git rev-list --count migrate-nx-<V>..origin/<base>`) and watch for open bump PRs; when the base moves, **redo the branch onto the fresh base** — only the repos whose base actually advanced need it. Redoing onto a newer base can also _shrink_ the diff: a beta.25→rc.0 redo is dep-only, whereas the old beta.23→rc.0 ran 16 migrations and rewrote source.
**The initiator repo runs in-place** in your working dir, so migrating it switches branches and churns `node_modules`. Restore it afterward — or run its migration in a throwaway worktree off the real base (`git worktree add -B migrate-nx-<V> /tmp/wt origin/<base>`) so the working copy is never touched.
**A concrete source collision.** The `CreateNodesContextV2`→`CreateNodesContext` rename migration collided with a vendored local `interface CreateNodesContext extends CreateNodesContextV2`, producing a self-referential `extends CreateNodesContext` (TS2310). Surface it for a human; the minimal fix is aliasing the import: `import { CreateNodesContext as NxCreateNodesContext } from '@nx/devkit'`. (That rewrite is a _beta.24_ migration — starting from beta.25 skips it entirely.)
**Push/auth pitfalls.** (1) The SSH agent can drop mid-run (`communication with agent failed`) — SSH `git push` then fails; retry, or have the user re-`ssh-add`. (2) A read-only `GH_TOKEN` env var can shadow a write-capable keychain login: every write (push, `pr edit`, `pr merge --auto`) returns `Resource not accessible by personal access token`. Prefix gh writes with `env -u GH_TOKEN` to fall back to keychain auth. (3) Polygraph `push_branch` does an internal `pull --rebase`, so it **cannot force-update a rebased branch** — use a direct `git push --force` (SSH/HTTPS) for those.
description: Run Nx generators with prioritization for workspace-plugin generators. Use this when generating code, scaffolding new features, or automating repetitive tasks in the monorepo.
description: Polls Nx Cloud CI pipeline and self-healing status. Returns structured state when actionable. Spawned by /nx-cloud-ci-monitor command to monitor CI Attempt status.
model: fast
---
# CI Watcher Subagent
You are a CI monitoring subagent responsible for polling Nx Cloud CI Attempt status and self-healing state. You report status back to the main agent - you do NOT make apply/reject decisions.
## Your Responsibilities
1. Poll CI status using the `ci_information` MCP tool
2. Implement exponential backoff between polls
3. Return structured state when an actionable condition is reached
4. Track iteration count and elapsed time
5. Output status updates based on verbosity level
## Input Parameters (from Main Agent)
The main agent may provide these optional parameters in the prompt:
# Large content fields - fetch only when returning to main agent
```
## Initial Wait
Before first poll, wait based on context:
- **Fresh start (no expected CIPE):** Wait 60 seconds to allow CI to start
- **Expecting new CIPE:** Wait 30 seconds (action already triggered)
**IMPORTANT:** Always run sleep in foreground, NOT as background command.
```bash
sleep 60# or 30 if expecting new CIPE (FOREGROUND, not background)
```
## Two-Phase Operation
The subagent operates in one of two modes depending on input:
### Mode 1: Fresh Start (no `expectedCommitSha` or `previousCipeUrl`)
Normal polling - process whatever CIPE is returned by `ci_information`.
### Mode 2: Wait-for-New-CIPE (when `expectedCommitSha` or `previousCipeUrl` provided)
**CRITICAL**: When expecting a new CIPE, the subagent must **completely ignore** the old/stale CIPE. Do NOT process its status, do NOT return actionable states based on it.
#### Phase A: Wait Mode
1. Start a **new-CIPE timeout** timer (default: 30 minutes)
2. On each poll of `ci_information`:
- Check if CIPE is NEW:
-`cipeUrl` differs from `previousCipeUrl` → **new CIPE detected**
[CI Monitor] Still seeing previous CI Attempt (ignoring): <oldCipeUrl>
[CI Monitor] Polling... (elapsed: 1m 30s)
[CI Monitor] Still seeing previous CI Attempt (ignoring): <oldCipeUrl>
[CI Monitor] Polling... (elapsed: 2m 30s)
[CI Monitor] ✓ New CI Attempt detected! URL: <newCipeUrl>, SHA: <newCommitSha>
[CI Monitor] Switching to normal polling mode...
```
### Why This Matters (Context Preservation)
**The problem**: Stale CIPE data can be very large:
-`taskOutputSummary`: potentially thousands of characters of build/test output
-`suggestedFix`: entire patch files
-`suggestedFixReasoning`: detailed explanation
If subagent returns stale CIPE data to main agent, it **pollutes main agent's context** with useless information (we already processed that CIPE). This wastes valuable context window.
**Without wait mode:**
1. Poll `ci_information` → get old CIPE with huge data
2. Return to main agent with all that stale data
3. Main agent's context gets polluted with useless info
4. Main agent has to process/ignore it anyway
**With wait mode:**
1. Poll `ci_information` → get old CIPE → **ignore it, don't return**
2. Keep waiting internally (stale data stays in subagent)
3. New CIPE appears → switch to normal mode
4. Return to main agent with only the NEW, relevant CIPE data
## Polling Loop
### Subagent State Management
Maintain internal accumulated state across polls:
```
accumulated_state = {}
```
### Call `ci_information` MCP Tool
**Wait Mode (expecting new CI Attempt):**
```
ci_information({
branch: "<branch_name>",
select: "cipeUrl,commitSha,cipeStatus"
})
```
Only fetch minimal fields needed to detect CI Attempt change. Do NOT fetch heavy fields - stale data wastes context.
Merge response into `accumulated_state`, then return merged state to main agent.
**Pagination:** Heavy string fields return first page only. If `hasMore` indicated, include in return format so main agent knows more content available.
### Return to Main Agent When
Return immediately with structured state if ANY of these conditions are true:
| `fix_auto_applying` | `selfHealingStatus == 'COMPLETED'` AND `couldAutoApplyTasks == true` AND `verificationStatus == 'COMPLETED'` |
| `fix_available` | `selfHealingStatus == 'COMPLETED'` AND `suggestedFix != null` AND (`couldAutoApplyTasks != true` OR `verificationStatus` in (`FAILED`, `NOT_EXECUTABLE`)) |
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
## Nx Cloud Connection Check
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
### Step 0: Verify Nx Cloud Connection
1.**Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
2.**If `nx.json` missing OR neither property exists** → exit with:
```
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
```
3. **If connected** → continue to main loop
## Session Context Behavior
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
- **For a completely clean slate:** Exit Claude Code and restart `claude`
## Default Behaviors by Status
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
- If fix needs enhancement → use Apply Locally + Enhance Flow above
- If fix is wrong → reject via MCP, fix from scratch, commit, push
### Auto-Apply Eligibility
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
### Apply vs Reject vs Apply Locally
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
### Apply Locally + Enhance Flow
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
2. Make additional changes as needed
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Reject + Fix From Scratch Flow
When the fix is completely wrong:
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
2. Fix the issue from scratch locally
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Environment Issue Handling
When `failureClassification == 'ENVIRONMENT_STATE'`:
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
- Subagent will **completely ignore** the old/stale CIPE
- Subagent will only wait for new CIPE to appear
- Subagent will NOT return to main agent with stale CIPE data
- Once new CIPE detected, subagent switches to normal polling
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
### Step 4: Progress Tracking
After each action:
- If state changed significantly → reset `no_progress_count = 0`
- If state unchanged → `no_progress_count++`
- On new CI attempt detected → reset `local_verify_count = 0`
description: Monitor Nx Cloud CI pipeline and handle self-healing fixes automatically. Checks for Nx Cloud connection before starting.
---
# CI Monitor Command
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
Parse any overrides from `$ARGUMENTS` and merge with defaults.
## Nx Cloud Connection Check
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
### Step 0: Verify Nx Cloud Connection
1.**Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
2.**If `nx.json` missing OR neither property exists** → exit with:
```
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
```
3. **If connected** → continue to main loop
## Session Context Behavior
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
- **For a completely clean slate:** Exit Claude Code and restart `claude`
## Default Behaviors by Status
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
- If fix needs enhancement → use Apply Locally + Enhance Flow above
- If fix is wrong → reject via MCP, fix from scratch, commit, push
### Auto-Apply Eligibility
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
### Apply vs Reject vs Apply Locally
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
### Apply Locally + Enhance Flow
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
2. Make additional changes as needed
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Reject + Fix From Scratch Flow
When the fix is completely wrong:
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
2. Fix the issue from scratch locally
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Environment Issue Handling
When `failureClassification == 'ENVIRONMENT_STATE'`:
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
- Subagent will **completely ignore** the old/stale CIPE
- Subagent will only wait for new CIPE to appear
- Subagent will NOT return to main agent with stale CIPE data
- Once new CIPE detected, subagent switches to normal polling
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
### Step 4: Progress Tracking
After each action:
- If state changed significantly → reset `no_progress_count = 0`
- If state unchanged → `no_progress_count++`
- On new CI attempt detected → reset `local_verify_count = 0`
description: Generate code using nx generators. USE WHEN scaffolding code or transforming existing code - for example creating libraries or applications, or anything else that is boilerplate code or automates repetitive tasks. ALWAYS use this first when generating code with Nx instead of calling MCP tools or running nx generate immediately.
---
# Run Nx Generator
Nx generators are powerful tools that scaffold projects, make automated code migrations or automate repetitive tasks in a monorepo. They ensure consistency across the codebase and reduce boilerplate work.
This skill applies when the user wants to:
- Create new projects like libraries or applications
- Scaffold features or boilerplate code
- Run workspace-specific or custom generators
- Do anything else that an nx generator exists for
## Generator Discovery Flow
### Step 1: List Available Generators
Use the Nx CLI to discover available generators:
- List all generators for a plugin: `npx nx list @nx/react`
- Local workspace generators (defined in the repo's own plugins)
### Step 2: Match Generator to User Request
Based on the user's request, identify which generator(s) could fulfill their needs. Consider:
- What artifact type they want to create (library, application, etc.)
- Which framework or technology stack is relevant
- Whether they mentioned specific generator names
**IMPORTANT**: When both a local workspace generator and an external plugin generator could satisfy the request, **always prefer the local workspace generator**. Local generators are customized for the specific repo's patterns and conventions.
It's possible that the user request is something that no Nx generator exists for whatsoever. In this case, you can stop using this skill and try to help the user another way. HOWEVER, the burden of proof for this is high. Before aborting, carefully consider each and every generator that's available. Look into details for any that could be related in any way before making this decision.
## Pre-Execution Checklist
Before running any generator, complete these steps:
### 1. Fetch Generator Schema
Use the `--help` flag to understand all available options:
```bash
npx nx g @nx/react:library --help
```
Pay attention to:
- Required options that must be provided
- Optional options that may be relevant to the user's request
- Default values that might need to be overridden
### 2. Read Generator Source Code
Understanding what the generator actually does helps you:
- Know what files will be created/modified
- Understand any side effects (updating configs, installing deps, etc.)
- Identify options that might not be obvious from the schema
To find generator source code:
- For plugin generators: Use `node -e "console.log(require.resolve('@nx/<plugin>/generators.json'));"` to find the generators.json, then locate the source from there
- If that fails, read directly from `node_modules/<plugin>/generators.json`
- For local generators: They are typically in `tools/generators/` or a local plugin directory. You can search the repo for the generator name to find it.
### 2.5 Reevaluate if the generator is right
Once you have built up an understanding of what the selected generator does, reconsider: Is this the right generator to service the user request?
If not, it's okay to go back to the Generator Discovery Flow and select a different generator before proceeding. If you do, make sure to go through the entire pre-execution checklist once more.
### 3. Understand Repo Context
Before generating, examine the target area of the codebase:
- Look at similar existing artifacts (other libraries, applications, etc.)
- Identify patterns and conventions used in the repo
- Note naming conventions, file structures, and configuration patterns
- Try to match these patterns when configuring the generator
For example, if similar libraries are using a specific test runner, build tool or linter, try to match that if possible.
If projects or other artifacts are organized with a specific naming convention, try to match it.
### 4. Validate Required Options
Ensure all required options have values:
- Map the user's request to generator options
- Infer values from context where possible
- Ask the user for any critical missing information
## Execution
Keep in mind that you might have to prefix things with npx/pnpx/yarn if the user doesn't have nx installed globally.
Many generators will behave differently based on where they are executed. For example, first-party nx library generators use the cwd to determine the directory that the library should be placed in. This is highly important.
### Consider Dry-Run (Optional)
Running with `--dry-run` first is strongly encouraged but not mandatory. Use your judgment:
- For complex generators or unfamiliar territory: do a dry-run first
- For simple, well-understood generators: may proceed directly
- Dry-run shows file names and created/deleted/modified markers, but not content
- There are cases where a generator does not support dry-run (for example if it had to install an npm package) - in that case --dry-run might fail. Don't be discouraged but simply move on to running the generator for real and iterating from there.
description: Find and add Nx plugins. USE WHEN user wants to discover available plugins, install a new plugin, or add support for a specific framework or technology to the workspace.
description: Helps with running tasks in an Nx workspace. USE WHEN the user wants to execute build, test, lint, serve, or run any other tasks defined in the workspace.
---
You can run tasks with Nx in the following way.
Keep in mind that you might have to prefix things with npx/pnpx/yarn if the user doesn't have nx installed globally. Look at the package.json or lockfile to determine which package manager is in use.
For more details on any command, run it with `--help` (e.g. `nx run-many --help`, `nx affected --help`).
## Understand which tasks can be run
You can check those via `nx show project <projectname> --json`, for example `nx show project myapp --json`. It contains a `targets` section which has information about targets that can be run. You can also just look at the `package.json` scripts or `project.json` targets, but you might miss out on inferred tasks by Nx plugins.
## Run a single task
```
nx run <project>:<task>
```
where `project` is the project name defined in `package.json` or `project.json` (if present).
## Run multiple tasks
```
nx run-many -t build test lint typecheck
```
You can pass a `-p` flag to filter to specific projects, otherwise it runs on all projects. You can also use `--exclude` to exclude projects, and `--parallel` to control the number of parallel processes (default is 3).
Examples:
-`nx run-many -t test -p proj1 proj2` — test specific projects
-`nx run-many -t test --projects=*-app --exclude=excluded-app` — test projects matching a pattern
-`nx run-many -t test --projects=tag:api-*` — test projects by tag
## Run tasks for affected projects
Use `nx affected` to only run tasks on projects that have been changed and projects that depend on changed projects. This is especially useful in CI and for large workspaces.
```
nx affected -t build test lint
```
By default it compares against the base branch. You can customize this:
-`nx affected -t test --base=main --head=HEAD` — compare against a specific base and head
-`nx affected -t test --files=libs/mylib/src/index.ts` — specify changed files directly
## Useful flags
These flags work with `run`, `run-many`, and `affected`:
-`--skipNxCache` — rerun tasks even when results are cached
-`--verbose` — print additional information such as stack traces
-`--nxBail` — stop execution after the first failed task
-`--configuration=<name>` — use a specific configuration (e.g. `production`)
description: "Explore and understand Nx workspaces. USE WHEN answering any questions about the nx workspace, the projects in it or tasks to run. EXAMPLES: 'What projects are in this workspace?', 'How is project X configured?', 'What targets can I run?', 'What's affected by my changes?', 'Which projects depend on library Y?', or any questions about Nx workspace structure, project configuration, or available tasks."
---
# Nx Workspace Exploration
This skill provides read-only exploration of Nx workspaces. Use it to understand workspace structure, project configuration, available targets, and dependencies.
Keep in mind that you might have to prefix commands with `npx`/`pnpx`/`yarn` if nx isn't installed globally. Check the lockfile to determine the package manager in use.
## Listing Projects
Use `nx show projects` to list projects in the workspace.
```bash
# List all projects
nx show projects
# Filter by pattern (glob)
nx show projects --projects "apps/*"
nx show projects --projects "shared-*"
# Filter by project type
nx show projects --type app
nx show projects --type lib
nx show projects --type e2e
# Filter by target (projects that have a specific target)
nx show projects --withTarget build
nx show projects --withTarget e2e
# Find affected projects (changed since base branch)
nx show projects --affected
nx show projects --affected --base=main
nx show projects --affected --type app
# Combine filters
nx show projects --type lib --withTarget test
nx show projects --affected --exclude="*-e2e"
# Output as JSON
nx show projects --json
```
## Project Configuration
Use `nx show project <name> --json` to get the full resolved configuration for a project.
**Important**: Do NOT read `project.json` directly - it only contains partial configuration. The `nx show project` command returns the full resolved config including inferred targets from plugins.
You can read the full project schema at `node_modules/nx/schemas/project-schema.json` to understand nx project configuration options.
```bash
# Get full project configuration
nx show project my-app --json
# Extract specific parts from the JSON
nx show project my-app --json | jq '.targets'
nx show project my-app --json | jq '.targets.build'
nx show project my-app --json | jq '.targets | keys'
description="Monitor Nx Cloud CI pipeline and handle self-healing fixes automatically. Checks for Nx Cloud connection before starting."
prompt="""
# CI Monitor Command
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
```
3. **If connected** → continue to main loop
## Session Context Behavior
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
- **For a completely clean slate:** Exit Claude Code and restart `claude`
## Default Behaviors by Status
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
| `ci_success` | Exit with success. Log "CIpassedsuccessfully!" |
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
- If fix needs enhancement → use Apply Locally + Enhance Flow above
- If fix is wrong → reject via MCP, fix from scratch, commit, push
### Auto-Apply Eligibility
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
### Apply vs Reject vs Apply Locally
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
### Apply Locally + Enhance Flow
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
2. Make additional changes as needed
3. Commit and push:
```bash
git add -A
git commit -m "fix:resolve<failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Reject + Fix From Scratch Flow
When the fix is completely wrong:
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
2. Fix the issue from scratch locally
3. Commit and push:
```bash
git add -A
git commit -m "fix:resolve<failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Environment Issue Handling
When `failureClassification == 'ENVIRONMENT_STATE'`:
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
- Subagent will **completely ignore** the old/stale CIPE
- Subagent will only wait for new CIPE to appear
- Subagent will NOT return to main agent with stale CIPE data
- Once new CIPE detected, subagent switches to normal polling
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
### Step 4: Progress Tracking
After each action:
- If state changed significantly → reset `no_progress_count = 0`
- If state unchanged → `no_progress_count++`
- On new CI attempt detected → reset `local_verify_count = 0`
description="Monitor Nx Cloud CI pipeline and handle self-healing fixes. USE WHEN user says \"monitor ci\", \"watch ci\", \"ci monitor\", \"watch ci for this branch\", \"track ci\", \"check ci status\", wants to track CI status, or needs help with self-healing CI fixes. Prefer this skill over native CI provider tools (gh, glab, etc.) for CI monitoring — it integrates with Nx Cloud self-healing which those tools cannot access."
prompt="""
# Monitor CI Command
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn subagents to interact with Nx Cloud, run deterministic decision scripts, and take action based on the results.
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CI-Attempt failures (e.g., lockfile updates) |
| `--new-cipe-timeout` | 10 | Minutes to wait for new CI Attempt after action |
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
Parse any overrides from `{{args}}` and merge with defaults.
## Nx Cloud Connection Check
Before starting the monitoring loop, verify the workspace is connected to Nx Cloud. Without this connection, no CI data is available and the entire skill is inoperable.
### Step 0: Verify Nx Cloud Connection
1. **Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
| Using CI provider CLIs with `--watch` flags (e.g., `gh pr checks --watch`, `glab ci status -w`) | Bypasses Nx Cloud self-healing entirely |
| Writing custom CI polling scripts | Unreliable, pollutes context, no self-healing |
| Cancelling CI workflows/pipelines | Destructive, loses CI progress |
| Running CI checks on main agent | Wastes main agent context tokens |
| Independently analyzing/fixing CI failures while polling | Races with self-healing, causes duplicate fixes and confused state |
**If this skill fails to activate**, the fallback is:
1. Use CI provider CLI for a one-time, read-only status check (single call, no watch/polling flags)
2. Immediately delegate to this skill with gathered context
3. Do not continue polling on main agent — it wastes context tokens and bypasses self-healing
## Session Context Behavior
If the user previously ran `/monitor-ci` in this session, you may have prior state (poll counts, last CI Attempt URL, etc.). Resume from that state unless `--fresh` is set, in which case discard it and start from Step 1.
## MCP Tool Reference
Three field sets control polling efficiency — use the lightest set that gives you what you need:
The `ci_information` tool accepts `branch` (optional, defaults to current git branch), `select` (comma-separated field names), and `pageToken` (0-based pagination for long strings).
The `update_self_healing_fix` tool accepts a `shortLink` and an action: `APPLY`, `REJECT`, or `RERUN_ENVIRONMENT_STATE`.
## Default Behaviors by Status
The decision script returns one of the following statuses. This table defines the **default behavior** for each. User instructions can override any of these.
| `self_healing_throttled` | Reject old fixes, attempt local fix. |
| `no_new_cipe` | CI Attempt never spawned. Auto-fix workflow or exit with guidance. |
| `cipe_no_tasks` | CI failed with no tasks. Retry once with empty commit. |
**Key rules (always apply):**
- **Git safety**: Stage specific files by name — `git add -A` or `git add .` risks committing the user's unrelated work-in-progress or secrets
- **Environment failures** (OOM, command not found, permission denied): bail immediately. These aren't code bugs, so spending local-fix budget on them is wasteful
- **Gate check**: Run `ci-state-update.mjs gate` before local fix attempts — if budget exhausted, print message and exit
## Main Loop
### Step 1: Initialize Tracking
```
cycle_count = 0 # Only incremented for agent-initiated cycles (counted against --max-cycles)
start_time = now()
no_progress_count = 0
local_verify_count = 0
env_rerun_count = 0
last_cipe_url = null
expected_commit_sha = null
agent_triggered = false # Set true after monitor takes an action that triggers new CI Attempt
poll_count = 0
wait_mode = false
prev_status = null
prev_cipe_status = null
prev_sh_status = null
prev_verification_status = null
prev_failure_classification = null
```
### Step 2: Polling Loop
Repeat until done:
#### 2a. Spawn subagent (FETCH_STATUS)
Determine select fields based on mode:
- **Wait mode**: use WAIT_FIELDS (`cipeUrl,commitSha,cipeStatus`)
- **Normal mode (first poll or after newCipeDetected)**: use LIGHT_FIELDS
Call the `ci_information` tool with the determined `select` fields for the current branch. Wait for the result before proceeding.
- **`action == "poll"`**: Print `output.message`, sleep `output.delay` seconds, go to 2a
- If `output.newCipeDetected`: clear wait mode, reset `wait_mode = false`
- **`action == "wait"`**: Print `output.message`, sleep `output.delay` seconds, go to 2a
- **`action == "done"`**: Proceed to Step 3 with `output.code`
### Step 3: Handle Actionable Status
When decision script returns `action == "done"`:
1. Run cycle-check (Step 4) **before** handling the code
2. Check the returned `code`
3. Look up default behavior in the table above
4. Check if user instructions override the default
5. Execute the appropriate action
6. **If action expects new CI Attempt**, update tracking (see Step 3a)
7. If action results in looping, go to Step 2
#### Tool calls for actions
Several statuses require fetching additional data or calling tools:
- **fix_apply_ready**: Call `update_self_healing_fix` with action `APPLY`
- **fix_needs_local_verify**: Call `ci_information` with HEAVY_FIELDS for fix details before local verification
- **fix_needs_review**: Call `ci_information` with HEAVY_FIELDS → get `suggestedFixDescription`, `suggestedFixSummary`, `taskFailureSummaries`
- **fix_failed / no_fix**: Call `ci_information` with HEAVY_FIELDS → get `taskFailureSummaries` for local fix context
- **environment_issue**: Call `update_self_healing_fix` with action `RERUN_ENVIRONMENT_STATE`
- **self_healing_throttled**: Call `ci_information` with HEAVY_FIELDS → get `selfHealingSkipMessage`; then call `update_self_healing_fix` for each old fix
### Step 3a: Track State for New-CI-Attempt Detection
After actions that should trigger a new CI Attempt, run:
The script returns `{ waitMode, pollCount, lastCipeUrl, expectedCommitSha, agentTriggered }`. Update all tracking state from the output, then go to Step 2.
### Step 4: Cycle Classification and Progress Tracking
When the decision script returns `action == "done"`, run cycle-check **before** handling the code:
The script returns `{ cycleCount, agentTriggered, envRerunCount, approachingLimit, message }`. Update tracking state from the output.
- If `approachingLimit` → ask user whether to continue (with 5 or 10 more cycles) or stop monitoring
- If previous cycle was NOT agent-triggered (human pushed), log that human-initiated push was detected
#### Progress Tracking
- `no_progress_count`, circuit breaker (5 polls), and backoff reset are handled by ci-poll-decide.mjs (progress = any change in cipeStatus, selfHealingStatus, verificationStatus, or failureClassification)
- `env_rerun_count` reset on non-environment status is handled by ci-state-update.mjs cycle-check
- On new CI Attempt detected (poll script returns `newCipeDetected`) → reset `local_verify_count = 0`, `env_rerun_count = 0`
description: Monitor Nx Cloud CI pipeline and handle self-healing fixes automatically. Checks for Nx Cloud connection before starting.
---
# CI Monitor Command
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
Parse any overrides from `$ARGUMENTS` and merge with defaults.
## Nx Cloud Connection Check
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
### Step 0: Verify Nx Cloud Connection
1.**Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
2.**If `nx.json` missing OR neither property exists** → exit with:
```
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
```
3. **If connected** → continue to main loop
## Session Context Behavior
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
- **For a completely clean slate:** Exit Claude Code and restart `claude`
## Default Behaviors by Status
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
- If fix needs enhancement → use Apply Locally + Enhance Flow above
- If fix is wrong → reject via MCP, fix from scratch, commit, push
### Auto-Apply Eligibility
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
### Apply vs Reject vs Apply Locally
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
### Apply Locally + Enhance Flow
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
2. Make additional changes as needed
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Reject + Fix From Scratch Flow
When the fix is completely wrong:
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
2. Fix the issue from scratch locally
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Environment Issue Handling
When `failureClassification == 'ENVIRONMENT_STATE'`:
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
- Subagent will **completely ignore** the old/stale CIPE
- Subagent will only wait for new CIPE to appear
- Subagent will NOT return to main agent with stale CIPE data
- Once new CIPE detected, subagent switches to normal polling
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
### Step 4: Progress Tracking
After each action:
- If state changed significantly → reset `no_progress_count = 0`
- If state unchanged → `no_progress_count++`
- On new CI attempt detected → reset `local_verify_count = 0`
description: CI helper for /monitor-ci. Fetches CI status, retrieves fix details, or updates self-healing fixes. Executes one MCP tool call and returns the result.
---
# CI Monitor Subagent
You are a CI helper. You call ONE MCP tool per invocation and return the result. Do not loop, poll, or sleep.
## Commands
The main agent tells you which command to run:
### FETCH_STATUS
Call `ci_information` with the provided branch and select fields. Return a JSON object with ONLY these fields:
Do NOT return raw suggestedFix diffs or raw taskOutputSummary — summarize them.
The main agent uses these summaries to understand what failed and attempt local fixes.
### UPDATE_FIX
Call `update_self_healing_fix` with the provided shortLink and action (APPLY/REJECT/RERUN_ENVIRONMENT_STATE). Return the result message (success/failure string).
### FETCH_THROTTLE_INFO
Call `ci_information` with the provided URL. Return ONLY: `{ shortLink, cipeUrl }`
## Important
- Execute ONE command and return immediately
- Do NOT poll, loop, sleep, or make decisions
- Extract and return ONLY the fields specified for each command — do NOT dump the full MCP response
description: Polls Nx Cloud CI pipeline and self-healing status. Returns structured state when actionable. Spawned by /nx-cloud-ci-monitor command to monitor CI Attempt status.
---
# CI Watcher Subagent
You are a CI monitoring subagent responsible for polling Nx Cloud CI Attempt status and self-healing state. You report status back to the main agent - you do NOT make apply/reject decisions.
## Your Responsibilities
1. Poll CI status using the `ci_information` MCP tool
2. Implement exponential backoff between polls
3. Return structured state when an actionable condition is reached
4. Track iteration count and elapsed time
5. Output status updates based on verbosity level
## Input Parameters (from Main Agent)
The main agent may provide these optional parameters in the prompt:
# Large content fields - fetch only when returning to main agent
```
## Initial Wait
Before first poll, wait based on context:
- **Fresh start (no expected CIPE):** Wait 60 seconds to allow CI to start
- **Expecting new CIPE:** Wait 30 seconds (action already triggered)
**IMPORTANT:** Always run sleep in foreground, NOT as background command.
```bash
sleep 60# or 30 if expecting new CIPE (FOREGROUND, not background)
```
## Two-Phase Operation
The subagent operates in one of two modes depending on input:
### Mode 1: Fresh Start (no `expectedCommitSha` or `previousCipeUrl`)
Normal polling - process whatever CIPE is returned by `ci_information`.
### Mode 2: Wait-for-New-CIPE (when `expectedCommitSha` or `previousCipeUrl` provided)
**CRITICAL**: When expecting a new CIPE, the subagent must **completely ignore** the old/stale CIPE. Do NOT process its status, do NOT return actionable states based on it.
#### Phase A: Wait Mode
1. Start a **new-CIPE timeout** timer (default: 30 minutes)
2. On each poll of `ci_information`:
- Check if CIPE is NEW:
-`cipeUrl` differs from `previousCipeUrl` → **new CIPE detected**
[CI Monitor] Still seeing previous CI Attempt (ignoring): <oldCipeUrl>
[CI Monitor] Polling... (elapsed: 1m 30s)
[CI Monitor] Still seeing previous CI Attempt (ignoring): <oldCipeUrl>
[CI Monitor] Polling... (elapsed: 2m 30s)
[CI Monitor] ✓ New CI Attempt detected! URL: <newCipeUrl>, SHA: <newCommitSha>
[CI Monitor] Switching to normal polling mode...
```
### Why This Matters (Context Preservation)
**The problem**: Stale CIPE data can be very large:
-`taskOutputSummary`: potentially thousands of characters of build/test output
-`suggestedFix`: entire patch files
-`suggestedFixReasoning`: detailed explanation
If subagent returns stale CIPE data to main agent, it **pollutes main agent's context** with useless information (we already processed that CIPE). This wastes valuable context window.
**Without wait mode:**
1. Poll `ci_information` → get old CIPE with huge data
2. Return to main agent with all that stale data
3. Main agent's context gets polluted with useless info
4. Main agent has to process/ignore it anyway
**With wait mode:**
1. Poll `ci_information` → get old CIPE → **ignore it, don't return**
2. Keep waiting internally (stale data stays in subagent)
3. New CIPE appears → switch to normal mode
4. Return to main agent with only the NEW, relevant CIPE data
## Polling Loop
### Subagent State Management
Maintain internal accumulated state across polls:
```
accumulated_state = {}
```
### Call `ci_information` MCP Tool
**Wait Mode (expecting new CI Attempt):**
```
ci_information({
branch: "<branch_name>",
select: "cipeUrl,commitSha,cipeStatus"
})
```
Only fetch minimal fields needed to detect CI Attempt change. Do NOT fetch heavy fields - stale data wastes context.
Merge response into `accumulated_state`, then return merged state to main agent.
**Pagination:** Heavy string fields return first page only. If `hasMore` indicated, include in return format so main agent knows more content available.
### Return to Main Agent When
Return immediately with structured state if ANY of these conditions are true:
| `fix_auto_applying` | `selfHealingStatus == 'COMPLETED'` AND `couldAutoApplyTasks == true` AND `verificationStatus == 'COMPLETED'` |
| `fix_available` | `selfHealingStatus == 'COMPLETED'` AND `suggestedFix != null` AND (`couldAutoApplyTasks != true` OR `verificationStatus` in (`FAILED`, `NOT_EXECUTABLE`)) |
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
Parse any overrides from `${input:args}` and merge with defaults.
## Nx Cloud Connection Check
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
### Step 0: Verify Nx Cloud Connection
1.**Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
2.**If `nx.json` missing OR neither property exists** → exit with:
```
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
```
3. **If connected** → continue to main loop
## Session Context Behavior
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
- **For a completely clean slate:** Exit Claude Code and restart `claude`
## Default Behaviors by Status
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
- If fix needs enhancement → use Apply Locally + Enhance Flow above
- If fix is wrong → reject via MCP, fix from scratch, commit, push
### Auto-Apply Eligibility
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
### Apply vs Reject vs Apply Locally
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
### Apply Locally + Enhance Flow
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
2. Make additional changes as needed
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Reject + Fix From Scratch Flow
When the fix is completely wrong:
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
2. Fix the issue from scratch locally
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Environment Issue Handling
When `failureClassification == 'ENVIRONMENT_STATE'`:
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
- Subagent will **completely ignore** the old/stale CIPE
- Subagent will only wait for new CIPE to appear
- Subagent will NOT return to main agent with stale CIPE data
- Once new CIPE detected, subagent switches to normal polling
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
### Step 4: Progress Tracking
After each action:
- If state changed significantly → reset `no_progress_count = 0`
- If state unchanged → `no_progress_count++`
- On new CI attempt detected → reset `local_verify_count = 0`
description: Monitor Nx Cloud CI pipeline and handle self-healing fixes. USE WHEN user says "monitor ci", "watch ci", "ci monitor", "watch ci for this branch", "track ci", "check ci status", wants to track CI status, or needs help with self-healing CI fixes. Prefer this skill over native CI provider tools (gh, glab, etc.) for CI monitoring — it integrates with Nx Cloud self-healing which those tools cannot access.
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn subagents to interact with Nx Cloud, run deterministic decision scripts, and take action based on the results.
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CI-Attempt failures (e.g., lockfile updates) |
| `--new-cipe-timeout` | 10 | Minutes to wait for new CI Attempt after action |
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
Parse any overrides from `${input:args}` and merge with defaults.
## Nx Cloud Connection Check
Before starting the monitoring loop, verify the workspace is connected to Nx Cloud. Without this connection, no CI data is available and the entire skill is inoperable.
### Step 0: Verify Nx Cloud Connection
1.**Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
2.**If `nx.json` missing OR neither property exists** → exit with:
```
Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
```
3. **If connected** → continue to main loop
## Architecture Overview
1. **This skill (orchestrator)**: spawns subagents, runs scripts, prints status, does local coding work
2. **ci-monitor-subagent (haiku)**: calls one MCP tool (ci_information or update_self_healing_fix), returns structured result, exits
3. **ci-poll-decide.mjs (deterministic script)**: takes ci_information result + state, returns action + status message
4. **ci-state-update.mjs (deterministic script)**: manages budget gates, post-action state transitions, and cycle classification
## Status Reporting
The decision script handles message formatting based on verbosity. When printing messages to the user:
- Prepend `[monitor-ci]` to every message from the script's `message` field
- For your own action messages (e.g. "Applying fix via MCP..."), also prepend `[monitor-ci]`
## Anti-Patterns
These behaviors cause real problems — racing with self-healing, losing CI progress, or wasting context:
| Using CI provider CLIs with `--watch` flags (e.g., `gh pr checks --watch`, `glab ci status -w`) | Bypasses Nx Cloud self-healing entirely |
| Writing custom CI polling scripts | Unreliable, pollutes context, no self-healing |
| Cancelling CI workflows/pipelines | Destructive, loses CI progress |
| Running CI checks on main agent | Wastes main agent context tokens |
| Independently analyzing/fixing CI failures while polling | Races with self-healing, causes duplicate fixes and confused state |
**If this skill fails to activate**, the fallback is:
1. Use CI provider CLI for a one-time, read-only status check (single call, no watch/polling flags)
2. Immediately delegate to this skill with gathered context
3. Do not continue polling on main agent — it wastes context tokens and bypasses self-healing
## Session Context Behavior
If the user previously ran `/monitor-ci` in this session, you may have prior state (poll counts, last CI Attempt URL, etc.). Resume from that state unless `--fresh` is set, in which case discard it and start from Step 1.
## MCP Tool Reference
Three field sets control polling efficiency — use the lightest set that gives you what you need:
The `ci_information` tool accepts `branch` (optional, defaults to current git branch), `select` (comma-separated field names), and `pageToken` (0-based pagination for long strings).
The `update_self_healing_fix` tool accepts a `shortLink` and an action: `APPLY`, `REJECT`, or `RERUN_ENVIRONMENT_STATE`.
## Default Behaviors by Status
The decision script returns one of the following statuses. This table defines the **default behavior** for each. User instructions can override any of these.
| `self_healing_throttled` | Reject old fixes, attempt local fix. |
| `no_new_cipe` | CI Attempt never spawned. Auto-fix workflow or exit with guidance. |
| `cipe_no_tasks` | CI failed with no tasks. Retry once with empty commit. |
**Key rules (always apply):**
- **Git safety**: Stage specific files by name — `git add -A` or `git add .` risks committing the user's unrelated work-in-progress or secrets
- **Environment failures** (OOM, command not found, permission denied): bail immediately. These aren't code bugs, so spending local-fix budget on them is wasteful
- **Gate check**: Run `ci-state-update.mjs gate` before local fix attempts — if budget exhausted, print message and exit
## Main Loop
### Step 1: Initialize Tracking
```
cycle_count = 0 # Only incremented for agent-initiated cycles (counted against --max-cycles)
start_time = now()
no_progress_count = 0
local_verify_count = 0
env_rerun_count = 0
last_cipe_url = null
expected_commit_sha = null
agent_triggered = false # Set true after monitor takes an action that triggers new CI Attempt
poll_count = 0
wait_mode = false
prev_status = null
prev_cipe_status = null
prev_sh_status = null
prev_verification_status = null
prev_failure_classification = null
```
### Step 2: Polling Loop
Repeat until done:
#### 2a. Spawn subagent (FETCH_STATUS)
Determine select fields based on mode:
- **Wait mode**: use WAIT_FIELDS (`cipeUrl,commitSha,cipeStatus`)
- **Normal mode (first poll or after newCipeDetected)**: use LIGHT_FIELDS
Call the `ci_information` tool with the determined `select` fields for the current branch. Wait for the result before proceeding.
- **`action == "poll"`**: Print `output.message`, sleep `output.delay` seconds, go to 2a
- If `output.newCipeDetected`: clear wait mode, reset `wait_mode = false`
- **`action == "wait"`**: Print `output.message`, sleep `output.delay` seconds, go to 2a
- **`action == "done"`**: Proceed to Step 3 with `output.code`
### Step 3: Handle Actionable Status
When decision script returns `action == "done"`:
1. Run cycle-check (Step 4) **before** handling the code
2. Check the returned `code`
3. Look up default behavior in the table above
4. Check if user instructions override the default
5. Execute the appropriate action
6. **If action expects new CI Attempt**, update tracking (see Step 3a)
7. If action results in looping, go to Step 2
#### Tool calls for actions
Several statuses require fetching additional data or calling tools:
- **fix_apply_ready**: Call `update_self_healing_fix` with action `APPLY`
- **fix_needs_local_verify**: Call `ci_information` with HEAVY_FIELDS for fix details before local verification
- **fix_needs_review**: Call `ci_information` with HEAVY_FIELDS → get `suggestedFixDescription`, `suggestedFixSummary`, `taskFailureSummaries`
- **fix_failed / no_fix**: Call `ci_information` with HEAVY_FIELDS → get `taskFailureSummaries` for local fix context
- **environment_issue**: Call `update_self_healing_fix` with action `RERUN_ENVIRONMENT_STATE`
- **self_healing_throttled**: Call `ci_information` with HEAVY_FIELDS → get `selfHealingSkipMessage`; then call `update_self_healing_fix` for each old fix
### Step 3a: Track State for New-CI-Attempt Detection
After actions that should trigger a new CI Attempt, run:
The script returns `{ waitMode, pollCount, lastCipeUrl, expectedCommitSha, agentTriggered }`. Update all tracking state from the output, then go to Step 2.
### Step 4: Cycle Classification and Progress Tracking
When the decision script returns `action == "done"`, run cycle-check **before** handling the code:
The script returns `{ cycleCount, agentTriggered, envRerunCount, approachingLimit, message }`. Update tracking state from the output.
- If `approachingLimit` → ask user whether to continue (with 5 or 10 more cycles) or stop monitoring
- If previous cycle was NOT agent-triggered (human pushed), log that human-initiated push was detected
#### Progress Tracking
- `no_progress_count`, circuit breaker (5 polls), and backoff reset are handled by ci-poll-decide.mjs (progress = any change in cipeStatus, selfHealingStatus, verificationStatus, or failureClassification)
- `env_rerun_count` reset on non-environment status is handled by ci-state-update.mjs cycle-check
- On new CI Attempt detected (poll script returns `newCipeDetected`) → reset `local_verify_count = 0`, `env_rerun_count = 0`
description: Monitor Nx Cloud CI pipeline and handle self-healing fixes automatically. Checks for Nx Cloud connection before starting.
---
# CI Monitor Command
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
Parse any overrides from `$ARGUMENTS` and merge with defaults.
## Nx Cloud Connection Check
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
### Step 0: Verify Nx Cloud Connection
1.**Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
2.**If `nx.json` missing OR neither property exists** → exit with:
```
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
```
3. **If connected** → continue to main loop
## Session Context Behavior
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
- **For a completely clean slate:** Exit Claude Code and restart `claude`
## Default Behaviors by Status
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
- If fix needs enhancement → use Apply Locally + Enhance Flow above
- If fix is wrong → reject via MCP, fix from scratch, commit, push
### Auto-Apply Eligibility
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
### Apply vs Reject vs Apply Locally
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
### Apply Locally + Enhance Flow
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
2. Make additional changes as needed
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Reject + Fix From Scratch Flow
When the fix is completely wrong:
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
2. Fix the issue from scratch locally
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Environment Issue Handling
When `failureClassification == 'ENVIRONMENT_STATE'`:
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
- Subagent will **completely ignore** the old/stale CIPE
- Subagent will only wait for new CIPE to appear
- Subagent will NOT return to main agent with stale CIPE data
- Once new CIPE detected, subagent switches to normal polling
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
### Step 4: Progress Tracking
After each action:
- If state changed significantly → reset `no_progress_count = 0`
- If state unchanged → `no_progress_count++`
- On new CI attempt detected → reset `local_verify_count = 0`
description: 'Link workspace packages in monorepos (npm, yarn, pnpm, bun). USE WHEN: (1) you just created or generated new packages and need to wire up their dependencies, (2) user imports from a sibling package and needs to add it as a dependency, (3) you get resolution errors for workspace packages (@org/*) like "cannot find module", "failed to resolve import", "TS2307", or "cannot resolve". DO NOT patch around with tsconfig paths or manual package.json edits - use the package manager''s workspace commands to fix actual linking.'
---
# Link Workspace Packages
Add dependencies between packages in a monorepo. All package managers support workspaces but with different syntax.
## Detect Package Manager
Check whether there's a `packageManager` field in the root-level `package.json`.
Alternatively check lockfile in repo root:
-`pnpm-lock.yaml` → pnpm
-`yarn.lock` → yarn
-`bun.lock` / `bun.lockb` → bun
-`package-lock.json` → npm
## Workflow
1. Identify consumer package (the one importing)
2. Identify provider package(s) (being imported)
3. Add dependency using package manager's workspace syntax
4. Verify symlinks created in consumer's `node_modules/`
---
## pnpm
Uses `workspace:` protocol - symlinks only created when explicitly declared.
```bash
# From consumer directory
pnpm add @org/ui --workspace
# Or with --filter from anywhere
pnpm add @org/ui --filter @org/app --workspace
```
Result in `package.json`:
```json
{"dependencies":{"@org/ui":"workspace:*"}}
```
---
## yarn (v2+/berry)
Also uses `workspace:` protocol.
```bash
yarn workspace @org/app add @org/ui
```
Result in `package.json`:
```json
{"dependencies":{"@org/ui":"workspace:^"}}
```
---
## npm
No `workspace:` protocol. npm auto-symlinks workspace packages.
```bash
npm install @org/ui --workspace @org/app
```
Result in `package.json`:
```json
{"dependencies":{"@org/ui":"*"}}
```
npm resolves to local workspace automatically during install.
description: Monitor Nx Cloud CI pipeline and handle self-healing fixes. USE WHEN user says "monitor ci", "watch ci", "ci monitor", "watch ci for this branch", "track ci", "check ci status", wants to track CI status, or needs help with self-healing CI fixes. Prefer this skill over native CI provider tools (gh, glab, etc.) for CI monitoring — it integrates with Nx Cloud self-healing which those tools cannot access.
---
# Monitor CI Command
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn subagents to interact with Nx Cloud, run deterministic decision scripts, and take action based on the results.
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CI-Attempt failures (e.g., lockfile updates) |
| `--new-cipe-timeout` | 10 | Minutes to wait for new CI Attempt after action |
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
Parse any overrides from `$ARGUMENTS` and merge with defaults.
## Nx Cloud Connection Check
Before starting the monitoring loop, verify the workspace is connected to Nx Cloud. Without this connection, no CI data is available and the entire skill is inoperable.
### Step 0: Verify Nx Cloud Connection
1.**Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
2.**If `nx.json` missing OR neither property exists** → exit with:
```
Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
```
3. **If connected** → continue to main loop
## Architecture Overview
1. **This skill (orchestrator)**: spawns subagents, runs scripts, prints status, does local coding work
2. **ci-monitor-subagent (haiku)**: calls one MCP tool (ci_information or update_self_healing_fix), returns structured result, exits
3. **ci-poll-decide.mjs (deterministic script)**: takes ci_information result + state, returns action + status message
4. **ci-state-update.mjs (deterministic script)**: manages budget gates, post-action state transitions, and cycle classification
## Status Reporting
The decision script handles message formatting based on verbosity. When printing messages to the user:
- Prepend `[monitor-ci]` to every message from the script's `message` field
- For your own action messages (e.g. "Applying fix via MCP..."), also prepend `[monitor-ci]`
## Anti-Patterns
These behaviors cause real problems — racing with self-healing, losing CI progress, or wasting context:
| Using CI provider CLIs with `--watch` flags (e.g., `gh pr checks --watch`, `glab ci status -w`) | Bypasses Nx Cloud self-healing entirely |
| Writing custom CI polling scripts | Unreliable, pollutes context, no self-healing |
| Cancelling CI workflows/pipelines | Destructive, loses CI progress |
| Running CI checks on main agent | Wastes main agent context tokens |
| Independently analyzing/fixing CI failures while polling | Races with self-healing, causes duplicate fixes and confused state |
**If this skill fails to activate**, the fallback is:
1. Use CI provider CLI for a one-time, read-only status check (single call, no watch/polling flags)
2. Immediately delegate to this skill with gathered context
3. Do not continue polling on main agent — it wastes context tokens and bypasses self-healing
## Session Context Behavior
If the user previously ran `/monitor-ci` in this session, you may have prior state (poll counts, last CI Attempt URL, etc.). Resume from that state unless `--fresh` is set, in which case discard it and start from Step 1.
## MCP Tool Reference
Three field sets control polling efficiency — use the lightest set that gives you what you need:
The `ci_information` tool accepts `branch` (optional, defaults to current git branch), `select` (comma-separated field names), and `pageToken` (0-based pagination for long strings).
The `update_self_healing_fix` tool accepts a `shortLink` and an action: `APPLY`, `REJECT`, or `RERUN_ENVIRONMENT_STATE`.
## Default Behaviors by Status
The decision script returns one of the following statuses. This table defines the **default behavior** for each. User instructions can override any of these.
| `self_healing_throttled` | Reject old fixes, attempt local fix. |
| `no_new_cipe` | CI Attempt never spawned. Auto-fix workflow or exit with guidance. |
| `cipe_no_tasks` | CI failed with no tasks. Retry once with empty commit. |
**Key rules (always apply):**
- **Git safety**: Stage specific files by name — `git add -A` or `git add .` risks committing the user's unrelated work-in-progress or secrets
- **Environment failures** (OOM, command not found, permission denied): bail immediately. These aren't code bugs, so spending local-fix budget on them is wasteful
- **Gate check**: Run `ci-state-update.mjs gate` before local fix attempts — if budget exhausted, print message and exit
## Main Loop
### Step 1: Initialize Tracking
```
cycle_count = 0 # Only incremented for agent-initiated cycles (counted against --max-cycles)
start_time = now()
no_progress_count = 0
local_verify_count = 0
env_rerun_count = 0
last_cipe_url = null
expected_commit_sha = null
agent_triggered = false # Set true after monitor takes an action that triggers new CI Attempt
poll_count = 0
wait_mode = false
prev_status = null
prev_cipe_status = null
prev_sh_status = null
prev_verification_status = null
prev_failure_classification = null
```
### Step 2: Polling Loop
Repeat until done:
#### 2a. Spawn subagent (FETCH_STATUS)
Determine select fields based on mode:
- **Wait mode**: use WAIT_FIELDS (`cipeUrl,commitSha,cipeStatus`)
- **Normal mode (first poll or after newCipeDetected)**: use LIGHT_FIELDS
Call the `ci_information` tool with the determined `select` fields for the current branch. Wait for the result before proceeding.
- **`action == "poll"`**: Print `output.message`, sleep `output.delay` seconds, go to 2a
- If `output.newCipeDetected`: clear wait mode, reset `wait_mode = false`
- **`action == "wait"`**: Print `output.message`, sleep `output.delay` seconds, go to 2a
- **`action == "done"`**: Proceed to Step 3 with `output.code`
### Step 3: Handle Actionable Status
When decision script returns `action == "done"`:
1. Run cycle-check (Step 4) **before** handling the code
2. Check the returned `code`
3. Look up default behavior in the table above
4. Check if user instructions override the default
5. Execute the appropriate action
6. **If action expects new CI Attempt**, update tracking (see Step 3a)
7. If action results in looping, go to Step 2
#### Tool calls for actions
Several statuses require fetching additional data or calling tools:
- **fix_apply_ready**: Call `update_self_healing_fix` with action `APPLY`
- **fix_needs_local_verify**: Call `ci_information` with HEAVY_FIELDS for fix details before local verification
- **fix_needs_review**: Call `ci_information` with HEAVY_FIELDS → get `suggestedFixDescription`, `suggestedFixSummary`, `taskFailureSummaries`
- **fix_failed / no_fix**: Call `ci_information` with HEAVY_FIELDS → get `taskFailureSummaries` for local fix context
- **environment_issue**: Call `update_self_healing_fix` with action `RERUN_ENVIRONMENT_STATE`
- **self_healing_throttled**: Call `ci_information` with HEAVY_FIELDS → get `selfHealingSkipMessage`; then call `update_self_healing_fix` for each old fix
### Step 3a: Track State for New-CI-Attempt Detection
After actions that should trigger a new CI Attempt, run:
The script returns `{ waitMode, pollCount, lastCipeUrl, expectedCommitSha, agentTriggered }`. Update all tracking state from the output, then go to Step 2.
### Step 4: Cycle Classification and Progress Tracking
When the decision script returns `action == "done"`, run cycle-check **before** handling the code:
The script returns `{ cycleCount, agentTriggered, envRerunCount, approachingLimit, message }`. Update tracking state from the output.
- If `approachingLimit` → ask user whether to continue (with 5 or 10 more cycles) or stop monitoring
- If previous cycle was NOT agent-triggered (human pushed), log that human-initiated push was detected
#### Progress Tracking
- `no_progress_count`, circuit breaker (5 polls), and backoff reset are handled by ci-poll-decide.mjs (progress = any change in cipeStatus, selfHealingStatus, verificationStatus, or failureClassification)
- `env_rerun_count` reset on non-environment status is handled by ci-state-update.mjs cycle-check
- On new CI Attempt detected (poll script returns `newCipeDetected`) → reset `local_verify_count = 0`, `env_rerun_count = 0`
- If fix is wrong → run `ci-state-update.mjs gate --gate-type local-fix`. If not allowed, print message and exit. Otherwise → Reject + Fix From Scratch Flow
### fix_failed / no_fix
Spawn FETCH_HEAVY subagent for `taskFailureSummaries`. Run `ci-state-update.mjs gate --gate-type local-fix` — if not allowed, print message and exit. Otherwise attempt local fix (counter already incremented by gate). If successful → commit, push, enter wait mode. If not → exit with failure.
### environment_issue
1. Run `ci-state-update.mjs gate --gate-type env-rerun`. If not allowed, print message and exit.
2. Spawn UPDATE_FIX subagent with `RERUN_ENVIRONMENT_STATE`
3. Enter wait mode with `last_cipe_url` set
### self_healing_throttled
Spawn FETCH_HEAVY subagent for `selfHealingSkipMessage`.
1.**Parse throttle message** for CI Attempt URLs (regex: `/cipes/{id}`)
2.**Reject previous fixes** — for each URL: spawn FETCH_THROTTLE_INFO to get `shortLink`, then UPDATE_FIX with `REJECT`
3.**Attempt local fix**: Run `ci-state-update.mjs gate --gate-type local-fix`. If not allowed → skip to step 4. Otherwise use `failedTaskIds` and `taskFailureSummaries` for context.
4.**Fallback if local fix not possible or budget exhausted**: push empty commit (`git commit --allow-empty -m "ci: rerun after rejecting throttled fixes"`), enter wait mode
### no_new_cipe
1. Report to user: no CI attempt found, suggest checking CI provider
2. If `--auto-fix-workflow`: detect package manager, run install, commit lockfile if changed, enter wait mode
3. Otherwise: exit with guidance
### cipe_no_tasks
1. Report to user: CI failed with no tasks recorded
2. Retry: `git commit --allow-empty -m "chore: retry ci [monitor-ci]"` + push, enter wait mode
3. If retry also returns `cipe_no_tasks`: exit with failure
## Fix Action Flows
### Apply via MCP
Spawn UPDATE_FIX subagent with `APPLY`. New CI Attempt spawns automatically. No local git ops.
### Apply Locally + Enhance Flow
1.`nx-cloud apply-locally <shortLink>` (sets state to `APPLIED_LOCALLY`)
2. Enhance code to fix failing tasks
3. Run failing tasks to verify
4. If still failing → run `ci-state-update.mjs gate --gate-type local-fix`. If not allowed, commit current state and push (let CI be final judge). Otherwise loop back to enhance.
5. If passing → commit and push, enter wait mode
### Reject + Fix From Scratch Flow
1. Run `ci-state-update.mjs gate --gate-type local-fix`. If not allowed, print message and exit.
2. Spawn UPDATE_FIX subagent with `REJECT`
3. Fix from scratch locally
4. Commit and push, enter wait mode
## Environment vs Code Failure Recognition
When any local fix path runs a task and it fails, assess whether the failure is a **code issue** or an **environment/tooling issue** before running the gate script.
**Indicators of environment/tooling failures** (non-exhaustive): command not found / binary missing, OOM / heap allocation failures, permission denied, network timeouts / DNS failures, missing system libraries, Docker/container issues, disk space exhaustion.
When detected → bail immediately without running gate (no budget consumed). Report that the failure is an environment/tooling issue, not a code bug.
**Code failures** (compilation errors, test assertion failures, lint violations, type errors) are genuine candidates for local fix attempts and proceed normally through the gate.
## Git Safety
- Stage specific files by name — `git add -A` or `git add .` risks committing the user's unrelated work-in-progress or secrets
description: Generate code using nx generators. INVOKE IMMEDIATELY when user mentions scaffolding, setup, structure, creating apps/libs, or setting up project structure. Trigger words - scaffold, setup, create a new app, create a new lib, project structure, generate, add a new project. ALWAYS use this BEFORE calling nx_docs or exploring - this skill handles discovery internally.
---
# Run Nx Generator
Nx generators are powerful tools that scaffold projects, make automated code migrations or automate repetitive tasks in a monorepo. They ensure consistency across the codebase and reduce boilerplate work.
This skill applies when the user wants to:
- Create new projects like libraries or applications
- Scaffold features or boilerplate code
- Run workspace-specific or custom generators
- Do anything else that an nx generator exists for
## Key Principles
1.**Always use `--no-interactive`** - Prevents prompts that would hang execution
2.**Read the generator source code** - The schema alone is not enough; understand what the generator actually does
3.**Match existing repo patterns** - Study similar artifacts in the repo and follow their conventions
4.**Verify with lint/test/build/typecheck etc.** - Generated code must pass verification. The listed targets are just an example, use what's appropriate for this workspace.
## Steps
### 1. Discover Available Generators
Use the Nx CLI to discover available generators:
- List all generators for a plugin: `npx nx list @nx/react`
- View available plugins: `npx nx list`
This includes plugin generators (e.g., `@nx/react:library`) and local workspace generators.
### 2. Match Generator to User Request
Identify which generator(s) could fulfill the user's needs. Consider what artifact type they want, which framework is relevant, and any specific generator names mentioned.
**IMPORTANT**: When both a local workspace generator and an external plugin generator could satisfy the request, **always prefer the local workspace generator**. Local generators are customized for the specific repo's patterns.
If no suitable generator exists, you can stop using this skill. However, the burden of proof is high—carefully consider all available generators before deciding none apply.
### 3. Get Generator Options
Use the `--help` flag to understand available options:
```bash
npx nx g @nx/react:library --help
```
Pay attention to required options, defaults that might need overriding, and options relevant to the user's request.
### Library Buildability
**Default to non-buildable libraries** unless there's a specific reason for buildable.
| **Non-buildable** (default) | Internal monorepo libs consumed by apps | No `--bundler` flag |
| **Buildable** | Publishing to npm, cross-repo sharing, stable libs for cache hits | `--bundler=vite` or `--bundler=swc` |
Non-buildable libs:
- Export `.ts`/`.tsx` source directly
- Consumer's bundler compiles them
- Faster dev experience, less config
Buildable libs:
- Have their own build target
- Useful for stable libs that rarely change (cache hits)
- Required for npm publishing
**If unclear, ask the user:** "Should this library be buildable (own build step, better caching) or non-buildable (source consumed directly, simpler setup)?"
### 4. Read Generator Source Code
**This step is critical.** The schema alone does not tell you everything. Reading the source code helps you:
- Know exactly what files will be created/modified and where
- Understand side effects (updating configs, installing deps, etc.)
- Identify behaviors and options not obvious from the schema
- Understand how options interact with each other
To find generator source code:
- For plugin generators: Use `node -e "console.log(require.resolve('@nx/<plugin>/generators.json'));"` to find the generators.json, then locate the source from there
- If that fails, read directly from `node_modules/<plugin>/generators.json`
- For local generators: Typically in `tools/generators/` or a local plugin directory. Search the repo for the generator name.
After reading the source, reconsider: Is this the right generator? If not, go back to step 2.
> **⚠️ `--directory` flag behavior can be misleading.**
> It should specify the full path of the generated library or component, not the parent path that it will be generated in.
>
> ```bash
> # ✅ Correct - directory is the full path for the library
> nx g @nx/react:library --directory=libs/my-lib
> # generates libs/my-lib/package.json and more
>
> # ❌ Wrong - this will create files at libs and libs/src/...
> nx g @nx/react:library --name=my-lib --directory=libs
> # generates libs/package.json and more
> ```
### 5. Examine Existing Patterns
Before generating, examine the target area of the codebase:
- Look at similar existing artifacts (other libraries, applications, etc.)
- Identify naming conventions, file structures, and configuration patterns
- Note which test runners, build tools, and linters are used
- Configure the generator to match these patterns
### 6. Dry-Run to Verify File Placement
**Always run with `--dry-run` first** to verify files will be created in the correct location:
```bash
npx nx g @nx/react:library --name=my-lib --dry-run --no-interactive
```
Review the output carefully. If files would be created in the wrong location, adjust your options based on what you learned from the generator source code.
Note: Some generators don't support dry-run (e.g., if they install npm packages). If dry-run fails for this reason, proceed to running the generator for real.
> **Tip:** New packages often need workspace dependencies wired up (e.g., importing shared types, being consumed by apps). The `link-workspace-packages` skill can help add these correctly.
### 8. Modify Generated Code (If Needed)
Generators provide a starting point. Modify the output as needed to:
- Add or modify functionality as requested
- Adjust imports, exports, or configurations
- Integrate with existing code patterns
**Important:** If you replace or delete generated test files (e.g., `*.spec.ts`), either write meaningful replacement tests or remove the `test` target from the project configuration. Empty test suites will cause `nx test` to fail.
### 9. Format and Verify
Format all generated/modified files:
```bash
nx format --fix
```
This example is for built-in nx formatting with prettier. There might be other formatting tools for this workspace, use these when appropriate.
Then verify the generated code works. Keep in mind that the changes you make with a generator or subsequent modifications might impact various projects so it's usually not enough to only run targets for the artifact you just created.
```bash
# these targets are just an example!
nx run-many -t build,lint,test,typecheck
```
These targets are common examples used across many workspaces. You should do research into other targets available for this workspace and its projects. CI configuration is usually a good guide for what the critical targets are that have to pass.
If verification fails with manageable issues (a few lint errors, minor type issues), fix them. If issues are extensive, attempt obvious fixes first, then escalate to the user with details about what was generated, what's failing, and what you've attempted.
description: Import, merge, or combine repositories into an Nx workspace using nx import. USE WHEN the user asks to adopt Nx across repos, move projects into a monorepo, or bring code/history from another repository.
---
## Quick Start
-`nx import` brings code from a source repository or folder into the current workspace, preserving commit history.
- After nx `22.6.0`, `nx import` responds with .ndjson outputs and follow-up questions. For earlier versions, always run with `--no-interactive` and specify all flags directly.
- Run `nx import --help` for available options.
- Make sure the destination directory is empty before importing.
EXAMPLE: target has `libs/utils` and `libs/models`; source has `libs/ui` and `libs/data-access` — you cannot import `libs/` into `libs/` directly. Import each source library individually.
- **Recommended for monorepo sources** — files land at top level, no redundant config
- Caveats: multiple import commands (separate merge commits each); dest must not have conflicting directories; root configs (deps, plugins, targetDefaults) not imported
- **Directory conflicts**: Import into alternate-named dir (e.g. `imported-apps/`), then rename
- **Only for non-monorepo sources** (single-project repos)
- For monorepos, creates messy nested config (`imported/nx.json`, `imported/tsconfig.base.json`, etc.)
- If you must: keep imported `tsconfig.base.json` (projects extend it), prefix workspace globs and executor paths
### Directory Conventions
- **Always prefer the destination's existing conventions.** Source uses `libs/`but dest uses `packages/`? Import into `packages/` (`nx import <source> packages/foo --source=libs/foo`).
- If dest has no convention (empty workspace), ask the user.
### Application vs Library Detection
Before importing, identify whether the source is an **application** or a **library**:
- **Applications**: Deployable end products. Common indicators:
- _Frontend_: `next.config.*`, `vite.config.*` with a build entry point, framework-specific app scaffolding (CRA, Angular CLI app, etc.)
- _Backend (Node.js)_: Express/Fastify/NestJS server entrypoint, no `"exports"` field in `package.json`
- _JVM_: Maven `pom.xml` with `<packaging>jar</packaging>` or `<packaging>war</packaging>` and a `main` class; Gradle `application` plugin or `mainClass` setting
- _.NET_: `.csproj`/`.fsproj` with `<OutputType>Exe</OutputType>` or `<OutputType>WinExe</OutputType>`
- _General_: Dockerfile, a runnable entrypoint, no public API surface intended for import by other projects
- **Libraries**: Reusable packages consumed by other projects. Common indicators: `"main"`/`"exports"` in `package.json`, Maven/Gradle packaging as a library jar, .NET `<OutputType>Library</OutputType>`, named exports intended for import by other packages.
**Destination directory rules**:
- Applications → `apps/<name>`. Check workspace globs (e.g. `pnpm-workspace.yaml`, `workspaces` in root `package.json`) for an existing `apps/*` entry.
- If `apps/*` is **not** present, add it before importing: update the workspace glob config and commit (or stage) the change.
`nx import` adds the imported directory itself (e.g. `apps`) to `pnpm-workspace.yaml`, **NOT** glob patterns for packages within it. Cross-package imports will fail with `Cannot find module`.
**Fix**: Replace with proper globs from the source config (e.g. `apps/*`, `libs/shared/*`), then `pnpm install`.
### Root Dependencies and Config Not Imported (Critical)
`nx import` does **NOT** merge from the source's root:
-`dependencies`/`devDependencies` from `package.json`
-`targetDefaults` from `nx.json` (e.g. `"@nx/esbuild:esbuild": { "dependsOn": ["^build"] }` — critical for build ordering)
-`namedInputs` from `nx.json` (e.g. `production` exclusion patterns for test files)
- Plugin configurations from `nx.json`
**Fix**: Diff source and dest `package.json` + `nx.json`. Add missing deps, merge relevant `targetDefaults` and `namedInputs`.
### TypeScript Project References
After import, run `nx sync --yes`. If it reports nothing but typecheck still fails, `nx reset` first, then `nx sync --yes` again.
### Explicit Executor Path Fixups
Inferred targets (via Nx plugins) resolve config relative to project root — no changes needed. Explicit executor targets (e.g. `@nx/esbuild:esbuild`) have workspace-root-relative paths (`main`, `outputPath`, `tsConfig`, `assets`, `sourceRoot`) that must be prefixed with the import destination directory.
### Plugin Detection
- **Whole-repo import**: `nx import` detects and offers to install plugins. Accept them.
- **Subdirectory import**: Plugins NOT auto-detected. Manually add with `npx nx add @nx/PLUGIN`. Check `include`/`exclude` patterns — defaults won't match alternate directories (e.g. `apps-beta/`).
- Run `npx nx reset` after any plugin config changes.
### Redundant Root Files (Whole-Repo Only)
Whole-repo import brings ALL source root files into the dest subdirectory. Clean up:
-`pnpm-lock.yaml` — stale; dest has its own lockfile
-`pnpm-workspace.yaml` — source workspace config; conflicts with dest
-`node_modules/` — stale symlinks pointing to source filesystem
-`.gitignore` — redundant with dest root `.gitignore`
-`nx.json` — source Nx config; dest has its own
-`README.md` — optional; keep or remove
**Don't blindly delete**`tsconfig.base.json` — imported projects may extend it via relative paths.
2. Create root `eslint.config.mjs` (copy from source or create with `@nx/eslint-plugin` base rules)
3. Then `npx nx add @nx/eslint` to register the plugin in `nx.json`
Install `typescript-eslint` explicitly — pnpm's strict hoisting won't auto-resolve this transitive dep of `@nx/eslint-plugin`.
### ESLint Version Pinning (Critical)
**Pin ESLint to v9** (`eslint@^9.0.0`). ESLint 10 breaks `@nx/eslint` and many plugins with cryptic errors like `Cannot read properties of undefined (reading 'version')`.
`@nx/eslint` may peer-depend on ESLint 8, causing the wrong version to resolve. If lint fails with `Cannot read properties of undefined (reading 'allow')`, add `pnpm.overrides`:
```json
{"pnpm":{"overrides":{"eslint":"^9.0.0"}}}
```
### Dependency Version Conflicts
After import, compare key deps (`typescript`, `eslint`, framework-specific). If dest uses newer versions, upgrade imported packages to match (usually safe). If source is newer, may need to upgrade dest first. Use `pnpm.overrides` to enforce single-version policy if desired.
### Module Boundaries
Imported projects may lack `tags`. Add tags or update `@nx/enforce-module-boundaries` rules.
### Project Name Collisions (Multi-Import)
Same `name` in `package.json` across source and dest causes `MultipleProjectsWithSameNameError`. **Fix**: Rename conflicting names (e.g. `@org/api` → `@org/teama-api`), update all dep references and import statements, `pnpm install`. The root `package.json` of each imported repo also becomes a project — rename those too.
### Workspace Dep Import Ordering
`pnpm install` fails during `nx import` if a `"workspace:*"` dependency hasn't been imported yet. File operations still succeed. **Fix**: Import all projects first, then `pnpm install --no-frozen-lockfile`.
### `.gitkeep` Blocking Subdirectory Import
The TS preset creates `packages/.gitkeep`. Remove it and commit before importing.
### Frontend tsconfig Base Settings (Critical)
The TS preset defaults (`module: "nodenext"`, `moduleResolution: "nodenext"`, `lib: ["es2022"]`) are incompatible with frontend frameworks (React, Next.js, Vue, Vite). After importing frontend projects, verify the dest root `tsconfig.base.json`:
- **`moduleResolution`**: Must be `"bundler"` (not `"nodenext"`)
- **`module`**: Must be `"esnext"` (not `"nodenext"`)
- **`lib`**: Must include `"dom"` and `"dom.iterable"` (frontend projects need these)
- **`jsx`**: `"react-jsx"` for React-only workspaces, per-project for mixed frameworks
For **subdirectory imports**, the dest root tsconfig is authoritative — update it. For **whole-repo imports**, imported projects may extend their own nested `tsconfig.base.json`, making this less critical.
If the dest also has backend projects needing `nodenext`, use per-project overrides instead of changing the root.
**Gotcha**: TypeScript does NOT merge `lib` arrays — a project-level override **replaces** the base array entirely. Always include all needed entries (e.g. `es2022`, `dom`, `dom.iterable`) in any project-level `lib`.
### `@nx/react` Typings for Libraries
React libraries generated with `@nx/react:library` reference `@nx/react/typings/cssmodule.d.ts` and `@nx/react/typings/image.d.ts` in their tsconfig `types`. These fail with `Cannot find type definition file` unless `@nx/react` is installed in the dest workspace.
**Fix**: `pnpm add -wD @nx/react`
### Jest Preset Missing (Subdirectory Import)
Nx presets create `jest.preset.js` at the workspace root, and project jest configs reference it (e.g. `../../jest.preset.js`). Subdirectory import does NOT bring this file.
**Fix**:
1. Run `npx nx add @nx/jest` — registers `@nx/jest/plugin` in `nx.json` and updates `namedInputs`
2. Create `jest.preset.js` at workspace root (see `references/JEST.md` for content) — `nx add` only creates this when a generator runs, not on bare `nx add`
3. Install test runner deps: `pnpm add -wD jest jest-environment-jsdom ts-jest @types/jest`
4. Install framework-specific test deps as needed (see `references/JEST.md`)
For deeper Jest issues (tsconfig.spec.json, Babel transforms, CI atomization, Jest vs Vitest coexistence), see `references/JEST.md`.
### Target Name Prefixing (Whole-Repo Import)
When importing a project with existing npm scripts (`build`, `dev`, `start`, `lint`), Nx plugins auto-prefix inferred target names to avoid conflicts: e.g. `next:build`, `vite:build`, `eslint:lint`.
**Fix**: Remove the Nx-rewritten npm scripts from the imported `package.json`, then either:
- Accept the prefixed names (e.g. `nx run app:next:build`)
- Rename plugin target names in `nx.json` to use unprefixed names
## Non-Nx Source Issues
When the source is a plain pnpm/npm workspace without `nx.json`.
### npm Script Rewriting (Critical)
Nx rewrites `package.json` scripts during init, creating broken commands (e.g. `vitest run` → `nx test run`). **Fix**: Remove all rewritten scripts — Nx plugins infer targets from config files.
- **Flat config (`eslint.config.js`)**: Self-contained configs can often be left as-is.
- **No ESLint**: Create both root and project-level configs from scratch.
### TypeScript `paths` Aliases
Nx uses `package.json``"exports"` + pnpm workspace linking instead of tsconfig `"paths"`. If packages have proper `"exports"`, paths are redundant. Otherwise, update paths for the new directory structure.
## Technology-specific Guidance
Identify technologies in the source repo, then read and apply the matching reference file(s).
Available references:
-`references/ESLINT.md` — ESLint projects: duplicate `lint`/`eslint:lint` targets, legacy `.eslintrc.*` linting generated files, flat config `.cjs` self-linting, `typescript-eslint` v7/v9 peer dep conflict, mixed ESLint v8+v9 in one workspace.
-`references/GRADLE.md`
-`references/JEST.md` — Jest testing: `@nx/jest/plugin` setup, jest.preset.js, testing deps by framework, tsconfig.spec.json, Jest vs Vitest coexistence, Babel transforms, CI atomization.
ESLint-specific guidance for `nx import`. For generic import issues (root deps, pnpm globs, project references), see `SKILL.md`.
---
### How `@nx/eslint/plugin` Works
`@nx/eslint/plugin` scans for ESLint config files and creates a lint target for each project. It detects **both** flat config files (`eslint.config.{js,mjs,cjs,ts,mts,cts}`) and legacy config files (`.eslintrc.{json,js,cjs,mjs,yml,yaml}`).
**Plugin options (set during `nx add @nx/eslint`):**
```json
{
"plugin":"@nx/eslint/plugin",
"options":{
"targetName":"eslint:lint"
}
}
```
**Auto-installation**: `nx import` auto-detects ESLint config files and offers to install `@nx/eslint`. Accept the offer — it registers the plugin and updates `namedInputs.production` to exclude ESLint config files.
---
### Duplicate `lint` and `eslint:lint` Targets
After import, projects will have **two** lint-related targets if the source `package.json` has a `"lint"` npm script:
-`eslint:lint` — inferred by `@nx/eslint/plugin`; has proper caching and input/output tracking
-`lint` — created by Nx from the npm script via `nx:run-script`; no caching intelligence, just wraps `npm run lint`
**Fix**: Remove the `"lint"` script from each project's `package.json`. Keep `"lint:fix"` if present — there is no plugin-inferred equivalent for auto-fixing.
When `@nx/eslint/plugin` runs `eslint .` on a project with a legacy `.eslintrc.*` config that uses `parserOptions.project`, it tries to lint **all** files in the project directory including:
- Generated `dist/**/*.d.ts` files (not in tsconfig `include`)
- The `.eslintrc.js` config file itself (not in tsconfig `include`)
This causes `Parsing error: ESLint was configured to run on X using parserOptions.project, however that TSConfig does not include this file`.
**Fix**: Add `ignorePatterns` to the `.eslintrc.*` config:
```json
// .eslintrc.json
{
"ignorePatterns":["dist/**"]
}
```
```js
// .eslintrc.js — also ignore the config file itself since module.exports isn't in tsconfig
module.exports={
ignorePatterns:['dist/**','.eslintrc.js'],
// ...
};
```
---
### Flat Config `.cjs` Files Self-Linting
When a project uses `eslint.config.cjs` (CJS flat config), `eslint .` lints the config file itself. The `require()` call on line 1 triggers `@typescript-eslint/no-require-imports`.
**Fix**: Add the config filename to the top-level `ignores` array:
The same applies to `eslint.config.js` in a CJS project (no `"type": "module"`) if it uses `require()`.
---
### `typescript-eslint` Version Conflict With ESLint 9
`typescript-eslint@7.x` declares `peerDependencies: { "eslint": "^8.56.0" }`, but it is commonly used alongside `"eslint": "^9.0.0"`. npm treats this as a hard peer dep conflict and refuses to install.
**Root cause**: `@nx/eslint` init adds `eslint@~8.57.0` at the workspace root (for its own peer deps). Workspace packages that request `eslint@^9.0.0` + `typescript-eslint@^7.0.0` trigger the conflict when npm resolves their deps.
**Fix**: Upgrade `typescript-eslint` from `^7.0.0` to `^8.0.0` directly in the affected workspace package's `package.json`. The `tseslint.config()` API and `tseslint.configs.recommended` are identical between v7 and v8 — no config changes needed.
```json
// packages/my-package/package.json
{
"devDependencies":{
"typescript-eslint":"^8.0.0"
}
}
```
**Note**: npm's root-level `"overrides"` field does not force versions for workspace packages' direct dependencies — update each package.json individually.
---
### Mixed ESLint v8 and v9 in One Workspace
Legacy v8 and flat-config v9 packages can coexist in the same workspace. Each package resolves its own `eslint` version. The root `eslint@~8.57.0` (added by `@nx/eslint` init) is used by legacy v8 packages; v9 packages get their own hoisted `eslint@9`.
`@nx/eslint/plugin` infers `eslint:lint` targets for **both** config formats. Legacy packages run ESLint v8 with `.eslintrc.*`; flat-config packages run ESLint v9 with `eslint.config.*`. No special nx.json configuration is needed to support both simultaneously.
- If you import an entire Gradle repository into a subfolder, files like `gradlew`, `gradlew.bat`, and `gradle/wrapper` will end up inside that imported subfolder.
- The `@nx/gradle` plugin expects those files at the workspace root to infer Gradle projects/tasks automatically.
- If the target workspace has no Gradle setup yet, consider moving those files to the root (especially when using `@nx/gradle`).
- If the target workspace already has Gradle configured, avoid duplicate wrappers: remove imported duplicates from the subfolder or merge carefully.
- Because the import lands in a subfolder, Gradle project references can break; review settings and project path references, then fix any errors.
- If `@nx/gradle` is installed, run `nx show projects` to verify that Gradle projects are being inferred.
Jest-specific guidance for `nx import`. For the basic "Jest Preset Missing" fix (create `jest.preset.js`, install deps), see `SKILL.md`. This file covers deeper Jest integration issues.
---
### How `@nx/jest` Works
`@nx/jest/plugin` scans for `jest.config.{ts,js,cjs,mjs,cts,mts}` and creates a `test` target for each project.
**Plugin options:**
```json
{
"plugin":"@nx/jest/plugin",
"options":{
"targetName":"test"
}
}
```
`npx nx add @nx/jest` does two things:
1.**Registers `@nx/jest/plugin` in `nx.json`** — without this, no `test` targets are inferred
2. Updates `namedInputs.production` to exclude test files
**Gotcha**: `nx add @nx/jest` does NOT create `jest.preset.js` — that file is only generated when you run a generator (e.g. `@nx/jest:configuration`). For imports, you must create it manually (see "Jest Preset" section below).
**Other gotcha**: If you create `jest.preset.js` manually but skip `npx nx add @nx/jest`, the plugin won't be registered and `nx run PROJECT:test` will fail with "Cannot find target 'test'". You need both.
---
### Jest Preset
The preset provides shared Jest configuration (test patterns, ts-jest transform, resolver, jsdom environment).
**Root `jest.preset.js`:**
```js
constnxPreset=require('@nx/jest/preset').default;
module.exports={...nxPreset};
```
**Project `jest.config.ts`:**
```ts
exportdefault{
displayName:'my-lib',
preset:'../../jest.preset.js',
// project-specific overrides
};
```
The `preset` path is relative from the project root to the workspace root. Subdirectory imports preserve the original relative path (e.g. `../../jest.preset.js`), which resolves correctly if the import destination matches the source directory depth.
Both `@nx/jest/plugin` and `@nx/vite/plugin` (which infers Vitest targets) coexist without conflicts — they detect different config files (`jest.config.*` vs `vite.config.*`).
**Target naming**: Both default to `test`. If a project somehow has both config files, rename one:
```json
{
"plugin":"@nx/jest/plugin",
"options":{"targetName":"jest-test"}
}
```
---
### `@testing-library/jest-dom` — Jest vs Vitest
Projects migrating from Jest to Vitest (or workspaces with both) need different imports:
**Jest** (in `test-setup.ts`):
```ts
import'@testing-library/jest-dom';
```
**Vitest** (in `test-setup.ts`):
```ts
import'@testing-library/jest-dom/vitest';
```
If the source used Jest but the dest workspace uses Vitest for that project type, update the import path. Also add `@testing-library/jest-dom` to tsconfig `types` array.
---
### Non-Nx Source: Test Script Rewriting
Nx rewrites `package.json` scripts during init. Test scripts get broken:
-`"test": "jest"` → `"test": "nx test"` (circular if no executor configured)
-`"test": "vitest run"` → `"test": "nx test run"` (broken — `run` becomes an argument)
**Fix**: Remove all rewritten test scripts. `@nx/jest/plugin` and `@nx/vite/plugin` infer test targets from config files.
---
### CI Atomization
`@nx/jest/plugin` supports splitting tests per-file for CI parallelism:
```json
{
"plugin":"@nx/jest/plugin",
"options":{
"targetName":"test",
"ciTargetName":"test-ci"
}
}
```
This creates `test-ci--src/lib/foo.spec.ts` targets for each test file, enabling Nx Cloud distribution. Not relevant during import, but useful for post-import CI setup.
---
### Common Post-Import Issues
1.**"Cannot find target 'test'"**: `@nx/jest/plugin` not registered in `nx.json`. Run `npx nx add @nx/jest` or manually add the plugin entry.
2.**"Cannot find module 'jest-preset'"**: `jest.preset.js` missing at workspace root. Create it (see SKILL.md).
3.**"Cannot find type definition file for 'jest'"**: Missing `@types/jest` or `tsconfig.spec.json` doesn't have `"types": ["jest", "node"]`.
4.**Tests fail with "Cannot use import statement outside a module"**: `ts-jest` not installed or not configured as transform. Check `jest.config.ts` transform section.
5.**Snapshot path mismatches**: After import, `__snapshots__` directories may have paths baked in. Run tests once with `--updateSnapshot` to regenerate.
---
## Fix Order
### Subdirectory Import (Nx Source)
1.`npx nx add @nx/jest` — registers plugin in `nx.json` (does NOT create `jest.preset.js`)
2. Create `jest.preset.js` manually (see "Jest Preset" section above)
3. Install deps: `pnpm add -wD jest jest-environment-jsdom ts-jest @types/jest`
4. Install framework test deps: `@testing-library/react @testing-library/jest-dom` (React), `@vue/test-utils` (Vue)
5. Verify `tsconfig.spec.json` has `"types": ["jest", "node"]`
6.`nx run-many -t test`
### Whole-Repo Import (Non-Nx Source)
1. Remove rewritten test scripts from `package.json`
Next.js-specific guidance for `nx import`. For generic import issues (pnpm globs, root deps, project references, name collisions, ESLint, frontend tsconfig base settings, `@nx/react` typings, Jest preset, target name prefixing, non-Nx source handling), see `SKILL.md`.
---
### `@nx/next/plugin` Inferred Targets
`@nx/next/plugin` detects `next.config.{ts,js,cjs,mjs}` and creates these targets:
-`build-deps` / `watch-deps` — for TS solution setup
**No separate typecheck target** — Next.js runs TypeScript checking as part of `next build`. The `@nx/js/typescript` plugin provides a standalone `typecheck` target for non-Next libraries in the workspace.
**Build target conflict**: Both `@nx/next/plugin` and `@nx/js/typescript` define a `build` target. `@nx/next/plugin` wins for Next.js projects (it detects `next.config.*`), while `@nx/js/typescript` handles libraries with `tsconfig.lib.json`. No rename needed — they coexist.
### `withNx` in `next.config.js`
Nx-generated Next.js projects use `composePlugins(withNx)` from `@nx/next`. This wrapper is optional for `next build` via the inferred plugin (which just runs `next build`), but it provides Nx-specific configuration. Keep it if present.
### Root Dependencies for Next.js
Beyond the generic root deps issue (see SKILL.md), Next.js projects typically need:
**Core**: `react`, `react-dom`, `@types/react`, `@types/react-dom`, `@types/node`, `@nx/react` (see SKILL.md for `@nx/react` typings)
**Nx plugins**: `@nx/next` (auto-installed by import), `@nx/eslint`, `@nx/jest`
**Testing**: see SKILL.md "Jest Preset Missing" section
**ESLint**: `@next/eslint-plugin-next` (in addition to generic ESLint deps from SKILL.md)
### Next.js Auto-Installing Dependencies via Wrong Package Manager
Next.js detects missing `@types/react` during `next build` and tries to install it using `yarn add` regardless of the actual package manager. In a pnpm workspace, this fails with a "nearest package directory isn't part of the project" error.
**Root cause**: `@types/react` is missing from root devDependencies.
**Fix**: Install deps at the root before building: `pnpm add -wD @types/react @types/react-dom`
### Next.js TypeScript Config Specifics
Next.js app tsconfigs have unique patterns compared to Vite:
- **`noEmit: true`** with `emitDeclarationOnly: false` — Next.js handles emit, TS just checks types. This conflicts with `composite: true` from the TS solution setup.
- **`"types": ["jest", "node"]`** — includes test types in the main tsconfig (no separate `tsconfig.app.json`)
- **`"plugins": [{ "name": "next" }]`** — for IDE integration
- **`include`** references `.next/types/**/*.ts` for Next.js auto-generated types
- **`"jsx": "preserve"`** — Next.js uses its own JSX transform, not React's
**Gotcha**: The Next.js tsconfig sets `"noEmit": true` which disables `composite` mode. This is fine because Next.js projects use `next build` for building, not `tsc`. The `@nx/js/typescript` plugin's `typecheck` target is not needed for Next.js apps.
### `next.config.js` Lint Warning
Imported Next.js configs may have `// eslint-disable-next-line @typescript-eslint/no-var-requires` but the project ESLint config enables different rule sets. This produces `Unused eslint-disable directive` warnings. Harmless — remove the comment or ignore.
### `@nx/next:init` Rewrites All npm Scripts (Whole-Repo Import)
When `@nx/next:init` runs during a whole-repo import, it rewrites the project's `package.json` scripts to prefixed `nx` calls:
```json
{
"dev":"nx next:dev",
"build":"nx next:build",
"start":"nx next:start"
}
```
This is the standard "npm Script Rewriting" issue from SKILL.md, but triggered by `@nx/next:init` rather than Nx init. **Fix**: Remove all rewritten scripts from `package.json` — `@nx/next/plugin` infers all targets from `next.config.*`.
---
## Non-Nx Source (create-next-app)
### Whole-Repo Import Recommended
For single-project `create-next-app` repos, use whole-repo import into a subdirectory:
`next build` auto-generates `next-env.d.ts` at the project root. Add `next-env.d.ts` to the dest root `.gitignore` — it is framework-generated and should not be committed.
### ESLint: Self-Contained `eslint-config-next`
`create-next-app` generates a flat ESLint config using `eslint-config-next` (which bundles its own plugins). This is **self-contained** — no root `eslint.config.mjs` needed, no `@nx/eslint-plugin` dependency. The `@nx/eslint/plugin` detects it and creates a lint target.
### TypeScript: No Changes Needed
Non-Nx Next.js projects have self-contained tsconfigs with `noEmit: true`, their own `lib`, `module`, `moduleResolution`, and `jsx` settings. Since `next build` handles type checking internally, no tsconfig modifications are needed. The project does NOT need to extend `tsconfig.base.json`.
**Gotcha**: The `@nx/js/typescript` plugin won't create a `typecheck` target because there's no `tsconfig.lib.json`. This is fine — use `next:build` for type checking.
### `noEmit: true` and TS Solution Setup
Non-Nx Next.js projects use `noEmit: true`, which conflicts with Nx's TS solution setup (`composite: true`). If the dest workspace uses project references and you want the Next.js app to participate:
**However**, this is optional for standalone Next.js apps that don't export types consumed by other workspace projects.
### Tailwind / PostCSS
`create-next-app` with Tailwind generates `postcss.config.mjs`. This works as-is after import — no path changes needed since PostCSS resolves relative to the project root.
---
## Mixed Next.js + Vite Coexistence
When both Next.js and Vite projects exist in the same workspace.
### Plugin Coexistence
Both `@nx/next/plugin` and `@nx/vite/plugin` can coexist in `nx.json`. They detect different config files (`next.config.*` vs `vite.config.*`) so there are no conflicts. The `@nx/js/typescript` plugin handles libraries.
### Vite Standalone Project tsconfig Fixes
Vite standalone projects (imported as whole-repo) have self-contained tsconfigs without `composite: true`. The `@nx/js/typescript` plugin's typecheck target runs `tsc --build --emitDeclarationOnly` which requires `composite`.
**Fix**:
1. Add `extends: "../../tsconfig.base.json"` to the root project tsconfig
2. Add `composite: true`, `declaration: true`, `declarationMap: true`, `tsBuildInfoFile` to `tsconfig.app.json` and `tsconfig.spec.json`
3. Set `moduleResolution: "bundler"` (replace `"node"`)
4. Add source files to `tsconfig.spec.json``include` — specs import app code, and `composite` mode requires all files to be listed
### Typecheck Target Names
-`@nx/vite/plugin` defaults `typecheckTargetName` to `"vite:typecheck"`
-`@nx/js/typescript` uses `"typecheck"`
- Next.js projects have NO standalone typecheck target — Next.js runs type checking during `next build`
No naming conflicts between frameworks.
---
## Fix Order — Nx Source (Subdirectory Import)
1. Import Next.js apps into `apps/<name>` (see SKILL.md: "Application vs Library Detection")
2. Generic fixes from SKILL.md (pnpm globs, root deps, `.gitkeep` removal, frontend tsconfig base settings, `@nx/react` typings)
- **`@repo/eslint-config`** (or similar) — ESLint config files and all ESLint plugin dependencies
These are not code libraries. They distribute config via Node module resolution (e.g., `"extends": "@repo/typescript-config/nextjs.json"`). This is the **default** Turborepo pattern — expect it in virtually every Turborepo import. Package names vary — check `package.json` files to identify the actual names.
## Check for Root Config Files First
**Before doing any config merging, check whether the destination workspace uses shared root configuration.** This decides how to handle the config packages.
- If the workspace has a root `tsconfig.base.json` and/or root `eslint.config.mjs` that projects extend, merge the config packages into these root configs (see steps below).
- If the workspace does NOT have root config files — each project manages its own configuration independently (similar to Turborepo). In this case, **do not create root config files or merge into them**. Just remove turbo-specific parts (`turbo.json`, `eslint-plugin-turbo`) and leave the config packages in place, or ask the user how they want to handle them.
If unclear, check for the presence of `tsconfig.base.json` at the root or ask the user.
## Merging TypeScript Config (Only When Root tsconfig.base.json Exists)
The config package contains a hierarchy of tsconfig files. Each project extends one via package name.
1.**Read the config package** — trace the full inheritance chain (e.g., `nextjs.json` extends `base.json`).
2.**Update root `tsconfig.base.json`** — absorb `compilerOptions` from the base config. Add Nx `paths` for cross-project imports (Turborepo doesn't use path aliases, Nx relies on them).
3.**Update each project's `tsconfig.json`**:
- Change `"extends"` from `"@repo/typescript-config/<variant>.json"` to the relative path to root `tsconfig.base.json`.
4.**Delete the config package** and remove it from all `devDependencies`.
## Merging ESLint Config (Only When Root eslint.config Exists)
The config package centralizes ESLint plugin dependencies and exports composable flat configs.
1.**Read the config package** — identify exported configs, plugin dependencies, and inheritance.
2.**Update root `eslint.config.mjs`** — absorb base rules (JS recommended, TypeScript-ESLint, Prettier, etc.). Drop `eslint-plugin-turbo`.
3.**Update each project's `eslint.config.mjs`** — switch from importing `@repo/eslint-config/<variant>` to extending the root config, adding framework-specific plugins inline.
4.**Move ESLint plugin dependencies** from the config package to root `devDependencies`.
5. If `@nx/eslint` plugin is configured with inferred targets, remove `"lint"` scripts from project `package.json` files.
6.**Delete the config package** and remove it from all `devDependencies`.
Vite-specific guidance for `nx import`. For generic import issues (pnpm globs, root deps, project references, name collisions, ESLint, frontend tsconfig base settings, `@nx/react` typings, Jest preset, non-Nx source handling), see `SKILL.md`.
---
### `@nx/vite/plugin` Typecheck Target
`@nx/vite/plugin` defaults `typecheckTargetName` to `"vite:typecheck"`. If the workspace expects `"typecheck"`, set it explicitly in `nx.json`. If `@nx/js/typescript` is also registered, rename one target to avoid conflicts (e.g. `"tsc-typecheck"` for the JS plugin).
Keep both plugins only if the workspace has non-Vite pure TS libraries — `@nx/js/typescript` handles those while `@nx/vite/plugin` handles Vite projects.
### @nx/vite Plugin Install Failure
Plugin init loads `vite.config.ts` before deps are available. **Fix**: `pnpm add -wD vite @vitejs/plugin-react` (or `@vitejs/plugin-vue`) first, then `pnpm exec nx add @nx/vite`.
### Vite `resolve.alias` and `__dirname` (Non-Nx Sources)
**`__dirname` undefined** (CJS-only): Replace with `fileURLToPath(new URL('./src', import.meta.url))` from `'node:url'`.
**`@/` path alias**: Vite's `resolve.alias` works at runtime but TS needs matching `"paths"`. Set `"baseUrl": "."` in project tsconfig.
**PostCSS/Tailwind**: Verify `content` globs resolve correctly after import.
### Missing TypeScript `types` (Non-Nx Sources)
Non-Nx tsconfigs may not declare all needed types. Ensure Vite projects include `"types": ["node", "vite/client"]` in their tsconfig.
### `noEmit` Fix: Vite-Specific Notes
See SKILL.md for the generic noEmit→composite fix. Vite-specific additions:
- Non-Nx Vite projects often have **both**`tsconfig.app.json` and `tsconfig.node.json` with `noEmit` — fix both
- Solution-style tsconfigs (`"files": [], "references": [...]`) may lack `extends`. Add `extends` pointing to the dest root `tsconfig.base.json` so base settings (`moduleResolution`, `lib`) apply.
- This is safe — Vite/Vitest ignore TypeScript emit settings.
### Dependency Version Conflicts
**Shared Vite deps (both frameworks):**`vite`, `vitest`, `jsdom`, `@types/node`, `typescript` (dev)
**Vite 6→7**: Typecheck fails (`Plugin<any>` type mismatch); build/serve still works. Fix: align versions.
**Vitest 3→4**: Usually works; type conflicts may surface in shared test utils.
---
## React Router 7 (Vite-Based)
React Router 7 (`@react-router/dev`) uses Vite under the hood with a `vite.config.ts` and a `react-router.config.ts`. The `@nx/vite/plugin` detects `vite.config.ts` and creates inferred targets.
### Targets
`@nx/vite/plugin` creates `build`, `dev`, `serve` targets. The `build` target invokes the script defined in `package.json` (usually `react-router build`), not `vite build` directly.
**No separate typecheck target from `@nx/vite/plugin`** — React Router 7 typegen is run as part of `typecheck` (e.g. `react-router typegen && tsc`). The `typecheck` target is inferred from the tsconfig. Keep the `typecheck` script in `package.json` if present; it is not rewritten.
### tsconfig Notes
React Router 7 uses a single `tsconfig.json` (no `tsconfig.app.json`/`tsconfig.node.json` split). It includes:
-`"rootDirs": [".", "./.react-router/types"]` — for generated type files; keep as-is
-`"noEmit": true` — replace with composite settings per SKILL.md
### Build Output
React Router 7 outputs to `build/` (not `dist/`). Add `build` to the dest root `.gitignore`.
### Generated Types Directory
React Router 7 generates `.react-router/` at the project root for route type generation. Add `.react-router` to the dest root `.gitignore`.
---
## TanStack Start (Vite-Based)
TanStack Start uses Vinxi under the hood, which wraps Vite. Projects have a standard `vite.config.ts` that `@nx/vite/plugin` detects normally.
### Targets
`@nx/vite/plugin` creates `build`, `dev`, `preview`, `serve-static`, `typecheck` targets. The `build` target runs `vite build` which invokes the TanStack Start Vinxi pipeline (produces both client and SSR bundles).
### tsconfig Notes
TanStack Start uses a single `tsconfig.json` with `"allowImportingTsExtensions": true` and `"noEmit": true`. Apply the standard noEmit → composite fix. `allowImportingTsExtensions` is compatible with `emitDeclarationOnly: true` — no change needed.
### `paths` Aliases
TanStack Start commonly uses `"#/*": ["./src/*"]` and `"@/*": ["./src/*"]`. These are self-referential — keep as-is for a single-project app.
### Uncommitted Source Repo
`create-tan-stack` initializes a git repo but does NOT make an initial commit. Before importing, commit first:
Both `@nx/js/typescript` and `@nx/vite/plugin` auto-detect `vue-tsc` when installed — no manual config needed. Remove source scripts like `"typecheck": "vue-tsc --noEmit"`.
### ESLint Plugin Installation Order (Critical)
`@nx/eslint` init **crashes** if Vue ESLint deps aren't installed first (it loads all config files).
**Important**: `vue-eslint-parser` override must come **AFTER** base config — `flat/typescript` sets the TS parser globally without a `files` filter, breaking `.vue` parsing.
`vue-eslint-parser` must be an explicit pnpm dependency (strict resolution prevents transitive import).
**Known issue**: Some generated Vue ESLint configs omit `vue-eslint-parser`. Use the pattern above instead.
---
## Mixed React + Vue
When both frameworks coexist, several settings become per-project.
### tsconfig `jsx` — Per-Project Only
- React: `"jsx": "react-jsx"` in project tsconfig
- Vue: `"jsx": "preserve"`, `"jsxImportSource": "vue"` in project tsconfig
- Root: **NO**`jsx` setting
### Typecheck — Auto-Detects Framework
`@nx/vite/plugin` uses `vue-tsc` for Vue projects and `tsc` for React automatically.
`@nx/react`/`@nx/vue` are for generators only — no target conflicts.
---
## Redundant npm Scripts After Import
`nx import` copies `package.json` verbatim, so npm scripts come along. For Vite-based projects `@nx/vite/plugin` already infers the same targets from `vite.config.ts` — the npm scripts just shadow the plugin with weaker `nx:run-script` wrappers (no first-class caching inputs/outputs). Remove them after import.
### Standalone Vite App (`create-vite`)
Remove the following scripts — every one is redundant:
Remove `build`, `dev`, `preview`, and `test` scripts, but move any hardcoded `--port` flag to `vite.config.ts` first:
```ts
// vite.config.ts
exportdefaultdefineConfig({
server:{port: 3000},// replaces `vite dev --port 3000`
...
})
```
### React Router 7 — Keep ALL scripts
Do **not** remove React Router 7 scripts. They use the framework CLI (`react-router build`, `react-router dev`, `react-router-serve`) which is not interchangeable with plain `vite`:
-`typecheck` runs `react-router typegen && tsc` — typegen must precede `tsc` or it fails on missing route types
-`start` serves the SSR bundle — no plugin equivalent
---
## Fix Orders
### Nx Source
1. Generic fixes from SKILL.md (pnpm globs, root deps, executor paths, frontend tsconfig base settings, `@nx/react` typings)
2. Configure `@nx/vite/plugin` typecheck target
3.**React**: `jsx: "react-jsx"` (root or per-project)
0. Import into `apps/<name>` (see SKILL.md: "Application vs Library Detection")
1. Generic fixes from SKILL.md (stale files cleanup, pnpm globs, rewritten scripts, target name prefixing, noEmit→composite, ESLint handling)
2. Fix `noEmit` in **all** tsconfigs (app, node, etc. — non-Nx projects often have multiple)
3. Add `extends` to solution-style tsconfigs so root settings apply
4. Fix `resolve.alias` / `__dirname` / `baseUrl`
5. Ensure `types` include `vite/client` and `node`
6. Install `@nx/vite` manually if it failed during import
7. Remove redundant npm scripts so `@nx/vite/plugin` infers them natively (see "Redundant npm Scripts" section)
8.**Vue**: Add `outDir` + `**/*.vue.d.ts` to ESLint ignores
9. Full verification
### Multiple-Source Imports
See SKILL.md for generic multi-import (name collisions, dep refs). Vite-specific: fix tsconfig `references` paths for alternate directories (`../../libs/` → `../../libs-beta/`).
### Non-Nx Source: React Router 7
1. Ensure source has at least one commit (see SKILL.md: "Source Repo Has No Commits")
2.`nx import` whole-repo into `apps/<name>` (see SKILL.md: "Application vs Library Detection") → auto-installs `@nx/vite`, `@nx/react`
3. Stale file cleanup: `node_modules/`, `package-lock.json`, `.gitignore`
description: Find and add Nx plugins. USE WHEN user wants to discover available plugins, install a new plugin, or add support for a specific framework or technology to the workspace.
description: Helps with running tasks in an Nx workspace. USE WHEN the user wants to execute build, test, lint, serve, or run any other tasks defined in the workspace.
---
You can run tasks with Nx in the following way.
Keep in mind that you might have to prefix things with npx/pnpx/yarn if the user doesn't have nx installed globally. Look at the package.json or lockfile to determine which package manager is in use.
For more details on any command, run it with `--help` (e.g. `nx run-many --help`, `nx affected --help`).
## Understand which tasks can be run
You can check those via `nx show project <projectname> --json`, for example `nx show project myapp --json`. It contains a `targets` section which has information about targets that can be run. You can also just look at the `package.json` scripts or `project.json` targets, but you might miss out on inferred tasks by Nx plugins.
## Run a single task
```
nx run <project>:<task>
```
where `project` is the project name defined in `package.json` or `project.json` (if present).
## Run multiple tasks
```
nx run-many -t build test lint typecheck
```
You can pass a `-p` flag to filter to specific projects, otherwise it runs on all projects. You can also use `--exclude` to exclude projects, and `--parallel` to control the number of parallel processes (default is 3).
Examples:
-`nx run-many -t test -p proj1 proj2` — test specific projects
-`nx run-many -t test --projects=*-app --exclude=excluded-app` — test projects matching a pattern
-`nx run-many -t test --projects=tag:api-*` — test projects by tag
## Run tasks for affected projects
Use `nx affected` to only run tasks on projects that have been changed and projects that depend on changed projects. This is especially useful in CI and for large workspaces.
```
nx affected -t build test lint
```
By default it compares against the base branch. You can customize this:
-`nx affected -t test --base=main --head=HEAD` — compare against a specific base and head
-`nx affected -t test --files=libs/mylib/src/index.ts` — specify changed files directly
## Useful flags
These flags work with `run`, `run-many`, and `affected`:
-`--skipNxCache` — rerun tasks even when results are cached
-`--verbose` — print additional information such as stack traces
-`--nxBail` — stop execution after the first failed task
-`--configuration=<name>` — use a specific configuration (e.g. `production`)
description: "Explore and understand Nx workspaces. USE WHEN answering questions about the workspace, projects, or tasks. ALSO USE WHEN an nx command fails or you need to check available targets/configuration before running a task. EXAMPLES: 'What projects are in this workspace?', 'How is project X configured?', 'What depends on library Y?', 'What targets can I run?', 'Cannot find configuration for task', 'debug nx task failure'."
---
# Nx Workspace Exploration
This skill provides read-only exploration of Nx workspaces. Use it to understand workspace structure, project configuration, available targets, and dependencies.
Keep in mind that you might have to prefix commands with `npx`/`pnpx`/`yarn` if nx isn't installed globally. Check the lockfile to determine the package manager in use.
## Listing Projects
Use `nx show projects` to list projects in the workspace.
The project filtering syntax (`-p`/`--projects`) works across many Nx commands including `nx run-many`, `nx release`, `nx show projects`, and more. Filters support explicit names, glob patterns, tag references (e.g. `tag:name`), directories, and negation (e.g. `!project-name`).
```bash
# List all projects
nx show projects
# Filter by pattern (glob)
nx show projects --projects "apps/*"
nx show projects --projects "shared-*"
# Filter by tag
nx show projects --projects "tag:publishable"
nx show projects -p 'tag:publishable,!tag:internal'
# Filter by target (projects that have a specific target)
nx show projects --withTarget build
# Combine filters
nx show projects --type lib --withTarget test
nx show projects --affected --exclude="*-e2e"
nx show projects -p "tag:scope:client,packages/*"
# Negate patterns
nx show projects -p '!tag:private'
nx show projects -p '!*-e2e'
# Output as JSON
nx show projects --json
```
## Project Configuration
Use `nx show project <name> --json` to get the full resolved configuration for a project.
**Important**: Do NOT read `project.json` directly - it only contains partial configuration. The `nx show project --json` command returns the full resolved config including inferred targets from plugins.
You can read the full project schema at `node_modules/nx/schemas/project-schema.json` to understand nx project configuration options.
```bash
# Get full project configuration
nx show project my-app --json
# Extract specific parts from the JSON
nx show project my-app --json | jq '.targets'
nx show project my-app --json | jq '.targets.build'
nx show project my-app --json | jq '.targets | keys'
When processing nx CLI results, use command-line tools to compute the answer programmatically rather than counting or parsing output manually. Always use `--json` flags to get structured output that can be processed with `jq`, `grep`, or other tools you have installed locally.
issue-inactive-days:"30"# Lock issues after 30 days of being closed
pr-inactive-days:"5"# Lock closed PRs after 5 days. This ensures that issues that stem from a PR are opened as issues, rather than comments on the recently merged PR.
// TODO (emily): Fix Windows support as gradle fails when running nx build https://staging.nx.app/runs/LgD4vxGn8w?utm_source=pull-request&utm_medium=comment
// TODO (Jack): Fix Windows support as gradle fails when running nx build https://staging.nx.app/runs/LgD4vxGn8w?utm_source=pull-request&utm_medium=comment
description: CI helper for /monitor-ci. Fetches CI status, retrieves fix details, or updates self-healing fixes. Executes one MCP tool call and returns the result.
mode: subagent
---
# CI Monitor Subagent
You are a CI helper. You call ONE MCP tool per invocation and return the result. Do not loop, poll, or sleep.
## Commands
The main agent tells you which command to run:
### FETCH_STATUS
Call `ci_information` with the provided branch and select fields. Return a JSON object with ONLY these fields:
Do NOT return raw suggestedFix diffs or raw taskOutputSummary — summarize them.
The main agent uses these summaries to understand what failed and attempt local fixes.
### UPDATE_FIX
Call `update_self_healing_fix` with the provided shortLink and action (APPLY/REJECT/RERUN_ENVIRONMENT_STATE). Return the result message (success/failure string).
### FETCH_THROTTLE_INFO
Call `ci_information` with the provided URL. Return ONLY: `{ shortLink, cipeUrl }`
## Important
- Execute ONE command and return immediately
- Do NOT poll, loop, sleep, or make decisions
- Extract and return ONLY the fields specified for each command — do NOT dump the full MCP response
description: Polls Nx Cloud CI pipeline and self-healing status. Returns structured state when actionable. Spawned by /nx-cloud-ci-monitor command to monitor CI Attempt status.
mode: subagent
---
# CI Watcher Subagent
You are a CI monitoring subagent responsible for polling Nx Cloud CI Attempt status and self-healing state. You report status back to the main agent - you do NOT make apply/reject decisions.
## Your Responsibilities
1. Poll CI status using the `ci_information` MCP tool
2. Implement exponential backoff between polls
3. Return structured state when an actionable condition is reached
4. Track iteration count and elapsed time
5. Output status updates based on verbosity level
## Input Parameters (from Main Agent)
The main agent may provide these optional parameters in the prompt:
# Large content fields - fetch only when returning to main agent
```
## Initial Wait
Before first poll, wait based on context:
- **Fresh start (no expected CIPE):** Wait 60 seconds to allow CI to start
- **Expecting new CIPE:** Wait 30 seconds (action already triggered)
**IMPORTANT:** Always run sleep in foreground, NOT as background command.
```bash
sleep 60# or 30 if expecting new CIPE (FOREGROUND, not background)
```
## Two-Phase Operation
The subagent operates in one of two modes depending on input:
### Mode 1: Fresh Start (no `expectedCommitSha` or `previousCipeUrl`)
Normal polling - process whatever CIPE is returned by `ci_information`.
### Mode 2: Wait-for-New-CIPE (when `expectedCommitSha` or `previousCipeUrl` provided)
**CRITICAL**: When expecting a new CIPE, the subagent must **completely ignore** the old/stale CIPE. Do NOT process its status, do NOT return actionable states based on it.
#### Phase A: Wait Mode
1. Start a **new-CIPE timeout** timer (default: 30 minutes)
2. On each poll of `ci_information`:
- Check if CIPE is NEW:
-`cipeUrl` differs from `previousCipeUrl` → **new CIPE detected**
[CI Monitor] Still seeing previous CI Attempt (ignoring): <oldCipeUrl>
[CI Monitor] Polling... (elapsed: 1m 30s)
[CI Monitor] Still seeing previous CI Attempt (ignoring): <oldCipeUrl>
[CI Monitor] Polling... (elapsed: 2m 30s)
[CI Monitor] ✓ New CI Attempt detected! URL: <newCipeUrl>, SHA: <newCommitSha>
[CI Monitor] Switching to normal polling mode...
```
### Why This Matters (Context Preservation)
**The problem**: Stale CIPE data can be very large:
-`taskOutputSummary`: potentially thousands of characters of build/test output
-`suggestedFix`: entire patch files
-`suggestedFixReasoning`: detailed explanation
If subagent returns stale CIPE data to main agent, it **pollutes main agent's context** with useless information (we already processed that CIPE). This wastes valuable context window.
**Without wait mode:**
1. Poll `ci_information` → get old CIPE with huge data
2. Return to main agent with all that stale data
3. Main agent's context gets polluted with useless info
4. Main agent has to process/ignore it anyway
**With wait mode:**
1. Poll `ci_information` → get old CIPE → **ignore it, don't return**
2. Keep waiting internally (stale data stays in subagent)
3. New CIPE appears → switch to normal mode
4. Return to main agent with only the NEW, relevant CIPE data
## Polling Loop
### Subagent State Management
Maintain internal accumulated state across polls:
```
accumulated_state = {}
```
### Call `ci_information` MCP Tool
**Wait Mode (expecting new CI Attempt):**
```
ci_information({
branch: "<branch_name>",
select: "cipeUrl,commitSha,cipeStatus"
})
```
Only fetch minimal fields needed to detect CI Attempt change. Do NOT fetch heavy fields - stale data wastes context.
Merge response into `accumulated_state`, then return merged state to main agent.
**Pagination:** Heavy string fields return first page only. If `hasMore` indicated, include in return format so main agent knows more content available.
### Return to Main Agent When
Return immediately with structured state if ANY of these conditions are true:
| `fix_auto_applying` | `selfHealingStatus == 'COMPLETED'` AND `couldAutoApplyTasks == true` AND `verificationStatus == 'COMPLETED'` |
| `fix_available` | `selfHealingStatus == 'COMPLETED'` AND `suggestedFix != null` AND (`couldAutoApplyTasks != true` OR `verificationStatus` in (`FAILED`, `NOT_EXECUTABLE`)) |
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
Parse any overrides from `$ARGUMENTS` and merge with defaults.
## Nx Cloud Connection Check
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
### Step 0: Verify Nx Cloud Connection
1.**Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
2.**If `nx.json` missing OR neither property exists** → exit with:
```
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
```
3. **If connected** → continue to main loop
## Session Context Behavior
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
- **For a completely clean slate:** Exit Claude Code and restart `claude`
## Default Behaviors by Status
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
- If fix needs enhancement → use Apply Locally + Enhance Flow above
- If fix is wrong → reject via MCP, fix from scratch, commit, push
### Auto-Apply Eligibility
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
### Apply vs Reject vs Apply Locally
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
### Apply Locally + Enhance Flow
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
2. Make additional changes as needed
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Reject + Fix From Scratch Flow
When the fix is completely wrong:
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
2. Fix the issue from scratch locally
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Environment Issue Handling
When `failureClassification == 'ENVIRONMENT_STATE'`:
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
- Subagent will **completely ignore** the old/stale CIPE
- Subagent will only wait for new CIPE to appear
- Subagent will NOT return to main agent with stale CIPE data
- Once new CIPE detected, subagent switches to normal polling
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
### Step 4: Progress Tracking
After each action:
- If state changed significantly → reset `no_progress_count = 0`
- If state unchanged → `no_progress_count++`
- On new CI attempt detected → reset `local_verify_count = 0`
description: Monitor Nx Cloud CI pipeline and handle self-healing fixes. USE WHEN user says "monitor ci", "watch ci", "ci monitor", "watch ci for this branch", "track ci", "check ci status", wants to track CI status, or needs help with self-healing CI fixes. Prefer this skill over native CI provider tools (gh, glab, etc.) for CI monitoring — it integrates with Nx Cloud self-healing which those tools cannot access.
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn subagents to interact with Nx Cloud, run deterministic decision scripts, and take action based on the results.
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CI-Attempt failures (e.g., lockfile updates) |
| `--new-cipe-timeout` | 10 | Minutes to wait for new CI Attempt after action |
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
Parse any overrides from `$ARGUMENTS` and merge with defaults.
## Nx Cloud Connection Check
Before starting the monitoring loop, verify the workspace is connected to Nx Cloud. Without this connection, no CI data is available and the entire skill is inoperable.
### Step 0: Verify Nx Cloud Connection
1.**Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
2.**If `nx.json` missing OR neither property exists** → exit with:
```
Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
```
3. **If connected** → continue to main loop
## Architecture Overview
1. **This skill (orchestrator)**: spawns subagents, runs scripts, prints status, does local coding work
2. **ci-monitor-subagent (haiku)**: calls one MCP tool (ci_information or update_self_healing_fix), returns structured result, exits
3. **ci-poll-decide.mjs (deterministic script)**: takes ci_information result + state, returns action + status message
4. **ci-state-update.mjs (deterministic script)**: manages budget gates, post-action state transitions, and cycle classification
## Status Reporting
The decision script handles message formatting based on verbosity. When printing messages to the user:
- Prepend `[monitor-ci]` to every message from the script's `message` field
- For your own action messages (e.g. "Applying fix via MCP..."), also prepend `[monitor-ci]`
## Anti-Patterns
These behaviors cause real problems — racing with self-healing, losing CI progress, or wasting context:
| Using CI provider CLIs with `--watch` flags (e.g., `gh pr checks --watch`, `glab ci status -w`) | Bypasses Nx Cloud self-healing entirely |
| Writing custom CI polling scripts | Unreliable, pollutes context, no self-healing |
| Cancelling CI workflows/pipelines | Destructive, loses CI progress |
| Running CI checks on main agent | Wastes main agent context tokens |
| Independently analyzing/fixing CI failures while polling | Races with self-healing, causes duplicate fixes and confused state |
**If this skill fails to activate**, the fallback is:
1. Use CI provider CLI for a one-time, read-only status check (single call, no watch/polling flags)
2. Immediately delegate to this skill with gathered context
3. Do not continue polling on main agent — it wastes context tokens and bypasses self-healing
## Session Context Behavior
If the user previously ran `/monitor-ci` in this session, you may have prior state (poll counts, last CI Attempt URL, etc.). Resume from that state unless `--fresh` is set, in which case discard it and start from Step 1.
## MCP Tool Reference
Three field sets control polling efficiency — use the lightest set that gives you what you need:
The `ci_information` tool accepts `branch` (optional, defaults to current git branch), `select` (comma-separated field names), and `pageToken` (0-based pagination for long strings).
The `update_self_healing_fix` tool accepts a `shortLink` and an action: `APPLY`, `REJECT`, or `RERUN_ENVIRONMENT_STATE`.
## Default Behaviors by Status
The decision script returns one of the following statuses. This table defines the **default behavior** for each. User instructions can override any of these.
| `self_healing_throttled` | Reject old fixes, attempt local fix. |
| `no_new_cipe` | CI Attempt never spawned. Auto-fix workflow or exit with guidance. |
| `cipe_no_tasks` | CI failed with no tasks. Retry once with empty commit. |
**Key rules (always apply):**
- **Git safety**: Stage specific files by name — `git add -A` or `git add .` risks committing the user's unrelated work-in-progress or secrets
- **Environment failures** (OOM, command not found, permission denied): bail immediately. These aren't code bugs, so spending local-fix budget on them is wasteful
- **Gate check**: Run `ci-state-update.mjs gate` before local fix attempts — if budget exhausted, print message and exit
## Main Loop
### Step 1: Initialize Tracking
```
cycle_count = 0 # Only incremented for agent-initiated cycles (counted against --max-cycles)
start_time = now()
no_progress_count = 0
local_verify_count = 0
env_rerun_count = 0
last_cipe_url = null
expected_commit_sha = null
agent_triggered = false # Set true after monitor takes an action that triggers new CI Attempt
poll_count = 0
wait_mode = false
prev_status = null
prev_cipe_status = null
prev_sh_status = null
prev_verification_status = null
prev_failure_classification = null
```
### Step 2: Polling Loop
Repeat until done:
#### 2a. Spawn subagent (FETCH_STATUS)
Determine select fields based on mode:
- **Wait mode**: use WAIT_FIELDS (`cipeUrl,commitSha,cipeStatus`)
- **Normal mode (first poll or after newCipeDetected)**: use LIGHT_FIELDS
Call the `ci_information` tool with the determined `select` fields for the current branch. Wait for the result before proceeding.
- **`action == "poll"`**: Print `output.message`, sleep `output.delay` seconds, go to 2a
- If `output.newCipeDetected`: clear wait mode, reset `wait_mode = false`
- **`action == "wait"`**: Print `output.message`, sleep `output.delay` seconds, go to 2a
- **`action == "done"`**: Proceed to Step 3 with `output.code`
### Step 3: Handle Actionable Status
When decision script returns `action == "done"`:
1. Run cycle-check (Step 4) **before** handling the code
2. Check the returned `code`
3. Look up default behavior in the table above
4. Check if user instructions override the default
5. Execute the appropriate action
6. **If action expects new CI Attempt**, update tracking (see Step 3a)
7. If action results in looping, go to Step 2
#### Tool calls for actions
Several statuses require fetching additional data or calling tools:
- **fix_apply_ready**: Call `update_self_healing_fix` with action `APPLY`
- **fix_needs_local_verify**: Call `ci_information` with HEAVY_FIELDS for fix details before local verification
- **fix_needs_review**: Call `ci_information` with HEAVY_FIELDS → get `suggestedFixDescription`, `suggestedFixSummary`, `taskFailureSummaries`
- **fix_failed / no_fix**: Call `ci_information` with HEAVY_FIELDS → get `taskFailureSummaries` for local fix context
- **environment_issue**: Call `update_self_healing_fix` with action `RERUN_ENVIRONMENT_STATE`
- **self_healing_throttled**: Call `ci_information` with HEAVY_FIELDS → get `selfHealingSkipMessage`; then call `update_self_healing_fix` for each old fix
### Step 3a: Track State for New-CI-Attempt Detection
After actions that should trigger a new CI Attempt, run:
The script returns `{ waitMode, pollCount, lastCipeUrl, expectedCommitSha, agentTriggered }`. Update all tracking state from the output, then go to Step 2.
### Step 4: Cycle Classification and Progress Tracking
When the decision script returns `action == "done"`, run cycle-check **before** handling the code:
The script returns `{ cycleCount, agentTriggered, envRerunCount, approachingLimit, message }`. Update tracking state from the output.
- If `approachingLimit` → ask user whether to continue (with 5 or 10 more cycles) or stop monitoring
- If previous cycle was NOT agent-triggered (human pushed), log that human-initiated push was detected
#### Progress Tracking
- `no_progress_count`, circuit breaker (5 polls), and backoff reset are handled by ci-poll-decide.mjs (progress = any change in cipeStatus, selfHealingStatus, verificationStatus, or failureClassification)
- `env_rerun_count` reset on non-environment status is handled by ci-state-update.mjs cycle-check
- On new CI Attempt detected (poll script returns `newCipeDetected`) → reset `local_verify_count = 0`, `env_rerun_count = 0`
description: Monitor Nx Cloud CI pipeline and handle self-healing fixes automatically. Checks for Nx Cloud connection before starting.
---
# CI Monitor Command
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
Parse any overrides from `$ARGUMENTS` and merge with defaults.
## Nx Cloud Connection Check
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
### Step 0: Verify Nx Cloud Connection
1.**Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
2.**If `nx.json` missing OR neither property exists** → exit with:
```
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
```
3. **If connected** → continue to main loop
## Session Context Behavior
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
- **For a completely clean slate:** Exit Claude Code and restart `claude`
## Default Behaviors by Status
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
- If fix needs enhancement → use Apply Locally + Enhance Flow above
- If fix is wrong → reject via MCP, fix from scratch, commit, push
### Auto-Apply Eligibility
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
### Apply vs Reject vs Apply Locally
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
### Apply Locally + Enhance Flow
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
2. Make additional changes as needed
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Reject + Fix From Scratch Flow
When the fix is completely wrong:
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
2. Fix the issue from scratch locally
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Environment Issue Handling
When `failureClassification == 'ENVIRONMENT_STATE'`:
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
- Subagent will **completely ignore** the old/stale CIPE
- Subagent will only wait for new CIPE to appear
- Subagent will NOT return to main agent with stale CIPE data
- Once new CIPE detected, subagent switches to normal polling
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
### Step 4: Progress Tracking
After each action:
- If state changed significantly → reset `no_progress_count = 0`
- If state unchanged → `no_progress_count++`
- On new CI attempt detected → reset `local_verify_count = 0`
description: 'Link workspace packages in monorepos (npm, yarn, pnpm, bun). USE WHEN: (1) you just created or generated new packages and need to wire up their dependencies, (2) user imports from a sibling package and needs to add it as a dependency, (3) you get resolution errors for workspace packages (@org/*) like "cannot find module", "failed to resolve import", "TS2307", or "cannot resolve". DO NOT patch around with tsconfig paths or manual package.json edits - use the package manager''s workspace commands to fix actual linking.'
---
# Link Workspace Packages
Add dependencies between packages in a monorepo. All package managers support workspaces but with different syntax.
## Detect Package Manager
Check whether there's a `packageManager` field in the root-level `package.json`.
Alternatively check lockfile in repo root:
-`pnpm-lock.yaml` → pnpm
-`yarn.lock` → yarn
-`bun.lock` / `bun.lockb` → bun
-`package-lock.json` → npm
## Workflow
1. Identify consumer package (the one importing)
2. Identify provider package(s) (being imported)
3. Add dependency using package manager's workspace syntax
4. Verify symlinks created in consumer's `node_modules/`
---
## pnpm
Uses `workspace:` protocol - symlinks only created when explicitly declared.
```bash
# From consumer directory
pnpm add @org/ui --workspace
# Or with --filter from anywhere
pnpm add @org/ui --filter @org/app --workspace
```
Result in `package.json`:
```json
{"dependencies":{"@org/ui":"workspace:*"}}
```
---
## yarn (v2+/berry)
Also uses `workspace:` protocol.
```bash
yarn workspace @org/app add @org/ui
```
Result in `package.json`:
```json
{"dependencies":{"@org/ui":"workspace:^"}}
```
---
## npm
No `workspace:` protocol. npm auto-symlinks workspace packages.
```bash
npm install @org/ui --workspace @org/app
```
Result in `package.json`:
```json
{"dependencies":{"@org/ui":"*"}}
```
npm resolves to local workspace automatically during install.
1. Check if dependency is declared in consumer's `package.json`
2. If not, add it using appropriate command above
3. Run install (`pnpm install`, `npm install`, etc.)
## Notes
- Symlinks appear in `<consumer>/node_modules/@org/<package>`
- **Hoisting differs by manager:**
- npm/bun: hoist shared deps to root `node_modules`
- pnpm: no hoisting (strict isolation, prevents phantom deps)
- yarn berry: uses Plug'n'Play by default (no `node_modules`)
- Root `package.json` should have `"private": true` to prevent accidental publish
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.