Compare commits

..

367 Commits

Author SHA1 Message Date
Jack Hsu 2a05751d78 docs(nx-plugin): add guide on writing performant createNodes v2 plugins
Document practical patterns and caveats for keeping createNodesV2 plugins
fast: prefer the batched v2 API, cache results to disk with a content hash,
hoist shared work out of the per-file loop, load configs in parallel, keep
globs narrow and output deterministic, and avoid per-file process spawning.
Also covers daemon/cache dev overrides and diagnosing slow graph creation
with NX_PERF_LOGGING and nx report.

Generated with [Linear](https://linear.app/nxdev/issue/DOC-516/document-performant-createnodes-v2-plugin-patterns#agent-session-07d2f00a)

Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
2026-06-12 11:16:41 -04:00
Jason Jean fce98704ed chore(gradle): bump gradle project graph plugin version to 0.1.22 (#35973)
## 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).
2026-06-12 00:31:32 -04:00
Jason Jean 82df7d76dc fix(gradle): exclude incremental-compilation .bin files from task inputs (#35975)
## 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.
2026-06-12 00:31:12 -04:00
Jason Jean 14fdda4d1e fix(core): override shell-quote to ^1.8.4 to patch GHSA-w7jw-789q-3m8p (#35974)
## 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.
2026-06-12 02:06:24 +00:00
Jason Jean 2984b3799c fix(gradle): make project graph configuration hash deterministic (#35972)
## 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.
2026-06-11 21:13:27 -04:00
Jason Jean d5143c0e9e chore(repo): migrate to nx 23.0.0-rc.1 (#35971)
## 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 -->
2026-06-11 23:19:48 +00:00
Leosvel Pérez Espinosa 4e76a4ba70 fix(core): degrade cooldown-blocked dist-tags within their own channel (#35967)
## 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>
2026-06-11 16:48:14 -04:00
Jack Hsu fbfbf8f400 feat(nx-dev): add docs top banner for ai monorepos conference (#35956)
## Current Behavior

No top promo bar on the Astro docs site.

## Expected Behavior

- Full-width top strip promoting the AI ♥ Monorepos conference (June
23).
- Theme-aware (light strip in light mode, dark strip in dark mode).
- Offsets header, sidebar, main content, and mobile TOC by the banner
height.
- Build-time gated via `activeUntil`; drops on the next rebuild after
the conference.

Preview:
https://deploy-preview-35956--nx-docs.netlify.app/docs/getting-started/intro

<img width="1392" height="1032" alt="Screenshot 2026-06-11 at 8 48
33 AM"
src="https://github.com/user-attachments/assets/64511293-b3ff-4706-976c-f10227ae56be"
/>
<img width="1392" height="1032" alt="Screenshot 2026-06-11 at 8 48
30 AM"
src="https://github.com/user-attachments/assets/28d5ee65-9f0b-4814-aad5-704cd9128006"
/>

## Related Issue(s)

DOC-521

<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/docs-conf-banner-e2599b5b)
<!-- polygraph-session-end -->
2026-06-11 16:28:24 -04:00
Jack Hsu ffb81334a0 docs(misc): remove polygraph mentions and redirect polygraph pages (#35958)
## Current Behavior

The Nx docs present Polygraph as an Nx Cloud feature, and search results
surface Nx Polygraph pages. Polygraph is launching as a standalone
product.

## Expected Behavior

- Polygraph-specific pages deleted and 301-redirected to
https://trypolygraph.com (keeps the indexed Nx URLs live, reroutes
visitors): `enterprise/polygraph`, `concepts/synthetic-monorepos`,
`enterprise/metadata-only-workspace`.
- Incidental Polygraph mentions stripped, pages kept:
`custom-workflows`, `nx-vs-turborepo`, `github-app-permissions`, Nx
Cloud `release-notes`.
- Removed from sidebar; orphaned Polygraph assets deleted.

Updated content:

-
https://deploy-preview-35958--nx-docs.netlify.app/docs/reference/nx-cloud/release-notes
-
https://deploy-preview-35958--nx-docs.netlify.app/docs/guides/nx-cloud/source-control-integration/github-app-permissions
-
https://deploy-preview-35958--nx-docs.netlify.app/docs/guides/adopting-nx/nx-vs-turborepo

Removed pages with redirects:

-
https://deploy-preview-35958--nx-docs.netlify.app/docs/enterprise/polygraph
(3 page views per day)
-
https://deploy-preview-35958--nx-docs.netlify.app/docs/concepts/synthetic-monorepos
(3 page views per day)
-
https://deploy-preview-35958--nx-docs.netlify.app/docs/enterprise/metadata-only-workspace
(0.4 page views a day)


## Related Issue(s)

DOC-522

<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/docs-conf-banner-e2599b5b)
<!-- polygraph-session-end -->
2026-06-11 15:56:20 -04:00
Jason Jean 1345e487fb fix(repo): correct config path in update-repos scripts and use next migrate CLI (#35968)
## 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 -->
2026-06-11 15:34:11 -04:00
Jason Jean 681b5928ac fix(core): exclude NX_CLOUD_ env vars from daemon env reflection (#35961)
## 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 -->
2026-06-11 15:33:57 -04:00
Jack Hsu 8b66c0c504 docs(misc): add v23 to docs version switcher (#35963)
## Current Behavior

Version dropdown shows v22 as current with no v22 archive link.

## Expected Behavior

v23 is current; v22 points to https://22.nx.dev/docs alongside
v21/v20/v19.

## Related Issue(s)

<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/docs-update-misc-updates-v23-837b8d30)
<!-- polygraph-session-end -->
2026-06-11 12:52:25 -04:00
Jason Jean 235e841523 chore(repo): migrate to nx 23.0.0-rc.0 (#35952)
## 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 -->
2026-06-11 16:35:05 +02:00
Jason Jean a5d1122fe1 fix(core): allow analytics requests through Claude Code sandbox network filter (#35949)
## 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>
2026-06-10 22:24:49 -04:00
Leosvel Pérez Espinosa 7fedcc89c6 fix(vite): improve vitest 4 migration to better handle vitest workspace config (#35940)
## 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>
2026-06-10 19:13:52 -04:00
Leosvel Pérez Espinosa 7dcd96927f feat(core): report analytics events for the nx migrate flow (#35937)
## 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>
2026-06-10 19:13:18 -04:00
Craigory Coppola fc4635d2c8 fix(misc): fix gitlab ci workflow for new repos and merge requests (#34237)
## 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
2026-06-10 16:54:31 -04:00
Jack Hsu e4d3571514 docs(nx-dev): document nx migrate agentic flow and modes (#35917)
This PR updates our docs to include new `nx migrate` features from v23.

Recommend running bare `nx migrate` (Nx 23+) and document the new
behavior:

- **Installation** lead with `nx migrate` and point to `Automate
updating dependencies` feature page.
- **Automate updating dependencies** adds the
`--include=required|optional|all` flag and agentic flow.
- **Advanced Update** documents `--include`, `--multi-major-mode`,
`--agentic` / `--validate`, and the `nx.json` `migrate` defaults.
- **Nx Console Migrate UI** documents the AI badge and the prompt-only /
hybrid card states.

Also moved advanced guide and Nx Console Migrate UI to Knowledge Base as
to not clutter up the feature sidebar section. The advanced guide is
linked to from the migrate feature page.

<img width="498" height="389" alt="image"
src="https://github.com/user-attachments/assets/6d828555-4cfc-4be0-9ac4-4b36258781c4"
/>

## Preview

-
https://deploy-preview-35917--nx-docs.netlify.app/docs/getting-started/installation
-
https://deploy-preview-35917--nx-docs.netlify.app/docs/features/automate-updating-dependencies
-
https://deploy-preview-35917--nx-docs.netlify.app/docs/guides/tips-n-tricks/advanced-update
-
https://deploy-preview-35917--nx-docs.netlify.app/docs/guides/nx-console/console-migrate-ui

## Related Issue(s)

NXC-4453

<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/docs-agentic--migrate-de6a34c5)
<!-- polygraph-session-end -->
2026-06-10 16:17:45 -04:00
Jason Jean ab099bdb94 fix(misc): declare continuous on inferred executor targets (#35941)
## 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.
2026-06-10 15:43:50 -04:00
polygraph-app[bot] aa9ce7a469 fix(misc): rename createNodesV2 value usages in v23 migration, not just imports (#35930)
## 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>
2026-06-10 14:51:51 -04:00
Craigory Coppola fefdfa2795 chore(repo): disable diagnose-sandbox-report skill to encourage use of cloud prompt (#35939)
## 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 #
2026-06-10 14:32:30 -04:00
polygraph-app[bot] 2a0c57217b fix(webpack): add webpack tooling to devDependencies in the v23 migration (#35944)
## 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>
2026-06-10 18:31:36 +00:00
Jack Hsu 8048d44738 docs(misc): align compat matrices with v23 ranges (#35943)
## Current Behavior

Technology docs version tables drift from v23 peer dep ranges (eslint
missing ^10, vitest page cites @nx/vite with ^1-^4, detox/nuxt/remix
floors stale). Nx version matrices stop at 22.x.

## Expected Behavior

Supported-version tables match packages/*/package.json peer deps. 23.x
rows added to TypeScript, Node (Node 20 dropped), NestJS and createNodes
matrices.

## Related Issue(s)

<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/docs-update-misc-updates-v23-837b8d30)
<!-- polygraph-session-end -->
2026-06-10 14:04:05 -04:00
Jack Hsu 4aa2271dc8 chore(nx-dev): upgrade next to 16 (from EOL 14) (#35923)
## 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>
2026-06-10 13:28:17 -04:00
Jack Hsu 7a9b3ca5ce feat(misc): prompt analytics earlier in init flow (#35922)
This PR moves the analytics prompt for `nx init` to after the Cloud
prompt so we match the CNW experience. Currently, it will prompt when
the first task is run.

<img width="1284" height="479" alt="Screenshot 2026-06-09 at 5 04 08 PM"
src="https://github.com/user-attachments/assets/d949d97b-8e21-46f7-95fc-356de9f2c863"
/>

Also captures response.

## Related Issue(s)

N/A

<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/capture-analytics-opt-in-22331534)
<!-- polygraph-session-end -->
2026-06-10 11:18:28 -04:00
polygraph-app[bot] b2f11a1e31 chore(repo): migrate to nx 23.0.0-beta.25 (#35928)
## 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>
2026-06-10 11:16:23 -04:00
Jason Jean 6598a0bb29 chore(core): rename supportsOptionalUpdates to supportsOptionalMigrations (#35924)
## 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>
2026-06-10 11:11:15 -04:00
Jason Jean e394d8b99a chore(release): stabilize custom-registries e2e by capping npm fetch-retry backoff (#35925)
## 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).
2026-06-09 18:43:49 -04:00
Jason Jean 27f9ae0a37 chore(repo): finish migrating builds off the workspace-root dist (#35915)
## 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>
2026-06-09 18:01:46 -04:00
Jason Jean 4748e485f3 chore(repo): migrate remaining 4 packages to nodenext module resolution (#35919)
## 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.
2026-06-09 16:04:39 -04:00
Jason Jean b9a0582454 feat(misc): multi-version support compliance for detox, expo, react-native, and remix (#35885)
## 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.
2026-06-09 18:01:00 +00:00
Jason Jean d39daeb9b8 fix(repo): increase FreeBSD publish VM memory to prevent OOM (#35914)
## 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.
2026-06-09 13:44:57 -04:00
Jason Jean 8039b7bae3 feat(misc): remove migrations prior to v21 in preparation for v23 (#35909)
## 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>
2026-06-09 16:48:09 +00:00
Justin McLellan d3bf911598 fix(release): scope ambiguous-scope check to active release group's projects (#35745)
## 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>
2026-06-09 18:02:47 +02:00
Leosvel Pérez Espinosa 38ca20b45e feat(core): extend nx migrate --include to any package that supports optional updates (#35905)
## 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 -->
2026-06-09 11:18:48 -04:00
Jason Jean 46d495d2f4 fix(core): show agentic migration prompt descriptions only for the focused choice (#35908)
## 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
2026-06-09 08:14:20 +00:00
Craigory Coppola f542d343b5 chore(repo): remove vestigial CircleCI config (#35907)
## 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 -->
2026-06-08 18:01:19 -04:00
Jason Jean 4bb6115625 chore(repo)!: migrate remaining packages to local dist build (#35900)
## 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>
2026-06-08 17:38:20 -04:00
Leosvel Pérez Espinosa 5a33ce62c1 fix(core): respect package manager minimum release age in nx migrate (#35902)
## 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>
2026-06-08 14:17:57 -04:00
Steven Nance 45a6a81b59 docs(nx-cloud): remove + variant resource classes from launch templates page (#35903)
## 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
2026-06-08 13:35:07 +00:00
Jason Jean 0596b18dd5 chore(repo): update nx to 23.0.0-beta.24 (#35898)
Updating Nx from 23.0.0-beta.22 to 23.0.0-beta.24

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-06-06 11:08:23 -04:00
Jason Jean 6d11894141 fix(remix): correct migration implementation path for flat-build packages (#35899)
## 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.
2026-06-06 09:44:08 -04:00
Jason Jean 0011a735cb fix(repo): use import type for type-only @nx/devkit imports in copy-a… (#35897)
…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 #
2026-06-06 00:30:39 +00:00
Jason Jean 5e8ee67275 fix(react-native): fix TS2742 build error and retarget rollup/webpack migrations to beta.24 (#35896)
## 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
2026-06-05 21:48:25 +00:00
Craigory Coppola 267e667b38 fix(dotnet)!: graduate @nx/dotnet — drop experimental banner and the /plugin export (#35895)
## 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>
2026-06-05 16:49:51 -04:00
Craigory Coppola 7601aaa55e fix(core): respect NX_PREFER_TS_NODE over native type stripping (#35892)
## 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>
2026-06-05 16:40:39 -04:00
Craigory Coppola 94d9358e0a feat(core): add migrations for createNodesV2 -> createNodes rename (#35893)
## 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.
2026-06-05 15:46:25 -04:00
Jason Jean 57abbb33bf chore(repo): bump workspace-plugin version and fix freebsd release (#35894)
## 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.
2026-06-05 19:15:36 +00:00
Jack Stevenson 11cf46db65 feat(misc): add --trustThirdPartyPreset flag to skip confirmation prompt (#35827)
## 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
2026-06-05 15:02:30 -04:00
Jason Jean 3a33712668 fix(repo)!: migrate remaining first-party plugins to local dist build (M2-M5) (#35785)
## 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>
2026-06-05 13:30:19 -04:00
Jason Jean 57e37c3659 fix(core): keep daemon alive when a recompute's plugin load fails (#35705)
## 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>
2026-06-05 13:26:57 -04:00
Jason Jean 951cd8b9d4 chore(testing): speed up and stabilize the e2e suite (#35303)
## 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>
2026-06-05 13:16:41 -04:00
Leosvel Pérez Espinosa 20b948bfbd feat(core): add JSON schema for migrations.json files (#35888)
## 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>
2026-06-05 12:59:08 -04:00
Juri f829f28fb8 docs(misc): relink automate-updating-dependencies in sidebar
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.
2026-06-05 17:23:37 +02:00
Leosvel Pérez Espinosa ac9792fa21 fix(core): respect --no-interactive across all nx migrate prompts (#35884)
## 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 -->
2026-06-05 10:31:19 -04:00
Leosvel Pérez Espinosa 3eaf95bdb6 fix(core): pre-authorize agentic handoff writes and consult the user before failing a step (#35889)
## 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>
2026-06-05 10:30:05 -04:00
Leosvel Pérez Espinosa 52760c58b3 feat(angular): deprecate SCAM generators (#35887)
## 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>
2026-06-05 10:06:04 -04:00
polygraph-snapshot-app[bot] 2caa22d5e9 feat(core): support prompt-only and hybrid migrations in Nx Console UI (#35822)
## 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>
2026-06-05 14:18:05 +02:00
Jack Hsu 925eda14e5 fix(nextjs): multi-version support compliance (NXC-4395) (#35870)
## 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>
2026-06-05 09:57:07 +02:00
Jack Hsu 51017485cf fix(react): multi-version support compliance (NXC-4399) (#35872)
## 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>
2026-06-05 09:56:50 +02:00
Jason Jean 193b8fce0b fix(misc): multi-version support compliance for rollup, webpack, and module-federation (#35860)
## 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>
2026-06-05 09:56:28 +02:00
Jason Jean 0862c2f0f4 fix(misc): publish migration prompt files and report the real migrate fetch error (#35886)
## 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>
2026-06-04 20:48:29 -04:00
Leosvel Pérez Espinosa e11e30acf5 fix(js): align tsconfig include and exclude inputs inference with TypeScript behavior (#35876)
## 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>
2026-06-04 17:07:02 -04:00
Craigory Coppola 2ded1d14b2 feat(devkit)!: deprecate the standalone parameter of addProjectConfiguration (#35883)
## 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>
2026-06-04 16:05:17 -04:00
Jack Hsu 0585f628eb docs(nx-dev): show spread token for extending target defaults (#35871)
## Current Behavior

The Configuring Tasks tutorial only covers overriding `targetDefaults`.

## Expected Behavior

Adds a section showing the `"..."` spread token to extend a target
default for a project instead of replacing it.

Preview:
https://deploy-preview-35871--nx-docs.netlify.app/docs/getting-started/tutorials/configuring-tasks#extending-target-defaults-for-a-project

## Related Issue(s)

DOC-509

<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/docs-spread-6df4621c)
<!-- polygraph-session-end -->
2026-06-04 13:53:29 -04:00
Jack Hsu e7d9625c9d docs(nx-cloud): mark Manual DTE as an Enterprise-only feature (#35864)
## Current Behavior

Docs present Manual DTE without noting it requires the Nx Enterprise
plan.

## Expected Behavior

Each page that documents Manual DTE notes it is an Enterprise plan
feature and points other plans to Nx Agents.

## Related Issue(s)

DOC-513

## Previews

-
https://deploy-preview-35864--nx-docs.netlify.app/docs/guides/nx-cloud/manual-dte
-
https://deploy-preview-35864--nx-docs.netlify.app/docs/features/ci-features/resource-usage#resource-metrics-with-manual-dte
-
https://deploy-preview-35864--nx-docs.netlify.app/docs/features/ci-features/self-healing-ci#configure-your-ci-pipeline
-
https://deploy-preview-35864--nx-docs.netlify.app/docs/guides/nx-cloud/enable-ai-features#manual-dte

<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/docs-manual-dte-7f9e1e20)
<!-- polygraph-session-end -->
2026-06-04 13:18:08 -04:00
Jack Hsu 346353de5c feat(webpack)!: deprecate webpack/rspack config compose helpers (#35867)
## 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 -->
2026-06-04 12:50:56 -04:00
Leosvel Pérez Espinosa d699ff3e58 fix(core): disallow nx migrate --interactive for Nx v23+ targets (#35874)
## 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 -->
2026-06-04 12:45:59 -04:00
Jason Jean ef02d3efd4 fix(repo): set workspace-plugin type to module to silence Node strip-types warning (#35880)
## 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).
2026-06-04 12:25:32 -04:00
Benjamin Cabanes ebc46265e6 chore(repo): add publicly accessible brand kit download assets (#35882)
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/`.
2026-06-04 16:03:41 +00:00
Juri 82c1825486 docs(module-federation): add Vite Module Federation example page
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.
2026-06-04 15:45:03 +02:00
Craigory Coppola dc3aab552f fix(dotnet): declare obj output for publish/pack and track vitest dep spec tsconfig input (#35858)
## 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>
2026-06-04 07:05:45 -04:00
Abdullah Alaqeel a2cb934067 fix(js): use TypeScript readConfigFile instead of JSON.parse in resolvePathsBaseUrl (#35539)
## 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 #35537
Fixes #35824
2026-06-04 10:37:32 +02:00
Jason Jean 714c2313ed chore(repo): bump default Node.js version to 26.3.0 (#35847)
## 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.
2026-06-04 04:43:16 +00:00
Craigory Coppola 44ceb6bd7f feat(core)!: rename CreateNodes V2 types to canonical OG names (#35386)
## 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
2026-06-04 03:59:40 +00:00
Jason Jean bf90783500 fix(core): use workspace package manager when fetching migrations via install (#35866)
## 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.
2026-06-04 03:30:21 +00:00
Jack Hsu 96081ab991 feat(nextjs)!: deprecate withNx function (#35861)
## 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 -->
2026-06-03 18:55:35 -04:00
Jason Jean 62356e481a chore(repo): update nx to 23.0.0-beta.22 (#35865)
Updating Nx from 23.0.0-beta.21 to 23.0.0-beta.22
2026-06-03 21:04:58 +00:00
Jason Jean fc8444b3ce fix(angular-rspack): dispose stylesheet bundler so one-shot builds exit (#35869)
## 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.
2026-06-03 20:04:18 +00:00
Minh Lê c0b8c34a0c fix(core): correct 'occured' typo to 'occurred' in error messages (#35852)
## 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.
2026-06-03 14:09:28 -04:00
Leosvel Pérez Espinosa f8364e5167 fix(core): gate nx migrate first-party/third-party modes to Nx v23+ targets (#35830)
## 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 -->
2026-06-03 11:53:15 -04:00
Craigory Coppola 6f0492098e fix(vue): multi-version support compliance for @nx/vue and @nx/nuxt (#35845)
## 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>
2026-06-03 11:14:20 -04:00
Jason Jean d037667357 feat(angular): deprecate convert-to-with-mf generator (#35862)
## 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>
2026-06-03 11:03:06 -04:00
Leosvel Pérez Espinosa 7438746d0a fix(core): support local plugins using NodeNext .js import specifiers (#35834)
## 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.
2026-06-03 15:22:05 +02:00
Alex Croteau 1c948000ba feat(core): avoid redundant rematch in findMatchingConfigFiles (#35793)
## 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>
2026-06-02 22:25:28 -04:00
Leosvel Pérez Espinosa 9f20267edc fix(vite): enforce multi-version support windows for @nx/vite and @nx/vitest (#35671)
## 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>
2026-06-02 22:03:13 +00:00
Jason Jean fc6b3e7eaa fix(core): forward full task graph to batch executors under DTE (#35859)
## 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.
2026-06-02 18:02:18 -04:00
Jack Hsu 4d6eddf884 feat(vite)!: deprecate the nxViteTsPaths and nxCopyAssetsPlugin helpers (#35664)
## 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>
2026-06-02 17:35:25 -04:00
Olawuni Olagoke 00270b554c fix(js): handle already-published version errors in release-publish executor (#35782)
## 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
2026-06-02 16:28:54 -04:00
Craigory Coppola 6ca3f3aff4 fix(testing): enforce jest 29-30 multi-version compliance for @nx/jest (#35758)
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>
2026-06-02 16:15:56 -04:00
Jack Hsu 98e9643a02 docs(nx-cloud): add platform pages for Nx Cloud add-ons (#35857)
## Current Behavior

Resource usage and sandboxing docs lived under guides; there were no
pages for the dedicated compute cluster, Docker layer caching, or the
Docker/npm read-through caches.

## Expected Behavior

Six consolidated platform-feature pages under `features/ci-features`,
grouped in a new "Nx Cloud add-ons" sidebar section (Resource usage,
Sandboxing, Dedicated compute cluster, Docker layer caching, Docker
read-through cache, npm read-through cache). Resource usage moved out of
guides with a redirect, and the launch-templates DinD note now points to
the dedicated compute cluster instead of being enterprise-only.

## Previews:

-
https://deploy-preview-35857--nx-docs.netlify.app/docs/features/ci-features/resource-usage
-
https://deploy-preview-35857--nx-docs.netlify.app/docs/features/ci-features/dedicated-compute-cluster
-
https://deploy-preview-35857--nx-docs.netlify.app/docs/features/ci-features/sandboxing
-
https://deploy-preview-35857--nx-docs.netlify.app/docs/features/ci-features/docker-layer-caching
-
https://deploy-preview-35857--nx-docs.netlify.app/docs/features/ci-features/docker-read-through-cache
-
https://deploy-preview-35857--nx-docs.netlify.app/docs/features/ci-features/npm-read-through-cache
-
https://deploy-preview-35857--nx-docs.netlify.app/docs/reference/nx-cloud/launch-templates#launch-templatestemplate-nameimage

<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/docs-sandbox-resource-usage-f431c8fc)
<!-- polygraph-session-end -->

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
2026-06-02 15:19:24 -04:00
Miroslav Jonaš a4b1029eca docs(nx-dev): determinism requirement for inferred plugins (#35741)
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>
2026-06-02 14:48:34 -04:00
Miroslav Jonaš 99263441c1 fix(release): require docker config for docker versioning (#35841)
<!-- 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 #
2026-06-02 14:08:26 -04:00
polygraph-snapshot-app[bot] b2657e3818 feat(linter): deprecate ESLint v8 support (#35819)
## 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>
2026-06-02 14:07:00 -04:00
Thiago Zanluca 7fd4207b50 fix(release): scope fallback to project history for new packages (#35323) 2026-06-02 21:57:22 +04:00
Miroslav Jonaš 7cdf5c1f37 docs(core): fix show target examples (#35842)
## 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 -->
2026-06-02 13:51:26 -04:00
Leosvel Pérez Espinosa b801ff7560 feat(core): feed migration docs to agents in nx migrate (#35835)
## 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>
2026-06-02 13:42:49 -04:00
Jason Jean 2f96710b45 chore(repo): update nx to 23.0.0-beta.21 (#35850)
Updating Nx from 23.0.0-beta.20 to 23.0.0-beta.21
2026-06-02 13:38:37 -04:00
Leosvel Pérez Espinosa 6ec056fa9a feat(testing)!: remove deprecated skipSetupFile and setupFile jest options (#35588)
## 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>
2026-06-02 12:13:22 -04:00
Leosvel Pérez Espinosa cd9bdc3979 feat(core): add a migrate configuration section to nx.json (#35831)
## 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 -->
2026-06-02 11:50:58 -04:00
Leosvel Pérez Espinosa 6c9d5e054e fix(core): read pod cgroup limits instead of node limits in resource metrics (#35622)
## 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.
2026-06-02 11:26:40 -04:00
Craigory Coppola 8403bca978 fix(repo): rename publish VERSION env var to avoid MSBuild leak (#35849)
## 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>
2026-06-02 11:03:23 -04:00
Leosvel Pérez Espinosa e957610695 chore(repo): drop start-date analysis from nightly e2e failure reporter (#35853)
## 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.
2026-06-02 10:56:10 -04:00
Jason Jean 36a95b2b61 chore(repo): print PR urls in update-repos script instead of creating PRs via gh (#35848)
## 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
2026-06-01 18:18:35 -04:00
Steven Nance a4991b6d36 fix(js): support auto mode for non-pnpm lock files in affected detection (#35141)
## 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>
2026-06-01 16:09:19 -04:00
Thomas Dekiere cd5f0b7029 fix(release): skip expensive changelog operations when changelogs are disabled (#35405) 2026-06-02 00:05:17 +04:00
polygraph-snapshot-app[bot] 9371aa571d fix(angular): bump zoneJsVersion to ~0.16.0 to align with Angular v21 (#35799)
## 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>
2026-06-01 14:00:16 -04:00
polygraph-snapshot-app[bot] 412a37a16c fix(misc): multi-version compliance for @nx/express, @nx/node, and @nx/nest (#35807)
## 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>
2026-06-01 07:01:49 -04:00
Jack Hsu 4125769ae0 chore(misc): update to 23.0.0-beta.20 (#35821)
Update to latest beta. Ran migrations.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-05-29 16:43:25 -04:00
Jack Hsu 0939540be1 feat(module-federation): deprecate old generators and add new consumer/provider generators (#35825)
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>
2026-05-29 14:21:50 -04:00
Jack Hsu 93d1470467 chore(misc): bump storybook to 10.2.10 to patch CVE-2026-27148 and CVE-2025-68429 (#35832)
Bumps Storybook and the co-versioned @storybook/* packages (addon-docs,
react-vite, react-webpack5) to 10.2.10.

Patches:
- CVE-2026-27148 (CRITICAL, CVSS 9.9) - Storybook dev-server WebSocket
origin-validation bypass -> XSS/RCE on the developer machine.
- CVE-2025-68429 (HIGH) - .env secrets embedded in static Storybook
build output.

Dev-dependency only; no production runtime impact. Part of a coordinated
cross-repo security patch.

<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/security-patch-20260528-f996c645)
<!-- polygraph-session-end -->
2026-05-29 13:07:33 -04:00
polygraph-snapshot-app[bot] b7fc1c5a47 fix(linter): multi-version support compliance for @nx/eslint and @nx/eslint-plugin (#35811)
## 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>
2026-05-29 16:39:32 +00:00
polygraph-snapshot-app[bot] eda1ee675e fix(angular-rspack): apply multi-version compliance to @nx/angular-rspack(-compiler) (#35806)
## 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>
2026-05-29 17:23:12 +02:00
polygraph-snapshot-app[bot] 6a19351a03 fix(storybook): multi-version support compliance (#35770)
## 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>
2026-05-29 17:16:01 +02:00
polygraph-snapshot-app[bot] ef62e169aa fix(core): correct nx migrate commit-tally undercount and consolidate outcome state (#35820)
## 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>
2026-05-29 16:15:33 +02:00
Jack Hsu 7e87698b6b fix(core): update tmp to 0.2.6 due to CVE-2026-44705 (#35813)
Bump `tmp` to `0.2.6`. Need exclusion for minimum release age just for
`0.2.6` so we can patch immediately.

See
https://linear.app/nxdev/issue/NXC-4494/patch-vulnerable-tmp-dependency-in-nx

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-05-27 15:01:22 -04:00
Jack Stevenson e03c511edf fix(core): allow nx build scripts in generated pnpm-workspace.yaml (#35564)
Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
Co-authored-by: Miroslav Jonaš <missing.manual@gmail.com>
2026-05-27 13:18:32 -04:00
polygraph-snapshot-app[bot] 1951413a93 docs(nx-cloud): remove Administration scope from Nx Cloud GitHub App permissions (#35808)
## 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>
2026-05-27 09:43:01 -07:00
Leosvel Pérez Espinosa e750b03720 feat(core): add agentic mode to nx migrate --run-migrations (#35718)
## 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>
2026-05-27 16:39:44 +02:00
Jack Hsu 53f71042fe docs(nx-dev): promote setting up CI to top-level Getting Started (#35802)
This PR moves the CI portion of the tutorial out to its own top-level
getting started page. The traffic to the tutorial page is low, and this
move surfaces the instructions more prominently.

Preview:
https://deploy-preview-35802--nx-docs.netlify.app/docs/getting-started/setup-ci

Redirects are set up from old CI page to new page.

Also updates the https://nx.dev/docs/guides/nx-cloud/access-tokens page
slightly since the settings have changed since this pages was written.

Closes DOC-503
2026-05-26 14:32:10 -04:00
Jack Hsu af294c425b fix(core): update brace-expansion and yaml (#35790)
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>
2026-05-25 12:41:41 -04:00
polygraph-snapshot-app[bot] 150a46beae fix(core): always set task.cache as an explicit boolean (#35778)
## 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>
2026-05-22 22:36:17 +00:00
Craigory Coppola 51545462a8 fix(nx-plugin): plugin lint checks should use dependentTasksOutputFiles (#35755)
<!-- 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>
2026-05-22 18:00:20 -04:00
polygraph-snapshot-app[bot] a90001a41f docs(misc): remove references to the Nx Cloud in-app CNW flow (#35779)
## 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>
2026-05-22 20:57:19 +00:00
Jason Jean d30fba47aa chore(repo): update nx to 23.0.0-beta.18 (#35776)
Updating Nx from 23.0.0-beta.17 to 23.0.0-beta.18
2026-05-22 16:21:40 -04:00
Jack Hsu 378709718c docs(misc): add structural anti-AI rules to docs style guide (#35777)
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>
2026-05-22 16:17:06 -04:00
Jason Jean b6fc86ef14 fix(testing): publish @nx/vitest, @nx/cypress, @nx/playwright, @nx/vite from local dist (#35743)
## 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>
2026-05-22 16:13:23 -04:00
Jason Jean 294344300f cleanup(core): use calculateHashesForCreateNodes for batch hashing in plugins (#35561)
## 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>
2026-05-22 16:12:33 -04:00
Jason Jean 8ca1434243 feat(js): support pnpm 11.2.2 (#35772)
## 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>
2026-05-22 13:50:30 -04:00
Jason Jean c96224d104 chore(repo): set pnpm minimumReleaseAge with nx exclusions (#35775)
## 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)
2026-05-22 13:03:41 -04:00
Jason Jean 461aad7f3e fix(repo): run dotnet restore before macos e2e job (#35774)
## 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
2026-05-22 16:25:24 +00:00
polygraph-snapshot-app[bot] ed87abfee0 cleanup(testing): consolidate cypress version helpers (#35773)
## 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>
2026-05-22 18:19:12 +02:00
polygraph-snapshot-app[bot] 5626a365ac fix(bundling): multi-version support compliance for @nx/esbuild (#35768)
## 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>
2026-05-22 12:16:58 -04:00
Jason Jean a48f44f3fe fix(repo): run dotnet restore before publish (#35771)
## 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
2026-05-22 14:45:10 +00:00
Jack Hsu 6fa8db1bfc feat(misc)!: drop deprecated webpack plugin re-exports + v23 polish (#35659)
## Current Behavior

`@nx/react` and `@nx/webpack` keep deprecated re-exports for
`NxReactWebpackPlugin` and `NxTsconfigPathsWebpackPlugin`. Four v23
migration polish items remain from end-to-end testing: devkit allowlist
drift (2 missing internal symbols), rollup `rollup-plugin-typescript2`
orphan devDep, playwright executor schema missing `x-deprecated`,
rolldown rename selector misses string-literal keys.

## Expected Behavior

- Deprecated re-exports removed; new `update-23-0-0` migrations rewrite
imports to the sub-paths (`@nx/react/webpack-plugin`,
`@nx/webpack/tsconfig-paths-plugin`).
- Devkit `DEVKIT_INTERNAL_SYMBOLS` gains `emitPluginWorkerLog`,
`throwForUnsupportedVersion`.
- Rollup migration also strips `rollup-plugin-typescript2` from devDeps.
- Playwright executor schema gains canonical `x-deprecated`.
- Rolldown rename selector matches both Identifier and StringLiteral
keys.
- Internal vite/react generators emit `rolldownOptions` instead of
`rollupOptions` for new v23 projects.

## Related Issue(s)

NXC-4318

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
2026-05-22 10:19:22 -04:00
polygraph-snapshot-app[bot] 526418996d fix(angular): only add @oxc-project/runtime on the vitest-analog path (#35734)
## 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>
2026-05-21 22:23:36 -04:00
polygraph-snapshot-app[bot] 51edd56239 fix(core): allow local plugin subpath imports without custom conditions (#35751)
## 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>
2026-05-21 22:11:28 -04:00
Craigory Coppola e3b47e75cf fix(dotnet): include Directory.*.* files in inputs (#35738)
## 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 #
2026-05-21 21:42:25 -04:00
Leosvel Pérez Espinosa 6c3a0af0b2 fix(js): multi-version support compliance (#35725)
## 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>
2026-05-21 21:38:39 -04:00
Craigory Coppola e1fe753ce9 chore(repo): preserve preset dist ignore in graph jest configs (#35754)
## 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.
2026-05-21 19:28:15 -04:00
Jason Jean f1993bbc62 feat(core): add shell tab-completion (bash, zsh, fish, powershell) (#34951)
## 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>
2026-05-21 19:27:54 -04:00
Craigory Coppola c4880d3bc8 docs(core): note that task-specific env vars are not loaded in batch mode (#35759)
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-21 21:49:06 +00:00
Jason Jean e2c8959b90 chore(misc): pin corepack default pnpm to packageManager version (#35765)
## 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.
2026-05-21 19:59:28 +00:00
Jason Jean b6858ba197 chore(module-federation): re-enable webpack-based react e2e tests (webpack 5.107.1 fix is live) (#35764)
## 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)
2026-05-21 15:03:53 -04:00
Jack Hsu 5426aa4514 docs(misc): remote cache (#35763)
e.g.
https://deploy-preview-35763--nx-docs.netlify.app/docs/reference/remote-cache-plugins/s3-cache/overview
2026-05-21 14:52:26 -04:00
Benjamin Staneck c3f1d8afee fix(core): detect vscode copilot ai agent (#35757) 2026-05-21 14:04:38 -04:00
Craigory Coppola 60f62909f7 docs(core): document the convert-target-defaults-to-array migration (#35752)
## 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>
2026-05-21 11:58:56 -04:00
Craigory Coppola e9451ecc98 docs(core): document spread token in nx.json target defaults reference (#35760)
## 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>
2026-05-21 11:57:11 -04:00
Jason Jean 878b103129 fix(js): fall back to npm publish when bun publish fails with auth error (#35756)
## 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 #
2026-05-21 10:08:29 -04:00
Jason Jean 6b09993c76 chore(repo): update nx to 23.0.0-beta.17 (#35749)
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-05-21 09:22:54 -04:00
polygraph-snapshot-app[bot] 47ac4924aa docs(nx-cloud): note Nx 22.1 requirement on manual DTE upload-agent-metrics examples (#35750)
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>
2026-05-21 12:28:03 +02:00
Jason Jean a51e340b78 chore(module-federation): skip webpack-based e2e tests pending webpack 5.107.0 compat (#35753)
## 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>
2026-05-20 21:09:10 +00:00
polygraph-snapshot-app[bot] 6e6edabb21 chore(repo): disable Roslyn shared compilation (#35724)
Co-authored-by: rarmatei <matei.rar@gmail.com>
2026-05-19 16:55:22 -04:00
Leosvel Pérez Espinosa b3127f268f feat(misc): convert prompt generator migrations to use prompt field (#35688)
## 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>
2026-05-19 15:38:08 -04:00
Jason Jean f17365050a fix(rsbuild): infer build outputs from distPath.root directly (#35707)
## 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
2026-05-19 15:32:26 -04:00
Leosvel Pérez Espinosa e0cb5a9597 chore(repo): drop unused root devDeps (postcss tooling + 3D suite) (#35717)
## 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).
2026-05-19 15:16:32 -04:00
Copilot 8ef4705d27 fix(misc): skip $ escaping in file paths on windows (#35692)
`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>
2026-05-19 14:57:17 -04:00
Artur a1749798eb fix(core): handle object form of bin field in getPrettierPath (#35680)
Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
2026-05-19 14:48:24 -04:00
Jason Jean 05c55890f3 fix(core): treat undefined task parallelism as parallel when scheduling (#35736)
## 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
2026-05-19 13:59:29 -04:00
Jason Jean 67d3502adc fix(linter)!: migrate @nx/eslint and @nx/eslint-plugin to local dist build (#35720)
## 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>
2026-05-19 13:58:40 -04:00
Benjamin Cabanes 2dd98115d0 docs(nx-dev): refresh docs header CTA and star widget styling (#35702)
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
2026-05-19 13:55:20 -04:00
Craigory Coppola 8adfd15961 cleanup(core): remove deprecated non-native scanner and stripSourceCode (#35729)
## 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>
2026-05-19 13:17:55 -04:00
Craigory Coppola adfe96a40a cleanup(core): drop stale @nrwl dedup TODO in nx report (#35730)
## 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>
2026-05-19 13:16:44 -04:00
Jack Hsu 1a58893692 fix(js): always register transpiler for registerTsProject so subsequent require(file) will work (#35735)
This fixes conformance's usage of `registerTsProject`.
2026-05-19 13:01:41 -04:00
Leosvel Pérez Espinosa 76d7728d1d fix(core): resolve local plugin subpath imports from source (#35631)
## 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>
2026-05-18 11:30:02 +02:00
Jason Jean c1f9851a67 chore(repo): update nx to 23.0.0-beta.16 (#35721)
Updating Nx from 23.0.0-beta.15 to 23.0.0-beta.16

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-05-18 09:36:52 +02:00
Jason Jean 0f298759a3 fix(testing)!: migrate @nx/jest to local dist build (#35713)
## 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>
2026-05-17 17:39:48 -04:00
Leosvel Pérez Espinosa e6e8fce87c chore(repo): replace glob with tinyglobby (#35715)
## 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.
2026-05-17 14:22:16 -04:00
Jason Jean 6cec0fe406 chore(repo): update nx to 23.0.0-beta.15 (#35709)
Updating Nx from 23.0.0-beta.12 to 23.0.0-beta.15
2026-05-17 10:38:06 -04:00
Jack Hsu dc8e2f48da feat(core): enable native Node.js TypeScript stripping by default (#35608)
## 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
2026-05-17 09:29:53 -04:00
polygraph-snapshot-app[bot] 511cf698b7 fix(core): restore nx/src/index entrypoint for Nx Cloud client compatibility (#35712)
## 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>
2026-05-16 13:43:00 -04:00
Jason Jean 962f146742 fix(core): do not drop target defaults in 23.0.0 array migration (#35711)
## 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`.
2026-05-16 09:14:31 -04:00
polygraph-app[bot] 753819450e fix(js): add backwards-compatible ./src/internal export shim for conformance@4/5 (#35710)
## 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>
2026-05-16 09:12:23 -04:00
Jason Jean 8220f34c31 fix(core): remove deprecated initTasksRunner API (#35708)
## 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>
2026-05-15 20:27:04 -04:00
Jason Jean 4f14067546 fix(js): build to local dist and use nodenext (#35538)
## 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>
2026-05-15 18:41:15 -04:00
Jason Jean d841e7d9ac fix(rspack): multi-version support compliance for @nx/rspack and @nx/rsbuild (#35676)
## 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>
2026-05-15 15:59:04 -04:00
Craigory Coppola e736a6391c fix(core): preserve input order in createNodes plugin results (#35595)
## 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>
2026-05-15 15:58:48 -04:00
Craigory Coppola 40420b0aec feat(core): support filtered array-shape targetDefaults with projects and source (#35340) 2026-05-15 14:38:14 -04:00
Jason Jean b15a359624 fix(gradle): pin generated e2e project toolchain to installed JDK (#35703)
## 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.
2026-05-15 14:35:22 -04:00
AI-JamesHenry 26bd2b110e test(release): cover v23 adjustSemverBumpsForZeroMajorVersion default (#35704) 2026-05-15 14:11:09 -04:00
AI-JamesHenry 0698d06837 feat(release)!: drop deprecated releaseTag* flat properties and update v23 defaults (#35694) 2026-05-15 17:30:56 +00:00
Leosvel Pérez Espinosa e37ab421c0 chore(repo): add multi-version-compliance skill (#35701)
## 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`).
2026-05-15 17:54:26 +02:00
AI-JamesHenry 4412956cbc feat(core): rename nx watch --includeDependentProjects to --includeDependencies (#35699) 2026-05-15 15:44:49 +00:00
Jack Hsu 39775b0443 docs(misc): add fix sandbox violations guide (#35693)
This PR adds a guide for fixing sandbox violations. It will be linked
from the UI.

Preview:
https://deploy-preview-35693--nx-docs.netlify.app/docs/guides/nx-cloud/fix-sandbox-violations

## Related Issue(s)
Q-443

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2026-05-15 11:16:35 -04:00
Steven Nance 7a0bfbaa9e docs(nx-dev): link to @nx/owners plugin from code ownership concept page (#35698)
## 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.
2026-05-15 10:21:29 -04:00
Leosvel Pérez Espinosa 4b4d6da9ff feat(linter): allow prompt-only entries in migration nx-plugin-checks (#35700)
## 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`.
2026-05-15 09:56:35 -04:00
Jason Jean c593aba91a fix(core): warn instead of silently dropping legacy 'self'/'dependencies' dependsOn values (#35687)
## 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>
2026-05-14 23:23:19 +00:00
Leosvel Pérez Espinosa 2885b784af fix(core): use native graceful process tree shutdown (#33655)
## 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>
2026-05-14 18:26:34 -04:00
polygraph-app[bot] feb64ba493 docs(nx-dev): restore powerpack license as noindex reference page (#35690)
## Current Behavior

`/powerpack/license` 301s to `/docs/enterprise` via the `/powerpack/*`
wildcard in `_redirects`. Breaks the link from `@nx/key` (npmjs page).

## Expected Behavior

Add legacy EULA at `astro-docs` `/docs/reference/powerpack-license` with
`noindex,nofollow` head meta and a sidebar entry under Reference. Add
specific 301 from `/powerpack/license` to the new docs URL before the
existing `/powerpack/*` wildcard.


- Old page: https://20.nx.dev/powerpack/license
- New page:
https://deploy-preview-35690--nx-docs.netlify.app/docs/reference/powerpack-license
## Related Issue(s)

Fixes DOC-505

<!-- polygraph-session-start -->
---
[View session information
↗](https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/doc-505-powerpack-license-09d8ce8f)
<!-- polygraph-session-end -->

Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
2026-05-14 15:12:56 -04:00
Altan Stalker ff07225b27 docs(nx-cloud): simplify resource class specifications in credits pricing (#35685)
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>
2026-05-14 14:32:59 -04:00
Jason Jean dae59384d4 chore(repo): update nx to 23.0.0-beta.12 (#35689)
Updating Nx from 23.0.0-beta.11 to 23.0.0-beta.12
2026-05-14 17:27:56 +00:00
Leosvel Pérez Espinosa 70e2752597 fix(misc): stop inferring projects: 'self' in dependsOn entries (#35686)
## 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'`.
2026-05-14 12:12:40 -04:00
Leosvel Pérez Espinosa 64cf826f74 feat(core): support prompt field in migration entries (#35638)
## 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.
2026-05-14 16:51:19 +02:00
Jack Hsu 44192646f1 docs(misc): add nx-cloud get sandbox-reports to CLI reference (#35684)
Add Cloud CLI reference section for `nx-cloud get sandbox-reports` with
usage, options, etc.

Fixes DOC-504

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
2026-05-14 10:45:55 -04:00
polygraph-app[bot] 0a445cac3d fix(testing): correct yargs-parser import in getJestProjectsAsync (#35672)
## 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>
2026-05-14 10:45:03 -04:00
Leosvel Pérez Espinosa bc35b484e3 fix(testing): multi-version support compliance for @nx/cypress (#35670)
## 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.
2026-05-14 10:37:19 -04:00
Leosvel Pérez Espinosa 14aa79b2c6 chore(angular): remove old migration entries and orphaned sources (#35683)
## 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.
2026-05-14 09:27:17 -04:00
Jason Jean f13b8fc260 fix(linter): only rewrite workspace-package peer deps to workspace:* (#35423)
## 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`.
2026-05-14 09:39:43 +02:00
Jason Jean 36132f806b chore(core): remove unused replaceNrwlPackageWithNxPackage devkit utility (#35679)
## 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.
2026-05-14 08:18:00 +02:00
Leosvel Pérez Espinosa 7dcbf428f8 fix(core): improve nx migrate multi-major flag handling and feedback (#35673)
## 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.
2026-05-14 08:14:38 +02:00
Jason Jean 772e8be165 chore(repo): update nx to 23.0.0-beta.11 (#35681)
Updating Nx from 23.0.0-beta.10 to 23.0.0-beta.11
2026-05-13 20:32:00 -07:00
Jason Jean cb2a9c66b3 docs(core): explain devkit dependency type and nx exclusion for plugins (#35674)
## 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.
2026-05-13 17:52:11 -04:00
Louie Weng 934a70be61 chore(gradle): bump gradle project graph plugin version to 0.1.21 (#35678)
<!-- 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 #
2026-05-13 20:04:02 +00:00
Louie Weng 3d62867ea5 fix(gradle): add transitive:true to all tasks (#35677)
<!-- 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
2026-05-13 12:26:54 -07:00
Jason Jean f108b87f2a fix(core): freshness-gate daemon recompute + per-OS force-flush grace (#35650)
## 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>
2026-05-13 15:19:35 -04:00
Johan Vrolix fe39d0ae8e chore(core): nx plugin submission @anarchitects/nx-typeorm (#35668)
<!-- 
_[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>
2026-05-13 12:32:41 -04:00
Jason Jean d520730c89 chore(repo): update nx to 23.0.0-beta.10 (#35667)
Updating Nx from 23.0.0-beta.9 to 23.0.0-beta.10
2026-05-13 10:42:37 -04:00
Jason Jean 07b16e43d4 chore(core)!: build @nx/workspace to local dist and use nodenext (#35643)
## 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>
2026-05-12 22:27:49 -04:00
Jason Jean a4f5382d20 chore(core): bump Rust toolchain to 1.95.0 (#35665)
## 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)).
2026-05-12 19:49:56 -04:00
Leosvel Pérez Espinosa e84989fec9 fix(linter): improve convert-to-flat-config output fidelity (#35330)
## 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.
2026-05-12 18:19:03 -04:00
Craigory Coppola 6d1da6250d fix(core): warn before installing unknown npm packages as preset (#35644)
## 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).
2026-05-12 18:11:51 -04:00
Jason Jean aa091aefcd fix(angular-rspack): exclude eslint config from tailwind v4 source scan (#35663)
## 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.
2026-05-12 17:56:00 -04:00
Leosvel Pérez Espinosa 99d3f8f62d feat(core): add --mode and --multi-major-mode flags to nx migrate (#35497)
## 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>
2026-05-12 15:32:25 -04:00
Leosvel Pérez Espinosa 7bdb937ea0 chore(repo): align direct-declaration version drift via catalog (#35639)
## 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>
2026-05-12 11:24:45 -04:00
Leosvel Pérez Espinosa 698de49f21 fix(testing): exclude dist and out-tsc from default jest module path scan (#35619)
## 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>
2026-05-12 11:17:26 -04:00
Leosvel Pérez Espinosa e2ef134645 fix(testing): multi-version support compliance for @nx/playwright (#35642)
## 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>
2026-05-12 11:16:41 -04:00
Jason Jean 06bffdb044 feat(misc)!: remove deprecated js option from component generators (#35616)
## 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)
2026-05-12 08:55:05 -04:00
polygraph-app[bot] 643bde9afb fix(core): allow nx mcp to run outside of an Nx workspace (#35655)
## 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>
2026-05-12 11:38:15 +00:00
Adam Keenan a7a5701821 fix(gradle): support Windows file paths (#35184)
<!-- 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>
2026-05-12 01:14:13 -04:00
Charlie Croom 90f675e789 fix(core): correct TUI sidebar viewport height off-by-one (#34682)
_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>
2026-05-12 00:20:06 -04:00
Jason Jean 8378f40831 chore(core): remove dead TUI selection lifecycle helpers (#35649)
## 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>
2026-05-11 21:42:32 -04:00
Jason Jean 1b00584d89 fix(core)!: drop legacy 'self'/'dependencies' magic strings in dependsOn (#35648)
## 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)
2026-05-11 22:21:17 +00:00
Jason Jean 45fd32b388 fix(core): drain in-flight notify events in daemon force_flush_pending (#35646)
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>
2026-05-11 22:11:21 +00:00
Leosvel Pérez Espinosa eec2b7362e fix(core): keep TUI task selection on the in-progress section (#35640)
## 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>
2026-05-11 18:03:16 -04:00
Jack Hsu 30b713221c fix(misc): vite migration import fix and ai doc corrections (#35647)
## 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
2026-05-11 16:27:51 -04:00
Jason Jean 08f498196b chore(repo): update nx to 23.0.0-beta.9 (#35627)
Updating Nx from 23.0.0-beta.8 to 23.0.0-beta.9
2026-05-11 05:14:15 +00:00
Jason Jean 0fbb9654a8 feat(testing): add migration for Jest 30 snapshot guide link (#35629)
## 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.
2026-05-10 13:13:42 -04:00
Jason Jean feffbd1dba fix(testing): correct paths and reserve ports across flaky React MF e2e tests (#35633)
## 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.
2026-05-10 13:13:31 -04:00
Leosvel Pérez Espinosa 484ce6e5d5 fix(angular): multi-version support compliance (#35587)
## 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.
2026-05-10 10:12:15 +02:00
Jack Hsu 305cd56960 feat(bundling): add Vite 7 -> 8 migrations (#35614)
## 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>
2026-05-08 18:14:21 -04:00
Jack Hsu d43c0c0a7e feat(testing): bump cypress to 15.14 + remove stale Vite 8 guard (#35613)
## 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>
2026-05-08 17:52:25 -04:00
Jason Jean 9f8a6c2979 fix(core): support skipped batch tasks end-to-end and fix TUI double logs (#35617)
## 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>
2026-05-08 16:28:30 -04:00
Jack Hsu 2445010810 chore(misc): migrate tailwind v3 to v4 (#35594)
## Current Behavior

graph apps and nx-dev pin tailwindcss 3.4.4 with JS configs and v3
directives.

## Expected Behavior

tailwindcss 4.1.11 via @tailwindcss/postcss. JS configs replaced with
CSS-based @import 'tailwindcss', @plugin, @source, @custom-variant.
astro-docs already on v4.

v3 utility renames applied across graph + nx-dev sources (per the
[Tailwind v4 upgrade
guide](https://tailwindcss.com/docs/upgrade-guide#renamed-utilities)) so
visuals don't shift:

- shadow-sm -> shadow-xs, shadow -> shadow-sm
- drop-shadow-sm -> drop-shadow-xs, drop-shadow -> drop-shadow-sm
- rounded-sm -> rounded-xs, rounded -> rounded-sm
- blur-sm -> blur-xs, blur -> blur-sm
- backdrop-blur-sm -> backdrop-blur-xs, backdrop-blur ->
backdrop-blur-sm

v3 default border-color (gray-200) preserved via @layer base compat shim
per the [upgrade guide's default border color
note](https://tailwindcss.com/docs/upgrade-guide#default-border-color),
since explicit border-color isn't always paired in existing markup.

AI Chat (not used publicly but still works):

<img width="2880" height="1800" alt="ai-chat-local-fixed"
src="https://github.com/user-attachments/assets/0d7ba997-8fc3-438e-aab5-dc83a3f9b8a5"
/>

Graph UI:

<img width="2880" height="1800" alt="graph-client-real-projects"
src="https://github.com/user-attachments/assets/9258c8f9-db05-4f76-a1bc-ffb61d15f0be"
/>

Graph (Storybook):

<img width="2880" height="1800" alt="storybook-projectdetails-rendered"
src="https://github.com/user-attachments/assets/e61cceb6-46fc-4ff4-8713-c720ce228986"
/>

## Related Issue(s)

NXC-4430

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-05-08 15:50:19 -04:00
Jack Hsu 9f18c6ae2f feat(bundling)!: remove SVGR option and provide withSvgr migration (#35611)
## 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>
2026-05-08 15:36:00 -04:00
Jason Jean 509d92efb2 chore(gradle): deprecate @nx/gradle/plugin-v1 entry (#35610)
## 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>
2026-05-08 15:31:17 -04:00
Jason Jean a2dfac6bfa feat(misc)!: deprecate executors with inferred-plugin replacements (#35576)
## 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>
2026-05-08 13:22:57 -04:00
Leosvel Pérez Espinosa 9116416dc0 fix(testing): handle absolute cypress screenshotsFolder/videosFolder paths (#35624)
## 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).
2026-05-08 13:12:27 -04:00
Leosvel Pérez Espinosa 9ae0e119f3 chore(repo): root deps housekeeping (#35625)
## 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>
2026-05-08 13:10:29 -04:00
Jack Hsu 78daae3be1 fix(repo): drop node 26 from nightly matrix until playwright/yauzl fix (#35626)
## 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
2026-05-08 12:41:48 -04:00
Jack Hsu 767d30eb28 docs(node): add Node 26 to compat matrix (#35623)
## 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
2026-05-08 11:37:47 -04:00
Leosvel Pérez Espinosa 89864f0aaf cleanup(angular): avoid project-graph cache writes inside the angular project during plugin spec (#35620)
## 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.
2026-05-08 10:50:46 -04:00
Leosvel Pérez Espinosa c5cc0005ab fix(angular-rspack): keep root-scoped assets out of per-locale i18n emit (#35621)
## 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.
2026-05-08 10:49:51 -04:00
Jason Jean 1eae7af5b2 fix(testing): pin jest to ~30.3.0 to avoid jest-runtime 30.4 RN incompat (#35618)
## 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>
2026-05-08 16:49:21 +02:00
Altan Stalker 57e6c29fb5 chore(ci): revert assignment rule (#34537)
Will bust cache several times to see whether we can get a repro

---------

Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
2026-05-08 14:57:29 +02:00
Craigory Coppola b3edd3dd23 fix(core): error with helpful error instead of looping nx invocations (#34820)
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>
2026-05-07 22:55:03 -04:00
Craigory Coppola b9868f2b95 fix(devkit): exclude dist from jest module path scan (#35615)
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-05-07 22:41:10 -04:00
Craigory Coppola 13c0a09582 chore(repo): fixup dotnet:lint sb violations (#35612)
## 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 #
2026-05-07 17:06:30 -04:00
Jason Jean 668f05c927 fix(gradle): exclude project-graph from jest module path scan (#35609)
## 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
2026-05-07 16:42:08 -04:00
Craigory Coppola 996228b6f7 fix(core): enable node's native v8 compile cache support (#35415)
## 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>
2026-05-07 16:41:40 -04:00
Leosvel Pérez Espinosa f2163705d6 feat(angular)!: remove deprecated ngrx generator (#35567)
## 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>
2026-05-07 14:33:12 -04:00
Jack Hsu 5f534cd5cd feat(misc): drop Node 20 support and bump @types/node (#35591)
## 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
2026-05-07 11:52:58 -04:00
Sharon Lougheed 7859b10849 fix(linter): prevent ENOENT crash in getRelativeImportPath for unresolvable paths (#35007)
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>
2026-05-07 14:33:22 +02:00
Leosvel Pérez Espinosa 5fd9a95a95 chore(testing): pin vite resolution on yarn for vite 7 downgrade (#35586)
## 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.
2026-05-07 06:57:40 +00:00
Jason Jean a3c5839ad0 fix(core): isolate cache env vars in splitArgs spec (#35584)
## 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>
2026-05-07 01:55:03 +00:00
Jason Jean 509c7d1828 chore(repo): update nx to 23.0.0-beta.8 (#35601)
Updating Nx from 23.0.0-beta.7 to 23.0.0-beta.8
2026-05-06 19:02:22 -04:00
Jason Jean e125af2ab4 fix(core): use gethostuuid(3) instead of ioreg on macOS (#35599)
## 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 #
2026-05-06 22:12:51 +00:00
Jack Hsu c494ec5df8 feat(vite)!: remove vitest support in favor of @nx/vitest (#35517)
## Current Behavior

`@nx/vite` carries a parallel vitest surface (version constants, `:test`
executor, `vitest` generator, plugin test inference, init-time install)
that duplicates `@nx/vitest`.

## Expected Behavior

`@nx/vite` owns vite. `@nx/vitest` owns vitest. Vitest surface removed
from `@nx/vite`; consumers route through `@nx/vitest`.

v23 migration installs `@nx/vitest`, swaps the executor, and registers
`@nx/vitest` plugin alongside default-config `@nx/vite/plugin` so test
inference is preserved.

## Related Issue(s)

Closes NXC-4158

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
2026-05-06 17:30:28 -04:00
Jason Jean 9ff5d90e08 fix(core): skip handleimport miss path when nx key packages are absent (#35596)
## 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 #
2026-05-06 17:02:37 -04:00
Jason Jean 49955f4e1d fix(release): restore packages/devkit/package.json after release (#35598)
## 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.
2026-05-06 16:58:04 -04:00
Jason Jean 6c840aecc6 chore(repo): exclude packages/nx/dist/src/native/*.node from sandbox reads (#35602)
## 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
2026-05-06 20:45:35 +00:00
Jason Jean 89e288af9e fix(maven): widen runCLI timeout for --no-batch maven.test.ts cases (#35589)
## 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.
2026-05-06 16:39:07 -04:00
Jason Jean 02bab05bcb chore(repo): use apt mirror+file failover for ubuntu sources (#35600)
## 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.
2026-05-06 15:59:34 -04:00
Craigory Coppola b594537984 chore(repo): provision build toolchain via mise in publish workflow (#35593)
## 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`.
2026-05-06 14:03:32 -04:00
Optischa b1e71ab2a7 fix(core): bump axios to 1.16.0 for all packages (#35568)
Fix Axios CVE's:
Axios: Authentication Bypass via Prototype Pollution Gadget in
`validateStatus` Merge Strategy -
https://github.com/advisories/GHSA-w9j2-pvgh-6h63
Axios: Incomplete Fix for CVE-2025-62718 — NO_PROXY Protection Bypassed
via RFC 1122 Loopback Subnet (127.0.0.0/8) in Axios 1.15.0 -
https://github.com/advisories/GHSA-pmwg-cvhr-8vh7
Axios: Invisible JSON Response Tampering via Prototype Pollution Gadget
in `parseReviver` - https://github.com/advisories/GHSA-3w6x-2g7m-8v23
Axios has prototype pollution read-side gadgets in HTTP adapter that
allow credential injection and request hijacking -
https://github.com/advisories/GHSA-q8qp-cvcw-x6jj
Axios: Null Byte Injection via Reverse-Encoding in AxiosURLSearchParams
- https://github.com/advisories/GHSA-xhjh-pmcv-23jw
Axios: CRLF Injection in multipart/form-data body via unsanitized
blob.type in formDataToStream -
https://github.com/advisories/GHSA-445q-vr5w-6q77
Axios: no_proxy bypass via IP alias allows SSRF -
https://github.com/advisories/GHSA-m7pr-hjqh-92cm
Axios: unbounded recursion in toFormData causes DoS via deeply nested
request data - https://github.com/advisories/GHSA-62hf-57xw-28j9
Axios' HTTP adapter-streamed uploads bypass maxBodyLength when
maxRedirects: 0 - https://github.com/advisories/GHSA-5c9x-8gcm-mpgx
Axios: HTTP adapter streamed responses bypass maxContentLength -
https://github.com/advisories/GHSA-vf2m-468p-8v99
Axios: Prototype Pollution Gadgets - Response Tampering, Data
Exfiltration, and Request Hijacking -
https://github.com/advisories/GHSA-pf86-5x62-jrwf
Axios: Header Injection via Prototype Pollution -
https://github.com/advisories/GHSA-6chq-wfr3-2hj9
Axios: XSRF Token Cross-Origin Leakage via Prototype Pollution Gadget in
`withXSRFToken` Boolean Coercion -
https://github.com/advisories/GHSA-xx6v-rp6x-q39c

---------

Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
2026-05-05 18:44:12 -04:00
Craigory Coppola cab8c9b6d2 fix(dotnet): correct output paths for Web SDK and centralized dist setups (#35398)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-05-05 17:55:58 -04:00
Jack Hsu ba0c07d961 chore(misc): include blog in auto-deployment (#35582)
It's a separate app now, needs to update as well.
2026-05-05 17:27:37 -04:00
Jason Jean 6c5b1f43d7 fix(core): isolate NX_PARALLEL env var in parallel-related specs (#35579)
## 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
2026-05-05 20:42:01 +00:00
Jason Jean a8b4cf1b73 chore(repo): update pnpm lockfile for @phenomnomnominal/tsquery 6.2.0 (#35578)
## 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
2026-05-05 15:51:28 -04:00
Jason Jean 1327d78134 fix(core): restore use-legacy-versioning shim for @nx/js@21 ensurePackage path (#35574)
## 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 #
2026-05-05 15:51:03 -04:00
Jason Jean ff0eec2641 chore(repo): update nx to 23.0.0-beta.7 (#35565)
Updating Nx from 23.0.0-beta.4 to 23.0.0-beta.7
2026-05-05 19:14:07 +00:00
beeman b6358a12ae fix(core): update minimatch to 10.2.5 (#35569)
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.
2026-05-05 14:52:22 -04:00
Jack Hsu 70d1c195bc feat(bundling)!: drop legacy typescript plugin and align rollup buildLibsFromSource default (#35516)
## 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
2026-05-05 14:49:04 -04:00
polygraph-app[bot] 57f1c31a19 fix(repo): resolve graph-client build-client sandbox violations (#35522)
## 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>
2026-05-05 14:11:54 -04:00
Leosvel Pérez Espinosa b07804182d chore(testing): split NX_E2E_SKIP_CLEANUP into global/project-scoped vars (#35572)
## 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>
2026-05-05 10:45:18 -04:00
Jason Jean 7b15a56088 fix(devkit): only rewrite deep-import paths in real import sites (#35566)
## 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>
2026-05-05 10:42:07 -04:00
Justin Mecham cb26390936 chore(repo): bump @phenomnomnominal/tsquery to ~6.2.0 (#35560)
## 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.
2026-05-05 10:37:15 -04:00
Jason Jean ed44fb3426 fix(core): use workspace root for package manager detection in script targets (#35550)
## 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.
2026-05-04 20:57:17 +00:00
Jason Jean 7630853b98 feat(devkit): migrate @nx/devkit/src/... deep imports (#35541)
## 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.
2026-05-04 20:37:25 +00:00
Jason Jean e8aa612971 chore(core): unify Task into a single Rust struct (#35540)
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-05-04 15:01:48 -04:00
Jason Jean d1bb46800a chore(core): bump ratatui to 0.30 and drop tui-term fork (#35547)
## 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.
2026-05-04 14:31:04 -04:00
Jason Jean 4193c31d95 chore(repo): parallelize e2e-ci tasks on large/xlarge agents (#35325)
## 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>
2026-05-04 18:20:12 +00:00
Jason Jean 058e56606e fix(js): include transitive workspace deps in pruned pnpm lockfile (#35532)
## 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>
2026-05-04 14:13:34 -04:00
Jason Jean a5d43009d7 feat(testing)!: deprecate the @nx/cypress:cypress executor (#35531)
## 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.
2026-05-04 14:09:25 -04:00
Jason Jean 803453de29 cleanup(misc): reuse PluginCache constructor cachePath in writeToDisk (#35546) 2026-05-04 13:21:59 -04:00
Jason Jean 0f5b8f461c chore(core): bump detect-port from ^1.5.1 to ^2.1.0 (#35533)
## 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>
2026-05-04 13:11:28 -04:00
MaxKless cbcd4d552b fix(maven): serialize Maven 4 build state recording (#35555)
## 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
2026-05-04 09:24:01 -04:00
Jason Jean 86f5e5eda3 fix(core): unique telemetry user_id; expose workspace_id dimension (#35553) 2026-05-04 00:09:06 -04:00
Jason Jean 8c17005567 feat(detox)!: deprecate the @nx/detox build and test executors (#35529)
## 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.
2026-05-02 12:28:19 -04:00
Jason Jean 6808588a14 fix(misc): adopt PluginCache across createNodes plugins to prevent flaky cache parse errors (#35544)
## 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.
2026-05-02 12:27:01 -04:00
Jason Jean c4bc8523de chore(repo): bump nx and powerpack packages in workspace-plugin (#35543)
## 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
2026-05-02 10:12:39 -04:00
Jason Jean f2872bb681 fix(devkit): drop build-base outputs override (#35542)
## 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.
2026-05-02 10:12:03 -04:00
Jason Jean 59e2e51a42 chore(devkit): build devkit to local dist and use nodenext (#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>
2026-05-01 22:42:16 -04:00
Jason Jean c4ca481574 feat(gradle): stream batch task results to nx as they finish (#35487)
## 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)
2026-05-01 17:09:04 -04:00
Craigory Coppola c937f47d1a chore(repo): prevent unit tests from walking the real workspace (#35441)
## 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.
2026-05-01 16:27:24 -04:00
Jason Jean d668a27545 chore(repo): resolve sandbox violations on nx-dev:next:build task (#35530)
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-05-01 16:25:35 -04:00
Jack Hsu fde01fae10 feat(nx-dev): track docs analytics for code copy, LLM prompt, YouTube (#35526)
## Current Behavior
Astro docs site tracks header nav, search, scroll depth, and 404s. Code
block copies and LLM prompt copies are not tracked. YouTube embeds lack
`enablejsapi=1`, so GA4 enhanced measurement cannot hook the player.

## Expected Behavior
Two new GTM dataLayer events: `code_block_copy` (with derived
alphanumeric `code_id`) and `llm_prompt_copy` (with `prompt_title`).
Both gated by existing production-only check. YouTube iframes get
`enablejsapi=1` patched on the client so GA4 enhanced measurement fires
`video_start` / `video_progress` / `video_complete` natively. GTM
container needs tags wired for the two new event names to forward to
GA4.

## Examples

Copy code block:
<img width="932" height="335" alt="image"
src="https://github.com/user-attachments/assets/e21e8979-0bf2-4e94-a1f6-d832897ad392"
/>

Copy LLM prompt:
<img width="1140" height="250" alt="image"
src="https://github.com/user-attachments/assets/ef4a558c-895d-49ed-8429-ead0997b9fed"
/>

Youtube play/pause/end
<img width="947" height="573" alt="image"
src="https://github.com/user-attachments/assets/dc73cea5-6bb2-4131-a545-45239a80ecc9"
/>


## Related Issue(s)
Fixes DOC-497
2026-05-01 15:52:51 -04:00
polygraph-app[bot] c83207be77 docs(nx-cloud): add metric uploader step to manual DTE examples (#35534)
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>
2026-05-01 18:01:49 +01:00
Jason Jean 81e7d472fe chore(repo): update nx to 23.0.0-beta.4 (#35528)
Updating Nx from 23.0.0-beta.3 to 23.0.0-beta.4
2026-05-01 04:28:04 +00:00
Jack Hsu 33a83e1c9e fix(nx-dev): short-circuit bot probes in framer rewrite edge function (#35527)
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
2026-04-30 18:11:26 -04:00
Jason Jean c401052abe chore(linter): bump ignore from ^5.0.4 to ^7.0.5 (#35508)
## 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.
2026-04-30 17:53:02 -04:00
Jason Jean 703e6d9251 fix(core): show flaky-task count in run summary (#35491)
## 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)
2026-04-30 17:32:32 -04:00
Leosvel Pérez Espinosa 5e76bf0154 feat(angular)!: remove deprecated move generator (#35513)
## 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.
2026-04-30 17:29:56 -04:00
Craigory Coppola 41e2cb1503 feat(core): show target uses task graph + filter broken dependsOn during normalization (#35367)
## 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.
2026-04-30 15:51:20 -04:00
Craigory Coppola f48c373736 fix(core): ensure verbose logs go to stderr and daemon logs are properly decorated (#34358)
## 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>
2026-04-30 15:50:29 -04:00
Jason Jean 9e01808fec fix(core): restore deprecated allWorkspaceFiles on WorkspaceFileMap (#35518)
## 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. -->
2026-04-30 15:42:12 -04:00
Jason Jean cf13bc60a1 chore(repo): update nx to 23.0.0-beta.3 (#35515)
Updating Nx from 23.0.0-beta.2 to 23.0.0-beta.3
2026-04-30 15:26:22 -04:00
Jason Jean b4cb43f86e chore(repo): remove offboarded core team members from README (#35504)
## 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)
2026-04-30 14:55:40 -04:00
Benjamin Cabanes d8e67f196a chore(nx-dev): remove '/pricing' from excluded URL rewrite paths (#35467)
Update Framer redirects on nx.dev.
2026-04-30 13:50:22 -04:00
Craigory Coppola f89e71dfd6 fix(nx-dev): document nested CLI subcommands beyond two levels (#35519)
## 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 #
2026-04-30 13:49:54 -04:00
Jason Jean 44ae15eef6 fix(maven): skip attached artifacts that fail to materialize in batch record (#35473)
## 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.
2026-04-30 12:56:19 -04:00
Leosvel Pérez Espinosa b541408a07 fix(core): correctly classify same-second in-place updates in macOS watcher (#35514)
## 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.
2026-04-30 14:35:20 +00:00
Jason Jean f4a25ec81a chore(linter): bump globals from ^15.9.0 to ^17.0.0 (#35505)
## 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.
2026-04-30 16:33:48 +02:00
Leosvel Pérez Espinosa 59670e7751 feat(angular)!: remove deprecated @nx/angular/module-federation entry point (#35512)
## 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`.
2026-04-30 10:22:08 -04:00
Leosvel Pérez Espinosa c8d7afc100 chore(angular): cleanup deprecated code and docs (#35496)
## 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)
2026-04-30 16:19:16 +02:00
Leosvel Pérez Espinosa b807ab57ea chore(repo): fix typecheck failures across e2e and eslint-rules (#35456)
## 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.
2026-04-30 10:11:13 -04:00
Leosvel Pérez Espinosa 5d6b1c3a47 fix(js): reference vitest.config in eslint dep-checks for vitest libs (#35460)
## 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
2026-04-30 10:06:55 -04:00
Juri ca93fb1a4f docs(misc): document agentic nx import flow 2026-04-30 11:33:59 +02:00
Jason Jean b70d0524d8 fix(core): preserve hydrateFileMap back-compat for cached nx-cloud workers (#35502)
## 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. -->
2026-04-29 22:30:37 +00:00
Leosvel Pérez Espinosa 4ffd9623d5 chore(nx-dev): resolve astro-docs:build sandbox violations (#35472)
## 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.
2026-04-29 17:23:41 -04:00
Jason Jean 2803c4437e fix(gradle): exclude batch-runner from jest haste-map crawl (#35501)
## 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`.
2026-04-29 17:15:56 -04:00
Craigory Coppola 2a412a3aea chore(repo): declare lazy-loaded packages as implicit deps (#35392)
## 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>
2026-04-29 17:15:43 -04:00
Jason Jean 37630086c8 chore(repo): update nx to 23.0.0-beta.2 (#35499)
Updating Nx from 23.0.0-beta.1 to 23.0.0-beta.2
2026-04-29 15:59:09 -04:00
Rares Matei 0205b22ec0 docs(misc): update nx-cloud-workflows references from v5 to v6 (#35498)
Bumps `nrwl/nx-cloud-workflows` references in launch template docs and
agents config from `v5` to `v6`.
2026-04-29 17:20:39 +00:00
Leosvel Pérez Espinosa 8fb55c7c02 fix(angular): disable vitest watch by default (#35493)
## 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.
2026-04-29 18:42:03 +02:00
Louie Weng c2290a6b1d chore(gradle): use task graph when traversing task excludes (#35413)
<!-- 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 #
2026-04-29 12:30:12 -04:00
MaxKless e59122c556 fix(core): remove access control header from graph app (#35494)
it's not needed anymore
2026-04-29 12:28:19 -04:00
Jason Jean 6ac9f82b04 chore(misc): update and align sass, sass-embedded, sass-loader versions (#35492)
## Current Behavior

The repo has `sass`, `sass-embedded`, and `sass-loader` references
scattered across multiple `package.json` files and generator
`versions.ts` constants, all on different (and in some cases very old)
versions and with inconsistent specifier styles (some exact, some
caret).

| File | `sass` | `sass-embedded` | `sass-loader` |
|---|---|---|---|
| `package.json` (root) | `1.55.0` | `1.85.1` | `16.0.5` |
| `packages/angular-rspack/package.json` | `1.89.2` | `^1.79.3` |
`^16.0.2` |
| `packages/angular-rspack-compiler/package.json` | — | `^1.79.3` | — |
| `packages/webpack/package.json` | `^1.85.0` | `^1.83.4` | `^16.0.4` |
| `packages/rspack/package.json` | `^1.85.0` | `^1.83.4` | `^16.0.4` |
| `packages/rspack/src/utils/versions.ts` | — | `^1.83.4` | `^16.0.4` |
| `packages/next/src/utils/versions.ts` | `1.62.1` | — | — |
| `packages/vue/src/utils/versions.ts` | `^1.70.0` | — | — |
| `packages/react/src/utils/versions.ts` | `^1.55.0` | — | — |

## Expected Behavior

All sass-family deps **updated to current versions** and aligned to a
single target version with consistent specifier conventions:

- `sass`: bumped to **`1.97.2`** everywhere it appears
- `sass-embedded`: bumped to **`1.97.2`** everywhere it appears
(matching `sass`)
- `sass-loader`: bumped to **`16.0.7`** everywhere it appears

Specifier style:

- **Published packages** use caret ranges (so consumers pick up patch
updates).
- **Root `package.json`** keeps exact pins (matching its existing
convention for other build tooling like `less`, `webpack`, `vite`).
- Generator version constants in `@nx/next`, `@nx/react`, `@nx/vue`
(also reused by `@nx/nuxt`) follow each file's existing caret/exact
pattern.

| File | `sass` | `sass-embedded` | `sass-loader` |
|---|---|---|---|
| `package.json` (root) | `1.97.2` | `1.97.2` | `16.0.7` |
| `packages/angular-rspack/package.json` | `^1.97.2` | `^1.97.2` |
`^16.0.7` |
| `packages/angular-rspack-compiler/package.json` | — | `^1.97.2` | — |
| `packages/webpack/package.json` | `^1.97.2` | `^1.97.2` | `^16.0.7` |
| `packages/rspack/package.json` | `^1.97.2` | `^1.97.2` | `^16.0.7` |
| `packages/rspack/src/utils/versions.ts` | — | `^1.97.2` | `^16.0.7` |
| `packages/next/src/utils/versions.ts` | `1.97.2` | — | — |
| `packages/vue/src/utils/versions.ts` | `^1.97.2` | — | — |
| `packages/react/src/utils/versions.ts` | `^1.97.2` | — | — |

These are minor-version bumps within `sass` v1.x and `sass-loader` v16.x
— no breaking changes expected, no public-API surface affected.

## Validation

The only source-level reference is a type-only import in
`@nx/angular-rspack`:

```ts
// packages/angular-rspack/src/lib/config/config-utils/style-config-utils.ts:13
import type { FileImporter } from 'sass';
```

`FileImporter` is part of dart-sass's stable public typings and
unchanged across this range.

- `pnpm nx run-many -t build -p
webpack,rspack,angular-rspack,angular-rspack-compiler,react,vue,next,nuxt`
— passes.
- `pnpm nx run angular-rspack:test` — 3 test files, 25 tests, all pass.
- `pnpm-lock.yaml` regeneration is clean: only sass-family specifiers
and corresponding peer-snapshot rehashes.

## Related Issue(s)

Part of [NXC-4329](https://linear.app/nxdev/issue/NXC-4329)
bulk-dependency-update sweep.
2026-04-29 12:11:30 -04:00
Jason Jean 5323ed4e1c fix(core): native watcher rewrite + daemon hardening for daemon-on e2e (#35204)
## 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>
2026-04-29 12:05:12 -04:00
Leosvel Pérez Espinosa a9c120c0bf fix(js): strip glob from inferred outputs before resolving as path (#35463)
## 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
2026-04-29 09:55:42 -04:00
Jack Hsu b59374a005 fix(react): withSvgr migration preserves other properties (#35484)
The current migration erroneously removes all withReact properties. This
will be copied for the Rspack migration for v23 as well.

Closes NXC-3982
2026-04-29 09:27:46 -04:00
Juri 81f8275ad4 docs(misc): note sandboxing rolling out to other Nx Cloud plans on June 1 2026-04-29 13:47:21 +02:00
Leosvel Pérez Espinosa f2c56ad495 chore(repo): resolve sandbox violations on graph-client:build-client task (#35471)
## 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.
2026-04-29 08:58:01 +00:00
Jason Jean 55d63ef10d chore(core): update misc e2e target info snapshot for tsconfig solution input (#35488)
## 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.
2026-04-28 22:55:52 +00:00
Craigory Coppola 6efd33f0a0 fix(core): skip target-defaults synthesis when defaults are incompatible with the specified target (#35486)
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.
2026-04-28 18:22:44 -04:00
Jack Hsu 929ef0fb00 chore(misc): a/b testing init Cloud prompt (#35468)
Do A/B test against the previous messaging from March that seems to have
higher success rate.

NXC-4363
2026-04-28 17:57:22 -04:00
polygraph-app[bot] ca7671afd6 fix(bundling): include tsconfig solution input for webpack (#35477)
## 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>
2026-04-28 17:56:56 -04:00
Leosvel Pérez Espinosa 7a6f796047 fix(bundling): include tsconfig solution input for rollup (#35476)
## 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.
2026-04-28 17:56:23 -04:00
Craigory Coppola 3c88f372f1 fix(core): add provenance check in nx console status path (#35485)
## 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 #
2026-04-28 17:55:37 -04:00
Jason Jean b6b047f884 fix(nextjs): use cached project graph in withNx (#35475)
## 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.
2026-04-28 20:24:23 +00:00
Jason Jean b05d65333e fix(core): prevent daemon shutdown from cache-poisoned in-process nx loads (#35482)
## 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
2026-04-28 19:24:17 +00:00
Jack Hsu d1e9a4349a feat(misc)!: remove deprecated stylesheet options from generators (#35103)
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
2026-04-28 14:25:42 -04:00
Craigory Coppola 64bf7674b1 chore(repo): include native dts header in build-native inputs (#35481) 2026-04-28 14:16:34 -04:00
Jason Jean c8de002bd0 chore(repo): update nx to 23.0.0-beta.1 (#35474)
Updating Nx from 23.0.0-beta.0 to 23.0.0-beta.1
2026-04-28 12:42:39 -04:00
Jack Hsu 933eb69826 feat(misc)!: remove Tailwind CSS setup-tailwind generators (#35049)
## Current Behavior

Nx ships `setup-tailwind` generators in 5 plugins (angular, react, next,
vue, remix) producing Tailwind v3 configs. `--style=tailwind`
(React/Next) and `--addTailwind` (Angular) call them. `@nx/*/tailwind`
barrel exports `createGlobPatternsForDependencies`.

## Expected Behavior

All `setup-tailwind` generators, schema options, and scaffolding
utilities removed. Barrel exports kept but warn at runtime; full removal
slated for Nx 24. Runtime tailwind plumbing that aids existing monorepos
(ng-packagr mirror, Cypress CT auto-injection) unchanged.

Removed:
- `setup-tailwind` generators in angular, react, next, vue, remix
- `--style=tailwind` from React/Next app/lib/component/host/remote
- `--addTailwind` from Angular app/lib/host/remote
- `tailwind` style choice from `create-nx-workspace`
- Tailwind version constants from `versions.ts`
- `@tailwindcss/aspect-ratio` from root `package.json`
- E2e tests for removed generators
- `createGlobPatternsForDependencies` usage in `graph/` tailwind configs
(now use `ui-*/src` + `shared/src` globs)

Kept, deprecated, removed in Nx 24:
- `@nx/{angular,react,next,vue}/tailwind` — warns once per process,
still exports `createGlobPatternsForDependencies`

Kept unchanged:
- `@nx/angular-rspack` runtime tailwind detection
- ng-packagr stylesheet tailwind support (mirrors upstream)
- Cypress CT tailwind injection in @nx/angular and @nx/next
(BYO-Tailwind users)

## Related Issue(s)

Fixes NXC-3711
2026-04-28 10:56:27 -04:00
Leosvel Pérez Espinosa 0e779e50c6 fix(core): use require for global to local Nx handoff so Windows drive paths work (#35478) 2026-04-28 09:10:04 -04:00
ShwethaSundar ef0eec76dc fix(release): handle short and full project names in commit scopes (#34219) 2026-04-28 11:52:34 +00:00
Craigory Coppola 8183ec2aee fix(core): start TUI event reader synchronously in enter() to prevent stdin race (#35465)
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>
2026-04-27 22:01:04 -04:00
Leosvel Pérez Espinosa 278568aa99 fix(misc): resolve pnpm catalog: refs in version lookups (#35459)
## 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
2026-04-27 21:59:39 -04:00
Leosvel Pérez Espinosa 1d6f14dff3 fix(node): include tsconfig input in node-app esbuild scaffold (#35466)
## 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.
2026-04-27 21:58:31 -04:00
Leosvel Pérez Espinosa 7581dfe373 fix(misc): exclude stories and specs from tailwind content scanning (#35470)
## 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`
2026-04-27 21:57:31 -04:00
Craigory Coppola 096a49d91c fix(core): keep continuous children alive when nx:noop orchestrator completes (#35388) 2026-04-27 21:33:35 -04:00
Jack Hsu 665358e79d fix(core): surface ./nx --version stderr and force devDeps install (#35469) 2026-04-27 15:16:32 -04:00
Jack Hsu 0fb8515af2 docs(nextjs): clarify Vercel root directory and document NEXT_PUBLIC_ cache issue (#35433)
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>
2026-04-27 13:58:17 -04:00
Leosvel Pérez Espinosa 1d892d7338 fix(js): include extended tsconfigs from project references in typecheck inputs (#35457)
## 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.
2026-04-27 11:04:38 -04:00
Jason Jean 0c2ec4b395 fix(core): consider virtual trees in multiGlobWithWorkspaceContext (#35447)
## 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.
2026-04-27 10:55:05 -04:00
Leosvel Pérez Espinosa 5e862cadcc chore(repo): resolve sandbox violations on graph typecheck tasks (#35458)
## 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.
2026-04-27 10:44:38 -04:00
Leosvel Pérez Espinosa b1ba26c59c fix(testing): convert executor-based jest.config.ts and preserve type-only imports (#35286)
## 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
2026-04-27 00:53:23 -04:00
Leosvel Pérez Espinosa 3480ef803b fix(linter): detect root lint target added in same generator run (#35296)
## 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
2026-04-27 00:51:46 -04:00
Leosvel Pérez Espinosa def51d7ff1 fix(core): provide actionable feedback when running migrations and pre-install fails with npm peer dep errors (#33961)
## 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
2026-04-27 00:49:55 -04:00
Caleb Ukle d929448acf docs(nx-cloud): update custom GH app steps for clarity (#35451)
clarify permissions must precede webhook event subscription

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 08:45:37 -04:00
Jason Jean 1cb67a256d chore(module-federation): re-enable webpack module federation e2e suites
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
2026-04-25 11:39:53 -04:00
Jason Jean bde1f18cef fix(detox): generate valid JSON in .detoxrc for non-expo apps
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.
2026-04-25 11:39:15 -04:00
Jack Hsu 182273670a fix(core): exclude hyperfine env vars from daemon env reflection
## 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.
2026-04-24 19:49:16 -04:00
FrozenPandaz 0a679ad822 chore(repo): update nx to 23.0.0-beta.0 2026-04-24 19:26:26 -04:00
Craigory Coppola b5ab6fa831 fix(core): prevent spinner flicker when sync applying (#35445) 2026-04-24 19:24:21 -04:00
Craigory Coppola 608c3bec02 feat(core): add support for '...' as a spread token when merging target config (#34285)
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>
2026-04-24 14:55:43 -04:00
Leosvel Pérez Espinosa 9cb8f61f02 fix(core): remove redundant allWorkspaceFiles from the project graph pipeline (#34425)
## 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>
2026-04-24 14:55:43 -04:00
2883 changed files with 117263 additions and 46845 deletions
-37
View File
@@ -1,37 +0,0 @@
version: 2.1
# -------------------------
# EXECUTORS
# -------------------------
defaults: &defaults
working_directory: ~/repo
executors:
linux:
<<: *defaults
docker:
- image: cimg/rust:1.84.0-browsers
resource_class: small
# -------------------------
# JOBS
# -------------------------
jobs:
# -------------------------
# JOBS: Main Linux
# -------------------------
main-linux:
executor: linux
steps:
- 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."
# -------------------------
# WORKFLOWS(JOBS)
# -------------------------
workflows:
version: 2
build:
jobs:
- main-linux
@@ -0,0 +1,3 @@
This skill is disabled to encourage use of the AI prompt from the nx cloud sandboxing dashboard.
If you still need the original skill, you can reference it with @./claude/disabled-skills/diagnose-sandbox-report/SKILL.md directly.
@@ -0,0 +1,590 @@
---
name: dist-build-migration
description: Migrate an Nx package to build to a local dist/ directory with nodenext module resolution, exports map, and @nx/nx-source condition.
allowed-tools: Bash, Read, Glob, Grep, Agent, Edit, Write
---
# Migrate Package to Local Dist Build
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:
**Before:**
```json
{
"compilerOptions": {
"module": "commonjs",
"outDir": "../../dist/packages/<name>",
"tsBuildInfoFile": "../../dist/packages/<name>/tsconfig.tsbuildinfo"
}
}
```
**After:**
```json
{
"compilerOptions": {
"outDir": "dist",
"rootDir": ".",
"declarationDir": "dist",
"declarationMap": false,
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo",
"types": ["node"],
"composite": true,
"module": "nodenext",
"moduleResolution": "nodenext",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
},
"exclude": ["node_modules", "dist", ...existing excludes, ".eslintrc.json"],
"include": ["*.ts", "src/**/*.ts"]
}
```
**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`).
### 6. Update `project.json`
Add these sections:
```json
{
"release": {
"version": {
"generator": "@nx/js:release-version",
"preserveLocalDependencyProtocols": true,
"manifestRootsToUpdate": ["packages/{projectName}"]
}
},
"targets": {
"nx-release-publish": {
"options": {
"packageRoot": "packages/{projectName}"
}
}
}
}
```
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:
```
node ./scripts/copy-readme.js <name> packages/<name>/readme-template.md packages/<name>/README.md
```
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
Use: `grep -r "from '@nx/<name>/src/" packages/ --include="*.ts" -l` to find affected files.
Also check for imports in:
- `e2e/` tests
- `scripts/`
- `tools/workspace-plugin/`
- `astro-docs/`
- `examples/`
### 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:
```bash
grep -rEln "from ['\"]@nx/<name>/src/" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.mjs" packages/ e2e/ scripts/
grep -rEln "(require|jest\.mock|jest\.requireActual)\(['\"]@nx/<name>/src/" packages/ e2e/ scripts/
```
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
grep -rEn "['\"]@nx/<name>/src/[^'\"]+['\"]" packages/nx/src/ --include="*.ts"
```
**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).
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
export { ... } from 'nx/src/plugins/.../something';
export { walkTsconfigExtendsChain, type RawTsconfigJsonCache } from './src/utils/typescript/raw-tsconfig';
// ... and so on, grouping by area.
```
Delete any pre-existing `packages/<name>/src/internal.ts` once its exports have been folded in.
**4. Update `packages/<name>/package.json`.** Drop wildcards, add `./internal`, keep runtime-string subpaths as explicit entries:
```jsonc
{
"exports": {
".": {
"@nx/nx-source": "./src/index.ts",
"types": "./dist/src/index.d.ts",
"default": "./dist/src/index.js",
},
"./package.json": "./package.json",
"./migrations.json": "./migrations.json",
"./generators.json": "./generators.json",
"./executors.json": "./executors.json",
// Public side-channels (whatever you already had).
"./babel": {
"@nx/nx-source": "./babel.ts",
"types": "./dist/babel.d.ts",
"default": "./dist/babel.js",
},
// The new curated entry.
"./internal": {
"@nx/nx-source": "./internal.ts",
"types": "./dist/internal.d.ts",
"default": "./dist/internal.js",
},
// Runtime-string-resolved subpath kept for back-compat.
"./src/release/version-actions": {
"@nx/nx-source": "./src/release/version-actions.ts",
"types": "./dist/src/release/version-actions.d.ts",
"default": "./dist/src/release/version-actions.js",
},
// DROPPED: "./src/*", "./src/*.js", "./src/*/schema", "./src/*/schema.json"
},
}
```
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.
Symptom:
```
Error: Cannot find module '@nx/<name>/internal'
requireStack: [
'/path/to/workspace/dist/packages/<X>/src/utils/foo.js',
...
]
}
```
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:
```bash
grep -rln "patchModuleFederationRequestPath\|Module._resolveFilename" examples/ e2e/ packages/
```
Any file that monkeypatches resolution is a candidate for needing the redirect.
#### Validation
After steps 19:
```bash
# Build the package (emits dist/internal.{js,d.ts})
pnpm nx run <name>:build-base
# Lint the package — @nx/dependency-checks may complain that the package
# "uses itself" because of the dynamic self-reference in versions.ts. Add
# `@nx/<name>` to `ignoredDependencies` in the dependency-checks rule config
# (with a comment explaining: self-reference for require(join('@nx/<name>', 'package.json'))).
pnpm nx run <name>:lint
# Spec the migration
pnpm nx test <name> -- --testPathPatterns=rewrite-<name>-internal-subpath-imports
# Full affected — catches consumers, example monkey-patches, and any
# missed split-mixed-imports.
pnpm nx affected -t build,lint --base=<base-sha-before-migration>
```
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:
```ts
// Before
export const nxVersion = require('../../package.json').version;
// After
import { join } from 'path';
export const nxVersion = require(join('@nx/<name>', 'package.json')).version;
```
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)
- `packages/devkit/src/utils/package-json.ts``NX_VERSION = require(join('nx', 'package.json')).version` (dynamic form)
This was the source of the workspace-migration e2e regressions (PR #35643) and is one of the most-failure-prone steps to forget. Audit aggressively.
### 15b. Audit `ensurePackage` + `await import(...)` pairs
Search for `ensurePackage\(['"]@nx/` inside `packages/<name>/src/`. For every match, look at the next 520 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:
```ts
// Before
ensurePackage('@nx/eslint', nxVersion);
const { foo, bar } = await import('@nx/eslint/internal');
// After
ensurePackage('@nx/eslint', nxVersion);
// `require()` honors Module._initPaths (which ensurePackage updates); ESM
// dynamic `import()` doesn't, so it can't see the temp install.
const {
foo,
bar,
}: typeof import('@nx/eslint/internal') = require('@nx/eslint/internal');
```
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.
@@ -10,12 +10,26 @@ Patterns to reject in your own work and flag in reviews. Each entry: what it loo
- `function cleanVersion(v) { return clean(v) ?? coerce(v)?.version ?? undefined; }` — duplicates `normalizeSemver`.
- `getInstalledRsbuildVersionRuntime` / `getInstalled<X>FromFs` reading `require('<pkg>/package.json')` directly — duplicates `getInstalledPackageVersion`.
- 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)`.
**Reference:** Compliant — `packages/cypress/src/utils/assert-supported-cypress-version.ts` (7 lines). 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.
For the `getInstalled<Pkg>Version(tree?)` wrapper specifically:
```ts
export function getInstalledCypressVersion(tree?: Tree): string | null {
if (!tree) {
return getInstalledPackageVersion('cypress');
}
return getDeclaredPackageVersion(tree, 'cypress');
}
```
**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
@@ -227,22 +227,23 @@ export function versions(tree: Tree): VitestVersions {
### The `getInstalled<Pkg>Version(tree?)` helper
Optional `tree` parameter — with tree, reads declared from `package.json` (normalizing `latest`/`next` to the fresh-install constant); without tree, routes through the shared `getInstalledPackageVersion` from `@nx/devkit/internal` (FS resolution via `getNxRequirePaths()`).
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');
}
const installedVersion = getDependencyVersionFromPackageJson(tree, 'vitest');
if (!installedVersion) {
return null;
}
if (installedVersion === 'latest' || installedVersion === 'next') {
return clean(vitestVersion) ?? coerce(vitestVersion)?.version ?? null;
}
return clean(installedVersion) ?? coerce(installedVersion)?.version ?? null;
return getDeclaredPackageVersion(tree, 'vitest');
}
export function getInstalledVitestMajorVersion(tree?: Tree): number | null {
@@ -251,7 +252,15 @@ export function getInstalledVitestMajorVersion(tree?: Tree): number | null {
}
```
This is the cypress/playwright/vitest pattern. Reference: `packages/cypress/src/utils/versions.ts`.
Reference: `packages/cypress/src/utils/versions.ts`, `packages/rspack/src/utils/version-utils.ts`, `packages/rsbuild/src/utils/version-utils.ts`.
#### 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
-9
View File
@@ -67,15 +67,6 @@ nx generate @nx/workspace-plugin:bump-maven-version \
This automates all the version bumping instead of manual file edits.
### Creating a New Plugin
For creating a new create-nodes plugin:
```bash
nx generate @nx/workspace-plugin:create-nodes-plugin \
--name my-custom-plugin
```
## When to Use This Skill
Use this skill when you need to:
+2 -7
View File
@@ -34,19 +34,14 @@ jobs:
# - windows-latest Windows fails to build gradle wrapper which always runs when we build nx.
## https://staging.nx.app/runs/LgD4vxGn8w?utm_source=pull-request&utm_medium=comment
node_version:
# TODO(v23): remove node 20 - EOL April 2026
- 20
- 22
- 24
- 26
exclude:
# run just node v24 on macos and windows
- os: macos-latest
node_version: 20
# macos skips the oldest node to keep the macos matrix slim
- os: macos-latest
node_version: 22
# - os: windows-latest TODO(Jack): Windows fails to build gradle wrapper which always runs when we build nx. Re-enable when we fix this.
# node_version: 20
# - os: windows-latest TODO (Jack): Windows fails to build gradle wrapper which always runs when we build nx. Re-enable when we fix this.
# node_version: 22
name: Cache install (${{ matrix.os }}, node v${{ matrix.node_version }})
+8 -267
View File
@@ -16,23 +16,6 @@ interface MatrixResult {
duration: number;
}
interface Streak {
consecutive_failures: number;
failing_since: string | null;
last_passing: string | null;
}
interface HistoryEntry {
date: string;
failed: string[];
}
interface ErrorDate {
testFile: string;
startDate: string;
days: number;
}
const REPO = process.env.GITHUB_REPOSITORY || 'nrwl/nx';
const RUN_ID = process.env.GITHUB_RUN_ID || '0';
@@ -186,53 +169,7 @@ export async function collectFailureDetails(
}
}
// Step 1: 30-day failure history
const histRunsRaw = gh(
`run list --workflow=e2e-matrix.yml --repo ${REPO} --limit 40 --json databaseId,createdAt,event --jq '[.[] | select(.event == "schedule" and .databaseId != ${RUN_ID})] | .[0:30]'`
);
const histRuns: Array<{ databaseId: number; createdAt: string }> =
histRunsRaw ? JSON.parse(histRunsRaw) : [];
const histResults = await ghParallel(
histRuns.map((r) => r.databaseId),
(rid) =>
`run view ${rid} --repo ${REPO} --json jobs --jq '[.jobs[] | select(.conclusion == "failure") | .name | split(" ") | last] | unique'`
);
const history: HistoryEntry[] = histRuns.map((run) => {
const raw = histResults.get(run.databaseId) || '[]';
try {
return { date: run.createdAt, failed: JSON.parse(raw) };
} catch {
return { date: run.createdAt, failed: [] };
}
});
// Compute streaks
const streaks = new Map<string, Streak>();
for (const project of projectNames) {
let streak = 0,
firstSeen: string | null = null,
lastPassing: string | null = null,
broken = false;
for (const entry of history) {
if (broken) break;
if (entry.failed.includes(project)) {
streak++;
firstSeen = entry.date;
} else {
broken = true;
lastPassing = entry.date;
}
}
streaks.set(project, {
consecutive_failures: streak,
failing_since: firstSeen ? firstSeen.split('T')[0] : null,
last_passing: lastPassing ? lastPassing.split('T')[0] : null,
});
}
// Step 2: Fetch failure logs (one per OS/PM combo per project)
// Step 1: Fetch failure logs (one per OS/PM combo per project)
const failedJobsRaw = gh(
`run view ${RUN_ID} --repo ${REPO} --json jobs --jq '[.jobs[] | select(.conclusion == "failure") | {id: .databaseId, name: .name, project: (.name | split(" ") | last), combo: (.name | split(" ")[0])}]'`
);
@@ -296,7 +233,7 @@ export async function collectFailureDetails(
);
}
// Step 3: Build distinct failures per project — each (testFile, signature, combos) is a "failure"
// Step 2: Build distinct failures per project — each (testFile, signature, combos) is a "failure"
interface DistinctFailure {
testFile: string;
signature: string;
@@ -328,183 +265,16 @@ export async function collectFailureDetails(
projectDistinctFailures.set(project, [...seen.values()]);
}
// Step 3b: Validate each distinct failure against the first-failing run
interface FailureValidation {
status: 'new' | 'confirmed' | 'different' | 'unknown';
startDate?: string;
days?: number;
}
// Key: "project|testFile|signature"
const failureValidations = new Map<string, FailureValidation>();
for (const project of projectNames) {
const streak = streaks.get(project)!;
const failures = projectDistinctFailures.get(project) || [];
if (streak.consecutive_failures <= 1 || !streak.failing_since) {
for (const f of failures) {
failureValidations.set(`${project}|${f.testFile}|${f.signature}`, {
status: 'new',
startDate: streak.failing_since || undefined,
days: streak.consecutive_failures || 1,
});
}
continue;
}
// Fetch first-failing run's signatures across all combos
const firstRun = histRuns.find(
(r) => r.createdAt.split('T')[0] === streak.failing_since
);
if (!firstRun) {
for (const f of failures) {
failureValidations.set(`${project}|${f.testFile}|${f.signature}`, {
status: 'unknown',
});
}
continue;
}
const firstJobIdsRaw = gh(
`run view ${firstRun.databaseId} --repo ${REPO} --json jobs --jq '[.jobs[] | select(.conclusion == "failure" and (.name | split(" ") | last) == "${project}")] | group_by(.name | split("/")[0:2] | join("/")) | map(.[0].databaseId) | .[]'`
);
const firstJobIds = firstJobIdsRaw
.split('\n')
.filter((id) => id && id !== 'null');
// Collect ALL signatures from the first run
const firstRunSigs = new Set<string>(); // "testFile|signature"
for (const jobId of firstJobIds) {
const log = gh(`api repos/${REPO}/actions/jobs/${jobId}/logs`);
if (!log) continue;
const cleanedLog = log
.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /gm, '')
.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
const files = extractTestFiles(cleanedLog);
for (const f of files) {
const sig = extractErrorSignature(cleanedLog, f);
firstRunSigs.add(`${f}|${sig}`);
}
}
// Validate each current failure
for (const f of failures) {
const key = `${f.testFile}|${f.signature}`;
const fullKey = `${project}|${key}`;
if (firstRunSigs.has(key)) {
failureValidations.set(fullKey, {
status: 'confirmed',
startDate: streak.failing_since!,
days: streak.consecutive_failures,
});
} else {
failureValidations.set(fullKey, {
status: 'different',
});
}
}
}
// Step 4: Binary search for start date of each "different" failure
for (const project of projectNames) {
const streak = streaks.get(project)!;
const failures = projectDistinctFailures.get(project) || [];
const different = failures.filter((f) => {
const v = failureValidations.get(
`${project}|${f.testFile}|${f.signature}`
);
return v?.status === 'different';
});
if (!different.length) continue;
const projRunIds = histRuns
.slice(0, streak.consecutive_failures)
.map((r) => r.databaseId);
if (projRunIds.length <= 1) continue;
for (const failure of different) {
const targetSig = failure.signature;
if (!targetSig) continue;
function runHasSignature(runId: number): boolean {
const jobIdsRaw = gh(
`run view ${runId} --repo ${REPO} --json jobs --jq '[.jobs[] | select(.conclusion == "failure" and (.name | split(" ") | last) == "${project}")] | group_by(.name | split("/")[0:2] | join("/")) | map(.[0].databaseId) | .[]'`
);
for (const jid of jobIdsRaw.split('\n').filter(Boolean)) {
const log = gh(`api repos/${REPO}/actions/jobs/${jid}/logs`);
if (!log) continue;
const cleaned = log
.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /gm, '')
.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
const sig = extractErrorSignature(cleaned, failure.testFile);
if (sig === targetSig) return true;
}
return false;
}
let low = 0,
high = projRunIds.length - 1;
// Check oldest run
const fullKey = `${project}|${failure.testFile}|${failure.signature}`;
const oldestHas = runHasSignature(projRunIds[high]);
if (oldestHas) {
const run = histRuns.find((r) => r.databaseId === projRunIds[high]);
failureValidations.set(fullKey, {
status: 'different',
startDate: run?.createdAt.split('T')[0] || 'unknown',
days: projRunIds.length,
});
continue;
}
// Binary search
while (high - low > 1) {
const mid = Math.floor((low + high) / 2);
if (runHasSignature(projRunIds[mid])) low = mid;
else high = mid;
}
const foundRun = histRuns.find((r) => r.databaseId === projRunIds[low]);
failureValidations.set(fullKey, {
status: 'different',
startDate: foundRun?.createdAt.split('T')[0] || 'unknown',
days: low + 1,
});
}
}
// Step 5: Recent commits
let commitCount = 0;
if (histRuns[0]?.createdAt) {
try {
const commits = execSync(
`git log origin/master --after="${histRuns[0].createdAt}" --format="%h" --no-merges 2>/dev/null | head -30`,
{ encoding: 'utf-8', timeout: 10_000 }
).trim();
commitCount = commits ? commits.split('\n').length : 0;
} catch {
/* git not available */
}
}
// Step 6: Format report
// Step 3: Format report
const lines: string[] = ['', '🔍 *Failure Details*', ''];
const sorted = [...projectNames].sort((a, b) => {
const sa = streaks.get(a)?.consecutive_failures || 0;
const sb = streaks.get(b)?.consecutive_failures || 0;
return (
sa - sb ||
const sorted = [...projectNames].sort(
(a, b) =>
(failuresByProject.get(b)?.length || 0) -
(failuresByProject.get(a)?.length || 0)
);
});
(failuresByProject.get(a)?.length || 0) || a.localeCompare(b)
);
for (const project of sorted) {
const streak = streaks.get(project)!;
const projResults = failuresByProject.get(project) || [];
const distinctFailures = projectDistinctFailures.get(project) || [];
const block = projectLogs.get(project) || '';
@@ -517,8 +287,6 @@ export async function collectFailureDetails(
? 'all PMs'
: pms.join('+');
const since = streak.failing_since || 'today (new)';
const lastPass = streak.last_passing || '—';
const uniqueCombos = [
...new Set(
failedJobs.filter((j) => j.project === project).map((j) => j.combo)
@@ -527,35 +295,12 @@ export async function collectFailureDetails(
lines.push('———————————————————————————');
lines.push(`*${project}* — ${projResults.length} combos (${pattern})`);
lines.push(
`Project failing since ${since} | Last fully passing: ${lastPass}`
);
lines.push('');
if (distinctFailures.length > 0) {
for (const failure of distinctFailures) {
const fullKey = `${project}|${failure.testFile}|${failure.signature}`;
const val = failureValidations.get(fullKey);
let errorDate = since;
let errorDays: number | string = streak.consecutive_failures || 1;
let label = '';
if (val?.startDate) {
errorDate = val.startDate;
errorDays = val.days || 1;
}
if (val?.status === 'different') {
label = ' ⚠️ error changed mid-streak';
}
if (errorDays === 1 || errorDays === '1') {
label = ' 🆕 NEW';
}
const comboStr = failure.combos.join(', ');
lines.push(
`📋 \`${failure.testFile}\` (${comboStr}) — failing since ${errorDate} (${errorDays} ${errorDays === 1 || errorDays === '1' ? 'day' : 'days'})${label}`
);
lines.push(`📋 \`${failure.testFile}\` (${comboStr})`);
if (failure.block) {
lines.push('```');
@@ -652,10 +397,6 @@ export async function collectFailureDetails(
lines.push('');
}
if (commitCount > 0) {
lines.push(`_${commitCount} commits since last nightly_`);
}
// Build job links for the summary section
const runUrl = `https://github.com/${REPO}/actions/runs/${RUN_ID}`;
const goldenJobLinks = new Map<string, JobLink[]>();
+10 -5
View File
@@ -16,7 +16,7 @@ type MatrixDataOS = {
type MatrixData = {
coreProjects: MatrixDataProject[],
projects: MatrixDataProject[],
nodeTLS: number,
lowestNodeLTS: number,
setup: MatrixDataOS[],
}
@@ -67,20 +67,25 @@ const matrixData: MatrixData = {
{ name: 'e2e-storybook', codeowners: 'S04SVQ8H0G5' },
{ name: 'e2e-nuxt', codeowners: 'S04SJ6PL98X' }
],
// TODO(v23): remove node 20 - EOL April 2026
nodeTLS: 20,
// Non-core plugins only run on the lowest LTS. Plugin-level changes are
// less Node-version-sensitive than core, so single-version coverage is enough.
lowestNodeLTS: 22,
setup: [
{
os: 'ubuntu-latest',
os_name: 'Linux',
os_timeout: 60,
package_managers: ['npm', 'pnpm', 'yarn'],
node_versions: ['20.19.0', '22.13.0', '24.0.0'],
// TODO: re-add '26.0.0' once playwright ships the yauzl fix for node 26 extract hang.
// See https://github.com/microsoft/playwright/issues/40724
node_versions: ['22.13.0', '24.0.0'],
excluded: ['e2e-detox', 'e2e-react-native', 'e2e-expo']
},
// Docker is not supported on ARM-based macOS runners (no nested virtualization)
// See: https://github.com/docker/setup-docker-action and https://github.com/douglascamata/setup-docker-macos-action
// We may want to look into adding intel only for this docker case, at least until vm-in-vm works on latest macos
// TODO: re-add '26.0.0' once playwright ships the yauzl fix for node 26 extract hang.
// See https://github.com/microsoft/playwright/issues/40724
{ os: 'macos-latest', os_name: 'MacOS', os_timeout: 90, package_managers: ['npm'], node_versions: ['24.0.0'], excluded: ['e2e-docker'] }
// 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
// { os: 'windows-latest', os_name: 'WinOS', os_timeout: 180, package_managers: ['npm'], node_versions: ['24.0.0'], excluded: ['e2e-detox', 'e2e-react-native', 'e2e-expo'] }
@@ -124,7 +129,7 @@ for (let p = 0; p < matrixData.coreProjects.length; p++) {
}
// process other projects
for (let p = 0; p < matrixData.projects.length; p++) {
processProject(matrixData.projects[p], matrixData.nodeTLS);
processProject(matrixData.projects[p], matrixData.lowestNodeLTS);
}
if (matrix.length > 256) {
+12 -12
View File
@@ -21,7 +21,7 @@ env:
DEBUG: napi:*
NX_RUN_GROUP: ${{ github.run_id }}-${{ github.run_attempt }}
CYPRESS_INSTALL_BINARY: 0
NODE_VERSION: 22.16.0
NODE_VERSION: 26.3.0
PNPM_VERSION: 11.2.2 # Aligned with root package.json (pnpm/action-setup will helpfully error if out of sync)
NX_GRADLE_PROJECT_GRAPH_TIMEOUT: 600
@@ -190,9 +190,9 @@ jobs:
dotnet --version
# Node.js musl build from unofficial-builds.nodejs.org
curl -fsSL https://unofficial-builds.nodejs.org/download/release/v22.16.0/node-v22.16.0-linux-x64-musl.tar.xz -o node.tar.xz
curl -fsSL https://unofficial-builds.nodejs.org/download/release/v\${NODE_VERSION}/node-v\${NODE_VERSION}-linux-x64-musl.tar.xz -o node.tar.xz
tar -xJf node.tar.xz
mv node-v22.16.0-linux-x64-musl /usr/local/node
mv node-v\${NODE_VERSION}-linux-x64-musl /usr/local/node
export PATH=\"/usr/local/node/bin:\$PATH\"
node --version
@@ -290,9 +290,9 @@ jobs:
# Node.js musl build from unofficial-builds.nodejs.org. Container is x64;
# rust cross-compiles to aarch64-unknown-linux-musl, so the host node binary
# is x64-musl regardless of the build target.
curl -fsSL https://unofficial-builds.nodejs.org/download/release/v22.16.0/node-v22.16.0-linux-x64-musl.tar.xz -o node.tar.xz
curl -fsSL https://unofficial-builds.nodejs.org/download/release/v\${NODE_VERSION}/node-v\${NODE_VERSION}-linux-x64-musl.tar.xz -o node.tar.xz
tar -xJf node.tar.xz
mv node-v22.16.0-linux-x64-musl /usr/local/node
mv node-v\${NODE_VERSION}-linux-x64-musl /usr/local/node
export PATH=\"/usr/local/node/bin:\$PATH\"
node --version
@@ -321,7 +321,7 @@ jobs:
export PATH="$JAVA_HOME\bin:$PATH"
java -version
pnpm nx run-many --target=build-native -- --target=aarch64-pc-windows-msvc
name: stable - ${{ matrix.settings.target }} - node@22.16.0
name: stable - ${{ matrix.settings.target }} - node@26.3.0
runs-on: ${{ matrix.settings.host }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
@@ -433,9 +433,8 @@ jobs:
env:
DEBUG: napi:*
RUSTUP_IO_THREADS: 1
NX_PREFER_TS_NODE: true
PLAYWRIGHT_BROWSERS_PATH: 0
NODE_VERSION: 22.16.0
NODE_VERSION: 26.3.0
NX_GRADLE_DISABLE: 'true'
NX_DOTNET_DISABLE: 'true'
NODE_OPTIONS: '--max-old-space-size=4096'
@@ -443,7 +442,7 @@ jobs:
operating_system: freebsd
version: '14.0'
architecture: x86-64
environment_variables: DEBUG RUSTUP_IO_THREADS CI NX_PREFER_TS_NODE PLAYWRIGHT_BROWSERS_PATH NODE_VERSION NX_GRADLE_DISABLE NX_DOTNET_DISABLE NODE_OPTIONS
environment_variables: DEBUG RUSTUP_IO_THREADS CI PLAYWRIGHT_BROWSERS_PATH NODE_VERSION NX_GRADLE_DISABLE NX_DOTNET_DISABLE NODE_OPTIONS
shell: bash
run: |
env
@@ -620,7 +619,8 @@ jobs:
pnpm build:wasm
- name: Publish
env:
VERSION: ${{ needs.resolve-required-data.outputs.version }}
# Not named `VERSION` to avoid MSBuild adopting it as $(Version).
NX_PUBLISH_VERSION: ${{ needs.resolve-required-data.outputs.version }}
DRY_RUN: ${{ needs.resolve-required-data.outputs.dry_run_flag }}
PUBLISH_BRANCH: ${{ needs.resolve-required-data.outputs.publish_branch }}
run: |
@@ -628,10 +628,10 @@ jobs:
# Create and check out the publish branch
git checkout -b $PUBLISH_BRANCH
echo ""
echo "Version set to: $VERSION"
echo "Version set to: $NX_PUBLISH_VERSION"
echo "DRY_RUN set to: $DRY_RUN"
echo ""
pnpm nx-release --local=false $VERSION $DRY_RUN
pnpm nx-release --local=false $NX_PUBLISH_VERSION $DRY_RUN
- name: (Stable Release Only) Trigger Docs Release
# Publish docs only on a full release
+37 -6
View File
@@ -134,6 +134,41 @@ packages/angular-rspack-compiler/README.md
packages/dotnet/README.md
packages/maven/README.md
packages/nx/README.md
packages/devkit/README.md
packages/workspace/README.md
packages/js/README.md
packages/jest/README.md
packages/eslint/README.md
packages/eslint-plugin/README.md
packages/vitest/README.md
packages/cypress/README.md
packages/playwright/README.md
packages/vite/README.md
packages/webpack/README.md
packages/rollup/README.md
packages/docker/README.md
packages/gradle/README.md
packages/rsbuild/README.md
packages/web/README.md
packages/node/README.md
packages/module-federation/README.md
packages/nest/README.md
packages/rspack/README.md
packages/storybook/README.md
packages/react/README.md
packages/vue/README.md
packages/esbuild/README.md
packages/angular/README.md
packages/express/README.md
packages/plugin/README.md
packages/react-native/README.md
packages/next/README.md
packages/remix/README.md
packages/detox/README.md
packages/expo/README.md
packages/nuxt/README.md
packages/create-nx-workspace/README.md
packages/create-nx-plugin/README.md
test-output
test-results
@@ -151,11 +186,7 @@ test-results
.claude/worktrees
.nx/self-healing
# Nx Typings Output
packages/nx/**/*.d.ts
!packages/nx/src/utils/perf-hooks.d.ts
!packages/nx/src/ai/set-up-ai-agents/schema.d.ts
!packages/nx/src/native/index.d.ts
!packages/nx/src/native/dts-header.d.ts
e2e/**/*.d.ts
e2e/**/*.d.ts.map
.nx/migrate-runs
+5
View File
@@ -5,6 +5,11 @@ common-env-vars: &common-env-vars
GIT_COMMITTER_NAME: Test
SELECTED_PM: 'pnpm'
NX_NATIVE_LOGGING: 'nx::native::db'
# Pin corepack to the pnpm version from packageManager. Without this, corepack
# falls back to "latest" in directories that have no packageManager field
# (e.g. e2e temp dirs created by create-nx-workspace), pulling pnpm 11 and
# breaking install. Same treatment as .github/workflows/{ci,e2e-matrix}.yml.
COREPACK_DEFAULT_TO_LATEST: '0'
# These are need for build and link validation for next.js and astro apps
NEXT_PUBLIC_ASTRO_URL: 'https://master--nx-docs.netlify.app'
NX_DEV_URL: 'https://canary.nx.dev'
+8 -47
View File
@@ -5,31 +5,6 @@ distribute-on:
large-changeset: 6 linux-large, 6 linux-extra-large
extra-large-changeset: 8 linux-large, 8 linux-extra-large
assignment-rules:
- projects:
- e2e-gradle
targets:
- e2e-ci**
run-on:
- agent: linux-extra-large
parallelism: 1
- projects:
- e2e-next
- e2e-plugin
targets:
- e2e-ci**
run-on:
- agent: linux-extra-large
parallelism: 2
- projects:
- e2e-angular
- e2e-node
- e2e-react
targets:
- e2e-ci**
run-on:
- agent: linux-extra-large
parallelism: 1
- projects:
- nx
- workspace
@@ -44,36 +19,22 @@ assignment-rules:
- agent: linux-extra-large
parallelism: 1
- projects:
- 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
targets:
- e2e-ci**
# Module federation e2e tests build + serve a host and its remotes (several
# webpack/rspack builds at once) — each one saturates ~7 cores. Pin them to
# the larger extra-large agents, one per machine. Must precede e2e-ci**.
- targets:
- e2e-ci--src/module-federation**
run-on:
- agent: linux-large
parallelism: 1
- agent: linux-extra-large
parallelism: 2
parallelism: 1
# All other e2e tests can run in parallel
- targets:
- e2e-ci**
run-on:
- agent: linux-large
parallelism: 2
- agent: linux-extra-large
parallelism: 3
- agent: linux-extra-large
parallelism: 6
- targets:
- bench:*
+6
View File
@@ -20,3 +20,9 @@ task-exclusions:
- '**'
exclude-writes:
- '**'
- project: graph-client
target: build-client
exclude-reads:
- '**/*.stories.{js,jsx,ts,tsx,mdx}'
- '**/*.{spec,test}.{js,jsx,ts,tsx}'
+10
View File
@@ -86,6 +86,16 @@ If the prepush validation suite fails, please fix the issues before proceeding w
code adheres to the project's standards and passes all tests. DO NOT make a new commit to fix these issues. Instead,
amend the current commit.
### Testing Changes in Other Repos
To test a locally built Nx package in another repository (e.g., to verify a fix end-to-end):
```bash
pnpm copy-built-package --package nx --repo ../path/to/test-repo
```
This builds the package and copies it into the target repo's `node_modules`. It works for all packages including native Rust code.
### Testing Changes
After code changes are made, first test the specific project where the changes were made:
Generated
+48 -277
View File
@@ -158,15 +158,6 @@ dependencies = [
"syn 2.0.114",
]
[[package]]
name = "async-priority-channel"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acde96f444d31031f760c5c43dc786b97d3e1cb2ee49dd06898383fe9a999758"
dependencies = [
"event-listener",
]
[[package]]
name = "async-trait"
version = "0.1.89"
@@ -187,12 +178,6 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "atomic-take"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8ab6b55fe97976e46f91ddbed8d147d966475dc29b2032757ba47e02376fbc3"
[[package]]
name = "atomic-waker"
version = "1.1.2"
@@ -217,7 +202,7 @@ dependencies = [
"miniz_oxide",
"object",
"rustc-demangle",
"windows-link 0.2.1",
"windows-link",
]
[[package]]
@@ -396,7 +381,7 @@ dependencies = [
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link 0.2.1",
"windows-link",
]
[[package]]
@@ -469,15 +454,6 @@ dependencies = [
"static_assertions",
]
[[package]]
name = "concurrent-queue"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "console"
version = "0.15.11"
@@ -876,17 +852,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "event-listener"
version = "4.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
[[package]]
name = "eyre"
version = "0.6.12"
@@ -1201,7 +1166,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
dependencies = [
"rustix 1.1.3",
"windows-link 0.2.1",
"windows-link",
]
[[package]]
@@ -1467,7 +1432,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.62.2",
"windows-core",
]
[[package]]
@@ -1976,9 +1941,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.186"
version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "libloading"
@@ -1987,7 +1952,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60"
dependencies = [
"cfg-if",
"windows-link 0.2.1",
"windows-link",
]
[[package]]
@@ -2135,28 +2100,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "miette"
version = "7.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7"
dependencies = [
"cfg-if",
"miette-derive",
"unicode-width 0.1.14",
]
[[package]]
name = "miette-derive"
version = "7.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@@ -2210,6 +2153,8 @@ dependencies = [
"napi-sys",
"nohash-hasher",
"rustc-hash 2.1.1",
"serde",
"serde_json",
"tokio",
]
@@ -2300,18 +2245,6 @@ dependencies = [
"libc",
]
[[package]]
name = "nix"
version = "0.31.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
dependencies = [
"bitflags 2.10.0",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "nohash-hasher"
version = "0.2.0"
@@ -2337,12 +2270,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "normalize-path"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5438dd2b2ff4c6df6e1ce22d825ed2fa93ee2922235cc45186991717f0a892d"
[[package]]
name = "notify"
version = "8.2.0"
@@ -2469,6 +2396,7 @@ dependencies = [
"itertools 0.10.5",
"jsonc-parser",
"jsonrpsee",
"libc",
"machine-uid",
"mio",
"napi",
@@ -2476,6 +2404,7 @@ dependencies = [
"napi-derive",
"nix 0.30.1",
"nom 7.1.3",
"notify",
"once_cell",
"parking_lot",
"portable-pty",
@@ -2512,9 +2441,6 @@ dependencies = [
"uuid",
"vt100-ctt",
"walkdir",
"watchexec",
"watchexec-events",
"watchexec-signals",
"winapi",
"winres",
"wrap-ansi",
@@ -2604,6 +2530,17 @@ dependencies = [
"objc2-core-foundation",
]
[[package]]
name = "objc2-open-directory"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb82bed227edf5201dfedf072bba4015a33d3d4a98519837295a90f0a23f676d"
dependencies = [
"objc2",
"objc2-core-foundation",
"objc2-foundation",
]
[[package]]
name = "object"
version = "0.37.3"
@@ -2650,12 +2587,6 @@ version = "4.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52"
[[package]]
name = "parking"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
[[package]]
name = "parking_lot"
version = "0.12.5"
@@ -2676,7 +2607,7 @@ dependencies = [
"libc",
"redox_syscall 0.5.18",
"smallvec",
"windows-link 0.2.1",
"windows-link",
]
[[package]]
@@ -2952,20 +2883,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "process-wrap"
version = "9.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e842efad9119158434d193c6682e2ebee4b44d6ad801d7b349623b3f57cdf55"
dependencies = [
"futures",
"indexmap",
"nix 0.31.3",
"tokio",
"tracing",
"windows 0.62.2",
]
[[package]]
name = "psm"
version = "0.1.29"
@@ -4142,16 +4059,17 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.37.2"
version = "0.39.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f"
checksum = "a4deba334e1190ba7cb498327affa11e5ece10d26a30ab2f27fcf09504b8d8b6"
dependencies = [
"libc",
"memchr",
"ntapi",
"objc2-core-foundation",
"objc2-io-kit",
"windows 0.61.3",
"objc2-open-directory",
"windows",
]
[[package]]
@@ -4439,7 +4357,6 @@ dependencies = [
"libc",
"mio",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.61.2",
@@ -4980,63 +4897,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "watchexec"
version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3de3c4a47a75176b13fc7b3f421a80a55b13cab5b3200547774f9b25d43a79a0"
dependencies = [
"async-priority-channel",
"atomic-take",
"futures",
"libc",
"miette",
"normalize-path",
"notify",
"thiserror 2.0.18",
"tokio",
"tracing",
"watchexec-events",
"watchexec-signals",
"watchexec-supervisor",
"windows-sys 0.61.2",
]
[[package]]
name = "watchexec-events"
version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad87c046fa1050d22100e7d234db2cbf6ffd020b0ae2deff4bef6faa8f71ac44"
dependencies = [
"notify-types",
"watchexec-signals",
]
[[package]]
name = "watchexec-signals"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fd4537617a323437550d34c73a6aeeb1b489bbcc526e63f044ca3e59347101f"
dependencies = [
"miette",
"nix 0.30.1",
"thiserror 2.0.18",
]
[[package]]
name = "watchexec-supervisor"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a710aaac2dfcfb8a2e117c2f5e926bf10c533345f468f2017160c5f0e9ee0c53"
dependencies = [
"futures",
"process-wrap",
"tokio",
"tracing",
"watchexec-events",
"watchexec-signals",
]
[[package]]
name = "wayland-backend"
version = "0.3.12"
@@ -5260,38 +5120,16 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.61.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
dependencies = [
"windows-collections 0.2.0",
"windows-core 0.61.2",
"windows-future 0.2.1",
"windows-link 0.1.3",
"windows-numerics 0.2.0",
]
[[package]]
name = "windows"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
dependencies = [
"windows-collections 0.3.2",
"windows-core 0.62.2",
"windows-future 0.3.2",
"windows-numerics 0.3.1",
]
[[package]]
name = "windows-collections"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
dependencies = [
"windows-core 0.61.2",
"windows-collections",
"windows-core",
"windows-future",
"windows-numerics",
]
[[package]]
@@ -5300,20 +5138,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
dependencies = [
"windows-core 0.62.2",
]
[[package]]
name = "windows-core"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link 0.1.3",
"windows-result 0.3.4",
"windows-strings 0.4.2",
"windows-core",
]
[[package]]
@@ -5324,20 +5149,9 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link 0.2.1",
"windows-result 0.4.1",
"windows-strings 0.5.1",
]
[[package]]
name = "windows-future"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
dependencies = [
"windows-core 0.61.2",
"windows-link 0.1.3",
"windows-threading 0.1.0",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
@@ -5346,9 +5160,9 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
dependencies = [
"windows-core 0.62.2",
"windows-link 0.2.1",
"windows-threading 0.2.1",
"windows-core",
"windows-link",
"windows-threading",
]
[[package]]
@@ -5373,36 +5187,20 @@ dependencies = [
"syn 2.0.114",
]
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-numerics"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
dependencies = [
"windows-core 0.61.2",
"windows-link 0.1.3",
]
[[package]]
name = "windows-numerics"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
dependencies = [
"windows-core 0.62.2",
"windows-link 0.2.1",
"windows-core",
"windows-link",
]
[[package]]
@@ -5411,18 +5209,9 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
dependencies = [
"windows-link 0.2.1",
"windows-result 0.4.1",
"windows-strings 0.5.1",
]
[[package]]
name = "windows-result"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
dependencies = [
"windows-link 0.1.3",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
@@ -5431,16 +5220,7 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "windows-strings"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
dependencies = [
"windows-link 0.1.3",
"windows-link",
]
[[package]]
@@ -5449,7 +5229,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link 0.2.1",
"windows-link",
]
[[package]]
@@ -5494,7 +5274,7 @@ version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link 0.2.1",
"windows-link",
]
[[package]]
@@ -5534,7 +5314,7 @@ version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link 0.2.1",
"windows-link",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
@@ -5545,22 +5325,13 @@ dependencies = [
"windows_x86_64_msvc 0.53.1",
]
[[package]]
name = "windows-threading"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-threading"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
dependencies = [
"windows-link 0.2.1",
"windows-link",
]
[[package]]
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -27,3 +27,15 @@ tokens:
- 'Game-changer'
- 'Cutting-edge'
- 'Groundbreaking'
- 'delve into'
- 'delving into'
- 'embark on a journey'
- 'embark on'
- 'navigate the realm of'
- 'in the realm of'
- 'in the world of'
- 'rich tapestry'
- 'tapestry of'
- 'at its core'
- 'a testament to'
- 'plays a (vital|crucial|pivotal|key) role'
+13
View File
@@ -0,0 +1,13 @@
extends: existence
message: "Possible restatement closer '%s'. Check whether this sentence adds new info or just summarizes."
level: warning
ignorecase: true
tokens:
- 'This is (why|how|what|the reason)'
- 'In short'
- 'In summary'
- 'Ultimately'
- 'All in all'
- 'At the end of the day'
- 'The (key|main) takeaway'
- 'This (is|was) the gap'
+2
View File
@@ -67,6 +67,8 @@ exceptions:
- Expo
- React Native
- Module Federation
- TanStack
- TanStack Router
- IntelliJ
- VS Code
- VSCode
@@ -18,3 +18,15 @@ tokens:
- 'world-class'
- 'next-level'
- 'supercharge'
- 'delve'
- 'underscore'
- 'underscores'
- 'foster'
- 'empower'
- 'meticulous'
- 'meticulously'
- 'crucial'
- 'pivotal'
- 'paramount'
- 'intricate'
- 'multifaceted'
+67 -19
View File
@@ -1,8 +1,7 @@
# Nx Documentation Style Guide
This document defines the standards for Nx documentation on nx.dev, including voice, grammar, formatting, and terminology.
For automated enforcement, see the [Vale configuration](#vale-configuration) section below.
These rules apply to all content under `astro-docs/src/content/docs/`.
[Vale](#vale-configuration) enforces the mechanical ones automatically.
## Information architecture
@@ -53,14 +52,55 @@ The sidebar has four top-level sections that follow the user journey:
3. **Knowledge Base** - Recipes, troubleshooting, and topic-specific guides
4. **Reference** - Exhaustive facts, no narrative (CLI commands, configuration, API docs)
## Structural anti-AI rules (longform)
Sentence-level edits don't fix AI voice in longform pieces. The structural tells matter more.
### One canonical home per point
Each substantive point lives in exactly one section. Other sections link or reference it in one phrase. They don't re-explain.
### No drama-beat echoes
A short sentence (under ~10 words) immediately after a long one, restating the long one for emphasis, is an AI tic.
Cover the short sentence with your thumb. If nothing is lost, cut it.
### No restatement closers
Read the last sentence of each paragraph alone.
If it summarizes what the paragraph said rather than adding a fact, judgment, or turn, cut it.
### Match claims to evidence
Every quantifier ("always", "never", "all", "completely", "fully", "rarely") and every counterfactual ("would have prevented", "would have caught") should be checked against the evidence actually in the doc.
Two failure modes:
- **Over-generalization**: one observed instance written as a broad pattern. If the doc has one data point, don't write "users frequently" or "this always happens."
- **Under-calibration**: softening a true absolute, or absolutizing a partial fix. "Would have prevented" is fine when the fix categorically closes the outcome. It's wrong when the fix closes one path of several.
Ask of each strong claim: "what in this doc supports the strength of this word?" If nothing, weaken or cite.
### Pre-publish pass order
Run passes in this order. Structural first, vocabulary last.
1. Canonical-home audit: where does each substantive point live?
2. Repetition count: grep your two or three core findings. If a finding appears more than twice in prose, the third is probably redundant.
3. Drama-beat sweep.
4. Closer pass.
5. Claim audit: for each absolute and each counterfactual, check what evidence in the doc supports that strength. Weaken or cite.
6. End-to-end consistency read.
7. Vocabulary grep (cheapest, lowest value).
## The Nx voice
Nx documentation is **direct, practical, and confident**. We write like a knowledgeable colleague pairing with you not like a textbook, not like a marketing page, and not like a chatbot.
Nx documentation is **direct, practical, and confident**. We write like a knowledgeable colleague pairing with you, not like a textbook, not like a marketing page, and not like a chatbot.
The voice should be:
- **Conversational but efficient.** Use contractions. Get to the point. Don't pad sentences.
- **Second person.** Write "you" — address the reader directly.
- **Second person.** Write "you". Address the reader directly.
- **Action-oriented.** Lead with what the reader can _do_, not what Nx _is_.
- **Honest about tradeoffs.** Don't oversell. If something has limitations, say so.
@@ -76,7 +116,7 @@ The voice should be:
### Anti-AI language
Documentation must not read like it was generated by an AI assistant. Even when AI tools are used in the writing process, the output must be edited to sound like a human wrote it.
Edit AI-assisted drafts so they don't read like AI wrote them. Phrase-level passes alone won't do it. Apply the structural rules above first.
**Never use these phrases:**
@@ -84,6 +124,13 @@ Documentation must not read like it was generated by an AI assistant. Even when
- "It's worth noting that..." / "It should be noted that..."
- "In this section, we will explore..."
- "Let's dive into..." / "Let's take a closer look at..."
- "Delve into..." / "Delving into..."
- "Embark on a journey..." / "Embark on..."
- "Navigate the realm of..." / "In the realm of..." / "In the world of..."
- "At its core..."
- "A testament to..."
- "Rich tapestry" / "Tapestry of..."
- "Plays a vital/crucial/pivotal/key role"
- "Whether you're a beginner or an experienced developer..."
- "In today's fast-paced development environment..."
- "Unlock the power of..." / "Harness the power of..."
@@ -95,7 +142,7 @@ Documentation must not read like it was generated by an AI assistant. Even when
- "Game-changer" / "Cutting-edge" / "Groundbreaking"
- "Seamless" / "Seamlessly" (unless describing an actual integration)
**Avoid hedging words unless genuinely needed:**
**Avoid hedging words:**
- "Essentially" / "Basically" / "Effectively"
- "Generally speaking"
@@ -106,9 +153,11 @@ Documentation must not read like it was generated by an AI assistant. Even when
**Watch for AI-style sentence patterns:**
- Sentences that start with "This allows you to..." or "This enables you to..." — rewrite to lead with the reader's action.
- Sentences that start with "This allows you to..." or "This enables you to...". Rewrite to lead with the reader's action.
- Paragraphs that start with a general claim and then restate it slightly differently. Say it once.
- Excessive use of "robust", "leverage", "utilize", "facilitate", "comprehensive", "aforementioned."
- TED-talk verbs: "delve", "underscore" (as verb), "foster", "empower", "embark", "unlock", "harness". Replace with the concrete action.
- Filler adjectives: "meticulous", "crucial", "pivotal", "paramount", "intricate", "multifaceted".
- Lists where every item starts with the same grammatical structure repeated 5+ times with slight variation. Vary your phrasing.
### Self-referential writing
@@ -125,13 +174,13 @@ Don't:
- "In this guide, we'll walk through..."
- "This document covers..."
Get right to the point. The reader already knows they're on a page — they want the information.
Get right to the point. The reader already knows they're on a page.
### Building trust
Don't use filler words that undermine the reader's trust.
- Don't use "easily", "simply", "just", or "straightforward" — if something were truly simple, you wouldn't need to document it. These words also make readers feel bad when they struggle.
- Don't use "easily", "simply", "just", or "straightforward". If something were truly simple, you wouldn't need to document it. These words also make readers feel bad when they struggle.
- Don't use marketing language: "This feature will save you hours" or "Nx makes CI effortless."
- Be specific instead: "Remote caching can reduce CI times from 45 minutes to under 5 minutes for cache-hit builds."
@@ -387,11 +436,10 @@ Use these terms consistently. When writing about Nx concepts, use the exact term
## Vale configuration
[Vale](https://vale.sh) enforces many of the rules in this style guide automatically.
Configuration lives in `astro-docs/`:
[Vale](https://vale.sh) enforces the mechanical rules automatically. Configuration lives in `astro-docs/`:
- `.vale.ini` — Main config. Scopes rules to `src/content/docs/**/*.{mdoc,mdx,md}`.
- `.vale/styles/Nx/` — Custom rules for Nx documentation.
- `.vale.ini`: main config. Scopes rules to `src/content/docs/**/*.{mdoc,mdx,md}`.
- `.vale/styles/Nx/`: custom rules for Nx documentation.
### Running Vale
@@ -410,11 +458,11 @@ You can also install directly via `brew install vale` (macOS) or `apt-get instal
### Rule tiers
| Tier | Severity | Rules |
| -------------- | ------------ | ------------------------------------------------------------------------------------------- |
| 1 - Mechanical | `error` | Banned phrases, product capitalization |
| 2 - Structural | `warning` | Heading case, terminology, product possessives, self-referential writing, sentence patterns |
| 3 - Voice | `suggestion` | Trust-undermining words, marketing language, passive voice, serial commas |
| Tier | Severity | Rules |
| -------------- | ------------ | ---------------------------------------------------------------------------------------------------------------- |
| 1 - Mechanical | `error` | Banned phrases, product capitalization |
| 2 - Structural | `warning` | Heading case, terminology, product possessives, self-referential writing, sentence patterns, restatement closers |
| 3 - Voice | `suggestion` | Trust-undermining words, marketing language, passive voice, serial commas |
### Adding new rules
+4
View File
@@ -42,6 +42,10 @@ export default defineConfig({
},
trailingSlash: 'never',
redirects: {
'/knowledge-base/installation':
'/docs/knowledge-base/installation-and-updates',
'/guides/nx-cloud/ci-resource-usage':
'/docs/features/ci-features/resource-usage',
'/reference/remote-cache-plugins':
'/docs/reference/deprecated/self-hosted-cache-packages',
'/reference/remote-cache-plugins/s3-cache':
+41
View File
@@ -67,6 +67,11 @@ to = "/docs/getting-started/tutorials/:splat"
from = "/docs/features/ci-features/self-healing"
to = "/docs/features/ci-features/self-healing-ci"
# DOC-513: Manual DTE guide renamed to Bring Your Own Compute
[[redirects]]
from = "/docs/guides/nx-cloud/manual-dte"
to = "/docs/guides/nx-cloud/bring-your-own-compute"
[[redirects]]
from = "/docs/extending-nx/recipes/create-preset"
to = "/docs/extending-nx/create-preset"
@@ -125,6 +130,42 @@ to = "/docs/reference/deprecated/self-hosted-cache-packages"
from = "/docs/reference/remote-cache-plugins/shared-fs-cache/generators"
to = "/docs/reference/deprecated/self-hosted-cache-packages"
# DOC-503: CI page moved out of tutorials to top-level Getting Started
[[redirects]]
from = "/docs/getting-started/tutorials/self-healing-ci-tutorial"
to = "/docs/getting-started/setup-ci"
# Resource usage consolidated into the Nx Cloud add-ons platform feature pages
[[redirects]]
from = "/docs/guides/nx-cloud/ci-resource-usage"
to = "/docs/features/ci-features/resource-usage"
# NXC-4453: Knowledge Base "Installation" section renamed to "Installation and updates"
[[redirects]]
from = "/docs/knowledge-base/installation"
to = "/docs/knowledge-base/installation-and-updates"
# DOC-522: Polygraph is a standalone product, no longer part of Nx Cloud.
# Reroute the Polygraph-specific docs pages to the standalone landing page.
# force = true so these win over the catch-all rewrite below.
[[redirects]]
from = "/docs/enterprise/polygraph"
to = "https://trypolygraph.com"
status = 301
force = true
[[redirects]]
from = "/docs/concepts/synthetic-monorepos"
to = "https://trypolygraph.com"
status = 301
force = true
[[redirects]]
from = "/docs/enterprise/metadata-only-workspace"
to = "https://trypolygraph.com"
status = 301
force = true
# Rewrite for base path handling (keeps URL the same)
[[redirects]]
from = "/docs/*"
+14 -2
View File
@@ -25,7 +25,13 @@
"devkit",
"create-nx-workspace",
"dotnet",
"maven"
"maven",
"esbuild",
"gradle",
"nuxt",
"plugin",
"react-native",
"remix"
],
"target": "build"
}
@@ -44,7 +50,13 @@
"devkit",
"create-nx-workspace",
"dotnet",
"maven"
"maven",
"esbuild",
"gradle",
"nuxt",
"plugin",
"react-native",
"remix"
],
"target": "build"
}
+62 -32
View File
@@ -34,6 +34,7 @@ const learnGroups: SidebarItems = [
link: 'getting-started/start-with-existing-project',
},
{ label: 'AI integrations', link: 'getting-started/ai-setup' },
{ label: 'CI setup', link: 'getting-started/setup-ci' },
{ label: 'Editor setup', link: 'getting-started/editor-setup' },
{
label: 'Tutorials',
@@ -67,10 +68,6 @@ const learnGroups: SidebarItems = [
label: 'Reducing boilerplate',
link: 'getting-started/tutorials/reducing-configuration-boilerplate',
},
{
label: 'Setting up CI',
link: 'getting-started/tutorials/self-healing-ci-tutorial',
},
{
label: 'Gradle monorepo',
link: 'getting-started/tutorials/gradle-tutorial',
@@ -109,10 +106,6 @@ const learnGroups: SidebarItems = [
link: 'concepts/ci-concepts/parallelization-distribution',
},
{ label: 'Nx Daemon', link: 'concepts/nx-daemon' },
{
label: 'Synthetic monorepos',
link: 'concepts/synthetic-monorepos',
},
],
},
{
@@ -190,10 +183,6 @@ const learnGroups: SidebarItems = [
label: 'Dynamically allocate agents',
link: 'features/ci-features/dynamic-agents',
},
{
label: 'CI resource usage',
link: 'guides/nx-cloud/ci-resource-usage',
},
{
label: 'Optimize your TTG',
link: 'guides/nx-cloud/optimize-your-ttg',
@@ -206,17 +195,36 @@ const learnGroups: SidebarItems = [
label: 'GitHub integration',
link: 'features/ci-features/github-integration',
},
{
label: 'Sandboxing',
link: 'features/ci-features/sandboxing',
badge: 'New',
},
{
label: 'CIPE affected project graph',
link: 'guides/nx-cloud/cipe-affected-project-graph',
},
{ label: 'Encryption', link: 'guides/nx-cloud/encryption' },
{ label: 'Google auth', link: 'guides/nx-cloud/google-auth' },
{
label: 'Resource usage',
link: 'features/ci-features/resource-usage',
},
{
label: 'Dedicated compute cluster',
link: 'features/ci-features/dedicated-compute-cluster',
},
{
label: 'Sandboxing',
link: 'features/ci-features/sandboxing',
},
{
label: 'Docker layer caching',
link: 'features/ci-features/docker-layer-caching',
},
{
label: 'Docker read-through cache',
link: 'features/ci-features/docker-read-through-cache',
},
{
label: 'npm read-through cache',
link: 'features/ci-features/npm-read-through-cache',
},
],
},
{
@@ -287,8 +295,8 @@ const learnGroups: SidebarItems = [
collapsed: true,
items: [
{
label: 'Nx Console migration assistance',
link: 'guides/nx-console/console-migrate-ui',
label: 'Automate updating dependencies',
link: 'features/automate-updating-dependencies',
},
{
label: 'Advanced update process',
@@ -330,12 +338,7 @@ const learnGroups: SidebarItems = [
link: 'enterprise/publish-conformance-rules-to-nx-cloud',
},
{ label: 'Owners', link: 'enterprise/owners' },
{ label: 'Polygraph', link: 'enterprise/polygraph' },
{ label: 'Custom workflows', link: 'enterprise/custom-workflows' },
{
label: 'Metadata only workspace',
link: 'enterprise/metadata-only-workspace',
},
{ label: 'Activate license', link: 'enterprise/activate-license' },
{
label: 'Single tenant',
@@ -449,12 +452,29 @@ const technologiesGroups: SidebarItems = [
{ label: 'Expo', link: 'technologies/react/expo/introduction' },
{ label: 'Vue', link: 'technologies/vue/introduction' },
{ label: 'Nuxt', link: 'technologies/vue/nuxt/introduction' },
{
label: 'Module Federation',
link: 'technologies/module-federation/introduction',
},
{ label: 'ESLint', link: 'technologies/eslint/introduction' },
],
},
{
label: 'Node',
collapsed: false,
items: [
{ label: 'Node.js', link: 'technologies/node/introduction' },
{
label: 'Express',
link: 'technologies/node/express/introduction',
},
{ label: 'Nest', link: 'technologies/node/nest/introduction' },
],
},
{
label: 'Java (JVM)',
collapsed: false,
items: [
{ label: 'Java', link: 'technologies/java/introduction' },
{
label: 'Gradle',
@@ -464,14 +484,13 @@ const technologiesGroups: SidebarItems = [
label: 'Maven',
link: 'technologies/java/maven/introduction',
},
{ label: '.NET', link: 'technologies/dotnet/introduction' },
{
label: 'Module Federation',
link: 'technologies/module-federation/introduction',
},
{ label: 'ESLint', link: 'technologies/eslint/introduction' },
],
},
{
label: '.NET',
collapsed: false,
items: [{ label: '.NET', link: 'technologies/dotnet/introduction' }],
},
{
label: 'Build tools',
collapsed: false,
@@ -694,7 +713,7 @@ const knowledgeBaseGroups: SidebarItems = [
],
},
{
label: 'Installation',
label: 'Installation and updates',
collapsed: true,
items: [
{
@@ -705,6 +724,10 @@ const knowledgeBaseGroups: SidebarItems = [
label: 'Update global installation',
link: 'guides/installation/update-global-installation',
},
{
label: 'Nx Console migration assistance',
link: 'guides/nx-console/console-migrate-ui',
},
],
},
{
@@ -784,6 +807,10 @@ const knowledgeBaseGroups: SidebarItems = [
label: 'CreateNodes compatibility',
link: 'extending-nx/createnodes-compatibility',
},
{
label: 'Performant project graph plugins',
link: 'extending-nx/performant-project-graph-plugins',
},
{
label: 'Organization-specific plugin',
link: 'extending-nx/organization-specific-plugin',
@@ -816,7 +843,10 @@ const knowledgeBaseGroups: SidebarItems = [
label: 'Personal access tokens',
link: 'guides/nx-cloud/personal-access-tokens',
},
{ label: 'Manual DTE', link: 'guides/nx-cloud/manual-dte' },
{
label: 'Bring Your Own Compute',
link: 'guides/nx-cloud/bring-your-own-compute',
},
{
label: 'Source control integration',
link: 'guides/nx-cloud/source-control-integration',
Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 25 KiB

@@ -0,0 +1,111 @@
---
import { confBanner, isConfBannerActive } from './conf-banner';
const active = isConfBannerActive();
---
{
active && (
<a
class="conf-banner"
href={confBanner.url}
target="_blank"
rel="noopener noreferrer"
id="conf-banner-link"
>
<span class="conf-banner__text">
<span class="conf-banner__brand">
AI <span class="conf-banner__heart">&#9829;</span> Monorepos
</span>
<span class="conf-banner__desc">Free online conference &middot; June 23</span>
</span>
<span class="conf-banner__cta">
Join us!
<svg
class="conf-banner__arrow"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M5 10a.75.75 0 0 1 .75-.75h6.638L9.96 7.046a.75.75 0 0 1 1.08-1.04l3.75 3.75a.75.75 0 0 1 0 1.04l-3.75 3.75a.75.75 0 1 1-1.08-1.04l2.428-2.206H5.75A.75.75 0 0 1 5 10Z"
clip-rule="evenodd"
/>
</svg>
</span>
</a>
)
}
<style>
.conf-banner {
position: fixed;
inset-block-start: 0;
inset-inline: 0;
z-index: calc(var(--sl-z-index-navbar) + 1);
height: var(--conf-banner-h, 2.5rem);
display: flex;
align-items: center;
justify-content: center;
gap: 0.625rem;
padding-inline: 1rem;
/* Theme-aware: light strip in light mode, dark strip in dark mode. */
background-color: var(--sl-color-bg-nav);
color: var(--sl-color-gray-2);
font-size: 0.875rem;
line-height: 1.2;
text-decoration: none;
border-bottom: 1px solid var(--sl-color-hairline-shade);
}
.conf-banner__text {
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-weight: 500;
}
.conf-banner__brand {
font-weight: 700;
color: var(--sl-color-gray-1);
white-space: nowrap;
}
.conf-banner__heart {
color: #f43f5e;
}
.conf-banner__desc {
color: var(--sl-color-gray-3);
white-space: nowrap;
}
.conf-banner__cta {
display: inline-flex;
align-items: center;
gap: 0.125rem;
font-weight: 600;
color: var(--sl-color-text-accent);
text-decoration: underline;
text-underline-offset: 2px;
white-space: nowrap;
transition: color 0.15s ease;
}
.conf-banner:hover .conf-banner__cta {
color: var(--sl-color-accent-high);
}
.conf-banner__arrow {
width: 1rem;
height: 1rem;
}
/* Tight viewports: drop the descriptor so the bar stays one line at 2.5rem. */
@media (max-width: 30rem) {
.conf-banner__desc {
display: none;
}
}
</style>
@@ -19,7 +19,8 @@ const nxDevUrl = import.meta.env.SITE || 'https://nx.dev';
// Version options
const versions = [
{ version: 'v22', href: '/docs/getting-started/intro', current: true },
{ version: 'v23', href: '/docs/getting-started/intro', current: true },
{ version: 'v22', href: 'https://22.nx.dev/docs' },
{ version: 'v21', href: 'https://21.nx.dev/docs' },
{ version: 'v20', href: 'https://20.nx.dev/docs' },
{ version: 'v19', href: 'https://19.nx.dev/docs' },
@@ -5,9 +5,12 @@ import { Footer } from '@nx/nx-dev-ui-common/src/lib/footer';
import { GitHubStarWidget } from '@nx/nx-dev-ui-common/src/lib/github-star-widget';
import { WebinarNotifier } from '@nx/nx-dev-ui-common/src/lib/webinar-notifier';
import { getCollection } from 'astro:content';
import ConfBanner from './ConfBanner.astro';
import { isConfBannerActive } from './conf-banner';
const { hasSidebar } = Astro.locals.starlightRoute;
const githubStarsCount = Astro.locals.githubStarsCount ?? 0;
const showConfBanner = isConfBannerActive();
// Get banner from collection
const bannerCollection = await getCollection('banner');
@@ -23,7 +26,8 @@ const showBanner = isBannerActive(bannerConfig);
const bannerId = bannerConfig ? `${bannerConfig.title}-${bannerConfig.activeUntil || 'no-expiry'}` : '';
---
<div class="page sl-flex">
<div class:list={['page', 'sl-flex', { 'has-conf-banner': showConfBanner }]}>
<ConfBanner />
<header class="header"><slot name="header" /></header>
{
hasSidebar && (
@@ -85,6 +89,11 @@ const bannerId = bannerConfig ? `${bannerConfig.title}-${bannerConfig.activeUnti
min-height: 100vh;
}
/* Height of the conference promo strip; reused to offset everything below it. */
.page.has-conf-banner {
--conf-banner-h: 2.5rem;
}
.header {
z-index: var(--sl-z-index-navbar);
position: fixed;
@@ -98,6 +107,11 @@ const bannerId = bannerConfig ? `${bannerConfig.title}-${bannerConfig.activeUnti
background-color: var(--sl-color-bg-nav);
}
/* Push the fixed header below the banner. */
.page.has-conf-banner .header {
inset-block-start: var(--conf-banner-h);
}
:global([data-has-sidebar]) .header {
padding-inline-end: calc(
var(--sl-nav-gap) + var(--sl-nav-pad-x) + var(--sl-menu-button-size)
@@ -115,6 +129,10 @@ const bannerId = bannerConfig ? `${bannerConfig.title}-${bannerConfig.activeUnti
overflow-y: auto;
}
.page.has-conf-banner .sidebar-pane {
inset-block-start: calc(var(--sl-nav-height) + var(--conf-banner-h));
}
:global([aria-expanded='true']) ~ .sidebar-pane {
--sl-sidebar-visibility: visible;
}
@@ -145,6 +163,18 @@ const bannerId = bannerConfig ? `${bannerConfig.title}-${bannerConfig.activeUnti
padding-inline-start: var(--sl-content-inline-start);
}
.page.has-conf-banner .main-frame {
padding-top: calc(
var(--sl-nav-height) + var(--sl-mobile-toc-height) +
var(--conf-banner-h)
);
}
/* Starlight's mobile on-this-page bar is fixed at the nav height; shift it too. */
.page.has-conf-banner :global(mobile-starlight-toc > nav) {
top: calc(var(--sl-nav-height) + var(--conf-banner-h));
}
@media (min-width: 50rem) {
:global([data-has-sidebar]) .header {
padding-inline-end: var(--sl-nav-pad-x);
@@ -0,0 +1,11 @@
// Time-boxed promo bar for the "AI <3 Monorepos" online conference.
// Build-time gated: the next rebuild after `activeUntil` drops the banner.
export const confBanner = {
url: 'https://monorepo.tools/conf?utm_campaign=2026%20Conferences&utm_source=nx-docs&utm_medium=banner',
// End of June 23 2026, ET (UTC-4).
activeUntil: '2026-06-24T04:00:00Z',
};
export function isConfBannerActive(now: Date = new Date()): boolean {
return now < new Date(confBanner.activeUntil);
}
@@ -126,6 +126,7 @@ const promptLines = finalPrompt.split('\n').filter((l) => l.length > 0);
border: 1px solid var(--sl-color-hairline);
border-radius: 0.75rem;
background: var(--sl-color-gray-6);
padding: 0;
margin-bottom: 1.5rem;
}
@@ -133,7 +134,6 @@ const promptLines = finalPrompt.split('\n').filter((l) => l.length > 0);
list-style: none;
list-style-type: none;
cursor: pointer;
padding: 1rem 1.25rem;
}
.llm-prompt-card > summary::-webkit-details-marker {
@@ -155,6 +155,8 @@ const promptLines = finalPrompt.split('\n').filter((l) => l.length > 0);
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
border-bottom: 1px solid var(--sl-color-hairline);
}
.llm-prompt-icon {
@@ -213,6 +215,7 @@ const promptLines = finalPrompt.split('\n').filter((l) => l.length > 0);
.llm-prompt-preview {
position: relative;
margin-top: 0.5rem;
padding: 0.5rem 1rem;
color: var(--sl-color-gray-3);
font-size: var(--sl-text-sm);
line-height: 1.5;
@@ -240,6 +243,7 @@ const promptLines = finalPrompt.split('\n').filter((l) => l.length > 0);
display: flex;
justify-content: center;
margin-top: 0.5rem;
padding-bottom: 0.5rem;
}
.llm-prompt-caret-expanded {
@@ -262,7 +266,7 @@ const promptLines = finalPrompt.split('\n').filter((l) => l.length > 0);
}
.llm-prompt-body {
padding: 0 1.25rem 1rem;
padding: 0 1rem 0.5rem;
color: var(--sl-color-gray-2);
font-size: var(--sl-text-sm);
line-height: 1.6;
@@ -275,7 +279,7 @@ const promptLines = finalPrompt.split('\n').filter((l) => l.length > 0);
.llm-prompt-body :global(ol),
.llm-prompt-body :global(ul) {
margin: 0 0 0.5rem;
padding-left: 1.25rem;
padding-left: 0.5rem;
}
.llm-prompt-body :global(li) {
@@ -43,10 +43,15 @@ Nx plugins infer the following properties by analyzing the tool configuration.
A typical workspace will have many plugins inferring tasks. Nx processes all the plugins registered in `nx.json` to create project configuration for individual projects and a project and task graph that shows the connections between them all.
### Plugin order matters
### Order matters
Plugins are processed in the order that they appear in the `plugins` array in `nx.json`. So, if multiple plugins create a task with the same name, the plugin listed last will win. If, for some reason, you have a project with both a `vite.config.js` file and a `webpack.config.js` file, both the `@nx/vite` plugin and the `@nx/webpack` plugin will try to create a `build` task. The `build` task that is executed will be the task that belongs to the plugin listed lower in the `plugins` array.
Nx hashes the project graph node produced by each plugin to determine whether cached task results can be reused. If your plugin's `createNodes`/`createNodesV2` function returns different output on different runs or machines, Nx will compute a different hash and treat unchanged code as a cache miss. Keep output stable by following two rules:
- **Sort arrays deterministically.** The order of `targets`, `inputs`, `outputs`, `dependsOn`, and `metadata.targetGroups` entries must be the same every time—sort them explicitly before returning.
- **Avoid machine-specific or run-specific values.** Do not leak `process.env` variables, absolute paths, timestamps, or random IDs into target configuration. Prefer workspace-relative tokens (e.g. `{workspaceRoot}`) over absolute paths.
### Scope plugins to specific projects
Plugins use config files to infer tasks for projects. You can specify which config files are processed by Nx plugins using the `include` and `exclude` properties in the plugin configuration object.
@@ -1,48 +0,0 @@
---
title: Synthetic Monorepos
description: Learn how synthetic monorepos connect separate repositories into a unified dependency graph, giving you monorepo intelligence without moving code.
---
Most organizations don't have a single giant monorepo. They have a handful of monorepos per team or domain, plus dozens of standalone repos. Consolidating everything into one repository is not just a technical challenge. The organizational side (bringing teams along, changing workflows, ensuring adoption) is often harder than the code migration itself.
Synthetic monorepos let you get monorepo benefits without that consolidation.
## What is a synthetic monorepo?
A synthetic monorepo connects separate repositories into a unified dependency graph without moving any code. Which repo depends on which, what a change affects downstream, how projects relate across teams: all of that becomes visible automatically.
![A synthetic monorepo connecting multiple monorepos and standalone repos into a unified dependency graph](../../../assets/concepts/synthetic-monorepo.svg)
Unlike a traditional monorepo where all code lives in one repository, a synthetic monorepo leaves each repository where it is. Instead, it builds a cross-repo graph that tooling can reason about, just as if the code were in one place.
## What synthetic monorepos enable
A synthetic monorepo addresses several downsides of a polyrepo setup:
**Visibility** — An automatic cross-repo dependency graph shows which repo depends on which and what a change affects downstream. Always up to date, discovered from actual code — not a manually maintained spreadsheet or catalog. Nx implements this through the [Workspace Graph](/docs/enterprise/polygraph).
**Coordination** — Cross-repo changes no longer require manually sequencing PRs, managing compatibility, and coordinating release order. Tooling on top of the graph enables impact analysis, coordinated changes, and conformance checking across repo boundaries.
**Governance** — Organizational standards apply across every connected repo through [conformance rules](/docs/enterprise/conformance). Scheduled [custom workflows](/docs/enterprise/custom-workflows) check repos continuously — even ones nobody has touched in months. Detection and enforcement happen automatically, not through tickets and follow-ups.
**CI intelligence** — [Affected detection](/docs/concepts/mental-model#affected-commands), [remote caching](/docs/concepts/how-caching-works), and [distributed task execution](/docs/concepts/ci-concepts/parallelization-distribution) work across the full graph, not just within a single repo.
**AI agents** — AI coding agents are [dramatically less effective in polyrepos](https://youtu.be/alIto5fqrfk) — they can only see one repo at a time, so cross-repo features require you to manually shuttle context between sessions. A synthetic monorepo gives agents cross-repo visibility, enabling coordinated changes, parallel execution, and automatic PR creation across boundaries. [Self-healing CI](/docs/features/ci-features/self-healing-ci) catches failures automatically.
## When to use a synthetic monorepo vs. a real monorepo
A real monorepo is the best option when you can consolidate. It gives you atomic commits, a single toolchain, and the simplest mental model.
A synthetic monorepo is the better starting point when:
- **Consolidation isn't feasible yet:** team autonomy concerns, divergent CI setups, or hundreds of repos make migration impractical.
- **You need cross-repo visibility now:** you can't wait months for a migration to see how projects relate across teams.
- **Teams need to stay autonomous:** each team keeps their repo, workflow, and release cadence while still participating in a unified graph.
The two aren't mutually exclusive. Start synthetic for org-wide visibility, then consolidate tightly coupled teams into real monorepos where it makes sense.
## Synthetic monorepos with Nx Polygraph
Nx implements synthetic monorepos through [Nx Polygraph](/docs/enterprise/polygraph). Polygraph connects existing repositories into a unified, intelligent graph that powers the visibility, coordination, and CI features described above. It works with any repo, even those that don't use Nx, and requires zero changes to target repos.
Learn more about [getting started with Nx Polygraph](/docs/enterprise/polygraph).
@@ -28,25 +28,18 @@ Custom Workflows are available as part of Nx Enterprise and [consume compute cre
## Access custom workflows
Navigate to your organization's Custom Workflows page in Nx Cloud. You'll see an overview of all the current workflows. Polygraph and Conformance based workflows are currently available. In a future release of Nx Cloud, custom workflow creation will be released.
Navigate to your organization's Custom Workflows page in Nx Cloud. You'll see an overview of all the current workflows. Conformance based workflows are currently available. In a future release of Nx Cloud, custom workflow creation will be released.
![Org Overview](../../../assets/enterprise/custom-workflows/org-overview.avif)
## Apply a workflow
{% tabs %}
{% tabitem label="Polygraph workflows" %}
Polygraph workflows are designed to grab the latest graph information from Nx to keep the Workspace graph up to date for your organization.
{% /tabitem %}
{% tabitem label="Conformance workflows" %}
Conformance based workflows allow running the pre-defined conformance rules for a given workspace. Once setup you can also force a rerun of these conformance rules as desired.
{% /tabitem %}
{% /tabs %}
1. Click **Polygraph** or **Conformance** for your repeating workflows provided by Nx Cloud
1. Click **Conformance** for your repeating workflows provided by Nx Cloud
- if using **Conformance** action, then make sure you've already configured and [published a conformance rule](/docs/enterprise/publish-conformance-rules-to-nx-cloud)
![Polygraph overview](../../../assets/enterprise/custom-workflows/org-polygraph-overview.avif)
![Org workflows overview](../../../assets/enterprise/custom-workflows/org-workflows-overview.avif)
2. Click **Apply workflow** and select the workspace you wish to use for the custom workflow.
![Apply Workflow](../../../assets/enterprise/custom-workflows/apply-workflow.avif)
@@ -64,7 +57,7 @@ You can also upload your own custom launch template by clicking **Configure temp
## Perform first run
Back in the custom workflow action page, you can manually trigger a run via clicking **Force**
![Polygraph overview](../../../assets/enterprise/custom-workflows/org-polygraph-overview.avif)
![Org workflows overview](../../../assets/enterprise/custom-workflows/org-workflows-overview.avif)
Once your workflow has run, you can view its most recent execution via clicking **View Latest Execution**. If you've used with [Nx Agents](/docs/features/ci-features/distribute-task-execution), then this interface will be familiar. The execution shows each step taken by the workflow and the logs associated with each step.
@@ -1,67 +0,0 @@
---
title: Metadata-Only Workspaces
description: 'Include non-Nx repositories in Polygraph features without requiring full Nx adoption. Enable zero-friction onboarding for legacy and existing repositories.'
sidebar:
order: 2
filter: 'type:Features'
---
Metadata-only workspaces are a way to connect repositories to Nx Cloud that don't have Nx installed or configured. Unlike traditional Nx Cloud workspaces that require Nx to be set up in the repository, metadata-only workspaces can be connected with minimal configuration, allowing you to:
- Include any repository in the Workspace Graph
- Run Conformance rules across all repositories
- Use Custom Workflows for automated compliance checking
- Track dependencies and relationships between repositories
The key benefit is **zero changes required to existing repositories** - you can start using [Polygraph features](/docs/enterprise/polygraph) immediately without requiring team buy-in or significant migration effort.
## When to use metadata-Only workspaces
Metadata-only workspaces are ideal for:
1. **Legacy Repository Integration**
- Gain visibility into previously "dark" areas of your codebase
- Enforce organizational standards across all repositories, not just new ones
2. **Mixed Technology Stacks**
- Organizations using multiple frameworks and build tools
- Teams that prefer their existing tooling but want organizational oversight
3. **Gradual Nx Adoption Strategy**
- Start with organizational visibility before full Nx migration
- Allow teams to see the benefits of Nx tooling without immediate commitment
4. **Large-Scale Organizations**
- Companies with hundreds of repositories across different teams
- Situations where immediate value is needed without waiting for complete migration
## Connecting
{% aside type="note" title="Onboarding Assistance" %}
Reach out to your assigned developer productivity engineer, if you need any assistance in getting set up with polygraph and metadata-only workspaces.
{%/aside %}
{% tabs %}
{% tabitem label="GitHub Integration" %}
When using the [GitHub VCS Integration](/docs/features/ci-features/github-integration#access-control), you can easily bulk onboard your existing repositories into polygraph.
1. Start with visiting your organization overview in Nx Cloud
2. Click the **Connect a repository** button
3. Click **Connect repositories to polygraph**
4. Follow the prompts to onboard your repositories
![Bulk onboard GitHub repositories](../../../assets/enterprise/polygraph/connect-polygraph-to-repos.avif)
5. Click **Connect repositories** to finish
{% /tabitem %}
{% tabitem label="Manual" %}
Manual onboarding is done via the normal workspace onboarding process inside Nx Cloud and enable **polygraph features** before finishing the connection.
1. Start with visiting your organization overview in Nx Cloud
2. Click the **Connect a repository** button
3. Follow the prompts to connect your workspace
4. Enable polygraph features as metadata-only workspace
5. Click **Connect workspace** to finish
Repeat process for each workspace you want to connect as metadata-only workspace.
{% /tabitem %}
{% /tabs %}
@@ -1,117 +0,0 @@
---
title: Nx Enterprise - What is Polygraph?
description: 'Scale development practices across multiple repositories with cross-repository visibility, automated standards enforcement, and zero-friction adoption for existing codebases.'
sidebar:
order: 1
label: Polygraph
filter: 'type:Features'
---
No longer needing to choose between monorepo or poly-repo.
Polygraph is Nx Cloud's suite of enterprise features designed to help organizations scale their development practices across multiple repositories. These features extend the powerful benefits of an Nx workspace to multi-workspace environments and take Nx Cloud beyond CI.
## Features
- **Cross-repository visibility** through the [Workspace Graph](#workspace-graph)
- **Organizational standards enforcement** via [Conformance](#conformance) rules
- **Automated compliance checking** across all repositories powered by [Custom Workflows](#custom-workflows)
All of this can be enabled with **Zero-friction adoption** for existing repositories without requiring teams to adopt Nx or modify their workflows. With these tools, a platform engineering team is able to affect positive change across 100+ repos quickly in automated fashion, instead of being stuck with manual processes taking multiple months.
{% aside type="note" title="Nx Enterprise" %}
Polygraph features require an Nx Enterprise license. [Reach out to the team](https://nx.dev/contact/sales?utm_source=nx.dev&utm_campaign=polygraph) if you're interested in exploring enterprise!
{%/aside %}
## Use cases
- **🔍 See the Big Picture**: Visualize dependencies across your entire organization, not just within individual repos. Understand organizational structure at a glance and make informed decisions about architecture and team boundaries.
- **📋 Enforce Standards at Scale**: Define and automatically enforce coding standards, tooling requirements, and best practices across 10s or 100s of repositories. Eliminate manual audits and spreadsheet tracking, saving platform teams hundreds of hours per quarter.
- **🛡️ Proactive Auditing and Monitoring**: Run your security tools, or any tool you prefer, on regular intervals to detect and address vulnerabilities quickly, even in repositories that haven't been touched in months.
## Workspace graph
Visualizes dependencies between all repositories in your organization, providing visibility into what repos depend on other repos within the organization.
- **Identify consumers of shared libraries**: Enable confident API changes and deprecations by knowing exactly who will be affected
- **Understand organizational code structure at a glance**: Help leadership make informed decisions about architecture and team boundaries
- **Impact analysis before breaking changes**: See the blast radius of changes across your entire tech stack
![Polygraph Workspace Graph](../../../assets/enterprise/polygraph/workspace-graph.avif)
Quickly onboard new workspaces with the [GitHub VCS integration](/docs/features/ci-features/github-integration#access-control)
![GitHub VCS onboarding](../../../assets/enterprise/polygraph/connect-polygraph-to-repos.avif)
## Conformance
Define and enforce consistency, maintainability, reliability and security standards across your organization.
- **Gradual rule rollout**: Give teams time to fix issues, at a scheduled time, change a rule from _evaluated_ to _enforced_ automatically.
- **Workspace coverage**: Understand which repositories and teams are subject to which rules
- **Compliance control**: Make sure teams are keeping up with organization wide standards that they can't disable when using Nx Cloud as the registry for rules.
![CIPE conformance table view](../../../assets/enterprise/polygraph/cipe-conformance-report.avif)
See an overview of all rules set for the workspace and their current status
![Conformance rules run Table](../../../assets/enterprise/polygraph/conformance-rule-results-table.avif)
![Conformance rules meta table](../../../assets/enterprise/polygraph/conformance-rules-table-run-meta.avif)
You can setup notifications for teams, so they're always kept in the loop
![Conformance notifications](../../../assets/enterprise/polygraph/conformance-notifications.avif)
Ready to write your first conformance rule? [See our conformance guide](/docs/enterprise/conformance) to start.
## Custom workflows
Run scheduled tasks for your needs across all or a selection of repositories.
- **Scheduled execution**: Run checks on your schedule, not just when code changes—especially valuable for rarely updated repositories
- **Notifications for failures**: Keeps responsible parties informed without requiring them to constantly check dashboards
- **Regular compliance monitoring**: Keep security audits current even for repositories with infrequent changes
- **Automated data collection**: Ensures your dashboards and reports always reflect current state without manual intervention
Custom workflows enable proactive monitoring and automated compliance checking, ensuring your dashboards always reflect the current state without manual intervention.
![custom workflows page](../../../assets/enterprise/polygraph/custom-workflow-repeating-workflows.avif)
## Metadata only workspaces
Gain quick visibility across your organization's repositories without needing to migrate each repository as an Nx Workspace.
Easily onboard any repository as a **metadata-only** to immediately start contributing to the [Workspace Graph](#workspace-graph).
Metadata-only workspaces can still leverage [custom workflows](/docs/enterprise/polygraph#custom-workflows) and [conformance rules](/docs/enterprise/conformance).
Read more about [onboarding a workspace as metadata-only](/docs/enterprise/metadata-only-workspace).
## FAQ
**Q: Do I need to migrate other teams to Nx to use Polygraph?**
No, Polygraph works with any repository through [metadata-only workspaces](/docs/enterprise/metadata-only-workspace).
**Q: How does pricing work?**
Existing Nx Cloud Enterprise workspaces include Polygraph at no extra cost. Additional fees apply only for metadata-only workspaces and when consuming compute credits.
**Q: Can I enforce rules gradually?**
Yes, rules can be set to _evaluate_ mode before enforcement, and you can schedule future enforcement dates or outright disable the rule.
**Q: What are the different conformance rule statuses?**
- **Evaluated**: Rule runs and reports violations in dashboards and notifications, but won't fail CI builds; therefore, it's perfect for introducing new standards gradually or giving teams time to prepare for upcoming changes.
- **Enforced**: Rule runs and creates errors that will fail CI builds when violations are found, ensuring immediate compliance. You can schedule rules to automatically transition from evaluated to enforced on a specific date.
- **Disabled**: Rule is turned off and won't run in any workspaces.
**Q: Can I use Conformance with Nx Cloud?**
Yes! Nx Enterprise plan enable conformance rules within individual workspaces. Polygraph extends this by publishing rules across your entire organization, configuring them across multiple workspaces, and tracking results at the organizational level. An Nx Cloud Enterprise license is required for Polygraph features.
{% aside type="tip" title="Ready to start using Polygraph? " %}
Existing enterprise customers should contact their assigned developer productivity engineer to get setup. Otherwise, reach out to us about [Nx Enterprise](https://nx.dev/enterprise) to unlock Polygraph's organizational scaling features.
{%/aside %}
@@ -14,7 +14,7 @@ This preset option is pointing to a special generator function (remember, a gene
## What is a preset?
At its core, a preset is a special [generator](/docs/features/generate-code) that is shipped as part of an Nx Plugin package.
A preset is a special [generator](/docs/features/generate-code) shipped as part of an Nx Plugin package.
All first-party Nx presets are built into Nx itself, but you can [create your own plugin](/docs/extending-nx/intro) and create a generator with the magic name: `preset`. Once you've [published your plugin](/docs/extending-nx/tooling-plugin) on npm, you can now run the `create-nx-workspace` command with the preset option set to the name of your published package.
@@ -10,12 +10,13 @@ This is a reference for knowing how Nx versions and the `createNodes`/`createNod
The following table shows which export Nx will call based on the Nx version:
| Nx Version | Calls `createNodes` | Calls `createNodesV2` | Nx Call Preference |
| ------------- | ------------------- | --------------------- | ---------------------------- |
| 17.x - 19.1.x | Yes | No | Only v1 supported |
| 19.2.x - 20.x | Yes (fallback) | Yes (preferred) | Prefers v2, falls back to v1 |
| 21.x | No | Yes | Only v2 supported |
| 22.x+ | Yes (v2 signature) | Yes | Both use v2 signature |
| Nx Version | Calls `createNodes` | Calls `createNodesV2` | Nx Call Preference |
| ------------- | ------------------- | --------------------- | ------------------------------------------------------------ |
| 17.x - 19.1.x | Yes | No | Only v1 supported |
| 19.2.x - 20.x | Yes (fallback) | Yes (preferred) | Prefers v2, falls back to v1 |
| 21.x | No | Yes | Only v2 supported |
| 22.x | Yes (v2 signature) | Yes | Both use v2 signature |
| 23.x+ | Yes (v2 signature) | Yes (fallback) | Prefers `createNodes`; `createNodesV2` is a deprecated alias |
## Which Nx versions does my plugin support?
@@ -0,0 +1,230 @@
---
title: Write a Performant Project Graph Plugin
description: Practical patterns and caveats for writing fast createNodes plugins, including disk caching, work hoisting, and parallel config loading.
filter: 'type:Guides'
---
Your plugin's `createNodes` function runs every time Nx computes the project graph, which happens before _any_ task is restored from cache. If it does expensive work for every matching file, that cost is paid on every command (`nx build`, `nx graph`, even editor integrations), and it is paid by every developer and every CI machine. A plugin that recomputes everything from scratch is a common cause of slow graph creation, and the cost is magnified on Windows, where filesystem and process-spawn operations are significantly more expensive than on Linux or macOS.
The patterns below are the ones the first-party Nx plugins use to keep graph creation fast. They assume you're already familiar with the [project graph plugin API](/docs/extending-nx/project-graph-plugins) and have written a basic [tooling plugin](/docs/extending-nx/tooling-plugin).
{% aside type="note" title="Internal helpers" %}
Some helpers shown below (`PluginCache`, `calculateHashesForCreateNodes`) are exported from `@nx/devkit/internal`, and `workspaceDataDirectory` comes from `nx/src/utils/cache-directory`. These are lower-stability surfaces than the main `@nx/devkit` entry point, so they can change between major versions. They are the same helpers first-party plugins rely on, but if you need to support a wide range of Nx versions you can implement the equivalent behavior yourself (a content hash plus a JSON file on disk).
{% /aside %}
## Set up shared state once per batch
`createNodes` hands you _all_ matching files in a single call, which lets you set up shared state once and amortize expensive work across every project. Every other pattern here depends on processing the batch together rather than file by file.
Wrap your per-file logic with `createNodesFromFiles`, which fans the files out and processes them in parallel while keeping the returned results in a deterministic order:
```ts
// src/index.ts
import {
CreateNodes,
CreateNodesContext,
createNodesFromFiles,
} from '@nx/devkit';
const configGlob = '**/my-tool.config.{js,ts}';
export const createNodes: CreateNodes<MyPluginOptions> = [
configGlob,
async (configFiles, options, context) => {
// Set up shared state here, once for the whole batch (see below).
return await createNodesFromFiles(
(configFile, options, context, idx) =>
createNodesInternal(configFile, options, context, idx),
configFiles,
options,
context
);
},
];
```
{% aside type="note" title="Export name by Nx version" %}
On Nx 23+, `createNodes` carries this batched signature and is the export to use for new plugins. On Nx 21 and 22 the equivalent export is named `createNodesV2`. To support both, export `createNodes` and add `export const createNodesV2 = createNodes`. See the [CreateNodes API Compatibility](/docs/extending-nx/createnodes-compatibility) guide.
{% /aside %}
## Cache results to disk
Most of a plugin's cost is recomputing target configuration that hasn't changed. Cache the result of processing each config file on disk, keyed by a hash of everything that can affect the output. On the next run, unchanged projects are read straight from the cache instead of being reprocessed.
The hash must include every input that affects the result: the project's files, the plugin options, and any external files the config references (for example, a lockfile or a shared base config). `calculateHashesForCreateNodes` batches the workspace-context hashing for all project roots into a single call, much faster than hashing each root individually.
```ts
import { createNodesFromFiles } from '@nx/devkit';
import {
PluginCache,
calculateHashesForCreateNodes,
} from '@nx/devkit/internal';
import { hashObject } from 'nx/src/devkit-internals';
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
import { dirname, join } from 'node:path';
export const createNodes: CreateNodes<MyPluginOptions> = [
configGlob,
async (configFiles, options, context) => {
// One cache file per unique set of options so different plugin
// configurations don't collide.
const optionsHash = hashObject(options);
const cachePath = join(
workspaceDataDirectory,
`my-plugin-${optionsHash}.hash`
);
const targetsCache = new PluginCache<MyTargets>(cachePath);
const projectRoots = configFiles.map((f) => dirname(f));
const hashes = await calculateHashesForCreateNodes(
projectRoots,
options,
context
);
try {
return await createNodesFromFiles(
async (configFile, options, context, idx) => {
const hash = hashes[idx];
if (!targetsCache.has(hash)) {
// Only the expensive work runs on a cache miss.
targetsCache.set(
hash,
await buildTargets(configFile, options, context)
);
}
return {
projects: {
[projectRoots[idx]]: { targets: targetsCache.get(hash) },
},
};
},
configFiles,
options,
context
);
} finally {
// Always persist, even if one file throws, so the work already
// done isn't lost.
targetsCache.writeToDisk();
}
},
];
```
A few things to get right:
- **Write the cache in a `finally` block** so a failure processing one file doesn't discard the entries computed for the others.
- **Include referenced files in the hash.** If your config `extends` a base file or your targets depend on a lockfile, pass those paths through the `additionalGlobs` argument of `calculateHashesForCreateNodes` so a change to them invalidates the cache:
```ts
const hashes = await calculateHashesForCreateNodes(
projectRoots,
options,
context,
projectRoots.map(() => [lockFileName, ...referencedConfigFiles])
);
```
- **Respect `NX_CACHE_PROJECT_GRAPH=false`.** `PluginCache` already does this: it returns an empty cache when the variable is set, which is what makes the [development overrides](#develop-and-debug-your-plugin) below work.
## Hoist shared work out of the per-file loop
Even with a disk cache, the first run (and any run after a change) processes files. Anything that's identical across files should be computed once per batch, not once per file. Set up in-memory caches in the `createNodes` body and pass them into your per-file function:
```ts
async (configFiles, options, context) => {
// Detected once for the whole workspace, not per project.
const packageManager = detectPackageManager(context.workspaceRoot);
// Shared across files: many projects extend the same base tsconfig or
// use the same preset, so read and resolve each one only once.
const tsconfigCache = new Map<string, RawTsconfig>();
const presetCache: Record<string, unknown> = {};
return await createNodesFromFiles(
(configFile, options, context, idx) =>
buildTargets(configFile, options, context, {
packageManager,
tsconfigCache,
presetCache,
}),
configFiles,
options,
context
);
};
```
Common things worth hoisting: package-manager detection, lockfile name resolution, shared base configs, and tool presets. The `@nx/jest` plugin, for example, caches resolved presets and the tsconfig `extends` chain this way because most projects in a workspace share them.
## Load configuration files in parallel
When you must read or evaluate config files, do it concurrently with `Promise.all` rather than in a sequential loop. Reading config off disk is I/O-bound, and transpiling and evaluating TypeScript configs is CPU-bound. Both parallelize well:
```ts
const loadedConfigs = await Promise.all(
configFiles.map((configFile) =>
loadConfigFile(join(context.workspaceRoot, configFile))
)
);
```
This matters most on Windows, where each `readFileSync`/`readdirSync` call carries more overhead, so collapsing serial I/O into parallel batches has an outsized effect.
## Keep your file glob narrow
The first element of the `createNodes` tuple is a glob Nx matches against the whole workspace. A broad pattern like `**/*.json` forces Nx to consider far more files and calls your function with files you'll only discard. Match the most specific filename you can:
```ts
// Prefer this
const configGlob = '**/vite.config.{js,ts,mjs,mts,cjs,cts}';
// Over a catch-all you have to filter down yourself
const configGlob = '**/*.config.*';
```
If you can only decide whether a file is relevant after looking at its siblings (for example, requiring a `package.json` or `project.json` next to it), do that check early and `return {}` before doing any expensive work. A precise glob is still cheaper than a broad glob plus a filter.
## Keep output deterministic
Nx hashes the graph node your plugin produces to decide whether cached task results can be reused. If your function returns different output across runs or machines, because of array ordering, absolute paths, or environment values, Nx computes a different hash and treats unchanged code as a cache miss, defeating both your plugin cache and task caching downstream.
Sort arrays (`targets`, `inputs`, `outputs`, `dependsOn`) explicitly before returning, and avoid leaking `process.env` values, absolute paths, timestamps, or random IDs into target configuration. Prefer workspace-relative tokens like `{workspaceRoot}` and `{projectRoot}`. See [Inferred Tasks](/docs/concepts/inferred-tasks) for the full set of determinism rules.
## Avoid per-file process spawning and heavy top-level imports
Two patterns quietly dominate graph-creation time:
- **Spawning a child process per file.** If your plugin shells out to a tool to read configuration, the process-startup cost is paid for every project and is especially expensive on Windows. Batch the work into a single invocation where possible, or read the configuration directly instead of shelling out.
- **Heavy imports at module load.** Everything imported at the top of your plugin entry point is loaded every time Nx loads the plugin, even on a full cache hit. Keep top-level imports light and `await import(...)` heavy or rarely-used dependencies inside the code path that actually needs them.
## Develop and debug your plugin
While iterating, disable the caching layers that would otherwise hide your changes:
```shell
# The daemon caches your plugin code, so changes won't take effect until it restarts.
NX_DAEMON=false nx graph
# Bypass the project graph cache so your createNodes logic always re-runs.
NX_CACHE_PROJECT_GRAPH=false nx graph
```
To find where graph-creation time is actually going, turn on performance logging. Nx prints the duration of each internal step, including project-graph construction:
```shell
NX_PERF_LOGGING=true NX_DAEMON=false nx graph
```
If a user reports slow graph creation, ask them for this output along with `nx report`. The per-step timings make it clear whether the cost is in your plugin (`createNodes`) versus elsewhere, and `nx report` confirms the Nx and plugin versions in play.
## Related documentation
- [Extending the Project Graph](/docs/extending-nx/project-graph-plugins) - The full project graph plugin API
- [Integrate a New Tool with a Tooling Plugin](/docs/extending-nx/tooling-plugin) - End-to-end tutorial for building a plugin
- [CreateNodes API Compatibility](/docs/extending-nx/createnodes-compatibility) - Supporting multiple Nx versions
- [Inferred Tasks](/docs/concepts/inferred-tasks) - How Nx builds the graph from plugins
- [CreateNodesV2 API Reference](/docs/reference/devkit/CreateNodesV2) - Detailed API documentation
@@ -17,6 +17,10 @@ Project graph plugins are able to add new nodes or dependencies to the project g
When developing project graph plugins, disable the [Nx Daemon](/docs/concepts/nx-daemon) by setting `NX_DAEMON=false`. The daemon caches your plugin code, so changes to your plugin won't be reflected until the daemon restarts.
{% /aside %}
{% aside type="note" title="Writing a fast plugin" %}
`createNodesV2` runs on every graph computation, so its cost is paid on every command. Once your plugin works, see [Write a Performant Project Graph Plugin](/docs/extending-nx/performant-project-graph-plugins) for caching and other patterns that keep graph creation fast.
{% /aside %}
## Adding plugins to workspace
You can register a plugin by adding it to the plugins array in `nx.json`:
@@ -448,6 +448,7 @@ it('should infer tasks', () => {
Now that you have a working plugin, here are a few other topics you may want to investigate:
- [Write a performant project graph plugin](/docs/extending-nx/performant-project-graph-plugins) with caching so graph creation stays fast
- [Publish your Nx plugin](/docs/extending-nx/publish-plugin) to npm and the Nx plugin registry
- [Write migration generators](/docs/extending-nx/migration-generators) to automatically account for breaking changes
- [Create a preset](/docs/extending-nx/create-preset) to scaffold out an entire new repository
@@ -269,7 +269,7 @@ Nx provides two methods to exclude glob patterns (files and folders) from `affec
## Marking projects affected by dependency updates
By default, Nx will mark all projects as affected whenever your package manager's lock file changes. This behavior is a failsafe in case Nx misses a project that should be affected by a dependency update. If you'd like to opt into smarter behavior, you can configure Nx to only mark projects as affected if they actually depend on the updated packages.
By default, Nx will mark **all** projects as affected whenever your package manager's lock file changes. This behavior is a failsafe in case Nx misses a project that should be affected by a dependency update. You can configure this behavior with the `projectsAffectedByDependencyUpdates` option in `nx.json`:
```json
// nx.json
@@ -282,7 +282,21 @@ By default, Nx will mark all projects as affected whenever your package manager'
}
```
The flag `projectsAffectedByDependencyUpdates` can be set to `auto`, `all`, or an array that contains project specifiers. The default value is `all`.
The `projectsAffectedByDependencyUpdates` option accepts the following values:
| Value | Description |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `"all"` | **(default)** Every project in the workspace is marked as affected when the lock file changes. This is the safest option but may result in unnecessary CI work. |
| `"auto"` | Only projects whose dependencies actually changed in the lock file are marked as affected. Nx inspects the lock file structure to determine which workspace projects had their resolved dependencies change. See the note below for details. |
| `string[]` | An array of project names or glob patterns (e.g., `["app-*", "shared-lib"]`). Only the matching projects are marked as affected when the lock file changes. |
{% aside type="note" title="How 'auto' works per package manager" %}
In `"auto"` mode, Nx parses both the base and head revisions of your lock file using the same parsers it uses to build the project graph, diffs the resolved package metadata to find which dependencies changed, then maps those packages back to the workspace projects that depend on them.
Nx supports the following lock files: `pnpm-lock.yaml`, `pnpm-lock.yml`, `package-lock.json`, `yarn.lock`, `bun.lock`, and `bun.lockb`.
For binary Bun lockfiles (`bun.lockb`), Nx asks Bun to render the lockfile before diffing it, so Bun needs to be available in the environment that runs `nx affected`.
{% /aside %}
## Not using git
@@ -0,0 +1,73 @@
---
title: 'Dedicated Compute Cluster'
description: 'Reserve an isolated Nx Cloud compute environment for your organization and unlock Docker-in-Docker, sandboxing, and read-through caches.'
keywords:
[
dedicated compute,
single-tenant,
docker-in-docker,
nx agents,
add-on,
nx cloud,
]
sidebar:
label: Dedicated compute cluster
order: 20
badge: new!
filter: 'type:Features'
---
A **dedicated compute cluster** reserves an isolated Nx Cloud compute environment for your
organization, so your [Nx Agents](/docs/features/ci-features/distribute-task-execution) run in a
cluster provisioned just for your org instead of on the shared multi-tenant pool.
The dedicated cluster lets agents run Docker-in-Docker (DinD) and unlocks additional Nx Cloud
add-ons that require isolation in order to run with elevated capabilities.
{% aside type="note" title="Nx Cloud add-on" %}
The **dedicated compute cluster** is an Nx Cloud add-on. Manage it under
**Settings > Add-ons** for your organization. Nx Enterprise customers on
[single-tenant](/docs/enterprise/single-tenant/overview) deployments already run in a dedicated
environment and get these capabilities through their deployment.
{% /aside %}
## What it unlocks
A dedicated compute cluster unlocks the following:
- [**Docker-in-Docker**](#docker-in-docker-on-agents) - build and push container images, run
Testcontainers, and run any task that needs a Docker daemon, directly on Nx Agents.
- [**Sandboxing**](/docs/features/ci-features/sandboxing) - confine each task to its declared
[`inputs`](/docs/reference/project-configuration#inputs-and-named-inputs) and [`outputs`](/docs/reference/project-configuration#outputs), and catch any read or write outside them.
- [**Docker layer caching**](/docs/features/ci-features/docker-layer-caching) - reuse Docker build
layers across CI runs.
- [**Docker read-through cache**](/docs/features/ci-features/docker-read-through-cache) - serve
repeated image pulls from a cache close to your agents.
- [**npm read-through cache**](/docs/features/ci-features/npm-read-through-cache) - serve repeated
npm installs from a cache close to your agents.
DinD is available on every agent in the cluster automatically. The four add-ons are enabled
individually once the cluster is active, and are cancelled if you cancel the dedicated compute
cluster.
## Docker-in-Docker on agents
Every agent in a dedicated compute cluster can run DinD. This lets your tasks build and push
container images, run [Testcontainers](https://testcontainers.com), and execute any workflow that
needs a Docker daemon, directly on Nx Agents.
On the shared multi-tenant pool, agents run a fixed set of approved images and cannot run privileged
containers. The dedicated cluster lifts that restriction for your organization, so you can run
custom agent images and DinD workloads.
## Enabling dedicated compute cluster
A dedicated compute cluster is provisioned through your organization settings:
1. Open **Settings > Add-ons** for your organization.
2. On the **Dedicated compute cluster** card, click **Request add-on** and confirm.
3. You will be notified via email when the cluster is ready to use.
Once the cluster is active, the dependent add-ons (sandboxing, Docker layer caching, and the
read-through caches) become available to enable on the same page. If you request one of them before
the cluster is ready, it is queued and activates automatically when the cluster comes online.
@@ -0,0 +1,87 @@
---
title: 'Docker Layer Caching'
description: 'Cache Docker build layers across CI runs on Nx Agents to speed up image builds.'
keywords: [docker, layer caching, buildkit, nx agents, nx cloud, add-on]
sidebar:
label: Docker layer caching
order: 21
badge: new!
filter: 'type:Features'
---
Docker layer caching reuses the intermediate layers produced by `docker build` (the result of each
`RUN`, `COPY`, and `ADD` instruction) across CI runs. When a layer's inputs haven't changed, the
build pulls it from a registry cache instead of rebuilding it, cutting image build times on
[Nx Agents](/docs/features/ci-features/distribute-task-execution).
## How to enable it
{% aside type="note" title="Requires a dedicated compute cluster" %}
Docker layer caching is an Nx Cloud add-on that runs on a
[dedicated compute cluster](/docs/features/ci-features/dedicated-compute-cluster). Request the
cluster, then enable this add-on under **Settings > Add-ons**.
{% /aside %}
You can enable Docker layer caching through your organization settings:
1. Open **Settings > Add-ons** for your organization.
2. Under **Dedicated compute cluster** card, find **Docker layer caching** and click **Request add-on** and confirm.
When the add-on is enabled, Nx Cloud runs a registry cache inside your dedicated cluster and injects
its address into every agent as the `NX_DOCKER_CACHE_REGISTRY` environment variable. You point your
Docker builds at that registry using BuildKit's `--cache-to` and `--cache-from` flags. Cached layers
are written to and read from the in-cluster registry, so they persist across CI runs.
## Setup
### 1. Set up Docker Buildx
Layer caching requires BuildKit. Add the Buildx setup step to the `init-steps` of the launch
template that runs your Docker builds, so it runs before any `docker build` command:
```yaml
- name: Setup Docker Buildx
uses: 'nrwl/nx-cloud-workflows/main/workflow-steps/setup-docker-buildx/main.yaml'
```
### 2. Add cache flags to your build commands
Update your `docker build` commands to export and import layers from the cache registry:
```bash
docker build \
--push \
-t my-registry.example.com/my-app:1.2.3 \
--cache-to type=registry,ref=${NX_DOCKER_CACHE_REGISTRY}/my-app:main,mode=max \
--cache-from type=registry,ref=${NX_DOCKER_CACHE_REGISTRY}/my-app:main \
.
```
- **`-t my-registry.example.com/my-app:1.2.3`** - your image, tag, and destination registry. Your
agents must be authenticated to that registry to push images. That setup is outside the scope of
this guide.
- **`${NX_DOCKER_CACHE_REGISTRY}`** - provided by Nx Cloud. It points at the in-cluster cache
registry, which is separate from your final image registry. Don't push your application images
there.
- **`/my-app:main`** - the cache reference and tag. Use the `main` tag so subsequent builds reuse
the cached layers.
- **`--cache-to mode=max`** - exports all layers for maximum reuse. `mode=min` exports fewer layers;
see the [Docker registry cache docs](https://docs.docker.com/build/cache/backends/registry).
- **`--cache-from`** - imports cached layers when available.
{% aside type="note" title="Use stable cache image " %}
Use the stable tag `main` for `--cache-to` and `--cache-from`. Layers stored under the `main` tag are
kept permanently. Other tags are removed periodically, so non-`main` tags won't persist as long-lived cache.
Use distinct names per cache image (e.g. `my-app:main` and `my-api:main`) so different builds don't
overwrite each other's layers.
{% /aside %}
See the [Docker registry cache docs](https://docs.docker.com/build/cache/backends/registry) for more details.
### 3. Verify it's working
After the cache is warm, subsequent builds should show:
- `[CACHED]` markers in the `docker build` logs where layers were reused.
- Shorter build times for unchanged layers.
@@ -0,0 +1,42 @@
---
title: 'Docker Read-Through Cache'
description: 'Serve repeated Docker image pulls from a cache close to your Nx Agents instead of the upstream registry.'
keywords:
[
docker,
read-through cache,
registry mirror,
pull-through,
nx agents,
nx cloud,
add-on,
]
sidebar:
label: Docker read-through cache
order: 22
badge: new!
filter: 'type:Features'
---
The Docker read-through cache puts a registry mirror close to your [Nx Agents](/docs/features/ci-features/distribute-task-execution).
The first time an image is pulled, it is fetched from the upstream registry (for example Docker Hub)
and stored in the cache. Repeated pulls of the same image are then served from the cache instead of
the upstream registry, cutting image download time and reducing dependence on external registries.
It also protects your organization from Docker registry outages.
## How to enable it
{% aside type="note" title="Requires a dedicated compute cluster" %}
The Docker read-through cache is an Nx Cloud add-on that runs on a
[dedicated compute cluster](/docs/features/ci-features/dedicated-compute-cluster). Request the
cluster, then enable this add-on under **Settings > Add-ons**.
{% /aside %}
You can enable Docker read-through cache through your organization settings:
1. Open **Settings > Add-ons** for your organization.
2. Under **Dedicated compute cluster** card, find **Docker read-through cache** and click **Request add-on** and confirm.
Once enabled, it works automatically. You don't change your Dockerfiles, `docker pull` commands, or
image references. Repeated image pulls are served from the cache instead of the upstream registry.
@@ -0,0 +1,65 @@
---
title: 'npm Read-Through Cache'
description: 'Serve repeated npm installs from a cache close to your Nx Agents instead of the public npm registry.'
keywords:
[npm, read-through cache, registry proxy, nx agents, nx cloud, add-on, npmrc]
sidebar:
label: npm read-through cache
order: 23
badge: new!
filter: 'type:Features'
---
The npm read-through cache puts a package registry proxy close to your
[Nx Agents](/docs/features/ci-features/distribute-task-execution). The first time a package is
requested, it is fetched from the public npm registry and stored in the cache. Repeated installs of
the same package are then served from the cache instead of the upstream registry, cutting install
time and external network usage. Once the cache is warm, packages often install faster than restoring
them from `node_modules` caching.
It also protects your organization from npm registry outages.
## How to enable it
{% aside type="note" title="Requires a dedicated compute cluster" %}
The npm read-through cache is an Nx Cloud add-on that runs on a
[dedicated compute cluster](/docs/features/ci-features/dedicated-compute-cluster). Request the
cluster, then enable this add-on under **Settings > Add-ons**.
{% /aside %}
You can enable npm read-through cache through your organization settings:
1. Open **Settings > Add-ons** for your organization.
2. Under **Dedicated compute cluster** card, find **npm read-through cache** and click **Request add-on** and confirm.
When the add-on is enabled, Nx Cloud runs a caching proxy in your dedicated cluster that sits in
front of the public npm registry. You point your package manager at the proxy through an `.npmrc`
file. Installs then flow through the cache.
The cache works with **npm**, **yarn**, and **pnpm**, since they all read the `registry` setting
from `.npmrc`.
## Scope
- **Public npm packages only.** The cache proxies the public npm registry.
- **Private and scoped packages that require authentication are not cached.** Keep their existing
registry entries in your `.npmrc`. Those requests bypass the cache and go straight to your private
registry.
## Configuration
Point your package manager at the cache by setting the registry in an `.npmrc`. Place it in the
project root (simplest) or in the agent's home directory (`~/.npmrc`):
```ini
# .npmrc
registry=http://npm:4873/
```
The cache is always reachable from within your cluster at the address above.
{% aside type="note" title="If you publish packages" %}
If your CI publishes npm packages, set a publish registry so `npm publish` targets your real
registry instead of the cache. Add `publishConfig.registry` to the package's `package.json`, or
pass `npm publish --registry <url>`.
{% /aside %}
@@ -0,0 +1,145 @@
---
title: 'Resource Usage'
description: 'Upload and view per-agent CPU and memory metrics for distributed task execution to find bottlenecks, debug out-of-memory errors, and right-size your agents.'
keywords:
[
resource usage,
resource profiling,
CPU,
memory,
out of memory,
nx agents,
nx cloud,
add-on,
]
sidebar:
label: Resource usage
order: 14
badge: new!
filter: 'type:Features'
---
The resource usage add-on records per-agent CPU and memory metrics during distributed task
execution and surfaces them in Nx Cloud. Use this data to find resource bottlenecks, debug
out-of-memory (OOM) errors, and pick the right agent size for your workload, all the way down to
which task caused a spike.
{% aside type="note" title="Nx Cloud add-on" %}
Resource usage is a standalone Nx Cloud add-on. Enable it under **Settings > Add-ons** for your
organization.
{% /aside %}
{% aside type="caution" title="Nx 22.1+ Required" %}
Resource usage requires Nx 22.1 or later.
{% /aside %}
## Enabling resource usage
Enable the add-on under **Settings > Add-ons**, or from the **Enable resource profiling** prompt on
the **Analysis** tab of any CI pipeline execution.
Once the add-on is active:
- **With [Nx Agents](/docs/features/ci-features/distribute-task-execution) on Nx Cloud compute**, metrics are collected
and uploaded automatically for every agent and task. There's nothing else to configure.
- **When you [bring your own compute](/docs/guides/nx-cloud/bring-your-own-compute)** (running the agents on your own CI),
add a single CLI step per agent job to upload metrics. See
[the section below](#resource-metrics-when-you-bring-your-own-compute).
When a CI pipeline execution doesn't yet have the add-on, Nx Cloud shows a preview with sample data
and a prompt to enable it, including a note on the
[Self-healing CI](/docs/features/ci-features/self-healing-ci) PR comment when a run hits memory or
CPU issues.
## Viewing resource usage
Open any CI pipeline execution and go to the **Analysis** tab.
### Agent resource usage summary
The **Agent resource usage** table lists every agent in the run with its average and maximum CPU and
memory, plus the machine specs (cores and RAM) of its resource class. It's the fastest way to spot an
agent that ran hot.
![Agent resource usage table showing per-agent average and maximum CPU and memory](../../../../assets/guides/nx-cloud/agent-resource-usage-table.png)
### Resource usage over time
Click an agent to open its **Resource usage over time** view. Separate memory and CPU charts plot
utilization across the agent's lifetime, with reference lines for the machine's capacity and peak
usage. When a task exceeds available memory and is killed, the chart marks the out-of-memory point so
you can trace the failure back to the task that caused it.
![Resource usage over time showing memory and CPU charts by task](../../../../assets/guides/nx-cloud/resource-chart-details.png)
The detail view has a few controls for digging in:
- **View mode** - switch between **Individual** (each task or process plotted separately) and
**Stacked** (total usage at any point in time).
![Stacked view showing total resource usage](../../../../assets/guides/nx-cloud/resource-stacked-chart-view.png)
- **Reference lines** - toggle the capacity and peak-usage lines on or off.
- **Snap to max** - zoom the axis to the peak memory or CPU value.
- **Legend** - click items to focus on specific tasks or processes (for example a single
`nx build`, the Nx daemon, or CLI overhead).
![Using the legend to focus on specific tasks](../../../../assets/guides/nx-cloud/resource-chart-legend.png)
- **Timeline scrubber** - jump to a point in time or zoom in on a spike.
![Timeline scrubber for navigating resource usage over time](../../../../assets/guides/nx-cloud/resource-chart-scrubber.jpg)
- **Download CSV** - export the raw per-process data for deeper analysis.
## Common use cases
- **Find memory-hungry tasks** - figure out which project eats the most memory when running in
parallel, then lower its parallelism instead of slowing everything down.
- **Debug OOM kills** - trace an out-of-memory failure to the exact task that caused it.
- **Spot misconfigured tooling** - catch a bundler or build tool pulling in more files than it
should.
- **Right-size agents** - pick the correct agent resource class when moving to Nx Agents from
GitHub Actions or another CI provider.
- **Detect memory leaks** - look for tasks where memory keeps climbing over time.
- **Compare before and after upgrades** - check whether a dependency upgrade spiked resource usage.
## Resource metrics when you bring your own compute
{% aside type="note" title="Enterprise Feature" %}
Bringing your own compute requires the [Nx Enterprise plan](https://nx.dev/enterprise?utm_source=nx.dev&utm_medium=callout&utm_campaign=bring-your-own-compute). [Nx Agents](/docs/features/ci-features/distribute-task-execution) distributes your tasks on all plans.
{% /aside %}
If you run the agents on your own CI compute, Nx Cloud can still collect per-agent CPU and
memory metrics. Opt in by adding a single CLI step to each agent job.
### What to add
At the end of each agent job, run `npx nx-cloud upload-agent-metrics`. Use your provider's
always-run mechanism so the step runs even when the agent is killed mid-task, which is precisely the
scenario where the metrics matter most.
Here's the GitHub Actions step:
```yaml
- name: Upload agent metrics
if: always()
run: npx nx-cloud upload-agent-metrics
env:
NX_AGENT_NAME: ${{ matrix.agent }}
```
The `if: always()` condition is important: if an agent is OOM-killed mid-run, the normal step
sequence stops, but the upload still needs to happen so you can see which task caused the kill.
The [bring your own compute guide](/docs/guides/nx-cloud/bring-your-own-compute) shows the equivalent step for CircleCI,
Azure Pipelines, Bitbucket Pipelines, GitLab CI, and Jenkins.
## Configuration
Metric collection is controlled by these environment variables:
| Variable | Description |
| ------------------------------------- | -------------------------------------------------------------------------------- |
| `NX_CLOUD_DISABLE_METRICS_COLLECTION` | Set to `true` to disable CPU and memory metric collection during task execution. |
| `NX_CLOUD_METRICS_DIRECTORY` | Directory where Nx writes resource metrics during task execution. |
@@ -1,24 +1,27 @@
---
title: 'Task Sandboxing'
description: 'Hermetic task execution with IO tracing to catch undeclared dependencies and ensure correct caching.'
keywords: [sandboxing, CI, hermeticity, IO tracing, caching]
description: 'Confine each task to its declared inputs and outputs to catch undeclared dependencies and keep caching correct.'
keywords: [sandboxing, CI, hermeticity, inputs, outputs, caching]
sidebar:
label: Sandboxing
label: Task sandboxing
order: 15
badge: new!
filter: 'type:Features'
---
Task sandboxing monitors file system access during task execution and flags any reads or writes
that fall outside the declared `inputs` and `outputs` in your
Task sandboxing confines each task to the files it declares as `inputs` and `outputs` in your
[project configuration](/docs/reference/project-configuration)
(whether explicit or [inferred](/docs/concepts/inferred-tasks)).
It doesn't block access to the rest of the file system, but undeclared dependencies have direct
implications on [caching](/docs/features/cache-task-results) correctness, from false cache hits
serving stale results to missing output files after a cache restore.
Reading a file the task didn't declare, or writing outside its declared outputs, is a sandbox
violation.
Undeclared dependencies have direct implications on [caching](/docs/features/cache-task-results)
correctness, from false cache hits serving stale results to missing output files after a cache
restore.
{% aside type="note" title="Enterprise Feature" %}
Sandboxing is currently available on the [Nx Enterprise plan](https://nx.dev/enterprise). We're working on rolling it out to other Nx Cloud plans starting June 1st. If you'd like to use it sooner, [reach out to learn more](https://nx.dev/enterprise).
{% aside type="note" title="Nx Cloud add-on" %}
Sandboxing is an Nx Cloud add-on that runs on a
[dedicated compute cluster](/docs/features/ci-features/dedicated-compute-cluster). Request the
cluster, then enable sandboxing under **Settings > Add-ons**.
{% /aside %}
{% aside type="caution" title="Nx 22.6+ Required" %}
@@ -101,14 +104,10 @@ Include both directories in `outputs` so they can be replayed from cache:
## How sandboxing works
Sandboxing runs each task in a monitored environment where all file system reads and writes are
tracked.
When a task accesses a file outside its declared inputs or writes to a path outside its declared
outputs, Nx Cloud flags it.
Sandboxing runs each task in an isolated environment scoped to its declared `inputs` and `outputs`.
You get an audit trail of every file each task touched during execution, warnings when tasks have
undeclared dependencies, and confidence that your cache configuration is correct rather than just
"working so far."
undeclared dependencies, and confidence that your cache configuration is correct rather than only
appearing to work.
In **Warning** mode (recommended when getting started), violations are reported in the Nx Cloud UI
but tasks continue to completion.
@@ -138,7 +137,7 @@ Files flagged as "unexpected read" or "unexpected write" are the ones not covere
![Sandbox analysis tab showing process tree with unexpected reads and writes highlighted](../../../../assets/features/sandboxing-analysis.png)
To export the raw trace data for further analysis, click **View raw sandbox report** to download
To export the raw report data for further analysis, click **View raw sandbox report** to download
the JSON report.
![View raw sandbox report button](../../../../assets/features/sandboxing-raw-report.png)
@@ -147,6 +146,21 @@ Once you have identified the violating tasks, follow
[Fix sandbox violations](/docs/guides/nx-cloud/fix-sandbox-violations)
to download every report on a branch, classify each violation, and update your project configuration in a structured loop.
## Sandbox violations dashboard
For an organization-wide view, open **Analytics > Sandbox violations** for your workspace.
It summarizes the most recent report for each task over a time window (the last 7 days by default)
with two tiles, **Tasks with violations** and **Clean tasks**, and a table of every task showing its
count of unexpected reads and writes and when it was last seen.
Filter by branch or task to narrow it down.
The **How to fix these violations** panel offers two paths.
**Fix with AI** copies a ready-made prompt for your coding agent that downloads the reports, edits
the task config, and validates before stopping.
The manual path gives you the equivalent command sequence.
Either way, [Fix sandbox violations](/docs/guides/nx-cloud/fix-sandbox-violations) walks through the
full loop.
## Inspecting inputs and outputs
Check what your tasks currently declare before enabling sandboxing.
@@ -225,11 +239,18 @@ and reports discrepancies.
## Enabling sandboxing
Sandboxing is available for [Nx Enterprise](https://nx.dev/enterprise) customers on
[single-tenant](/docs/enterprise/single-tenant/overview) deployments using
Sandboxing requires a
[dedicated compute cluster](/docs/features/ci-features/dedicated-compute-cluster) and runs on
[Nx Agents](/docs/features/ci-features/distribute-task-execution).
It is not supported with [manual distributed task execution](/docs/guides/nx-cloud/manual-dte).
Contact your Nx Enterprise representative to enable sandboxing for your deployment.
It is not supported when you [bring your own compute](/docs/guides/nx-cloud/bring-your-own-compute).
1. Request a dedicated compute cluster under **Settings > Add-ons**, if you don't already have one.
2. Enable **Sandboxing** on the same page. If the cluster is still being provisioned, sandboxing is
queued and activates automatically once the cluster is ready.
Nx Enterprise [single-tenant](/docs/enterprise/single-tenant/overview) customers already run on a
dedicated environment.
Contact your Nx representative to turn on sandboxing for your deployment.
### Excluding paths
@@ -259,22 +280,15 @@ Patterns use glob syntax relative to the workspace root.
## Cloud settings
Enterprise customers with sandboxing enabled can configure the enforcement mode in the Nx Cloud
workspace settings under **Settings > General**.
Once sandboxing is enabled, configure the enforcement mode in the Nx Cloud workspace settings under
**Settings > General**.
![Nx Cloud settings sidebar showing General settings](../../../../assets/features/sandboxing-settings-sidebar.png)
Three enforcement modes are available:
- **Strict** tasks that violate sandbox isolation fail immediately.
- **Warning** tasks complete but violations are reported in the Nx Cloud UI.
- **Off** sandboxing is disabled.
- **Strict** - tasks that violate sandbox isolation fail immediately.
- **Warning** - tasks complete but violations are reported in the Nx Cloud UI.
- **Off** - sandboxing is disabled.
![Sandboxing enforcement mode setting with Strict, Warning, and Off options](../../../../assets/features/sandboxing-settings.png)
## Learn more
- [Cache task results](/docs/features/cache-task-results)
- [Remote cache](/docs/features/ci-features/remote-cache)
- [Project configuration reference](/docs/reference/project-configuration)
- [Nx Enterprise](https://nx.dev/enterprise)
@@ -153,8 +153,8 @@ pipelines:
> NOTE: If all tasks succeed then the `fix-ci` command becomes a no-op automatically, so that is why "always" is recommended.
{% aside type="note" title="Using manual DTE?" %}
If you use [manual distributed task execution](/docs/guides/nx-cloud/manual-dte) instead of the Nx Agents, Self-Healing CI works the same way. Add `nx fix-ci` to both the **main job** (the orchestrator) and each **agent job** with the appropriate "always run" condition. See the [manual DTE guide](/docs/guides/nx-cloud/manual-dte) for complete examples.
{% aside type="note" title="Bringing your own compute?" %}
Bringing your own compute requires the [Nx Enterprise plan](https://nx.dev/enterprise?utm_source=nx.dev&utm_medium=callout&utm_campaign=bring-your-own-compute). When you run the agents on your own CI, Self-Healing CI works the same way. Add `nx fix-ci` to both the **main job** (the orchestrator) and each **agent job** with the appropriate "always run" condition. See the [bring your own compute guide](/docs/guides/nx-cloud/bring-your-own-compute) for complete examples.
{% /aside %}
## Configuring self-healing CI
@@ -12,91 +12,129 @@ src="https://youtu.be/A0FjwsTlZ8A"
title="How Automated Code Migrations Work"
/%}
Keeping your tooling up to date is crucial for the health of your project. Tooling maintenance work can be tedious and time consuming, though. The **Nx migrate** functionality provides a way for you to
Keeping your tooling up to date is a tedious and time-consuming part of maintaining any project. The `nx migrate` command automates that work by:
- automatically update your **`package.json` dependencies**
- migrate your **configuration files** (e.g. Jest, ESLint, Nx config)
- **adjust your source code** to match the new versions of packages (e.g., migrating across breaking changes)
- Updating your `package.json` dependencies.
- Updating your configuration files (e.g. Vite, Playwright, Nx config).
- Updating your source code to match the new versions of packages (e.g., migrating across breaking changes).
To update your workspace, run:
The command guides you through the update interactively:
```shell
npx nx@latest migrate latest
nx migrate
```
{% aside type="note" title="Visual migration tool from Nx Console" %}
Want a more visual and guided way to migrate? Check out the [Migrate UI](/docs/guides/nx-console/console-migrate-ui) that comes with the [Nx Console extension](/docs/getting-started/editor-setup).
{% /aside %}
## How Nx migrate works
## How does it work?
Nx knows where its configuration files are located and ensures they match the expected format. This automated update process is commonly referred to as "migration." Each [Nx plugin](/docs/plugin-registry) can provide migrations for its area of competency. For example, the Vite plugin ships migrations that update Vite configuration files across breaking changes. When you run `nx migrate`, Nx collects the pending migrations from all the plugins you have installed and applies the necessary changes to your workspace.
Nx knows where its configuration files are located and ensures they match the expected format. This automated update process, commonly referred to as "migration," becomes even **more powerful when you leverage [Nx plugins](/docs/plugin-registry)**. Each plugin can provide migrations for its area of competency.
## Migration steps
For example, the [Nx React plugin](/docs/technologies/react/introduction) knows where to look for React and Nx specific configuration files and knows how to apply certain changes when updating to a given version of React.
Updating your Nx workspace happens in two phases:
In the example below, the React plugin defines a migration script (`./src/migrations/.../add-babel-core`) that runs when upgrading to Nx `16.7.0-beta.2` (or higher).
1. **Generate** - `nx migrate` applies the package version updates to your `package.json` and writes a `migrations.json` file. No source code is touched yet.
2. **Run** - `nx migrate --run-migrations` runs the generated migrations to update your configuration files and source code.
```json {% meta="{7,8}" %}
// migrations.json
{
"generators": {
...
"add-babel-core": {
...
"version": "16.7.0-beta.2",
"implementation": "./src/migrations/update-16-7-0/add-babel-core"
},
},
}
```
You can intervene between the phases and make adjustments as needed for your specific workspaces. This is especially important in large codebases where you might want to control the changes more granularly.
When running `nx migrate latest`, Nx parses all the available plugins and their migration files and applies the necessary changes to your workspace.
### Step 1: Generate migrations
## How do i upgrade my Nx workspace?
Updating your Nx workspace happens in three steps:
1. The **installed dependencies**, including the `package.json` and `node_modules`, are updated.
2. Nx produces a `migrations.json` file containing the **migrations to be run** based on your workspace configuration. You can inspect and adjust the file. Run the migrations to update your configuration files and source code.
3. Optionally, you can remove the `migrations.json` file or keep it to re-run the migration in different Git branches.
You can intervene at each step and make adjustments as needed for your specific workspaces. This is especially important in large codebases where you might want to control the changes more granularly.
### Step 1: update dependencies and generate migrations
First, run the `migrate` command:
Run the `migrate` command and follow the prompts:
```shell
nx migrate latest
nx migrate
```
Note you can also specify an exact version by replacing `latest` with `nx@<version>`.
Nx resolves the latest version and, when the update crosses more than one major version, asks how far to jump. Updating [one major version at a time](/docs/guides/tips-n-tricks/advanced-update#one-major-version-at-a-time-small-steps) is the safest path and is what Nx recommends.
{% aside title="Update One Major Version at a Time" %}
To avoid potential issues, it is [recommended to update one major version of Nx at a time](/docs/guides/tips-n-tricks/advanced-update#one-major-version-at-a-time-small-steps).
{% /aside %}
Nx also asks which package versions to migrate. The answer maps to the `--include` flag:
- `required` - the target package and the packages it ships with. For example, Nx itself and its plugins such as `@nx/vite`.
- `optional` - the dependency updates those packages recommend. For example, `vite` itself rather than `@nx/vite`.
- `all` - both of the above.
When unsure, choose `required`. Updating only Nx and its plugins keeps the PR scope small and has less chance of introducing issues, which matters most in large workspaces. Follow up with `nx migrate --include=optional` to catch up on the rest. If you're okay with doing everything in one PR, use `--include=all`.
In some cases you can scope the optional catch-up to a single plugin's dependencies, such as `nx migrate @nx/vite --include=optional` - see [choosing which packages to migrate](/docs/guides/tips-n-tricks/advanced-update#choosing-which-packages-to-migrate) for the caveat.
This results in:
- The `package.json` being updated
- The `package.json` being updated with the new package versions
- A `migrations.json` being generated if there are pending migrations.
At this point, no packages have been installed, and no other files have been touched.
Now, you can **inspect `package.json` to see if the changes make sense**. Sometimes the migration can update a package to a version that is either not allowed or conflicts with another package. Feel free to manually apply the desired adjustments. Also look at the `migrations.json` for the type of migrations that are going to be applied.
Now, inspect `package.json` to see if the changes make sense. Sometimes the migration can update a package to a version that is either not allowed or conflicts with another package. You are free to adjust versions before running install.
{% tabs syncKey="install-type" %}
{% tabitem label="npm" %}
```shell
npm install
```
{% /tabitem %}
{% tabitem label="yarn" %}
```shell
yarn install
```
{% /tabitem %}
{% tabitem label="pnpm" %}
```shell
pnpm install
```
{% /tabitem %}
{% tabitem label="bun" %}
```shell
bun install
```
{% /tabitem %}
{% /tabs %}
Also, look at the `migrations.json` file for the type of migrations that are going to be applied. If this file does not exist, then there are no migrations to run.
### Step 2: Run migrations
You can now run the actual code migrations that were generated in the `migrations.json` in the previous step.
Run the migrations that were generated in the previous step (`migrations.json`):
```shell
nx migrate --run-migrations
```
Depending on the migrations that ran, this might **update your source code** and **configuration files** in your workspace. All the changes will be unstaged ready for you to review and commit yourself.
#### What's in a migration?
Migrations run one at a time and contain two types of changes:
1. **Generator-based** ("script-based"): programmatic config or code changes (e.g. `rollupOptions` becomes `rolldownOptions` in `vite.config.ts` for Vite 8).
2. **Prompt-based**: AI-aided changes that can't be expressed deterministically and need judgment about your specific code.
A migration can be **generator-only**, **prompt-only**, or a **hybrid** (a generator followed by AI-aided changes).
#### Running the migrations
Generator-only migrations run automatically. All the changes are unstaged ready for you to review.
When prompt-only or hybrid migrations are queued and a supported AI agent is installed (Claude Code, OpenAI Codex, or OpenCode), Nx asks whether to continue with an agentic flow. You can answer for this run only, or have Nx remember your choice in `nx.json`. With the agentic flow enabled:
- Generator-based changes run first, and the agent validates the results.
- The agent then applies the prompt-based changes as instructed in the prompt.
Nx creates a commit for each migration while the agentic flow is enabled, so the agent reviews each migration's changes in isolation.
Without an agent, generator-only migrations and the generator half of hybrid migrations still run. The skipped prompt files are listed in the next-steps output, in order, so you can apply them yourself.
{% aside type="note" title="Running inside an AI agent" %}
If you run `nx migrate --run-migrations` from within an AI agent's terminal, Nx defers the prompt-based migrations to that agent instead of spawning another one.
{% /aside %}
{% aside type="tip" title="Migrations are version specific" %}
Note that each Nx plugin is able to provide a set of migrations which are relevant to particular versions of the package. Hence `migrations.json` will only contain migrations which are appropriate for the update you are currently applying.
Each Nx plugin provides migrations that are relevant to particular versions of the package. The generated `migrations.json` only contains the migrations appropriate for the update you are currently applying.
{% /aside %}
### Step 3: Clean up
@@ -119,14 +157,31 @@ For a list of all the plugins you currently have installed, run:
nx report
```
## Configure migrate defaults
Set workspace-wide defaults for `nx migrate` in the `migrate` section of `nx.json` instead of passing the same flags on every run. You can control commit behavior, package selection, multi-major version handling, and the agentic flow:
```json
// nx.json
{
"migrate": {
"agentic": "claude-code",
"createCommits": true,
"commitPrefix": "chore(repo): apply nx migration "
}
}
```
For all available options, see the [`migrate` section of the `nx.json` reference](/docs/reference/nx-json#migrate).
## Keep Nx packages on the same version
When you run `nx migrate`, the `nx` package and all the `@nx/` packages get updated to the same version. It is important to [keep these versions in sync](/docs/guides/tips-n-tricks/keep-nx-versions-in-sync) to have Nx work properly.
As long as you run `nx migrate` instead of manually changing the version numbers, you shouldn't have to worry about it. Also, when you add a new plugin, use `nx add <plugin>` to automatically install the version that matches your repository's version of Nx.
## Need to opt-out of some migrations?
## Need more control?
Sometimes you need to temporarily opt-out from some migrations because your workspace is not ready yet. You can manually adjust the `migrations.json` or run the update with the `--interactive` flag to choose which migrations you accept.
Sometimes you need to deviate from the defaults: skip optional package updates and catch them up later, pin a specific AI agent or disable the agentic flow, run migrations one at a time, or opt out of specific migrations by adjusting `migrations.json`.
Find more details in our [Advanced Update Process](/docs/guides/tips-n-tricks/advanced-update) guide.
Find all of these in our [Advanced Update Process](/docs/guides/tips-n-tricks/advanced-update) guide.
@@ -516,7 +516,7 @@ Not all tasks might be cacheable though. You can configure the `cache` settings
Here are some things you can dive into next:
- [Set up CI](/docs/getting-started/tutorials/self-healing-ci-tutorial) with remote caching and self-healing
- [Set up CI](/docs/getting-started/setup-ci) with remote caching and self-healing
- Read more about [how Nx compares to the Angular CLI](/docs/technologies/angular/guides/nx-and-angular)
- Learn more about the [underlying mental model of Nx](/docs/concepts/mental-model)
- Learn about popular generators such as [how to setup Tailwind](/docs/technologies/angular/guides/using-tailwind-css-with-angular-projects)
@@ -5,7 +5,7 @@ sidebar:
order: 5
---
{% llm_copy_prompt title="Tutorial 5/8: Enable and configure caching" %}
{% llm_copy_prompt title="Tutorial 5/7: Enable and configure caching" %}
Help me set up caching in my Nx workspace.
Use my existing workspace and projects for hands-on examples.
@@ -31,7 +31,6 @@ The examples below use Vite and Vitest, but the concepts apply to any tool. Subs
5. **Caching** (you are here)
6. [Understanding your workspace](/docs/getting-started/tutorials/understanding-your-workspace)
7. [Reducing boilerplate](/docs/getting-started/tutorials/reducing-configuration-boilerplate)
8. [Setting up CI](/docs/getting-started/tutorials/self-healing-ci-tutorial)
{% /aside %}
@@ -54,7 +53,7 @@ Caching is opt-in per task. The recommended approach is to set `cache: true` in
You can also enable caching per-project in `package.json` or `project.json`:
{% tabs %}
{% tabs syncKey="project-config-file" %}
{% tabitem label="package.json" %}
```jsonc
@@ -156,6 +155,45 @@ Local cache is stored in `.nx/cache` by default. Run `nx reset` to clear all loc
}
```
When a single project needs an extra input on top of the shared defaults, add it per project with the spread token (`"..."`):
{% tabs syncKey="project-config-file" %}
{% tabitem label="package.json" %}
```jsonc
// apps/my-app/package.json
{
"nx": {
"targets": {
"build": {
"inputs": ["...", "{projectRoot}/src/**/*.json"],
"outputs": ["{projectRoot}/dist"],
},
},
},
}
```
{% /tabitem %}
{% tabitem label="project.json" %}
```jsonc
// apps/my-app/project.json
{
"targets": {
"build": {
"inputs": ["...", "{projectRoot}/src/**/*.json"],
"outputs": ["{projectRoot}/dist"],
},
},
}
```
{% /tabitem %}
{% /tabs %}
The `"..."` in `inputs` expands to the inputs already set in `targetDefaults`, so this project keeps `{projectRoot}/src/**/*` and `{projectRoot}/tsconfig.json` and adds `{projectRoot}/src/**/*.json`. The `outputs` array has no spread token, so it replaces the default. For the full reference, see [spread token](/docs/reference/project-configuration#spread-token).
### Smart defaults
Nx provides sensible defaults out of the box. For example, test specification files (like `*.spec.ts`) are typically excluded from build inputs because changing a test shouldn't invalidate the build cache. This is configured through [named inputs](/docs/reference/inputs):
@@ -220,7 +258,7 @@ This command guides you through creating a free Nx Cloud account and stores an a
When a teammate or CI pipeline has already run a task with the same inputs, you get the cached result instantly, even on a fresh checkout.
For more on how remote caching works, see [remote cache (Nx Replay)](/docs/features/ci-features/remote-cache). To set up CI with Nx Cloud, see [Setting up CI](/docs/getting-started/tutorials/self-healing-ci-tutorial).
For more on how remote caching works, see [remote cache (Nx Replay)](/docs/features/ci-features/remote-cache). To set up CI with Nx Cloud, see [Setting up CI](/docs/getting-started/setup-ci).
## Learn more
@@ -5,7 +5,7 @@ sidebar:
order: 3
---
{% llm_copy_prompt title="Tutorial 3/8: Configure tasks for your projects" %}
{% llm_copy_prompt title="Tutorial 3/7: Configure tasks for your projects" %}
Help me configure tasks (build, test, lint, serve) for my Nx workspace projects.
Use my existing workspace and projects for hands-on examples.
@@ -29,7 +29,6 @@ The examples below use Vite and Vitest, but the concepts apply to any tool. Subs
5. [Caching](/docs/getting-started/tutorials/caching)
6. [Understanding your workspace](/docs/getting-started/tutorials/understanding-your-workspace)
7. [Reducing boilerplate](/docs/getting-started/tutorials/reducing-configuration-boilerplate)
8. [Setting up CI](/docs/getting-started/tutorials/self-healing-ci-tutorial)
{% /aside %}
@@ -114,7 +113,7 @@ The `^` prefix means "the same task on projects this project depends on." So `nx
You can also define dependencies without `^` for tasks within the same project:
{% tabs %}
{% tabs syncKey="project-config-file" %}
{% tabitem label="package.json" %}
```jsonc
@@ -161,7 +160,7 @@ Here, `build` always runs `generate-api-types` first within the same project.
Some tasks, like development servers, never exit. If another task depends on a long-running process, it would wait forever. Mark these tasks as `continuous` so Nx starts them alongside their dependents instead of waiting for them to finish:
{% tabs %}
{% tabs syncKey="project-config-file" %}
{% tabitem label="package.json" %}
```jsonc
@@ -229,6 +228,47 @@ When many projects share the same task configuration, defining it in every `proj
Individual projects can still override these defaults when needed. The cascade order is: project-level config > target defaults > defaults.
### Extending target defaults for a project
By default, when a project redefines a property it replaces the target default entirely. So if `build` defaults to `dependsOn: ["^build"]` and a project sets its own `dependsOn`, the `^build` entry is lost.
Use the spread token (`"..."`) to extend that configuration rather than replace it:
{% tabs syncKey="project-config-file" %}
{% tabitem label="package.json" %}
```jsonc
// apps/my-app/package.json
{
"nx": {
"targets": {
"build": {
"dependsOn": ["...", "generate-api-types"],
},
},
},
}
```
{% /tabitem %}
{% tabitem label="project.json" %}
```jsonc
// apps/my-app/project.json
{
"targets": {
"build": {
"dependsOn": ["...", "generate-api-types"],
},
},
}
```
{% /tabitem %}
{% /tabs %}
The `"..."` expands to whatever configuration the target already has, so here `my-app`'s `build` keeps `^build` and adds `generate-api-types`. That existing configuration can come from `targetDefaults` or a plugin's inferred task, not only from target defaults. The token also works in objects and configurations. For the full reference, see [spread token](/docs/reference/project-configuration#spread-token).
For more on reducing configuration, see [Reducing Configuration Boilerplate](/docs/getting-started/tutorials/reducing-configuration-boilerplate).
## Learn more
@@ -5,7 +5,7 @@ sidebar:
order: 1
---
{% llm_copy_prompt title="Tutorial 1/8: Set up an Nx workspace" %}
{% llm_copy_prompt title="Tutorial 1/7: Set up an Nx workspace" %}
Help me learn Nx step by step using this tutorial series.
If my current directory already has nx.json, skip setup and teach me using my existing workspace.
@@ -32,7 +32,6 @@ Nx works with any repo structure and plays well with tools you already use: pnpm
5. [Caching](/docs/getting-started/tutorials/caching)
6. [Understanding your workspace](/docs/getting-started/tutorials/understanding-your-workspace)
7. [Reducing boilerplate](/docs/getting-started/tutorials/reducing-configuration-boilerplate)
8. [Setting up CI](/docs/getting-started/tutorials/self-healing-ci-tutorial)
{% /aside %}
@@ -5,7 +5,7 @@ sidebar:
order: 2
---
{% llm_copy_prompt title="Tutorial 2/8: Understand project dependencies" %}
{% llm_copy_prompt title="Tutorial 2/7: Understand project dependencies" %}
Help me understand how my Nx workspace tracks dependencies between projects.
Use my existing workspace and projects for hands-on examples.
@@ -29,7 +29,6 @@ As your workspace grows, projects start depending on each other and on external
5. [Caching](/docs/getting-started/tutorials/caching)
6. [Understanding your workspace](/docs/getting-started/tutorials/understanding-your-workspace)
7. [Reducing boilerplate](/docs/getting-started/tutorials/reducing-configuration-boilerplate)
8. [Setting up CI](/docs/getting-started/tutorials/self-healing-ci-tutorial)
{% /aside %}
@@ -479,7 +479,7 @@ Not all tasks might be cacheable though. You can configure the `cache` settings
Here are some things you can dive into next:
- [Set up CI](/docs/getting-started/tutorials/self-healing-ci-tutorial) with remote caching and self-healing
- [Set up CI](/docs/getting-started/setup-ci) with remote caching and self-healing
- Learn more about the [underlying mental model of Nx](/docs/concepts/mental-model)
- Learn how to [migrate your existing project to Nx](/docs/guides/adopting-nx/adding-to-existing-project)
- [Setup Storybook for our shared UI library](/docs/technologies/test-tools/storybook/guides/overview-react)
@@ -5,7 +5,7 @@ sidebar:
order: 7
---
{% llm_copy_prompt title="Tutorial 7/8: Reduce configuration with plugins" %}
{% llm_copy_prompt title="Tutorial 7/7: Reduce configuration with plugins" %}
Help me reduce configuration boilerplate in my Nx workspace.
Use my existing workspace and projects for hands-on examples.
@@ -31,7 +31,6 @@ The examples below use Vite and Vitest, but the concepts apply to any tool. Subs
5. [Caching](/docs/getting-started/tutorials/caching)
6. [Understanding your workspace](/docs/getting-started/tutorials/understanding-your-workspace)
7. **Reducing boilerplate** (you are here)
8. [Setting up CI](/docs/getting-started/tutorials/self-healing-ci-tutorial)
{% /aside %}
@@ -41,7 +40,7 @@ This tutorial assumes you have an Nx workspace with configured tasks. If you're
Consider a workspace with 20 libraries, each with verbose task configuration:
{% tabs %}
{% tabs syncKey="project-config-file" %}
{% tabitem label="package.json" %}
```jsonc
@@ -127,7 +126,7 @@ Move shared configuration to `nx.json` so projects inherit defaults:
Now each project only needs to specify what's unique:
{% tabs %}
{% tabs syncKey="project-config-file" %}
{% tabitem label="package.json" %}
```jsonc
@@ -357,6 +356,43 @@ Task configuration can come from three sources, applied in this order:
Each layer can override the previous one. This means you can use plugins for sensible defaults and only add project-level config when a project needs something different.
Overriding replaces the inherited value. When a project needs to add to that configuration instead, whether it comes from `targetDefaults` or an inferred plugin task, use the spread token (`"..."`):
{% tabs syncKey="project-config-file" %}
{% tabitem label="package.json" %}
```jsonc
// packages/my-lib/package.json
{
"nx": {
"targets": {
"build": {
"inputs": ["...", "{projectRoot}/extra.config.ts"],
},
},
},
}
```
{% /tabitem %}
{% tabitem label="project.json" %}
```jsonc
// packages/my-lib/project.json
{
"targets": {
"build": {
"inputs": ["...", "{projectRoot}/extra.config.ts"],
},
},
}
```
{% /tabitem %}
{% /tabs %}
Here `"..."` expands to the `inputs` already inferred for `build`, so the project keeps them and adds `{projectRoot}/extra.config.ts`. For the full reference, see [spread token](/docs/reference/project-configuration#spread-token).
## This is optional
Plugins are optional. The explicit task configuration from [Configuring Tasks](/docs/getting-started/tutorials/configuring-tasks) works perfectly well. Use plugins when:
@@ -380,5 +416,5 @@ Stick with explicit configuration when:
{% cards cols=2 %}
{% card title="Previous: Understanding Your Workspace" description="Explore projects, graphs, and debug issues" url="/docs/getting-started/tutorials/understanding-your-workspace" /%}
{% card title="Next: Setting Up CI" description="Configure CI with remote caching and self-healing" url="/docs/getting-started/tutorials/self-healing-ci-tutorial" /%}
{% card title="Set Up CI" description="Connect Nx Cloud for remote caching and self-healing CI" url="/docs/getting-started/setup-ci" /%}
{% /cards %}
@@ -5,7 +5,7 @@ sidebar:
order: 4
---
{% llm_copy_prompt title="Tutorial 4/8: Run tasks across your workspace" %}
{% llm_copy_prompt title="Tutorial 4/7: Run tasks across your workspace" %}
Help me run tasks in my Nx workspace efficiently.
Use my existing workspace and projects for hands-on examples.
@@ -29,7 +29,6 @@ The examples below use Vite and Vitest, but the concepts apply to any tool. Subs
5. [Caching](/docs/getting-started/tutorials/caching)
6. [Understanding your workspace](/docs/getting-started/tutorials/understanding-your-workspace)
7. [Reducing boilerplate](/docs/getting-started/tutorials/reducing-configuration-boilerplate)
8. [Setting up CI](/docs/getting-started/tutorials/self-healing-ci-tutorial)
{% /aside %}
@@ -1,174 +0,0 @@
---
title: 'Setting Up CI'
description: Configure CI for your Nx workspace with remote caching, affected commands, distributed task execution, and self-healing to keep your pipeline fast and reliable.
sidebar:
label: 'Setting Up CI'
filter: 'type:Guides'
---
{% llm_copy_prompt title="Tutorial 8/8: Set up CI with Nx Cloud" %}
Help me set up CI for my Nx workspace.
Connect to Nx Cloud with `nx connect`, generate a CI workflow with `nx g @nx/workspace:ci-workflow`, and walk me through remote caching, affected commands, and self-healing CI.
Stay on-topic: only teach what's covered on this page. Do not introduce concepts from later tutorials.
Tutorial: {pageUrl}
{% /llm_copy_prompt %}
Connect your workspace to Nx Cloud, generate a CI workflow, and enable remote caching, affected commands, distributed task execution, and self-healing to keep your pipeline fast and reliable.
{% aside type="note" title="Tutorial Series" %}
1. [Crafting your workspace](/docs/getting-started/tutorials/crafting-your-workspace)
2. [Managing dependencies](/docs/getting-started/tutorials/managing-dependencies)
3. [Configuring tasks](/docs/getting-started/tutorials/configuring-tasks)
4. [Running tasks](/docs/getting-started/tutorials/running-tasks)
5. [Caching](/docs/getting-started/tutorials/caching)
6. [Understanding your workspace](/docs/getting-started/tutorials/understanding-your-workspace)
7. [Reducing boilerplate](/docs/getting-started/tutorials/reducing-configuration-boilerplate)
8. **Setting up CI** (you are here)
{% /aside %}
This tutorial assumes you have a [GitHub account](https://github.com) and [Node.js](https://nodejs.org) v20.19 or later.
## Connect to Nx Cloud
### Don't have a workspace yet?
Learn how to create a new workspace and connect it to Nx Cloud:
{% call_to_action variant="default" title="Set up a new Nx workspace" url="https://cloud.nx.app/get-started?utm_source=nx-dev&utm_medium=ci-tutorial&utm_campaign=try-nx-cloud" description="Setup takes less than 5 minutes" /%}
This also generates a CI workflow, so you can skip ahead to [Remote caching](#remote-caching).
### Connect an existing workspace
If you already have an Nx workspace, connect it to Nx Cloud:
{% aside type="note" title="Prerequisites for nx connect" %}
Your workspace must be pushed to a Git provider (GitHub, GitLab, Bitbucket, or Azure DevOps) before running `nx connect`. After connecting, Nx Cloud opens a PR that adds `nxCloudId` to `nx.json`. Merge this PR before proceeding so CI runs appear on the Nx Cloud dashboard.
{% /aside %}
```shell
nx connect
```
This creates an Nx Cloud account (if you don't have one) and connects your workspace. Once connected, you can see your workspace in your [Nx Cloud organization](https://cloud.nx.app/orgs).
The access token is stored in `nx.json` and should be committed to your repository. It only grants cache read/write access, not admin access to your Nx Cloud organization.
## Generate a CI workflow
If your workspace already has a CI workflow (e.g., `.github/workflows/ci.yml`), skip to [Remote caching](#remote-caching).
Generate a CI workflow for GitHub Actions:
```shell
nx add @nx/workspace
nx g @nx/workspace:ci-workflow --ci=github
```
The `@nx/workspace` package provides the CI workflow generator. Once installed, the generator creates a `.github/workflows/ci.yml` file. It also supports CircleCI, GitLab CI, Azure Pipelines, and Bitbucket Pipelines. Pass a different `--ci` value or run `nx g @nx/workspace:ci-workflow --help` to see all options.
{% aside type="note" title="Generated output may differ" %}
The generated workflow may differ from the example below depending on your workspace setup and Nx version. The key elements (affected command, remote caching, fix-ci) will be present.
{% /aside %}
```yaml
# .github/workflows/ci.yml
name: CI
on:
push:
branches:
- main
pull_request:
permissions:
actions: read
contents: read
jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
filter: tree:0
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npx nx affected -t lint test build
- run: npx nx fix-ci
if: always()
```
This workflow includes several Nx CI features out of the box. The sections below explain each one.
## Remote caching
When Nx Cloud is connected, task results are cached remotely. If a task has already run with the same inputs (on any machine or CI run), the result is replayed instantly instead of running again.
This means:
- The second CI run on a PR is faster because unchanged tasks hit the cache
- Developers pulling the latest `main` get cached results from CI
- Build artifacts like `dist/` and test coverage are restored from cache, not recomputed
For more details, see [remote cache (Nx Replay)](/docs/features/ci-features/remote-cache).
## Running only affected tasks
The generated workflow uses `nx affected` instead of `nx run-many`. This compares the PR's changes against the base branch and only runs tasks for projects that could be impacted:
```shell
nx affected -t lint test build
```
Nx determines the base and head commits using `NX_BASE` and `NX_HEAD` environment variables. The generated CI workflow configures these automatically through the `fetch-depth: 0` checkout, which gives Nx access to the full git history for comparison.
On a PR, Nx compares the PR branch against `main` (or whatever `defaultBase` is set to in `nx.json`). On a push to `main`, it compares against the previous commit.
For more details, see [affected](/docs/features/ci-features/affected).
## Distributing tasks across machines
For larger workspaces, you can distribute task execution across multiple machines using Nx Agents. Instead of running all tasks on a single CI runner, Nx Cloud coordinates the work across a fleet of agents:
```yaml
# Add to your CI workflow
- run: npx nx start-ci-run --distribute-on="3 linux-medium-js"
```
Nx Agents automatically split tasks across the available agents, respecting task dependencies and maximizing parallelism. No configuration changes to your tasks are needed.
For more details, see [distribute task execution (Nx Agents)](/docs/features/ci-features/distribute-task-execution).
## Self-healing CI
The `npx nx fix-ci` command at the end of the workflow enables self-healing CI. When a task fails, Nx Cloud analyzes the failure and suggests a fix that you can apply directly from your editor (via [Nx Console](/docs/getting-started/editor-setup)).
This is useful for catching flaky tests, configuration drift, and other issues that can be auto-remediated without manual debugging.
For more details, see [self-healing CI](/docs/features/ci-features/self-healing-ci).
## Next steps
- [Remote cache (Nx Replay)](/docs/features/ci-features/remote-cache): how remote caching works
- [Affected](/docs/features/ci-features/affected): how Nx determines what changed
- [Distribute task execution (Nx Agents)](/docs/features/ci-features/distribute-task-execution): run tasks across multiple machines
- [Self-healing CI](/docs/features/ci-features/self-healing-ci): automatic failure detection and fixes
- [AI integration](/docs/getting-started/ai-setup): enhance CI with AI-powered workflows
{% cards cols=2 %}
{% card title="Previous: Reducing Configuration Boilerplate" description="Automate task configuration with plugins" url="/docs/getting-started/tutorials/reducing-configuration-boilerplate" /%}
{% /cards %}
@@ -624,7 +624,7 @@ After this first release, you can remove the `--first-release` flag and just run
Here are some things you can dive into next:
- [Set up CI](/docs/getting-started/tutorials/self-healing-ci-tutorial) with remote caching and self-healing
- [Set up CI](/docs/getting-started/setup-ci) with remote caching and self-healing
- Learn more about the [underlying mental model of Nx](/docs/concepts/mental-model)
- Learn how to [migrate your existing project to Nx](/docs/guides/adopting-nx/adding-to-existing-project)
- [Learn more about Nx release for publishing packages](/docs/features/manage-releases)
@@ -5,7 +5,7 @@ sidebar:
order: 6
---
{% llm_copy_prompt title="Tutorial 6/8: Explore and debug your workspace" %}
{% llm_copy_prompt title="Tutorial 6/7: Explore and debug your workspace" %}
Help me explore and debug my Nx workspace.
Use my existing workspace and projects for hands-on examples.
@@ -29,7 +29,6 @@ As your workspace grows to dozens or hundreds of projects, you need tools to exp
5. [Caching](/docs/getting-started/tutorials/caching)
6. **Understanding your workspace** (you are here)
7. [Reducing boilerplate](/docs/getting-started/tutorials/reducing-configuration-boilerplate)
8. [Setting up CI](/docs/getting-started/tutorials/self-healing-ci-tutorial)
{% /aside %}
@@ -109,10 +109,12 @@ You can also manually install the [`nx` NPM package](https://www.npmjs.com/packa
### Update Nx in your repository
When you update Nx in your repository, it will also [automatically update your dependencies](/docs/features/automate-updating-dependencies) if you have an [Nx plugin](/docs/concepts/nx-plugins) installed for that dependency. To update Nx, run:
When you update Nx in your repository, it will also [automatically update your dependencies](/docs/features/automate-updating-dependencies) if you have an [Nx plugin](/docs/concepts/nx-plugins) installed for that dependency.
To update Nx, run `nx migrate`. It guides you through the update interactively:
```shell
nx migrate latest
nx migrate
```
This creates a `migrations.json` file with any update scripts that need to be run. Run them with:
@@ -121,6 +123,8 @@ This creates a `migrations.json` file with any update scripts that need to be ru
nx migrate --run-migrations
```
For the full walkthrough, including the AI-assisted agentic flow, see [Automate Updating Dependencies](/docs/features/automate-updating-dependencies).
{% aside type="note" title="Update One Major Version at a Time" %}
To avoid potential issues, it is [recommended to update one major version of Nx at a time](/docs/guides/tips-n-tricks/advanced-update#one-major-version-at-a-time-small-steps).
{% /aside %}
@@ -24,7 +24,7 @@ Monorepos have many advantages and are especially powerful for AI-assisted devel
**Nx reduces friction across your entire development cycle** with intelligent caching, task orchestration, and deep understanding of your codebase structure.
At its core, Nx:
Nx:
1. **Runs tasks fast** - [Caches results](/docs/features/cache-task-results) so you never rebuild the same code twice.
2. **Understands your codebase** - Builds [project and task graphs](/docs/features/explore-graph) showing how everything connects.
@@ -40,7 +40,7 @@ nx run-many -t build test # Run across all projects
{% callout type="deepdive" title="How does Nx run tasks?" %}
At its core, Nx is a fast, intelligent task runner. Take the example of an NPM workspace. This could be a project's `package.json`:
Nx is a fast, intelligent task runner. Take the example of an NPM workspace. This could be a project's `package.json`:
```json
// package.json
@@ -0,0 +1,136 @@
---
title: 'Setting Up CI'
description: Configure CI for your Nx workspace with remote caching, affected, distributed task execution, and self-healing.
sidebar:
order: 7
label: 'Setting Up CI'
filter: 'type:Guides'
---
{% llm_copy_prompt title="Let an AI agent set it up for you" %}
Help me set up CI for my Nx workspace with remote caching.
Before touching anything, verify the workspace state:
**A. Is Nx installed?**
- Check for `nx.json` and `nx` in `package.json` devDeps.
- Confirm `node_modules` exists. If not, install deps using the package manager that matches my lockfile (`pnpm install`, `npm install`, or `yarn`).
- If `nx.json` is missing entirely, ask me before running `npx nx@latest init`.
**B. Is there an existing CI workflow?**
- **Yes, and it already calls `nx run` or `nx run-many`**: likely already set up. Confirm with me before changing anything.
- **Yes, but it calls raw tooling directly** (`jest`, `tsc`, `eslint`, etc.): work with me to update it. Propose minimal edits swapping the raw calls for `nx run-many -t <task>` or `nx run <project>:<task>`, and add a final `npx nx fix-ci` step. Show me the diff and wait for approval before writing.
- **No**: run `nx g @nx/workspace:ci-workflow --ci=<provider>`. Detect the provider from `git remote -v` (github.com -> `github`, gitlab.com -> `gitlab`, etc.). Ask me if it's ambiguous.
Then connect to Nx Cloud:
1. Run `npx nx-cloud onboard connect-workspace` and parse the JSON.
2. If the response includes an `actionRequired` payload (typically GitHub authorization), surface the message and any URLs to me and stop. Do not retry blindly.
3. Confirm `nxCloudId` is written to `nx.json`. If it is not, surface the JSON error to me instead of retrying.
Stage the generated or edited files but do not commit on my behalf. Stay on topic: getting remote cache running in CI. For deeper coverage link to {pageUrl} and to [/docs/features/ci-features/remote-cache](/docs/features/ci-features/remote-cache).
Page: {pageUrl}
{% /llm_copy_prompt %}
Connect your workspace to Nx Cloud and run your CI tasks through `nx`. That turns on remote caching, affected, distribution, and self-healing CI.
## Make sure you have Nx
If you don't have Nx in your repo yet, add it first.
For existing repos, run the init command and follow the prompts:
```shell
npx nx@latest init
```
Or, start fresh with a new repo:
```shell
npx create-nx-workspace@latest
```
## Make sure CI invokes Nx CLI
Remote caching, affected, distribution, and self-healing only kick in when `nx` runs your tasks. `nx test` is fine, and so is `npm test` if it wraps `nx test`. Direct calls to `jest`, `tsc`, or `eslint` bypass Nx Cloud.
If you have a workflow file, swap raw tool invocations for `nx run-many` or `nx affected`:
```yaml
# .github/workflows/ci.yml
- run: npx nx run-many -t lint test build
```
Use `nx run-many -t <task>` for multiple projects or `nx run <project>:<task>` for a single project.
{% aside type="note" title="No CI workflow yet?" %}
Generate one:
```shell
nx add @nx/workspace
nx g @nx/workspace:ci-workflow --ci=github
```
Supported `--ci` values: `github`, `circleci`, `gitlab`, `azure`, `bitbucket-pipelines`. The generator wires up the CI task runner, remote caching, and `nx fix-ci`.
{% /aside %}
## Remote caching
Remote cache allows your CI runs to benefit from previous runs. It takes less than 5 minutes to set up and is free for small teams.
{% call_to_action variant="default" title="Connect your workspace" url="https://cloud.nx.app/setup/connect-workspace/guide?utm_source=nx-dev&utm_medium=ci-tutorial&utm_campaign=try-nx-cloud" description="Setup takes less than 5 minutes" /%}
See [Remote Caching](/docs/features/ci-features/remote-cache) for details on the security model and eviction. For more granular control in CI, with separate read-only and read-write tokens and branch-scoped permissions, see [CI access tokens](/docs/guides/nx-cloud/access-tokens).
## Running only affected tasks
Use `nx affected` to run tasks only for projects impacted by the PR's changes:
```yaml {% meta="{5}" %}
# .github/workflows/ci.yml
- uses: actions/checkout@v4
with:
fetch-depth: 0
- run: npx nx affected -t lint test build
```
Nx uses `NX_BASE` and `NX_HEAD` to determine the comparison range. `fetch-depth: 0` gives Nx access to the full git history. On a PR, Nx compares the branch against `main` (or whatever `defaultBase` is set to in `nx.json`). On a push to `main`, it compares against the previous commit.
See [Affected](/docs/features/ci-features/affected) for more information.
## Distributing tasks across machines
With [Nx Agents](/docs/features/ci-features/distribute-task-execution), you can distribute tasks across multiple machines with a single line of code in your CI workflow. No complicated configuration required.
```yaml {% meta="{2}" %}
# .github/workflows/ci.yml
- run: npx nx start-ci-run --distribute-on="3 linux-medium-js"
- run: npx nx affected -t lint test build
```
It works seamlessly with [remote caching](#remote-caching) and enables [task splitting](/docs/features/ci-features/split-e2e-tasks) for Playwright, Vitest, etc. across machines.
## Self-healing CI
Add `npx nx fix-ci` as the final step in your workflow. When a task fails, Nx Cloud analyzes the failure and proposes a fix you can apply from GitHub or the Nx Cloud UI.
```yaml {% meta="{3-4}" %}
# .github/workflows/ci.yml
- run: npx nx affected -t lint test build
- run: npx nx fix-ci
if: always()
```
The `if: always()` ensures `fix-ci` runs even when prior steps fail. It catches flaky tests, configuration drift, and other issues Nx Cloud can fix without manual debugging.
See [Self-healing CI](/docs/features/ci-features/self-healing-ci) for the trigger model.
## Resources
- [Remote cache (Nx Replay)](/docs/features/ci-features/remote-cache): how remote caching works
- [Affected](/docs/features/ci-features/affected): how Nx determines what changed
- [Distribute task execution (Nx Agents)](/docs/features/ci-features/distribute-task-execution): run tasks across multiple machines
- [Self-healing CI](/docs/features/ci-features/self-healing-ci): automatic failure detection and fixes
@@ -16,21 +16,20 @@ All benchmarks on this page use the same [pnpm workspace](https://github.com/mee
This page starts with the basics, like onboarding, and progressively moves into more advanced capabilities.
| Topic | Nx | Turborepo |
| --------------------------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------- |
| [Onboarding](#onboarding) | Zero-config or guided `nx init` (+3 lines) | Manual `turbo.json` (+144 lines) |
| [Running tasks](#running-tasks) | Runs `package.json` scripts, optional plugin-based task inference | Runs `package.json` scripts, requires `turbo.json` config |
| [Caching](#caching) | Explicit opt-in, composable `namedInputs` | Cached by default, flat input lists |
| [Task sandboxing](#task-sandboxing) | IO tracing + cache poisoning protection | Not available |
| [Code generation](#code-generation) | Programmatic generators with AST transforms and graph awareness | Template-based file scaffolding (Plop) |
| [Module boundary rules](#module-boundary-rules) | Tag-based lint rule + conformance rules (polyglot) | Experimental `turbo boundaries` (since 2024) |
| [Polyglot support](#polyglot-support) | Native support for JS/TS, Java, .NET, Python, Rust | Any CLI via `package.json` scripts, no native graph |
| [AI integration](#ai-integration) | Agent skills, MCP, `configure-ai-agents`, self-healing CI | Official skill, no MCP or CI integration |
| [CI solution](#running-nx-vs-turbo-on-ci) | Nx Cloud: distribution (9m 20s), self-healing, flaky detection | No CI solution (19m 18s with manual binning) |
| [Cross-repo coordination](#cross-repo-coordination) | Polygraph (synthetic monorepo) | Not available |
| [Release management](#release-management) | Built-in versioning, changelogs, and publishing | Requires manual setup or 3rd party tools |
| [Observability](#observability) | Integrated dashboards and AI-powered run analysis | Experimental OpenTelemetry (OTLP) export |
| [Developer experience](#developer-experience) | TUI, IDE extensions, and interactive project graph | Basic TUI and LSP support |
| Topic | Nx | Turborepo |
| ----------------------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------- |
| [Onboarding](#onboarding) | Zero-config or guided `nx init` (+3 lines) | Manual `turbo.json` (+144 lines) |
| [Running tasks](#running-tasks) | Runs `package.json` scripts, optional plugin-based task inference | Runs `package.json` scripts, requires `turbo.json` config |
| [Caching](#caching) | Explicit opt-in, composable `namedInputs` | Cached by default, flat input lists |
| [Task sandboxing](#task-sandboxing) | IO tracing + cache poisoning protection | Not available |
| [Code generation](#code-generation) | Programmatic generators with AST transforms and graph awareness | Template-based file scaffolding (Plop) |
| [Module boundary rules](#module-boundary-rules) | Tag-based lint rule + conformance rules (polyglot) | Experimental `turbo boundaries` (since 2024) |
| [Polyglot support](#polyglot-support) | Native support for JS/TS, Java, .NET, Python, Rust | Any CLI via `package.json` scripts, no native graph |
| [AI integration](#ai-integration) | Agent skills, MCP, `configure-ai-agents`, self-healing CI | Official skill, no MCP or CI integration |
| [CI solution](#running-nx-vs-turbo-on-ci) | Nx Cloud: distribution (9m 20s), self-healing, flaky detection | No CI solution (19m 18s with manual binning) |
| [Release management](#release-management) | Built-in versioning, changelogs, and publishing | Requires manual setup or 3rd party tools |
| [Observability](#observability) | Integrated dashboards and AI-powered run analysis | Experimental OpenTelemetry (OTLP) export |
| [Developer experience](#developer-experience) | TUI, IDE extensions, and interactive project graph | Basic TUI and LSP support |
## Onboarding
@@ -346,18 +345,10 @@ Both tools offer visibility into your pipelines, but through different models.
_Nx Agent utilization chart, showing even distribution across CI runners._
For deep dives into resource utilization, see [CI Resource Usage](/docs/guides/nx-cloud/ci-resource-usage).
For deep dives into resource utilization, see [Resource Usage](/docs/features/ci-features/resource-usage).
**Turborepo** exposes run metrics via experimental **OpenTelemetry (OTLP)**. This is useful if you already have a mature observability stack (like Datadog or Grafana) and want to route build metrics into it, though it requires significant manual setup and maintenance of your own collector and visualization layer.
## Cross-repo coordination
Most organizations don't have a single monorepo. They have several monorepos plus standalone repos across teams.
Nx Cloud's [Polygraph](/docs/enterprise/polygraph) creates a [synthetic monorepo](/docs/concepts/synthetic-monorepos): a unified dependency graph across separate repositories without moving any code. AI agents can read the cross-repo graph, coordinate changes across repos, and manage PRs across repo boundaries.
Turborepo has no cross-repo coordination.
## Release management
Versioning and publishing libraries in a monorepo is a complex orchestration task. You need to identify what changed, determine the next version for each package, update internal dependencies, generate changelogs, and publish to registries.
@@ -6,11 +6,14 @@ filter: 'type:References'
The Nx Cloud GitHub App requires the following permissions to provide CI/CD integration and setup experiences. Most information is used transiently during operations and not stored in our systems.
{% callout type="note" title="Administration (read & write) permission removed." %}
The Nx Cloud GitHub App no longer requests the `Administration` (read & write) permission. It was previously required to generate new workspaces from the browser; that feature has since been removed, so we've dropped the permission to reduce the access we request.
{% /callout %}
## Required permissions
Repository permissions:
- `Administration: Read & Write`
- `Checks: Read & Write`
- `Contents: Read & Write`
- `Commit Statuses: Read`
@@ -27,12 +30,6 @@ Organization permissions:
## Permission details
### Administration (write)
**Used for:** Creating new repositories with a pre-configured Nx workspace during initial onboarding.
**When it's used:** Only when you explicitly choose to create a new workspace through Nx Cloud's setup flow. [Single tenant instances](/docs/enterprise/single-tenant/overview) can safely forego this scope and will only lose the ability to create new workspaces through the app.
### Checks (write)
**Used for:** Updating CI run statuses so you can see the progress and results of your Nx Cloud pipeline executions directly in GitHub. Also used for Self-Healing CI status check runs in PRs.
@@ -81,9 +78,9 @@ Organization permissions:
### Actions (read)
**Used for:** Retrieving GitHub Action logs so that they can be surfaced on Nx Cloud to help resolve failures before Nx Cloud had a chance to run tasks, and for Polygraph support.
**Used for:** Retrieving GitHub Action logs so that they can be surfaced on Nx Cloud to help resolve failures before Nx Cloud had a chance to run tasks.
**When it's used:** Only when using Polygraph, and Nx Cloud MCP tools to get CI information.
**When it's used:** Only when using Nx Cloud MCP tools to get CI information.
## Your data and security
@@ -11,7 +11,11 @@ filter: 'type:Guides'
The permissions and membership define what developers can access on [nx.app](https://cloud.nx.app?utm_source=nx.dev&utm_medium=docs&utm_campaign=nx-cloud-security), but they don't affect what happens when you run Nx commands in CI. To manage that, you need to provision CI access tokens in your workspace settings, under the `Access Control` tab.
Learn more about [cache security best practices](/docs/concepts/ci-concepts/cache-security).
![Access Control Settings Page](../../../../assets/nx-cloud/access-control-settings.avif)
{% aside type="tip" title="Quickest path: use recommended settings" %}
The **Access Control** tab in your Nx Cloud workspace has a **Use recommended settings** button that generates the right CI access tokens, requires developer logins for cache reads, etc.
![](../../../../assets/nx-cloud/access-control-settings.avif)
{% /aside %}
## Access types
@@ -1,10 +1,10 @@
---
title: 'Manual Distributed Task Execution'
description: 'Learn how to set up manual distributed task execution on various CI providers'
title: 'Bring Your Own Compute'
description: 'Learn how to run Nx Agents on your own CI compute across various CI providers'
filter: 'type:Guides'
---
Using [Nx Agents](/docs/features/ci-features/distribute-task-execution) is the easiest way to distribute task execution, but your organization may not be able to use hosted Nx Agents. You can set up distributed task execution on your own CI provider using the recipes below.
[Nx Agents](/docs/features/ci-features/distribute-task-execution) distributes your tasks across multiple machines on every Nx Cloud plan. On the [Nx Enterprise plan](https://nx.dev/enterprise?utm_source=nx.dev&utm_medium=callout&utm_campaign=bring-your-own-compute) you can also bring your own compute, running the agents on your own CI provider instead of Nx Cloud-hosted machines. The recipes below show how to set that up.
{% tabs syncKey="ci-provider" %}
@@ -106,7 +106,7 @@ jobs:
env:
NX_AGENT_NAME: ${{ matrix.agent }}
# Upload agent resource metrics (Requires Nx 22.1 or higher.) — https://nx.dev/docs/guides/nx-cloud/ci-resource-usage
# Upload agent resource metrics (Requires Nx 22.1 or higher.) — https://nx.dev/docs/features/ci-features/resource-usage
- name: Upload agent metrics
if: always()
run: npx nx-cloud upload-agent-metrics
@@ -167,7 +167,7 @@ jobs:
no_output_timeout: 60m
environment:
NX_AGENT_NAME: << parameters.ordinal >>
# Upload agent resource metrics (Requires Nx 22.1 or higher.) — https://nx.dev/docs/guides/nx-cloud/ci-resource-usage
# Upload agent resource metrics (Requires Nx 22.1 or higher.) — https://nx.dev/docs/features/ci-features/resource-usage
- run:
command: npx nx-cloud upload-agent-metrics
environment:
@@ -237,7 +237,7 @@ jobs:
env:
NX_AGENT_NAME: $(System.JobPositionInPhase)
# Upload agent resource metrics (Requires Nx 22.1 or higher.) — https://nx.dev/docs/guides/nx-cloud/ci-resource-usage
# Upload agent resource metrics (Requires Nx 22.1 or higher.) — https://nx.dev/docs/features/ci-features/resource-usage
- script: npx nx-cloud upload-agent-metrics
condition: always()
env:
@@ -311,7 +311,7 @@ definitions:
- npx nx start-agent
after-script:
- export NX_AGENT_NAME=$BITBUCKET_STEP_UUID
# Upload agent resource metrics (Requires Nx 22.1 or higher.) — https://nx.dev/docs/guides/nx-cloud/ci-resource-usage
# Upload agent resource metrics (Requires Nx 22.1 or higher.) — https://nx.dev/docs/features/ci-features/resource-usage
- npx nx-cloud upload-agent-metrics
# Self-Healing CI: recommend fixes for failures. Learn more: https://nx.dev/ci/features/self-healing-ci
- npx nx fix-ci
@@ -368,7 +368,7 @@ image: node:18
- yarn nx start-agent
after_script:
- export NX_AGENT_NAME=$CI_JOB_ID
# Upload agent resource metrics (Requires Nx 22.1 or higher.) — https://nx.dev/docs/guides/nx-cloud/ci-resource-usage
# Upload agent resource metrics (Requires Nx 22.1 or higher.) — https://nx.dev/docs/features/ci-features/resource-usage
- yarn nx-cloud upload-agent-metrics
# Self-Healing CI: recommend fixes for failures. Learn more: https://nx.dev/ci/features/self-healing-ci
- yarn nx fix-ci
@@ -490,7 +490,7 @@ pipeline {
}
post {
always {
// Upload agent resource metrics (Requires Nx 22.1 or higher.) — https://nx.dev/docs/guides/nx-cloud/ci-resource-usage
// Upload agent resource metrics (Requires Nx 22.1 or higher.) — https://nx.dev/docs/features/ci-features/resource-usage
sh "npx nx-cloud upload-agent-metrics"
// Self-Healing CI: recommend fixes for failures. Learn more: https://nx.dev/ci/features/self-healing-ci
sh "npx nx fix-ci"
@@ -508,7 +508,7 @@ pipeline {
}
post {
always {
// Upload agent resource metrics (Requires Nx 22.1 or higher.) — https://nx.dev/docs/guides/nx-cloud/ci-resource-usage
// Upload agent resource metrics (Requires Nx 22.1 or higher.) — https://nx.dev/docs/features/ci-features/resource-usage
sh "npx nx-cloud upload-agent-metrics"
// Self-Healing CI: recommend fixes for failures. Learn more: https://nx.dev/ci/features/self-healing-ci
sh "npx nx fix-ci"
@@ -526,7 +526,7 @@ pipeline {
}
post {
always {
// Upload agent resource metrics (Requires Nx 22.1 or higher.) — https://nx.dev/docs/guides/nx-cloud/ci-resource-usage
// Upload agent resource metrics (Requires Nx 22.1 or higher.) — https://nx.dev/docs/features/ci-features/resource-usage
sh "npx nx-cloud upload-agent-metrics"
// Self-Healing CI: recommend fixes for failures. Learn more: https://nx.dev/ci/features/self-healing-ci
sh "npx nx fix-ci"
@@ -1,97 +0,0 @@
---
title: Track CI Resource Usage
description: Track CPU and memory usage for each task in your CI pipeline to find resource bottlenecks, debug out-of-memory errors, and optimize your CI agent configuration.
sidebar:
label: View Resource Usage
badge: new!
filter: 'type:Guides'
---
Nx Cloud tracks CPU and memory usage for each task in your CI pipeline. Use this data to find resource bottlenecks, debug out-of-memory errors, and pick the right agent size for your workload.
{% aside type="note" title="Requirements" %}
Requires Nx 22.1 or higher.
The CI resource usage feature with Nx Cloud requires an [Enterprise plan](https://nx.dev/enterprise?utm_source=nx.dev&utm_medium=documentation-guide&utm_campaign=nx-cloud-task-metrics).
{% /aside %}
## Resource usage with Nx Agents
With [Nx Agents](/docs/features/ci-features/distribute-task-execution), resource metrics are collected automatically. You can view this data in the Nx Cloud dashboard for any CI pipeline execution.
### Viewing the analysis summary
Open any CI pipeline execution in Nx Cloud and go to the analysis section. You'll see a list of agents used for the run, along with:
- Average and maximum CPU usage
- Average and maximum memory usage
- Machine specs for that resource class
This gives you a quick look at how resources were used across all agents.
![Resource usage summary showing agents with CPU and memory stats](../../../../assets/guides/nx-cloud/agent-resource-usage-table.png)
### Viewing usage details
Click on any agent to see a breakdown of resource usage over time. The detail view shows:
- Memory usage by process
- CPU usage by process
- Resource consumption for each task
- Nx CLI overhead
This view helps you find exactly which task is using the most resources, not just that "something" in your pipeline is the problem.
![Resource usage details showing memory and CPU by process](../../../../assets/guides/nx-cloud/resource-chart-details.png)
### Using the detail view
The detail view has a few features to help you dig into resource usage:
- **Legend**: Click items in the legend to focus on specific tasks or processes
![Using the legend to focus on specific tasks](../../../../assets/guides/nx-cloud/resource-chart-legend.png)
- **Timeline scrubber**: Use the scrubber at the bottom to jump to specific points in time or zoom in on peak usage
![Timeline scrubber for navigating resource usage over time](../../../../assets/guides/nx-cloud/resource-chart-scrubber.jpg)
- **View modes**: Switch between "stacked" view (total usage at any time) and "individual" view (each process separately)
![Stacked view showing total resource usage](../../../../assets/guides/nx-cloud/resource-stacked-chart-view.png)
- **CSV export**: Download the raw data if you need to dig into sub-process details
## Common use cases
- **Finding memory-hungry tasks**: Figure out which project eats the most memory when running tasks in parallel. You can then run just that project with lower parallelism instead of slowing down everything.
- **Spotting misconfigured tooling**: See when a bundler or build tool is pulling in more files than it should.
- **Debugging E2E bottlenecks**: Find out if the slow part is the tests themselves or something in the dependency chain.
- **Comparing before and after upgrades**: Check if a dependency upgrade caused a spike in resource usage.
- **Detecting memory leaks**: Look for tasks where memory keeps climbing over time.
- **Picking the right resource class**: Figure out the right agent size when moving to Nx Agents from GitHub Actions or other CI providers.
## Resource metrics with manual DTE
If you're running your own CI agents instead of Nx Agents, Nx Cloud can still collect per-agent CPU and memory metrics so you can debug slow tasks and OOM kills in the run timeline. Opt in by adding a single CLI step to each agent job.
{% aside type="note" title="Nx Enterprise Required" %}
This feature is available on Nx Cloud Enterprise plans only. [Reach out to learn more about Nx Enterprise](https://nx.dev/enterprise).
{% /aside %}
### What to add
At the end of each agent job, run `npx nx-cloud upload-agent-metrics`. Use your provider's always-run mechanism so the step runs even when the agent is killed mid-task — that's precisely the scenario where metrics are most useful.
Here's the GitHub Actions step:
```yaml
- name: Upload agent metrics
if: always()
run: npx nx-cloud upload-agent-metrics
env:
NX_AGENT_NAME: ${{ matrix.agent }}
```
The `if: always()` condition is important: if an agent is OOM-killed mid-run, the normal step sequence stops — but the upload still needs to happen so you can see which task caused the kill.
### Other CI providers
The [Manual DTE guide](/docs/guides/nx-cloud/manual-dte) shows the equivalent step for Circle CI, Azure Pipelines, Bitbucket Pipelines, GitLab CI, and Jenkins.
@@ -56,9 +56,11 @@ Then, enable AI features in the [organization settings](https://cloud.nx.app/go/
If using DTE agents, the self-healing CI step will be automatically added when the setting is enabled in the workspace settings.
#### Manual DTE
#### Bring your own compute
For manual DTE configurations, the `nx fix-ci` command must be included in the agent configuration after running nx tasks.
Bringing your own compute requires the [Nx Enterprise plan](https://nx.dev/enterprise?utm_source=nx.dev&utm_medium=callout&utm_campaign=bring-your-own-compute).
When you bring your own compute, the `nx fix-ci` command must be included in the agent configuration after running nx tasks.
**Important:** This command must run **always**, meaning that even when previous nx tasks fail, the `fix-ci` command should still execute.
@@ -16,7 +16,7 @@ When an update to Nx is available, a badge will appear on the Nx Console icon in
![](../../../../assets/guides/nx-console/console-migrate-1-start.avif)
By default, clicking the migration button starts the migration process by upgrading to the recommended Nx version — the latest version of the next major release. This method ensures you upgrade one major version at a time in order to [avoid breakages](/docs/guides/tips-n-tricks/advanced-update#one-major-version-at-a-time-small-steps). To customize the version, click the pencil icon to provide a specific version to update to. You may also provide additional CLI options such as `--to` or `--from`.
By default, clicking the migration button starts the migration process by upgrading to the recommended Nx version — the latest version of the next major release. This method ensures you upgrade one major version at a time in order to [avoid breakages](/docs/guides/tips-n-tricks/advanced-update#one-major-version-at-a-time-small-steps). To customize the version, click the pencil icon to provide a specific version to update to. You may also provide additional CLI options such as `--include`.
![](../../../../assets/guides/nx-console/console-migrate-2-customize-version.avif)
@@ -42,6 +42,19 @@ You can click through to view the migration source code, giving you the opportun
Alternatively, you may choose to skip a problematic migration.
### Migrations that use an AI prompt
Some migrations ship an AI prompt instead of (or in addition to) a deterministic script - see [what's in a migration](/docs/features/automate-updating-dependencies#whats-in-a-migration). The Migrate UI marks these with an **AI** badge and handles them without running an agent itself:
- **Prompt-only** migrations can't run automatically. The card shows **"AI prompt pending"** with a hint pointing at the prompt file, and **View Source** opens that prompt. Apply the prompt yourself - for example, with your AI agent - then click **Mark as Run** to record it and move on.
- **Hybrid** migrations run their generator automatically, then the card shows **Generator complete** alongside **AI prompt pending**. Click **Approve Changes** to accept the generator's edits and advance, or **Mark as Run** if the generator made no changes.
Your acknowledgment is remembered across editor reloads, so reopening a session brings you back to any prompt migrations you still need to finish.
{% aside type="note" title="Requires a recent Nx and Nx Console" %}
The AI badge and prompt handling require Nx 23.0.0-beta.24 or later and an up-to-date Nx Console extension. On older versions the Migrate UI still works without these controls.
{% /aside %}
## Finalizing the migration
When all migrations are done, or you don't want to run further migrations, you can finish the process by clicking the Finish button. By default, this will squash all commits created during the migration together, but you can opt into preserving them.
@@ -155,22 +155,22 @@ To watch for specific projects and echo the changed files, run this command:
nx watch --projects=app1,app2 -- echo \$NX_FILE_CHANGES
```
### Watching for dependent projects
### Watching a project and its dependencies
To watch for a project and it's dependencies, run this command:
To watch a project and the projects it depends on, run this command:
```shell
nx watch --projects=app1 --includeDependentProjects -- echo \$NX_PROJECT_NAME
nx watch --projects=app1 --includeDependencies -- echo \$NX_PROJECT_NAME
```
### Rebuilding dependent projects while developing an application
### Rebuilding an application's dependencies while developing it
In a monorepo setup, your application might rely on several libraries that need to be built before they can be used in the application. While the [task pipeline](/docs/guides/tasks--caching/defining-task-pipeline) automatically handles this during builds, you'd want the same behavior during development when serving your application with a dev server.
To watch and rebuild the dependent libraries of an application, use the following command:
To watch and rebuild the libraries an application depends on, use the following command:
```shell
nx watch --projects=my-app --includeDependentProjects -- nx run-many -t build -p \$NX_PROJECT_NAME --exclude=my-app
nx watch --projects=my-app --includeDependencies -- nx run-many -t build -p \$NX_PROJECT_NAME --exclude=my-app
```
`--includeDependentProjects` ensures that any changes to projects your application depends on trigger a rebuild, while `--exclude=my-app` skips rebuilding the app itself since it's already being served by the development server.
`--includeDependencies` ensures that any changes to projects your application depends on trigger a rebuild, while `--exclude=my-app` skips rebuilding the app itself since it's already being served by the development server.
@@ -15,7 +15,7 @@ The following steps are a summary of the [standard update process](/docs/feature
First, run the `migrate` command:
```shell
nx migrate latest # same as nx migrate nx@latest
nx migrate
```
This performs the following changes:
@@ -43,17 +43,64 @@ After you run all the migrations, you can remove `migrations.json` and commit an
Migrating Jest, Cypress, ESLint, React, Angular, Next, and more is a difficult task. All the tools change at different rates, and they can conflict with each other. In addition, every workspace is different. Even though our goal is for you to update any version of Nx to a newer version of Nx in a single go, sometimes it doesn't work. The recommended process is to update, at most, one major version at a time.
Say you want to migrate from Nx 17.1.0 to Nx 18.2.4. The following steps are more likely to work comparing to `nx migrate 18.2.4`.
Say you want to migrate from Nx 22.1.0 to Nx 23.0.0. The following steps are more likely to work comparing to `nx migrate 23.0.0`.
- Run `nx migrate 17.3.2` to update the latest version in the 17.x branch.
- Run `nx migrate 22.7.5` to update the latest version in the 22.x branch.
- Run `nx migrate --run-migrations`.
- Next, run `nx migrate 18.2.4`.
- Next, run `nx migrate 23.0.0`.
- Run `nx migrate --run-migrations`.
{% aside type="caution" title="Angular updates" %}
If your workspace uses Angular, this becomes a requirement rather than a recommendation. The Angular packages maintain migrations for a single major version at a time. If you try to update over multiple major versions, only the migrations for the latest major version will be applied. This can lead to issues in your workspace.
{% /aside %}
## Crossing multiple major versions
When the target is more than one major version ahead of your installed version, `nx migrate` prompts you to choose how far to jump: migrate to the latest in your current major (recommended), step into the next major, or go directly to the target. To skip the prompt, use `--multi-major-mode`:
- `--multi-major-mode=gradual` migrates to the smallest recommended step (typically the latest in your current major), then tells you to re-run to continue.
- `--multi-major-mode=direct` migrates straight to the target.
The `NX_MULTI_MAJOR_MODE` environment variable is equivalent and takes precedence over a `multiMajorMode` value set in `nx.json`. In non-interactive environments there is no prompt - Nx warns that updating one major at a time is recommended and proceeds directly to the target.
## Choosing which packages to migrate
While in most cases you want to be up to date with Nx and the dependencies it manages, sometimes you might need to stay on an older version of such a dependency. For example, you might want to update Nx to the latest version but keep Angular on **v21.x.x** and not update it to **v22.x.x**.
The `--include` flag controls which packages are updated: `required` (the target package and the packages it ships with), `optional` (the dependency updates those packages recommend), or `all` (the default). The interactive `nx migrate` flow prompts for this. Pass `--include` to set it ahead of time.
`--include` applies only when the target package opts into package selection. Nx and its official plugins do starting in Nx 23, and other plugin authors can opt in as well. For targets that don't, Nx uses `all` without prompting, and passing `--include` explicitly errors. Non-interactive runs also default to `all`.
When unsure, prefer `--include=required` and follow up with `--include=optional`. Updating only Nx and its plugins keeps the PR scope small and has less chance of introducing issues, which matters most in large workspaces. Use `--include=all` when you're okay with doing everything in one PR.
{% aside type="note" title="Optional package updates" %}
You can't choose to skip any arbitrary package update. To ensure that a plugin works well with older versions of a given package, the plugin must support it. Therefore, Nx plugin authors define what package updates are optional.
{% /aside %}
{% aside type="caution" title="Taking control of package updates" %}
While opting out of applying some package updates is supported by Nx, please keep in mind that you are effectively taking control of those package updates and opting out of Nx managing them. This means you'll need to keep up with the version requirements for those packages and those that depend on them. You'll also need to consider more things when updating them at some point [as explained later](#updating-dependencies-that-are-behind-the-versions-nx-manages).
{% /aside %}
### Skipping optional package updates
To skip the optional package updates, generate the migration with `--include=required`:
```shell
nx migrate --include=required
```
Only the target package and the packages it ships with are updated. The `package.json` and the `migrations.json` are generated without the optional dependency updates, and you can catch up on those later.
### Updating dependencies that are behind the versions Nx manages
Once you have skipped some optional updates, there'll come a time when you'll want to update those packages. Run `nx migrate --include=optional` to collect the optional dependency updates recommended for your installed version. It anchors to your installed version, so the target package must be installed and you can't migrate to a version higher than what's installed.
The catch-up can be scoped to a single plugin by naming it as the target. For example, `nx migrate @nx/vite --include=optional` collects only the optional updates `@nx/vite` recommends, such as `vite` itself.
{% aside type="caution" title="Scoping to a single plugin" %}
Some plugin updates depend on other plugins running their updates too. For example, `@nx/angular` sometimes requires the `@nx/js` updates for TypeScript. Scoping the catch-up to a single plugin skips those, so prefer the full `nx migrate --include=optional` when unsure.
{% /aside %}
## Managing migration steps
When you run into problems running the `nx migrate --run-migrations` command, here are some solutions to break the process down into manageable steps.
@@ -129,107 +176,20 @@ You can even provide a custom location for the migrations file if you wish, you
nx migrate --run-migrations=migrations.json
```
## Choosing optional package updates to apply
## Using an AI agent to apply migrations
While in most cases you want to be up to date with Nx and the dependencies it manages, sometimes you might need to stay on an older version of such a dependency. For example, you might want to update Nx to the latest version but keep Angular on **v15.x.x** and not update it to **v16.x.x**. For such scenarios, `nx migrate` allows you to choose what to update using the `--interactive` flag.
Some migrations ship an AI prompt that an installed agent applies for you. The [agentic flow](/docs/features/automate-updating-dependencies#step-2-run-migrations) is interactive by default, but you can control it with flags:
{% aside type="note" title="Optional package updates" %}
You can't choose to skip any arbitrary package update. To ensure that a plugin works well with older versions of a given package, the plugin must support it. Therefore, Nx plugin authors define what package updates are optional.
{% /aside %}
- `--agentic` enables it and resolves the installed agent. Use `--agentic=claude-code` (or `codex`, `opencode`) to pin one, or `--no-agentic` to disable it.
- `--validate` / `--no-validate` toggles agent validation of generator-only migrations - on by default when the agentic flow is enabled.
{% aside type="caution" title="Taking control of package updates" %}
While opting out of applying some package updates is supported by Nx, please keep in mind that you are effectively taking control of those package updates and opting out of Nx managing them. This means you'll need to keep up with the version requirements for those packages and those that depend on them. You'll also need to consider more things when updating them at some point [as explained later](#updating-dependencies-that-are-behind-the-versions-nx-manages).
{% /aside %}
A non-interactive `--agentic` run warns and continues without the agent, since the flow requires an interactive terminal. When the flow is enabled, Nx commits each migration separately by default so the agent can review an isolated diff.
### Interactively opting out of package updates
To always use the agentic flow (or a specific agent) without passing the flag, set the `agentic` and `validate` options in the [`migrate` section of `nx.json`](/docs/reference/nx-json#migrate).
To opt out of package updates, you need to run the migration in interactive mode:
## Workspace-wide migrate defaults
```shell
nx migrate latest --interactive
```
As the migration runs and collects the package updates, you'll be prompted to apply optional package updates, and you can choose what to do based on your needs. The `package.json` will be updated and the `migrations.json` will be generated considering your responses to those prompts.
### Updating dependencies that are behind the versions Nx manages
Once you have skipped some optional updates, there'll come a time when you'll want to update those packages. To do so, you'll need to generate the package updates and migrations from the Nx version that contained those skipped updates.
Say you skipped updating Angular to **v16.x.x**. That package update was meant to happen as part of the `@nx/angular@16.1.0` update, but you decided to skip it at the time. The recommended way to collect the migrations from such an older version is to run the following:
```shell
nx migrate latest --from=nx@16.0.0 --exclude-applied-migrations
```
A couple of things are happening there:
- The `--from=nx@16.0.0` flag tells the `migrate` command to use the version **16.0.0** as the installed version for the `nx` package and all the first-party Nx plugins. Note we use a version lower than the one where the update was meant to happen. This is to account for the fact that the update is normally targeted to a prerelease version for testing it before the final release.
- The `--exclude-applied-migrations` flag tells the `migrate` command not to collect migrations that should have been applied on previous updates.
So, the above command will effectively collect any package update and migration meant to run if your workspace had `nx@16.0.0` installed while excluding those that should have been applied before. You can provide a different older version to collect migrations from.
{% aside type="caution" title="Automatically excluding previously applied migrations" %}
Automatically excluding previously applied migrations doesn't consider migrations manually removed from the `migrations.json` in previous updates. If you've manually removed migrations in the past and want to run them, don't pass the `--exclude-applied-migrations` and collect all previous migrations.
{% /aside %}
### Identifying the Nx version to migrate from to collect previously skipped updates
After running the migrations in interactive mode and opting-out of some package updates, a message is printed to the terminal with the command to run later to collect and apply those skipped updates. For example, if you skipped updating Angular to **v16.0.0**, this is the output you'll see (simplified for brevity):
```shell
nx migrate latest --interactive
Fetching meta data about packages.
It may take a few minutes.
...
✔ Do you want to update to TypeScript v5.0? (Y/n) · false
✔ Do you want to update the Angular version to v16? (Y/n) · false
NX The migrate command has run successfully.
- package.json has been updated.
- migrations.json has been generated.
NX Next steps:
- Make sure package.json changes make sense and then run 'pnpm install --no-frozen-lockfile',
- Run 'pnpm exec nx migrate --run-migrations'
- You opted out of some migrations for now. Write the following command down somewhere to apply these migrations later:
- nx migrate 16.5.3 --from nx@16.1.0-beta.0 --exclude-applied-migrations
- To learn more go to https://nx.dev/recipes/other/advanced-update
```
You can see in the "Next steps" section a suggested command to run to apply the skipped package updates. Make sure to store that information somewhere so you can later remember from which version you need to run the migration to apply the skipped package updates.
Please note the suggested command is only based on a particular run of the `nx migrate` command. If you've skipped package updates in previous runs, you'll need to use the oldest version you've stored that you haven't yet run the migration from.
If you don't have the command and need to find out from which version to run the migration, you can take a look at the `migrations.json` file of the relevant package. For example, if you skipped updating Angular to **v16.0.0**, you can take a look at the `migrations.json` file of the `@nx/angular` package and you'll find the following:
```jsonc
// node_modules/@nx/angular/migrations.json
{
// ...
"packageJsonUpdates": {
// ...
"16.1.0": {
"version": "16.1.0-beta.1",
"x-prompt": "Do you want to update the Angular version to v16?",
"requires": {
"@angular/core": ">=15.2.0 <16.0.0",
},
"packages": {
"@angular/core": {
"version": "~16.0.0",
"alwaysAddToPackageJson": true,
},
// ...
},
},
// ...
},
}
```
You can see the `16.1.0-beta.1` version is the one that contains the update for `@angular/core` to **~16.0.0**. That's the version you need to run the migration from to apply the package update.
To avoid passing the same flags on every run, set defaults in the [`migrate` section of `nx.json`](/docs/reference/nx-json#migrate) - `createCommits`, `commitPrefix`, `include`, `multiMajorMode`, `agentic`, and `validate`. A command-line flag always overrides the `nx.json` value, which in turn overrides the built-in Nx defaults.
## Other advanced capabilities
@@ -238,13 +198,13 @@ You can see the `16.1.0-beta.1` version is the one that contains the update for
Sometimes, you may want to use a different version of a package than what Nx recommends. To do that, specify the package and version:
```shell
nx migrate latest --to="jest@22.0.0,cypress@3.4.0"
nx migrate --to="jest@22.0.0,cypress@3.4.0"
```
By default, Nx uses currently installed packages to calculate what migrations need to run. To override them, override the version:
```shell
nx migrate latest --to="@nx/jest@12.0.0"
nx migrate --to="@nx/jest@12.0.0"
```
{% aside type="caution" title="Overriding versions" %}
@@ -6,4 +6,4 @@ sidebar:
pagefind: false
---
{% sidebar_group_cards group="How Nx Works" /%}
{% sidebar_group_cards group="How Nx works" /%}
@@ -0,0 +1,9 @@
---
title: Installation and updates
description: Installation and update guides for Nx
sidebar:
hidden: true
pagefind: false
---
{% sidebar_group_cards group="Knowledge Base/Installation and updates" /%}
@@ -1,9 +0,0 @@
---
title: Installation
description: Installation guides for Nx
sidebar:
hidden: true
pagefind: false
---
{% sidebar_group_cards group="Knowledge Base/Installation" /%}
@@ -1,12 +1,12 @@
---
title: Assignment Rules
description: Control which tasks run on which agents using assignment rules with Nx Agents or manual DTE
description: Control which tasks run on which agents using assignment rules with Nx Agents
filter: 'type:References'
---
Assignment rules allow you to control which tasks can run on which agents. Save on agent costs by provisioning different sizes of agents to suite the individual needs of your tasks. Ensure resource intensive targets like `e2e-ci` and `build` have what they need by using larger agents and with a specified parallelism. Lighter tasks like `lint` and `test` can run on smaller agents.
Assignment rules are defined in `yaml` files within your workspace's `.nx/workflows` directory. You can use assignment rules with [Manual Distributed Task Execution (DTE)](/docs/guides/nx-cloud/manual-dte) or with [dynamic Nx Agents](/docs/features/ci-features/dynamic-agents). Note that additional configuration is required when using Manual DTE.
Assignment rules are defined in `yaml` files within your workspace's `.nx/workflows` directory. You can use assignment rules with [Nx Agents](/docs/features/ci-features/distribute-task-execution).
## How to define an assignment rule
@@ -40,7 +40,7 @@ assignment-rules:
```
{% /tabitem %}
{% tabitem label="Assignment rules with manual DTE" %}
{% tabitem label="Assignment rules when you bring your own compute" %}
```yaml
// .nx/workflows/assignment-rules.yaml
@@ -73,9 +73,9 @@ You can mix and match any of the criteria in an assignment rule provided that yo
- There is at least one [agent type](/docs/reference/nx-cloud/launch-templates) specified in the `run-on` field. If no parallelism is specified, the parallelism of the executed command will be used instead. If that is not specified, then the parallelism will default to `1`
- For assignment rules with Nx Agents, every changeset in your `distribute-on` field must include at **least one agent** that matches each agent type specified in the `run-on` field across all assignment rules. For example, if your rules distribute tasks on `linux-small-js`, `linux-medium-js`, and `linux-large-js`, then at least one agent of each type must be available; otherwise, tasks associated with those rules cannot be executed.
{% aside type="note" title="If you are using Manual DTE, you must define your own agent types" %}
{% aside type="note" title="If you bring your own compute, you must define your own agent types" %}
You must define your own agent types and attach them to your agents using the `NX_AGENT_LAUNCH_TEMPLATE` environment variable. Ensure that for each `run-on` field in your assignment rules, you have corresponding agents in your agent pool that have the same agent type.
See below for an [example](#using-assignment-rules-with-manual-dte) of how to define your own agent types when using Manual DTE.
See below for an [example](#using-assignment-rules-when-you-bring-your-own-compute) of how to define your own agent types.
{% /aside %}
### Assignment rule property reference
@@ -296,7 +296,11 @@ assignment-rules:
parallelism: 5
```
## Using assignment rules with manual DTE
## Using assignment rules when you bring your own compute
{% aside type="note" title="Enterprise Feature" %}
Bringing your own compute requires the [Nx Enterprise plan](https://nx.dev/enterprise?utm_source=nx.dev&utm_medium=callout&utm_campaign=bring-your-own-compute). [Nx Agents](/docs/features/ci-features/distribute-task-execution) distributes your tasks on all plans.
{% /aside %}
A typical `assignment-rules.yaml` file might look like this:
@@ -56,11 +56,8 @@ The following resource classes are available:
- `docker_linux_amd64/small`
- `docker_linux_amd64/medium`
- `docker_linux_amd64/medium+`
- `docker_linux_amd64/large`
- `docker_linux_amd64/large+`
- `docker_linux_amd64/extra_large`
- `docker_linux_amd64/extra_large+`
- `docker_linux_arm64/medium`
- `docker_linux_arm64/large`
- `docker_linux_arm64/extra_large`
@@ -74,9 +71,13 @@ A launch template's `image` defines the available base software for the agent ma
{% aside type="tip" title="Looking for Docker in Docker support?" %}
Docker in Docker support (DinD) is currently limited to Organizations on the enterprise plan.
Docker-in-Docker (DinD) runs on a
[dedicated compute cluster](/docs/features/ci-features/dedicated-compute-cluster). Every agent in
the cluster can run DinD, so your tasks can build and push container images or run Testcontainers.
Request the add-on under **Settings > Add-ons**.
If you're interested in our [Enterprise plan please reach out!](/contact/sales)
Nx Enterprise [single-tenant](/docs/enterprise/single-tenant/overview) customers get DinD through
their dedicated deployment.
{% /aside %}
```yaml
@@ -251,7 +251,7 @@ nxApi:
##### Updates
- Feat: [Polygraph availability](/docs/enterprise/polygraph) (Conformance, Workspace Graph, Custom Workflows)
- Feat: Conformance, Workspace Graph, and Custom Workflows availability
- Feat: Nx 21 [continuous tasks](https://nx.dev/blog/nx-21-continuous-tasks) support
- Feat: Download artifacts button
- When you view a task that just ran in CI on the NxCloud UI, there is now a button to download any artifacts that task produced directly from your browser
@@ -44,42 +44,47 @@ The following environment variables are ones that you can set to change the beha
### Advanced
| Property | Type | Description |
| ------------------------------------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NX_ADD_PLUGINS` | boolean | If set to `false`, Nx will not add plugins to infer tasks. This is `true` by default. |
| `NX_CACHE_PROJECT_GRAPH` | boolean | If set to `false`, disables the project graph cache. Most useful when developing a plugin that modifies the project graph. |
| `NX_COMPILE_CACHE` | boolean | If set to `false`, disables Node's built-in V8 bytecode compile cache for Nx processes (CLI, daemon, plugin workers). The cache is enabled by default on Node 22.8+ and speeds up repeated invocations by reusing compiled bytecode. Has no effect on older Node versions. |
| `NX_DAEMON_SOCKET_DIR` | string | Alias of `NX_SOCKET_DIR`, used only when `NX_SOCKET_DIR` is not set. Despite the name, it controls all Nx socket placements, not just the daemon socket. Prefer `NX_SOCKET_DIR` in new setups. |
| `NX_FORCE_REUSE_CACHED_GRAPH` | boolean | If set to `true`, Nx will reuse an existing cached project graph when available and skip recomputing it. Useful in short-lived CI steps that run immediately after a step which already computed the graph. |
| `NX_FORMAT_SORT_TSCONFIG_PATHS` | boolean | If set to `true`, generators will sort the TypeScript path mappings in the root tsconfig file. |
| `NX_GENERATE_QUIET` | boolean | If set to `true`, will prevent Nx logging file operations during generate |
| `NX_ISOLATE_PLUGINS` | boolean | Forces plugin isolation on or off, overriding the automatic detection. Set to `true` to always run inference plugins in isolated workers, or `false` to always run them in-process. |
| `NX_MIGRATE_INSTALL_CONCURRENCY` | number | Limits the number of concurrent package installs when fetching migration metadata. Useful for avoiding package manager cache conflicts on Windows with private registries. If not set, installs run with no concurrency limit. |
| `NX_MIGRATE_SKIP_REGISTRY_FETCH` | boolean | If set to `true`, will skip fetching metadata from the registry and instead use the installation method directly. |
| `NX_NATIVE_COMMAND_RUNNER` | boolean | If set to `false`, disables the native pseudo-terminal command runner and falls back to the standard Node.js child process. Enabled by default. |
| `NX_NATIVE_FILE_CACHE_DIRECTORY` | string | The cache for native `.node` files is stored under a global temp directory by default. Set this variable to use a different directory. This is interpreted as an absolute path. |
| `NX_NATIVE_FILE_LOGGING` | string | Enables the native (Rust) tracing logger and writes logs to `.nx/workspace-data/nx.log`. Accepts the same filter directives as `NX_NATIVE_LOGGING` (e.g. `debug`, `info`, `[{project_name=myapp}]`). Useful for debugging native code paths. |
| `NX_NATIVE_LOGGING` | string | Filter directive for the native (Rust) tracing logger. Defaults to `nx::native=info`. Set to a log level (`trace`, `debug`, `info`, `warn`, `error`, `off`) to control all native logs, or scope by crate or module (e.g. `nx=trace`, `nx::native::tasks::hashers=debug`, `[{project_name=myapp}]`). |
| `NX_PERF_LOGGING` | boolean | If set to `true`, will print debug information useful for profiling executors and Nx itself |
| `NX_PLUGIN_NO_TIMEOUTS` | boolean | If set to `true`, plugin operations will not timeout |
| `NX_PREFER_NODE_STRIP_TYPES` | boolean | If set to `true` and running on Node.js 22.6+, Nx will use Node.js native TypeScript type stripping instead of `@swc-node/register` or `ts-node` when loading TypeScript configuration files. This improves performance. |
| `NX_PREFER_TS_NODE` | boolean | If set to `true`, Nx will use `ts-node` for local execution of plugins even if `@swc-node/register` is installed. |
| `NX_PROFILE` | string | Prepend `NX_PROFILE=profile.json` before running targets with Nx to generate a file that be [loaded in Chrome dev tools](/docs/troubleshooting/performance-profiling) to visualize the performance of Nx across multiple processes. |
| `NX_REJECT_UNKNOWN_LOCAL_CACHE` | boolean | Legacy cache safety toggle. Set to `0` or `false` to allow reading a local cache that wasn't populated by the current machine. Not supported with the new database cache. See [legacy cache](/docs/reference/deprecated/legacy-cache#nxrejectunknownlocalcache). |
| `NX_RUNNER` | string | The name of task runner from the config to use. Can be overridden on the command line with `--runner`. Not read if `NX_TASKS_RUNNER` is set. |
| `NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN` | string | Bearer token passed as the `Authorization` header when contacting the self-hosted remote cache server configured via `NX_SELF_HOSTED_REMOTE_CACHE_SERVER`. |
| `NX_SELF_HOSTED_REMOTE_CACHE_SERVER` | string | URL of a self-hosted remote cache server. When set, Nx uses this endpoint for remote cache reads and writes instead of Nx Cloud. |
| `NX_SKIP_ATOMIZER_VALIDATION` | boolean | If set to `true`, suppresses the atomizer warning Nx shows when Nx Cloud isn't configured. |
| `NX_SKIP_FORMAT` | boolean | If set to `true`, skips Prettier formatting in generators and migrations. Useful for repositories that use alternative formatters like Biome, dprint, or have custom formatting requirements. |
| `NX_SKIP_LOG_GROUPING` | boolean | If set to `true`, Nx will not group command's logs on CI. |
| `NX_SKIP_NATIVE_FILE_CACHE` | boolean | If set to `true`, disables the native `.node` file cache. Nx otherwise copies native bindings to a cache directory to avoid file-locking issues when multiple processes load them. |
| `NX_SKIP_PROVENANCE_CHECK` | boolean | If set to `true`, skips `npm` provenance verification when installing packages during `nx migrate`. This is a security-sensitive check. Only disable it if you understand the implications. |
| `NX_SKIP_VSCODE_EXTENSION_INSTALL` | boolean | If set to `true`, skips the automatic installation of the Nx Console extension for supported editors. Set this in environments where the temp file that we store this information in otherwise isn't accessible. |
| `NX_SOCKET_DIR` | string | Directory for all Nx sockets (daemon, forked process, and plugin). Set this to a shorter path when the default temp directory produces a socket path that exceeds the OS limit. Takes precedence over `NX_DAEMON_SOCKET_DIR`. |
| `NX_TASKS_RUNNER` | string | The name of task runner from the config to use. Can be overridden on the command line with `--runner`. Preferred over `NX_RUNNER`. |
| `NX_TASKS_RUNNER_DYNAMIC_OUTPUT` | boolean | If set to `false`, will use non-dynamic terminal output strategy (what you see in CI), even when you terminal can support the dynamic version |
| `NX_USE_V8_SERIALIZER` | boolean | If set to `true`, Nx will use v8 serialization in the pseudo-IPC channel between the daemon and task processes instead of JSON. Improves throughput for workspaces with large task payloads. |
| `NX_WRAPPER_SKIP_INSTALL` | boolean | If set to `true`, the `.nx/nxw.js` wrapper skips verifying and self-installing the pinned Nx version before each command. |
| Property | Type | Description |
| ------------------------------------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NX_ADD_PLUGINS` | boolean | If set to `false`, Nx will not add plugins to infer tasks. This is `true` by default. |
| `NX_CACHE_PROJECT_GRAPH` | boolean | If set to `false`, disables the project graph cache. Most useful when developing a plugin that modifies the project graph. |
| `NX_COMPILE_CACHE` | boolean | If set to `false`, disables Node's built-in V8 bytecode compile cache for Nx processes (CLI, daemon, plugin workers). The cache is enabled by default on Node 22.8+ and speeds up repeated invocations by reusing compiled bytecode. Has no effect on older Node versions. |
| `NX_COMPLETE` | string | Internal. Set by the shell completion wrapper scripts (generated by `nx completion <shell>`) to put Nx in tab-completion mode. The value is the shell name (`bash`, `zsh`, `fish`, or `powershell`). Do not set this manually. |
| `NX_DAEMON_SOCKET_DIR` | string | Alias of `NX_SOCKET_DIR`, used only when `NX_SOCKET_DIR` is not set. Despite the name, it controls all Nx socket placements, not just the daemon socket. Prefer `NX_SOCKET_DIR` in new setups. |
| `NX_FORCE_REUSE_CACHED_GRAPH` | boolean | If set to `true`, Nx will reuse an existing cached project graph when available and skip recomputing it. Useful in short-lived CI steps that run immediately after a step which already computed the graph. |
| `NX_FORMAT_SORT_TSCONFIG_PATHS` | boolean | If set to `true`, generators will sort the TypeScript path mappings in the root tsconfig file. |
| `NX_GENERATE_QUIET` | boolean | If set to `true`, will prevent Nx logging file operations during generate |
| `NX_INVOCATION_ROOT_PID` | number | Internal. Set by Nx to the PID of the root Nx process. Used to detect recursive task invocation loops across nested Nx processes. Do not set this manually. |
| `NX_ISOLATE_PLUGINS` | boolean | Forces plugin isolation on or off, overriding the automatic detection. Set to `true` to always run inference plugins in isolated workers, or `false` to always run them in-process. |
| `NX_MIGRATE_INSTALL_CONCURRENCY` | number | Limits the number of concurrent package installs when fetching migration metadata. Useful for avoiding package manager cache conflicts on Windows with private registries. If not set, installs run with no concurrency limit. |
| `NX_MIGRATE_SKIP_REGISTRY_FETCH` | boolean | **Deprecated, will be removed in Nx 24** - use `NX_MIGRATE_USE_REGISTRY_RESOLUTION` (set to `false`) instead. If set to `true`, will skip fetching metadata from the registry and instead use the installation method directly. Legacy alias for setting `NX_MIGRATE_USE_REGISTRY_RESOLUTION` to `false`; the newer variable takes precedence when both are set. |
| `NX_MIGRATE_USE_REGISTRY_RESOLUTION` | boolean | Whether `nx migrate` resolves package versions and migration metadata via the npm registry (faster) instead of a package-manager install. Set to `false` to always resolve through your package manager. Takes precedence over the legacy `NX_MIGRATE_SKIP_REGISTRY_FETCH` variable and the `migrate.useRegistryResolution` setting in `nx.json`. Defaults to `true`. |
| `NX_NATIVE_COMMAND_RUNNER` | boolean | If set to `false`, disables the native pseudo-terminal command runner and falls back to the standard Node.js child process. Enabled by default. |
| `NX_NATIVE_FILE_CACHE_DIRECTORY` | string | The cache for native `.node` files is stored under a global temp directory by default. Set this variable to use a different directory. This is interpreted as an absolute path. |
| `NX_NATIVE_FILE_LOGGING` | string | Enables the native (Rust) tracing logger and writes logs to `.nx/workspace-data/nx.log`. Accepts the same filter directives as `NX_NATIVE_LOGGING` (e.g. `debug`, `info`, `[{project_name=myapp}]`). Useful for debugging native code paths. |
| `NX_NATIVE_LOGGING` | string | Filter directive for the native (Rust) tracing logger. Defaults to `nx::native=info`. Set to a log level (`trace`, `debug`, `info`, `warn`, `error`, `off`) to control all native logs, or scope by crate or module (e.g. `nx=trace`, `nx::native::tasks::hashers=debug`, `[{project_name=myapp}]`). |
| `NX_PERF_LOGGING` | boolean | If set to `true`, will print debug information useful for profiling executors and Nx itself |
| `NX_PLUGIN_NO_TIMEOUTS` | boolean | If set to `true`, plugin operations will not timeout |
| `NX_PREFER_NODE_STRIP_TYPES` | boolean | When the Node.js runtime exposes native TypeScript stripping (`process.features.typescript` - Node 23.6+ unflagged, 22.18+ LTS unflagged, or 22.6+ with `--experimental-strip-types`), Nx loads TypeScript configuration files via Node directly (faster, no `@swc-node/register`/`ts-node` registration). Set to `false` to opt out and force `@swc-node/register` or `ts-node`. When native stripping fails on an unsupported construct (e.g. `enum`, runtime `namespace`), Nx falls back to `@swc-node/register`/`ts-node` + `tsconfig-paths` automatically; set `NX_VERBOSE_LOGGING=true` to see when fallback triggers. |
| `NX_DISABLE_TSCONFIG_PATHS` | boolean | Skip `tsconfig-paths` registration on the `@swc-node/register`/`ts-node` fallback path. Set to `true` when relying on package manager workspaces (pnpm/yarn/npm) for project linking and `tsconfig` path aliases aren't needed. |
| `NX_PREFER_TS_NODE` | boolean | If set to `true`, Nx will use `ts-node` for local execution of plugins even if `@swc-node/register` is installed. |
| `NX_PROCESS_KILL_GRACE_PERIOD` | number | Time in milliseconds to wait for child processes to exit gracefully before force-killing them. Defaults to `5000`. |
| `NX_PROFILE` | string | Prepend `NX_PROFILE=profile.json` before running targets with Nx to generate a file that be [loaded in Chrome dev tools](/docs/troubleshooting/performance-profiling) to visualize the performance of Nx across multiple processes. |
| `NX_REJECT_UNKNOWN_LOCAL_CACHE` | boolean | Legacy cache safety toggle. Set to `0` or `false` to allow reading a local cache that wasn't populated by the current machine. Not supported with the new database cache. See [legacy cache](/docs/reference/deprecated/legacy-cache#nxrejectunknownlocalcache). |
| `NX_RUNNER` | string | The name of task runner from the config to use. Can be overridden on the command line with `--runner`. Not read if `NX_TASKS_RUNNER` is set. |
| `NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN` | string | Bearer token passed as the `Authorization` header when contacting the self-hosted remote cache server configured via `NX_SELF_HOSTED_REMOTE_CACHE_SERVER`. |
| `NX_SELF_HOSTED_REMOTE_CACHE_SERVER` | string | URL of a self-hosted remote cache server. When set, Nx uses this endpoint for remote cache reads and writes instead of Nx Cloud. |
| `NX_SKIP_ATOMIZER_VALIDATION` | boolean | If set to `true`, suppresses the atomizer warning Nx shows when Nx Cloud isn't configured. |
| `NX_SKIP_FORMAT` | boolean | If set to `true`, skips Prettier formatting in generators and migrations. Useful for repositories that use alternative formatters like Biome, dprint, or have custom formatting requirements. |
| `NX_SKIP_LOG_GROUPING` | boolean | If set to `true`, Nx will not group command's logs on CI. |
| `NX_SKIP_NATIVE_FILE_CACHE` | boolean | If set to `true`, disables the native `.node` file cache. Nx otherwise copies native bindings to a cache directory to avoid file-locking issues when multiple processes load them. |
| `NX_SKIP_PROVENANCE_CHECK` | boolean | If set to `true`, skips `npm` provenance verification when installing packages during `nx migrate`. This is a security-sensitive check. Only disable it if you understand the implications. |
| `NX_SKIP_VSCODE_EXTENSION_INSTALL` | boolean | If set to `true`, skips the automatic installation of the Nx Console extension for supported editors. Set this in environments where the temp file that we store this information in otherwise isn't accessible. |
| `NX_SOCKET_DIR` | string | Directory for all Nx sockets (daemon, forked process, and plugin). Set this to a shorter path when the default temp directory produces a socket path that exceeds the OS limit. Takes precedence over `NX_DAEMON_SOCKET_DIR`. |
| `NX_TASKS_RUNNER` | string | The name of task runner from the config to use. Can be overridden on the command line with `--runner`. Preferred over `NX_RUNNER`. |
| `NX_TASKS_RUNNER_DYNAMIC_OUTPUT` | boolean | If set to `false`, will use non-dynamic terminal output strategy (what you see in CI), even when you terminal can support the dynamic version |
| `NX_USE_V8_SERIALIZER` | boolean | If set to `true`, Nx will use v8 serialization in the pseudo-IPC channel between the daemon and task processes instead of JSON. Improves throughput for workspaces with large task payloads. |
| `NX_WRAPPER_SKIP_INSTALL` | boolean | If set to `true`, the `.nx/nxw.js` wrapper skips verifying and self-installing the pinned Nx version before each command. |
## Plugin environment variables
@@ -116,11 +121,11 @@ Similar to the Nx CLI, Nx Cloud also uses the `NX_VERBOSE_LOGGING` environment v
| Property | Type | Description |
| ------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NX_BRANCH` | string | The current branch name. For most CI providers, `nx-cloud` determines this automatically. Must be set to the PR number for GitHub, BitBucket and GitLab integrations to work properly. |
| `NX_CLOUD_DISABLE_METRICS_COLLECTION` | boolean | Disables collection of CPU and memory metrics during task execution (enabled by default for enterprise users on Nx 22.1+). See [Task Resource Usage](/docs/guides/nx-cloud/ci-resource-usage). |
| `NX_CLOUD_METRICS_DIRECTORY` | string | Directory where Nx writes resource metrics during task execution. See [Task Resource Usage](/docs/guides/nx-cloud/ci-resource-usage). |
| `NX_CLOUD_DISABLE_METRICS_COLLECTION` | boolean | Disables collection of CPU and memory metrics during task execution (enabled by default for enterprise users on Nx 22.1+). See [Resource Usage](/docs/features/ci-features/resource-usage). |
| `NX_CLOUD_METRICS_DIRECTORY` | string | Directory where Nx writes resource metrics during task execution. See [Resource Usage](/docs/features/ci-features/resource-usage). |
| `NX_CI_EXECUTION_ID` | string | A unique identifier for the current CI run or job. For most CI providers, `nx-cloud` determines this automatically. The value on the main job must match the value on all agents. |
| `NX_CI_EXECUTION_ENV` | string | Used when you have multiple main jobs (e.g., running CI on both Linux and Windows). The main job with this env variable will connect to agents with the same env name. |
| `NX_AGENT_LAUNCH_TEMPLATE` | string | Should only be used when running agents with Manual DTE. Attaches a launch template type to your agents to leverage assignment rules for task distribution. |
| `NX_AGENT_LAUNCH_TEMPLATE` | string | Should only be used when you bring your own compute. Attaches a launch template type to your agents to leverage assignment rules for task distribution. |
| `NX_CLOUD_ACCESS_TOKEN` | string | Configure the Nx Cloud access token. Takes precedence over the `accessToken` property in `nx.json`. Common to have a read-only token in `nx.json` and a read-write token set via this environment variable in CI. |
| `NX_CLOUD_API` | string | The URL of the Nx Cloud instance to connect to. Overrides `nxCloudUrl` in `nx.json`. |
| `NX_CLOUD_ENCRYPTION_KEY` | string | Enable end-to-end encryption of artifacts. Artifacts will be encrypted/decrypted on your machine. Can also be set via the `encryptionKey` property in `nx.json`. |
@@ -373,7 +373,7 @@ If you accidentally run this command locally, remove all generated marker files
| Option | Type | Description | Default |
| ------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `--distribute-on` | string | Configure the number of agents, [launch templates](/docs/reference/nx-cloud/launch-templates) and [assignment rules](/docs/reference/nx-cloud/assignment-rules) for distributed execution | |
| `--assignment-rules` | string | Path to the [assignment rules configuration](/docs/reference/nx-cloud/assignment-rules#using-assignment-rules-with-manual-dte) for manual distribution. | |
| `--assignment-rules` | string | Path to the [assignment rules configuration](/docs/reference/nx-cloud/assignment-rules#using-assignment-rules-when-you-bring-your-own-compute) for manual distribution. | |
| `--require-explicit-completion` | boolean | Disable automatic completion monitoring and require explicit `nx complete-ci-run` | `false` |
| `--stop-agents-after` | string | Comma-separated list of targets after which agents should terminate | |
| `--stop-agents-on-failure` | boolean | Terminate all agents when a command fails. This flag does not interrupt the command itself. To cancel the command on failure use [nxBail](/docs/reference/nx-commands) | `true` |
@@ -575,7 +575,7 @@ nx affected -t build --no-dte
### `nx-cloud start-agent`
Starts an agent process for [manual distributed task execution](/docs/guides/nx-cloud/manual-dte). The agent waits for Nx Cloud to assign tasks that have been distributed by the main CI job via `start-ci-run`. For automatic agent management, use [Nx Agents](/docs/features/ci-features/distribute-task-execution) instead.
Starts an agent process when you [bring your own compute](/docs/guides/nx-cloud/bring-your-own-compute). The agent waits for Nx Cloud to assign tasks that have been distributed by the main CI job via `start-ci-run`. To have Nx Cloud provision and manage the agents for you, see [Nx Agents](/docs/features/ci-features/distribute-task-execution).
This command is the same as running `nx start-agent`.
@@ -599,7 +599,7 @@ Use the `NX_AGENT_NAME` environment variable to assign a name to the agent for i
NX_AGENT_NAME=agent-1 npx nx-cloud start-agent
```
For complete examples across different CI providers, see the [Manual Distributed Task Execution guide](/docs/guides/nx-cloud/manual-dte).
For complete examples across different CI providers, see the [bring your own compute guide](/docs/guides/nx-cloud/bring-your-own-compute).
### `nx-cloud stop-all-agents`

Some files were not shown because too many files have changed in this diff Show More