Compare commits

..

1160 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
Jason Jean 210a434fb5 chore(repo): update nx to 22.7.0-rc.2 (#35437)
Updating Nx from 22.7.0-rc.1 to 22.7.0-rc.2
2026-04-24 17:11:41 +00:00
Craigory Coppola 2fbea5cc6d fix(core): prevent deferred spinner text update from causing early spinner appearance (#35435)
## Current Behavior
Calling `delayedSpinner.setMessage` before it would have already
appeared causes it to appear earlier than it should

## Expected Behavior
Calling delayedSpinner.setMessage doesn't actually invoke the spinner
update message if the delayed spinner hasn't fired yet, instead it
stores the message under `lastMessage`, and whenever the spinner fires
it reads lastMessage

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-04-24 16:57:04 +00:00
Copilot 706d1add6d feat(dotnet): add ci-workflow generator (#33321)
Implements a ci-workflow generator for .NET projects, following the
pattern established by the gradle ci-workflow generator.

## Blocked

Waiting for https://github.com/nrwl/nx-cloud-workflows/pull/112

## Changes

- **Generator implementation**
(`packages/dotnet/src/generators/ci-workflow/`)
  - Supports GitHub Actions and CircleCI
  - Configures .NET SDK 8.x
  - Uses `linux-medium` agent type for Nx Cloud task distribution
  - Includes Nx affected commands and self-healing CI (`nx fix-ci`)

- **Templates**
  - GitHub Actions: uses `actions/setup-dotnet@v4`
  - CircleCI: uses `mcr.microsoft.com/dotnet/sdk:8.0` docker image

- **Registration**: Added to `generators.json` alongside existing `init`
generator

## Usage

```bash
# Generate GitHub Actions workflow
nx g @nx/dotnet:ci-workflow --ci=github

# Generate CircleCI workflow  
nx g @nx/dotnet:ci-workflow --ci=circleci
```

Fixes
https://linear.app/nxdev/issue/NXC-3356/generate-ci-workflow-for-net

> [!WARNING]
>
> <details>
> <summary>Firewall rules blocked me from connecting to one or more
addresses (expand for details)</summary>
>
> #### I tried to connect to the following addresses, but was blocked by
firewall rules:
>
> - `repo.gradle.org`
> - `staging.nx.app`
>
> If you need me to access, download, or install something from one of
these locations, you can either:
>
> - Configure [Actions setup
steps](https://gh.io/copilot/actions-setup-steps) to set up my
environment, which run before the firewall is enabled
> - Add the appropriate URLs or hosts to the custom allowlist in this
repository's [Copilot coding agent
settings](https://github.com/nrwl/nx/settings/copilot/coding_agent)
(admins only)
>
> </details>

<!-- START COPILOT CODING AGENT SUFFIX -->



<details>

<summary>Original prompt</summary>

> Issue Title: Generate `ci-workflow` for .NET
> Issue Description: This should be similar to the existing ci-workflow
generator for gradle, reference
[https://github.com/nrwl/nx/tree/master/packages/gradle/src/generators/ci-workflow](https://github.com/nrwl/nx/tree/master/packages/gradle/src/generators/ci-workflow)
> 
> The generator should be scaffolded using `nx g generator
./packages/dotnet/src/generators/ci-workflow/ci-workflow`
> Fixes
https://linear.app/nxdev/issue/NXC-3356/generate-ci-workflow-for-net
> 
> 
> Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6:
> [https://github.com/nrwl/nx](https://github.com/nrwl/nx)
> 
> Comment by User d484ef82-7f7d-4a95-be09-9d82ca3905dc:
> 📋 I wasn't able to determine which GitHub repository to work in.
> 
> I think it's one of these, but can you tell me which one is right?
> 
> Comment by User :
> This thread is for an agent session with githubcopilot.
> 
> 


</details>



<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AgentEnder <6933928+AgentEnder@users.noreply.github.com>
Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
2026-04-24 12:57:02 -04:00
Leosvel Pérez Espinosa ac465a0090 fix(bundling): declare tsconfig.json as input for esbuild targets (#35432)
## Current Behavior

The `@nx/esbuild:esbuild` executor calls `isUsingTsSolutionSetup` at
task runtime, which reads `extends`, `files`, and `include` from the
workspace-root `tsconfig.json`. Targets using the executor do not
declare those fields as inputs, so cache hashes do not reflect changes
to them and builds can hit stale cache entries after a `tsconfig.json`
edit.

## Expected Behavior

Targets using the `@nx/esbuild:esbuild` executor include a field-scoped
`{workspaceRoot}/tsconfig.json` input covering exactly the fields the
executor reads, so hashes change when those fields change and remain
unaffected by unrelated edits.

- `addBuildTargetDefaults` gains an optional `extraInputs` parameter so
plugin generators can extend the default/production inputs when seeding
executor-keyed target defaults. Existing call sites are unchanged.
- The `@nx/esbuild:configuration` generator uses that parameter to add a
field-scoped `{workspaceRoot}/tsconfig.json` input (fields: `extends`,
`files`, `include`) to the `@nx/esbuild:esbuild` target defaults it
seeds.
- This repo's `nx.json` gains the same executor-keyed target defaults so
`tools-documentation-create-embeddings` (the only esbuild target in the
repo) inherits the fix. `dependsOn` mirrors the existing `build`
target-name default to preserve the inferred `typecheck` and
`build-base` dependencies.
2026-04-24 11:37:00 -04:00
Lucas Estevão 6c6d399eaa feat(vite): add compiler option to vite plugin for tsgo support (#35429)
## Description

Adds a `compiler` option to the `@nx/vite` plugin, mirroring the same
option already in `@nx/js` (added in #33821).

This lets users specify an alternative TypeScript compiler for the
inferred `typecheck` target — specifically `tsgo` from
`@typescript/native-preview` (TypeScript 7 Go compiler).

## Problem

`@nx/vite` hardcodes `'tsc'` for typecheck while `@nx/js` already
supports a configurable `compiler` option. Users who want `tsgo` have to
patch `@nx/vite` manually.

## Solution

3 lines in `packages/vite/src/plugins/plugin.ts`:

1. Add `compiler?: string` to `VitePluginOptions` (with JSDoc)
2. `options.compiler ?? 'tsc'` instead of hardcoded `'tsc'` (Vue
projects still use `vue-tsc`)
3. `options.compiler ??= 'tsc'` in `normalizeOptions`

## Usage

```json
// nx.json
{
  "plugins": [
    {
      "plugin": "@nx/vite/plugin",
      "options": {
        "compiler": "tsgo"
      }
    }
  ]
}
```

## Benchmarks (large monorepo, ~400 projects)

| Project | `tsc` | `tsgo` | Speedup |
|---|---|---|---|
| `dashboard-web` | 2.33s | 0.43s | **5.4×** |
| `market-react` | 11.57s | 3.64s | **3.2×** |

On CI: total typecheck CPU dropped **2.7×**, allowing us to eliminate
worker sharding entirely.

Currently working around this with a pnpm patch on `@nx/vite` — happy to
remove it once this lands.

## Prior art

- #33821 — same `compiler` option added to `@nx/js`
- #35047 / #35167 — Nx team experimented with tsgo internally

---------

Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
2026-04-24 11:35:41 -04:00
Jack Hsu afa4e0b8f1 docs(nx-dev): add kb article for migrating nx imports to @nx/devkit (#35412)
We want a page to link users to when we deprecate and start removing
`nx` exports. There should be zero imports from `nx` in the wild.

KB article:
https://deploy-preview-35412--nx-docs.netlify.app/docs/guides/tips-n-tricks/migrate-nx-imports-to-devkit

## Current Behavior

No guidance for users importing from `nx` (CLI). These imports break in
future major when `nx` stops exporting them.

## Expected Behavior

New recipe under Tips & Tricks. Lists common devkit symbols,
before/after code blocks, and a copy-prompt for AI-driven migration.
Calls out `@nx/devkit/testing` and `@nx/devkit/ngcli-adapter` subpaths.

Also fixes `llm_copy_prompt` transform: inline code, links, and ordered
list numbering were being stripped from extracted prompt text.

## Related Issue(s)

Fixes DOC-462
2026-04-24 11:32:58 -04:00
Jason Jean b156395b1d fix(core): wait for stdio drain before capturing task output (#35422)
## Current Behavior

`NodeChildProcessWithNonDirectOutput` in
`packages/nx/src/tasks-runner/running-tasks/node-child-process.ts:48`
listens for the child process `'exit'` event. In that listener it joins
`terminalOutputChunks` into a single string and clears the buffer before
notifying `exitCallbacks`.

The child process `'exit'` event fires when the child exits, but its
stdout/stderr streams may still have buffered `'data'` events pending on
Node's event queue. When a task writes output and exits on the same tick
(e.g. `echo X && exit 0`), the `'exit'` listener can run *before* the
final `'data'` callback lands in `terminalOutputChunks`. The result: the
late chunk is pushed into a new (now-empty) array that nobody reads, and
the joined output that reaches `exitCallbacks` is missing its tail. The
effect is visible as missing stdout when running many tasks at once
(#35302).

## Expected Behavior

Switch the listener from `'exit'` to `'close'`. Per Node.js docs,
`'close'` fires only after the child has exited **and** its stdio
streams have closed — so every `'data'` event has been delivered by
then. The listener body is otherwise unchanged (`(code, signal)` is
still the argument shape).

No callers of `exitCallbacks` are timing-sensitive: they only read
`code` and `terminalOutput`. The slight delay from waiting for stdio
drain is precisely what we want here.

Added a regression test that simulates the race (`'exit'` fires, then
stdout `'data'` arrives, then `'close'` fires) and asserts the late
output is captured in the joined `terminalOutput`.

## Related Issue(s)

Fixes #35302
2026-04-24 11:07:18 -04:00
Leosvel Pérez Espinosa 9062074b91 fix(core): restore top-level schemas/ in published nx package (#35427)
## Current Behavior

In 22.7.0-rc.1, `$schema` references in `nx.json` and `project.json` no
longer resolve in VS Code / JetBrains / any editor that reads `$schema`
as a filesystem path.

Root cause — two commits combined:

1. #34111 moved schemas from `packages/nx/schemas/` to
`packages/nx/dist/schemas/`.
2. #35109 introduced a `files` allowlist that shipped only `dist/`,
dropping the legacy top-level `schemas/` from the published npm tarball.

Node subpath `exports` (`"./schemas/*": "./dist/schemas/*.json"`) do
**not** redirect filesystem paths — editors bypass Node resolution
entirely. So the regression affected both existing workspaces upgrading
from 22.6.x (with `$schema` already in their configs) and fresh installs
(since `nx init`, `create-nx-workspace`, and the project-configuration
generator all write `./node_modules/nx/schemas/...`).

## Expected Behavior

`schemas/*.json` ship at the root of the published `nx` package again,
so `./node_modules/nx/schemas/nx-schema.json` (and the project/workspace
variants) resolve on disk — no migration required, no changes to the
paths generators write.

Schemas are static JSON assets, not build artifacts — they're now
published from source, not copied through `dist/`:

- `packages/nx/package.json` `files`: `"dist/schemas"` → `"schemas"`.
- `packages/nx/package.json` `exports`: `./schemas/*` and
`./schemas/*.json` both map to `./schemas/*.json`.
- `packages/nx/assets.json`: dropped the `schemas/*.json` → `dist/` copy
step.

Verified via `npm pack --dry-run` — tarball now contains
`schemas/nx-schema.json`, `schemas/project-schema.json`,
`schemas/workspace-schema.json` at root, with no `dist/schemas/`
entries.

## Related Issue(s)

Fixes #35411
2026-04-24 10:30:54 -04:00
Leosvel Pérez Espinosa e1e3f47f87 chore(testing): resolve @nx/cypress, @nx/maven, @nx/plugin, @nx/vitest to source in jest tests (#35420)
## Current Behavior

The patched Jest resolver does not redirect imports of `@nx/cypress`,
`@nx/maven`, `@nx/plugin`, or `@nx/vitest` to their source entrypoints,
so Jest runs against the built output for those packages.

## Expected Behavior

The patched Jest resolver redirects imports of `@nx/cypress`,
`@nx/maven`, `@nx/plugin`, and `@nx/vitest` to source, matching the
treatment of the other `@nx/*` packages.
2026-04-24 09:58:02 -04:00
Jack Hsu 07b0edff24 chore(nx-dev): document how canary.nx.dev works (#35431)
Add a section for nx-dev README that explains how canary.nx.dev works.
2026-04-24 09:49:58 -04:00
MaxKless e0c530adbe fix(core): make nx version lookup bundle-safe (#35430)
## Current Behavior

Nx reads its own version through a package.json path derived from
__filename. When Nx internals are bundled into another tool, that
relative path can point outside the original package layout.

## Expected Behavior

Nx resolves its version through the exported nx/package.json
self-reference, so bundlers can resolve it statically and the runtime no
longer depends on the source/dist file layout.

## Related Issue(s)

N/A
2026-04-24 09:33:55 -04:00
Jack Hsu 081cb1131a docs(misc): fix link to plugin registry from batch mode definition (#35428)
PR to fix broken link. The plugin registry is the obvious page to link
to from the batch mode definition.

Fixes DOC-491
2026-04-24 08:44:33 -04:00
Jeff Miller 04ec111c88 fix(maven): honor settings.xml in Maven 3 batch runner (#35216)
## Current Behavior

When `@nx/maven` runs Maven targets in Nx batch mode, the Maven 3 batch
runner builds a `MavenExecutionRequest` and only calls
`MavenExecutionRequestPopulator.populateDefaults()`. That path injects
default remote repositories but does not merge user and global
`settings.xml` (mirrors, servers, proxies, profile repositories, etc.).
Resolution can ignore corporate mirrors and behave differently from the
`mvn` CLI and from non-batch execution.

## Expected Behavior

Batch mode should apply the same effective settings as the `mvn` CLI:
build settings with `SettingsBuilder`, then `populateFromSettings()`
before `populateDefaults()`. Global `settings.xml` should resolve the
same way as the launcher (`${maven.conf}/settings.xml`), so `maven.conf`
is set from `maven.home` when unset.

## Related Issue(s)

Fixes https://github.com/nrwl/nx/issues/34948
2026-04-23 17:04:39 -04:00
Seth Davenport fe994b6836 fix(misc): remove process exit handlers when child process exits (#35279)
## Current Behavior

I noticed this while investigating a 6-minute hang in our ~2600-project
monorepo for any target that uses `run_command` (in our case, a
`check-types` target that runs `tsc --noEmit`). The hang happens after
Nx appears to have run all the tasks, as seen in github actions log with
the "timestamps" setting enabled:

<img width="1258" height="336" alt="image"
src="https://github.com/user-attachments/assets/14a66362-3253-4649-b750-a69d9baf7c1d"
/>

We've patched our Nx instance locally (`pnpm patch`) with the changes in
this PR and the hang is now gone for us. Offering this PR upstream in
case anyone else is affected.

What seems to be happening is that each `RunningNodeProcess` registers 4
process-level event handlers (`exit`, `SIGINT`, `SIGTERM`, `SIGHUP`) in
`addListeners()` but never removes them after the child process exits.
This causes two problems:

1. **`MaxListenersExceededWarning`** when more than ~10 `run-commands`
tasks execute in parallel
2. **Multi-minute synchronous hang at process exit** — when
`process.exit()` is called, Node.js runs every leaked `exit` handler
sequentially. Each one calls `treeKill()` on an already-dead PID, which
takes ~143ms. With thousands of tasks, this adds minutes of dead time at
the end of every CI run.

### Reproduction

Run any target using `nx:run-commands` on a monorepo with 1000+
projects. After "Successfully ran target..." prints, the process hangs
for several minutes before exiting.

### Measured impact

On our 2610-project monorepo:
- **2610 leaked exit handlers** × **~143ms each** = **~6.2 minutes** of
synchronous blocking after every `check-types` CI run
- Identified via `process.on('exit')` instrumentation showing each
handler calling `treeKill` on dead PIDs from
`RunningNodeProcess.addListeners` at `running-tasks.ts:522`

## Expected Behavior

Process exit handlers registered by `RunningNodeProcess` are cleaned up
when the child process exits. The Node.js process exits promptly after
task completion with no leaked listeners.

## Fix

Store the 4 signal/exit handlers as named references and remove them via
`process.removeListener()` when the child process exits or errors. This
is a minimal, targeted change — no new dependencies, no behavioral
changes to signal handling during task execution.
2026-04-23 16:57:48 -04:00
Craigory Coppola f21ace324c fix(core): allow --target/-t and -p flags for nx run with colon targets (#35394)
## Current Behavior

`nx run -p my-project -t test:unit` crashes with a `TypeError` instead
of running the target:

```
NX   parsedArgs[PROJECT_TARGET_CONFIG]?.lastIndexOf is not a function

TypeError: parsedArgs[PROJECT_TARGET_CONFIG]?.lastIndexOf is not a function
    at parseRunOneOptions (.../nx/src/command-line/run/run-one.js:105:44)
```

Two bugs are combining here:

1. `--target` / `-t` is never registered as a yargs option on the `run`
command (`withRunOneOptions` only declares `--project` and `--help`), so
the flag silently falls through into the overrides array and the target
value is lost.
2. With no positional value provided but flags present, yargs assigns
boolean `true` to the `project:target:configuration` positional that the
`run [project][:target][:configuration] [_..]` signature declares.
`parseRunOneOptions` then calls `.lastIndexOf(':')` on `true` and
crashes.

Workaround today is to escape the colon: `nx run my-project:test\:unit`.
That works but is non-obvious, and the flag-based form is what most
users reach for first.

## Expected Behavior

`nx run -p my-project -t test:unit` runs the `test:unit` target on
`my-project`. Escaped and long-form invocations continue to work.

Changes:

- Register `--target` / `-t` as a real yargs option in
`withRunOneOptions`, and add `-p` as an alias for `--project`.
- Guard the positional check in `parseRunOneOptions` with `typeof
parsedArgs[PROJECT_TARGET_CONFIG] === 'string'` so a non-string value
can never crash `.lastIndexOf`.
- Add unit tests for the flag-based invocation (short, long, and `=`
forms) and for the boolean-positional guard. Update `compareArgs` in
`command-object.spec.ts` to strip the new `p`/`t` alias keys when
comparing infix vs. `run` invocations.

Verified end-to-end against a reproduction workspace: `nx run -p
my-project -t test:unit` now runs successfully.

## Related Issue(s)

Fixes #35098
2026-04-23 14:44:04 -04:00
Jack Hsu 2c4a2eb422 chore(misc): richer telemetry for init and connect commands (#35389)
## Current Behavior

`nx init` error telemetry is largely opaque: ~22% of starts land in a
bare `Command failed: npm install` bucket, and ~5% record an empty
`errorMessage`. We can't tell what's actually going wrong. `nx connect`
has no start/error events at all — failures (missing remote, auth,
network) go untracked.

## Expected Behavior

Telemetry-only change. Child-process calls in init pipe stderr so the
captured output reaches the error payload; error events now include
`errorName` (from Node `e.code` or an extracted `E…`/`ERR_…` token like
`E404`, `ERESOLVE`, `EINTEGRITY`, `ERR_PNPM_*`) and the same env context
(`nodeVersion`, `os`, `packageManager`, `isCI`, `aiAgent`) as start
events. `toErrorString` fixes the empty-message bucket. `nx connect`
gains proper start/complete/error events.

No behavioral fixes — once the enriched data comes in we'll prioritize
real fixes by actual failure distribution.

## Related Issue(s)

Fixes NXC-4262

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
2026-04-23 13:17:05 -04:00
Jason Jean 163ed4bcd6 chore(repo): update nx to 22.7.0-rc.1 (#35409)
Updating Nx from 22.7.0-rc.0 to 22.7.0-rc.1
2026-04-23 12:02:13 -04:00
Leosvel Pérez Espinosa 7615f6a93e chore(nx-dev): declare next:build manifests as sitemap inputs (#35380)
## Current Behavior

The `nx-dev:sitemap` task invokes `next-sitemap`, whose `ManifestParser`
reads four manifests from the `nx-dev:next:build` output
(`build-manifest.json`, `export-marker.json`, `prerender-manifest.json`,
`routes-manifest.json` under `.next/`). None of these are declared as
inputs on `sitemap`, so the reads show up as sandbox violations and the
sitemap cache key doesn't change when those manifests change.

## Expected Behavior

The four manifests are declared inputs via a single
`dependentTasksOutputFiles` entry, narrowly scoped to
`**/.next/{build-manifest,export-marker,prerender-manifest,routes-manifest}.json`
so unrelated `.next/` artifacts don't invalidate the sitemap cache. `nx
show target inputs nx-dev:sitemap --check` confirms all four paths
resolve as declared inputs.
2026-04-23 11:48:34 -04:00
Jack Hsu de266bfd93 fix(js): add npm workspace support to prune-lockfile executor (#35383)
## Current Behavior

The `@nx/js:prune-lockfile` executor only identifies workspace module
dependencies when the `package.json` version string starts with
`workspace:`, `file:`, or `link:`. npm workspaces reference sibling
packages using plain semver (e.g. `"@repo/schemas": "0.0.1"` or
`"@repo/schemas": "*"`), so those dependencies are never rewritten to
point at `workspace_modules/` and pruning fails for npm-based
workspaces.

## Expected Behavior

Workspace module dependencies are correctly identified and rewritten to
`file:./workspace_modules/<pkg>` regardless of package manager,
including npm with plain-semver versions.

The fix pulls the set of workspace packages from the project graph via
`getWorkspacePackagesFromGraph` and treats any dependency whose name
matches a workspace package as a workspace module, in addition to the
existing protocol-prefix checks.

A regression e2e test covers the npm plain-semver case (`"*"`): it
asserts the pruned `package.json` has its dependency rewritten to
`file:./workspace_modules/@scope/nodelib`. The existing npm test case
used `file:../<lib>` which matched the `file:` prefix check and would've
passed even without the fix, so it didn't actually cover the reported
scenario.

## Related Issue(s)

Fixes #33523
2026-04-23 11:42:06 -04:00
Juri Strumpflohner cde6e59ec7 fix(nx-dev): remove broken header links (podcast) (#35410)
## Current Behavior

The Resources dropdown in the astro-docs header contained links
returning 404 on nx.dev:

- `/podcast`
- `/resources-library?*` -> `/resources?...`

Additionally, `/webinar` redirected to `/webinars` (canonical).

## Expected Behavior

- Podcasts entry removed (page no longer exists).
- Books / Case Studies / Whitepapers repointed to the new
`/resources?filterBy=...` URLs.
- Webinars points directly to `/webinars` (no redirect).

All 12 Resources dropdown links verified returning 200. Books link
clicked through from the local astro-docs dropdown and confirmed to load
the Resources Library page.
2026-04-23 17:36:53 +02:00
Jason Jean 0c5a5b64eb chore(repo): update nx to 22.7.0-rc.0 (#35400)
Updating Nx from 22.7.0-beta.17 to 22.7.0-rc.0
2026-04-23 11:06:59 -04:00
Leosvel Pérez Espinosa 8745774d56 chore(misc): ignore generated banner.json in astro-docs lint (#35404)
## Current Behavior

The project's eslint config ignores common generated directories
(`dist/`, `.astro/`, `.netlify/`, ...) but not
`astro-docs/src/content/banner.json`. That file is a build artifact
produced at prebuild time by `astro-docs:prebuild-banner` (fetches
banner config from a remote URL and writes it to disk) and is
`.gitignore`d. The root eslint config registers `jsonc-eslint-parser`
for `**/*.json`, so `eslint .` walks into this generated file and parses
it — wasted work, and its content is irrelevant to lint correctness.

As a side effect, the undeclared read also shows up as a sandbox
violation for `astro-docs:lint` because Nx's `{projectRoot}/**/*` input
expansion (correctly) excludes gitignored files.

## Expected Behavior

`astro-docs/eslint.config.mjs` excludes the generated banner file
alongside the other build-artifact paths it already ignores, so eslint
no longer reads it. The sandbox violation disappears as a consequence.
2026-04-23 09:33:13 -04:00
Craigory Coppola 82e7afe455 feat(js): support nx.sync.ignoredDependencies in typescript-sync (#35401)
## Current Behavior

`@nx/js:typescript-sync` always materializes every project-graph edge of
a project as a TypeScript project reference in the corresponding runtime
tsconfig. The existing `nx.sync.ignoredReferences` opt-out keeps
user-authored reference paths from being pruned, but there is no way to
tell the generator "don't add a reference for this dependency in the
first place."

That becomes a problem when the project graph contains intentional
cycles — for example when `@nx/workspace` declares its lazy-loaded
plugin peers (`@nx/js`, `@nx/angular`, etc.) as optional peers, and
those same plugins depend back on `@nx/workspace`. Materializing both
edges as TS project references produces `TS6202: Project references may
not form a circular graph`. The only workaround today is
`implicitDependencies: ["!name", …]` in `project.json`, which removes
the edge from the project graph entirely and hides it from every other
consumer (dependency tooling, graph visualizations, supply-chain
audits).

## Expected Behavior

Tsconfig files now accept an `nx.sync.ignoredDependencies: string[]`
field (sibling of the existing `nx.sync.ignoredReferences`). When the
sync generator processes that tsconfig, any project-graph dependency
whose project name is in the set is skipped — no new reference is added
for it, and any existing reference for that dependency is pruned as
stale.

This lets a workspace keep real, cyclic project-graph edges (so the
package.json peer relationships stay visible) while opting the affected
tsconfig out of materializing the cycle into project references. The
task graph is kept acyclic separately via explicit `dependsOn` entries.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 08:28:55 -04:00
Leosvel Pérez Espinosa 7af6da1063 chore(nx-dev): declare sitemap on build and deploy-build outputs (#35402)
## Current Behavior

`nx-dev:build` is a no-op aggregator over `sitemap` and `copy-redirects`
but only declares `{projectRoot}/.next` as its output. Tasks that depend
on `nx-dev:build` and use `dependentTasksOutputFiles` — e.g.
`astro-docs:validate-links`, which reads
`nx-dev/nx-dev/public/sitemap-0.xml` to cross-check links — never pick
up the sitemap as a declared input. Under sandboxing this surfaces as an
unexpected read, and it also means the sitemap doesn't participate in
the consumer's input hash.

Separately, `nx-dev:next:build` lists `public/sitemap*.xml` as an output
even though `next build` never writes sitemaps (the `sitemap` target
does). At best the glob captures nothing; at worst, on a re-run it
snapshots stale files from a previous build into `next:build`'s cache.

## Expected Behavior

`nx-dev:build` and `nx-dev:deploy-build` declare
`{projectRoot}/public/sitemap*.xml` alongside `{projectRoot}/.next`,
matching what their dependsOn chain actually produces. This mirrors the
existing `nx:noop` atomizer pattern used by `@nx/playwright` and
`@nx/cypress`, where the rollup target declares the superset of outputs
its children produce.

`nx:next:build` no longer claims an output it doesn't write.

Verified: `nx show target inputs astro-docs:validate-links --check
nx-dev/nx-dev/public/sitemap-0.xml nx-dev/nx-dev/public/sitemap.xml` now
reports both as inputs.
2026-04-23 08:14:43 -04:00
Craigory Coppola 0ca6e3fbd3 chore(testing): resolve @nx/dotnet to source in jest tests (#35399)
## Current Behavior

Running `nx test dotnet` resolves `@nx/dotnet/*` specifiers to compiled
`dist/*.js` files instead of TypeScript source.

`scripts/patched-jest-resolver.js` maintains a `workspacePackages`
allowlist of `@nx/*` packages that should route to their TS source
during tests. `@nx/dotnet` is missing from that list, so the resolver
falls through to `enhanced-resolve`, which honors
`packages/dotnet/package.json#exports` — and those entries all point at
`./dist/*.js`.

Every other `@nx/*` plugin in the repo is on the allowlist, so this is
specific to `@nx/dotnet`.

## Expected Behavior

`nx test dotnet` resolves `@nx/dotnet/*` imports to TypeScript source
files under `packages/dotnet/src/` like every other workspace package,
so tests exercise the current source and don't require a prior build.

## Related Issue(s)

<!-- None; internal dev loop fix surfaced while running the dotnet test
suite. -->
2026-04-22 23:13:36 -04:00
Craigory Coppola ae43589b2e chore(core): remove debug tmp writes from new generator spec (#35397)
The generate-workspace-files spec wrote each rendered README to
`__dirname/tmp/<preset>-<nxCloud>/README.md` via raw fs, landing inside
the source tree and triggering Nx sandbox violations during `nx test
workspace`. The toMatchSnapshot assertion that followed was always the
real check; the filesystem writes were leftover debug scaffolding from
the bulk README regeneration in #27038.

Also drops the now-unused `fs` and `path` imports.
2026-04-22 22:53:23 -04:00
Raashish Aggarwal 4229697a02 fix(js): avoid double-prefixing node executor output paths (#35050)
## Summary
- avoid prepending `outputPath` twice when `@nx/js:node` derives the
runnable file for `@nx/js:tsc` and `@nx/js:swc`
- keep the existing source-relative behavior for normal `src/...`
entries while preserving nested paths already inside the output
directory
- respect a configured `rootDir` on the build target so the node
executor does not add an extra path segment that tsc/swc stripped from
the output
- reuse the canonical `normalizePath` and
`getRelativeDirectoryToProjectRoot` utilities instead of re-declaring
them
- add focused coverage for the `dist/main.js`, source-entry,
nested-output, and `rootDir` cases

Fixes #35044
Fixes #33577

## Validation
- `corepack pnpm exec prettier --check
packages/js/src/executors/node/node.impl.ts
packages/js/src/executors/node/lib/output-file.ts
packages/js/src/executors/node/lib/output-file.spec.ts`
- `corepack pnpm exec eslint packages/js/src/executors/node/node.impl.ts
packages/js/src/executors/node/lib/output-file.ts
packages/js/src/executors/node/lib/output-file.spec.ts`
- `pnpm nx test js --testPathPatterns=output-file` — all 4 cases pass

---------

Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
2026-04-22 18:52:27 -04:00
Craigory Coppola 331ebce75b chore(repo): stop update-package-json spec from writing to disk (#35395)
## Current Behavior

Running `nx run rollup:test` produces three unexpected writes to the
workspace root that are not declared as outputs:

- `dist/index.js/index.cjs.default.js`
- `dist/index.js/index.cjs.mjs`
- `dist/index.js/package.json`

These show up as sandbox violations because the spec file at
`packages/rollup/src/plugins/package-json/update-package-json.spec.ts`
is, in reality, writing to the filesystem during test runs:

1. `jest.spyOn(utils, 'writeJsonFile')` is used without
`.mockImplementation(...)`. `spyOn` wraps the function to record calls
but does not replace its behavior, so the real `writeJsonFile` runs and
writes `package.json`.
2. `update-package-json.ts` also calls `fs.writeFileSync` directly (for
the `.cjs.mjs` / `.cjs.default.js` CJS-interop shims), which the spec
never mocked at all.

Every run pollutes the workspace root with a `dist/index.js/` directory.

## Expected Behavior

Tests verify call arguments via the spies without touching the real
filesystem. No `dist/index.js/` directory is created, and the three
sandbox violations for `rollup:test` go away.

- All 11 `writeJsonFile` spies now use `.mockImplementation(() =>
undefined)`.
- A `beforeEach` / `afterEach` adds a `jest.spyOn(fs,
'writeFileSync').mockImplementation(() => undefined)` using
`require('fs')` so the spy lands on the real fs module (using `import *
as fs from 'fs'` would go through `esModuleInterop`'s `__importStar`
copy and miss the call site in `update-package-json.ts`).
- Assertions continue to work — `toHaveBeenCalledWith(...)` tracks calls
on spies with or without a mock implementation.

Verified: `nx test rollup --testPathPatterns=update-package-json.spec` →
11/11 passing, and no `dist/index.js/` is created during the run.

## Related Issue(s)

N/A — caught via the sandbox-violation report for `rollup:test`.
2026-04-22 18:49:39 -04:00
Jack Hsu 3027707ede fix(release): apply preid to dependent patch bumps (#35381)
## Current Behavior
When running \`nx release version --preid rc\` with a project filter,
projects with a dependent patch bump (from another project being bumped)
do not have the preid applied. semver.inc('1.0.0', 'patch', 'rc')
returns '1.0.1' because semver silently ignores preid for non-prerelease
specifiers.

## Expected Behavior
The preid is applied to dependent patch bumps, so dependents get
'0.0.2-rc.0' instead of '0.0.2'. The fix adds `applyPreidToBumpType`
which converts patch to prepatch, minor to preminor, and major to
premajor when a preid is set.

## Related Issue(s)
Fixes #33488
2026-04-22 18:28:03 -04:00
Jason Jean 0e3b0988e4 chore(misc): declare lazy-loaded @nx/* packages as optional peers for storybook, web, vitest, and vue (#35393)
## Current Behavior

Several `ensurePackage('@nx/X', nxVersion)` and `ensurePackage<typeof
import('@nx/X')>(...)` references are invisible to Nx's project-graph
source analysis, so the corresponding workspace edges are missing today:

-
`packages/storybook/src/generators/configuration/lib/util-functions.ts`
lazy-loads `@nx/web`
- `packages/web/src/generators/application/application.ts` lazy-loads
`@nx/cypress`, `@nx/eslint`, `@nx/jest`, `@nx/playwright`, `@nx/vite`,
`@nx/webpack`
- `packages/vitest/src/utils/ignore-vitest-temp-files.ts` lazy-loads
`@nx/eslint`
- `packages/vue/src/**` lazy-loads `@nx/cypress`, `@nx/playwright`,
`@nx/rsbuild`, `@nx/storybook`

This means tasks in those packages read files from their lazy-loaded
dependencies at module-load time without declaring them as inputs — the
motivation behind the sandbox-violation work in #35377.

## Expected Behavior

Declares each lazy-loaded package as an optional `peerDependency` with
`peerDependenciesMeta.*.optional: true`.
`explicit-package-json-dependencies` walks `peerDependencies` alongside
`dependencies`, materializing the edges in the graph. `optional: true`
keeps them off the user's install surface — package managers don't
auto-install optional peers and don't warn when they're missing.

`@nx/vitest` is already declared as a `devDependency` of `@nx/web`, so
the `web → vitest` edge is already present; no change needed there.

Follows the pattern established in #35377 (`@nx/eslint → @nx/jest`).

### Scope

Narrower subset of #35392, scoped to `@nx/storybook`, `@nx/web`,
`@nx/vitest`, and `@nx/vue`. Those four close the most commonly hit
transitive lazy-load chains (`storybook → web → jest`, `vue → storybook
→ web → jest`, `web → vitest → eslint`, etc.) without needing the
`implicitDependencies: ["!name", ...]` cycle negations that
`@nx/workspace` and `@nx/js` require in #35392.

Note: `@nx/vitest` is not covered by #35392 at all, so this PR adds at
least one edge that isn't in the broader PR.

### Verification

- Regenerated project graph locally — all new edges appear as `static`
type:
  - `storybook → web`
  - `web → {cypress, eslint, jest, playwright, vite, webpack}`
  - `vitest → eslint`
  - `vue → {cypress, playwright, rsbuild, storybook}`
- Full cycle scan: **0 cycles introduced**.
- `nx prepush` passes cleanly.

## Related Issue(s)

<!-- No open issue; follow-up to #35377 and narrower alternative to
#35392 -->
2026-04-22 22:12:46 +00:00
Jack Hsu 7a8d23baf3 chore(core): lock in Nx Cloud prompt A/B winner for init and CNW (#35390)
## Current Behavior

`create-nx-workspace` randomly serves one of three Nx Cloud prompt copy
variants during the template flow; `nx init` pins a baseline copy that
predates the test.

A/B results (NXC-4336):

- Variant 0 (baseline): `Enable remote caching to speed up builds with
Nx Cloud?` — 15.4% yes / 29.2% never
- Variant 1: `Never rebuild the same code twice — enable Nx Cloud?` —
13.4% yes / 28.4% never
- **Variant 2: `Speed up GitHub Actions, GitLab CI, and more with Nx
Cloud?` — 17.8% yes / 22.4% never**

## Expected Behavior

Both `create-nx-workspace` (`setupNxCloudV2`) and `nx init`
(`setupNxCloud`) serve the winning variant. Footer is reworded to lead
with the free-tier messaging:

> Free for small teams. Remote caching and task distribution. 2-minute
setup: https://nx.dev/nx-cloud

The CNW `setupNxCloudV2` array collapses from 3 variants to 1;
`PromptMessages.getPrompt` already falls back to index 0 when
`flowVariant >= length`, so existing flow-variant plumbing keeps
working. Spec updated to assert the locked-in code for all flow variants
(0/1/2) and docs generation.

## Related Issue(s)

Fixes NXC-4336
2026-04-22 17:45:06 -04:00
Leosvel Pérez Espinosa a97bd37198 chore(testing): stub plugin imports in devkit specs to avoid cross-project reads (#35374)
## Current Behavior

Three devkit specs exercise helpers that do `await
import('@nx/<plugin>/plugin')` at runtime (`findPluginForConfigFile`
when a registration has `include`/`exclude`, and
`addE2eCiTargetDefaults` unconditionally for every e2e plugin
registration). Combined with the custom jest resolver in
`scripts/patched-jest-resolver.js` — which maps `@nx/<pkg>` subpaths to
workspace source — the dynamic imports pull the real plugin source plus
everything transitively re-exported by `@nx/js` and `@nx/vite` into the
jest process.

Concretely, `devkit:test` ends up reading ~49 files across
`packages/js/src/**` and `packages/vite/**` that are not declared (and
cannot be declared without creating a project-graph cycle since `@nx/js`
and `@nx/vite` both depend on `@nx/devkit`). The sandbox flags all 49 as
undeclared-read violations.

## Expected Behavior

None of these specs aim to validate the real plugin's behavior. They
exercise devkit's own logic — how a registration is matched to a config
file, how target defaults are written into `nx.json`, and how e2e web
server info is resolved from a registered plugin — treating
`@nx/<plugin>/plugin` as an opaque reference. The plugin's
`createNodesV2[0]` glob is used by the devkit helpers only as a pattern
pre-filter against conventional config filenames (`vite.config.ts`,
`cypress.config.ts`); what the real plugin does beyond that is outside
the scope of these tests.

Stubbing the three plugin modules with `jest.mock(..., { virtual: true
})` that exposes just the real plugin's glob pattern:

- Preserves the minimal contract the devkit helpers rely on (the module
resolves, and its glob matches the conventional config filenames used in
the tests).
- Drops the incidental loading of the real plugin and its entire
transitive graph — removing all 49 reported sandbox violations.
- Eliminates an accidental coupling to the pinned `@nx/cypress` version
currently pulled from `node_modules`, which also transitively requires
`@nx/js` workspace source via the custom resolver.

Changes are test-only; no production code is touched.
2026-04-22 16:52:47 -04:00
Craigory Coppola 77f8e1e842 chore(repo): update workspace-plugin to hit installed versions of packages (#35262)
## Current Behavior
workspace-plugin:test has violations

## Expected Behavior
<img width="650" height="399" alt="image"
src="https://github.com/user-attachments/assets/d77c720f-dee9-4e83-8b2f-2823da4990e0"
/>

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-22 20:31:55 +00:00
Jason Jean 65a17696e0 chore(repo): use mise exec when running commands in cloned repos (#35391)
## Current Behavior

`update-repos` spawns child processes to run `pnpm install`, `nx
migrate`, etc. inside each cloned target repo. The child inherits the
launching shell's `PATH`, which mise activation populated with hardcoded
version-specific install paths (e.g. `/.../installs/node/24.11.0/bin`).
Mise activation is a shell hook on `cd` — it does not re-fire when a
child process changes its own `cwd`. As a result, every cloned repo's
commands run against the outer shell's pinned tool versions rather than
the versions in the cloned repo's own `mise.toml`.

## Expected Behavior

Each cloned repo's commands honor the tool versions pinned in that
repo's `mise.toml`.

`execWithOutput` now prefixes every spawned command with `mise exec --`
(except commands that already start with `mise`, so `mise trust` / `mise
install` aren't wrapped in themselves). `mise exec` re-reads `mise.toml`
from the `cwd` at invocation time and activates the correct tool
versions for that repo.

## Related Issue(s)

Fixes #
2026-04-22 16:03:48 -04:00
Caleb Ukle 3d9bc7a2b3 docs(repo): add conformance rule for NX_* env var documentation (#35353)
## Current Behavior

`NX_*` env vars can be added to Nx source without being documented in
`astro-docs/src/content/docs/reference/environment-variables.mdoc`.
Nothing catches the drift.

## Expected Behavior

Adds a conformance rule (`env-vars-documented`) that fails when an
`NX_*` var is read in source but missing from the docs. Covers TS/JS
`process.env.NX_*` and Rust `env::var` / `env!` patterns, skipping tests
and fixtures. An `ignore` list in `nx.json` handles internal markers not
meant to be documented.

Also documents the user-facing vars the first run surfaced (self-hosted
cache, provenance, plugin isolation, Nx Cloud timeouts, etc.) and marks
`NX_CLOUD_AUTH_TOKEN` and `NX_CLOUD_DISTRIBUTED_EXECUTION_AGENT_COUNT`
as deprecated.

## Related Issue(s)

N/A — internal tooling improvement.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: barbados-clemens <barbados-clemens@users.noreply.github.com>
2026-04-22 18:28:06 +00:00
Craigory Coppola 6c159a463d fix(core): don't spread plugin name into set constructor (#35385)
This pull request contains a small fix in the `build-project-graph.ts`
file to correct the way `Set` is initialized for tracking in-progress
plugins. The change removes the unnecessary spread operator when
creating the `Set` from plugin names.

* Fixed `inProgressPlugins` initialization in both
`updateProjectGraphWithPlugins` and `applyProjectMetadata` functions by
removing the spread operator, ensuring plugin names are correctly added
to the `Set`.
[[1]](diffhunk://#diff-14bd07dde50d74ec748f1bce0f9d8cf05504e36c83928d1ad077dcff088b6822L323-R323)
[[2]](diffhunk://#diff-14bd07dde50d74ec748f1bce0f9d8cf05504e36c83928d1ad077dcff088b6822L441-R441)
2026-04-22 18:01:42 +00:00
Leosvel Pérez Espinosa 2f5e5b1392 chore(linter): declare @nx/jest as optional peer dependency (#35377)
## Current Behavior


`packages/eslint/src/generators/workspace-rules-project/workspace-rules-project.ts:37`
calls `ensurePackage<typeof import('@nx/jest')>('@nx/jest', nxVersion)`.
This runtime-optional reference (type-only import plus a string
argument) is invisible to Nx's static project-graph analysis, so the
graph has no `@nx/eslint → @nx/jest` edge today.

Combined with the custom jest resolver in
`scripts/patched-jest-resolver.js` — which maps `@nx/jest` subpaths to
workspace source — the `eslint:test` task ends up reading
`packages/jest/index.ts` and 19 files under `packages/jest/src/**` at
module-load time. Those reads are undeclared inputs and are flagged as
sandbox violations by the staging sandbox reports.

## Expected Behavior

Declaring `@nx/jest` as an **optional** `peerDependency` of `@nx/eslint`
materializes the missing edge in Nx's project graph (the graph reader in
`explicit-package-json-dependencies.ts` walks `peerDependencies`
alongside `dependencies`). The inferred test-task input `^production` is
transitive, so `packages/jest/**` production source is then covered as
declared inputs of `eslint:test` and the 20 violations disappear.

`peerDependenciesMeta.@nx/jest.optional: true` keeps `@nx/jest` off the
user's install surface — package managers don't auto-install it and
don't warn about missing optional peers. Users who invoke the
`workspace-rules-project` generator with jest scaffolding still get
`@nx/jest` via the existing `ensurePackage` runtime install path,
unchanged.
2026-04-22 12:23:57 -04:00
Jack Hsu 2a3b0e5bc7 fix(react): support Vite 8 for React Router apps (#35365)
## Current Behavior

`@react-router/dev` peer dep tops out at Vite 7, so the React app
generator forces Vite 7 when `--use-react-router` is passed, and the
react-router typecheck e2e test is skipped in CI.

## Expected Behavior

`@react-router/dev` 7.14.2 expands its peer dep to include Vite 8
(`^5.1.0 || ^6.0.0 || ^7.0.0 || ^8.0.0`), so:

- `useViteV7: true` is no longer forced when generating a React Router
app
- The `useViteV7` field is removed from
`ViteConfigurationGeneratorSchema` (was only added for this workaround
and was never wired into the configuration generator body)
- The react-router typecheck e2e test is un-skipped
- `reactRouterVersion` is bumped to `^7.14.2`
- A new `22.7.0` packageJsonUpdate migrates `@react-router/*` packages
to `7.14.2` in existing workspaces

## Related Issue(s)

Fixes NXC-4182
2026-04-22 11:25:23 -04:00
Jack Hsu 7b42332642 fix(nextjs): add semver to required packages in update-package-json (#35384)
## Current Behavior
When using @nx/next:build with Yarn PnP, the generated
.nx-helpers/with-nx.js requires semver at runtime, but semver is not
included in the generated package.json. This causes the application to
fail when starting in a deployed environment with the error: "Required
package: semver, Required by: /app/.nx-helpers/with-nx.js"

## Expected Behavior
The generated package.json should include semver as a dependency so that
.nx-helpers/with-nx.js can resolve it at runtime in all package manager
environments, including Yarn PnP.

## Related Issue(s)
Fixes #34095
2026-04-22 11:20:07 -04:00
Caleb Ukle 7fae6ba000 docs(nx-cloud): document no-output-timeout launch template syntax (#35382)
allow setting a timeout for nx agents based on the last time output was
emitted.

By default this is 10m. this is set per launch template but can be
overridden per step via env var: `NX_NO_OUTPUT_TIMEOUT`

fixes DOC-488
2026-04-22 15:11:13 +00:00
Jason Jean 5e8a3bc5a1 chore(repo): update nx to 22.7.0-beta.17 (#35379)
Updating Nx from 22.7.0-beta.16 to 22.7.0-beta.17
2026-04-22 10:31:54 -04:00
Juri Strumpflohner fcbc20d5ed docs(nx-dev): correct sandbox mode availability to 22.6 (#35378)
<!-- 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-04-22 09:56:07 -04:00
Leosvel Pérez Espinosa 4bbd4b1adc chore(repo): migrate nx repo to eslint v9 flat config (#35359)
## Current Behavior

The nx repo uses the legacy eslintrc format (`.eslintrc.json`,
`.eslintignore`) with ESLint v8 and outdated plugin versions. Several of
those plugins ship code that crashes under ESLint v9 (`context.getScope`
removed, etc.), and the `@nx/eslint` / `@nx/eslint-plugin` sources still
reference APIs removed in v9 (`Linter.defineParser`,
`Linter.defineRule`, classic `Linter.Config` shape).

## Expected Behavior

Every project in the repo uses an `eslint.config.mjs` flat config, the
ESLint ecosystem is on the latest v9-compatible versions, and
`@nx/eslint` / `@nx/eslint-plugin` sources compile and test cleanly
against the v9 type split.

### Main changes

- Replaced every `.eslintrc.json` / `.eslintignore` with an
`eslint.config.mjs`.
- Rewrote the root `eslint.config.mjs`:
- Exports a named `baseConfig` that leaf configs import, plus a default
export that opts out of linting (`**/*` ignored) so the root acts as a
shared base and isn't lint-checked directly.
  - Drops `FlatCompat` in favor of native plugin imports.
- Exports shared `reactHooksV7Off`, `e2eTestOnlyIgnores`, and a
`storybookConfigs` wrapper that filters v10's preset down to
`storybook/*` rules (the preset references foreign rules like
`import-x/*` that we don't register).
- Cleaned up leaf configs emitted by the generator: deduplicated file
entries, merged split `package.json` blocks, fixed the jsonc parser
namespace import after v3 dropped its default export.
- Scoped the 15 e2e projects to `*.test.ts` files only via a shared
export.
- Bumped the ESLint ecosystem to the latest v9-compatible versions:
`eslint@^9.39.4`, `@eslint/js@^9.39.4`, `@typescript-eslint/*@^8.58.2`,
`eslint-plugin-storybook@^10.3.5`, `eslint-plugin-react-hooks@^7.1.0`,
`eslint-plugin-jsx-a11y@^6.10.2`, `eslint-plugin-import@^2.32.0`,
`eslint-plugin-playwright@^2.10.1`, `eslint-plugin-cypress@^6.3.1`,
`eslint-plugin-jest@^29.15.2`, `toml-eslint-parser@^1.0.3`,
`jsonc-eslint-parser@^3.1.0`, `angular-eslint@^21.3.1`. Dropped
`@eslint/eslintrc`, `@types/eslint`, `@types/eslint__js`.
- Aligned `@nx/eslint` source with the v9 type split: `Linter.Config` →
`Linter.LegacyConfig`, `ESLint.Options` → `ESLint.LegacyOptions`
wherever the underlying shape is eslintrc.
- Rewrote `@nx/eslint-plugin` rule specs (`dependency-checks`,
`enforce-module-boundaries`) as flat-config inline tests since
`Linter.defineParser`/`defineRule` were removed in v9.
- Aligned react/angular/expo/react-native add-linting helpers with the
v9 type split.
- Migrated `tools/eslint-rules` specs from `TSESLint.RuleTester` (legacy
config, rejected by v9's flat Linter) to
`@typescript-eslint/rule-tester`; added `isolatedModules: true` so
ts-jest resolves the new types under `module: node16`.
- Downgraded the new react-hooks v7 rules to `off` via a shared export
so the migration doesn't require rewriting legacy code.
- Auto-fixed unused eslint-disable directives
(`linterOptions.reportUnusedDisableDirectives` defaults to warn in v9).
- Ignored `packages/workspace/**/__fixtures__/**` in lint and updated
the affected snapshot so the fixture matches what the jest generator
templates actually emit.

### Note on ESLint v10

This PR stays on ESLint v9. A few plugins we rely on
(`eslint-plugin-import`, `eslint-plugin-jsx-a11y`,
`eslint-plugin-react`) still don't declare v10 peer support. The jump to
v10 will happen in a follow-up PR once those plugins publish
v10-compatible releases.
2026-04-22 08:34:44 -04:00
Leosvel Pérez Espinosa 1507788f12 chore(repo): short-circuit isUsingTsSolutionSetup in unit tests (#35371)
## Current Behavior

`isUsingTsSolutionSetup()` (in both `@nx/js` and `@nx/workspace`) falls
back to `new FsTree(workspaceRoot, false)` when called without a tree,
reading the real repo's `tsconfig.json` / `tsconfig.base.json`. Many
unit tests indirectly invoke it (cypress-preset, playwright-preset,
plugin `createNodesV2`, executor `normalize`, etc.), which surfaces as
sandbox violations across ~13 test targets (angular, rspack, vite,
rollup, webpack, react, next, js, cypress, remix, workspace, nest,
node).

## Expected Behavior

Unit tests should never touch the real workspace FS. A global mock in
`scripts/unit-test-setup.js` short-circuits `isUsingTsSolutionSetup()`
when called without a tree, returning `true` to match the de-facto
behavior of hitting the real FS (the Nx repo is a TS solution workspace)
and preserve every test's existing expectations. Calls that pass an
explicit (virtual) tree still run the real implementation.

Two specs that deliberately simulate a non-TS-solution workspace (via
`node:fs` / `workspaceRoot` mocks) add a per-file override returning
`false` to keep expressing that intent:

- `packages/vite/src/plugins/plugin-vitest.spec.ts`
- `packages/rollup/src/plugins/with-nx/normalize-options.spec.ts`
2026-04-22 08:28:47 -04:00
Leosvel Pérez Espinosa a0b4bf899e chore(repo): remove polygraph plugin entry from claude settings (#35370)
## Current Behavior

`.claude/settings.json` enables `polygraph@nx-claude-plugins`, but the
`nx-claude-plugins` marketplace only ships the `nx` plugin. The
`polygraph` plugin lives in the separate `polygraph-plugins`
marketplace, which is not declared in `extraKnownMarketplaces` either —
so the entry refers to a plugin+marketplace pair that doesn't exist.
Every contributor picks up the broken id when they pull the repo.

The bad id was introduced in #34790, which moved to the new plugin but
left the marketplace name pointing at the old one.

## Expected Behavior

Polygraph is now installed globally via the session-start opt-in prompt,
so the repo-level `enabledPlugins` entry is no longer needed. Remove it
entirely rather than pointing it at a marketplace the settings file
doesn't declare.
2026-04-22 12:06:35 +00:00
mungodewar 8a942d9c6a fix(testing): include config file path in plugin hash calculation (#35346)
## Current Behavior

The createNodesV2 function in the nx/cypress plugin calculates the hash
based only on the output of the standard `calculateHashForCreateNodes`.
This means that when multiple Cypress configuration files exist within a
single application hash. As a result, only one set of inferred targets
is generated, and the additional configuration variants are effectively
ignored.

## Expected Behavior
The createNodesV2 function now takes the configuration file name into
the hash calculation, removing the limitation of a single config within
an application.

---------

Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
2026-04-22 08:02:04 +02:00
Jason Jean 4c11b62378 chore(misc): fix NPM audit by removing unused faro deps and stale patch (#35368)
## Current Behavior

The scheduled `NPM Audit` workflow is failing on `master` due to a
critical advisory
([GHSA-xq3m-2v4x-88gg](https://github.com/advisories/GHSA-xq3m-2v4x-88gg))
in `protobufjs@7.5.3`, pulled in transitively via:

```
@grafana/faro-web-sdk > @grafana/faro-core > @opentelemetry/otlp-transformer > protobufjs
```

Failing run: https://github.com/nrwl/nx/actions/runs/24753360724

Separately, the workspace carries a `@nx/jest@22.7.0-beta.12` patch even
though the workspace is on `22.7.0-beta.16`, and `allowUnusedPatches:
true` was set in `pnpm-workspace.yaml` to suppress the warning.

## Expected Behavior

- `NPM Audit` workflow passes.
- No stale patches, and unused patches fail loudly rather than being
silently allowed.

### Changes

1. **Remove `@grafana/faro-web-sdk` and `@grafana/faro-web-tracing`.** A
`git grep` confirms neither package is imported anywhere in the codebase
— they were listed in `package.json` but unused. Removing them drops the
transitive `protobufjs@7.5.3` entirely and clears the critical advisory
(audit verified locally).
2. **Delete the stale `@nx/jest@22.7.0-beta.12` patch and drop
`allowUnusedPatches: true`** from `pnpm-workspace.yaml` so future stale
patches surface immediately.

## Related Issue(s)

N/A (fixes failing scheduled CI workflow).
2026-04-22 02:56:30 +00:00
Craigory Coppola 362ff61f52 feat(core): add logging and progress message types to daemon (#35342)
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-22 01:25:21 +00:00
Jack Stevenson c19a8e82c0 fix(misc): allow create-nx-workspace . --no-interactive in empty directory (#35281)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-21 20:31:43 -04:00
Craigory Coppola ac7afa396d fix(core): improve native TypeScript type definitions (#35251)
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-21 20:31:31 -04:00
Jack Hsu a459f45097 feat(nx-dev): add nx-blog sitemap to root sitemap index (#35363)
Add sitemap for blog:
- https://deploy-preview-35363--nx-dev.netlify.app/sitemap.xml
- https://deploy-preview-35363--nx-dev.netlify.app/sitemap-2.xml
(rewritten to blog)
## Current Behavior

The root sitemap.xml index references only /sitemap-1.xml (Framer via
edge-function proxy) and /docs/sitemap-index.xml (astro-docs). Blog
posts published by nx-blog (BLOG_URL) are not advertised to crawlers
through the root nx.dev sitemap.

## Expected Behavior

The root sitemap index additionally references /sitemap-2.xml, which is
served by a consolidated `additional-sitemaps.ts` edge function that
proxies the per-source sitemaps:

  /sitemap-1.xml -> <NEXT_PUBLIC_FRAMER_URL>/sitemap.xml
  /sitemap-2.xml -> <BLOG_URL>/blog/sitemap.xml

URLs in the upstream XML are rewritten from the source origin to nx.dev.
The previous per-source edge functions (framer-sitemap.ts,
blog-sitemap.ts) are replaced by the single additional-sitemaps.ts to
match the "additionalSitemaps" language used in the Next.js sitemap
config.

## Related Issue(s)

Fixes DOC-486

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-04-21 16:08:30 -04:00
Jason Jean b11118bd21 fix(maven): log analyzer startup under verbose instead of stdout (#35361)
## Current Behavior

The Maven analyzer prints `[Maven Analyzer] Starting analysis with
options: { verbose: false }` unconditionally to stdout. This corrupts
commands that expect clean stdout output, such as `nx show projects
--json`, where consumers end up parsing:

```
[Maven Analyzer] Starting analysis with options: { verbose: false }
["@somnex/krista","@somnex/somnex"]
```

## Expected Behavior

The startup message should only be shown in verbose mode, matching the
rest of the analyzer diagnostics in this file that already use
`logger.verbose`. Normal `nx show projects --json` output stays clean.

## Related Issue(s)

Fixes NXC-4253
2026-04-21 15:35:00 -04:00
Craigory Coppola 0b11c5081c fix(core): create process report on fatal error in .nx/workspace-data (#35193)
## Current Behavior
On process segfault there is no way at all to diagnose where it is
coming from

## Expected Behavior
On segfault we write a report file that has information in it and log
that info on the next nx command if one happens to be ran.

Because segfaults exit the node process immediately, we do not have
another way of handling them so we do have to rely on a follow up
command to surface them. Unfortunately the errors are pretty rare so the
likelihood of this helping isn't awesome, but it could give us some
info.

## AI summary

This pull request introduces a new utility for surfacing Node.js fatal
error diagnostic reports and refactors how project graph data is
accessed in the format command. The most significant changes are the
addition of the fatal error reporting mechanism and the simplification
of how project graph data is loaded, which should improve
maintainability and reliability.

**Fatal error reporting utility:**

* Added a new `surfaceFatalErrorReports` function in
`report-on-fatal-error.ts` that scans for Node.js fatal error reports in
the workspace data directory, surfaces them in the console, and emits
GitHub Actions workflow annotations and step summaries if running in CI.
Reports are renamed after processing to prevent duplication.

**Format command refactoring:**

* Refactored `format.ts` to remove unused imports (`FileData`,
`allFileData`) and to only load the project graph when necessary, which
reduces unnecessary file system and computation overhead.
[[1]](diffhunk://#diff-ff26ab1495d5bd9854f0377c1aad51b1a749fa0ea82d2c66d363c06d417ebc67L6-R6)
[[2]](diffhunk://#diff-ff26ab1495d5bd9854f0377c1aad51b1a749fa0ea82d2c66d363c06d417ebc67L23)
* Updated the logic in `getPatterns` and related functions to lazily
load the project graph and to remove the need for passing all workspace
files, simplifying function signatures and improving performance.
[[1]](diffhunk://#diff-ff26ab1495d5bd9854f0377c1aad51b1a749fa0ea82d2c66d363c06d417ebc67L104)
[[2]](diffhunk://#diff-ff26ab1495d5bd9854f0377c1aad51b1a749fa0ea82d2c66d363c06d417ebc67R111)
[[3]](diffhunk://#diff-ff26ab1495d5bd9854f0377c1aad51b1a749fa0ea82d2c66d363c06d417ebc67L135-R138)
[[4]](diffhunk://#diff-ff26ab1495d5bd9854f0377c1aad51b1a749fa0ea82d2c66d363c06d417ebc67L155)

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-04-21 15:32:21 -04:00
Jason Jean 1ce33527da chore(repo): update nx to 22.7.0-beta.16 (#35360)
Updating Nx from 22.7.0-beta.15 to 22.7.0-beta.16
2026-04-21 18:08:46 +00:00
Leosvel Pérez Espinosa 9844f0d794 fix(js): resolve build output dir from globbed outputs in node executor (#35288)
## Current Behavior

The `@nx/js:node` executor fails when combined with the inferred
`@nx/js/typescript` build target — in two distinct ways:

1. **Script-to-run resolution**: fails with `Could not find
<project>/dist/**/*.{js,cjs,mjs,jsx,d.ts,d.cts,d.mts}{,.map}/main.js.
Make sure your build succeeded.` — the executor's `getFileToRun` was
appending `main.js` onto the glob output pattern.
2. **Buildable-dep import resolution**: when the node app imports a
buildable lib that also uses the inferred build target,
`require('@scope/my-lib')` throws `MODULE_NOT_FOUND` because
`calculateResolveMappings` passes the literal glob into `NX_MAPPINGS`,
which the CJS require override / ESM loader then tries to resolve.

Both regressed after #35041 narrowed the inferred build target's
`outputs[0]` from `{projectRoot}/dist` to
`{projectRoot}/dist/**/*.{js,...}{,.map}` (to prevent cross-OS cache
pollution).

## Expected Behavior

`outputs` entries are cache patterns and may legitimately contain globs.
The node executor now strips the glob portion back to the last path
separator before using the value as a directory, in both `getFileToRun`
and `calculateResolveMappings`. Handles `**`, `*`, `?`, character
classes, brace expansion, extglob, and Windows/POSIX separators.

## Related Issue(s)

Fixes #35198
Fixes #35301

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-21 09:32:32 -04:00
Leosvel Pérez Espinosa 4a376daeab chore(misc): declare .editorconfig as input for astro-docs:format (#35355)
## Current Behavior

The `astro-docs:format` target runs `prettier **/*.mdoc --check`.
Prettier has `--editorconfig` enabled by default and walks up from each
source file to read `.editorconfig`, deriving options like `endOfLine`,
`tabWidth`, `useTabs`, and `printWidth` from it. The target's declared
inputs do not include `.editorconfig`, so the task sandbox flags it as
an unexpected read and changes to `.editorconfig` do not invalidate the
cache even though they can change `--check` results.

## Expected Behavior

`.editorconfig` is declared as an input of the `astro-docs:format`
target so the sandbox recognizes the read and the cache key reflects
changes to the file.
2026-04-21 09:27:36 -04:00
Leosvel Pérez Espinosa 437b31defb fix(js): declare .d.cts/.d.mts as typecheck inputs and outputs (#35357)
## Current Behavior

The `@nx/js/typescript` plugin's inferred `typecheck` target only
declares `.d.ts`/`.d.ts.map` as outputs (in the `emitDeclarationOnly`
branch) and only `.d.ts` in the dependency/dependent-task input globs.
When a project has `.mts` or `.cts` sources, `tsc` emits
`.d.mts`/`.d.mts.map` (and `.d.cts`/`.d.cts.map`) declaration files that
fall outside the declared outputs, causing sandbox violations and missed
cache inputs from upstream projects that produce those files.

## Expected Behavior

Declaration file globs cover all three TypeScript declaration extensions
(`.d.ts`, `.d.cts`, `.d.mts`) consistently across inputs and outputs,
matching what `tsc` actually emits for `.ts`/`.cts`/`.mts` sources.

Changes in `packages/js/src/plugins/typescript/plugin.ts`:
- `getOutputs` (`emitDeclarationOnly` branch): `**/*.d.ts{,.map}` →
`**/*.{d.ts,d.cts,d.mts}{,.map}`
- `getInputs` `dependentTasksOutputFiles`: `**/*.{d.ts,tsbuildinfo}` →
`**/*.{d.ts,d.cts,d.mts,tsbuildinfo}`
- `getInputs` dependencies fileset: `{projectRoot}/**/*.d.ts` →
`{projectRoot}/**/*.{d.ts,d.cts,d.mts}`

The non-`emitDeclarationOnly` branch already used the full extension
set, so this brings the other paths in line.
2026-04-21 09:27:06 -04:00
Leosvel Pérez Espinosa 9d476a117f fix(core): normalize spawned run-commands output (#35358)
## Current Behavior

Direct `nx:run-commands` tasks on the non-PTY path can forward `Buffer`
chunks into the TUI lifecycle after the recent `exec()` to `spawn()`
change. When native progressive output handling receives that value,
task execution can fail with `StringExpected` instead of surfacing the
underlying command output.

## Expected Behavior

Spawned `run-commands` output is decoded to UTF-8 strings before it
reaches progressive output consumers, so TUI rendering can continue
streaming command output without crashing.
2026-04-21 09:26:36 -04:00
Leosvel Pérez Espinosa 7c1b94435d fix(core): resolve native binary crash on aarch64 linux with 16K/64K page kernels (#35356)
## Current Behavior

On aarch64 Linux kernels with page sizes larger than 4 KiB — 16 KiB on
Asahi Linux / Apple Silicon, 64 KiB on some Ampere/Fedora server configs
— every Nx command (including `nx --version`) aborts immediately:

```
<jemalloc>: Unsupported system page size
<jemalloc>: arena_new: allocation failed
memory allocation of 576 bytes failed
Aborted (core dumped)
```

The cause: jemalloc is compiled with `--with-lg-page=12` (4 KiB
allocator page) by default, and refuses to operate when the system page
size exceeds the compiled-in value.

## Expected Behavior

Nx native binaries run correctly on every shipping aarch64 Linux kernel
(4 KiB, 16 KiB, 64 KiB pages), without losing the performance benefits
of jemalloc on the common 4 KiB-page configurations (Graviton, cloud
ARM, Raspberry Pi OS, Debian/Ubuntu ARM).

The fix: build `@nx/nx-linux-arm64-gnu` and `@nx/nx-linux-arm64-musl`
with `JEMALLOC_SYS_WITH_LG_PAGE=16` (64 KiB allocator page). jemalloc's
documented contract is `allocator_page_size ≥ system_page_size`, so a
binary built this way runs on any aarch64 Linux kernel with page size ≤
64 KiB — which covers all known configurations.

## Prior art

The same `JEMALLOC_SYS_WITH_LG_PAGE=16` approach is used by:

- **rustc** —
[rust-lang/rust#145353](https://github.com/rust-lang/rust/pull/145353):
`cargo.env("JEMALLOC_SYS_WITH_LG_PAGE", "16")` for aarch64 targets in
`src/bootstrap/src/core/build_steps/tool.rs`
- **swc** —
[swc-project/swc#6541](https://github.com/swc-project/swc/pull/6541):
`export JEMALLOC_SYS_WITH_LG_PAGE=16` in the publish workflow for both
aarch64 gnu and musl rows
- **fd** — [sharkdp/fd#1547](https://github.com/sharkdp/fd/pull/1547)
and follow-up [#1549](https://github.com/sharkdp/fd/pull/1549) moving
the env var into `Cross.toml` via `passthrough`
- **Homebrew** —
[`Library/Homebrew/extend/os/linux/extend/ENV/super.rb`](https://github.com/Homebrew/brew/blob/master/Library/Homebrew/extend/os/linux/extend/ENV/super.rb):
`self["JEMALLOC_SYS_WITH_LG_PAGE"] = "16"` (applied globally to Homebrew
Linux aarch64 builds)
- **Arch Linux ARM** —
[`extra/fd/PKGBUILD`](https://github.com/archlinuxarm/PKGBUILDs/blob/master/extra/fd/PKGBUILD):
`[[ $CARCH == "aarch64" ]] && export JEMALLOC_SYS_WITH_LG_PAGE=16`

## Related Issue(s)

Fixes #35345
2026-04-21 09:04:59 -04:00
Jason Jean 7f3155c9df fix(gradle): resolve sandbox violations in e2e-gradle tests (#35349)
## Current Behavior

The `e2e-gradle:e2e-ci--**/*.test.ts` tasks produce hundreds of sandbox
violations: ~163 unexpected reads + ~160 unexpected writes under
`packages/gradle/project-graph/build/**`, plus reads of workspace-root
gradle config files.

The root cause is `e2e/gradle/src/utils/create-gradle-project.ts`
invoking `./gradlew :gradle-project-graph:publishToMavenLocal
-PskipSign=true` inline via `execSync` during test setup, which compiles
Kotlin sources inside the sandboxed task and produces all those file
accesses.

## Expected Behavior

The `publishToMavenLocal` step runs as a proper Nx task dependency
before the e2e test, outside the sandbox. Its outputs are declared by
the `@nx/gradle`-inferred target.

### Changes

- **Move publishing out of the test** — delete the inline
`execSync(gradlew :gradle-project-graph:publishToMavenLocal)` in
`create-gradle-project.ts`; make the `e2e-local` and `e2e-ci--**/**`
targets on `e2e-gradle` depend on
`:gradle-project-graph:gradle:publishToMavenLocal`.
- **Fix signing** — the inferred `publishToMavenLocal` target doesn't
pass `-PskipSign=true`, so replace the `skipSign` flag in
`packages/gradle/project-graph/build.gradle.kts` with `setRequired({
gradle.taskGraph.hasTask(":gradle-project-graph:publish") })`. Signing
is required for the Maven Central path (`publish` lifecycle task) but
silently no-ops for `publishToMavenLocal` when no GPG keys are
provisioned.
- **Declare workspace wrapper inputs** — the bootstrap `gradle init`
call must use the workspace wrapper, so add
`gradle/wrapper/gradle-wrapper.jar`,
`gradle/wrapper/gradle-wrapper.properties`, and `gradle.properties` as
inputs on the e2e targets, matching the `@nx/gradle` plugin's inferred
gradle-task input set.
- **Disable cache on the publish task** — `publishToMavenLocal` writes
to `~/.m2/repository/` (outside the workspace, can't be declared as an
Nx output). A remote cache hit would skip launching gradle, leaving
`~/.m2/` empty on the agent and breaking plugin resolution. Override
`cache: false` on `:gradle-project-graph:gradle:publishToMavenLocal`.
- **Narrow `e2eInputs` tsconfig input** — use `{ json, fields }` in
`nx.json` to hash only the fields that affect compilation (same pattern
as `@nx/playwright`).
- **Tighten `astro-docs:validate-links` inputs** — swap the
`**/sitemap*.xml` dep-task-outputs glob for `**/*.html` +
`**/sitemap*.xml` (the actual files the script reads) and drop the dead
`nx-dev/public/sitemap-0.xml` branch since #35315 removed `next-sitemap`
from nx-dev.

## Related Issue(s)

NXC-3981

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-21 00:39:04 +00:00
Jason Jean c9b01b1990 fix(core): speed up nx --version by avoiding heavy imports (#35326)
## Current Behavior

`nx --version` (and other trivial CLI invocations) eagerly load the
daemon client, dotenv loader, analytics + perf-logging stack, and
`init-local` at the top of `bin/nx.ts`. That's ~225ms of unrelated
module load time before `main()` starts running.

On a typical workspace, the cost looks like:

| Layer | Time |
|---|---|
| `daemonClient` import | ~88ms |
| `perf-logging` (loads `analytics` → native binding) | ~63ms |
| `init-local` | ~36ms |
| `dotenv` | ~19ms |
| `analytics-prompt` | ~19ms |

End-to-end: `nx --version` was ~440ms via `pnpm exec`.

## Expected Behavior

`--version` (and other no-workspace fast paths) only load what they
actually need.

This PR:
- Lazy-loads the heavy modules inside the code paths that actually use
them (cloud commands, local install handoff, etc.).
- Adds a `--version` fast path at the top of `main()` that exits before
any heavy module is touched.
- Reworks `src/utils/perf-logging.ts` to lazy-require `analytics` /
daemon logger inside the `PerformanceObserver` callback. Importing the
module no longer pulls in the native binding.
- Updates `benchmarks/bench:*` scripts to invoke the workspace nx
directly via `node ../packages/nx/dist/bin/nx.js` instead of `pnpm exec
nx`, which removes ~220ms of pnpm wrapper overhead from the measurement
and works on CI agents (which don't sync per-project
`node_modules/.bin`). `goals.json` is adjusted to the new floor.

## Benchmark Results

Measured locally against the `benchmarks/` workspace (1,110 projects).
Deltas are shown as `(vs Goal | vs Baseline)`.

| Benchmark | Goal | Baseline | Current |
|---|---|---|---|
| version | 50ms | 440ms | **41ms** (-19% \| -91%) |
| show-projects | 100ms | 1.07s | **434ms** (+334% \| -60%) |
| cat-warm | 300ms | 1.88s | **1.04s** (+245% \| -45%) |
| copy-warm | 500ms | 1.48s | **1.31s** (+163% \| -11%) |
| build-warm | 750ms | 1.71s | **1.16s** (+54% \| -32%) |

`version` clears the goal; the other benchmarks pick up the
wrapper-overhead delta too, but several are still above goal and remain
targets for follow-up work.

## Related Issue(s)

N/A — internal performance work on the `speed-version` branch.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-20 19:45:12 -04:00
Jack Hsu 902dcb40c5 fix(nx-dev): restore sitemap generation (#35351)
The previous clean-up was overly zealous and removed both `sitemap.xml`
and `sitemap-0.xml` because I assumed there was nothing from Next.js
app. This restores both.

A follow-up will be to move these out of Next.js, but for now the root
one will point to both Framer and Next.js routes, and the latter has
`/courses/*` which we use still.
2026-04-20 16:19:38 -04:00
Jack Hsu b65809d2b8 docs(nx-dev): add agent-readiness signals for link headers and robots.txt (#35348)
## Current Behavior

[isitagentready.com](isitagentready.com) scan flagged two gaps on
nx.dev: no agent-useful Link relation types in response headers, and no
Content-Signal directives in robots.txt.

## Expected Behavior

- Netlify serves Link response headers pointing agents at /llms.txt
(describedby), /llms-full.txt (service-doc), and /sitemap-index.xml
(sitemap).
- Production robots.txt declares permissive Content-Signal preferences
(search=yes, ai-input=yes, ai-train=yes) so AI crawlers know the docs
are intentionally open.



<img width="873" height="1269" alt="image"
src="https://github.com/user-attachments/assets/70fbcbee-04df-4f5c-b274-c360f420060d"
/>

Note: robots.txt won't work until we go to prod, so I'll re-run
benchmarks again once deployed.

## Related Issue(s)

DOC-479

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2026-04-20 13:22:15 -04:00
Jack Hsu 7812501c65 fix(module-federation): bump @module-federation/enhanced to ^2.3.3 (#35314)
This PR updates our `@module-federation/enhanced` range from `^2.1.0` to
`^2.3.3` since `2.3.1` has a dependency on a compromised axios version.
This does not block existing workspaces from updating themselves, but
ensures `npm install` in an existing workspace will force an update of
the enhanced package.

Fixes ##35311
2026-04-20 13:07:28 -04:00
Louie Weng 0d239d3141 chore(gradle): bump Gradle plugin to 0.1.20 (#35338)
## Current Behavior

The `dev.nx.gradle.project-graph` Gradle plugin is pinned to version
`0.1.19` in the Nx Gradle plugin source and referenced version constant.

## Expected Behavior

The plugin version is updated to `0.1.20`, with an accompanying
migration that updates consumer `build.gradle(.kts)` files and version
catalogs automatically when users upgrade to `22.7.0-beta.16`.

## Related Issue(s)

No related issue.
2026-04-20 09:07:27 -07:00
Steven Nance af5643f1a9 chore(repo): remove unused browser installs and system deps from main-linux CI (#35337)
## Current Behavior

The `main-linux` orchestrator job in `.github/workflows/ci.yml` installs
several system packages, Chrome, and Playwright browsers on every run:

```yaml
- name: Install dependencies
  run: |
    sudo apt-get update
    sudo apt-get install -y ca-certificates lsof libvips-dev libglib2.0-dev libgirepository1.0-dev

- name: Install Chrome
  uses: browser-actions/setup-chrome@...

- name: Install Playwright
  run: pnpm playwright install --with-deps
```

None of these are actually used by tasks the orchestrator executes. The
orchestrator runs `format:check`, `sync:check`, `conformance:check`,
`check-imports`, `check-lock-files`, `check-codeowners`, and kicks off
`nx affected` -- which distributes all
`lint`/`test`/`build`/`e2e`/`e2e-ci` tasks to Nx agents per
`.nx/workflows/dynamic-changesets.yaml`.

Historically these steps were copy-pasted during the CircleCI to GHA
migration (5b9d43f9) without evaluating whether the orchestrator
actually needed them. Tracing each back to its origin:

- **`lsof`** (added in c9cd67ea) -- only used by `kill-port` in e2e
tests (`e2e/next`, `e2e/storybook`, etc.), which run on agents.
- **`libvips-dev` / `libglib2.0-dev` / `libgirepository1.0-dev`** (added
in cfcedb48 for storybook/Next.js image tests) -- only needed if `sharp`
falls back to a source build. Sharp ships prebuilt binaries for
linux-x64 and is only reached via `astro-docs`/Next.js builds, which run
on agents.
- **`ca-certificates`** -- already present on `ubuntu-latest`.
- **`browser-actions/setup-chrome`** -- no orchestrator-local task uses
Chrome. No Karma/Puppeteer tests run here. Browser-driven tests run on
agents, which get Chromium via Playwright.
- **`pnpm playwright install --with-deps`** -- only needed by browser
tests, which run on agents (agents install Playwright themselves, see
`.nx/workflows/agents.yaml` lines 51-54).

## Expected Behavior

The `main-linux` orchestrator skips the unnecessary install steps,
saving ~30-60s per CI run with no functional change. Nx agents continue
to install these packages themselves when they need them.

The macOS React Native job (`main-macos`) keeps its Playwright install
because those e2e tests run directly on the macOS runner, not on agents.

## Related Issue(s)

N/A -- cleanup.
2026-04-17 18:05:59 -04:00
Louie Weng fb79edbb03 fix(gradle): recognize Kotlin compile tasks in inferred input extensions (#35335)
## Current Behavior

`inferExtensionsFromInputProperties` uses `is AbstractCompile` to
determine when to add `.class` as an inferred input extension. As a
result, Kotlin compile tasks are silently excluded from the `.class`
branch, producing incomplete inferred inputs in the Nx project graph for
Kotlin projects.

## Expected Behavior

All Kotlin compile tasks should be recognized alongside
`AbstractCompile` tasks and produce `.class` as an inferred input
extension, regardless of which Gradle plugin hierarchy they belong to.
Java-only projects are unaffected.

## Related Issue(s)

<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-04-17 16:57:52 -04:00
Jason Jean a7d955016f chore(repo): update nx to 22.7.0-beta.15 (#35319)
Updating Nx from 22.7.0-beta.12 to 22.7.0-beta.15

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-17 16:37:25 -04:00
Craigory Coppola 8f47bf2a6a fix(core): recognize json inputs in TS task hasher fallback (#35334) 2026-04-17 14:56:06 -04:00
Craigory Coppola e4b7a3d9e7 fix(core): use v8 serialization in pseudo-IPC channel (#35332)
## Current Behavior

The pseudo-IPC channel (used when Rust forks a Node wrapper that then
re-forks the Node task process) and the plugin isolation channel (used
between the main Nx process and its plugin-worker subprocesses) both
serialize every message with `JSON.stringify` / `JSON.parse`. That means
payloads such as `Buffer`, `Date`, `Error`, `undefined`, or objects with
cycles can't flow over these channels, even though the daemon
client/server already supports them via the shared `serialize()` helper
in `daemon/socket-utils.ts` (v8 with JSON fallback).

Notably, `Error` objects over the plugin channel currently flatten to
`{}` under JSON, losing stack and message.

The read-side `isJsonMessage ? JSON.parse :
v8.deserialize(Buffer.from(..., 'binary'))` detection was also
duplicated across three files.

## Expected Behavior

Pseudo-IPC and plugin isolation both use the same opt-in v8/JSON
serialization convention as the daemon channel, so non-JSON-safe
payloads flow through consistently. Default behavior is unchanged — v8
serialization is gated by `isV8SerializerEnabled()` and falls back to
JSON otherwise.

The duplicated read-side detection is hoisted into a single
`parseMessage<T>()` helper next to `isJsonMessage` in
`utils/consume-messages-from-socket.ts`. Round-trip coverage for the
helper is added.

Commits:
1. `fix(core): use v8 serialization in pseudo-IPC channel` — matches
daemon wire format on the pseudo-IPC server/client.
2. `refactor(core): hoist parseMessage helper for socket payloads` —
deduplicates the parse pattern and adds unit tests.
3. `refactor(core): adopt parseMessage/serialize in plugin isolation
IPC` — applies the same convention to the internal plugin-worker channel
(not public API).

## Related Issue(s)

N/A

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
2026-04-17 14:20:14 -04:00
Jack Hsu b7f7c8bd3c chore(misc): update docs version script (#35313)
Add support for `--redirect-to-prod` flag for 16, 17, 18 which we do not
have versioned docs for. Also update README.md with more details on how
Netlify and Squarespace are used to support versions docs.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
2026-04-17 11:49:16 -04:00
Jack Hsu 52c3626b67 cleanup(nx-dev): pare nx-dev down to /ai-chat, /api, /courses only (#35315)
## Current Behavior

`nx-dev` is a monolithic Next.js app still serving blog, docs,
changelog, pricing, podcast, resources-library, whitepaper pages, etc. —
all of which now live in `astro-docs` (docs), `nx-blog` (blog), or have
been deprecated. The top-level `docs/` folder (~333MB) duplicates
content already in astro-docs, and 20+ `nx-dev/*` UI/feature libraries
are maintained despite only a handful being reachable from a live route.

## Expected Behavior

`nx-dev` only serves:

- `/ai-chat` — the AI chat UI
- `/api/query-ai-handler` — streaming chat endpoint
- `/api/query-ai-embeddings` — doc-search endpoint used by the Nx MCP
doc search tool
- `/courses` — video courses landing + detail + lesson pages (moved into
nx-dev from `docs/courses`)

### Changes

- Deleted routes: `/blog`, `/podcast`, `/pricing`, `/changelog`,
`/resources-library`, `/whitepaper-fast-ci`, `/500`, `/brands`, and
every other page that previously rendered here.
- Deleted `nx-dev/*` libraries not reachable from the surviving routes:
`feature-feedback`, `ui-podcast`, `ui-pricing`, `ui-resources` (plus a
handful of now-unused transitive helpers).
- Moved `docs/courses/` → `nx-dev/nx-dev/courses-content/` so the
top-level `docs/` folder could be deleted. Updated `CoursesApi` to
accept a configurable `authorsPath`.
- Deleted the entire top-level `docs/` folder (~333MB of stale content).
- Removed consumers of `docs/`:
- Removed `blog-description` and `blog-cover-image` conformance rules +
their registrations in `nx.json`.
- Removed
`scripts/documentation/{map-link-checker,internal-link-checker,prebuild-banner}.ts`.
  - Removed `check-documentation-map` npm script.
- Removed `validateCrossSiteLinks` from `astro-docs/validate-links.ts`.
- `tools/documentation/create-embeddings` no longer includes
`docs/*.json` in its tsconfig; default `--mode` is now `astro`.
- Simplified `feature-ai`: dropped `feature-analytics`, `ui-common`, and
`ui-markdoc` deps. AI markdown rendering now uses a minimal inline
renderer on top of `@markdoc/markdoc` core.
- Simplified `_app.tsx`, `_document.tsx`, and `app/layout.tsx`: no more
`bannerCollection`, `GlobalSearchHandler`, `FrontendObservability`, or
GTM scripts.
- `/ai-chat` uses an inline minimal header instead of the full marketing
Header.

### Verification

- `pnpm nx build nx-dev`  — emits all four routes above (`Route (app)`
for `/courses/*`; `Route (pages)` for `/ai-chat`, `/api/query-ai-*`).
- `/_redirects` still copied into `.next/` for Netlify.
- `/docs/*`, `/llms.txt`, `/llms-full.txt` rewrites to astro-docs
preserved.

## Related Issue(s)

Fixes DOC-478

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
2026-04-17 11:48:26 -04:00
Jason Jean b1717a687d fix(core): await queued processTask promises before cache.getBatch (#35322)
## Current Behavior

Nx Cloud agents crash on warm-cache runs with:

```
Error: Failed to convert JavaScript value `Null` into rust type `String`
    at DbCache.getBatch (.../cache.js:115:41)
    at TaskOrchestrator.fetchCacheHits (.../task-orchestrator.js:270:47)
    at TaskOrchestrator.resolveCachedTasks (.../task-orchestrator.js:564:38)
    at runDiscreteTasks (.../init-tasks-runner.js:133:42)
  code: 'StringExpected'
```

Regression introduced by #35172 (warm-cache perf optimization). Cloud
agents call `runDiscreteTasks` with `task.hash = null` (they
intentionally null hashes — see `ocean/.../execute-tasks-v3.ts`).
`init-tasks-runner.ts::createOrchestrator` queues `processTask` promises
via fire-and-forget `processAllScheduledTasks()`, but `runDiscreteTasks`
immediately calls `resolveCachedTasks` which doesn't await them.
`cache.getBatch(tasks.map(t => t.hash))` then receives nulls and the
napi binding rejects them.

## Expected Behavior

`resolveCachedTasks` awaits the queued `processTask` promises (which set
`task.hash` via `hashTask`) before passing hashes into `cache.getBatch`.

The single-task `runTaskDirectly` path already awaits
`this.processedTasks.get(task.id)` for the same reason — this fix
mirrors that pattern in the bulk path.

Bonus: a second tiny commit changes coordinator step 1's pre-hash guard
from `unhashed.length > 1` to `> 0`. The `> 1` micro-optimization
silently skipped cache lookup for single-task cycles, because
`resolveCachedTasksBulk` filters candidates by `task.hash &&` — a
length-1 unhashed task got dropped and ran without a cache check. Cost
more in lost cache hits than it saved in batch setup.

## Related Issue(s)

Surfaced internally; no GitHub issue.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-17 11:48:16 -04:00
Jason Jean 30bdd1c6f3 fix(repo): switch agent apt mirror to azure to avoid canonical sync races (#35324)
## Current Behavior

CI agents launched by `.nx/workflows/agents.yaml` are failing during the
`Install system deps` step on a majority of agents:

```
E: Failed to fetch http://archive.ubuntu.com/ubuntu/dists/jammy-updates/main/binary-amd64/Packages.gz
File has unexpected size (4263778 != 4263737). Mirror sync in progress? [IP: 91.189.92.22 80]
```

`archive.ubuntu.com` is serving a `Packages.gz` whose size/hash doesn't
match its own `InRelease` metadata while the canonical mirror is
mid-sync. Apt detects the mismatch and refuses to use the index. Without
that index, `apt-get install` fails to find packages and the agent dies
before any task runs. Every agent hits the same mirror, so every agent
fails simultaneously — taking down distributed CI runs.

This is currently affecting both PR CI and master CI on staging
(confirmed via `gh run` and Nx Cloud CIPE status).

## Expected Behavior

`apt-get update` succeeds reliably on agent provisioning, regardless of
canonical-mirror sync races.

Switch the agent's apt sources from `archive.ubuntu.com` /
`security.ubuntu.com` to `azure.archive.ubuntu.com` via a one-line `sed`
on `/etc/apt/sources.list`. Azure's mirror is what GitHub Actions
runners use by default — historically much more stable than canonical
for the "many concurrent CI agents hammering one mirror" workload that
triggers the sync race.

## Related Issue(s)

No GitHub issue — surfaced today via the linked CI failures.
2026-04-17 14:08:37 +00:00
Ben Snyder 576fb82d8e fix(core): support pnpm multi-document lockfiles (#35271)
## Current Behavior

Before this change, `nx/js/dependencies-and-lockfile` assumed
`pnpm-lock.yaml` contained a single YAML document.

When `pnpm-workspace.yaml` enables `managePackageManagerVersions: true`,
pnpm 11 writes a package-manager metadata document before the workspace
lock document. Nx then fails while building the project graph with:

```text
An error occurred while processing files for the nx/js/dependencies-and-lockfile plugin.
- pnpm-lock.yaml: expected a single document in the stream, but found more
```

## Expected Behavior

After this change, Nx reads multi-document pnpm lockfiles, selects the
workspace lockfile document, and continues parsing dependencies
normally.

This allows monorepos to keep pnpm 11 package-manager metadata enabled
while still running Nx commands such as `bun x nx sync` without
disabling `managePackageManagerVersions`.

## Related Issue(s)

Fixes Issue https://github.com/nrwl/nx/issues/35270

## Validation

- `pnpm exec jest
packages/nx/src/plugins/js/lock-file/pnpm-parser.spec.ts --config
packages/nx/jest.config.cts --runInBand`
- Verified `getPnpmLockfileNodes` against the repository multi-document
`pnpm-lock.yaml` via direct source invocation

---------

Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
2026-04-17 14:49:28 +02:00
Caleb Ukle e39c3df758 fix(core): allow controlling migrate fallback installation concurrency (#35312)
- fix(core): allow controlling migration dep install concurrency

there are cases where parallel installs of a migration dependencies can
cause concurrent writes to package managers cache which can cause fs
errors of files overwriting each others peer deps. this most often
occurs when a migration for a 3rd party plugin is hosted in a private
registry that doesn't support custom metadata e.g. GH npm registry.

This is now controllable via the `NX_MIGRATE_INSTALL_CONCURRENCY` env
var. if it's not set then the default behavior of running all installs
in parallel still occurs

- docs(core): add `NX_MIGRATE_INSTALL_CONCURRENCY` env var info
2026-04-17 08:10:12 +02:00
Jay Bell 4d15754626 feat(core): add page up/down to tui shortcuts (#34525)
Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
2026-04-16 21:00:40 -04:00
Jason Jean c8b59c9d24 fix(repo): resolve FreeBSD build OOM and disk exhaustion (#35309)
## Current Behavior

The FreeBSD native build in the publish workflow fails with "filesystem
full" or OOM. Two issues:

1. **Jest plugin OOM**: PR #35231 changed `createNodes` to load all jest
configs upfront in a sequential `for` loop before hash computation. Each
`loadConfigFile` registers a ts-node transpiler via `registerTsProject`
(`packages/nx/src/plugins/js/utils/register.js`), whose dedup is
refcounted. Serial register/unregister cycles drive refCount to 0
between iterations and delete the Map entry — but ts-node's
`transpilerCleanup` is a no-op, so the service stays alive in
`require.extensions`. The next iteration creates a fresh ts-node
service. Across 96 TS jest configs under `NX_PREFER_TS_NODE=true` (set
for the FreeBSD build), 96 ts-node services stack and OOM at V8's ~2GB
heap limit.

2. **Core dump fills disk**: When the jest plugin OOMs, FreeBSD writes a
2.3GB `node.core` file to the workspace root, filling the remaining 4GB
of free disk space before cargo can run.

### Failing runs

- [beta.13 publish (Apr
15)](https://github.com/nrwl/nx/actions/runs/24480325293/job/71547815608)
— `filesystem full` during project graph, cargo never ran
- [Canary after beta.12 (Apr
10)](https://github.com/nrwl/nx/actions/runs/24260184601/job/70841801347)
— jest plugin OOM at 2GB heap, `node.core` filled disk
- [Diagnostics
run](https://github.com/nrwl/nx/actions/runs/24490680528/job/71574929552)
— confirmed 2.3GB `node.core` file in workspace root

### Passing run (with pnpm patch)

- [Fix validation run (Apr
16)](https://github.com/nrwl/nx/actions/runs/24508325992/job/71632565433)
— jest plugin no longer OOMs, cargo compiles successfully (validated an
earlier lazy-load form of the patch; the current form uses parallel load
but is equivalent for FreeBSD's resource envelope)

## Expected Behavior

The FreeBSD build completes successfully. The jest plugin loads configs
in parallel so the ts-node transpiler dedup holds across all
registrations and only one service is created.

### Changes

**Jest plugin memory fix** (`packages/jest/src/plugins/plugin.ts`):
- Convert the upfront config-loading `for` loop to
`Promise.all(validConfigFiles.map(async ...))`. Keeps all
`registerTsProject` registrations alive concurrently so refCount goes
`0→1→2→…→N→N-1→…→0` and only one ts-node service is ever created.
- Preserves #35231's hash correctness: preset path and tsconfig extends
chain remain inputs to `calculateHashesForCreateNodes`; `needsDtsInputs`
is still derived from the real jest config + ts-jest transform
inspection.

**pnpm patch** (`patches/@nx__jest@22.7.0-beta.12.patch`):
- Same fix applied to the installed `@nx/jest@22.7.0-beta.12` so the
FreeBSD build (which uses the published package, not source) gets the
fix immediately. Generated via `pnpm patch` from the built source output
— installed `plugin.js` is byte-identical to
`dist/packages/jest/src/plugins/plugin.js`.

**Workflow hardening** (`.github/workflows/publish.yml`):
- `ulimit -c 0` to disable core dumps (prevents 2.3GB files from filling
disk)
- `NODE_OPTIONS='--max-old-space-size=4096'` as a safety net
- Disk usage diagnostics on build failure for future debugging

### Local verification

Cold cache (`npx nx reset` before each run), `NX_PREFER_TS_NODE=true
NX_CACHE_PROJECT_GRAPH=false NX_DAEMON=false`, 96 TS jest configs:

- **Pre-fix (serial for-loop)**: per-iteration heap grows `40 → 60 → 160
→ 756 MB` at iter 1/10/20/30, then OOM at iter ~33 with
`--max-old-space-size=4096`.
- **Post-fix (parallel)**: heap flat at **272 MB from load 1 through
96**. `require.cache` constant at 599 entries. No accumulation.
- Real `nx show projects` end-to-end: 600 MB RSS, 5 s.

### Follow-up (separate PR)

`packages/nx/src/plugins/js/utils/register.js` has a latent leak: when
the transpiler has no real cleanup (ts-node always, swc sometimes),
`registered.delete(registrationKey)` on refCount==0 allows the next
registration with the same key to stack a fresh service. Any Nx plugin
that loads configs serially hits this. Worth gating `registered.delete`
on whether the transpiler is actually disposable, analogous to the
existing `isTsEsmLoaderRegistered` flag. Not in scope for this PR.

## Related Issue(s)

Fixes the FreeBSD build failure in the publish workflow.

---------

Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
2026-04-16 21:55:42 +00:00
Caleb Ukle 8d5bed18dc docs(nx-cloud): clarify dind launch template image support (#35316)
remove dind images that are not in the allow list and add aside to
mention dind is enterprise only feature atm
2026-04-16 17:49:17 -04:00
Leosvel Pérez Espinosa ea73cf4df8 chore(repo): add diagnose-sandbox-report skill (#35269)
## Current Behavior

No skill for investigating Nx sandbox violations.

## Expected Behavior

The `diagnose-sandbox-report` skill provides a structured workflow for
diagnosing sandbox violations, with a TypeScript script that automates
report parsing, Nx context gathering, violation validation, and file
classification.
2026-04-16 08:12:21 +02:00
Jason Jean fd0535953d fix(core): optimize warm cache performance for task execution (#35172)
## Current Behavior

Running cached tasks in a large workspace takes much longer than the
work actually warrants. The task orchestrator hashes, cache-checks,
schedules, and reports each task individually:

- Per-task JS→Rust→SQLite cache lookups (~N boundary crossings).
- Per-task daemon IPC calls for recording/matching output hashes (~2N
sequential round-trips).
- Per-task filesystem scans for output expansion.
- `TasksSchedule` re-sorts the full schedule array on every insert
(O(n²·log n)).
- The coordinator awaits each worker one at a time via a single-task
dispatch path.

On a 1,110-project benchmark workspace, this accumulates into
multi-second overhead even when every task is a cache hit with nothing
to do.

## Expected Behavior

Warm cache runs should resolve quickly. Every hot-path operation that
can be batched — cache lookups, daemon calls, scheduling, output-hash
tracking, filesystem scans — is batched, and the coordinator dispatches
discrete workers in parallel rather than sequentially.

### Rust native

- **`NxCache.get_batch`** (`cache.rs`) — single `UPDATE … WHERE hash IN
(…) RETURNING` with Rayon-parallel terminal-output reads replaces N
individual round-trips. Uses `rarray` for the `IN` clause, groups the
query / build stages as separate helpers, and collects rows straight
into a `HashMap`.
- **`get_files_for_outputs_batch`** — Rayon-parallel filesystem scanning
for cached-output expansion. Drop the old singular
`get_files_for_outputs` napi export now that every caller goes through
the batch path.

### Daemon output tracking

- Replace the non-batch `recordOutputsHash` / `outputsHashesMatch` chain
(client → server handlers → outputs-tracking helpers) with `*Batch`
equivalents. The single-entry path was dead after the orchestrator
routed everything through the batch methods.
- Short-circuit `outputsHashesMatchBatch` when the daemon has no
recorded hashes — avoids an unnecessary Rayon filesystem scan right
after `nx reset`.
- Skip recording for `local-cache-kept-existing`: the daemon already has
the right hash.

### Task orchestrator

- **Coordinator loop** rewrite: bulk-resolve all cache hits up-front,
batch-hash remaining unhashed tasks before any per-task lifecycle fires,
then dispatch cache-miss workers concurrently up to `parallelism`.
Tracks in-flight workers as a `Set<Promise<void>>` instead of a counter,
and the dispatch is extracted to `dispatchDiscreteWorker` +
`handleDiscreteWorkerFailure` instead of an inline fire-and-forget IIFE.
- **Split cache check from task execution**: `resolveCachedTasksBulk`
reports hits/misses without touching the execution path, so the
coordinator can batch lifecycle calls for the hit set and only dispatch
workers for misses.
- **Route `applyCachedResults` through `DbCache.getBatch`** — one daemon
call per cycle instead of one per task.
- **Group slots**: `closeGroup` can overflow without throwing when all
slots are claimed (parallelism gating is enforced elsewhere), and every
task keeps a single `groupId` through `runDiscreteTasks`.
- **Drop the dead single-entry fallback paths** in the orchestrator
(`shouldCopyOutputsFromCache`, `recordOutputsHash`, and the `processTask
will hash individually` try/catch around `hashTasks`).
- **`getExecutorForTask` takes a `projects` record directly** instead of
re-deriving it from the project graph per call via a module-level
`WeakMap`. Callers compute the record once.

### Task scheduler

- **Parallelism gating + sort stability** — batch scheduling collects
all schedulable roots, pushes them in one pass, and sorts the array once
per cycle instead of after every insert.

### Repo / infra

- `benchmarks/package.json`: run benches through `pnpm exec nx` so
hyperfine sub-shells always hit the workspace-local CLI.
- `fix(core): honor NX_NO_CLOUD / neverConnectToCloud in runner
selection` (`run-command.ts`) — surfaced while unblocking the benchmark
CI: with an ambient `NX_CLOUD_AUTH_TOKEN` on the CI agent,
`getTasksRunnerPath` still routed through the cloud shell even when
`NX_NO_CLOUD=true` was set. The cloud client's light-client require
bridge then loaded the default tasks runner from the parent workspace's
`node_modules/nx` (a published version), creating a cross-version API
mismatch. The guard now short-circuits runner selection to the default
path when cloud is explicitly opted out of.
- Test updates for the new `TasksSchedule(projectGraph, projects,
taskGraph, options)` signature.

## Benchmark Results

Measured locally against the `benchmarks/` workspace (1,110 projects)
with the `bench:*` scripts. Baseline is committed in
`benchmarks/baseline.json`.

| Benchmark | Goal | Baseline | Current | vs Goal | vs Baseline |
  |---|---|---|---|---|---|
  | version | 50ms | 348ms | 333ms | +566% | -4% |
  | show-projects | 100ms | 710ms | 647ms | +547% | -9% |
  | cat-warm | 300ms | 2.44s | 1.17s | +290% | -52% |
  | copy-warm | 1.00s | 6.85s | 1.35s | +35% | -80% |
  | build-warm | 5.00s | 17.75s | 1.44s | -71% | -92% |

## Related Issue(s)

Fixes #31067

No specific issue — performance-focused, driven by benchmark wall time
on large workspaces.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-04-15 17:50:28 -04:00
jase b6e0775633 feat(core): add NX_BAIL environment variable (#34711) 2026-04-15 17:49:14 -04:00
Leosvel Pérez Espinosa 878ec01117 fix(vitest): infer ancestor tsconfig files as test task inputs (#35241)
## Current Behavior

Vite <8 uses esbuild to bundle config files (`vitest.config.mts`).
esbuild walks up from the entry point and reads every `tsconfig.json` in
ancestor directories plus their `extends` chains. These files are not
declared as task inputs, so changes to them don't invalidate the test
cache.

Sandbox violation:
https://staging.nx.app/runs/B5EjkJA6p1/task/angular-rspack-compiler%3Atest?batchId=7f8749c3-4e9a-4a93-b8f4-56efa69f14ab

## Expected Behavior

The `@nx/vite` and `@nx/vitest` plugins walk ancestor directories and
`extends` chains, declaring discovered tsconfig files as selective JSON
inputs that only hash `compilerOptions`. This avoids cache invalidation
from irrelevant tsconfig changes (`include`, `exclude`, `references`,
etc.) while correctly invalidating when compilation-affecting settings
change.

Files already covered elsewhere are excluded:
- Inside the project root (covered by `default`)
- The root tsconfig handled by the native `TsConfiguration` hasher
- Inside `node_modules` (invalidated via lockfile)
- Outside the workspace
2026-04-15 17:48:08 -04:00
Jouke Visser 5686a9e83f chore(core): nx plugin submission @frontenderz/backstage-insights (#32817) 2026-04-15 17:29:01 -04:00
Leosvel Pérez Espinosa bdb53abf25 fix(angular): fall back to addUndefinedDefaults when addUndefinedObjectDefaults is unavailable (#35290) 2026-04-15 17:02:32 -04:00
Jack Hsu eaa7461ae9 chore(misc): add versioned docs snapshot script (#35264)
## Current Behavior

No automated way to create static snapshots of the docs site for
versioned branches (e.g., v22.nx.dev). The existing `release-docs.ts`
force-pushes the full source branch, requiring Netlify to build from
source.

## Expected Behavior

`node ./scripts/create-versioned-docs.mts v22` creates a deployable
orphan branch with the pre-built static site:

- Fetches latest stable git tag for the major version (e.g., `22.6.4`)
- Builds `astro-docs` (v21+) or `nx-dev` with static export (v18-v20)
- Creates orphan branch with pre-built files at `nx-dev/nx-dev/.next/`
- Includes `netlify.toml` that skips `@netlify/plugin-nextjs` for pure
static serving
- Resolves `GITHUB_TOKEN` from 1Password or env var
- Server-side redirects for `/docs` → `/docs/getting-started/intro`
- `--force` flag to overwrite existing branches

Deployed via Netlify branch deploys at `v{major}.nx.dev`.

### Usage

```bash
node ./scripts/create-versioned-docs.mts v22
node ./scripts/create-versioned-docs.mts v21 --force
git push -f origin v22
```

### Tested

- v21 https://v21.nx.dev/docs
- v20 https://v20.nx.dev/docs
- v19 https://v19.nx.dev/docs 

## Related Issue(s)

Fixes DOC-69
2026-04-15 16:39:27 -04:00
Leosvel Pérez Espinosa 0e101c77ef fix(core): don't hang when workspace contains a named pipe (#35289) 2026-04-15 16:16:04 -04:00
Leosvel Pérez Espinosa 45fc97871f fix(js): resolve project tsconfig for inferred tsc run-commands targets in dependency-checks (#35291) 2026-04-15 16:15:36 -04:00
Miroslav Jonaš c05e6e85c7 feat(core): update nx-set-shas usage to v5 (#34934)
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-15 16:11:44 -04:00
Jack Hsu cc3901318b fix(angular): preserve specific file paths in tsconfig when adding secondary entry point (#35254)
## Current Behavior
The secondary entry point generator rewrites all tsconfig
include/exclude entries by stripping the `src/` prefix (e.g.
`src/**/*.ts` → `**/*.ts`). This also strips `src/` from literal file
paths like `src/test-setup.ts`, breaking the exclude rule since the file
still lives at that path.

## Expected Behavior
The generator should add new include/exclude entries scoped to the
secondary entry point directory instead of mutating existing entries.
This is additive — existing entries are left untouched and new
`<name>/src/**/*.ts` patterns are appended for the secondary entry
point.

## Related Issue(s)
Fixes #33051
2026-04-15 15:48:13 -04:00
Miroslav Jonaš 075b627763 docs(nx-cloud): improve the misleading neverConnectToCloud messaging (#35182)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: Caleb Ukle <caleb@nrwl.io>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: meeroslav <meeroslav@users.noreply.github.com>
2026-04-15 15:23:38 -04:00
Leosvel Pérez Espinosa 1e7b29ca38 fix(js): avoid full source scan in readTsConfigPaths (#35300)
## Current Behavior

In large monorepos where the root tsconfig lacks `files`/`include`,
`@nx/next:server` (and other executors that rely on
`readTsConfigPaths()` via `withNx`) can hang for minutes and fail with
`ECONNREFUSED` as the Next.js server never gets a chance to start.

## Expected Behavior

`readTsConfigPaths()` returns the configured path mappings quickly
regardless of workspace size, so the Next.js dev server starts normally.

## Technical Details

`readTsConfigPaths()` only needs `compilerOptions.paths`, but
TypeScript's default `ParseConfigHost` enumerates every `.ts` file under
the tsconfig directory when `files`/`include` are absent. Stubbing
`readDirectory` on the host skips the source-file scan while preserving
`extends` resolution.
2026-04-15 15:51:57 +02:00
Leosvel Pérez Espinosa f0a710dda4 fix(core): cap TUI parallel slots by total task count (#35299) 2026-04-15 09:46:10 -04:00
Sai Asish Y cf9c96afcb fix(node): split package-manager exec command for VS Code launch.json (#35295)
## Current Behavior

When generating a `@nx/node:application` from a pnpm workspace, the
generated `launch.json` sets `runtimeExecutable` to the full
`getPackageManagerCommand().exec` string (e.g. `"pnpm exec"`). On
Windows, VS Code rejects this:

```
Can't find Node.js binary "pnpm exec": path does not exist.
Make sure Node.js is installed and in your PATH, or set the
"runtimeExecutable" in your launch.json.
```

The same issue affects npm (`npm exec --`) and yarn (`yarn exec`).

## Expected Behavior

`runtimeExecutable` should be a real binary path (`pnpm`, `npm`,
`yarn`), and the `exec` / `exec --` tokens should live in `runtimeArgs`.

## Fix

Split `getPackageManagerCommand().exec` on whitespace, feed the first
token into `runtimeExecutable`, and prepend the remaining tokens to
`runtimeArgs`. Bun (`bunx`) is unaffected because it has no space.

## Related Issue(s)

Fixes #35276
2026-04-15 11:26:15 +02:00
dan-winters b26087b18e fix(angular-rspack): fixes issues with angular-rspack hmr (#35294)
HMR is freezing on compilation failures but should support recompilation
when updating files after a previous compilation error


## Current Behavior
When running dev server with HMR, compilation errors are freezing the
dev server


## Expected Behavior
HMR should not abort or freeze and should allow recompilation when
saving updated files.

Fixes #35040
2026-04-15 11:25:37 +02:00
Leosvel Pérez Espinosa 04d7df3cc6 fix(testing): declare external tsconfig files as playwright e2e task inputs (#35287)
## Current Behavior

Playwright reads tsconfig files that aren't declared as task inputs:

- The config loader walks the project tsconfig `extends` chain at
compile time.
- The Playwright worker reads the workspace root `tsconfig.json` at
runtime via `isUsingTsSolutionSetup` (called by `nxE2EPreset`).

When any of these files live outside the project root, sandboxed runs
report violations and cached tasks can become stale when those files
change.

## Expected Behavior

The `@nx/playwright` plugin walks the project tsconfig `extends` chain
and also declares the workspace root `tsconfig.json` (when present and
not already handled by the native `TsConfiguration` hasher), exposing
them as selective JSON filesets that hash only `compilerOptions`,
`extends`, `files`, and `include`. This invalidates the cache for
compilation-affecting changes while staying stable when unrelated fields
(e.g. `references`) churn.

Files already covered elsewhere are excluded:
- Inside the project root — covered by `default`
- The native `TsConfiguration` hasher file (`tsconfig.base.json` when it
exists, otherwise `tsconfig.json`)
- Inside `node_modules` — invalidated via the lockfile
- Outside the workspace
2026-04-14 22:24:33 -04:00
Miroslav Jonaš 5ddd24f95e docs(misc): revert conformance doc changes from nx-cloud to nx rename (#35147)
Conformance commands should remain as `nx-cloud conformance` not `nx
conformance`.

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 18:17:47 +00:00
Nelson Dominguez dcd61cd123 fix(release): surface swallowed publish errors when stdout is not valid JSON (#35283)
## Current Behavior
<!-- This is the behavior we have today -->
When `npm publish` or `pnpm publish` fails, the executor assumes
`err.stdout` (always) contains valid JSON. If a lifecycle script (e.g.
`prepublishOnly`) fails, it writes plaintext to stdout instead. This
causes `JSON.parse` to throw and the actual error to be swallowed by the
outer catch, printing only a generic "something unexpected went wrong"
message.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Wrap `JSON.parse(err.stdout...)` in a try/catch block. If parsing fails,
fall back to logging raw stderr/stdout directly and return early, so
lifecycle script failures and other non-JSON errors are always visible
to the user.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #34497
2026-04-14 15:07:35 +02:00
Tomas Ptacek b0349877c1 fix(angular-rspack): add fileReplacements to resolve.alias (#34197)
## Current Behavior

The `fileReplacements` option in `@nx/angular-rspack` is passed to the
Angular AOT compiler but not added to rspack's `resolve.alias`
configuration. This means file replacements only work during TypeScript
compilation, not during module resolution/bundling.

## Expected Behavior

File replacements should work consistently, replacing modules both
during compilation and bundling - matching the behavior of `@nx/rspack`.

## Related Issue(s)

https://github.com/nrwl/nx/issues/32647

Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
2026-04-14 12:11:54 +02:00
Craigory Coppola ff50d97cf3 fix(core): don't cache project graph errors on daemon (#35088)
## Current Behavior
Daemon graph errors are cached until a file change triggers
recomputation

## Expected Behavior
Avoid caching daemon errors, and refresh daemon env on request start

## AI Summary
This pull request introduces several important improvements to the Nx
daemon's client-server communication, focusing on standardizing message
types, improving environment variable handling, and cleaning up imports
and type usage. The main changes include the introduction of a unified
`DaemonMessage` type, a new mechanism for synchronizing environment
variables between client and daemon, and significant import and type
refactoring for clarity and maintainability.

**Key changes:**

### Message Type Standardization

- Introduced a new `DaemonMessage` type in `daemon-message.ts` to serve
as the standard for all messages exchanged between the Nx client and
daemon, replacing the previous generic `Message` type. This includes a
type guard function `isDaemonMessage` for runtime checks.
- Updated all relevant client and server methods, such as `sendMessage`,
`sendToDaemonViaQueue`, and `sendMessageToDaemon`, to use the new
`DaemonMessage` type, ensuring type safety and consistency throughout
the codebase.
[[1]](diffhunk://#diff-dbd790ee3e31e772f1c6d54bf0681b982d9eac6adacc3149463d28e60e11f59dL7-R9)
[[2]](diffhunk://#diff-dbd790ee3e31e772f1c6d54bf0681b982d9eac6adacc3149463d28e60e11f59dL26-R25)
[[3]](diffhunk://#diff-ac77bbed6ea29034032e92619972b1ad989e0accad1b05b5462a276707549da0L1053-R1062)
[[4]](diffhunk://#diff-ac77bbed6ea29034032e92619972b1ad989e0accad1b05b5462a276707549da0R1222-R1232)

### Environment Variable Synchronization

- Implemented a new mechanism to synchronize environment variables
between the client and daemon:
- Added a `getDaemonEnv` function to centralize the construction of the
environment object.
- Modified the client to send environment variables to the daemon with
the first message after startup, and the daemon to update its
`process.env` accordingly.
[[1]](diffhunk://#diff-ac77bbed6ea29034032e92619972b1ad989e0accad1b05b5462a276707549da0R1222-R1232)
[[2]](diffhunk://#diff-ac77bbed6ea29034032e92619972b1ad989e0accad1b05b5462a276707549da0L1323-R1343)
[[3]](diffhunk://#diff-0b91261b5503dc84fcf6ba119ee1c27ae7edff8cf9535a126a6a46439179f1fdR255-R260)

### Import and Type Refactoring

- Refactored imports in both client and server files to remove unused or
redundant imports, group related imports, and improve code organization
and readability.
[[1]](diffhunk://#diff-ac77bbed6ea29034032e92619972b1ad989e0accad1b05b5462a276707549da0R45-R52)
[[2]](diffhunk://#diff-ac77bbed6ea29034032e92619972b1ad989e0accad1b05b5462a276707549da0L86-L92)
[[3]](diffhunk://#diff-ac77bbed6ea29034032e92619972b1ad989e0accad1b05b5462a276707549da0L120-L123)
[[4]](diffhunk://#diff-d5bf3c66e62cac1884a071bf07fd1991320a3e62b07bfc03af3b9557b714c892R11-R17)
[[5]](diffhunk://#diff-d5bf3c66e62cac1884a071bf07fd1991320a3e62b07bfc03af3b9557b714c892R26-R29)
[[6]](diffhunk://#diff-d5bf3c66e62cac1884a071bf07fd1991320a3e62b07bfc03af3b9557b714c892L30-L43)
[[7]](diffhunk://#diff-0b91261b5503dc84fcf6ba119ee1c27ae7edff8cf9535a126a6a46439179f1fdL4-R135)
[[8]](diffhunk://#diff-0b91261b5503dc84fcf6ba119ee1c27ae7edff8cf9535a126a6a46439179f1fdR144-R148)
[[9]](diffhunk://#diff-0b91261b5503dc84fcf6ba119ee1c27ae7edff8cf9535a126a6a46439179f1fdR161-L173)

### Project Graph Error Handling

- Improved error handling in project graph recomputation by ensuring
that if errors are encountered, the cached project graph promise is
cleared, preventing stale or erroneous state from persisting.

These changes collectively make the daemon-client architecture more
robust, maintainable, and extensible, particularly as Nx evolves to
support more complex workflows and integrations.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-14 01:10:19 -04:00
Jason Jean b1b54e9244 fix(core): inline daemon status check, drop subprocess workaround (#35273)
## Current Behavior

`nx daemon` (status, no flags) calls `generateDaemonHelpOutput`, which
`spawnSync`s a helper Node process (`exec-is-server-available.js`) just
to bridge the async `daemonClient.isServerAvailable()` probe into a sync
caller. The only caller — `daemonHandler` in
`packages/nx/src/command-line/daemon/daemon.ts` — is already `async`, so
the sync workaround isn't needed.

That subprocess also has a bug. It is spawned with `cwd: __dirname`,
which points inside `packages/nx/dist/src/daemon/client`. When `nx` is
installed via a pnpm workspace symlink from a nested workspace (e.g. a
`benchmarks` project with `"nx": "workspace:*"`), the child's `cwd`
resolves through the symlink into the parent repo. The child's
workspace-root detection walks up from there and stops at the **outer**
`nx.json`, so the probe queries the wrong workspace's socket.

Reproducer:
```
cd benchmarks
nx daemon --start   # starts benchmarks daemon, succeeds
nx daemon           # reports "Nx Daemon is not running."
```
The daemon is running — the status command is just looking at the parent
repo's socket.

## Expected Behavior

`nx daemon` reports the status of the workspace it was invoked from,
regardless of how `nx` is installed, and without paying for a second
Node process.

This PR inlines the status check directly in `daemonHandler` via `await
daemonClient.isServerAvailable()` and deletes the two helper files
(`generate-help-output.ts`, `exec-is-server-available.ts`).

Behavioral equivalence: socket errors are already resolved to `false`
inside `isServerAvailable()`, so the "running"/"not running" outputs are
byte-identical. The one deliberate change is that `VersionMismatchError`
— which `isServerAvailable()` explicitly `reject`s — now surfaces to the
caller instead of being swallowed as "not running" by the old child's
try/catch.

## Related Issue(s)

N/A — surfaced while benchmarking against a nested workspace that
symlinks `nx` from the host repo.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-13 16:52:47 -04:00
Leosvel Pérez Espinosa 7d2745fd7a chore(core): update native d.ts for json input type changes (#35274)
## Current Behavior

The `index.d.ts` for native bindings had an outdated doc comment for
`inspectInputs` that didn't reflect the `JsonFileSet` resolution added
in #35248.

## Expected Behavior

The doc comment accurately describes `JsonFileSet` resolution behavior.
2026-04-13 16:48:41 -04:00
Craigory Coppola 339328ec3c chore(repo): fix inputs for nx:test-native (#35260)
## Current Behavior
Snapshots are not an input for test:native

## Expected Behavior
Snapshots are an input for test:native
2026-04-10 18:24:50 -04:00
Craigory Coppola 789a734f98 fix(core): replace exec() with spawn() to prevent maxBuffer crash on large command output (#35256)
## Current Behavior

The `run-commands` and `run-script` executors use Node.js
`child_process.exec()` to run shell commands. `exec()` internally
buffers **all** stdout/stderr into memory and compares the total against
a `maxBuffer` limit (set to ~1GB via `LARGE_BUFFER`). When a command
produces output exceeding this limit, Node.js kills the child process
and throws `ERR_CHILD_PROCESS_STDIO_MAXBUFFER`:

```
NX   stdout maxBuffer length exceeded
Pass --verbose to see the stacktrace.
```

This is the **default code path on CI**, because
`PseudoTerminal.isSupported()` checks `process.stdout.isTTY`, which is
`false` when stdout is piped (as it is on all CI runners). So while
local development typically uses the Rust PTY path (which streams output
with no buffer limit), every `run-commands` task on CI goes through the
`exec()` fallback.

The reason this hasn't been hit more often is that the 1GB
`LARGE_BUFFER` is large enough for most commands. But commands that
produce very large output quickly (e.g., deploying a site with thousands
of files) can exceed it.

## Expected Behavior

Commands that produce arbitrarily large output should complete
successfully without crashing Nx. Output should stream through data
events with no internal buffering limit.

This PR replaces `exec()` with `spawn()` + `{ shell: true }` in both the
`run-commands` and `run-script` executors. `spawn()` provides identical
shell-based command execution but uses streaming I/O — there is no
`maxBuffer` at all. Since both executors already consumed output via
stream event listeners (`stdout.on('data')`) rather than the `exec()`
callback, this is a safe swap with no behavioral change.

The PR also includes a test that directly demonstrates the issue: the
same command that crashes `exec()` with a maxBuffer error completes
successfully under `spawn()`.

## Related Issue(s)

N/A — encountered during a deploy task producing large stdout.
2026-04-10 16:55:54 -04:00
Jason Jean 77927e15f6 feat(core): add json input type for selective JSON field hashing (#35248)
## Current Behavior

When configuring task inputs for cache hashing, Nx hashes entire files.
For JSON config files like `tsconfig.json` or `package.json`, changing
any field invalidates the cache — even fields irrelevant to the task
(e.g., changing `description` in package.json invalidates a build task).

The only special case is tsconfig, which has hardcoded selective hashing
in the native hasher.

## Expected Behavior

A new `json` input type allows users and plugins to specify exactly
which fields from a JSON file should be included in the hash. This
enables more granular cache invalidation.

```jsonc
// Only hash the "engines" field from package.json
{ "json": "{projectRoot}/package.json", "fields": ["engines"] }

// Hash all of compilerOptions except paths
{ "json": "{workspaceRoot}/tsconfig.json", "fields": ["compilerOptions"], "excludeFields": ["compilerOptions.paths"] }
```

### Features
- **`{workspaceRoot}` and `{projectRoot}` tokens** — same syntax as
`fileset` inputs
- **Glob support** — e.g. `{projectRoot}/tsconfig*.json`
- **Dot notation** — nested field paths like `compilerOptions.target`
- **Allowlist (`fields`) and denylist (`excludeFields`)** — can be used
together
- **Deterministic hashing** — canonical JSON serialization with sorted
keys

### Files changed
- **TypeScript**: `InputDefinition` type + JSON schemas for IDE support
- **Rust NAPI bridge**: `JsonInput` struct, `Either8` → `Either9`,
`Input::Json` variant
- **Rust hash planning**: `HashInstruction::JsonFileSet`, planner emits
it from `gather_self_inputs`
- **Rust hasher**: new `hash_json.rs` with field filtering, canonical
serialization, and 10 unit tests

## Related Issue(s)

<!-- Link related issues here -->

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-10 16:55:34 -04:00
Caleb Ukle 686b523f32 docs(misc): add troubleshooting guide for Nx in Claude Code sandbox (#35255)
add a kb article explaining using nx w/ claude code sandboxes w/ the
recommended fix (`allowAllUnixSockets: true`) and alternative
workarounds with their tradeoffs.

added some cross-linking to this article so users can discover it in ai
specific pages


Fixes DOC-456

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: barbados-clemens <barbados-clemens@users.noreply.github.com>
2026-04-10 15:18:53 -05:00
Leaf Rogers f9ceff0cc7 docs(core): fix minor typo (#35246)
Just a small typo fix 🎁
2026-04-10 16:14:24 -04:00
FUASHI LOT-BILL 536a30703a chore(core): update dotenv-expand to 12.0.3 (#35232) 2026-04-10 16:12:14 -04:00
Caleb Ukle 3629b377d1 fix(nx-dev): seo improvements for nx.dev/docs (#35244)
The nx.dev/docs site has several SEO issues a few minor a few larger
impacts.

Changes:

- **robots.txt** is served by Next.js with correct `Sitemap:
https://nx.dev/sitemap.xml` and explicit AI crawler policies
- **AI crawlers** (GPTBot, ClaudeBot, Google-Extended, PerplexityBot,
OAI-SearchBot) have explicit Allow rules
- **llms.txt** and **llms-full.txt** are served correctly
(Astro-generated, proxied via Next.js)
- **Sitemap index** has no duplicate entries 
- **Every docs page** has BreadcrumbList + TechArticle JSON-LD schema
markup
- **Logo images** are eager-loaded (`loading="eager"`), improving mobile
LCP
- **Docs sitemap** includes `lastmod` dates (build timestamp)
- **Security headers** (Referrer-Policy, Permissions-Policy) set on
Astro docs
- **Page title** expanded to 47 chars with key terms
- **Twitter card** meta tags explicitly set on all docs pages
- **CSS bundle** no longer scans unused @nx/nx-dev-ui-icons and
@nx/nx-dev-ui-animations packages

## Related Issue(s)

closes DOC-473

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 19:33:04 +00:00
Jack Hsu a53e6509b4 docs(misc): add back Cloud CNW CTA in the CI tutorial (#35257)
Add the CTA back as an option for users to quickly get a repo up and
running to try the CI tutorial.

Preview:
https://deploy-preview-35257--nx-docs.netlify.app/docs/getting-started/tutorials/self-healing-ci-tutorial
2026-04-10 15:24:01 -04:00
Caleb Ukle 9d50dc0c51 docs(release): add CLI reference links to Nx Release guides (#35245)
## Current Behavior

The Nx Release guide pages (`/docs/features/manage-releases` and
`/docs/guides/nx-release/*`) reference CLI commands like `nx release`,
`nx release version`, `nx release publish`, etc. but don't link to their
CLI reference pages. Developers configuring CI pipelines have to
navigate separately to find the full list of available flags and
options.

## Expected Behavior

Release guide pages now link to the relevant CLI reference pages
(`/docs/reference/nx-commands#nx-release-*`) so developers can quickly
look up available flags and options while reading the guides.

Changes across 8 files:

- **`features/manage-releases.mdoc`** — Added all 5 release subcommand
links to the "References" section
- **`publish-in-ci-cd.mdoc`** — Linked `nx release`, `nx release
publish`, `nx release version`, and `nx release changelog` where
subcommands are introduced
- **`file-based-versioning-version-plans.mdoc`** — Linked `nx release
plan` where the command is introduced
- **`release-projects-independently.mdoc`** — Linked `nx release` CLI
reference near the `--projects` flag discussion
- **`release-groups.mdoc`** — Linked `nx release` CLI reference in the
filters section
- **`release-docker-images.mdoc`** — Linked `nx release` CLI reference
near docker-specific flags
- **`programmatic-api.mdoc`** — Linked `nx release` CLI where the CLI is
mentioned as the counterpart to the programmatic API
- **`automatically-version-with-conventional-commits.mdoc`** — Linked
`nx release version` CLI reference for versioning options

## Related Issue(s)

<!-- Linear issue DOC-472: Add CLI reference links from Nx Release guide
-->


🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: barbados-clemens <barbados-clemens@users.noreply.github.com>
2026-04-10 14:55:36 -04:00
Leosvel Pérez Espinosa 4d207509c9 chore(nx-dev): exclude .next from jest haste-map crawl (#35249)
## Current Behavior

The `nx-dev:test` task reads 124 `.js`/`.ts` files from the `.next/`
build output directory. These reads come from jest-haste-map's
filesystem crawl — it scans all files under the project root matching
`moduleFileExtensions` to build its module index and compute SHA-1
hashes, and `.next/` is not excluded.

## Expected Behavior

Jest should not scan the `.next/` build output directory during its
haste-map crawl, as these files are not needed for test discovery or
execution.

## Fix

Add `modulePathIgnorePatterns: ['<rootDir>/.next']` to the nx-dev jest
config. This tells jest-haste-map to skip the `.next/` directory
entirely during its initial filesystem crawl, eliminating all 124
unexpected file reads.
2026-04-10 14:52:06 -04:00
Jason Jean e6e2f4bfac fix(nextjs): align nx-dev build inputs and update plugin defaults (#35238)
## Current Behavior

- The Next.js plugin infers `default` instead of `production` for build
inputs, meaning test file changes invalidate the build cache
unnecessarily
- The Next.js plugin doesn't infer `.d.ts` dependent task output files,
causing sandbox violations when builds read type declarations from
dependencies
- nx-dev's `project.json` overrides inputs but is missing
`externalDependencies: [next]` and `.d.ts` dependent task outputs that
the plugin would normally provide
- nx-dev's `next:build` doesn't depend on `^build` or `^typecheck`, so
dependency type declarations aren't produced before the build
- `banner.json` (a generated file) was listed as a fileset input instead
of a dependent task output file
- `banner.json` was not excluded from eslint

## Expected Behavior

- Next.js plugin uses `production` for build inputs (matching Vite)
- Next.js plugin infers `dependentTasksOutputFiles: **/*.d.ts` for
builds
- nx-dev build has all necessary inputs and dependsOn to work correctly
with the sandbox
- Generated files are properly handled as dependent task outputs

## Related Issue(s)

N/A - discovered during sandbox violation investigation
2026-04-10 14:44:44 -04:00
Jack Hsu 44ba349fbf fix(js): suppress false swc-node/ts-node warning on Node 22.18+ (#35247)
On Node 22.18+ (which supports native TypeScript execution via type
stripping), Nx incorrectly warns "Unable to locate swc-node or ts-node.
Nx will be unable to run local ts files without transpiling." even
though Node can handle .ts files natively without any transpiler.

The warning should only appear on Node versions that cannot natively
execute TypeScript files. On Node 22.6+ (where
process.features.typescript is truthy), no warning should be emitted
when swc-node and ts-node are absent.

Fixes #32567

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2026-04-10 14:24:11 -04:00
Jason Jean c1cbdbbfaf chore(repo): update workspace-plugin dependencies (#35253)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

Somehow, these versions got out of sync

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

These versions are in sync with the package.json in the root

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-10 14:02:54 -04:00
Jack Hsu ef8da7bd26 fix(angular-rspack): normalize Windows path separators for i18n (#35252)
## Current Behavior
On Windows, `nx serve project --configuration=some-i18n-configuration`
fails with "cannot find project in graph.nodes" because
`posix.normalize()` does not convert backslashes to forward slashes.
Windows-style paths from `path.relative()` produce backslash-separated
strings that never match the forward-slash keys in the project root map.

## Expected Behavior
i18n configurations should resolve correctly on Windows. The path lookup
in `findProjectForPath` should succeed regardless of whether the input
path uses backslashes (Windows) or forward slashes (POSIX).

## Related Issue(s)
Fixes #32864

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
2026-04-10 13:33:10 -04:00
Leosvel Pérez Espinosa 931f86c25a fix(vitest): add dependent task output files as inputs for vitest test targets (#35242)
## Current Behavior

Vitest test targets can resolve workspace dependency imports to build
artifacts (e.g., `dist/*.js`). These build outputs aren't declared as
task inputs, causing sandbox I/O violations (unexpected reads from
dependency `dist/` directories).

## Expected Behavior

The vite and vitest plugins include `dependentTasksOutputFiles` in the
inferred test target inputs. This declares dependency build outputs as
inputs when a test target depends on build tasks via `dependsOn`. The
input is a no-op when there are no build task dependencies. When
`typecheck.enabled` is configured, `.d.ts` files are also included since
`tsc --noEmit` reads type declarations from dependencies.

Also removes redundant explicit `inputs` overrides from `angular-rspack`
and `angular-rspack-compiler` test targets — the plugin-inferred
defaults are now more complete.
2026-04-10 12:30:19 -04:00
Jason Jean 23891a3d0a chore(repo): update nx to 22.7.0-beta.12 (#35250)
Updating Nx from 22.7.0-beta.11 to 22.7.0-beta.12
2026-04-10 16:08:56 +00:00
Jack Hsu a7f170c3aa docs(misc): review remote cache docs and fix powerpack redirect (#35240)
## Current Behavior

- `/powerpack` redirects to the `/enterprise` marketing page, which
doesn't help users who have an expired or missing activation key for
shared cache plugins (`@nx/s3-cache`, `@nx/gcs-cache`, etc.)
- The self-hosted caching guide lists available packages but requires
clicking through to individual reference pages to find install commands
and key setup instructions
- No short, stable URL exists for CLI error messages to link to

## Expected Behavior

- `/powerpack` redirects to the self-hosted caching guide
(`/docs/guides/tasks--caching/self-hosted-caching`), which is the right
landing page for cache plugin users
- `/powerpack/conformance` and `/powerpack/owners` redirect to their
respective enterprise docs pages
- A new `/remote-cache` short URL points to the self-hosted caching
guide for use in CLI error messages (e.g., the `nx-key` hardcoded link
in ocean)
- The self-hosted caching page includes a table with install commands
(`nx add @nx/...`), key setup instructions (`.nx/key/key.ini`, `NX_KEY`
env var, `nx register`), and a section pointing former Powerpack users
to conformance/owners enterprise docs

Preview:
https://deploy-preview-35240--nx-docs.netlify.app/docs/guides/tasks--caching/self-hosted-caching

## Related Issue(s)

Fixes DOC-477

**Follow-up:** The `nx-key` hardcoded link in the ocean repo
(`libs/nx-packages/nx-key/src/consts.rs` L1) should be updated to
`nx.dev/remote-cache` in a separate PR.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-10 11:06:35 -04:00
Leosvel Pérez Espinosa dc479c50a5 fix(js): stop generating baseUrl in tsconfig, use ./ prefix for path mappings (#34965)
## Current Behavior

Nx generators set `compilerOptions.baseUrl: "."` in generated tsconfig
files and write path mappings as bare relative paths (e.g.,
`my-lib/src/index.ts`). `baseUrl` is deprecated in TS 6 and removed in
TS 7.

## Expected Behavior

Nx no longer generates `baseUrl` in any tsconfig. Path mappings use `./`
prefix (e.g., `./my-lib/src/index.ts`), making them relative to the
tsconfig file without needing `baseUrl`. Existing user tsconfigs with
`baseUrl` continue to work correctly.

### Generator and template changes
- Remove `baseUrl` from all templates and generator code
- `addTsConfigPath` normalizes lookup paths with `./` prefix
- Move and remove generators handle `./`-prefixed paths correctly
- Angular secondary entry points and Remix server entry paths use `./`
prefix

### `resolvePathsBaseUrl` helper
New function (in `ts-config.ts`, duplicated in `register.ts`) that walks
the tsconfig `extends` chain to determine the correct directory for
resolving `paths` values. Finds where `paths` is defined, then looks for
the applicable `baseUrl` from that point toward the root — ignoring
child overrides that don't apply to the paths-defining tsconfig. When no
`baseUrl` applies, returns the directory of the tsconfig that defines
`paths`. All path resolver plugins and buildable-libs-utils use this
helper.

### Runtime and bundler fixes for baseUrl-less tsconfigs
- **Rollup**: resolve path mappings to absolute in compiler options
override using `resolvePathsBaseUrl`; use original tsconfig path (not
tmp) for resolution base
- **Module Federation**: add `workspaceRoot` to `resolve.modules` in all
8 MF plugin variants (Angular/React webpack, Angular/React rspack,
webpack SSR, rspack SSR, Angular rspack plugin, rspack plugin) so
workspace-relative expose paths resolve without `baseUrl`
- **Next.js/Jest**: null out SWC `resolvedBaseUrl` in generated jest
configs to prevent SWC from doing incorrect path alias resolution; Nx
jest resolver handles this via `resolvePathsBaseUrl`
- **register.ts**: use `resolvePathsBaseUrl` for correct path alias
registration
- **Path resolver plugins** (webpack, rspack, vite, expo, react-native,
jest, react component testing): use `resolvePathsBaseUrl` for correct
path resolution
- **buildable-libs-utils**: resolve tmp tsconfig paths to absolute so
they work without `baseUrl`
- **eslint-plugin**: handle `./`-prefixed paths in AST utils

## Related Issue(s)

Fixes #32958
2026-04-10 16:33:02 +02:00
Caleb Ukle a0621c8864 fix(nx-dev): improve search ranking for reference pages (#35243)
## Current Behavior

Searching for `nx.json` on nx.dev/docs does not surface the actual
nx.json reference page in the top results. The `.NET Plugin for Nx` and
other technology introduction pages rank higher because they have
`weight: 5` in frontmatter, which inflates their body text scoring via
Pagefind's `data-pagefind-weight` attribute (~25x impact via quadratic
scaling). The same issue affects `project.json`, `inputs`, and other
reference pages.

## Expected Behavior

Reference pages whose title exactly matches the search query should rank
first or near the top. Technology introduction pages should still be
discoverable for their framework name but should not outrank reference
pages for terms they merely mention in body text.

**After this change:**
- `nx.json` → "nx.json Reference" is `#1` (was `#7+`)
- `project.json` → "Project Configuration" is `#1`
- `angular` → "Angular Plugin for Nx" is `#1` (preserved)
- `nest` → "Nest.js Plugin for Nx" is `#1` (preserved)
- `react` → "React Plugin for Nx" is `#2` (React Native `#1` — valid
match)

**Changes:**
- Reduce `weight` on 35 technology introduction pages from `5` → `2` to
reduce body text inflation while keeping a modest boost
- Reduce `weight` on adding-to-monorepo guide from `6.4` → `2`
- Adjust Pagefind ranking params: `termFrequency: 0.75 → 0.65`,
`pageLength: 0.5 → 0.3` to reduce the penalty on long reference pages

## Related Issue(s)

Fixes DOC-475

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 09:18:17 -05:00
Jason Jean a884f55201 fix(linter): add missing inputs to eslint executor target defaults (#35236)
## Current Behavior

After the custom eslint hasher was removed in d64aeef5df, the
`@nx/eslint:lint` executor target defaults are missing `^default` and
`{workspaceRoot}/tools/eslint-rules/**/*` from their inputs. The old
custom hasher was compensating for this by manually handling dependency
hashing, but now that it's gone, the inputs need to be self-sufficient.

This means:
- Changes in dependencies don't invalidate the lint cache (problematic
for type-aware rules that inspect imported types)
- Changes to custom workspace eslint rules don't trigger re-linting

The inferred/plugin path (`plugin.ts`) already includes these inputs
correctly.

## Expected Behavior

The executor target defaults should include `^default` (dependency file
changes) and `{workspaceRoot}/tools/eslint-rules/**/*` (custom workspace
rules) to match what the eslint plugin already sets for inferred
targets.

## Related Issue(s)

Follows up on d64aeef5df (remove custom eslint hasher).

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-04-10 00:11:15 -04:00
Craigory Coppola c782489023 fix(core): exclude populate-local-registry-storage from sandbox I/O checks (#35239)
## Current Behavior

The `populate-local-registry-storage` target has massive I/O violations
in sandbox reports (~8k unexpected reads, ~7.6k unexpected writes)
because it runs `nx-release --local` which reads all build outputs and
writes version-bumped packages across the entire workspace.

## Expected Behavior

Exclude this target from sandbox I/O validation until it is reworked to
have more focused I/O boundaries. The task is doing too much in a single
step to have meaningful input/output declarations.

## Related Issue(s)

Sandbox violation report:
https://staging.nx.app/runs/ffYkwp6x9i/task/%40nx%2Fnx-source%3Apopulate-local-registry-storage/sandbox-report-raw?sandboxReportId=59a38b85-b0f4-40cc-a914-5383abe98adf&workspaceId=62d013ea0852fe0a2df74438
2026-04-09 18:25:56 -04:00
Miguel 0fb3d8c26e chore(core): skip connect to cloud prompt during migration if neverConnectToCloud is set (#33717) 2026-04-09 18:17:26 -04:00
Craigory Coppola ea644b3e49 feat(core): prompt for setup mode when running nx init in empty git directory (#35226)
## Current Behavior

When running `nx init` in an empty git directory (no `package.json`),
the V2 init handler silently defaults to the `.nx` installation method
without prompting the user. This may not be suitable for users who
intend to create a JavaScript/TypeScript project and would prefer a
`package.json`-based setup.

## Expected Behavior

When running `nx init` in an empty git directory, users are now prompted
to choose between two setup methods:

- **`.nx installation`** — recommended for non-JavaScript projects
(Gradle, .NET, etc.)
- **`package.json installation`** — recommended for
JavaScript/TypeScript projects

The prompt only appears when:
- No `package.json` exists in the directory
- The `--useDotNxInstallation` flag was not explicitly passed
- Running in interactive mode (not AI agent mode)

If the user chooses `package.json`, a minimal `package.json` is created
and the existing npm-repo setup flow takes over. If they choose `.nx`,
the existing dot-nx setup flow is used. In both cases, the workspace is
created in the current directory (not a subfolder).

## Related Issue(s)

Fixes NXC-3983
2026-04-09 17:29:54 -04:00
Craigory Coppola 7edc7b1c44 fix(core): overwrite inferred script target when nx prop defines executor or command (#35227)
## Current Behavior

When a `package.json` has both a script entry and an `nx.targets` entry
for the same target name, and the `nx.targets` entry uses command
shorthand (`command: "tsc"`) or an explicit executor, the two targets
are merged together. This produces an invalid hybrid target that has
both `executor: "nx:run-script"` (from the inferred script target) and
the `command` property (from the nx prop), causing the node to fail to
merge into the project graph.

## Expected Behavior

When the `nx.targets` entry specifies how to run (via `executor` or
`command`), it should completely overwrite the inferred script target
instead of merging with it. Targets without `executor` or `command`
(e.g., just adding `outputs` or `dependsOn`) should continue to merge as
before.

## Related Issue(s)

Fixes NXC-3923

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-09 17:28:16 -04:00
Jack Hsu 2a6ecc4437 fix(misc): bump axios to 1.15.0 for all packages (#35237)
Closes #NXC-4237
2026-04-09 19:48:25 +00:00
Craigory Coppola 069ed82686 fix(core): add run-native-target script input to dotnet build-analyzer (#35221)
## Current Behavior

The `dotnet:build-analyzer` target runs `node
./scripts/run-native-target.js _build-analyzer dotnet` but does not
include the `run-native-target.js` script itself in its `inputs` array.
This means changes to the script won't invalidate the Nx cache,
potentially leading to stale cached results.

## Expected Behavior

The `run-native-target.js` script is included as an input to the
`build-analyzer` target, ensuring cache correctness when the script
changes.

## Related Issue(s)

Fixes NXC-4219
2026-04-09 15:11:17 -04:00
Craigory Coppola 5878f36aea feat(core): add source map annotations to nx show target (#35225)
Large refactor for `nx show target` to increase clarity and fixup a few
issues.

## Custom Hashers

Notes when a custom hasher is used and inputs will not be considered.
See screenshots

<img width="646" height="107" alt="image"
src="https://github.com/user-attachments/assets/a6fecf17-a512-4956-964b-f04557567334"
/>

<img width="646" height="225" alt="image"
src="https://github.com/user-attachments/assets/0d4e0a85-2a8b-4bac-b0ff-4b6ea9e6b0dd"
/>

## Duplicated inputs
- Fixes issue where inputs could show up multiple times and appear
identical. In practice, this was from filesets that had specific
projects arrays that differed.

e.g. `nx show target populate-local-registry-storage` in latest shows

```
Inputs:
  - !{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)
  - !{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)
  - !{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)
  - !{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)
  - !{projectRoot}/.eslintrc.json
  - !{projectRoot}/.eslintrc.json
  - !{projectRoot}/.storybook/**/*
  - !{projectRoot}/.storybook/**/*
  - !{projectRoot}/jest.config.[jt]s
  - !{projectRoot}/jest.config.[jt]s
  - !{projectRoot}/src/test-setup.[jt]s
  - !{projectRoot}/src/test-setup.[jt]s
  - !{projectRoot}/tsconfig.spec.json
  - !{projectRoot}/tsconfig.spec.json
  - !{projectRoot}/tsconfig.storybook.json
  - !{projectRoot}/tsconfig.storybook.json
  - default
  - default
  - {projectRoot}/**/*.rs
  - {projectRoot}/**/Cargo.*
  - {workspaceRoot}/scripts/local-registry
  - {"runtime":"node -p '`${process.platform}_${process.arch}`'"}
  - {"runtime":"rustc --version"}
  - {"externalDependencies":["npm:@monodon/rust","npm:@napi-rs/cli"]}
```

After this PR it is
```
Inputs:
  - {projectRoot}/**/*.rs
  - {projectRoot}/**/Cargo.*
  - {workspaceRoot}/.cargo/config.toml
  - {workspaceRoot}/Cargo.lock
  - {workspaceRoot}/Cargo.toml
  - {workspaceRoot}/clippy.toml
  - {workspaceRoot}/scripts/local-registry
  - {"input":"production","projects":["tag:npm:public"]}
  - {"input":"production","projects":["tag:maven:dev.nx.maven"]}
  - {"runtime":"node -p '`${process.platform}_${process.arch}`'"}
  - {"runtime":"rustc --version"}
  - {"externalDependencies":["npm:@monodon/rust","npm:@napi-rs/cli"]}
Outputs:
  - {workspaceRoot}/dist/local-registry/storage
```

## Others

- When running with --verbose, data includes its source location. 
- Changes defaultConfiguration display to `(default)` badge after a
config if it is indeed default, instead of (default: ...) at the end of
the list

Fixes NXC-4077
Fixes NXC-4068
Fixes NXC-4200

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
2026-04-09 15:10:45 -04:00
Leosvel Pérez Espinosa 3ece32d6b9 fix(linter): infer extended tsconfig files as task inputs (#35190)
## Current Behavior

The `@nx/eslint` plugin does not infer extended tsconfig files as inputs
for the inferred lint target. Projects whose `tsconfig.json` extends a
file outside the project root (e.g. `../../tsconfig.base.json`) omit
that upstream file from the lint task's inputs. Tools that walk the
tsconfig chain during linting — the typescript-eslint parser in
type-aware mode, `@nx/enforce-module-boundaries`, Angular template
parsers, and similar — read these files, so sandboxing reports them as
undeclared reads and changes to upstream tsconfigs don't invalidate the
lint cache.

## Expected Behavior

The inferred lint target declares every tsconfig file reached via the
`extends` chain of the project's `tsconfig.json` as an input, when those
files live outside the project root. Files inside the project root are
already covered by `default` (`{projectRoot}/**/*`); shareable configs
resolved from `node_modules` are invalidated via the lockfile; paths
that escape the workspace cannot be declared as `{workspaceRoot}/...`
inputs.

A new lightweight `walkTsconfigExtendsChain` helper is introduced in
`@nx/js/src/internal` so other plugins that need to inspect a tsconfig
extends chain can reuse it. It reads tsconfigs as JSONC without loading
the `typescript` package, walks `extends` arrays in reverse precedence
(matching TypeScript semantics), and accepts a visitor that can
short-circuit for precedence-aware lookups or walk exhaustively for
input collection. The helper is cycle-safe and accepts a caller-supplied
JSON cache to dedupe reads across overlapping chains.
2026-04-09 15:02:36 -04:00
Leosvel Pérez Espinosa 169c1d4a79 fix(testing): add dependent .d.ts inputs for ts-jest without isolatedModules (#35231)
## Current Behavior

When ts-jest runs without `isolatedModules`, it creates a TypeScript
Language Service that reads `.d.ts` files from dependency projects.
Changes to those `.d.ts` files don't invalidate the test cache, leading
to stale test results.

## Expected Behavior

The jest plugin detects ts-jest usage without `isolatedModules` and adds
`dependentTasksOutputFiles: '**/*.d.ts'` as a transitive input. This
ensures dependency type declaration changes correctly invalidate the
test cache.

The plugin:
- Inspects jest config transforms (including presets) for ts-jest
- Walks the tsconfig `extends` chain to resolve the effective
`isolatedModules` value, reusing the lightweight walker from `@nx/js`
(intentionally avoids loading `typescript` for performance)
- Handles `verbatimModuleSyntax` as implying `isolatedModules`
- Respects ts-jest v29 vs v30 semantics for the deprecated
`isolatedModules` transform option
- Mirrors `ts.findConfigFile` upward walk (capped at workspace root)
when no explicit tsconfig is configured
- Tracks external file references (presets, tsconfigs outside project
root) for correct hash computation

## Additional Changes

- **Plugin hash correctness**: config loading moved before hash
computation so external file references (preset files, tsconfig extends
chains) are included in the hash. Previously, changes to files outside
the project root that the plugin reads during inference (e.g., a shared
jest preset or base tsconfig) would not invalidate the cached target
configuration. The lockfile is also now included as a hash input.
- **e2eInputs**: added `dependentTasksOutputFiles` to the `e2eInputs`
named input in `nx.json` since e2e target defaults override
plugin-inferred inputs.
- **Jest preset**: pre-resolve the `@swc-contrib/mut-cjs-exports` SWC
plugin to an absolute path. Tests that `chdir` into temp dirs would fail
SWC plugin resolution since the temp dir has no `node_modules`.
2026-04-09 15:02:00 -04:00
Juri Strumpflohner 5a7e851ce1 docs(nx-dev): blog post on sharing Tailwind styles in a monorepo (#35229)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: juristr <juristr@users.noreply.github.com>
2026-04-09 14:30:06 +00:00
Jack Hsu 1851fe884e fix(js): include npm overrides in generated lockfile (#35192)
## Current Behavior

When using `generateLockfile: true` (e.g. with `@nx/next:build`), the
generated `package-lock.json` is missing overridden packages for two
reasons:

1. `normalizePackageJson()` strips the `overrides` field, so the
generated lockfile lacks overrides both at the top level and in
`packages[""]`.

2. `findTarget()` uses semver satisfaction to match dependency edges,
but npm overrides can force versions outside the declared range (e.g.
`minimatch@^9.0.4` overridden to `10.2.1`). This causes overridden
packages and their transitive deps to be dropped from the pruned graph
entirely.

Running `npm ci` in the output directory fails:
```
npm error Missing: minimatch@10.2.1 from lock file
```

Note: yarn (`resolutions`) and pnpm (`pnpm.overrides`) were already
working correctly.

## Expected Behavior

The generated `package-lock.json` includes `overrides` and all
overridden packages. `npm ci` succeeds.

This is tested in the original issue repro repo, where with the applied
patch `npm ci` works from dist.

<img width="1272" height="362" alt="image"
src="https://github.com/user-attachments/assets/2cdbb266-71f8-45c8-8ee0-cec6dcd12705"
/>

The missing parts are both `overrides` in `package.json`, but also the
lockfile must include the pacakges in the overrides. In this example it
is like this in `package-lock.json`:

```
    "node_modules/minimatch": {
      "version": "10.2.1",
      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.1.tgz",
      "integrity": "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==",
      "license": "BlueOak-1.0.0",
      "dependencies": {
        "brace-expansion": "^5.0.2"
      },
      "engines": {
        "node": "20 || >=22"
      },
      "funding": {
        "url": "https://github.com/sponsors/isaacs"
      }
    },
```

## Related Issue(s)

Fixes #34529
2026-04-09 10:11:06 -04:00
Leosvel Pérez Espinosa d64aeef5df fix(linter): remove custom eslint hasher
The custom eslint hasher was an optimization from the Node.js hashing
era that stripped dependency file hashes for non-type-aware lint rules.
With the native Rust hasher this optimization is no longer needed.

Deprecate `hasTypeAwareRules` option (removal in v23).
2026-04-09 09:26:13 -04:00
Craigory Coppola fd0dff1078 fix(core): add missing build inputs for angular-rspack example projects
The example builds read dependent task outputs (devkit, angular-rspack,
angular-rspack-compiler dist JS files) and shared workspace files
(patch-devkit-request-path.js, tsconfig.base.json) that were not
declared as inputs, causing sandbox violations.
2026-04-09 09:23:42 -04:00
Craigory Coppola 3e373612ea fix(angular): add storybook and playwright as implicit dependencies (#35224)
## Current Behavior

The `angular:test` task sandboxing run shows unexpected file reads from
`@nx/storybook` and `@nx/playwright` packages — including `.template`
and `__tmpl__` generator files. These packages are loaded dynamically
via `ensurePackage()` calls (in `generate-storybook-configuration.ts`
and `add-e2e.ts`), so the static dependency analyzer doesn't detect them
as dependencies of the angular project. This means their files are not
included in the test task's input hash, causing sandbox violations.

Relevant run: [staging.nx.app task
run](https://staging.nx.app/runs/B5EjkJA6p1/task/angular%3Atest?batchId=77ad1700-bc33-4663-b344-cbfab899c6c4)

## Expected Behavior

The `storybook` and `playwright` projects are declared as
`implicitDependencies` of the angular project (alongside the existing
`vite` entry which exists for the same reason). This ensures their files
are included in the angular test task's input hash via the `^production`
input qualifier, resolving the sandbox failures.

## Related Issue(s)

Fixes NXC-4216
2026-04-09 08:25:15 +02:00
Craigory Coppola d4c55d806a fix(core): add vale-changed.mjs script to vale target inputs
NXC-4218
2026-04-08 19:39:34 -04:00
Jack Hsu baaa14ea57 fix(misc): stream Framer proxy responses and add edge function timing (#35215)
## Current Behavior

Framer proxy buffers entire HTML response before URL rewriting and
returning to client. No CDN caching on proxied responses. No timing
instrumentation for diagnosing slow page loads.

## Expected Behavior

- **Streaming URL rewrite** via `TransformStream` — reduces TTFB by
sending chunks as they arrive (handles cross-chunk boundary matches)
- **Edge CDN caching** — `Netlify-CDN-Cache-Control` with
`stale-while-revalidate` on Framer/blog proxied responses only (docs
analytics unaffected)
- **Timing instrumentation** — console logs in all edge functions +
`Server-Timing` headers on docs functions for DevTools visibility

## Lighthouse scores

Before: 

<img width="739" height="1056" alt="image"
src="https://github.com/user-attachments/assets/ee5c383c-5ba8-41d1-b568-bcbec9e7cb73"
/>


After:

<img width="703" height="1031" alt="image"
src="https://github.com/user-attachments/assets/67366145-35f8-476c-a333-8a0848c9aee3"
/>
2026-04-08 19:26:07 -04:00
Craigory Coppola fd5d210151 fix(core): add prettier config inputs to astro-docs format target (#35222)
## Current Behavior

The `astro-docs:format` target is cached but has no explicit `inputs`,
so it uses the default named input which only tracks
`{projectRoot}/**/*`. Changes to workspace-root prettier configuration
files (`.prettierrc`, `.prettierignore`) don't invalidate the cache,
potentially returning stale format check results.

## Expected Behavior

The `format` target explicitly lists its inputs: the `.mdoc` files it
formats and the prettier config files that control formatting behavior.
This ensures the cache invalidates correctly when prettier configuration
changes.

## Related Issue(s)

Fixes NXC-4217
2026-04-08 19:10:57 -04:00
Craigory Coppola 8f8fc344e9 fix(core): ensure build tasks use copyReadme named input (#35217)
## Current Behavior

Three packages (`angular-rspack-compiler`, `angular-rspack`, `esbuild`)
have build targets that run `copy-readme.js` but don't correctly declare
all required inputs:

- `angular-rspack-compiler` and `angular-rspack` manually listed partial
inputs (missing `.prettierignore` and using a bare directory path
`{workspaceRoot}/scripts/readme-fragments` that doesn't resolve to
files)
- `esbuild` had no inputs at all for its build target, falling back to
the default `["production", "^production"]`

This means changes to `.prettierignore` or `readme-fragments/*.md` would
not invalidate the build cache for these projects.

## Expected Behavior

All three packages use the `copyReadme` named input (defined in
`nx.json`), consistent with every other package in the repo. This
ensures `.prettierignore`, `readme-fragments/**/*`, `copy-readme.js`,
and project README files are all correctly tracked as build inputs.

## Related Issue(s)

Fixes NXC-4214
2026-04-08 16:03:43 -04:00
Jack Hsu 1526f89612 feat(core): use CNW variant 1 cloud prompt in nx init (#35155)
## Current Behavior

`nx init` uses a different cloud prompt (`code: "enable-ci"`, message:
"Would you like to enable AI-powered Self-Healing CI and Remote
Caching?") with only Yes/Skip choices. There is no way to permanently
opt out of the prompt, and package manager install output is printed
during init.

## Expected Behavior

`nx init` now uses CNW's variant 1 prompt copy:
- **Prompt:** "Enable remote caching to speed up builds with Nx Cloud?"
- **Footer:** "Free for small teams. 2-minute setup with GitHub — cache
locally and in CI"
- **Choices:** Yes / Skip for now / No, don't ask again

Behavior per choice:
| Choice | Action |
|--------|--------|
| **Yes** | Connect to Nx Cloud (set `nxCloudId`) |
| **Skip for now** | Do nothing |
| **No, don't ask again** | Set `neverConnectToCloud: true` in nx.json |

Additional changes:
- Telemetry now tracks the raw choice via `nxCloudArg` field
(yes/skip/never)
- `nx migrate` cloud prompt also supports the "never" choice
- Install stdout suppressed during init (stderr preserved for errors)
<img width="1392" height="978" alt="init"
src="https://github.com/user-attachments/assets/fdacc72b-64ed-4e87-b4c3-01a467051e24"
/>

## Related Issue(s)

Fixes NXC-4189
2026-04-08 14:38:38 -04:00
Craigory Coppola 703e2ee595 fix(core): prevent phantom connections and dead polling in plugin workers (#34823)
The setInterval(async, 10) polling loop used to connect to plugin
workers created overlapping connection attempts because setInterval does
not await its callback. This caused phantom socket connections, event
loop saturation, and cascading worker deaths.

Changes:
- Replace setInterval with recursive setTimeout so only one connection
attempt is ever in flight at a time
- Close the worker's server on first connection to reject phantom
connections that would create duplicate load timeouts
- Detect worker exit during polling and reject immediately instead of
burning through 10,000 attempts against a dead socket
- Clear _connectPromise on failure so ensureAlive() retries instead of
re-awaiting a permanently rejected promise

Fixes: #34388

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-08 14:13:36 -04:00
Craigory Coppola 51cd80af1d chore(repo): issues scraper closures fix (#35195)
## Current Behavior
`closed` issues weren't scraped correctly. If no prior data was present,
we scraped the full repo which caps at 10k issues, when we have 11k now.

## Expected Behavior
Scraping works consistently

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-04-08 14:03:05 -04:00
Jason Jean 1334f75da3 fix(core): add missing inputs and sandbox exclusions for native tasks (#35212)
## Current Behavior

The `native` named input in `nx.json` only includes
`{projectRoot}/**/Cargo.*`, which misses workspace-root files that
Cargo/Clippy read: `Cargo.toml` (workspace manifest), `Cargo.lock`,
`clippy.toml`, and `.cargo/config.toml`. This means changes to these
files don't invalidate the cache for native tasks.

Additionally, Cargo writes intermediate build artifacts to
`dist/target/` which causes sandbox violations in Nx Cloud CI.

## Expected Behavior

Native task cache is correctly invalidated when workspace-root
Cargo/Clippy config files change. Sandbox no longer flags `dist/target/`
reads/writes as violations.

## Related Issue(s)

N/A

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-08 13:44:07 -04:00
Jason Jean 68d357fd45 chore(module-federation): disable webpack-incompatible react e2e suites (#35214)
## Current Behavior

React module federation e2e suites that exercise webpack generation
paths are failing because webpack 5.106.0 removed
`lib/util/create-schema-validation.js`, which
`@module-federation/enhanced@2.3.1` still depends on.

## Expected Behavior

These known-broken suites should be skipped until the upstream module
federation dependency is compatible with webpack 5.106.0+ so they do not
keep failing CI.

## Related Issue(s)

N/A

## Summary

This PR temporarily disables the affected React module federation e2e
suites by switching their top-level test groups to `describe.skip(...)`
and adding a short note about the webpack /
`@module-federation/enhanced` incompatibility.

It covers the webpack-specific suites as well as the mixed Rspack
interoperability/convert flows that still generate webpack-based module
federation apps.

## Validation

- `npx prettier --write` on the modified test files
- `git push -u origin fix/disable-mf-webpack-tests`
- repository pre-push hook passed during push (`nx` prepush checks)

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-04-08 13:38:11 -04:00
Jason Jean 9c76cf199a fix(core): use fresh package manager cache for e2e tests (#35211)
## Current Behavior

When e2e tests publish packages to the local Verdaccio registry with the
same version number (e.g., `23.0.0`), npm and yarn serve stale cached
tarballs from previous test runs instead of fetching the freshly
published packages. This causes e2e tests to run against outdated code.

## Expected Behavior

Each e2e test run uses a fresh package manager cache directory, ensuring
that npm and yarn always fetch the latest packages from the local
registry — even when the version number hasn't changed.

- **npm**: `npm_config_cache` set to a temp directory
- **yarn v1**: `YARN_CACHE_FOLDER` set to a temp directory
- **yarn v2**: `YARN_ENABLE_GLOBAL_CACHE` set to `false`
- **pnpm**: not affected (content-addressed store)
- **bun**: already handled via `--no-cache` flag

## Related Issue(s)

<!-- No specific issue — discovered during local e2e testing -->

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-08 13:37:06 -04:00
Jack Hsu af22e5c289 chore(misc): make expand-deps idempotent and wrap release in try-catch to reset package.json (#35213)
If the publish fails for any reason, the `package.json` files are not
reset, this can leads to `expand-deps` erroring in subsequent runs. Wrap
`resetPackageJsons` in finally block.

Also ensures `expand-deps` can run twice without errors, at least
locally, not in CI.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-08 13:35:57 -04:00
MaxKless 593b8a5d26 fix(core): disable Yarn scripts for temp nx@latest installs (#35210)
## Current Behavior

When Nx pulls `nx@latest` into a temporary directory (for example from
daemon latest resolution or `configure-ai-agents`/`init` handoff), Yarn
Berry can still run lifecycle scripts unless disabled via environment
configuration.

## Expected Behavior

All temporary `nx@latest` installs disable lifecycle scripts
consistently, including Yarn Berry, by always setting
`YARN_ENABLE_SCRIPTS=false` in the install process environment.

## Related Issue(s)

N/A. Follow-up parity change related to
https://github.com/nrwl/nx-console/pull/3108.

Fixes #N/A
2026-04-08 22:49:47 +09:00
Jason Jean 7b6aed81ac chore(repo): update nx to 22.7.0-beta.11 (#35208)
Updating Nx from 22.7.0-beta.10 to 22.7.0-beta.11
2026-04-08 09:47:14 -04:00
FUASHI LOT-BILL e6e2576670 fix(core): support cross-file variable references in .env files (#34956)
## Current Behavior

When Nx loads multiple .env files (such as `apps/nx-22-5/.env`,
`.local.env`, `.env.local`, and `.env`), each file is loaded and has its
variables expanded in sequence. This means referencing works up the
priority list but not down. For example, root `.env` correctly
references variables in project-specific `apps/example/.env`, but not
vice versa.

**Example:**

If root `.env` contains:
```env
WILL_RESOLVE=$FIRST_APP_NAME
GLOBAL_NX_VERSION=22.5.1
```

And `apps/nx-22-5/.env` contains:

```env
WILL_NOT_RESOLVE=$GLOBAL_NX_VERSION
FIRST_APP_NAME=nx-22-5
```

The `WILL_NOT_RESOLVE` variable will not expand correctly to `22.5.1`.
Instead, it becomes an empty string because `GLOBAL_NX_VERSION` is not
available in the environment at the time `apps/nx-22-5/.env` is being
processed. However, `WILL_RESOLVE` correctly resolves to` nx-22-5`.

This is because project-specific .env files are loaded before root .env
files, and variable expansion happens immediately during loading rather
than after all files are loaded.

## Expected Behavior
Variables in one .env file should be able to reference variables from
other .env files that are loaded together, regardless of their loading
priority. Both up-chain and down-chain references should work.

Using the example above:

- `WILL_RESOLVE` should resolve to nx-22-5  (works today)
- `WILL_NOT_RESOLVE` should resolve to 22.5.1  (fixed by this PR)

## Changes Made
Updated `loadAndExpandDotEnvFile` in
`packages/nx/src/tasks-runner/task-env.ts` to accept an array of file
paths instead of a single path. The function now:

1. Loads all .env files first to collect the complete set of variables
2. Performs variable expansion once with all variables available
3. This ensures bi-directional cross-file variable references work
correctly
Also updated `loadRootEnvFiles` in `packages/nx/src/utils/dotenv.ts` to
use the new batched loading approach.

Testing
A reproduction repository demonstrating the issue is available at:
[https://github.com/dullbenz/nx-22-5-env-referencing-example](vscode-file://vscode-app/Applications/Visual%20Studio%20Code.app/Contents/Resources/app/out/vs/code/electron-browser/workbench/workbench.html)

After this fix, both `WILL_RESOLVE` and `WILL_NOT_RESOLVE` should
correctly expand their variable references.

Related Issue(s)
Fixes #34955

Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
2026-04-08 07:57:43 -04:00
Jason Jean 44136ca74f fix(core): kill discrete tasks and use tree-kill for batch cleanup on SIGINT (#35175)
## Current Behavior

When Nx receives SIGINT (Ctrl+C), `performCleanup()` in the task
orchestrator kills continuous tasks and run-commands tasks, but **not
discrete tasks** (executor-based builds like `@nx/js:tsc`,
`nx:run-script`, etc.). In TUI mode, fork workers run in separate PTY
process groups and don't receive SIGINT directly, so cleanup hangs
waiting for tasks that are never terminated.

Additionally, `BatchProcess.kill()` only sends a signal to the immediate
child process, leaving grandchild processes (e.g. Java JVM, Gradle
daemon, Gradle workers) alive.

The Gradle batch executor also uses `execSync`, which blocks the Node
event loop and prevents the worker from responding to signals.

## Expected Behavior

All task types — discrete, continuous, and run-commands — should be
explicitly killed during SIGINT cleanup. Batch processes should use
`tree-kill` to terminate the entire process tree. The Gradle batch
executor should use async `spawn` instead of blocking `execSync`.

## Related Issue(s)

N/A — found via code inspection and confirmed with tests.
2026-04-07 23:00:49 +00:00
Jason Jean f7725e4e84 chore(maven): bump maven plugin version to 0.0.17 (#35203)
## Current Behavior

The Maven plugin version is `0.0.16`.

## Expected Behavior

The Maven plugin version is bumped to `0.0.17`, with a corresponding
migration for Nx `22.7.0-beta.11` that updates `pom.xml` files in user
workspaces.

## Related Issue(s)

N/A — routine version bump.
2026-04-07 22:50:31 +00:00
Louie Weng 6a9e270f1c fix(gradle): patch 0.1.19 to beta.11 (#35202)
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

Gradle project graph plugin 0.1.19 should be tied to Nx `22.7.0.beta.11`
2026-04-07 18:29:34 -04:00
Jason Jean 53c306a3f4 chore(repo): add pnpm store caching to CI workflow (#35197)
## Current Behavior

The `ci.yml` workflow runs `pnpm install --frozen-lockfile` and `pnpm
playwright install --with-deps` without any caching. Every CI run:
- Downloads all npm packages from the registry from scratch
- Downloads Playwright browser binaries (~hundreds of MB)
- Installs ~100+ system apt packages for Playwright dependencies

This was partially caused by #33772 which migrated from
`actions/setup-node` (which had built-in `cache: 'pnpm'`) to
`mise-action` without adding back equivalent caching.

## Expected Behavior

- **pnpm store** is cached between runs, so `pnpm install` only links
from the local store instead of downloading
- **Playwright browsers** are cached by version, so browser downloads
are skipped on cache hit
- System apt deps still install on cache hit (they're fast), but browser
downloads are skipped

## Related Issue(s)

N/A — performance improvement for CI install times.
2026-04-07 21:40:16 +00:00
Louie Weng 6f62590e6d fix(gradle): hoist shared task computation out of per-class loop in atomized CI target generation (#35199)
## Current Behavior

During atomized CI target generation, `buildTestCiTarget` is called once
per test class discovered in a project. Each invocation independently
recomputes `taskInputs`, `outputs`, and `dependsOn` for the same
`testTask`. Internally, `getInputsForTask` was called with `null` for
`dependsOnTasks`, which forced `getDependsOnTask(task)` — an uncached
call to `task.taskDependencies.getDependencies(task)` — on every
invocation. For a project with 158 test classes backed by the same
Gradle task, this results in 158 redundant dependency tree walks during
project graph discovery.

## Expected Behavior

The shared computation (`getDependsOnTask`, `getInputsForTask`,
`getOutputsForTask`, `getDependsOnForTask`) is performed once per
`testTask` in `processTestFiles` and the pre-computed values are passed
into `buildTestCiTarget`. This eliminates redundant work proportional to
the number of test classes, reducing CPU overhead during project graph
generation — especially on cold Gradle daemon starts.

## Related Issue(s)

Fixes #
2026-04-07 14:22:41 -07:00
Jason Jean 918193acd8 chore(repo): isolate astro-docs build (#35134)
## Current Behavior

The `astro-docs` project is not listed in the isolated build assignment
rules in the dynamic changesets CI workflow. This means it runs
alongside other projects instead of being isolated like `nx-dev`.

## Expected Behavior

The `astro-docs` project is added to the isolated build assignment rules
alongside `nx-dev`, ensuring its build runs in isolation during CI.

## Related Issue(s)

N/A — internal CI configuration improvement.
2026-04-07 19:55:27 +00:00
Jack Hsu 2521f98b6d fix(core): supply chain hardening via transitive dependency pinning (#35159)
## Current Behavior

When users run `pnpm install nx@latest`, transitive dependencies are
resolved at install time by the package manager. An attacker who
compromises a transitive dep (e.g. publishes a malicious patch to a
dep-of-a-dep) can inject code into any `nx@latest` install, even if `nx`
itself is secure. The `nx` package has ~36 direct deps but ~110 total
packages in its dependency tree, leaving ~76 transitive deps with
resolver freedom.

Additionally, `packages/nx/package.json` uses version ranges (`^`, `~`)
and `catalog:` references for some dependencies, giving the resolver
further freedom.

## Expected Behavior

At publish time, the entire transitive dependency tree of `nx` is
flattened into explicit, pinned direct dependencies in `package.json`.
Every package version is predetermined by the Nx team based on what was
tested in CI — zero resolver freedom.

### Changes

**`scripts/expand-deps.ts`** — New script that:
- Parses `pnpm-lock.yaml` to walk the full transitive dep tree for a
given project
- Pins all versions exactly (no ranges, resolves `catalog:` refs)
- Fails on version conflicts with dependency path reporting
- Supports `--dry-run` for preview
- Wired into `nx-release.ts` publish pipeline and
`packages/nx/project.json` as `expand-deps` target

**Inlined `@yarnpkg/parsers`** — Copied the syml parser (~2050 lines,
mostly PEG-generated grammar) into `packages/nx/src/utils/yarn-syml/`,
swapping `js-yaml` for `@zkochan/js-yaml` (already an nx dep). This
eliminates the `argparse@1` vs `argparse@2` transitive conflict.

**Removed `front-matter` dependency** — Last published 6 years ago,
replaced with an inline `parseFrontMatter` function ported from the
original source, using `@zkochan/js-yaml`.

**Replaced `jest-diff` with `@jest/diff-sequences`** — `jest-diff`
pulled in `pretty-format`, `chalk`, `ansi-styles@5`, `@jest/schemas`,
`@sinclair/typebox`, and `react-is`. Replaced with a minimal inline diff
implementation backed by `@jest/diff-sequences` (zero transitive deps).
This eliminates the `ansi-styles@4` vs `@5` conflict.

### Result

`nx run nx:expand-deps -- --dry-run` reports **0 conflicts, 110 total
pinned deps** (34 direct + 76 transitive).

`package.json` now has the pinned dependencies:

```49   "dependencies": {
  50     "@emnapi/core": "1.4.5",
  51     "@emnapi/runtime": "1.4.5",
  52     "@emnapi/wasi-threads": "1.0.4",
  53     "@jest/diff-sequences": "30.0.1",
  54     "@ltd/j-toml": "1.38.0",
  55     "@napi-rs/wasm-runtime": "0.2.4",
  56     "@tybys/wasm-util": "0.9.0",
  57     "@yarnpkg/lockfile": "1.1.0",
  58     "@zkochan/js-yaml": "0.0.7",
  59     "ansi-colors": "4.1.3",
  60     "ansi-regex": "5.0.1",
  61     "ansi-styles": "4.3.0",
  62     "argparse": "2.0.1",
  63     "asynckit": "0.4.0",
  64     "axios": "1.13.5",
  65     "balanced-match": "4.0.3",
  66     "base64-js": "1.5.1",
  67     "bl": "4.1.0",
  68     "brace-expansion": "5.0.2",
  69     "buffer": "5.7.1",
  70     "call-bind-apply-helpers": "1.0.2",
  71     "chalk": "4.1.2",
  72     "cli-cursor": "3.1.0",
  73     "cli-spinners": "2.6.1",
  74     "cliui": "8.0.1",
  75     "clone": "1.0.4",
  76     "color-convert": "2.0.1",
  77     "color-name": "1.1.4",
  78     "combined-stream": "1.0.8",
  79     "defaults": "1.0.4",
  80     "define-lazy-prop": "2.0.0",
  81     "delayed-stream": "1.0.0",
  82     "dotenv": "16.4.7",
  83     "dotenv-expand": "11.0.7",
  84     "dunder-proto": "1.0.1",
  85     "ejs": "5.0.1",
  86     "emoji-regex": "8.0.0",
  87     "end-of-stream": "1.4.5",
  88     "enquirer": "2.3.6",
  89     "es-define-property": "1.0.1",
  90     "es-errors": "1.3.0",
  91     "es-object-atoms": "1.1.1",
  92     "es-set-tostringtag": "2.1.0",
  93     "escalade": "3.2.0",
  94     "escape-string-regexp": "1.0.5",
  95     "figures": "3.2.0",
  96     "flat": "5.0.2",
  97     "follow-redirects": "1.15.11",
  98     "form-data": "4.0.5",
  99     "fs-constants": "1.0.0",
 100     "function-bind": "1.1.2",
 101     "get-caller-file": "2.0.5",
 102     "get-intrinsic": "1.3.0",
 103     "get-proto": "1.0.1",
 104     "gopd": "1.2.0",
 105     "has-flag": "4.0.0",
 106     "has-symbols": "1.1.0",
 107     "has-tostringtag": "1.0.2",
 108     "hasown": "2.0.2",
 109     "ieee754": "1.2.1",
 110     "ignore": "7.0.5",
 111     "inherits": "2.0.4",
 112     "is-docker": "2.2.1",
 113     "is-fullwidth-code-point": "3.0.0",
 114     "is-interactive": "1.0.0",
 115     "is-unicode-supported": "0.1.0",
 116     "is-wsl": "2.2.0",
 117     "json5": "2.2.3",
 118     "jsonc-parser": "3.2.0",
 119     "lines-and-columns": "2.0.3",
 120     "log-symbols": "4.1.0",
 121     "math-intrinsics": "1.1.0",
 122     "mime-db": "1.52.0",
 123     "mime-types": "2.1.35",
 124     "mimic-fn": "2.1.0",
 125     "minimatch": "10.2.4",
 126     "minimist": "1.2.8",
 127     "npm-run-path": "4.0.1",
 128     "once": "1.4.0",
 129     "onetime": "5.1.2",
 130     "open": "8.4.2",
 131     "ora": "5.3.0",
 132     "path-key": "3.1.1",
 133     "picocolors": "1.1.1",
 134     "proxy-from-env": "1.1.0",
 135     "readable-stream": "3.6.2",
 136     "require-directory": "2.1.1",
 137     "resolve.exports": "2.0.3",
 138     "restore-cursor": "3.1.0",
 139     "safe-buffer": "5.2.1",
 140     "semver": "7.7.4",
 141     "signal-exit": "3.0.7",
 142     "string-width": "4.2.3",
 143     "string_decoder": "1.3.0",
 144     "strip-ansi": "6.0.1",
 145     "strip-bom": "3.0.0",
 146     "supports-color": "7.2.0",
 147     "tar-stream": "2.2.0",
 148     "tmp": "0.2.4",
 149     "tree-kill": "1.2.2",
 150     "tsconfig-paths": "4.2.0",
 151     "tslib": "2.8.1",
 152     "util-deprecate": "1.0.2",
 153     "wcwidth": "1.0.1",
 154     "wrap-ansi": "7.0.0",
 155     "wrappy": "1.0.2",
 156     "y18n": "5.0.8",
 157     "yaml": "2.8.0",
 158     "yargs": "17.7.2",
 159     "yargs-parser": "21.1.1"
 160   },
```

## Related Issue(s)

Closes NXC-4197

---------

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-07 15:47:28 -04:00
Jack Hsu 48a6a86628 docs(misc): update sidebar to expand tutorials and collapse concepts (#35194)
## Current Behavior

- The **Tutorials** sidebar section is collapsed by default with a "New"
badge
- **How Nx Works** (Concepts) and **Platform Features** sections are
expanded by default
- Only ~10% of traffic reaches the CI tutorial, suggesting tutorials are
not discoverable enough

## Expected Behavior

- **Tutorials** section is expanded by default (no badge) to increase
discoverability
- **How Nx Works** and **Platform Features** sections are collapsed by
default to reduce noise and draw attention to tutorials
- This should drive higher engagement with the tutorial flow including
CI setup

<img width="502" height="735" alt="image"
src="https://github.com/user-attachments/assets/76a63a36-ab5d-4459-aab6-dc04282e3779"
/>


## Related Issue(s)

Fixes DOC-474
2026-04-07 14:01:48 -04:00
Louie Weng fe8f120a7e chore(gradle): bump gradle project graph plugin version to 0.1.19 (#35162)
## Current Behavior

The Gradle project graph plugin is at version 0.1.18.

## Expected Behavior

The Gradle project graph plugin is bumped to version 0.1.19, with the
corresponding migration files created so that users upgrading Nx will
automatically get the new plugin version.

## Related Issue(s)

N/A - routine version bump
2026-04-07 10:29:24 -07:00
Louie Weng bebadf607b fix(gradle): infer input extensions on project graph generation (#35160)
## Current Behavior

When output directories are empty (e.g. on a clean build), the plugin
cannot discover file extensions from actual files on disk. This means
`dependentTasksOutputFiles` glob patterns are missing for extensions
like `.class` and `.jar`, leading to incomplete cache inputs.

## Expected Behavior

Add `inferExtensionsFromInputProperties` to supplement file-based
extension discovery using task type checks:
- `Test` tasks → `class` + `jar` (they consume compiled code and library
jars on the test classpath)
- `AbstractCompile` tasks → `class` only (they produce/consume compiled
classes, not jars)
- `AbstractArchiveTask` dependents → their declared archive extension
(jar, war, zip, etc.)

This works at configuration time without requiring files to exist on
disk.

## Related Issue(s)

N/A
2026-04-07 17:20:34 +00:00
Alexandre Ducarne 794e0b42c8 fix(core): replace LGPL-licensed @ltd/j-toml with BSD-3-Clause smol-toml (#35188)
## Current Behavior

`nx` depends on `@ltd/j-toml` which is licensed under **LGPL-3.0**. This
creates licensing concerns for projects that bundle or distribute nx, as
LGPL requires downstream users to allow relinking/modification of the
LGPL component.

A previous attempt
([3446dd2](https://github.com/nrwl/nx/commit/3446dd2f77a1b182f9b64a83586ab68a2f0c063f))
replaced it with `@iarna/toml`, but that library:
- Only supports TOML 1.0.0-rc.1 (not even the final 1.0.0 spec)
- Has been effectively unmaintained since ~2020
- Is significantly slower than alternatives

## Expected Behavior

Use a permissively-licensed, actively maintained, fast TOML parser.

## Solution

Replace `@ltd/j-toml` with
[`smol-toml`](https://github.com/squirrelchat/smol-toml) (BSD-3-Clause):

| | @ltd/j-toml (current) | @iarna/toml (other PR) | **smol-toml (this
PR)** |
|---|---|---|---|
| License | LGPL-3.0 | ISC | **BSD-3-Clause** |
| TOML spec | 1.0.0 | 1.0.0-rc.1 | **1.1.0** |
| Weekly downloads | — | 2.7M | **6.8M** |
| Maintained | Yes | Dormant (~2020) | **Active (2026)** |
| Performance | Baseline | ~same | **2-4x faster** |
| CJS support | Yes | Yes | **Yes** |

### Changes

- Replace `@ltd/j-toml` imports with `smol-toml` in
`set-up-ai-agents.ts` and `test-utils.ts`
- Remove j-toml-specific APIs (`TOML.Section()`, `TOML.inline()`,
`newlineAround` option) in favor of smol-toml's simpler
`parse()`/`stringify()`
- Update inline test snapshots (single quotes → double quotes, minor
formatting differences)

### Note on inline snapshots

Some inline snapshots may need a final update via `--updateSnapshot`
once CI runs the full test suite. The snapshot changes included here
follow the pattern observed from smol-toml's output format
(double-quoted strings, no leading newline before first section).

## Test plan

- [ ] CI passes with updated snapshots
- [ ] `nx configure-ai-agents` generates valid Codex config.toml
- [ ] Release versioning for Rust/Cargo projects still produces correct
Cargo.toml output

---------

Co-authored-by: Alexandre Ducarne <aducarne@ripple.com>
Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
2026-04-07 12:35:33 -04:00
Craigory Coppola a5a76e65d6 fix(core): misc tui perf fixes (#35187)
- Add `handles_cursor_movement: Arc<AtomicBool>` field to PtyInstance
- Short-circuit `has_cursor_movement_in_output` on subsequent calls once
  a cursor-movement sequence has been detected, skipping the O(n) UTF-8
  buffer scan on every arrow-key event
- The flag is monotonic (false → true, never reverts) so Relaxed
ordering
  is sufficient; Arc makes it Clone-safe across async resize threads

This pull request refactors the TUI (terminal user interface) codebase
to improve performance, safety, and code clarity. The most significant
changes include switching from debouncing to throttling for PTY resize
operations, updating method signatures to use string slices (`&str`)
instead of owned `String` where possible, and making related adjustments
throughout the codebase. These changes help reduce unnecessary
allocations, improve responsiveness, and clarify intent.

**PTY Resize Handling Improvements:**

* Replaced the `debounce_pty_resize` method with `throttle_pty_resize`,
which limits PTY resize operations to at most one every 200ms
(fire-then-block), reducing excessive work during rapid events like
window resizing. All calls to the old debounce method are updated to use
the new throttle method.
[[1]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL473-R471)
[[2]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL490-R493)
[[3]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL590-R590)
[[4]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL615-R608)
[[5]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL1149-R1131)
[[6]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL1543-R1525)
[[7]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL1852-L1862)
[[8]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL2550-R2541)
[[9]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL2659-R2643)

**API and Type Signature Updates:**

* Changed many method signatures (such as `update_task_status`,
`select_task`, and `select_batch_group`) to accept `&str` instead of
`String`, reducing unnecessary allocations and clarifying ownership. All
corresponding call sites and trait implementations are updated.
[[1]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL220-R220)
[[2]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL417-R426)
[[3]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL534-R527)
[[4]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL590-R590)
[[5]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL1543-R1525)
[[6]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL2550-R2541)
[[7]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL2573-R2558)
[[8]](diffhunk://#diff-c899b0b1f501248ea65ff62c761294b7e6c5f5ece77ed422eae9c3a4849ac85aL218-R224)
[[9]](diffhunk://#diff-c899b0b1f501248ea65ff62c761294b7e6c5f5ece77ed422eae9c3a4849ac85aL709-R709)
[[10]](diffhunk://#diff-c899b0b1f501248ea65ff62c761294b7e6c5f5ece77ed422eae9c3a4849ac85aL833-R847)

**Cloning and Ownership Adjustments:**

* Replaced some `.clone()` and `.to_string()` calls with `.to_vec()` and
`.to_owned()` where more appropriate, further reducing unnecessary
allocations and clarifying intent.
[[1]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL168-R172)
[[2]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL189-R189)
[[3]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL203-R203)
[[4]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL368-R368)
[[5]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL2630-R2614)

**Code Cleanliness and Logic Simplification:**

* Simplified and cleaned up logic in several places, such as input
handling and filter mode transitions, to make the code more readable and
maintainable.
[[1]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL972-R970)
[[2]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL1006-R990)
[[3]](diffhunk://#diff-a7ecbb01dd3a8b83922f97d7040b457813c44325d346172693d1638b2b45a48fL1017)

Overall, these changes improve performance, safety, and maintainability
of the TUI code.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-07 12:04:07 -04:00
MaxKless b063b8475b fix(maven): make install targets noop when maven.install.skip=true (#35009)
When a Maven project sets maven.install.skip=true, the install:install
goal is a no-op at runtime. However, the NxTargetFactory still generated
a full batch executor target with cache:false, causing unnecessary Maven
invocations on every CI agent. This led to flaky failures in DTE when
the batch runner's graph setup failed on some agents.

Now detects maven.install.skip=true and emits an nx:noop target with
cache:true instead. The target still acts as a synchronization point in
the task graph (dependsOn chain is preserved) but avoids spinning up the
batch runner entirely.

<img width="2124" height="842" alt="image"
src="https://github.com/user-attachments/assets/b6c09b3e-c34e-45f8-9c33-2d6ce493bc3d"
/>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:55:46 -04:00
MaxKless 9361a75f7c feat(core): remove polygraph cloud passthrough (#35153) 2026-04-07 22:07:53 +09:00
Craigory Coppola e394165191 fix(repo): update issue-notifier.yml (#35178)
## Current Behavior
issue-notifier.yml uses an outdated syntax that throws

## Expected Behavior
it doesn't throw

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-04-07 08:32:32 -04:00
Jason Jean 828303bb2b feat(repo): add e2e test for nx build process verification (#35119)
## Current Behavior

There is no e2e test that verifies the nx build process works correctly
end-to-end — that the source code compiles, produces expected output
files, and correctly detects source changes on rebuild.

The verdaccio `max_body_size` was set to 20mb, which is too small for
the nx package. The nx `.npmignore` was also missing exclusions for
`.rs` and `.snap` files.

## Expected Behavior

- A new `e2e-nx-build` test project that:
  - Clones the nx repo to a temp directory
- Swaps `@nx/*` and `nx` dependency versions to match what's published
in the local verdaccio registry
  - Installs dependencies from the local registry
  - Builds all `tag:npm:public` packages (same as nx-release)
- Verifies key output files exist (`bin/nx.js`, `src/index.js`,
`src/index.d.ts`)
- Modifies a source file (`bin/nx.ts`), rebuilds, and verifies the
change appears in the output — catching cache misconfiguration (verified
by sabotaging `inputs: []` and confirming the test fails)
- Verdaccio `max_body_size` bumped to 100mb
- `.rs` and `.snap` files excluded from the nx npm package

## Related Issue(s)

N/A — new test infrastructure

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-04-03 21:23:22 -04:00
Victor Savkin 93624a5c24 feat(core): allow generate command to skip project graph creation (#35170)
When `skipProjectGraph` is passed to the `generate()` function, use
`retrieveProjectConfigurationsWithoutPluginInference` instead of
`createProjectGraphAsync`. This loads only default plugins (js,
package-json, project-json) and skips dependency edge computation,
making generation faster while still supporting local plugins.

This is a private API — callers must import the `generate` function
directly and pass `skipProjectGraph: true`.

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:01:58 -04:00
Jason Jean 8d35b8a8f7 fix(maven): prevent batch executor hang from premature worker exit (#35001)
## Current Behavior

When running `nx run-many -t clean --batch` in a multi-module Maven
project, the batch executor hangs indefinitely. Workers use
`taskQueue.poll()` which returns `null` immediately when the queue is
temporarily empty. Workers exit their loop prematurely — even though
other workers are still processing tasks that will produce new root
tasks. Eventually all workers exit and `completionLatch.await()` blocks
forever.

## Expected Behavior

`nx run-many -t clean --batch` completes successfully for Maven projects
of any size. Worker threads now use `taskQueue.take()` which blocks
until a task is available, instead of exiting when the queue is
momentarily empty. When all tasks complete, `executor.shutdownNow()`
interrupts any workers still blocked on `take()`, allowing clean
shutdown.

## Related Issue(s)

Fixes #34757
2026-04-03 13:52:14 -04:00
Leosvel Pérez Espinosa 070b748e73 chore(repo): revert tsgo compiler for nx package (#35167)
## Current Behavior

The `packages/nx` project uses `tsgo` (Go-based TypeScript compiler) via
a dedicated `@nx/js/typescript` plugin entry with `compiler: "tsgo"`.
All other packages use `tsc`.

When a downstream package (e.g., `node:build-base`) runs `tsc --build`
with project references to `nx`, `tsc` detects the `.tsbuildinfo` was
produced by a different compiler version (`7.0.0-dev` vs `5.9.2`) and
recompiles `nx` entirely. This cascades through the reference chain —
every project referencing `nx` is then considered out of date,
triggering rebuilds across devkit, js, workspace, eslint, jest, docker,
and the originating project.

Verified locally with `tsc --build --verbose`:
```
Project '../nx/tsconfig.lib.json' is out of date because output for it
was generated with version '7.0.0-dev.20260327.2' that differs with
current version '5.9.2'
```

## Expected Behavior

All packages use `tsc`, eliminating the compiler version mismatch. `tsc
--build` finds all referenced projects up to date and skips
recompilation.

> **Note:** We'll switch all packages to `tsgo` once they're all
migrated to `nodenext` and prepared for it, so we use a single compiler
across the workspace at a time.
2026-04-03 13:40:45 -04:00
Jason Jean 7439d58b22 chore(repo): add CLI performance benchmarks (#35078)
## Current Behavior

No standardized way to measure Nx CLI performance or detect regressions.

## Expected Behavior

A `benchmarks/` workspace provides reproducible CLI performance
benchmarks using [hyperfine](https://github.com/sharkdp/hyperfine).

### Project Structure

1110 dummy projects in a 3-level fan-out (10 groups → 10 subs → 10
leaves) with implicit dependencies.

### Benchmarks

| Benchmark | Command | Outputs | Dependencies | What it measures |
| --------------- | --------------------- | ------- | ------------ |
----------------------------------------- |
| `version` | `nx --version` | — | — | CLI startup / module loading |
| `show-projects` | `nx show projects` | — | — | Project graph query via
daemon |
| `cat-warm` | `cat lorem.md` × 1110 | No | Flat | Task scheduling +
hashing (no output I/O) |
| `lint-warm` | `cp lorem.md` × 1110 | Yes | Flat | Cached tasks with
output tracking |
| `build-warm` | `cp lorem.md` × 1110 | Yes | Topological | Cached tasks
with deps (disabled) |

### Usage

```bash
# Run all benchmarks
pnpm bench

# Run a single benchmark
pnpm nx bench:cat-warm benchmarks

# Set local baseline for comparison
pnpm bench -- --set-baseline
```

### How it works

- Each `bench:*` target runs hyperfine with `--setup 'nx reset'` (clean
daemon) and `--warmup 1` (warm daemon for measurements)
- `goals.json` (committed) defines target times the team agrees on
- `baseline.json` (gitignored) captures local per-machine numbers for
personal comparison
- First run auto-saves a baseline; subsequent runs compare against it
- The report shows colored deltas against both goals and baseline
- Benchmarks run in CI via `nx affected --targets=bench` on
`linux-large` agents

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-04-03 13:40:06 -04:00
Jason Jean 557c876e96 chore(repo): update nx to 22.7.0-beta.10 (#35166)
Updating Nx from 22.7.0-beta.9 to 22.7.0-beta.10
2026-04-02 19:00:22 -04:00
Jack Hsu 727e8b760e chore(misc): disable failing tests (#35165)
These have been failing for a while, at least a week. Let's disable for
now to unblock PRs, and fix them properly later
2026-04-02 17:41:46 -04:00
Jason Jean a80f24aaa0 fix(gradle): prevent Gradle and Maven daemon accumulation during project graph recalculation (#35143)
## Current Behavior

When file changes trigger rapid project graph recalculations (e.g.,
during development with `nx graph` or a dev server running), each call
to `populateProjectGraph` spawns a new Gradle process via
`execGradleAsync`. If the previous Gradle daemon is still busy
processing the prior request, Gradle spawns a **new daemon**. These
daemons persist for 3 hours by default (Gradle's idle timeout), leading
to dozens of orphaned `java.exe` processes consuming significant memory.

Maven has a similar issue — `runMavenAnalysis` spawns a long-lived
process with no timeout or cancellation support at all.

### Root Cause Analysis

The daemon explosion happens due to three compounding issues:

1. **No cancellation of in-flight processes**: When a newer project
graph request arrives, the previous invocation continues running. Each
concurrent invocation finds the existing daemon busy and spawns a new
one.

2. **Windows process tree issue**: On Windows, `execFile` with `shell:
true` runs `cmd.exe → gradlew.bat → java.exe`. Node's `AbortSignal` only
terminates `cmd.exe` (the immediate child process), leaving `java.exe`
running as an orphan.

3. **No timeout for Maven**: Maven analysis could run indefinitely with
no way to cancel or time out.

### Reproduction

1. Run `nx graph` in a workspace with `@nx/gradle` registered
2. Rapidly modify a `build.gradle.kts` file (e.g., `while true; do echo
"// tick" >> build.gradle.kts; sleep 0.01; done`)
3. Watch `java.exe` processes accumulate: `tasklist | grep java`
(Windows) or `ps aux | grep java` (Unix)
4. Without this fix: **15+ Gradle daemons** within seconds, persisting
for 3 hours each
5. With this fix: **1 Gradle daemon** remains stable under the same
conditions

## Expected Behavior

When rapid file changes trigger multiple project graph recalculations:
- The previous invocation is cancelled before starting a new one
- Cancelled calls that have already spawned a process get their entire
process tree killed (not just the shell wrapper)
- Only 1 daemon remains active at any time, rather than accumulating
dozens
- Both Gradle and Maven have configurable timeouts with clear error
messages

## Changes

### Gradle

#### 1. Self-contained cancellation in `get-project-graph-lines.ts`
Moved the `AbortController` from
`get-project-graph-from-gradle-plugin.ts` into
`get-project-graph-lines.ts`, closer to where processes are spawned.
`getNxProjectGraphLines` now manages its own abort controller —
cancelling any in-flight request before starting a new one. Uses
`abort('cancelled')` reason to distinguish external cancellation from
timeout.

#### 2. Tree-kill on abort (`exec-gradle.ts`)
Instead of passing the `AbortSignal` directly to Node's `execFile`
(which only kills the immediate child process), we intercept the signal
and use `tree-kill` to terminate the entire process tree. This ensures
`java.exe` is killed along with `cmd.exe` and `gradlew.bat` on Windows.

### Maven

#### 3. Timeout and cancellation support for `maven-analyzer.ts`
Added the same abort controller + tree-kill + timeout pattern to Maven
analysis:
- Configurable timeout via `NX_MAVEN_ANALYSIS_TIMEOUT` env var (default:
120s local, 600s CI)
- `cancelPendingMavenAnalysis()` cancels in-flight processes on repeated
calls
- `tree-kill` ensures the entire Maven process tree is killed on abort
- Clear timeout error messages with actionable steps

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-02 15:27:56 -04:00
James Henry 2665550d24 fix(core): update and pin ejs to 5.0.1 (#35157)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

We are using an old version of `ejs` and it is not pinned.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

We are using the latest version of `ejs` and it is pinned.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-02 13:50:04 -04:00
Caleb Ukle 097484bda3 docs(nx-cloud): add nx-cloud onboard command reference (#34973)
## Current Behavior

The `nx-cloud onboard` command and its subcommands are not documented in
the Cloud CLI reference page.

## Expected Behavior

The Cloud CLI reference page includes complete documentation for the
`nx-cloud onboard` command, covering:

- Main `onboard` command with interactive and non-interactive modes
- All subcommands: `status`, `connect-workspace`, `connect github`,
`connect github poll`, `orgs list`, `orgs create`, `repos list`,
`templates list`, `vcs status`, and `workspace create`
- Options tables for each subcommand
- Automation notes for AI agent integration

Also changed `--non-interactive` to `--no-interactive` to match the Nx
CLI convention (yargs `--no-` prefix).

## Related Issue(s)

Fixes DOC-451

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 12:27:04 -05:00
Robert Sidzinka aca93cc9bb fix(core): bump axios to 1.13.5 to resolve CVE-2026-25639 (#35148)
## Current Behavior

The `nx`, `create-nx-workspace`, and `nx-dev` packages pin `axios` at
version `1.12.0`, which has a known security vulnerability
([CVE-2026-25639](https://github.com/advisories/GHSA-43fc-jf86-j433)).

## Expected Behavior

Axios is pinned at `1.13.5`, which includes the fix for CVE-2026-25639,
eliminating the security vulnerability.

## Related Issue(s)

Fixes #35145
2026-04-02 13:07:42 -04:00
Steven Nance 54991812e6 docs(misc): add Node 22 and Node 24 agent images to launch templates reference (#35149)
## Current Behavior

The launch templates reference page only lists Node 20-based agent
images (`ubuntu22.04-node20.11-*` and `ubuntu22.04-node20.19-*`).

## Expected Behavior

The launch templates reference page also lists the new Node 22 and Node
24 agent images:

- `ubuntu22.04-node22.22-v1`
- `ubuntu22.04-node24.14-v1`

These images were added to the cloud infrastructure config map via
[CLOUD-4403](https://linear.app/nxdev/issue/CLOUD-4403).

## Related Issue(s)

N/A (documentation update to reflect infrastructure changes from
CLOUD-4403)

---------

Co-authored-by: Caleb Ukle <caleb@nrwl.io>
2026-04-02 19:05:02 +02:00
Jack Hsu af21c0bec8 feat(misc): lock in CNW cloud prompt A/B winner and add new variants (#35154)
## Current Behavior

The CNW cloud prompt A/B test (NXC-4113, shipped in 22.6.3) has three
variants with these results (~2,900 completions across 22.6.3+22.6.4):

| FV | Completions | Yes% | Never% |
| -- | -- | -- | -- |
| 0 ("Connect to Nx Cloud?") | 915 | 7.5% | 27.7% |
| 1 ("Enable remote caching...") | 1,040 | **12.2%** | 18.8% |
| 2 ("Speed up your CI...") | 974 | 10.0% | 19.5% |

FV 1 is the clear winner — 63% relative lift in "yes" over FV 0, nearly
halving the "never" rate.

## Expected Behavior

Lock in FV 1 as the new baseline (FV 0) and introduce two new A/B
variants:

| FV | Code | Prompt | Footer |
| -- | -- | -- | -- |
| 0 (new baseline) | `connect-to-cloud` | "Enable remote caching to
speed up builds with Nx Cloud?" | "Free for small teams. 2-minute setup
with GitHub — cache locally and in CI" |
| 1 | `cloud-ab-never-rebuild` | "Never rebuild the same code twice —
enable Nx Cloud?" | "Free for small teams. Remote caching for local dev
and CI. 2-minute setup" |
| 2 | `cloud-ab-ci-providers-speed` | "Speed up GitHub Actions, GitLab
CI, and more with Nx Cloud?" | "Free remote caching and task
distribution. 2-minute setup" |

Variant 0 (new baseline):
<img width="1392" height="1004" alt="variant_0_baseline"
src="https://github.com/user-attachments/assets/8b05dc5d-64ae-42bb-98cb-942ef1856c96"
/>

Variant 1 (never rebuild same code twice - remote cache in footer):
<img width="1392" height="970" alt="variant_1"
src="https://github.com/user-attachments/assets/eac6bdab-9c7d-41b7-a110-fa73ed188c67"
/>

Variant 2 (speed up CI and mention providers - remote caching in
footer):
<img width="1392" height="1004" alt="variant_2"
src="https://github.com/user-attachments/assets/c962a408-c4b5-4b68-8d79-5cc046841316"
/>

## Related Issue(s)

Fixes NXC-4190
2026-04-02 13:04:36 -04:00
Jason Jean a9cfce55be feat(repo): enforce no-disabled-tests via ESLint with per-project warning caps (#35122)
## Current Behavior

Disabled tests (`.skip()`, `.todo()`, `xit()`, `xdescribe()`, `xtest()`)
can be committed freely with no guardrails. Over time this leads to
tests silently rotting — there are currently ~65 disabled tests across
the workspace.

## Expected Behavior

ESLint warns on any disabled test via `jest/no-disabled-tests`, and
`--max-warnings` caps prevent the count from growing.

### How it works

The setup is like a ratchet — existing disabled tests are grandfathered
at their current counts, but adding new ones fails lint:

- **Global default**: `max-warnings: 5` in `nx.json` target defaults —
covers most projects
- **Per-project overrides** for projects that exceed 5, capped at their
current count:
  - `graph-client`: 15 (react-hooks/exhaustive-deps, unused-vars)
- `e2e-angular`: 9, `e2e-react`: 10, `e2e-next`: 8, `e2e-node`: 6,
`e2e-storybook`: 6
  - `nx-dev-feature-package-schema-viewer`: 7, `nx-dev-ui-markdoc`: 7
- **e2e `.eslintrc.json` files** only un-ignore test files (`*.test.ts`,
`*.spec.ts`) to avoid exposing unrelated lint errors in non-test code

### Changes

- Install `eslint-plugin-jest` and add `jest/no-disabled-tests: warn` to
root `.eslintrc.json` for test file overrides
- Add `.eslintrc.json` to 15 e2e projects
- Set `max-warnings: 5` globally and per-project overrides where needed
- Ignore Maven `target/` build output from linting
2026-04-02 12:58:10 -04:00
Louie Weng ea3450b4b8 chore(gradle): add OpenTelemetry tracing to project graph plugin (#35140)
## Current Behavior

The Gradle project graph plugin (`dev.nx.gradle.project-graph`) has no
observability into where time is spent during project graph generation.
When users report slow `nx show projects` or project graph resolution,
there is no way to identify bottlenecks in the Kotlin plugin code.

## Expected Behavior

The plugin now has opt-in OpenTelemetry distributed tracing. When
`OTEL_EXPORTER_OTLP_ENDPOINT` is set, spans are created for key
operations and exported via OTLP/gRPC to any compatible collector
(Jaeger, Grafana Tempo, etc.). When the env var is not set, tracing is
completely no-op with zero overhead.

Run any Nx command that triggers Gradle project graph generation:
   ```bash
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 pnpm nx show projects
   ```

## Related Issue(s)

N/A — internal observability improvement for debugging Gradle project
graph performance.
2026-04-02 12:43:27 -04:00
Parker Norwood 8b7e277774 fix(js): resolve ENOWORKSPACES test error in setupVerdaccio for @nx/js:library generator (#34755) 2026-04-02 10:53:20 -04:00
Leosvel Pérez Espinosa be8c579b28 chore(repo): remove redundant tsc command from workspace-plugin build (#35146)
## Current Behavior

The `workspace-plugin:build` target explicitly runs `tsc --build
tsconfig.lib.json`, which is the same command the inferred `build-base`
target runs. Since `build` depends on `build-base`, tsc runs twice — the
second time finding nothing to do.

The `nx` package was listed as a dependency but is only referenced in
generator template files, not in compiled source.

## Expected Behavior

- The `build` target is a pure dependency orchestrator with no command.
`build-base` (inferred by `@nx/js/typescript`) handles the actual
compilation.
- The `@nx/dependency-checks` rule is configured with `buildTargets:
["build-base"]` to align with all other packages in the repo.
- The `nx` package is removed from dependencies since it's not imported
in any source file.
2026-04-02 08:47:50 -04:00
Craigory Coppola 51f9d3e4e2 chore(core): adjust error when trying to resolve a migration and hitting a previous version (#35107)
## Current Behavior
If a user has `overrides` set for the `nx` package, we error with
"cannot find the implementation of xxx" if the override pins nx to a
version prior to that migration's inclusion.

## Expected Behavior
We point out the `overrides` field for easier debugging

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-02 07:13:33 -04:00
Jason Jean facec58e57 fix(misc): use workspace root for package manager detection and normalize paths in plugins (#35116)
## Current Behavior

Two issues in plugin code:

1. **Package manager detection at module scope**:
`getPackageManagerCommand()` is called at module level in all 18 plugin
files. This detects the package manager based on the CWD or
`npm_config_user_agent` at import time, rather than using the actual
workspace root. This produces inconsistent `pmc.exec` commands (e.g.
`pnpm exec` vs `npx`) depending on how the process was invoked.

2. **Platform-dependent paths in target configs**: Plugins use
`path.join()` and `path.relative()` to build target configuration values
(outputs, commands, env vars). On Windows this produces backslashes,
making the project graph differ between platforms.

## Expected Behavior

1. Package manager detection uses `context.workspaceRoot` from the
`createNodes` callback, then passes `pmc` down to per-project
target-building functions.
2. All paths in target configs use forward slashes regardless of OS,
using `joinPathFragments` and `normalizePath` from `@nx/devkit`.

## Changes

**Plugin source (20 files):**
- All 18 plugin files: moved `getPackageManagerCommand()` from module
scope into the `createNodes` callback where `context.workspaceRoot` is
available
- `packages/playwright/src/plugins/plugin.ts`: replaced
`path.join`/`posix.join`/`posix.relative` with
`joinPathFragments`/`normalizePath` for target config paths
- `packages/webpack/src/plugins/plugin.ts`,
`packages/vite/src/plugins/plugin.ts`,
`packages/vitest/src/plugins/plugin.ts`,
`packages/nuxt/src/plugins/plugin.ts`: replaced `path.join` with
`joinPathFragments` in `normalizeOutputPath()`, added `normalizePath`
for relative test paths

**Tests (19 files):**
- Added `package-lock.json` to TempFs dirs for deterministic package
manager detection
- Converted next/nuxt root project tests from `workspaceRoot: ''` to
TempFs-based setup
- Fixed all backslash path separators in snapshots and inline snapshots

## Related Issue(s)

<!-- No specific issue — discovered during test debugging -->
2026-04-01 19:07:32 -04:00
Steven Nance 3a2d17ec71 fix(core): display actual error message when plugin loading fails (#35138)
## Current Behavior

When a workspace-local Nx plugin cannot be resolved (e.g., the
`node_modules` symlink for a workspace package is missing), the error
message displays `[object Object]` instead of the underlying module
resolution error:

```
NX   Failed to load 1 Nx plugin(s):

  - @repro/my-plugin/plugin: [object Object]
```

This happens because plugin workers serialize errors as plain objects
via `createSerializableError()`. When these serialized errors are
received back in the parent process, the `instanceof Error` check fails,
and `String(reason)` on a plain object produces `[object Object]`.

## Expected Behavior

The actual error message is displayed:

```
NX   Failed to load 1 Nx plugin(s):

  - @repro/my-plugin/plugin: Cannot find module '@repro/my-plugin/plugin'
```

The fix adds a `reasonToError` helper that checks for an object with a
`message` property (serialized error) before falling back to `String()`
conversion. This properly handles:
- Real `Error` instances (unchanged behavior)
- Serialized error objects from plugin workers (now extracts `message`
and `stack`)
- Other non-error rejection reasons (unchanged `String()` fallback)

## Screenshot of fix
<img width="972" height="244" alt="image"
src="https://github.com/user-attachments/assets/ca87d9a6-43a4-4fd4-a215-71277f6dcc8b"
/>


## Related Issue(s)

Fixes #35137
2026-04-01 22:42:14 +00:00
Leosvel Pérez Espinosa 887fca4ac8 fix(repo): narrow copy-assets outputs to prevent overlap with build-base (#35097)
## Current Behavior

Broad globs in `assets.json` (`**/*.json`, `**/*.js`, `**/*.d.ts`) cause
`copy-assets` targets to claim cache ownership over files also produced
by `build-base` (tsc). Even though a recent change reordered
`copy-assets` to run before `build-base` (reducing the likelihood of the
race condition), the underlying task ownership model is still broken —
both targets claim overlapping files in their output patterns.

## Expected Behavior

Each target exclusively owns its output files. `copy-assets` only claims
non-tsc assets (templates, type declarations, native artifacts), and
`build-base` owns all compiler outputs.

## Changes

**37 `assets.json` files** — replaced broad extension globs with narrow,
destination-safe patterns: template dirs (`**/files/**`), schema type
declarations (`src/**/schema.d.ts`), non-tsc extensions (`.jar`,
`.node`, `.wasm`, `.md`), and explicit file paths for package-specific
assets.

**30 `tsconfig.lib.json` files** — removed `**/*.json` from `include` so
tsc only compiles TypeScript. JSON files are now handled by copy-assets
with explicit entries
(`@(package|executors|generators|migrations).json`,
`src/**/schema.json`).

**Exception:** jest and vite use `import('./schema.json')` which
requires JSON in tsconfig scope with `composite: true`. These keep
`src/**/schema.json` in tsconfig.

**vite** — excludes `test-utils.ts` from lib build (only used by specs)
and includes it in `tsconfig.spec.json`.
2026-04-01 17:56:09 -04:00
Leosvel Pérez Espinosa d2642e13da fix(core): improve migrate error reporting (#34980)
## Current Behavior

`nx migrate` can hide the actual package manager failure behind parent
wrapper noise, and invalid migration metadata can lead to confusing
follow-up failures.

## Expected Behavior

`nx migrate` should surface the underlying fallback install error
clearly, avoid noisy parent-level wrapper failures, and only fail on
invalid migration metadata when the invalid update is actually consumed.
2026-04-01 17:49:19 -04:00
Leosvel Pérez Espinosa 0d0cb3ddc8 fix(core): copy pnpm install configuration to generated package.json (#35016)
## Current Behavior

The generated package.json (used for deployment) only copies
`pnpm.overrides` from the root package.json. Other pnpm fields that
affect `pnpm install` behavior are missing, causing issues like
lifecycle scripts not running (pnpm v10+) or wrong platform-specific
dependencies being installed.

## Expected Behavior

All pnpm configuration fields that affect `pnpm install` in a deployment
context should be copied to the generated package.json:

- `onlyBuiltDependencies` — allowlist for lifecycle scripts (pnpm v10
requirement)
- `neverBuiltDependencies` — denylist for lifecycle scripts
- `allowBuilds` — unified replacement for the above two (pnpm 10.26+)
- `supportedArchitectures` — platform-specific dependency selection
- `ignoredOptionalDependencies` — skip optional dependencies

## Related Issue(s)

Fixes #30240

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: leosvelperez <leosvelperez@users.noreply.github.com>
2026-04-01 17:43:25 -04:00
MaxKless d579fbfb6f fix(core): clean up legacy .gemini/skills during configure-ai-agents (#35117)
## Current Behavior

When `configure-ai-agents` runs for Gemini, it copies skills to the
shared `.agents/skills` directory (used by Codex, Cursor, and Gemini).
However, workspaces that were configured with an older version of Nx
still have a `.gemini/skills` directory containing duplicate Nx-managed
skills. These legacy files are never cleaned up, leaving stale
duplicates in the workspace.

## Expected Behavior

When `configure-ai-agents` runs for Gemini, it should remove any
`.gemini/skills` entries that also exist in `.agents/skills` (indicating
they are Nx-managed skills that have been migrated). User-created custom
skills in `.gemini/skills` that have no counterpart in `.agents/skills`
are preserved.

## Related Issue(s)

N/A — discovered during investigation of template repo AI agent
configurations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-01 17:03:48 -04:00
Jason Jean 0a0d43e6df fix(repo): correct build target outputs for docker and vue packages (#35136)
## Current Behavior

- The `docker` package has its build output path pointing to
`{workspaceRoot}/build/packages/docker/README.md`, but the
`copy-readme.js` script writes to `dist/packages/docker/README.md`. This
means the cache output doesn't match the actual output location.
- The `vue` package has an empty build target (`{}`) with no README copy
step, unlike every other publishable package.

## Expected Behavior

- The `docker` package output path correctly points to
`{workspaceRoot}/dist/packages/docker/README.md`.
- The `vue` package has a proper build target that copies and processes
the README, matching the pattern used by all other packages.

## Related Issue(s)

N/A — found during audit of build target outputs.
2026-04-01 20:36:48 +00:00
Leosvel Pérez Espinosa 37eb4ecde4 fix(bundling): bump esbuild for new projects to a version compatible with vite 8 (#35132)
## Current Behavior

Newly generated JS/esbuild-based projects still default `esbuild` to
`^0.19.2`. That conflicts with Vite 8 which requires `esbuild ^0.27.0`.

## Expected Behavior

Newly generated projects should default to an `esbuild` version
compatible with Vite 8. If a workspace already has `esbuild` installed,
generators should preserve that version instead of blindly bumping it,
and Vite init should fall back to Vite 7 with a warning when the
installed `esbuild` range is incompatible with Vite 8.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-04-01 16:23:42 -04:00
Leosvel Pérez Espinosa cbeedca9e5 chore(repo): add root tsconfig.json to nx-dev-e2e lint inputs (#35130)
## Current Behavior

`nx-dev-e2e` has no `tsconfig.json`. When `eslint .` runs,
`@typescript-eslint/parser` walks up from the project root to the
workspace root `tsconfig.json`. This read is not declared as a lint
input, causing a sandbox violation.

## Expected Behavior

A minimal `tsconfig.json` extending `tsconfig.base.json` exists in the
project, so the parser resolves locally and the sandbox violation is
eliminated.

## Why not fix in the `@nx/eslint/plugin`?

The plugin would need to resolve the ESLint config chain, identify which
parser is in use, and replicate that parser's tsconfig resolution logic
(walking up directories, following `extends` chains). This adds file I/O
and config parsing to `createNodesV2`, which runs on the critical path
during project graph computation — every `nx` command pays the cost. Not
worth it for an edge case that only surfaces when a project has no local
tsconfig.

## Why not an input override?

An input override in `project.json` would duplicate the plugin's
inferred inputs and could drift if the plugin changes what it infers in
the future.
2026-04-01 13:30:53 -04:00
Leosvel Pérez Espinosa 788c8dd2a5 fix(repo): clean Angular CLI restore target before cache copy (#35121)
## Current Behavior

Nightly `e2e-nx-init` runs can fail when the Angular CLI legacy suite
restores a cached workspace into an already-existing temp project
directory. With `NX_E2E_SKIP_CLEANUP=true`, the previous test workspace
is intentionally left on disk, and `fs-extra.copySync` then hits
symlinked `node_modules/.bin` entries and aborts with errors like
`Cannot copy '../which/bin/which.js' to a subdirectory of itself`.

## Expected Behavior

Restoring the cached Angular CLI workspace should be idempotent even
when cleanup is skipped between tests. The restore step should start
from a clean target directory so the suite can reuse the cached baseline
without tripping over stale symlinks from the previous test run.

## Related Issue(s)

No tracked issue. Investigated from nightly GitHub Actions run
`23833817151`.

Validation:
- `pnpm nx typecheck e2e-nx-init`
- Focused local repro on macOS/npm with `NX_E2E_SKIP_CLEANUP=true`
failing before the change and passing after it
2026-04-01 12:40:48 -04:00
Jack Hsu 49a0e3f69e fix(js): use explicit nx/bin/nx path in start-local-registry (#35127)
## Current Behavior

`start-local-registry.ts` uses `require.resolve('nx')` to find the nx
CLI binary for forking a child process. This resolves the package's
`main` entry point, which only incidentally points to the binary. In
pnpm strict mode, this resolution fails when called from within `@nx/js`
because `@nx/js` doesn't declare `nx` as a direct dependency.

## Expected Behavior

Use `require.resolve('nx/bin/nx')` to explicitly resolve the CLI binary
entry point. This is semantically correct (the intent is to fork the nx
CLI) and doesn't rely on the `main` field or pnpm hoisting behavior.
2026-04-01 16:27:27 +00:00
Jack Hsu 7838f73b27 docs(js): use require.resolve('nx/bin/nx') which is more reliable (#35129)
`require.resolve('nx)` can fail for a number of reasons:
- If `./nx` exists, it'll resolve to that first, and fail
- If module resolution is set differently it may resolve to the index
file, or something unexpected
2026-04-01 11:58:16 -04:00
Craigory Coppola b05e9c0909 chore(repo): update preinstall to vary message based on mise status (#35128)
## Current Behavior
pre-install says rust isn't available if mise isn't trusting the dir,
but doesn't mention mise and suggests installing rust. This can trip up
AI agents if they see the output and think rust should be installed

## Expected Behavior
Error message mentions mise

## Notes

This pull request significantly refactors and improves the
`scripts/preinstall.js` dependency check script. The script now performs
more robust version checks for Node, pnpm, and Rust, and adds support
for detecting and guiding users regarding `mise` trust status. The
refactoring also improves maintainability by modularizing checks and
error handling.

**Dependency checks and error handling:**

* Refactored version checks for Node, pnpm, and Rust into separate
functions for better readability and maintainability.
* Improved error messages and guidance, including specific instructions
for updating or installing missing tools, and added checks for minimum
required versions (`Node 20.19.0+`, `pnpm 10.0.0+`, `Rust 1.70.0+`).

**Support for mise integration:**

* Added detection of `mise` installation and trust status, with user
guidance to run `mise trust` when the directory is untrusted and Rust is
missing or outdated.

**Codebase improvements:**

* Consolidated tool version gathering into a single `getToolData`
function and replaced repeated code with a reusable `execOrNull` helper.
* Changed process exit logic to only exit when errors are detected,
improving script robustness. (F43b11f
2026-04-01 11:43:46 -04:00
Leosvel Pérez Espinosa 61dc66d0ea chore(repo): add eslint config chain to lint-pnpm-lock inputs (#35125)
## Current Behavior

The `lint-pnpm-lock` task only declares `pnpm-lock.yaml` as input.
ESLint also reads its config chain (`.eslintrc.json`, `.eslintignore`),
`tsconfig.json`, and the custom rules plugin (`tools/eslint-rules/**`),
causing sandbox violations for 10 undeclared reads.

## Expected Behavior

All files ESLint needs are declared as inputs so sandbox reports no
violations for `@nx/nx-source:lint-pnpm-lock`.
2026-04-01 10:59:05 -04:00
Jason Jean 19bac461a2 fix(core): reduce published nx package size with files allowlist (#35109)
## Current Behavior

The `nx` npm package includes unnecessary files in the published
artifact:
- ~137 Rust source files (`.rs`) from `src/native/`
- Test fixtures, snapshots, and other dev-only files
- Duplicate source directories (`bin/`, `plugins/`, `schemas/`, etc.)
alongside their compiled `dist/` equivalents

The `.npmignore` blocklist approach missed several file types, and the
CI `.node` file cleanup (`find ./dist`) no longer works because the `nx`
package now builds to `packages/nx/dist/` instead of
`dist/packages/nx/`.

## Expected Behavior

- Only `dist/` and essential root JSON files (`migrations.json`,
`executors.json`, `generators.json`) are published
- No Rust source, test fixtures, snapshots, or duplicate source dirs in
the published package
- Native type declarations (`src/native/index.d.ts`) are copied to
`dist/` via `assets.json` so all exports reference `dist/` consistently
- CI correctly removes `.node` files from `packages/nx/dist/` before
publishing to npm

## Related Issue(s)

N/A — discovered during package audit.

---------

Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
2026-04-01 14:49:17 +00:00
Leosvel Pérez Espinosa 804b5abcb3 chore(misc): add dist to eslint ignorePatterns (#35124)
## Current Behavior

Packages that build to `{projectRoot}/dist` (`maven`, `dotnet`,
`angular-rspack-compiler`) use `!**/*` in their project-level
`.eslintrc.json` `ignorePatterns` to un-ignore dotfiles. This negation
also overrides the root config's `**/dist` ignore pattern, causing
`eslint .` to traverse and read build artifacts in `dist/` during
linting. This produces sandbox violations (46 unexpected reads for
`@nx/maven:lint`).

## Expected Behavior

ESLint skips the `dist/` directory during linting, matching the behavior
already in place for `packages/nx` and `packages/angular-rspack` which
explicitly re-ignore `dist` after the `!**/*` pattern.
2026-04-01 10:18:56 -04:00
Jack Hsu 2426941a88 docs(misc): add tutorial series ToC to all tutorial pages (#35120)
## Current Behavior

Tutorial pages are standalone with no visible series navigation. Path
analysis shows most users drop off after the first tutorial, even though
they generally follow the intended path.

## Expected Behavior

Each of the 8 tutorial pages now shows a "Tutorial Series" aside after
the intro paragraph, listing all tutorials with the current one bolded.
This makes the series feel connected while still allowing users to jump
around or skip as needed.

Additionally, prerequisites are standardized as plain paragraphs (not
asides) across all tutorial pages for a cleaner, less noisy layout.

<img width="971" height="816" alt="image"
src="https://github.com/user-attachments/assets/b5389d98-4809-4400-8f9f-ab9834caba94"
/>


## Related Issue(s)

Closes DOC-466

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
2026-04-01 10:11:11 -04:00
Jack Hsu eeee05a72e chore(repo): fix inputs for CNW and CNP (#35123)
They don't specify the right inputs, so you will get the wrong bin file
whenever you build CNW and CNP.

The problem is ordering:

1. `build-base` runs → sees source change → compiles fresh
`bin/create-nx-workspace.js` into `dist/`
2. `build` runs next → checks inputs (`copyReadme`) → cache hit →
restores its cached outputs
3. That cached output includes the old `bin/create-nx-workspace.js`,
which overwrites the fresh one from step 1

So even though the `build-base` is compiling new source with `tsc`, the
`build` task overrides just the bin. This is for both CNW and CNP.
2026-04-01 10:10:27 -04:00
Jason Jean 4e003b7a20 chore(repo): update nx to 22.7.0-beta.9 (#35100)
Updating Nx from 22.7.0-beta.7 to 22.7.0-beta.9
2026-04-01 03:38:14 +00:00
Jason Jean 6962a3d7a1 chore(repo): skip react-router typecheck e2e test due to vite version conflict (#35110)
## Current Behavior

The react-router typecheck e2e test fails in CI because pnpm resolves
both vite 7 and vite 8 in the generated project. `@react-router/dev`
picks up vite 8 plugin types while `defineConfig` uses vite 7 types,
causing a TypeScript incompatibility.

## Expected Behavior

The test is skipped until `@react-router/dev` adds Vite 8 support, at
which point the `useViteV7` workaround and this skip can both be
removed.

## Related Issue(s)

Follow-up to #35101
2026-04-01 01:38:20 +00:00
Craigory Coppola a5523d2c52 fix(core): no-interactive should disable prompts during migrate (#35106)
<!-- 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-03-31 19:42:13 -04:00
Jason Jean bd463d055c fix(core): restore metadata table for telemetry session tracking (#35099)
## Current Behavior

Telemetry initialization fails with `no such table: metadata` because
the metadata table was removed during the DB schema refactor that
decoupled DB version from Nx version. The telemetry service depends on
this table to store and retrieve session IDs for analytics tracking.

## Expected Behavior

The metadata table is created as part of DB initialization alongside the
other tables (`task_details`, `running_tasks`, `task_history`).
Telemetry session tracking works without errors. The DB version is
bumped from `1` to `2` so existing databases without the table are
recreated with the correct schema.

### Changes

- Add metadata table creation to `create_all_tables` in `initialize.rs`
- Bump `DB_VERSION` from `1` to `2` to trigger fresh DB creation for
users with v1 databases
- Widen `initialize` module and `initialize_db` visibility to
`pub(crate)` for testability
- Add regression tests in `telemetry/mod.rs` that verify the session
query works against a freshly initialized DB and that session
persist/retrieve round-trips correctly
- Refactor `initialize.rs` tests to use `NxDbConnection` instead of raw
rusqlite `Connection`

## Related Issue(s)

<!-- No open issue for this bug -->
2026-03-31 19:40:51 -04:00
Jason Jean 7c3df37845 fix(repo): re-enable Cypress HMR e2e tests after upstream tapable fix (#35105)
## Current Behavior

Six e2e tests were skipped in #34969 because they were failing with a
Cypress uncaught exception: `[HMR] Hot Module Replacement is disabled`.
The error originated from the webpack `styles.js` bundle during the
`before each` hook due to an upstream tapable issue
(webpack/webpack#20693).

Skipped tests:
- `e2e-nx:e2e-ci--src/workspace-legacy.test.ts`
-
`e2e-react:e2e-ci--src/module-federation/independent-deployability.webpack.test.ts`
-
`e2e-react:e2e-ci--src/module-federation/core-webpack-basic-host-remote-generation.test.ts`
-
`e2e-react:e2e-ci--src/module-federation/misc-rspack-interoperability.test.ts`
-
`e2e-react:e2e-ci--src/module-federation/dynamic-federation.webpack.test.ts`
-
`e2e-react:e2e-ci--src/module-federation/federate-module.webpack.test.ts`

## Expected Behavior

The upstream tapable issue has been resolved (webpack/webpack#20693).
All 6 tests should be re-enabled and passing.

## Related Issue(s)

Upstream fix: webpack/webpack#20693
Reverts the skip from #34969
2026-03-31 19:32:34 -04:00
Louie Weng 2630420dc5 chore(gradle): bump gradle project graph plugin version to 0.1.18 (#35091)
## Current Behavior

The Gradle project graph plugin is at version 0.1.17.

## Expected Behavior

The Gradle project graph plugin is bumped to version 0.1.18 with a
corresponding migration entry at version 22.7.0-beta.9

## Related Issue(s)

N/A - routine version bump
2026-03-31 17:41:38 -04:00
Jack Hsu 9dce32118c chore(repo): skip tests failing due to bad lodash@4.18.0 for now (#35104)
Unblock PRs and re-enable once lodash is patched.
2026-03-31 17:25:33 -04:00
Jason Jean fa7eb3b442 chore(repo): make build-base depend on copy-assets instead of reverse (#35102)
## Current Behavior

`copy-assets` depends on `build-base`, which means it has to wait for
the entire `build-native` → `build-base` chain to finish before it can
start — even though asset copying doesn't need compiled output.

## Expected Behavior

`build-base` depends on `copy-assets` instead. This lets `copy-assets`
start immediately (in parallel with `build-native` and `^build-base`)
rather than waiting for them to complete first.

<img width="767" height="721" alt="image"
src="https://github.com/user-attachments/assets/6a1d8090-8390-4075-850e-bfd309cdc6c9"
/>


### Changes

- Removed `dependsOn: ['build-base']` from the `copy-assets` target
generated by `copy-assets-plugin.ts`
- Added `copy-assets` to `build-base.dependsOn` in `nx.json` and
project-level overrides (`gradle`)
- Removed now-transitive `copy-assets` references from `build.dependsOn`
in `nx.json`, `packages/nx`, `packages/angular`, and `packages/gradle`
- Removed `build-base` from `copy-assets.dependsOn` in `packages/nx` and
`packages/dotnet` project overrides

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-03-31 16:24:14 -04:00
Louie Weng f9e5564781 fix(gradle): detect @Input provider-based dependencies (#35090)
## Current Behavior

When Kotlin compilations are associated (e.g. test -> main), the
`friendPathsSet` `@Input` provider creates implicit dependencies on
producer tasks like `compileKotlin`, `compileJava`, and `jar`. Without
detecting these, Nx excludes them via `--exclude-task`, causing a
provider resolution error at execution time when Gradle tries to resolve
the `friendPathsSet` provider.

The previous implementation only detected lifecycle-based provider
dependencies (Phase 1), missing the `@Input` property-based ones.

## Expected Behavior

`findProviderBasedDependencies` now detects both:
1. **Lifecycle dependencies** — `ProviderInternal` and `TaskProvider`
entries in `lifecycleDependencies` (e.g.
`checkKotlinGradlePluginConfigurationErrors`)
2. **`@Input` property dependencies** — producer tasks discovered by
walking task properties with Gradle's `PropertyWalker` +
`PropertyVisitor`, resolving `taskDependencies` from each `@Input`
`PropertyValue` (e.g. `compileKotlin`, `compileJava`, `jar`)

These tasks are added to `includeDependsOnTasks` so they are not
excluded from Gradle execution.

The function is refactored into two immutable collectors
(`collectLifecycleDependencies` and `collectInputPropertyDependencies`)
merged in the parent, replacing the previous mutable-set-passing
pattern.

## Related Issue(s)

Fixes NXC-4174
2026-03-31 13:09:30 -07:00
Craigory Coppola d063b1a4d5 fix(repo): fixup lock-threads failing with resource inaccessible message (#35005)
## Current Behavior
Every lock threads run is failing

## Expected Behavior
Lock threads at least occasionally passes

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-03-31 15:59:52 -04:00
Jack Hsu 2cfc2898a7 fix(react): force Vite 7 when using React Router in framework mode (#35101)
## Current Behavior

Creating a custom React workspace with React Router for server rendering
(framework mode) fails due to a peer dependency conflict between Vite 8
and React Router. Vite 8 is the current default for new workspaces, but
`@react-router/dev` does not yet support it.

## Expected Behavior

The React application generator uses Vite 7 when React Router is
selected, avoiding the peer dependency conflict until React Router adds
Vite 8 support.

<img width="1270" height="1204" alt="image"
src="https://github.com/user-attachments/assets/87495e85-20df-4086-a1f0-eb3380a78771"
/>


## Related Issue(s)

Fixes NXC-4176
2026-03-31 13:32:14 -04:00
Robert Sidzinka 63a8f27e82 fix(webpack): bump postcss-loader to ^8.2.1 to eliminate transitive yaml@1.x CVE (#35028)
## Current Behavior

`@nx/webpack` depends on `postcss-loader@^6.1.1`, which pulls in
`cosmiconfig@7` → `yaml@1.x`. The `yaml@1.x` package has a known stack
overflow vulnerability
([GHSA-48c2-rrv3-qjmp](https://github.com/advisories/GHSA-48c2-rrv3-qjmp)).

## Expected Behavior

By bumping `postcss-loader` to `^8.2.1`, the transitive dependency chain
is eliminated entirely — `postcss-loader@8` uses `cosmiconfig@9`, which
no longer depends on `yaml` at all. This is a cleaner fix than applying
a `pnpm.overrides` workaround.

The upgrade is safe because:
- `postcss-loader@8` peer deps (`postcss ^7||^8`, `webpack ^5`) are
unchanged
- The `implementation` option and function-based `postcssOptions` API
used by `@nx/webpack` are fully supported in v8
- Nx already requires Node 18+, matching postcss-loader@8's engine
requirement

## Related Issue(s)

Fixes #35025
2026-03-31 11:26:07 -04:00
Jason Jean c59040f340 fix(core): sandbox exclusions, multi-line typeof import detection, global ensurePackage mock (#35056)
## Current Behavior

1. **Sandboxing false positives**: `tsc --build` reads `.tsbuildinfo`
files as an optimization hint, and the `nx-plugin-checks` lint rule
reads `schema.json` from `dist/` directories. Both are flagged as
sandbox violations even though they don't affect caching correctness.

2. **Missing dependencies in project graph**: `typeof import('...')`
inside multi-line generic type parameters (e.g. `ensurePackage<typeof
import('@nx/playwright')>()`) is not detected by the import analyzer.
The newline between `<` and `import()` resets the import type to
Dynamic, so packages like `@nx/playwright` and `@nx/storybook` are
missing from the dependency graph.

3. **ensurePackage mock duplication**: Multiple test files individually
mock `@nx/devkit` just to override `ensurePackage` so it resolves from
source instead of `node_modules`. This is repetitive and easy to miss in
new tests.

## Expected Behavior

1. **Sandboxing**: `.tsbuildinfo` reads are globally excluded.
`dist/**/*.json` reads are excluded for lint targets.

2. **Import analyzer**: `typeof import('...')` inside multi-line
generics is correctly detected as a static import by tracking angle
bracket depth and preserving import type across newlines inside
generics.

3. **ensurePackage mock**: A global `ensurePackage` mock in
`scripts/unit-test-setup.js` replaces per-file mocks, using
`jest.requireActual` to resolve from source code.

## Related Issue(s)

<!-- No directly related open issues found -->

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-31 11:25:16 -04:00
Leosvel Pérez Espinosa 424dabbbd7 fix(core): restore nx package exports compatibility (#35095)
## Current Behavior

The `nx` package's `exports` field uses conditional exports that
restrict subpath access. Consumers relying on deep imports (e.g.,
`nx/src/command-line`, `nx/src/project-graph/plugins`) or importing with
file extensions (e.g., `nx/bin/nx.js`) can't resolve modules or types.
The `typesVersions` field is also incomplete and out of sync with
`exports`, breaking type resolution for consumers using
`moduleResolution: "node"` (node10).

## Expected Behavior

The `nx` package exposes all necessary subpaths through both `exports`
(for modern resolution) and `typesVersions` (for node10 resolution),
keeping them in sync. A new conformance rule prevents future drift
between the two fields.

> **Note:** This restores backwards compatibility to avoid breaking
changes in the current major version. Deep imports into `nx/src/*`
access private/internal APIs that are not part of the public contract —
they are not guaranteed to be stable and may break without notice. In Nx
v23, we plan to constrain the exports to a well-defined public API,
which will be a breaking change.

## Changes

- **Restore `nx` package exports**: expand `exports` and `typesVersions`
to cover all public subpaths including `bin/*`, `plugins/*`,
`src/command-line`, `src/project-graph/plugins`, `release/*`,
`tasks-runners/*`, and their `.js` extension variants
- **Add `types-versions-exports-sync` conformance rule**: enforces that
every `exports` entry with a `types` condition has a corresponding
`typesVersions` entry and vice versa, preventing future drift
2026-03-31 17:11:47 +02:00
Leosvel Pérez Espinosa 77a119a021 fix(core): preserve sibling dependency inputs in native hashing (#35071) 2026-03-31 10:20:24 -04:00
Miroslav Jonaš 43a108e6df docs(nx-dev): replace nx-cloud calls with nx wrapper in docs (#35094)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: meeroslav <meeroslav@users.noreply.github.com>
2026-03-31 15:17:02 +02:00
Miroslav Jonaš 5b8cd7336f fix(core): pin version of axios (#35093)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-31 15:15:43 +02:00
Jason Jean b1d8db3938 fix(js): include tsbuildinfo in narrowed tsc build-base outputs (#35086)
## Current Behavior

PR #35041 narrowed the `build-base` target outputs to only match
tsc-produced file types (e.g. `**/*.{js,d.ts,...}{,.map}`), preventing
cross-OS cache pollution from native binaries. However, `.tsbuildinfo`
files were not included in the narrowed glob, so they are no longer
captured as build outputs. The tsbuildinfo handling was also spread
across multiple conditional branches with duplicated logic.

## Expected Behavior

`.tsbuildinfo` files are always included as a build output since `tsc
--build` implicitly enables `incremental: true` and always produces
them. A new `getTsBuildInfoOutputPath` helper centralizes the logic for
determining the tsbuildinfo file location (respecting `tsBuildInfoFile`,
`outFile`, `outDir`, or the default), and is called once unconditionally
at the end of the output resolution loop.

## Related Issue(s)

Follow-up to #35041
2026-03-31 09:32:34 +02:00
Louie Weng 8a77a08890 fix(gradle): use object notation for exclude tasks (#35085)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
                  
The Gradle executor's task exclusion logic represents running tasks and
dependency relationships using colon-delimited string IDs (e.g.
"project:target"). Parsing task identity by splitting on : breaks
silently when project or target names contain colons — a common pattern
in Gradle (e.g. :sub:project, compile:java).
## Expected Behavior     

Task identity is represented as a structured ProjectTarget object with
explicit project and target fields, eliminating string-splitting
ambiguity. The getExcludeTasks, getAllDependsOn, and getGradleTaskName
functions now accept and return typed objects, and test fixtures use
object-notation dependsOn entries. A new test case verifies correct
behavior when names contain colons.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: lourw <lourw@users.noreply.github.com>
2026-03-30 18:17:07 -07:00
Jamie Mason d24aa1a0ce docs(misc): add link to syncpack documentation (#35084)
Hey all,
Thanks a lot for mentioning [syncpack](https://syncpack.dev/) in the
[Managing
Dependencies](https://nx.dev/docs/getting-started/tutorials/managing-dependencies)
tutorial, this PR asks if you would kindly add a link to the official
site 🙏

Thanks a lot,

Jamie
2026-03-30 14:15:02 -04:00
Jack Hsu b86d64119f chore(misc): rename vulnerable packages in test fixture lockfiles (#35072)
This PR removes 62% of dependabot alerts, stemming from unit test
fixtures.

## Current Behavior

Dependabot scans `package.json` files in lock-file test fixtures and
raises vulnerability alerts for packages that are only used as test data
(e.g. `express`, `minimatch`, `postcss`). These are not real
dependencies.

## Expected Behavior

Renaming fixture files from `package.json` to `package.fixture.json`
prevents dependabot from scanning them. The `.fixture.json` extension
still ends in `.json`, so Node's `require()` continues to work without
any test logic changes — only the file paths in the spec files needed
updating.

## Related Issue(s)

Fixes NXC-4169

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
2026-03-30 13:47:55 -04:00
Jack Hsu fe5a463c15 feat(misc): update nx init telemetry meta from CSV to JSON format (#35076)
## Current Behavior

`nx init` emits telemetry meta as a CSV string (e.g. "22.6.3,enable-ci")
via `recordStat`, only at the cloud prompt step. There is no start or
error tracking, no AI detection, and no environment context.

## Expected Behavior

`recordStat` now accepts a typed `RecordStatMeta` object and serializes
as JSON, matching the CNW format. Three lifecycle events are recorded:

- **start**: nodeVersion, os, packageManager, aiAgent, isCI
- **complete**: same env info plus pluginsInstalled, useCloud
- **error**: errorCode, errorMessage, aiAgent

The existing cloud prompt `recordStat` calls also now include env info.

## Related Issue(s)

Closes NXC-4168
2026-03-30 12:12:20 -04:00
Jack Hsu d61123be47 fix(core): handle "." and absolute paths as workspace name in CNW (#35083)
## Current Behavior

CNW rejects "." and absolute paths (e.g. `/tmp/acme`) as workspace
names, causing over 1,300 INVALID_WORKSPACE_NAME and DIRECTORY_EXISTS
errors per month. This is the #1 input validation error for both AI
agents and humans.

## Expected Behavior

- "." and "./" in a non-empty directory suggests using `nx init` instead
- "." and "./" in an empty directory resolves to the directory's
basename and creates the workspace in-place
- Absolute paths like `/tmp/acme` extract the basename as the workspace
name and create the workspace at the specified location

The `workingDir` override is threaded through `CreateWorkspaceOptions`
to downstream functions (`createWorkspace`, `createEmptyWorkspace`,
`createPreset`, `cloneTemplate`) without mutating `process.cwd()`.

## Related Issue(s)

Closes NXC-4172
2026-03-30 12:12:11 -04:00
Jack Hsu 203203f238 fix(repo): bump picomatch from 4.0.2 to 4.0.4 (#35081)
## Current Behavior

The pnpm catalog pins picomatch to `4.0.2`, which has two high-severity
vulnerabilities:
-
[GHSA-3v7f-55p6-f55p](https://github.com/advisories/GHSA-3v7f-55p6-f55p)
— Method Injection in POSIX Character Classes causes incorrect glob
matching
-
[GHSA-c2c7-rcm5-vvqj](https://github.com/advisories/GHSA-c2c7-rcm5-vvqj)
— ReDoS via extglob quantifiers

Running `npm audit` on any workspace using `@nx/angular`, `@nx/js`, or
`@nx/workspace` reports these vulnerabilities.

## Expected Behavior

No picomatch-related vulnerabilities reported by `npm audit`. The bump
to `4.0.4` is a patch release that only fixes the security issues with
no API changes.

## Related Issue(s)

Fixes #35068
2026-03-30 12:09:55 -04:00
Jack Hsu 4b8b46b9e4 fix(vite): bump sass version for vue/nuxt presets for Vite 8 compat (#35073)
## Current Behavior

CNW vue-monorepo and nuxt presets pin sass@1.62.1, but Vite 8 requires
sass >= ^1.70.0, causing ERESOLVE failures on npm during workspace
creation.

## Expected Behavior

Workspaces created with vue-monorepo and nuxt presets install
successfully with Vite 8 by using a compatible sass version range.

## Related Issue(s)

Fixes NXC-4171
2026-03-30 11:33:23 -04:00
Jack Hsu a309e3181a fix(core): validate bundler option for Angular presets in create-nx-workspace (#35074)
## Current Behavior

When `--bundler=vite` is passed with an Angular preset
(`angular-monorepo` or `angular-standalone`), yargs accepts it since
`--bundler` is a shared `type: 'string'` option with no per-stack
`choices` constraint. The invalid value flows through to the preset
generator which rejects it after a full pnpm install (~25s), wasting the
user's time.

## Expected Behavior

Validate the bundler early in `determineAngularOptions` before any
install starts. Invalid bundlers throw `CnwError('INVALID_BUNDLER')` so
the error is:
- Properly recorded via `recordStat` for telemetry
- Surfaced as NDJSON for AI agents
- Shown as a clear `output.error()` for interactive users

Valid Angular bundlers: `esbuild`, `rspack`, `webpack`.

## Related Issue(s)

Fixes NXC-4166
2026-03-30 10:30:56 -04:00
Jason Jean 5de23860d3 feat(repo): enable tsgo compiler for nx package (#35047)
## Current Behavior

All packages in the workspace use the standard `tsc` compiler via the
`@nx/js/typescript` plugin.

## Expected Behavior

The `packages/nx` project uses the new Go-based TypeScript compiler
(`tsgo`) for faster builds and typechecks, while all other projects
continue using `tsc`.

### Changes
- Install `@typescript/native-preview` as a dev dependency
- Add a separate `@nx/js/typescript` plugin entry scoped to
`packages/nx` with `compiler: "tsgo"`
- Override `baseUrl: null` in the nx tsconfig to clear the inherited
`baseUrl` (removed in tsgo)
- Set `strict: false` to match existing tsc behavior (tsgo defaults to
strict mode)

## Related Issue(s)

N/A — exploratory adoption of tsgo

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-29 18:56:13 -04:00
Jason Jean 886d78dfb2 chore(repo): update nx to 22.7.0-beta.7 (#35065)
Updating Nx from 22.7.0-beta.6 to 22.7.0-beta.7

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-03-29 00:50:08 +00:00
Jack Hsu c9253fba67 fix(vite): update vitest and plugin-react-swc versions for vite 8 compat (#35062)
## Current Behavior

When creating a workspace with `create-nx-workspace` using presets that
install vitest (react-monorepo with vite bundler, nuxt with vitest, vue,
etc.) and **npm** as the package manager, `npm install` fails with
`ERESOLVE unable to resolve dependency tree`.

**Root cause:** vitest `~4.0.x` depends on `@vitest/mocker@4.0.x` which
has a peer dependency on `vite: "^6.0.0 || ^7.0.0"` — it does **not**
support vite 8. Since Nx now defaults to installing `vite@^8.0.0`, npm
cannot satisfy `@vitest/mocker`'s peer dependency.

Additionally, `@vitejs/plugin-react-swc@^3.5.0` has a peer dependency on
`vite: "^4 || ^5 || ^6 || ^7"` (no vite 8 support), and the SWC compiler
path in `ensure-dependencies.ts` lacks the vite-version detection logic
that the babel path already has.

## Expected Behavior

- Workspaces using vite 8 + vitest install successfully with npm
- `@vitejs/plugin-react-swc` version is selected based on the installed
vite version (v4.3+ for vite 8, v3.x for older vite)

## Changes

1. **Bump `vitestV4Version`** from `~4.0.0`/`~4.0.8` to `~4.1.0` in both
`@nx/vite` and `@nx/vitest` — vitest 4.1.x's dependencies support vite 8
2. **Bump `vitestV4CoverageV8Version` and
`vitestV4CoverageIstanbulVersion`** to `~4.1.0` to match
3. **Update `vitePluginReactSwcVersion`** from `^3.5.0` to `^4.3.0`
(first version with vite 8 peer dep support), add
`vitePluginReactSwcV3Version = '^3.5.0'` for backward compat
4. **Add vite-version detection** to the SWC path in
`ensure-dependencies.ts` (both `@nx/vite` and `@nx/vitest`), mirroring
the existing babel path logic

## Related Issue(s)

Fixes NXC-4164
2026-03-28 12:28:35 -04:00
Jason Jean 87a7c921b1 fix(repo): pass env vars into docker builds in publish workflow (#35060)
## Current Behavior

The `publish.yml` workflow defines `NX_GRADLE_PROJECT_GRAPH_TIMEOUT` and
`NX_VERBOSE_LOGGING` as workflow-level env vars, but the `docker run`
command for Linux native builds only passes `-e PNPM_VERSION`. This
means those env vars are not available inside the Docker containers,
causing Gradle timeouts and missing verbose logs during publish.

## Expected Behavior

`NX_GRADLE_PROJECT_GRAPH_TIMEOUT` and `NX_VERBOSE_LOGGING` are passed
into the Docker containers via `-e` flags so they take effect during
Linux native builds.

## Related Issue(s)

N/A
2026-03-28 16:04:12 +00:00
Jason Jean 439fa7308a chore(repo): fix critical handlebars and underscore vulnerabilities in npm audit (#35063)
## Current Behavior

The npm security audit CI job fails due to two critical vulnerabilities:
- **handlebars** (GHSA-2w6w-674q-4c4q): JavaScript Injection via AST
Type Confusion in versions `>=4.0.0 <=4.7.8`, pulled in transitively via
`verdaccio > @verdaccio/hooks > handlebars@4.7.7`
- **underscore** (GHSA-cf4h-3jhx-xvhq): Arbitrary Code Execution in
versions `<1.13.8`, pulled in via `parse-markdown-links > remarkable >
argparse > underscore`

## Expected Behavior

The npm security audit CI job passes with zero critical vulnerabilities.

## Changes

- Update `verdaccio` from `6.0.5` to `6.3.2` (drops direct handlebars
dependency)
- Remove unused direct `handlebars` devDependency (nothing in the repo
imports it)
- Add `pnpm.overrides` for `handlebars@4.7.9` (needed because
`@verdaccio/hooks` still pins `handlebars@4.7.7` with no stable fix
available) and `underscore@^1.13.8` (needed because
`parse-markdown-links` pins `remarkable@1.7.1` with no fix available)
- Update verdaccio generator version to `^6.3.2` so new workspaces get
the safe version
- Add migration for `22.6.4` to bump verdaccio in existing workspaces

## Related Issue(s)

Fixes the failing [npm-audit CI
job](https://github.com/nrwl/nx/actions/runs/23672915671/job/68970040091)
2026-03-28 14:44:04 +00:00
Jason Jean 9a0ac75974 fix(gradle): increase project graph timeout defaults (#35058)
## Current Behavior

The Gradle plugin's `createNodes` project graph generation has a fixed
60-second timeout. For large Gradle workspaces, this is too short and
causes timeouts — especially in CI environments where builds may be
slower.

## Expected Behavior

- **Local**: Default timeout increased to 3 minutes (180s)
- **CI**: Default timeout increased to 10 minutes (600s)
- The `NX_GRADLE_PROJECT_GRAPH_TIMEOUT` env var still works as an
override
- The publish workflow explicitly sets
`NX_GRADLE_PROJECT_GRAPH_TIMEOUT=600` for extra safety
2026-03-28 03:49:30 +00:00
Jason Jean f39f30417a fix(js): narrow tsc build-base outputs to only tsc-produced file types (#35041)
## Current Behavior

The `@nx/js/typescript` plugin claims the entire `outDir` (e.g.
`{projectRoot}/dist`) as the output for `build-base` targets. When other
tasks like `copy-assets` also write into the same directory (e.g.
`.node` or `.wasm` native binaries), those files get captured in the
`build-base` cache. This causes cross-OS cache pollution — linux native
binaries get cached and restored on macOS (or vice versa).

## Expected Behavior

`build-base` outputs are scoped to only the file types that `tsc`
actually produces:
- `**/*.{js,cjs,mjs,jsx,d.ts,d.cts,d.mts}{,.map}` (default)
- `**/*.{js,cjs,mjs,jsx,json,d.ts,d.cts,d.mts}{,.map}` (when
`resolveJsonModule` is enabled)

Native binaries (`.node`, `.wasm`) and other non-tsc files in the same
output directory are no longer captured by the `build-base` cache,
preventing cross-OS cache corruption.

## Related Issue(s)

N/A — discovered during investigation of cross-OS cache artifacts.
2026-03-27 22:22:40 +00:00
Jason Jean ff351f7294 chore(repo): update nx to 22.7.0-beta.6 (#35031)
Updating Nx from 22.7.0-beta.4 to 22.7.0-beta.6

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-03-27 20:53:58 +00:00
Jason Jean 1d01a675e4 fix(js): recognize tsgo in dependency-checks lint rule (#35048)
## Current Behavior

The `@nx/dependency-checks` lint rule detects `tslib` as a required
dependency by checking if the build command contains `tsc` (via
`/\btsc\b/` regex). When a project uses `tsgo` as its compiler, the
build command is `tsgo --build` which doesn't match — causing a false
positive "tslib is not used" lint error.

## Expected Behavior

The regex also matches `tsgo`, so projects using either `tsc` or `tsgo`
correctly detect `tslib` as needed when `importHelpers: true` is set.

## Related Issue(s)

N/A — discovered while enabling tsgo for the nx package
2026-03-27 20:12:28 +00:00
Jason Jean 691bb11c68 fix(repo): copy-assets plugin and e2e improvements (#35042)
## Current Behavior

- The copy-assets plugin copies `assets.json` into the output directory
(it doesn't exclude itself)
- The copy-assets executor catches errors with `error.message` but
`error` is typed as `unknown`, which can fail at runtime
- No way to skip e2e cleanup when debugging locally

## Expected Behavior

- `assets.json` is excluded from being copied into the output directory
- Error handling properly checks `instanceof Error` before accessing
`.message`
- Setting `NX_E2E_SKIP_CLEANUP=true` preserves the test project
directory for debugging

## Related Issue(s)

Follow-up to #34994 — addresses review comments from that PR.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-27 14:49:01 -04:00
Jack Hsu a8b5d0466d feat(nx-dev): add conditional blog/changelog proxy in edge function (#35043)
## Current Behavior

`/blog` and `/changelog` paths are always served by the Next.js app. The
Netlify edge function excludes these paths (`/blog/*` in `excludedPath`,
`/blog` and `/changelog` in `nextjsPaths`) so they bypass the Framer
proxy and fall through to Next.js.

## Expected Behavior

When the `BLOG_URL` env var is set in Netlify, `/blog/*` and
`/changelog/*` requests are proxied to the standalone blog site (e.g.,
`nrwl-blog.netlify.app`) via the existing Netlify edge function. When
`BLOG_URL` is **not** set, behavior is unchanged — paths fall through to
Next.js as before.

**Changes to `netlify/edge-functions/rewrite-framer-urls.ts`:**

- Read `BLOG_URL` env var
- Conditionally remove `/blog` and `/changelog` from `nextjsPaths` when
`BLOG_URL` is set
- Remove `/blog/*` and `/changelog` from static `excludedPath` so the
edge function can intercept these paths
- Add blog proxy branch: when `BLOG_URL` is set and path matches, proxy
to blog site with URL rewriting (`blogUrl` → `https://nx.dev`) and
security headers (`X-Frame-Options`, `CSP`)
- When `BLOG_URL` is unset, blog/changelog paths fall through to Next.js
via explicit `context.next()` guard

**Asset resolution note:** The edge function only handles `text/html`
requests. Blog site assets (CSS, JS) will need to be served from the
blog site's own CDN (via `build.assetsPrefix` or equivalent in the blog
repo). All existing `/_next/*` assets remain in `excludedPath` and are
unaffected.

## Related Issue(s)

Fixes DOC-455
2026-03-27 14:04:39 -04:00
Jack Hsu 7540eb784e fix(misc): handle non-interactive mode and add template shorthand names for CNW (#35045)
This PR address some common errors seen during CNW around `--template`
and `--preset` during non-interactive flows that are not AI agents. Also
sees that some template names are not qualified with
`nrwl/<name>-template` so we can normal though (which also makes it
shorter in docs).

## Current Behavior

In non-interactive contexts (IDE terminals, scripts, SSH without `-t`),
`determineTemplate()` returns `'custom'` which routes to the preset
flow. Without `--preset` provided, this throws "Preset is required",
which affects ~15 users/day (~145 occurrences Mar 18-27).

Users must also pass full template paths like
`--template=nrwl/angular-template`.

## Expected Behavior

Non-interactive mode defaults to `nrwl/empty-template` (template flow)
instead of `'custom'` (preset flow) when neither `--preset` nor
`--template` is provided.

Shorthand template names are supported:
- `--template=angular` → `nrwl/angular-template`
- `--template=react` → `nrwl/react-template`
- `--template=typescript` → `nrwl/typescript-template`
- `--template=empty` → `nrwl/empty-template`

## Related Issue(s)

Fixes NXC-4153
2026-03-27 13:34:07 -04:00
Jason Jean 6c92d9201f fix(js): add {projectRoot} prefix to d.ts fileset in typescript plugin (#35037)
## Current Behavior

The TypeScript plugin emits a bare `**/*.d.ts` fileset input (without a
`{projectRoot}/` prefix) for dependency file tracking. The Nx hasher
expects all filesets to start with either `{projectRoot}/` or
`{workspaceRoot}/`, so it logs a warning for every project:

```
NX **/*.d.ts does not start with {workspaceRoot}/. This will throw an error in Nx 20.
```

## Expected Behavior

No warning is emitted. The fileset correctly uses
`{projectRoot}/**/*.d.ts` so the hasher knows it's scoped to the
dependency project's root.

## Related Issue(s)

N/A — discovered while debugging e2e test failures.
2026-03-27 15:53:08 +00:00
Jason Jean a040a93791 fix(repo): add copy-assets plugin and migrate all packages from legacy-post-build (#34994)
## Current Behavior

Each package defines a `legacy-post-build` target in `project.json` with
inline asset copy configuration. Inputs are not accurately declared,
leading to sandbox violations in CI. The asset globs, ignores, and
outputs must be manually kept in sync across 37 packages.

## Expected Behavior

A `copy-assets` createNodesV2 plugin reads `assets.json` from each
package and automatically generates the target with:
- Inputs derived from asset globs (positive patterns first, then
negations)
- Outputs derived using the same dest logic as `CopyAssetsHandler`
- `dependentTasksOutputFiles` for gitignored build artifacts (jars,
native binaries)
- Automatic `outDir` exclusion from asset copies

## Changes

**New infrastructure:**
- Add `copy-assets` createNodesV2 plugin in `tools/workspace-plugin`
- Add `copy-assets` executor (simplified from `legacy-post-build` — just
copies assets, no package.json field manipulation)
- Extract `normalizeAssets` and `getAssetOutputPath` into reusable
module in `packages/js`
- Add `copyReadme` namedInput for copy-readme build targets

**Migration (all 37 packages):**
- Create `assets.json` for every package defining what to copy
- Remove all `legacy-post-build` targets from project.json files
- Remove `legacy-post-build` target defaults from nx.json
- Remove redundant `copy-local-native` target (replaced by
`.node`/`.wasm` in asset glob)

**Cleanup:**
- Remove dead config: `creator-files` globs, non-existent template
files, typo'd directory names
- Use root-level `tsconfig*.json` ignore instead of recursive (so
template tsconfigs in `files/` dirs are still copied)
- Replace `!(*.ts)` extglob patterns with explicit globs (extglobs don't
work correctly in Nx inputs)
- Fix gradle lint: add `buildTargets: ["build-base"]` and `tslib`
dependency
- Add jar outputs to maven `_package` target for correct
`dependentTasksOutputFiles` resolution

**Other fixes:**
- Exclude `.swc` directories from sandbox write checks
- Enable typecheck for `angular-rspack` packages (remove
`addTypecheckTarget: false`)
- Pin workspace-plugin deps to explicit versions, add `@nx/plugin` to
root package.json

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-03-27 11:49:24 -04:00
Jack Hsu cc6b5a3046 feat(misc): a/b test cloud prompt copy in create-nx-workspace (#35039)
## Current Behavior

The cloud prompt in CNW is disabled (shouldShowCloudPrompt returns
false). Users see no cloud prompt during workspace creation.

## Expected Behavior

Re-enables the cloud prompt with 3 copy variants tied to the flow
variant (NX_CNW_FLOW_VARIANT), using the existing A/B testing
infrastructure:
- Variant 0 (baseline): "Connect to Nx Cloud?"
- Variant 1 (remote caching): "Enable remote caching to speed up builds
and CI?"
- Variant 2 (CI-first): "Speed up your CI with Nx Cloud?"

Each variant has a unique tracking code for measuring yes/skip/never
rates. New variants emphasize concrete benefits (remote caching, CI
speed), mention CI providers (GitHub, GitLab), and note free tier and
2-minute setup.

Also removes unused shouldShowCloudPrompt() function.

## Screenshots

Variant 0 (current prompts):

<img width="1392" height="935" alt="cnw-variant-0"
src="https://github.com/user-attachments/assets/38187e44-5c8f-41b1-b2d9-bdb0eead3f7d"
/>

Variant 1 (remote cache to speed up builds, free for small teams):

<img width="1392" height="939" alt="image"
src="https://github.com/user-attachments/assets/ba7ab127-c91e-4566-bae6-6f7fe797959e"
/>

Variant 2 (speed up CI, mention CI provides):

<img width="1392" height="935" alt="cnw-variant-2"
src="https://github.com/user-attachments/assets/05bc027a-dcc9-47c4-944c-35ca2fce2007"
/>

## Related Issue(s)

Closes NXC-4113
2026-03-27 11:11:40 -04:00
Jack Hsu a4e8ce9f63 chore(repo): ensure Cypress CT unit tests use Vite 7 since 8 is unsupported (#35038)
Currently, updating to 22.7.0-beta.5 causes Angular and React unit tests
to fail when we are pulling in Vite 8 instead of 7.

Cypress component tests do not support Vite 8 yet, so let's ensure that
the unit tests set up v7 for now.

https://github.com/cypress-io/cypress/issues/33078
2026-03-27 10:27:46 -04:00
James Henry 380aaf1e92 fix(core): add package export for nx/release/changelog-renderer (#35033) 2026-03-27 09:58:47 -04:00
Szymon Wojciechowski a007fce766 chore(repo): use SHAs for GH actions (#35035)
Pin actions versions to SHAs in workflows for improved security.
2026-03-27 14:27:56 +01:00
Juri Strumpflohner fbe33a9cc9 fix(nx-dev): correct YouTube channel URL on courses page (#35034)
## Current Behavior

The courses page YouTube link points to
`https://www.youtube.com/@naborx` which is incorrect.

## Expected Behavior

The link points to `https://www.youtube.com/@nxdevtools`, the actual Nx
YouTube channel.

## Related Issue(s)

N/A
2026-03-27 13:48:22 +01:00
Jason Jean 9747038c90 fix(repo): resolve FreeBSD build disk space issue (#35030)
## Current Behavior

The FreeBSD native build in the publish workflow runs out of disk space
(`No space left on device`). The VM has an 11G disk and the Rust build
fills it completely. The build was already on the edge — the Mar 25 run
succeeded with only 11MB to spare, and the Mar 26 run failed after rustc
1.94.1 slightly increased artifact sizes.

A major contributor is OpenJDK17 and its ~30 X11/font dependencies
(~500MB) being installed solely for the `@nx/gradle` plugin's project
graph step, which is irrelevant to building native Rust bindings.

## Expected Behavior

The FreeBSD build completes successfully with comfortable disk headroom
by disabling the Gradle plugin via `NX_GRADLE_DISABLE=true`, which
eliminates the need for Java and its heavy dependency tree.

## Related Issue(s)

Fixes the FreeBSD build failure:
https://github.com/nrwl/nx/actions/runs/23613960439/job/68776656960
2026-03-26 22:39:50 +00:00
Jack Hsu dd376c2a86 fix(misc): make webinar banner theme-aware with light mode support (#35029)
## Current Behavior

The webinar banner has a hardcoded dark background (`bg-zinc-950`) with
white text in both light and dark mode. This makes the close button
barely visible in light mode since Starlight's global styles override
inherited text colors. The banner design also differs noticeably from
the Framer marketing site, which uses a light background in light mode.

## Expected Behavior

The banner adapts to the current theme:
- **Light mode**: White background, subtle border, dark text, dark CTA
button — matching the Framer marketing site design
- **Dark mode**: Retains the existing dark background with white text
and pink CTA button

All interactive elements (close button, CTA buttons) have proper
contrast in both modes.

Dark:

<img width="759" height="499" alt="image"
src="https://github.com/user-attachments/assets/684113e8-cc48-43df-b17d-431ae8c864fc"
/>

Light: 

<img width="635" height="318" alt="image"
src="https://github.com/user-attachments/assets/9ed514b7-dfe9-45c6-91d7-158434aa6cdc"
/>


## Related Issue(s)

Fixes DOC-457
2026-03-26 16:51:26 -04:00
Jason Jean cf82d78b9c chore(core): bump Rust toolchain to 1.94.0 and fix all warnings (#35021)
## Current Behavior

Rust toolchain is pinned to 1.90.0 (4 versions behind stable). Building
the native code produces 23 compiler warnings (elided lifetimes, unused
imports, dead code).

## Expected Behavior

Rust toolchain is updated to 1.94.0 (latest stable). Native code
compiles with zero warnings.

## Related Issue(s)

N/A — maintenance/hygiene change.

### Changes

- **`rust-toolchain.toml`**: 1.90.0 → 1.94.0
- **Glob parser**: Fixed 19 elided lifetime warnings via `cargo fix`
- **`task_hasher.rs`**: Removed unused `anyhow::anyhow` import
- **`command.rs`**: Removed unnecessary `mut`
- **`hash_plan_inspector.rs`**: Removed unused `project_graph` field and
constructor parameter
- **`hash-plan-inspector.ts`**: Updated TS caller to match new native
constructor signature
- **`index.d.ts`**: Updated generated types to reflect removed parameter

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-26 15:58:04 -04:00
Louie Weng c8ddfedf61 chore(gradle): add project graph timeout (#35018)
## Current Behavior

When the Gradle plugin invokes `gradlew nxProjectGraph` to generate the
project graph, there is no timeout. If Gradle hangs or takes an
unexpectedly long time, the Nx process blocks indefinitely with no
feedback to the user.

## Expected Behavior

The Gradle project graph generation now has a configurable timeout that
defaults to 60 seconds. If the process exceeds this limit:

- The Gradle process is aborted via an `AbortController` signal
- A clear error message is shown with actionable remediation steps:
  - Run `gradlew --stop` and `gradlew clean`
  - Increase the timeout via `NX_GRADLE_PROJECT_GRAPH_TIMEOUT`
  - Disable the plugin entirely via `NX_GRADLE_DISABLE=true`

Users can configure the timeout by setting the
`NX_GRADLE_PROJECT_GRAPH_TIMEOUT` environment variable (in seconds).
Invalid or non-positive values fall back to the 30-second default.

Documentation for the new environment variable has been added to the
environment variables reference page.

## Related Issue(s)

Fixes NXC-4140
2026-03-26 18:57:19 +00:00
Jack Hsu 27c7928519 docs(misc): add consistent next steps and tutorial links across getting started pages (#35024)
## Current Behavior

Getting started pages have inconsistent navigation patterns. Some use
card grids, some use bullet lists, and some have no next steps at all.
The new tutorial series is not linked from most getting started pages.

## Expected Behavior

All getting started pages use consistent bullet-list navigation with
clear next steps. Every page links to the tutorial series so first-time
users can discover it from any entry point.

For example, on the "Add to an existing project" page:

<img width="1011" height="709" alt="image"
src="https://github.com/user-attachments/assets/6534d490-1bdf-4026-9962-0a60506564ce"
/>

### Changes

- **intro.mdoc**: Updated tutorial link from generic "Follow a tutorial"
to "Follow the tutorial series" pointing to the first tutorial
- **installation.mdoc**: Added "Next steps" section linking to new
project, existing project, and tutorial series
- **start-new-project.mdoc**: Added "Next steps" section with tutorial
series, editor setup, and CI setup links
- **start-with-existing-project.mdoc**: Replaced card grid with bullet
list for in-depth guides, added "Keep learning" section with tutorial
series link
- **sidebar.mts**: Fixed label consistency ("Reduce boilerplate" →
"Reducing boilerplate")
2026-03-26 12:06:53 -04:00
Jason Jean b3cb4d8261 fix(core): prevent nx watch infinite loop from overly broad output globs (#34995)
## Current Behavior

Running `nx watch --projects nx -i -- nx build nx` enters an infinite
rebuild loop. Two issues cause this:

1. **Overly broad output globs**: `build-base` declared
`{projectRoot}/src/**/*.d.ts` and `legacy-post-build` declared
`{projectRoot}/**/*.d.ts` as outputs. Cache restore deletes and
recreates committed `.d.ts` files (like `schema.d.ts`,
`perf-hooks.d.ts`) that aren't actual build outputs, triggering the file
watcher.

2. **`declarationDir` in source tree**: With `declarationDir: "."`, tsc
wrote `.d.ts` files into the source tree, making them vulnerable to
cache restore churn.

## Expected Behavior

`nx watch` should not loop when the build produces the same outputs.
Only files actually produced by a build target should be declared as
outputs.

## Changes

- **Move `declarationDir` to `dist`**: Declaration files now go to
`dist/` alongside `.js` output, keeping the source tree clean
- **Remove manual output overrides from `build-base`**: The
`@nx/js/typescript` plugin correctly infers `{projectRoot}/dist`
- **Narrow `legacy-post-build` outputs**: Only declares
`{projectRoot}/dist` since that's the only directory the executor writes
to
- **Fix package.json exports**: Replace invalid `**` subpath patterns
with `*` (Node.js exports only support `*` as wildcard)
- **Add `typesVersions`**: Ensures TypeScript projects using
`moduleResolution: "node"` (which ignores exports maps) can still
resolve `.d.ts` files in `dist/`
- **Remove `@types/minimatch`**: `minimatch@10` ships its own types; the
`@types` package was outdated and conflicted
- **Add verbose watcher logging**: Behind `NX_VERBOSE_LOGGING`, shows
which files were created/updated/deleted by the workspace watcher

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-03-26 04:52:20 +00:00
Leosvel Pérez Espinosa d0ae7045e8 chore(repo): improve nightly failure report with per-error details, start dates, and job links (#34988)
## Current Behavior

The nightly failure report already includes per-error details, but has
several accuracy and completeness issues:

1. **Incorrect per-error start dates** — e.g., `e2e-web`'s
`file-server-legacy.test.ts` reports "failing since 2026-03-24 (1 day)
NEW" when the `edgesOut` error actually started on 2026-03-18 (7 days).
The validation only compared test file names, not error content, so
same-file-different-error changes were missed.

2. **Different combo errors merged** — `e2e-web` fails with different
root causes on different combos (yarn: "could not find a copy of vite to
link", npm: "Cannot read properties of null (reading 'edgesOut')"), but
only one error block is shown with all combos listed together.

3. **Build failures show no useful details** — when tests don't run
because a build step fails (e.g., `gradle:build-base` with TS errors),
the report shows "No test output available" instead of the actual build
errors.

4. **No clickable links to jobs** — the golden projects table is a plain
code block with no links to the specific failed jobs.

5. **Other projects table** wastes vertical space with a full code-block
table.

## Expected Behavior

All issues above are fixed:

**Summary section** — golden projects with clickable job links, compact
other-projects list:
```
🌟 Golden Projects
 Passing: 18 |  Failing: 2

🚨 Failed Golden Projects

e2e-react-native
  · MacOS/npm/20              ← clickable link to job

e2e-web
  · MacOS/npm/20              ← clickable link to job
  · Linux/yarn/20             ← clickable link to job
  · Linux/npm/20              ← clickable link to job

⚠️ Failed Other Projects: e2e-angular (4), e2e-nuxt (4), e2e-nx (10), ...
```

**Failure details** — per-combo errors shown separately with accurate
start dates:
```
e2e-web — 3 combos (npm+yarn)
Project failing since 2026-03-13 | Last fully passing: 2026-03-12

📋 file-server-legacy.test.ts (Linux/yarn/20) — failing since 2026-03-13 (12 days)
    error Invariant Violation: could not find a copy of vite to link ...

📋 file-server-legacy.test.ts (MacOS/npm/20, Linux/npm/20) — failing since 2026-03-18 (7 days) ⚠️ error changed mid-streak
    npm error Cannot read properties of null (reading 'edgesOut') ...
```

**Build failures** — when tests don't run, the report now shows the Nx
failure summary AND the actual failed task output:
```
⚠️ Tests did not run — failed at step: Run e2e tests with pnpm (Linux/Windows)
  NX Running target e2e-local for project e2e-docker and 153 tasks it depends on failed
  Failed tasks: gradle:build-base
   > nx run gradle:build-base
  > tsc --build tsconfig.lib.json
  error TS6307: File '...assert-supported-platform.ts' is not listed within the file list ...
```

## Changes

- **Error signature comparison** — compares normalized error messages
(not just file names) between the current run and first-failing run.
Dynamic parts (temp paths, random IDs, timestamps, versions) are
stripped before comparison. Different errors on different combos are
detected and reported separately.
- **Signature-based binary search** — when the error changed mid-streak,
finds when the current error ACTUALLY started by checking error
signatures in historical runs across all combos.
- **Failed Nx task block capture** — extracts the ` > nx run <task>`
output block with actual build errors, plus a fallback for generic error
patterns when no Nx task markers are found.
- **Clickable job links** — each failing combo in the golden projects
summary links to its specific GitHub Actions job.
- **Compact other-projects** — replaced the code-block table with a
one-liner count summary.
- **Graceful degradation** — if failure details collection fails, the
brief report still posts with a warning.
2026-03-25 23:41:09 -04:00
Jack Hsu a1dee7286f docs(misc): replace monolithic tutorials with focused topic-based tutorials (#34998)
## Current Behavior

The Tutorials section has three large framework-specific tutorials
(React Monorepo, Angular Monorepo, TypeScript Monorepo) that each teach
everything at once. They are 500+ lines, tightly coupled to a specific
tech stack, and don't build knowledge incrementally.

## Expected Behavior

Seven focused, technology-agnostic tutorials that each teach one concept
in ~5 minutes. The design follows progressive disclosure so users learn
one thing well before moving to the next.

Preview:
https://deploy-preview-34998--nx-docs.netlify.app/docs/getting-started/tutorials/crafting-your-workspace

### Design goals

- **Learn the basics in an hour**: A user going through all 7 tutorials
covers workspace structure, dependencies, tasks, running, caching,
debugging, and plugins.
- **AI agent friendly**: Each page has an `llm_copy_prompt` at the top
that an AI agent can use as a tutor prompt. A user can paste this into
Claude Code, Cursor, or any AI tool and be guided through the topic in
their own workspace.
- **No assumed workspace state**: Each tutorial works whether you used
`create-nx-workspace`, ran `nx init` on an existing repo, or jumped to a
specific topic. Pages link back to prerequisites when needed.
- **One concept per page**: Progressive disclosure means plugins aren't
mentioned until tutorial 7, `nx graph` isn't shown until tutorial 6, and
caching configuration doesn't appear until tutorial 5.

### Tutorial sequence

1. **Crafting your workspace** - Nx as a build intelligence layer,
workspace structure, package manager workspaces, TypeScript
solution-style project references, adding projects
2. **Managing dependencies** - Workspace libraries, buildable vs
non-buildable (with `exports` and `customConditions`), `workspace:*`
protocol, single version policy with catalogs
3. **Configuring tasks** - `package.json` scripts vs `project.json`
targets, `dependsOn` with `^` syntax, continuous tasks, named
configurations, target defaults
4. **Running tasks** - `nx run`, shorthand, `run-many` with
`--targets`/`--projects`, task ordering with SVG diagram, parallelism
control, passing arguments
5. **Caching tasks** - Run-twice demo, computation hashing with SVG
diagram, inputs/outputs, named inputs, env vars/runtime inputs,
sandboxing, remote caching with `nx connect`
6. **Understanding your workspace** - `nx graph` as entry point, project
graph with edge screenshots, task graph with screenshots, `nx show
project`/`nx show target`, affected analysis with `--base`/`--head`,
cache debugging
7. **Reducing configuration boilerplate** - `targetDefaults`, Nx
plugins, inferred tasks, `nx add`, configuration cascade, presented as
optional

### Other changes

- **CI tutorial rewritten** to cover remote caching, affected (with
`NX_BASE`/`NX_HEAD`), Nx Agents, and self-healing (was previously
self-healing only)
- **Concept pages** ("How Nx works") link to relevant tutorials via
"Learn by doing" callouts
- **Redirects** from old tutorial URLs to new pages
- **Cross-references** updated across 11 technology/guide pages
- **SVG diagrams** for task dependency ordering and cache hash flow
(same style as sandboxing page)
- **Screenshots** for project graph edge detail and task graph views
- **Sidebar**: Tutorials group has "New" badge, collapsed by default

## Screenshots

Tutorials topics as ordered in sidebar:

<img width="324" height="290" alt="image"
src="https://github.com/user-attachments/assets/87b27e6a-30f6-4c9b-b52d-b371f66e72e8"
/>

AI instructions that can be copied and pasted into Claude Code to act as
a tutor:

<img width="803" height="308" alt="image"
src="https://github.com/user-attachments/assets/2dcf402e-19a6-4cf4-bcac-7005491529bf"
/>

Linking to prev/next tutorials at the bottom of each tutorial topic:

<img width="871" height="457" alt="image"
src="https://github.com/user-attachments/assets/b9a198b5-edc7-4afd-8c13-fd70c10cf858"
/>

For concept pages that are covered by a tutorial, link to the tutorial
in `Next steps`:

<img width="945" height="435" alt="image"
src="https://github.com/user-attachments/assets/a05d1c0b-c95a-42c5-baec-1fb30dff7c64"
/>

## Related Issue(s)

Fixes DOC-452
2026-03-25 20:29:59 -04:00
Robert Sidzinka c6990488d8 fix(vite): add support for Vite 8 (#34850)
## Current Behavior

The `@nx/vite` plugin only supports Vite 5, 6, and 7. Users on Vite 8
get peer dependency errors:

```
peer vite@"^5.0.0 || ^6.0.0 || ^7.0.0" from @nx/vite
```

## Expected Behavior

Full Vite 8 support for new and existing workspaces:

**Nx plugin support:**
- Update peer deps to include `^8.0.0` in `@nx/vite` and `@nx/vitest`
- Default new workspaces to Vite 8 (`viteVersion = '^8.0.0'`)
- Bump `@vitejs/plugin-react` to `^6.0.0` (required for Vite 8, uses Oxc
instead of Babel)
- Add `useViteV7` backward compatibility flag (follows existing
`useViteV5`/`useViteV6` pattern)

**Rolldown migration (Vite 8 replaced Rollup with Rolldown):**
- Handle both `rollupOptions` (Vite <8) and `rolldownOptions` (Vite >=8)
in build executor and plugin detection
- Fix build executor environments API to preserve env-specific
`rolldownOptions` config
- Update e2e tests for Rolldown's different module counts

**Type fixes for Vite 8's ESM-only declarations:**
- Vite 8 ships `.d.mts` type declarations not resolvable under
`moduleResolution: "node"`
- Fix `typeof import('vite')` and `import type` usages across
`@nx/vite`, `@nx/vitest`, `@nx/cypress`, `@nx/react`, `@nx/angular`,
`@nx/remix` with inline casts
- All fixes have `TODO(jack)` comments to remove when switching to
`moduleResolution: "nodenext"`

**Plugin compatibility:**
- `@vitejs/plugin-react@^6.0.0` only supports Vite 8; `^4.2.0` for Vite
<=7
- `ensure-dependencies` detects installed vite version and picks the
correct plugin-react version
- Cypress CT does not support Vite 8 yet — e2e test downgrades to Vite 7

**Angular vitest fix:**
- `@angular/build` depends on `rolldown` which injects
`@oxc-project/runtime` helpers at transform time without declaring it as
a dependency
- Add `@oxc-project/runtime` as an explicit devDependency in the Angular
vitest generator

**Docs:**
- Updated supported versions table to include `^8.0.0`
- Added `rolldownOptions.input` to buildable project detection docs

**E2E coverage:**
- New Vite 8 + React build/test e2e test
- New Vite 7 + React backward compat e2e test (downgrades vite +
plugin-react)
- Updated environments API test for Vite 8 `rolldownOptions`
- Updated incremental build test for Rolldown's module counts
- Cypress CT e2e downgrades to Vite 7 (Cypress doesn't support Vite 8
yet)

## Other notes

We'll do a follow-up PR to include migrations for 22.7.0. This PR's
scope is only to ensure peer deps and our generators work for workspaces
that are already using Vite 8.

## Related Issue(s)

Fixes #34849

Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
2026-03-25 15:58:44 -04:00
Jason Jean bc982391aa feat(repo): add nx-labs repo target and use glob pattern for update-all-repos (#34999)
## Current Behavior

The `update-all-repos` target explicitly lists each repo target in its
`dependsOn`. Adding a new repo requires updating this list manually.
There is no target for updating the nx-labs repo.

## Expected Behavior

- New `update-nx-labs-repo` target added for updating the nx-labs
repository
- `update-all-repos` uses a `update-*-repo` glob pattern in `dependsOn`,
so new repo targets are automatically picked up
2026-03-25 14:36:58 -04:00
Jack Hsu 96537f84d6 fix(core): add timeouts to GitHub push flow to prevent CLI hangs (#35011)
## Current Behavior

The `pushToGitHub` flow in create-nx-workspace calls `gh api user` and
`gh repo create --push` with no timeout. When `gh` CLI is wrapped by
1Password SSH agent, credential managers with GUI prompts, SSH keys with
passphrases, or corporate SSO, these calls hang indefinitely — freezing
the CLI after workspace creation with no indication of what's happening.

YTD data shows a **96.3% failure rate** (16,426 failures out of 17,058
push attempts).

## Expected Behavior

- The initial `gh` auth check (`gh api user`) has a **2-second
timeout**. If `gh` is slow to respond (hung on credential prompt), the
push flow is skipped gracefully instead of freezing.
- The `gh repo create --push` command has a **15-second timeout**. If
the push hangs, the process is killed and a helpful message is shown.
- If `gh` CLI is not installed, the push flow is skipped immediately
without attempting any network calls.
- Error messages now include actionable fallback: *"You can push
manually with: git push -u origin main"*.

The error is now captured in `recordStat` as well, so when users see
this we'll also see it in our stats.

<img width="1026" height="334" alt="image"
src="https://github.com/user-attachments/assets/10c0b4a8-9083-4063-9558-88ebb8a5a850"
/>


## Related Issue(s)

Fixes #34482, NXC-4141
2026-03-25 14:29:37 -04:00
Louie Weng f003f56f8e fix(core): prevent batch executor error on prematurely completed tasks (#35015)
## Current Behavior

When a task is prematurely completed (e.g., due to a failure that causes
early termination) before its dependents have been scheduled, calling
`scheduleNextTasks` crashes the batch executor. The crash occurs in
`processTaskForBatches`, which traverses reverse dependencies and
attempts to read `notScheduledTaskGraph.dependencies[task.id]` for a
task that was already removed from `notScheduledTaskGraph` via
`complete()`. This yields `undefined` for the dependencies array,
causing downstream code to throw.

## Expected Behavior

When a task has been prematurely completed before batch scheduling runs,
`processTaskForBatches` should skip that task gracefully rather than
crashing. Tasks that were removed from `notScheduledTaskGraph` early
should be detected and skipped during batch traversal.

## Related Issue(s)

Fixes NXC-4144
2026-03-25 13:41:16 -04:00
Craigory Coppola f81ad0715f fix(js): add input on .d.ts files within dependency projects (#34968)
This pull request makes a targeted improvement to how TypeScript
dependency tracking is handled in the build system. Specifically, it
ensures that all `*.d.ts` files from dependent projects are included as
inputs, improving type safety and correctness in incremental builds.

Dependency tracking improvements:

* Updated the `getInputs` function in `plugin.ts` to add all `*.d.ts`
files from dependencies as tracked inputs, ensuring that changes to type
definition files in dependent projects are properly detected and trigger
rebuilds.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
2026-03-25 16:55:58 +01:00
Jack Hsu 543a8b1dd6 feat(core): auto-open browser for Cloud setup URL during create-nx-workspace (#35014)
## Current Behavior

When a user selects "yes" to connect to Nx Cloud during
`create-nx-workspace`, the Cloud setup URL is only displayed in the
terminal banner. The user must manually click or copy the link to
complete onboarding.

## Expected Behavior

After displaying the banner, the setup URL is automatically opened in
the user's default browser, removing friction at the critical Cloud
conversion moment.

- Skips browser open in CI environments (`CI=true`, GitHub Actions,
etc.)
- Fails gracefully if the browser cannot be opened (e.g., headless/no
display server) — the URL remains visible in the terminal
- Only triggers when user actually connected to Cloud (not for "Maybe
later" / `skipCloudConnect`)

## Related Issue(s)

Fixes NXC-4112
2026-03-25 10:46:37 -04:00
Leosvel Pérez Espinosa 0a381299e7 fix(core): resolve published nx migrate package resolution (#35013)
## Current Behavior

When `nx migrate latest` runs, it installs the target version of `nx`
into a temporary directory and executes that published CLI. During
migration, Nx checks whether `nx` is already installed in the workspace
by resolving `nx/package.json`.

This used to resolve through the workspace-oriented lookup paths, so
migrate correctly detected the workspace-installed `nx` package and
expanded the first-party Nx package group.

That changed recently with `chore(core): build nx to local dist and use
nodenext (#34111)`. As part of that work, the published `nx` package
gained an `exports` map. In Node, a package with `name: "nx"` and
`exports` can self-reference by package name. As a result, when code
running inside the temporary published `nx` package resolves
`nx/package.json`, Node now resolves that request back to the temporary
package itself.

The resolved path points outside the workspace root, so migrate's
existing safety check treats `nx` as not installed in the workspace.
Once that happens, migrate only updates `nx` and never expands the rest
of the first-party Nx package group.

Separately, provenance package-group lookup assumes a
source-layout-relative path to `package.json`, which does not hold for
published artifacts built into local `dist/`.

## Expected Behavior

Published temporary migrate CLIs should still resolve the
workspace-installed `nx` package when migrate determines what is
installed in the workspace. That preserves the existing package-group
migration behavior even after the recent `exports`-based packaging
change.

Provenance package-group lookup should resolve the manifest of the
currently running `nx` package in a way that works for published
artifacts as well as source checkouts.
2026-03-25 10:34:06 -04:00
Leosvel Pérez Espinosa 55f8888dda fix(bundling): disable swc input source map resolution (#35010)
## Current Behavior

When using `compiler: 'swc'` with `useLegacyTypescriptPlugin: false` in
rollup config, builds fail with errors like:

```
ERROR failed to read input source map: failed to find input source map file "index.js.map"
```

SWC defaults `inputSourceMap` to `true`, causing it to look for
`.js.map` files for TypeScript source inputs that obviously don't have
them.

## Expected Behavior

Builds should succeed without source map resolution errors. Rollup
handles source maps via its own output pipeline — the SWC transform step
should not independently try to resolve input source maps.

## Related Issue(s)

Fixes #32671
2026-03-25 09:55:51 -04:00
Jason Jean 3fa06c98d7 chore(repo): update nx to 22.7.0-beta.4 (#35000)
Updating Nx from 22.7.0-beta.3 to 22.7.0-beta.4
2026-03-24 18:57:14 -04:00
Caleb Ukle f6692fc7b6 fix(core): handle owners and conformance project refs on move/remove (#34815)
## Current Behavior

When removing or moving a project in an Nx workspace, the `owners` and
`conformance` sections in `nx.json` are not updated. This leaves stale
project references in:
- `conformance.rules[].projects` (both plain strings and `{ matcher }`
objects)
- `owners.patterns[].projects` (top-level and section-level for GitLab)

## Expected Behavior

- **On project removal**: Strip the removed project from all conformance
rules and owners patterns. Remove entries that become empty after
cleanup (rules with no projects, patterns with no projects).
- **On project move/rename**: Rename all references to the old project
name with the new name in conformance rules and owners patterns
(including section-level patterns for GitLab-style CODEOWNERS).
- **Schema**: Add `owners` configuration schema to `nx-schema.json` for
validation and IDE support, including sections (GitLab CODEOWNERS).

## Related Issue(s)

<!-- No specific issue linked -->

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: barbados-clemens <barbados-clemens@users.noreply.github.com>
2026-03-24 22:06:42 +00:00
Steven Nance 6cc873550a fix(devkit): add startTime and endTime to TaskResult interface (#34996)
## Current Behavior

The `TaskResult` interface exported from the devkit is missing
`startTime` and `endTime` properties. At runtime, these properties are
present on `TaskResult` objects — set by the task orchestrator during
execution and consumed by multiple lifecycle implementations (profiling,
timings, history, store-run-information) — but the public type does not
declare them. This means the [TaskResult reference
docs](https://nx.dev/docs/reference/devkit/TaskResult) don't show these
fields.

## Expected Behavior

The `TaskResult` interface includes optional `startTime` and `endTime`
properties (Unix timestamps) matching what is actually present on
runtime objects. The generated devkit reference docs will now include
these fields.

## Related Issue(s)

N/A
2026-03-24 21:58:16 +00:00
Craigory Coppola 068db9a8ca fix(core): show better log message when isolated plugin shuts down after hook completion (#34922)
## Current Behavior
`shut down after last hook`

## Expected Behavior
`shut down after [hook]`

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-03-24 17:31:58 -04:00
Colum Ferry 36534a4237 fix(angular-rspack): ensure rebuild chunks emitted summary accurate (#34979)
## Current Behavior
Currently, rebuilds produce a summary table that is not accurate.
A key difference in how Rspack sets `chunks.rendered` compared to
Webpack resulted in the logic for determining rebuilt chunks to be
incorrect.

## Expected Behavior
Ensure the summary table for emitted chunks is accurate on rebuild

## Related Issues

It does not _fully_ solve #34936, however it should be a good first step
to finding out if too many chunks are being emitted
2026-03-24 16:49:51 -04:00
Caleb Ukle f368b6a7d6 fix(nx-cloud): remove invalid images (#34997)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->
lists invalid images

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
lists valid images
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #34970
2026-03-24 16:31:16 -04:00
Leosvel Pérez Espinosa 9cca97fc02 fix(js): pass configName to typecheck command in TS plugin (#34989)
## Current Behavior

The `@nx/js` TypeScript plugin ignores the `configName` option when
constructing the typecheck target command. It always runs `tsc --build
--emitDeclarationOnly` without specifying which tsconfig to use,
defaulting to whatever `tsc` resolves on its own. This means custom
`configName` values (e.g. `tsconfig.lib.json`) have no effect on
typechecking.

## Expected Behavior

The typecheck target should pass `configName` to the `tsc --build`
command, matching how the build target already works: `tsc --build
<configName> --emitDeclarationOnly`.

## Related Issue(s)

Fixes #34274
2026-03-24 15:43:32 -04:00
Louie Weng a52c42ff05 chore(gradle): bump gradle project graph plugin version to 0.1.17 (#34993)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

Current plugin version is at 0.1.16

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

Bump version to 0.1.17

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 11:49:44 -07:00
Caleb Ukle 34b8256b97 fix(nx-dev): improve docs search ranking and metrics (#34992)
## Current Behavior

- Searching for a framework (e.g. "nestjs", "vite", "angular") returns
random pages that mention the term instead of the plugin introduction
page.
- The launch templates page is too long impacting word saturation
- Search event tracking uses wrong event

## Expected Behavior

- Technology introduction pages rank first or second when searching for
their framework name.
- Launch templates are split into a focused reference page and a
separate examples page, both cross-linked.
- Use correct search event and track user selected item from query
- enforce usage of pagefind filter names in content collection schemas


Fixes DOC-408
2026-03-24 14:05:19 -04:00
Jason Jean ac2031508c fix(core): suppress postinstall error output when nx is not yet built (#34986)
## Current Behavior

The `postinstall` script in `packages/nx/package.json` runs `node
./dist/bin/post-install` which prints noisy error logs to stderr when
the `dist` directory hasn't been built yet (e.g. during `pnpm install`
in the workspace). The script already exits cleanly via `|| exit 0`, but
the error output is confusing.

## Expected Behavior

The postinstall script silently succeeds when the dist directory doesn't
exist, without printing error logs to the console.

## Related Issue(s)

N/A - minor DX improvement
2026-03-24 13:43:54 -04:00
Jason Jean 7da079877e fix(vitest): resolve addPlugin default in init generator (#34990)
## Current Behavior

Running `nx add @nx/vitest` does not register the `@nx/vitest` plugin in
`nx.json`. The init generator had a wrapper function (`initGenerator`)
that hardcoded `addPlugin: false` before spreading the user-provided
schema. Since `nx add` calls the generator via CLI without passing
`addPlugin`, the value stayed `false` — and the `??=` default logic in
`initGeneratorInternal` never fired because `false` is not nullish.

## Expected Behavior

Running `nx add @nx/vitest` should register the `@nx/vitest` plugin in
`nx.json`, matching the behavior of other plugins like `@nx/jest`,
`@nx/vite`, and `@nx/playwright`.

## Related Issue(s)

<!-- No existing issue tracked for this -->

## Changes

- Merged `initGenerator` and `initGeneratorInternal` into a single
`initGenerator` function that uses the `??=` default logic directly
- Added unit tests for the init generator covering plugin registration
defaults, `NX_ADD_PLUGINS` env var, `addPlugin: true` vs `false`
behavior, package.json handling, and namedInputs
2026-03-24 17:42:48 +00:00
Louie Weng 4a073cd1f3 fix(gradle): ignore test enums when atomizing (#34974)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
When the Gradle plugin's regex-based test parser (used as a fallback
when AST parsing fails) encounters a Kotlin file
containing enum classes, those enum classes are incorrectly included as
test atomization targets. This causes spurious
test entries to appear for enum types like TestStatus or Priority that
are not actual test classes.
## Expected Behavior

Enum classes are excluded from the list of discovered test classes
during atomization, consistent with how abstract
classes and annotation classes are already filtered out. Only real test
classes should be identified as atomization targets.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-03-24 10:39:48 -07:00
Jason Jean 1ed644799e chore(repo): add mise trust step and nx-labs to update-repos (#34991)
## Current Behavior

The `update-repos` tool clones repositories into `/tmp` and runs `pnpm
install` / `nx migrate` without trusting the repo's mise configuration.
This means the wrong tool versions (node, bun, etc.) may be used.
Additionally, `nrwl/nx-labs` is not included in the update-repos config.

## Expected Behavior

- Run `mise trust` before installing dependencies if the cloned repo has
a `mise.toml` or `.mise.toml`, ensuring correct tool versions are
activated.
- Include `nrwl/nx-labs` in the repos that get updated.

## Related Issue(s)

N/A - internal tooling improvement
2026-03-24 17:36:10 +00:00
Craigory Coppola c96de0be13 fix(core): runtime inputs shouldn't be cached at task_hasher layer and filesets should be in the hash_plans layer (#34971)
This pull request refactors the caching strategy in the task hashing
logic to improve cache isolation and correctness. The main change is to
eliminate shared, long-lived caches in favor of creating fresh caches
for each invocation, preventing stale data from persisting across CLI
commands. Additionally, new caches for project and workspace file set
hashes are introduced, and cache handling is streamlined for better
maintainability.

**Cache management improvements:**

* Removed the `runtime_cache` field from the `TaskHasher` struct and now
create fresh `DashMap` caches for runtime, project file set, and
workspace file set hashes within each invocation of the main hash
function. This ensures no stale cache data persists across CLI commands.
[[1]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL153)
[[2]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL184)
[[3]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL196-R208)
* Updated the `hash_runtime` function and its usage to accept a
reference to a `DashMap` instead of an `Arc<DashMap>`, simplifying
ownership and concurrency concerns.
[[1]](diffhunk://#diff-77cbfff0572545027ac8ad107859d5135e6f78ee7c32babc56b2c9bbf89cfaccL5-R11)
[[2]](diffhunk://#diff-77cbfff0572545027ac8ad107859d5135e6f78ee7c32babc56b2c9bbf89cfaccL54-R61)

**File set hash caching:**

* Introduced the `CachedFileSetHash` struct to store both the hash value
and the list of matched files for project and workspace file set
hashing, enabling more efficient input collection and cache lookups.
* Added per-invocation caches for project and workspace file set hashes,
with logic to check the cache before computing a new hash and to insert
results after computation.
[[1]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL196-R208)
[[2]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR353-R385)
[[3]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR412-R438)
[[4]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR554-R556)

**Function signature and argument updates:**

* Updated function signatures and argument passing to propagate the new
cache references throughout the hashing logic, including
`HashInstructionArgs` and related functions.
[[1]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR264-R266)
[[2]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR341-R343)
[[3]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR554-R556)

Fixes #30170

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-24 13:18:33 -04:00
Jason Jean d659486402 feat(core): decouple DB version from Nx version and share DB across worktrees (#34942)
## Current Behavior

Nx uses its package version as the DB version. Any Nx version change
**wipes the entire database**, losing all task history and cache
metadata. With multiple worktrees, each worktree maintains its own
separate DB — so switching between them constantly nukes task history,
and worktrees can't benefit from each other's cached results.

## Expected Behavior

1. **Versioned DB filenames**: DB version is decoupled from Nx version.
A new `DB_VERSION` constant is bumped only when the schema changes. The
version is encoded in the filename (`{machine_id}-v{DB_VERSION}.db`), so
multiple schema versions coexist on disk without wiping each other.

2. **Shared DB across worktrees**: When running inside a git worktree,
Nx detects the main repo root and stores the DB in the main repo's
`.nx/workspace-data/` directory. All worktrees share the same task
history, cache metadata, and task details — but **running tasks are
tracked per-worktree** since they are inherently local to each working
copy.

### Changes

- Add `DB_VERSION` constant in `initialize.rs` (bumped only on schema
changes)
- Encode version in DB filename: `{machine_id}-v1.db`
- Remove `nx_version` parameter from `connect_to_nx_db` and
`initialize_db`
- Remove metadata table entirely — schema version is in the filename, so
no runtime version check needed
- Simplify `initialize_db` to use file-existence check instead of
querying metadata
- Add `get_main_worktree_root` napi function in a dedicated `worktree`
module for worktree detection via `git rev-parse --git-common-dir`
- Add `sharedWorkspaceDataDirectory` in TypeScript that resolves to the
main repo's workspace-data dir when in a worktree
- Add `getLocalDbConnection` for per-worktree data (e.g. running tasks)
that should not be shared
- Running tasks use a local DB to avoid false "already running"
conflicts between worktrees
- Daemon stays per-worktree (no changes needed)
- Stale DB files from old schema versions are cleaned up automatically
after 7 days

### What's shared vs local

| Data | Scope | Why |
|------|-------|-----|
| `task_details` | Shared | Keyed by content hash — same code = same
hash regardless of worktree |
| `task_history` | Shared | More data = better time estimates and flaky
detection |
| `cache_outputs` | Shared | Cache lives in one place, tracking should
too |
| `running_tasks` | **Per-worktree** | Each worktree runs tasks
independently — sharing would cause false conflicts |

### Migration

- Old `{machine_id}.db` files are simply ignored (Nx now looks for
`{machine_id}-v1.db`)
- No data migration — fresh DB on first use (same as today on version
bumps)
- Old files cleaned up on `nx reset` and automatically after 7 days of
inactivity

## Related Issue(s)

<!-- N/A - internal improvement -->
2026-03-24 11:51:33 -04:00
Leosvel Pérez Espinosa 95621cd329 fix(core): use upsert to prevent FK constraint violations in task DB (#34977)
## Current Behavior

`INSERT OR REPLACE` is used in `task_details` and `cache_outputs`
tables. The bundled SQLite (`libsqlite3-sys`) is compiled with
`SQLITE_DEFAULT_FOREIGN_KEYS=1`, so FK constraints are enforced by
default. `INSERT OR REPLACE` does a DELETE + INSERT on PK conflict, and
the implicit DELETE on `task_details` fails when child rows exist in
`task_history` or `cache_outputs`:

```
NX   DB transaction error: SqliteFailure(Error { code: ConstraintViolation, extended_code: 787 }, Some("FOREIGN KEY constraint failed"))
```

This happens because `record_task_details` is called multiple times with
the same hash across different code paths (`hashTask`, `hashTasks`,
`hashBatchTasks`), and by the second call, child rows already reference
that hash.

## Expected Behavior

Use `INSERT ... ON CONFLICT DO UPDATE` (upsert) which updates the
existing row in-place without deleting it. No DELETE means no FK
violation, while preserving the same idempotent behavior the code relies
on.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-24 09:42:26 -04:00
Jason Jean cbd3951a12 chore(repo): update nx to 22.7.0-beta.3 (#34958)
Updating Nx from 22.7.0-beta.1 to 22.7.0-beta.3
2026-03-24 13:41:46 +00:00
Jason Jean 96f9ccc824 fix(repo): use @nx/nx-source export condition in jest resolver (#34972)
## Current Behavior

The patched jest resolver handles `nx/*` imports differently for unit
tests and e2e tests:
- **Unit tests**: Manual filesystem-based resolution with regex matching
and `fs.existsSync` checks to find `.ts` source files
- **E2e tests**: Falls through to `options.defaultResolver`, which
follows pnpm `workspace:*` symlinks to `packages/nx/` and resolves via
the package.json exports map — landing on `dist/*.js` files instead of
`.ts` source

This causes ~200 sandbox warnings for undeclared file reads from
`packages/nx/dist/` during e2e task execution, since those files aren't
listed as task inputs.

Additionally, `e2eInputs` in `nx.json` references
`{workspaceRoot}/jest.preset.js` (the root unit test preset) instead of
the actual e2e preset at `{workspaceRoot}/e2e/jest.preset.e2e.js`.

## Expected Behavior

Both unit and e2e tests resolve `nx/*` imports using the `@nx/nx-source`
custom export condition via `options.defaultResolver`. This leverages
the existing exports map in `packages/nx/package.json` to resolve to
`.ts` source files, eliminating:
- The manual filesystem resolution code for `nx/*` (regex matching,
`fs.existsSync` calls)
- The e2e-specific code path that fell through to compiled `.js` files
- Sandbox warnings for undeclared reads from `packages/nx/dist/`

The `e2eInputs` preset reference is corrected to point to the actual e2e
preset file.

## Related Issue(s)

<!-- No specific issue — this was discovered during sandbox warning
investigation -->

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-24 08:54:00 -04:00
Jack Baker 5b2a0a745b cleanup(angular): remove postcss-url dependency (#34976)
Remove the postcss-url from the angular generators as it does not seem
to be used by ng-packagr or tailwind anymore.
https://github.com/ng-packagr/ng-packagr/pull/2736

https://v3.tailwindcss.com/docs/using-with-preprocessors#using-post-css-as-your-preprocessor

## Current Behavior
When generating an angular library postcss-url is automatically added as
dependency.

## Expected Behavior
postcss-url should not be added to be dependencies as it is not used.
2026-03-24 11:49:04 +01:00
Jason Jean d047b81c42 fix(core): add explicit exports entry for nx/src/native directory (#34967)
## Current Behavior

After the nodenext PR (#34111) added an `exports` map to
`packages/nx/package.json`, the nx-cloud light client (ocean) fails to
import `nx/src/native`.

The wildcard exports pattern `"./src/*"` resolves `nx/src/native` to
`./dist/src/native.js` — but the actual file is
`./dist/src/native/index.js` (it's a directory with an index file).
Node's exports map does literal `*` substitution and does **not**
perform CJS-style directory/index resolution.

The cloud client wraps all its nx imports in a single try/catch, so when
the native import fails, `getDbConnection` is never assigned either,
causing `getDbConnection is not a function` errors in CI.

This was not an issue with `nx@22.7.0-beta.1` because that version had
no `exports` map — Node fell back to normal CJS resolution which handles
directory/index lookups.

## Expected Behavior

`require('nx/src/native')` correctly resolves to
`dist/src/native/index.js` via an explicit export entry, and the cloud
client can load the native module and `getDbConnection` without errors.

## Related Issue(s)

N/A — discovered during version bump CI failure investigation.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-23 19:03:28 -04:00
Jason Jean dd8f69ea72 fix(repo): skip flaky Cypress HMR e2e tests (#34969)
## Current Behavior

Six e2e tests are failing on master due to a Cypress uncaught exception:
`[HMR] Hot Module Replacement is disabled`. The error originates from
the webpack `styles.js` bundle during the `before each` hook, causing
Cypress to abort the test suite. This appears to be triggered by an
external dependency update.

Failing tests:
- `e2e-nx:e2e-ci--src/workspace-legacy.test.ts`
-
`e2e-react:e2e-ci--src/module-federation/independent-deployability.webpack.test.ts`
-
`e2e-react:e2e-ci--src/module-federation/core-webpack-basic-host-remote-generation.test.ts`
-
`e2e-react:e2e-ci--src/module-federation/misc-rspack-interoperability.test.ts`
-
`e2e-react:e2e-ci--src/module-federation/dynamic-federation.webpack.test.ts`
-
`e2e-react:e2e-ci--src/module-federation/federate-module.webpack.test.ts`

## Expected Behavior

The flaky tests are skipped so that master CI is green while the root
cause (likely an external dependency update) is investigated separately.

## Related Issue(s)

N/A — CI stabilization fix.
2026-03-23 22:32:25 +00:00
Jason Jean 89cdbb17c2 chore(repo): add update-repos target and restore update-all-repos (#34957)
## Current Behavior

`update-all-repos` only updates ocean and nx-console repositories. There
is no way to update just those two repos separately from the full set.

## Expected Behavior

- `update-all-repos` now includes all four repos: nx, ocean,
nx-examples, and nx-console
- A new `update-repos` target updates only ocean and nx-console (the
most commonly used subset)

## Related Issue(s)

Internal tooling improvement — no external issue.
2026-03-23 15:32:40 -04:00
Jason Jean d9ec2c00fd fix(core): skip workspace context setup when global bin hands off to local (#34953)
## Current Behavior

When the globally installed Nx binary (`bin/nx.ts`) runs, it calls
`setupWorkspaceContext()` **before** determining whether it should hand
off to a local Nx installation. If the global Nx version differs from
the local version (e.g. global `22.7.0-beta.2` vs local
`22.7.0-beta.1`), `isNxVersionMismatch()` returns true,
`daemonClient.enabled()` returns false, and `setupWorkspaceContext()`
creates an in-process `WorkspaceContext` that locks the workspace data
directory.

When the local Nx then tries to start the daemon, it conflicts with this
lock and the daemon fails to start. This means running `nx reset`
followed by any command (e.g. `nx show projects`) results in the daemon
never auto-starting, falling back to in-process graph construction, or
erroring out entirely.

## Expected Behavior

The global bin should not set up a workspace context when it's about to
hand off to a local Nx installation. The local Nx will handle workspace
context setup itself.

`setupWorkspaceContext()` is now only called in the `isLocalInstall` and
`isNxCloudCommand` branches — skipped in the handoff path. This matches
the existing pattern established in #34914 for analytics and DB
connections.

## Related Issue(s)

<!-- No specific issue filed — discovered during development in the nx
repo -->
2026-03-23 15:31:50 -04:00
Jason Jean 7e64e4a3cb fix(core): include command name on all telemetry events (#34949)
## Current Behavior

The command name (e.g., `build`, `test`, `generate`) is only sent as the
`dt` (document title) parameter on `page_view` events. All other
telemetry events like `run_completed` and `project_graph_computed` have
no command context, making it impossible to correlate them with the
command that triggered them in Google Analytics.

## Expected Behavior

All telemetry events include the `dt` parameter with the command name.
The Rust telemetry background thread stores the page title when a
`page_view` is received and automatically injects it into all subsequent
events in the same process.

## Related Issue(s)

N/A — internal analytics improvement

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-03-23 15:31:17 -04:00
Jason Jean fbc4e63a35 fix(core): skip import-equals namespace aliases in native scanner (#34947)
## Current Behavior

The native Rust import scanner mishandles TypeScript's `import X = Y.Z`
namespace alias syntax. When it encounters this pattern, it fails to
recognize it as a non-module statement and continues scanning forward
for string literals, treating the next one it finds as a module
specifier.

On case-insensitive filesystems (macOS default HFS+/APFS), this causes
phantom npm dependencies in the project graph. For example, `import
MyBar = Foo.Bar` followed by `case 'Open':` causes `npm:open` to appear
as a static dependency because `node_modules/Open` resolves to
`node_modules/open`.

This makes `@nx/dependency-checks` impossible to satisfy across
platforms — macOS and Linux disagree on whether the phantom package is
used.

## Expected Behavior

`import X = Y.Z` is a TypeScript namespace alias, not a module import.
The scanner should skip it entirely. Only actual module imports (`import
X = require('module')`, `import ... from 'module'`, etc.) should produce
dependency entries.

After this fix:
- `import X = Y.Z` is correctly identified as a namespace alias and
skipped
- `import X = require('module')` continues to work as a valid CommonJS
import
- String literals in switch/case statements and type aliases are no
longer misidentified as imports

## Related Issue(s)

Fixes #34644
2026-03-23 15:30:54 -04:00
Leosvel Pérez Espinosa d34beae458 chore(repo): fetch and post nightly failure details (#34928)
Example Slack message that would be sent (from a [trimmed-down test
run](https://github.com/nrwl/nx/actions/runs/23434241122)):

>🌟 *Golden Projects*
> Passing: 1
> Failing: 1
>
>🚨 *Failed Golden Projects*
>```
>| Failed project                 |
>|--------------------------------|
>| e2e-web                        |
>```
>
>🔧 *Other Projects*
> Passing: 0
> Failing: 0
>
>
>🔍 *Failure Details*
>
>———————————————————————————
>*e2e-web* — 2 combos (npm+yarn)
>Project failing since 2026-03-13 | Last fully passing: 2026-03-12
>
>📋 `src/file-server-legacy.test.ts` — failing since 2026-03-23 (1 day) 🆕
NEW
>```
> FAIL   e2e-web  src/file-server-legacy.test.ts (142.665 s)
>  ● file-server › should setup and serve static files from app
>
> Command failed: npx nx generate @nx/react:app react-app2511858
--no-interactive --e2eTestRunner=none --verbose
>    npm error Cannot read properties of null (reading edgesOut)
> npm error A complete log of this run can be found in:
/home/runner/.npm/_logs/2026-03-23T11_38_59_333Z-debug-0.log
>
>      426 |     logInfo(`Run Command: ${command}`);
>      427 |     const startTime = performance.now();
>    > 428 |     const logs = execSync(commandToRun, {
>          |                          ^
>      429 |       cwd: opts.cwd || tmpProjPath(),
>      430 |       env: {
>      431 |         CI: true,
>
>      at runCLI (../utils/command-utils.ts:428:26)
>      at Object.<anonymous> (src/file-server-legacy.test.ts:33:11)
>
>[plugin-worker] "nx/core/package-json" (pid: 36960) connected
>[plugin-worker] "nx/js/dependencies-and-lockfile" (pid: 36952) loaded
successfully
>```
>Failing combos: Linux/yarn/20, Linux/npm/20
2026-03-23 14:21:19 +01:00
Jack Baker 0e1f64dce7 fix(angular): update duplicate migration keys (#34961)
Fix duplicate migrations keys as only the last migration was getting
picked up.

## Current Behavior
When running nx migrate only the module federation migration is picked
up and not updating the core angular packages.

## Expected Behavior
Both migrations should be added to migrations.json.
2026-03-23 12:29:41 +00:00
AI-JamesHenry 9e5ba41546 fix(release): fall back to gh user search for author usernames (#34904) 2026-03-23 16:04:52 +04:00
Charlie Croom 79bbe1bdbc fix(core): use scroll-offset-based scrollbar positioning in TUI (#34689)
## Current Behavior

The TUI sidebar scrollbar thumb position is driven by the selected
task's index among tasks (`selected_task_index`). This means the
scrollbar can appear offset from the top even when `scroll_offset=0`
(i.e., the viewport is showing the very beginning of the list), because
the selected task might not be the first one. This makes it look like
there is content above that cannot be scrolled to.

<img width="1165" height="833" alt="Screenshot 2026-03-03 at 8 53 00 PM"
src="https://github.com/user-attachments/assets/43c695ad-c4a8-40c9-85b0-7677a8b83d12"
/>

Here, it looks scrolled down...but actually all the content is in view.

## Expected Behavior

The scrollbar should accurately represent which portion of the entry
list is currently in view. When at the top of the list
(`scroll_offset=0`), the thumb should be at the top. When scrolled to
the bottom, the thumb should be at the bottom.

## Related Issue(s)

N/A (discovered via visual inspection)

## Details

Switch the scrollbar from selection-based metrics to scroll-offset-based
metrics:

- `content_length` = total entries (including spacer rows)
- `viewport_content_length` = viewport height
- `position` = scroll offset

This ensures the scrollbar reflects the actual viewport position rather
than the selected task's position within the task list.

Also adds `total_entries` and `viewport_height` fields to
`ScrollMetrics` to make these values available without additional lock
acquisitions.

Co-authored-by: Amp <amp@ampcode.com>
2026-03-20 22:25:21 -04:00
Leosvel Pérez Espinosa e0b7f1236b fix(core): respect --parallel limit for discrete task concurrency (#34721)
## Current Behavior

`--parallel=N` doesn't cap discrete task concurrency when continuous
tasks exist. `getThreadCount` inflates the thread count to `N +
continuousCount`, and all threads are fungible — any thread picks up any
task. With `--parallel=1` and 2 continuous tasks, 3 threads run,
allowing up to 3 discrete tasks concurrently.

## Expected Behavior

`--parallel=N` caps discrete task concurrency to N. Continuous tasks get
dedicated threads that don't inflate the discrete limit.

### Changes

- **Two-pool thread model**: Split the unified thread pool into discrete
and continuous pools. Each pool runs its own loop and only picks up
matching tasks.
- **`getThreadPoolSize`** (renamed from `getThreadCount`): Returns `{
discrete, continuous, total }`. Discrete pool = `options.parallel`,
continuous pool = number of continuous tasks.
- **`executeDiscreteTaskLoop`**: Handles batches + discrete tasks only.
Uses slot-based groupIds via `closeGroup`/`openGroup`.
- **`executeContinuousTaskLoop`**: Handles continuous tasks only. Uses
counter-based groupIds (`parallel + n++`). Exits once all continuous
tasks have been started.
- **`nextTask(filter?)`** on `TasksSchedule`: Optional filter param to
dequeue only matching tasks. Scheduler stays unaware of pools/limits.

## Related Issue(s)

Fixes #34117
Fixes #31494
2026-03-20 22:23:38 -04:00
Leosvel Pérez Espinosa 5e5184ed69 fix(core): prevent TUI crash when task output arrives after completion (#34785)
## Current Behavior

`nx run-many` with TUI enabled crashes with `TypeError: Cannot read
properties of undefined (reading 'push')` in `tui-summary-life-cycle.ts`
when `appendTaskOutput` is called for a task whose output entry was
already cleaned up by `endTasks`.

## Expected Behavior

Late-arriving task output after `endTasks` has finalized the task is
silently discarded instead of crashing. The output is redundant since it
was already captured when the task completed.

## Related Issue(s)

Fixes #34677
2026-03-20 22:19:23 -04:00
Craigory Coppola 1fc384147b fix(core): split-target should handle projects with colons in name better (#34725)
## Current Behavior
- `splitTarget` causes issues when a project name has more than one
colon
- Project name substitution is triggered when a project depends on the
orginal name of a renamed project, even if the renamed project was
renamed before the dep was registered

That second one is hard to understand. Imagine the real scenario below:

- @nx/gradle was reading settings.gradle.kts and naming the root project
`nx`
- The package.json inference plugin renames it to `@nx/nx-source`
- The package.json inference plugin infers the `nx` project in
`packages/nx`
- The package.json inference plugin reads a config that has `dependsOn:
[nx:build]`
- The substitutors run, and update the dependsOn to
`@nx/nx-source:build`

## Expected Behavior
splitTarget follows the below precedence:

- If splitting a target string thats embedded in a specific project
configuration, targets on that project are preferred
- Targets belonging to the project with the longest name that is valid
from joining segments left to right
- Targets that have the longest name from joining segments are preferred
- Configuration is the remaining segments after picking off the longest
valid project containing a valid target.

Substitutors prefer registering by root if a project with a given name
exists, and only register by name if no project with that root exists
(e.g. if a plugin adds a dependsOn to a project that was inferred by a
later plugin, this would be common if a custom plugin is reading
project.json to get a name or smth).

## AI Summary

> # Branch Analysis: `fix/split-target-fixes` vs `master`
> 
> ## Summary
> 
> | Metric | Lines |
> |--------|------:|
> | **Total raw diff (added+removed, non-test)** | 2,840 + 1,221 = 4,061
|
> | **Lines that were just moved (file split)** | ~960 |
> | **Truly new/changed lines (non-test)** | ~570 |
> | **Test file changes (raw diff)** | 3,109 added / 2,242 removed |
> 
> ## Commits
> 
> | SHA | Message |
> |-----|---------|
> | `57ec87b3c4` | fix(core): split-target should handle projects with
colons in name better |
> | `af1ebc90ab` | fix(core): avoid renaming projects based on former
name if they were rooted when dep is drawn |
> | `b2fffc60c7` | cleanup(core): split project-configuration-utils into
focused modules |
> | `3bae3048d8` | fix(core): fixup name substitution manager |
> 
> ## File Split: `project-configuration-utils.ts` -> 4 modules
> 
> The original `project-configuration-utils.ts` (1,407 lines) was split
into focused modules. **~960 lines were moved as-is** (identical minus
whitespace/formatting) into the new files. The remaining changes are
actual logic modifications.
> 
> | File | Total Lines | Moved from original | Truly new/changed |
> |------|------------:|--------------------:|------------------:|
> | `project-configuration-utils.ts` (remaining) | 444 | ~395 | ~49 |
> | `target-merging.ts` | 494 | ~430 | } |
> | `target-normalization.ts` | 282 | ~250 | } ~111 combined |
> | `project-nodes-manager.ts` | 365 | ~285 | } |
> | **Subtotal** | 1,585 | ~1,360 | ~160 |
> 
> Additionally, ~39 lines were removed from the original and not moved
anywhere (dead code removal or refactored away).
> 
> ## Actual Code Changes (non-test, excluding moved lines)
> 
> ### Major changes
> 
> | File | New | Removed | Net | Description |
> |------|----:|--------:|----:|-------------|
> | `split-target.ts` | ~211 | ~38 | +173 | New logic for handling
projects with colons in names |
> | `name-substitution-manager.ts` | ~152 | ~85 | +67 | Fix: avoid
renaming rooted projects based on former name |
> | Split files (combined, new logic only) | ~111 | — | +111 | New code
introduced during the split refactor |
> | `project-configuration-utils.ts` (new logic only) | ~49 | ~39 | +10
| Residual new code after split |
> 
> ### Minor edits (import path updates, small fixes)
> 
> | File | Added | Removed |
> |------|------:|--------:|
> | `parse-target-string.ts` | 7 | 1 |
> | `command-line/show/target.ts` | 11 | 7 |
> | `devkit-internals.ts` | 3 | 5 |
> | `tasks-runner/utils.ts` | 5 | 3 |
> | `build-project-graph.ts` | 2 | 4 |
> | `error-types.ts` | 2 | 4 |
> | `command-line/run/run-one.ts` | 2 | 1 |
> | `ngcli-adapter.ts` | 1 | 1 |
> | `convert-nx-executor.ts` | 1 | 1 |
> | `project-configuration.ts` (generators) | 1 | 1 |
> | `package-json.ts` | 1 | 1 |
> | `settings.gradle.kts` | 1 | 1 |
> | **Minor edits subtotal** | **37** | **30** |
> 
> ### Other new files
> 
> | File | Lines | Description |
> |------|------:|-------------|
> | `packages/devkit/CLAUDE.md` | 62 | Dev documentation |
> | `__fixtures__/merge-create-nodes-args.json` | 100 | Test fixture
data |
> 
> ## Final Tally: True Non-Test Changes
> 
> | Category | Lines |
> |----------|------:|
> | New logic in `split-target.ts` | ~211 |
> | New logic in `name-substitution-manager.ts` | ~152 |
> | New logic in split module files | ~111 |
> | New logic in remaining `project-configuration-utils.ts` | ~49 |
> | Minor import/path updates across 12 files | ~37 added / ~30 removed
|
> | New non-code files (CLAUDE.md, fixture JSON) | 162 |
> | **Total truly new/changed lines** | **~570 added, ~160 removed** |
> | Moved lines (file split, not real changes) | **~960** |


## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-21 00:37:00 +00:00
Colum Ferry 732a08c77d chore(core): build nx to local dist and use nodenext (#34111)
## Current Behavior

The `nx` package compiles its TypeScript output to
`../../dist/packages/nx/` (relative to the package root), which places
build artifacts outside the package directory at the repo root level
(`dist/packages/nx/`). This makes the package structure harder to reason
about, complicates the build pipeline, and doesn't align with how most
packages organize their output.

The package uses `"module": "commonjs"` with basic module resolution,
which limits future migration paths toward ESM.

## Expected Behavior

The `nx` package now builds to a local `dist/` directory within the
package itself (`packages/nx/dist/`). This is a cleaner, more standard
layout — like having your tools in your own toolbox instead of scattered
across the workshop.

### Key changes:

**Build configuration (`packages/nx/tsconfig.lib.json`):**
- `outDir` changed from `../../dist/packages/nx` to `dist` (local to
package)
- `module` changed to `nodenext` with `moduleResolution: nodenext`
- Updated `include` patterns to explicitly list source directories

**Package entry points (`packages/nx/package.json`):**
- `bin` paths updated: `./bin/nx.js` → `./dist/bin/nx.js`
- Added `"type": "commonjs"` explicitly
- Added comprehensive `exports` map with `@nx/nx-source` condition for
dev/test resolution back to TS source
- Added `postinstall` path update to `./dist/bin/post-install`

**Module resolution fixes:**
- Created `src/utils/handle-import.ts` — a CJS-first import utility that
falls back to ESM `import()` for ESM-only packages, providing a single
migration point for future ESM work
- Converted dynamic `await import()` calls to
`require(require.resolve())` pattern where needed to satisfy `nodenext`
extension requirements
- Plugin worker spawn path now uses correct `.ts`/`.js` extension based
on runtime context (source vs compiled)

**Test infrastructure:**
- Added custom `jest-resolver.js` for the `nx` package that resolves
`nx/...` imports using the `@nx/nx-source` exports condition, so tests
run against TS source
- Updated `jest.preset.js` with SWC transformer configuration
- Added chalk mock for test compatibility

**CI and tooling:**
- Conformance check updated to build `workspace-plugin` first (the Nx
Cloud runner lacks `@swc-node/register` for TS resolution)
- Conformance rule paths in `nx.json` now point to compiled
`dist/workspace-plugin/src/...` output
- Added `dist` to eslint ignore patterns to prevent linting compiled
output
- Added workspace-plugin build target and updated its dependencies

**Other fixes:**
- Various import path fixes across `create-nx-workspace`, gradle, and
other packages to work with `nodenext` resolution
- Updated e2e test paths to reference the new dist location
- Fixed `.gitignore` and `.npmignore` for the new output structure

## Related Issue(s)

Internal infrastructure improvement — no external issue.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Coly010 <Coly010@users.noreply.github.com>
Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
2026-03-20 19:30:03 -04:00
Leosvel Pérez Espinosa d5f51d6d33 fix(linter): prepend framework configs before baseConfig in flat config generation (#34898)
## Current Behavior

Framework generators append predefined configs (`flat/react`,
`flat/angular`, etc.) after `baseConfig` in the flat config array. In
flat config, later entries override earlier ones, so framework rules
override user root rules.

Additionally, the parser/plugins config entries in `flat/typescript`,
`flat/javascript`, and `flat/angular` have no `files` restriction,
applying the TypeScript parser to all files globally — including
`.html`, `.json`, and other non-TS/JS files. This was partially fixed in
PR #28381 (which scoped rules/extends) but the parser entries were
missed.

## Expected Behavior

Framework configs are inserted before `baseConfig`, giving user root
config higher priority. Root standalone projects (no `baseConfig`) are
unaffected.

Parser/plugins entries are scoped to their respective file types, so
`.html` files get the correct parser (Angular template parser, or ESLint
default) instead of the TypeScript parser.

## Changes

- Implement `checkBaseConfig` option in `addBlockToFlatConfigExport` to
insert before `...baseConfig` when present
- Framework generators (`@nx/react`, `@nx/angular`, `@nx/next`, etc.)
pass `checkBaseConfig: true`
- Scope parser entries in `flat/typescript` to `**/*.ts, **/*.tsx,
**/*.cts, **/*.mts`
- Scope parser entries in `flat/javascript` to `**/*.js, **/*.jsx,
**/*.cjs, **/*.mjs`
- Scope processor/plugins entry in `flat/angular` to `**/*.ts`
- Refactor `addPredefinedConfigToFlatLintConfig` to use an options
object for optional params
- Fix regex patterns in react-native and expo jest config templates to
use `[.]` instead of `\.` — avoids `no-useless-escape` lint errors and
fixes a latent bug where `\.` in a string literal matched any character
instead of a literal dot

## Related Issue(s)

Fixes #32923
2026-03-20 17:53:24 -04:00
Jason Jean 2c4fb2abc0 fix(module-federation): enable ESM output for Angular rspack MF plugin (#34839)
## Current Behavior

When using Angular + Rspack + Module Federation, the
`NxModuleFederationPlugin` (Angular variant) sets `library: { type:
'module' }` on the Module Federation plugin config, which causes
`remoteEntry.js` to emit ESM `export` statements. However, the
compiler's `experiments.outputModule` and `output.module` flags are not
set, so the MF runtime tries to load the remote entry as a classic
script. This results in:

```
Uncaught SyntaxError: Unexpected token 'export' (at remoteEntry.js:48774:1)
```

This is especially broken during `nx serve` (dev server), because
`@nx/angular-rspack`'s `createConfig` only sets
`experiments.outputModule = true` for production builds, not dev server
builds.

## Expected Behavior

The Angular rspack `NxModuleFederationPlugin` should ensure
`experiments.outputModule = true` and `output.module = true` are set on
the compiler when using `library: { type: 'module' }`, so that Module
Federation works out of the box in both dev and production modes.

This aligns with how the Angular **webpack** MF config already handles
it (in `with-module-federation/angular/with-module-federation.ts`).

## Related Issue(s)

Fixes #34584
Fixes #33992
2026-03-20 14:37:50 -04:00
Jack Hsu 30d64c4ba6 fix(nx-dev): build nx-dev in-place to fix ai package resolution (#34730)
## Current Behavior

The default `@nx/next:build` for nx-dev outputs to `dist/nx-dev/nx-dev`.
At runtime, Node.js can't resolve the `ai` package from that path
because `node_modules` is at the workspace root — causing
`ERR_MODULE_NOT_FOUND` on `/ai-chat`.

Additionally, `ui-video-courses` is missing `@nx/nx-dev-ui-icons` as a
dependency (introduced by #34669), causing a webpack compilation
failure.

## Expected Behavior

Build in-place to `nx-dev/nx-dev` (matching the existing Netlify config)
so `.next` stays close to `node_modules` and package resolution works.
This also simplifies config by removing the Netlify vs Vercel branching
in sitemap scripts and project.json configurations.


## Other Notes

This PR also:
- Redirects `/` to `/blog` since almost everything else, including
homepage, are in Framer.
- Uses `@nx/next/plugin` to infer targets now so we no longer use any
`@nx/next:*` executors.
- Cleans up `nx-dev` project config so it no longer has `serve-docs` and
other weird setup. It is purely just `nx dev nx-dev` or `nx build
nx-dev` or `nx start nx-dev` like a normal Next.js app.
- Removes extra `NETLIFY` env var checks to point outputs in different
places.

## Related Issue(s)

Closes DOC-418

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
2026-03-20 14:33:10 -04:00
Jack Hsu 68fbfae286 docs(misc): replace cloud onboarding with CNW in tutorials (#34935)
## Current Behavior

Tutorials direct users to cloud.nx.app CTAs requiring GitHub account +
full cloud onboarding before the tutorial starts. CNW starts dropped to
~1,800/day weekday (target ~2,700). Self-healing CI content buried at
bottom of tutorials where only 15-20% of users scroll.

## Expected Behavior

- Tutorials use npx create-nx-workspace as primary path, cloud link kept
as secondary text link
- llm_only tags added to tell AI agents to use CLI only (they can't
handle the browser OAuth flow)
- Tutorial file trees, app names, and scopes updated to match what CNW
actually generates
- Self-healing CI extracted to standalone "Setting up CI" tutorial
- Nx Cloud page moved from Getting Started to Orchestration & CI
overview (redirect added)
- Sidebar labels converted to sentence case per style guide
- AI integrations moved before Editor setup in sidebar (higher traffic)
- Tutorials expanded by default in sidebar
- Intro page now shows CNW and nx init commands directly

## Notes

A follow-up to break tutorials down into smaller, more focused topics
will be the next step.

<img width="547" height="400" alt="image"
src="https://github.com/user-attachments/assets/b717ee54-0dc6-4c08-b1d3-0d80c9dce1df"
/>


## Related Issue(s)

Closes DOC-448
2026-03-20 13:12:43 -04:00
Jason Jean 7d25d08a6b fix(devkit): prevent double install in generators for TS solution workspaces (#34891)
## Current Behavior

In TS solution workspaces, library generators pass `alwaysRun=true` to
`installPackagesTask` to ensure symlinks are created for new packages.
However, when the init generator already triggered an install (via
`addDependenciesToPackageJson`), the `alwaysRun` flag bypasses the cache
and forces a redundant second install.

## Expected Behavior

Only a single install should run per generator invocation. If install
already ran this cycle, subsequent `ensureInstall` calls should be
skipped since the previous install already picked up all filesystem
changes (including `pnpm-workspace.yaml` updates for symlinks).

## Fix

Renamed `alwaysRun` to `ensureInstall` in `installPackagesTask` and
simplified the install condition:

```typescript
if (packageJsonDiffers || (ensureInstall && !installAlreadyRan))
```

This handles both cases:
- **First library** (deps change): init callback runs install →
`ensureInstall` callback sees install already ran → skips. One install.
- **Second+ library** (no dep changes): init callbacks are no-ops →
`ensureInstall` callback sees no prior install → runs. One install.

## Related Issue(s)

<!-- No existing issue — discovered during investigation -->
2026-03-20 17:02:20 +00:00
Louie Weng 52e737deb2 chore(gradle): bump gradle project graph plugin version to 0.1.16 (#34939)
## Current Behavior

The `dev.nx.gradle.project-graph` plugin is at version `0.1.15`.

## Expected Behavior

The `dev.nx.gradle.project-graph` plugin is bumped to version `0.1.16`,
with a corresponding Nx migration so users are automatically updated
when they migrate to `22.7.0-beta.2`.

## Related Issue(s)

N/A - routine version bump

---

### Changes

- Updated `gradleProjectGraphVersion` in
`packages/gradle/src/utils/versions.ts`
- Updated `version` in `packages/gradle/project-graph/build.gradle.kts`
- Created migration TS and MD files in
`packages/gradle/src/migrations/22-7-0/`
- Added migration entry in `packages/gradle/migrations.json`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 16:50:07 +00:00
Jason Jean ef3a517dcd chore(repo): update nx to 22.7.0-beta.1 (#34932)
## Current Behavior

Workspace uses nx 22.7.0-beta.0 and related @nx/* packages at
22.7.0-beta.0.

## Expected Behavior

Workspace uses nx 22.7.0-beta.1 and related @nx/* packages at
22.7.0-beta.1.

## Related Issue(s)

N/A — routine version bump.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-03-20 11:36:09 -04:00
Juri 56acdf7ed2 docs(nx-cloud): add guide for using bun with Nx Cloud CI 2026-03-20 15:29:15 +01:00
Jason Jean c1a93cb061 fix(core): set windowsHide: true on all child process spawns (#34894)
## Current Behavior

On Windows, the Nx daemon runs as a detached background process with no
console. When child processes are spawned without `windowsHide: true`
(Node.js) or `CREATE_NO_WINDOW` (Rust/Win32), Windows allocates a new
visible console window for each subprocess. This causes command prompt
windows to flash on screen during:

- Project graph creation (daemon spawn, plugin workers)
- Task hashing (runtime hashers)
- NX Console extension detection (`code.cmd --list-extensions`, etc.)
- AI agent configuration checks (`git ls-remote`, npm install)
- Machine ID retrieval, package manager version detection, git
operations, and more

The issue is especially noticeable "after a little bit" following daemon
startup, because background operations like the NX Console status check
and AI agents configuration check kick off after the initial project
graph is computed.

## Expected Behavior

No console windows should flash on Windows. All child process spawns use
`windowsHide: true` (Node.js) or `CREATE_NO_WINDOW` (Rust) to suppress
console windows.

## Root Causes Found

Investigation using a child_process interceptor in the daemon revealed
multiple sources:

1. **Rust native `ide/install.rs`** — `Command::new("code.cmd")` calls
for NX Console extension detection/installation were missing
`CREATE_NO_WINDOW`
2. **Rust native `hash_runtime.rs`** — Had its own `CREATE_NO_WINDOW`
handling but was duplicated
3. **`nx@latest` temp install** — The daemon downloads `nx@latest` to a
temp directory for NX Console and AI agent checks. The install process
(`pnpm add -D nx@latest`) and the downloaded code's `git ls-remote`
calls run without `windowsHide: true`
4. **~120 Node.js `child_process` calls** — Various
`spawn`/`exec`/`execSync` calls across the codebase were missing
`windowsHide: true`

## Changes

### Node.js child_process fixes
- Set `windowsHide: true` on all `spawn`/`exec`/`execSync`/`spawnSync`
calls across the codebase (~120 files)
- Added custom ESLint rule `@nx/workspace/require-windows-hide` that
errors when any spawn/exec call is missing `windowsHide: true`

### Rust native fixes
- **New shared util `native/utils/command.rs`** with `create_command()`
and `create_shell_command()` that set `CREATE_NO_WINDOW` on Windows —
centralizes the pattern so future Rust code gets it right by default
- **`ide/install.rs`** — Use `create_command()` for `code.cmd` calls
(list-extensions, install-extension, version check)
- **`hash_runtime.rs`** — Replaced local `create_command_builder()` with
shared `create_shell_command()`
- **`machine_id/mod.rs`** — Updated to use shared
`create_shell_command()`

### Daemon background operation fixes
- **`handle-configure-ai-agents.ts`** — Now respects `NX_USE_LOCAL` env
var to skip downloading `nx@latest`, avoiding the pnpm install that
opens windows. Once these fixes ship in a release, the downloaded
`nx@latest` will also have the fixes.

### Inlined node-machine-id
- Replaced the `node-machine-id` npm package with an inlined
implementation in `machine-id-cache.ts` that uses `windowsHide: true`
- The original package used `exec`/`execSync` without `windowsHide`

## Related Issue(s)

Supersedes #34455

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-03-20 09:29:44 -04:00
Juri Strumpflohner 2086e4c0b6 fix(testing): gracefully handle broken jest configs in alias migration (#34901)
## Current Behavior

The `replace-removed-matcher-aliases` migration crashes the entire
migration runner when a project has a broken or misconfigured
`jest.config.ts`. This was discovered while upgrading nx-labs from Nx 21
to 22 via `nx migrate --run-migrations`.

The migration uses Jest's `readConfig()` and `Runtime.createContext()`
to resolve jest configs, but has no error handling around these calls.
If any project has issues like:

- A missing file referenced in the config (e.g. `.lib.swcrc` that was
renamed to `.swcrc`)
- A missing transform module (e.g. `@swc/jest` not installed)
- A missing preset file (e.g. `jest.preset.ts` instead of
`jest.preset.js`)

...the entire migration fails with an opaque error:

```
Error: Command failed: /var/folders/.../node_modules/.bin/nx _migrate --run-migrations
    at checkExecSyncError (node:child_process:925:11)
  status: 1,
  stdout: null,
  stderr: null
```

The actual errors are swallowed by the nested `execSync` call, making it
very hard to debug.

## Expected Behavior

The migration should skip projects with broken jest configs and continue
processing the rest of the workspace.

## Fix

Wrapped the `readConfig` / `Runtime.createContext` /
`SearchSource.getTestPaths` block in a try-catch that skips the failing
project. This matches the defensive pattern already used in
`packages/jest/src/plugins/plugin.ts` for similar jest config
resolution.

## Test Plan

- Added 3 tests covering: missing file reference, missing preset, and
verifying valid projects are still processed when a sibling project has
a broken config
- All existing tests continue to pass
2026-03-20 10:20:38 +01:00
Louie Weng 747df3159c fix(gradle): remove annotations from atomizer (#34871)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->
The Gradle plugin's test class atomizer was incorrectly treating Kotlin
annotation class declarations as test targets, generating invalid test
tasks for them. This caused issues when projects defined custom
annotations alongside their test classes—the atomizer would attempt to
run annotation classes as tests.

## Expected Behavior
              
Both the AST-based parser and the regex fallback parser now skip
annotation classes, matching the existing behavior for data classes,
enum classes, sealed classes, and abstract classes. A new test suite
covers all annotation class exclusion scenarios for both parsers,
including files with multiple annotation classes and mixed
annotation/test class files.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: lourw <lourw@users.noreply.github.com>
2026-03-19 20:16:09 -07:00
Leosvel Pérez Espinosa a4e8fefd5c fix(core): prevent DB corruption from concurrent initialization (#34861)
## Current Behavior

When multiple processes call `connectToNxDb()` concurrently (plugin
workers via `startAnalytics()`, daemon, main CLI), two bugs can corrupt
the workspace database:

**Bug 1: Lock file inode race.** `unlock_file()` deletes the lock file
after unlocking, allowing a subsequent `File::create()` to produce a new
file with a different inode. Two processes can hold "the lock"
simultaneously on different file objects, breaking mutual exclusion.

**Bug 2: Partial file cleanup on version mismatch/connection failure.**
The `reason` arm and `Err` arm in `initialize_db` call
`remove_file(db_path)` which only deletes `.db`, leaving stale `.db-wal`
and `.db-shm` on disk. The recursive `initialize_db` creates a fresh
`.db`, but SQLite detects the stale WAL (different inode salt) and
deletes it — destroying all data that existed only in the WAL.

Both bugs lead to:
```
Database file exists but has no metadata table.
```

## Expected Behavior

1. Lock file persists across lock/unlock cycles — all processes
serialize through the same inode
2. When DB recreation is needed, all auxiliary files (`.db`, `.db-wal`,
`.db-shm`) are cleaned up together via `remove_all_database_files`

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-19 18:51:03 -04:00
Craigory Coppola 91b1414a9e fix(core): pass collectInputs flag through daemon IPC for task hashing (#34915)
When the daemon handles task hashing, the `NativeTaskHasherImpl` checked
`hasTaskInputSubscribers()` in the daemon process where no subscribers
exist, causing the native Rust hasher to skip input collection entirely.
This resulted in empty input arrays in IO tracing signals.

The fix passes collectInputs from the client process (where subscribers
are registered) through the daemon IPC to the hasher, so the daemon uses
the client's subscriber state instead of its own.
2026-03-19 17:02:45 -04:00
Jack Hsu 1081dbdceb fix(core): remove CRA migration logic from nx init (#34912)
## Current Behavior

`nx init` has a special code path for Create React App (CRA) projects
that installs Vite-related dependencies incompatible with `@nx/vite`
when Vite 8 is resolved, causing failures for npm workspaces.

CRA is essentially unused for years so there's no point to keep it
around.

## Expected Behavior

CRA projects are no longer special-cased in `nx init`. They flow through
the normal npm repo path, which detects plugins like `@nx/vite` via the
standard `detectPlugins()` mechanism.

<img width="1279" height="448" alt="Screenshot 2026-03-18 at 2 34 56 PM"
src="https://github.com/user-attachments/assets/17da3c6d-8aef-450f-baaa-6014e03cf41f"
/>

<img width="1280" height="432" alt="Screenshot 2026-03-18 at 2 35 01 PM"
src="https://github.com/user-attachments/assets/e02ccda5-9faf-428f-878d-348e4cc44d74"
/>

<img width="1270" height="821" alt="Screenshot 2026-03-18 at 2 35 15 PM"
src="https://github.com/user-attachments/assets/fdd99af5-db7e-422b-be42-a817c9dcefa2"
/>


## Related Issue(s)

Closes NXC-4107
2026-03-19 16:38:22 -04:00
cylewaitforit ffd2f614e0 docs(core): add Yarn catalog to dependency management (#34874)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Adds references to [Yarn catalog](https://yarnpkg.com/features/catalogs)
to dependency management documentation.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Related #34377

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
2026-03-19 15:14:46 -04:00
Juri ea69d99a9b docs(misc): update self-healing CI video embed 2026-03-19 18:52:34 +01:00
Leosvel Pérez Espinosa b9f251d6fc fix(core): improve error handling in nx migrate registry fetching (#34926)
## Current Behavior

Several issues in `nx migrate` registry fetching cause confusing errors:

- `packageRegistryPack` uses `pnpm pack` for pnpm users, but `pnpm pack`
only packs the local project (unlike `npm pack` which downloads remote
packages). This silently fails for all pnpm users, always falling back
to the slower install path.
- On Windows, `extractFileFromTarball` fails because `join('package',
migrationsFilePath)` produces backslash paths that don't match
forward-slash tarball entries.
- When the install fallback also fails,
`getPackageMigrationsUsingInstall` returns `{}` instead of throwing. The
missing `version` property (`undefined`) propagates through
`packageUpdates` and `collectedVersions`, producing `Fetching
nx@undefined`.
- The install fallback can fail on peer dependency conflicts since
`legacy-peer-deps` was removed as a default (PR #33014), even though the
install only needs files on disk (not a valid dependency tree).
- The `.catch()` in `fetchMigrations` swallows errors without logging,
making it impossible to diagnose registry fetch failures.

## Expected Behavior

- `packageRegistryPack` always uses `npm pack` since it's the only
package manager that supports downloading remote packages
- Tarball entry matching uses `joinPathFragments` to normalize paths
(forward slashes) for cross-platform compatibility
- `getPackageMigrationsUsingInstall` throws on failure with a
descriptive error instead of returning an empty object
- Install fallback sets `npm_config_legacy_peer_deps=true` since it only
needs files on disk, not a valid dependency tree
- Registry fetch errors are logged at verbose level
(`NX_VERBOSE_LOGGING=true`) for debuggability

## Related Issue(s)

Fixes #33135
2026-03-19 17:45:50 +01:00
Leosvel Pérez Espinosa 1db5b32410 fix(core): ensure postTasksExecution fires on SIGINT for continuous tasks (#34876)
## Current Behavior

When pressing Ctrl+C on continuous tasks, `postTasksExecution` never
fires because `process.exit()` in `running-tasks.ts` SIGINT handlers
kills the process before the orchestrator can run async cleanup and
lifecycle hooks.

## Expected Behavior

`postTasksExecution` fires reliably on Ctrl+C with complete task
results, without causing stale `RunningTasksService` DB entries that
block subsequent `nx` invocations with "Waiting for ... in another nx
process" messages.

## Technical Details

Re-applies #34623 (reverted in #34869) with fixes for the issues that
caused CI failures.

**Root cause of the revert**: Removing `process.exit()` from SIGINT
handlers left the nx process alive during async cleanup. The
`running_tasks` DB entry persisted with a still-alive PID, so new nx
processes saw it as a running task and hung.

**Fix 1 — Early DB cleanup**: Synchronously remove all owned DB entries
in the SIGINT handler *before* starting async cleanup. This replicates
the cleanup that `process.exit()` + Rust `Drop` previously provided —
from any external observer's perspective, the tasks are gone
immediately.

**Fix 2 — Always register onExit in startContinuousTask**: The original
PR added a guard that skipped `onExit` registration for initiating
tasks, assuming `executeNextBatchOfTasksUsingTaskSchedule` would handle
it. But `runContinuousTasks()` (used by DTE agents and Playwright) calls
`startContinuousTask` directly without going through `run()`. The
missing handler meant task exit was never processed — no DB cleanup, no
lifecycle hooks. This caused verdaccio (local-registry) to become
unreachable on DTE agents. The fix always registers `onExit` in
`startContinuousTask` and simplifies the
`executeNextBatchOfTasksUsingTaskSchedule` handler to only unblock the
thread.

Changes:
- Remove `process.exit()` from `running-tasks.ts` SIGINT handlers (lets
orchestrator run)
- Replace `cleanupDone` boolean with `cleanupPromise` (fixes
SIGINT/SIGTERM race)
- Set `stopRequested = true` for SIGTERM/SIGHUP (correct task
classification)
- Early synchronous DB cleanup in non-TUI SIGINT handler
- Always register `onExit` handler in `startContinuousTask` for both
initiating and non-initiating tasks
- Simplify initiating task handler in
`executeNextBatchOfTasksUsingTaskSchedule` to only call `res()`
2026-03-19 11:22:13 -04:00
Leosvel Pérez Espinosa e1e4ac8401 fix(core): avoid redundant project graph requests in ngcli adapter (#34907)
## Current Behavior

The Angular devkit adapter (`ngcli-adapter.ts`) calls
`createProjectGraphAsync()` in multiple places, even though callers
(executors, generate command, migrate command) already have the project
graph available. This results in redundant requests to create the graph.

## Expected Behavior

Reuse the project graph from callers when available, falling back to
reading from cache (`readCachedProjectGraph()`) or
`createProjectGraphAsync()` only when necessary.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-19 11:10:54 -04:00
Craigory Coppola 17de526516 docs(core): show supported version range for subcommand (#34889)
## Current Behavior
There's not a great way to attach metadata to CLI commands to influence
rendering the markdown docs for them

## Expected Behavior
There's a metadata system currently used to express minimum support
version for the show target command

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-03-19 11:10:19 -04:00
John Wiegert ba6e036b1c fix(testing): handle undefined options in playwright preset (#34750)
## Current Behavior

When calling `nxE2EPreset(__filename)` without passing the optional
`options` parameter, the function throws a runtime error because
`options.openHtmlReport` accesses a property on `undefined`. Other
property accesses in the function already use optional chaining
(`options?.testDir`, `options?.generateBlobReports`), but this one was
missed.

Additionally, the `openHtmlReport` property documents a default value of
`'on-failure'` via JSDoc, but that default was never actually applied in
the code.

## Expected Behavior

Calling `nxE2EPreset(__filename)` without the `options` argument works
without errors. The `openHtmlReport` option correctly falls back to
`'on-failure'` when not specified, matching the documented `@default` in
the interface.

## Related Issue(s)

N/A - Discovered when upgrading NX and we didn't pass options
2026-03-19 11:02:55 -04:00
Jack Hsu ec8be2deda docs(misc): remove outdated Angular multiple-workspace migration page (#34913)
## Current Behavior
The "Migrating Multiple Angular CLI Workspaces" page is a 26-line stub
with only a YouTube video. It doesn't mention `nx import` and contains
outdated guidance. The single-workspace migration guide (`angular.mdoc`)
also has outdated sections: a list of supported builders, a full CI
setup walkthrough, an irrelevant "From Nx Console" section, and a
reference to `karma.conf.js`.

## Expected Behavior
- The `angular-multiple.mdoc` page is removed with a redirect to the
main Angular migration guide.
- The main migration guide mentions `nx import` for consolidating
multiple Angular CLI projects.
- Outdated sections (Modified folder structure, Set up CI, From Nx
Console) are removed and replaced with concise links.
- The `nx-and-angular.mdoc` guide links to `nx import` docs instead of
the deleted page.

Preview:
https://deploy-preview-34913--nx-docs.netlify.app/docs/technologies/angular/migration/angular-multiple
(redirects to the updated page)


## Related Issue(s)
Closes DOC-419
2026-03-19 10:25:36 -04:00
Leosvel Pérez Espinosa 7054ffaf82 fix(linter): use root config to determine ESLint class in plugin (#34900)
## Current Behavior

The `@nx/eslint/plugin` uses `eslintConfigFiles[0]` to decide between
`FlatESLint` and `LegacyESLint`. Due to glob pattern ordering,
`.eslintrc.*` files sort before `eslint.config.*`, so a stray nested
`.eslintrc.json` (e.g., in `eslint-local-rules/`) causes the plugin to
pick `LegacyESLint` even when the root has a flat config — crashing with
"No ESLint configuration found" during project graph creation.

## Expected Behavior

The ESLint class is determined from the root config, matching ESLint's
own behavior where `find-up` from cwd decides the mode. Nested legacy
config files are irrelevant when a root flat config exists. When both
flat and legacy configs exist at root (mid-migration), flat config is
preferred.

## Related Issue(s)

Fixes #32110
2026-03-19 09:42:38 -04:00
Leosvel Pérez Espinosa f91bc6a665 fix(linter): convert project-level eslint configs and log when skipped (#34899)
## Current Behavior

The `convert-to-flat-config` generator silently skips project-level
`.eslintrc.json` files when `projectConfig.targets` is undefined (e.g.,
package.json-only projects in pnpm workspaces). The `@nx/eslint/plugin`
check is gated behind the targets check and never reached.

## Expected Behavior

Projects with `.eslintrc.json` are converted when `@nx/eslint/plugin` is
registered, even without explicit targets. When a project is skipped
because no ESLint lint target is detected, a warning is logged so users
know which projects were not converted and why.

## Related Issue(s)

Fixes #29458
2026-03-19 09:40:37 -04:00
Leosvel Pérez Espinosa 564d2a5a3e fix(linter): use native nx.configs in convert-to-flat-config for Nx plugins (#34897)
## Current Behavior

The `convert-to-flat-config` generator wraps all plugin extends with
`FlatCompat`, including Nx-specific ones like `plugin:@nx/typescript`.
The output doesn't match what freshly generated projects produce
(`nx.configs['flat/typescript']`). The `@nx` plugin registration uses a
manual `{ plugins: { '@nx': nxEslintPlugin } }` block instead of
`flat/base`.

## Expected Behavior

Nx plugin extends are converted to native `nx.configs['flat/X']`
entries. The `@nx` plugin registration uses `flat/base`. `FlatCompat` is
only used for third-party plugins. The import variable is normalized to
`nx` to match fresh generation.

## Related Issue(s)

Fixes #31736
2026-03-19 09:38:35 -04:00
Leosvel Pérez Espinosa 0f0f99624d fix(linter): detect require() calls in enforce-module-boundaries rule (#34896)
## Current Behavior

The `enforce-module-boundaries` rule only visits ESM AST nodes
(`ImportDeclaration`, `ImportExpression`, `ExportAllDeclaration`,
`ExportNamedDeclaration`). CommonJS `require()` calls bypass all
boundary checks entirely.

## Expected Behavior

`require()` and `require.resolve()` calls are detected and validated
against the same module boundary rules as ESM imports. Auto-fix is
skipped for `require()` nodes since they have no `specifiers`.

## Related Issue(s)

Fixes #34096
2026-03-19 09:37:17 -04:00
Eric Baer 213b1e44dc fix(core): properly quote shell metacharacters in CLI args passed to tasks (#34491)
## Current Behavior

When CLI arguments containing shell metacharacters (like `|`, `&`, `$`,
`;`, `*`, etc.) are passed through Nx to underlying tasks, they are not
properly quoted, causing shell interpretation errors.

For example, running:
```bash
nx test app --grep="@tag1|@tag2"
```

Would fail with `/bin/sh: @smoke: command not found` because the pipe
character `|` was interpreted by the shell as a pipe operator instead of
being passed as a literal string to the underlying command.

Users had to use awkward double-quoting workarounds like
`--grep='"@tag1|@tag2"'` to get the expected behavior.

## Expected Behavior

CLI arguments containing shell metacharacters should be automatically
quoted before being passed to underlying commands, so that:
```bash
nx test app --grep="@tag1|@tag2"
```

Works correctly and passes `--grep="@tag1|@tag2"` to the underlying test
runner without shell interpretation.

## Related Issue(s)

Fixes #32305
Fixes #26682

## Implementation Details

- Created a shared `needsShellQuoting()` utility in
`packages/nx/src/utils/shell-quoting.ts` that detects shell
metacharacters
- Updated `wrapArgIntoQuotesIfNeeded()` in `run-commands.impl.ts` to use
the shared utility
- Updated `stringShouldBeWrappedIntoQuotes()` in
`serialize-overrides-into-command-line.ts` to use the shared utility
- Fixed a bug where `arg.split('=')` would incorrectly split values
containing `=` (e.g., `--define=FOO=bar|baz`)
- Added proper escaping of embedded double quotes when wrapping values
- Added comprehensive test coverage

### Shell metacharacters now handled:
`|` `&` `;` `<` `>` `(` `)` `$` `` ` `` `\` `"` `'` `*` `?` `[` `]` `{`
`}` `~` `#` `!` and whitespace

---------

Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
2026-03-19 10:33:53 +01:00
Jason Jean 259b8236c3 fix(core): skip analytics and DB connection when global bin hands off to local (#34914)
## Current Behavior

When the globally installed Nx binary (`bin/nx.ts`) runs, it goes
through the following flow before determining whether to hand off to a
local Nx installation:

1. `ensureAnalyticsPreferenceSet()` — prompts user if analytics
preference not set
2. `startAnalytics()` — which calls `getDbConnection()` →
`connectToNxDb(directory, NX_VERSION)` using the **global** Nx version
and native bindings, then calls `initializeTelemetry(dbConnection, ...)`
to initialize telemetry
3. Sets `NX_ANALYTICS_SESSION_ID` env var

Then it checks which execution path to take:
- **`isNxCloudCommand`** — executes commands directly (no handoff)
- **`isLocalInstall`** — the global IS the local, calls `initLocal()`
(no handoff)
- **`localNx` exists** — hands off to the local Nx via
`require(localNx)`

In the handoff case, the local `bin/nx.ts` runs `main()` from scratch,
which calls `startAnalytics()` again. It sees `NX_ANALYTICS_SESSION_ID`
is already set and takes the "reuse session" shortcut — but telemetry
was already initialized with the wrong version's native bindings and DB
connection.

**Problems:**
- The global bin opens a DB connection with the **wrong `NX_VERSION`**
(global version, not local version) and **wrong native bindings**
- Analytics is initialized twice — once from global (incorrect), once
from local (correct but using session reuse path)
- The DB connection opened by the global bin is unnecessary since the
local Nx handles everything

## Expected Behavior

When the global bin is about to hand off to a local Nx installation, it
should **not** initialize analytics or open any DB connections. The
local Nx will handle analytics initialization correctly with its own
version and native bindings.

Analytics and DB connections should only be initialized in the two
branches where the global bin actually executes commands itself:
- `isNxCloudCommand` — needs analytics because it runs commands directly
- `isLocalInstall` — needs analytics because `initLocal()` doesn't
re-enter `bin/nx.ts`

## Related Issue(s)

<!-- Internal discovery during analytics work -->

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-18 23:31:49 -04:00
Jason Jean 724737d0cd chore(repo): correctly pin ktfmt version (#34917)
### Current Behavior

ktfmt... is supposed to be pinned

### Expected Behaior

ktfmt is now actually pinned
2026-03-18 21:03:34 -04:00
Caleb Ukle c03c4ccfcb fix(nx-dev): resolve changelog page 500 error (#34920)
## Current Behavior

Visiting [nx.dev/changelog](https://nx.dev/changelog) returns an HTTP
500 Internal Server Error. The page builds successfully during
deployment (prerendered as SSG), but the Netlify server handler fails at
runtime when serving the page.

## Expected Behavior

The changelog page loads correctly, displaying Nx release history with
version timelines and any manually authored changelog content.

## Related Issue(s)

Fixes #34909

## Changes

- **`next.config.js`**: Add `outputFileTracingIncludes` for
`public/documentation/changelog/**` so the changelog content directory
is included in the Netlify serverless function bundle (Next.js file
tracing can't detect `readdirSync` with string paths)
- **`pages/changelog.tsx`**: Wrap `changeLogApi.getChangelogEntries()`
in try/catch for graceful degradation if the directory is unavailable
during on-demand rendering
- **`rewrite-framer-urls.ts`**: Add `/changelog` to the edge function
`excludedPath` config so the Framer proxy edge function is bypassed
entirely for changelog requests
- **`_redirects`**: Fix malformed redirect on line 55 — destination path
was missing a leading `/`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 20:56:44 -04:00
Leosvel Pérez Espinosa 9e4a9aabb9 fix(js): preserve tsconfig fields in typescript plugin cache (#34908)
## Current Behavior

After certain cache state transitions (e.g., tsconfig parsing cache warm
but targets cache cold), the `@nx/js/typescript` plugin falls back to
the `production` named input instead of deriving precise inputs from
tsconfig `include`/`exclude` paths. This results in broader cache
invalidation than necessary, and in projects with `allowJs` or
`resolveJsonModule`, the wrong file extensions are tracked as inputs.
Output inference is also affected when `emitDeclarationOnly` or
`declarationMap` are set.

Running `nx reset` restores correct behavior, but the issue recurs.

## Expected Behavior

Inferred inputs and outputs are consistent regardless of cache state —
always matching what the tsconfig actually specifies.

The root cause was the tsconfig parsing cache serialization
(`toAbsolutePaths`/`toRelativePaths`) dropping fields the plugin relies
on: `raw.include`, `raw.exclude`, `raw.files`, and `options.allowJs`,
`options.resolveJsonModule`, `options.emitDeclarationOnly`,
`options.declarationMap`. These are now preserved, and
`TSCONFIG_CACHE_VERSION` is bumped to invalidate stale caches.
2026-03-18 14:21:01 -04:00
Leosvel Pérez Espinosa 2962f624b7 fix(js): normalize cwd path separator in typescript plugin targets (#34911)
## Current Behavior

On Windows, the TypeScript plugin produces `cwd` with backslash
separators (e.g., `cwd: "packages\\nx"`) for both the typecheck and
build targets. All paths in Nx targets should use `/` separators.

## Expected Behavior

The `cwd` option uses forward slashes on all platforms (e.g., `cwd:
"packages/nx"`).

## Related Issue(s)

Fixes NXC-4105
2026-03-18 14:15:52 -04:00
MaxKless 100d4f40a4 feat(core): add .nx/self-healing to .gitignore (#34855)
## Summary
- Self-healing auto-apply writes fix context to `.nx/self-healing/`, but
that directory was not in `.gitignore`. This adds it via a migration and
in the CAIA generator, following the same pattern as `.nx/polygraph`.

## Current Behavior

The `.nx/self-healing` directory is not gitignored. Users who run
self-healing auto-apply may accidentally commit generated fix context
files.

## Expected Behavior

`.nx/self-healing` is added to `.gitignore` automatically during `nx
migrate` (via a new migration) and when running the CAIA setup
generator.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-18 17:14:21 +00:00
Jack Hsu 44db03a87b fix(core): wrap CNW normalize args function in error handler (#34905)
This is a follow-up to https://github.com/nrwl/nx/pull/34902, where we
throw `CnwError` for known problems.

We need to wrap `normalizeArgsMiddleware` in a try-catch to invoke the
shared (extracted) error handler, since these errors are not within the
`main` function body but happens prior.

Before this PR:

<img width="1339" height="277" alt="image"
src="https://github.com/user-attachments/assets/911ecd4d-7117-434b-bff7-ff20dddfd84d"
/>


After:

<img width="1258" height="112" alt="image"
src="https://github.com/user-attachments/assets/f9c15bed-2df4-4e78-ac95-11f67cb9d4f5"
/>

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-18 09:56:02 -04:00
Caleb Ukle 58f4bc7b7f fix(nx-dev): add clickjacking protection headers to Netlify configs (#34893)
Add X-Frame-Options and Content-Security-Policy frame-ancestors headers
to prevent clickjacking on nx.dev marketing and docs sites.

Fixes DOC-449

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 08:44:17 -05:00
Craigory Coppola a8c5ca97be chore(core): abortGraphPhase -> notifyPhaseAborted (#34885)
## Current Behavior
#34799 has some method names that could have been better

## Expected Behavior
The methods are named to signify they are notifications, not
instructions

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-18 09:37:14 -04:00
MaxKless 2679029077 fix(core): share .agents skills dir across codex, cursor, gemini (#34882)
## Summary
- The upstream config repo (nx-ai-agents-config) now generates skills
into a shared `.agents/skills/` directory instead of separate per-agent
skills directories for codex, cursor, and gemini.
- Updated the `agentDirs` mapping so `.agents` is copied when any of
codex, cursor, or gemini are enabled, not just codex.
- Widened the `agent` field type from `Agent` to `Agent | Agent[]` to
support mapping a single directory to multiple agents.

## Key decisions
- Used `Agent | Agent[]` union type rather than always-array to minimize
changes to existing single-agent entries. The loop normalizes with
`Array.isArray` before checking.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 22:04:51 +09:00
Jack Hsu 5c0e05c3b3 fix(core): gracefully handle missing package manager and invalid workspace for CNW (#34902)
We want to capture more error and cancel events so that start events
match complete/error/cancel events. This PR ensures that more errors are
properly captured as `CnwError` rather than just `output.error` +
`process.exit(1)`;

- Invalid workpspace name (i.e. starting with a number) now throws
`CnwError` so we record them correctly
- Missing package manager is now captured as `CnwError` correctly
- SIGINT when workpspace is already created now send "cancel" event

Closes NXC-4095
2026-03-18 08:58:46 -04:00
Jason Jean 98ba5ac8ef chore(core): update nx to 22.6.0-rc.2 (#34892)
## Current Behavior

Nx packages are on a previous version.

## Expected Behavior

All Nx packages updated to 22.6.0-rc.2.

## Related Issue(s)

N/A - routine version bump for RC testing.
2026-03-17 20:01:27 -04:00
Jason Jean 6ee7091c55 Revert "feat(js): add deps-sync generator (#34407)" (#34888)
This reverts commit 8d71d5b57b.

## Current Behavior
<!-- This is the behavior we have today -->

This generator causes unnecessary changes

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

The generator is removed for now and will be reintroduced when it is
refined.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-03-17 17:15:54 -04:00
Caleb Ukle d6f22a2749 fix(nx-dev): cross site link checks working as expected (#34685)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: barbados-clemens <barbados-clemens@users.noreply.github.com>
2026-03-17 15:55:06 -05:00
Caleb Ukle 47857419e6 docs(core): add technology-agnostic batch mode guide (#34813)
## Current Behavior

Batch mode documentation is scattered across individual technology pages
(TypeScript, Gradle, Maven, Jest) with no central guide explaining the
concept, how it works, or which executors support it.

## Expected Behavior

A new guide page at `guides/tasks--caching/batch-mode` provides a
technology-agnostic overview of batch mode, covering:

- What batch mode is and why it's faster
- How to enable it (`NX_BATCH_MODE=true` env var and `--batch` CLI arg)
- Which executors support it (with notes that Gradle/Maven have it on by
default)
- Caching and CI compatibility

Each technology-specific page now links back to the central guide for
the full explanation.

## Related Issue(s)

Fixes #DOC-420

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: barbados-clemens <barbados-clemens@users.noreply.github.com>
2026-03-17 15:54:52 -05:00
Jack Hsu e2a056084a feat(core): bring back cloud prompts and templates in CNW (#34887)
## Current Behavior

CNW flow matches v22.1.3 (restored in #34671): no template prompt is
shown, the cloud prompt uses simplified "Would you like remote caching?"
wording, banner variant is locked to 0, and preset flow connects to
cloud during workspace creation.

## Expected Behavior

Restore the template prompt and cloud prompts that were removed in
#34671:
- **Template prompt**: "Which starter do you want to use?" with 5
choices (Minimal, React, Angular, NPM Packages, Custom)
- **Cloud prompt**: `determineNxCloudV2` ("Connect to Nx Cloud?") for
preset flow when no `--nxCloud` CLI arg; existing CI provider prompt
when `--nxCloud` is explicitly provided
- **Banner**: Variant 2 (box banner) for standard Nx Cloud URLs, variant
0 for enterprise
- **Preset flow**: Deferred cloud connection (`nxCloud: 'skip'`) instead
of connecting during workspace creation
- **Push logic**: Push to GitHub for both `nxCloud === 'github'` and
`nxCloud === 'yes'`
- **Messages**: "Try the full Nx platform?" wording, GitHub repo link in
push messages, "Your remote cache setup is almost complete." title

Preserves all subsequent changes (analytics prompt from #34818,
SANDBOX_FAILED fix, .gitignore updates).

## Related Issue(s)

Fixes NXC-4096
2026-03-17 16:15:04 -04:00
Jason Jean b73cc6939b fix(core): avoid overwhelming DB with connections during analytics init (#34881)
## Current Behavior

Every process that initializes analytics opens its own DB connection to
get/create a session ID. This includes the CLI, the daemon, and **every
plugin worker**. In workspaces with many plugins, dozens of plugin
workers spawn simultaneously, each opening a DB connection and
querying/writing session metadata. This overwhelms the SQLite database
with concurrent connections and causes failures.

## How This Fixes It

The root cause is that plugin workers each independently open a DB
connection just to read the session ID. The fix eliminates this by
having the parent process (CLI or daemon) fetch the session ID once and
pass it to child processes via the `NX_ANALYTICS_SESSION_ID` environment
variable. Plugin workers inherit this env var and initialize telemetry
without touching the DB at all.

| Process | Before | After |
|---------|--------|-------|
| CLI | Opens DB connection | Opens DB connection (1x) |
| Daemon | Opens DB connection | Opens DB connection (1x) |
| Plugin worker (×N) | Each opens DB connection | Reads env var, **no DB
connection** |

In a workspace with 20 plugins, this reduces DB connections from 22 (CLI
+ daemon + 20 workers) down to 2 (CLI + daemon only).

## Two Initialization Paths

- **`initializeTelemetry(dbConnection, ...)`** — Used by CLI and daemon.
Gets/creates the session ID from the DB via a transaction, stores the
connection for persisting session refreshes on flush, and returns the
session ID so the caller can set it as an env var for child processes.
- **`initializeTelemetryWithSessionId(sessionId, ...)`** — Used by
plugin workers. Takes the session ID inherited from the parent process
env var. No DB connection, no DB queries.

## Session Refresh for Long-Lived Processes

The daemon is long-lived and could hold a stale session ID for hours.
The telemetry background thread now tracks activity and generates a new
session ID after 30 minutes of inactivity (matching the existing GA4
session timeout). When a session refreshes, the background thread
notifies the main thread via a channel, which persists the new session
to the DB in a transaction on flush.

## Other Changes

- Extracted `TelemetryOptions` struct to replace the long parameter list
in `TelemetryService::new`
- Moved `SESSION_TIMEOUT_SECS` to `constants.rs` so it can be shared
between modules
- Extracted `init_service` helper to deduplicate between the two init
paths
- Extracted `persist_session_to_db` helper that wraps both metadata
writes in a transaction
- Separated session ID retrieval (`get_or_create_session_id`) from
telemetry service initialization

## Related Issue(s)

Fixes database connection exhaustion when many plugin workers initialize
analytics simultaneously.
2026-03-17 15:34:51 -04:00
Jack Hsu dcdabdda27 docs(core): add telemetry documentation page (#34884)
## Current Behavior

There is no documentation for Nx CLI telemetry, which was added in Nx
22.6.0. Users who are prompted to opt in have no reference page to learn
what data is collected or how to opt out.

## Expected Behavior

A dedicated telemetry reference page at `/reference/telemetry` explains
what is collected, what is not collected, and how to disable telemetry
via `nx.json`. The `nx.json` reference page also documents the
`analytics` property.

Changes:
- New page: `astro-docs/src/content/docs/reference/telemetry.mdoc`
- Sidebar entry added under Reference
- `analytics` property added to nx.json reference (expanded example +
new section)

## Related Issue(s)

Fixes DOC-446
2026-03-17 15:20:40 -04:00
Craigory Coppola cbd64acca6 chore(repo): reduce codeowners to represent team shape a bit better (#34886)
## Current Behavior
The CODEOWNERS setup is getting in the way as core maintainers have
moved around and timezones and such...

## Expected Behavior
Anyone on the CLI reviewers team should be capable of recognizing PRs
they are capable of assessing, this eases the burden of increased PRs
from AI chatbots and timezones.
2026-03-17 15:12:07 -04:00
Jack Hsu c9b261db64 feat(misc): track server page views for AI traffic using Netlify-Agent-Category (#34883)
## Current Behavior

Only the astro-docs (nx-docs) Netlify app tracks server-side page views
via edge functions. AI tool and bot detection relies on fragile
User-Agent regex matching against a hardcoded list of known bot strings.

## Expected Behavior

Both nx-dev and nx-docs page views are tracked server-side via GA4,
using Netlify's built-in `Netlify-Agent-Category` header to distinguish
`ai-agent` from `crawler` traffic.

Tracking edge functions are consolidated into the nx-dev app
(`netlify/edge-functions/`) since all traffic flows through it before
being rewritten to nx-docs. Framer-proxied pages are tracked inline in
`rewrite-framer-urls.ts` because the proxy short-circuits
`context.next()`, preventing downstream edge functions from firing.

**Changes:**
- Moved `track-page-requests.ts` and `track-asset-requests.ts` from
`astro-docs/netlify/edge-functions/` to `netlify/edge-functions/`
- Replaced User-Agent regex with `Netlify-Agent-Category` header checks
in all tracking functions
- Added GA4 tracking to `rewrite-framer-urls.ts` for Framer-proxied
pages

## Related Issue(s)

Fixes DOC-445
2026-03-17 13:58:41 -04:00
Craigory Coppola 631734028f fix(core): ensure workers shutdown after phase cancelled (#34799)
## Current Behavior
New logic in the daemon can cancel an active graph creation, resulting
in worker shutdown not behaving as intended

## Expected Behavior
When the daemon aborts graph construction, the plugins still shut down

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-03-17 13:53:56 -04:00
Caleb Ukle 6482a2c6a9 docs(nx-cloud): update image list for nx agents (#34791)
- define image version list
- document mise step
2026-03-17 18:06:46 +01:00
Jason Jean 1367d2a211 fix(vite): pin vitest v4 to ~4.0.x to fix Yarn Classic resolution failure (#34878)
## Current Behavior

Projects scaffolded with `@nx/vite` or `@nx/vitest` generators use
`^4.0.0` for vitest v4 dependencies. Since vitest 4.1.0 (released Mar
12), its vite peer dep expanded to `^6.0.0 || ^7.0.0 || ^8.0.0-0`. Yarn
Classic's linker fails with `Invariant Violation: could not find a copy
of vite to link` when encountering this expanded OR range.

This breaks all e2e tests running with Yarn Classic (e2e-cypress,
e2e-eslint, e2e-jest, e2e-playwright, e2e-web, e2e-webpack on
Linux/yarn/20 combos).

## Expected Behavior

Scaffolded projects use `~4.0.x` tilde ranges for vitest v4, restricting
to patch updates only. This avoids pulling in vitest 4.1.0+ and the
problematic peer dep range, keeping Yarn Classic compatibility intact.

## Related Issue(s)

Fixes failing CI runs on yarn combos since Mar 12-13.
2026-03-17 11:26:40 -04:00
Craigory Coppola cf79f486f5 fix(core): trim memory usage associated with io-tracing service (#34866)
## Current Behavior
We accumulate `inputs`/`outputs`/`pids` and store them even if no
subscriber is subscribed, and then they hang around to notify late
subscribers that may never come

## Expected Behavior
`inputs`/`outputs`/`pids` are only sent to current subscribers

## AI Summary 

This pull request optimizes how task input notifications are handled in
Nx by ensuring that expensive input collection and storage only occur
when there are active subscribers. This change prevents unnecessary
memory growth in long-lived processes, such as the Nx daemon, and
improves performance by avoiding redundant work.

Notification and input collection optimization:

* Added a `hasTaskInputSubscribers()` method to the `TaskIOService`
class, allowing the hasher to check if any input subscribers are
registered before collecting and notifying task inputs.
* Updated all hashing functions in `hash-task.ts` to only notify task
inputs if there are active subscribers, reducing unnecessary work and
memory usage.
[[1]](diffhunk://#diff-d061dc5551f692abad009b8284c719466cff2f0d3d19bd52e81b7921a9e543d6R57-R63)
[[2]](diffhunk://#diff-d061dc5551f692abad009b8284c719466cff2f0d3d19bd52e81b7921a9e543d6L114-R117)
[[3]](diffhunk://#diff-d061dc5551f692abad009b8284c719466cff2f0d3d19bd52e81b7921a9e543d6R172)
[[4]](diffhunk://#diff-d061dc5551f692abad009b8284c719466cff2f0d3d19bd52e81b7921a9e543d6L185-R188)
[[5]](diffhunk://#diff-d061dc5551f692abad009b8284c719466cff2f0d3d19bd52e81b7921a9e543d6L201-R204)
* Modified the native task hasher implementation
(`native-task-hasher-impl.ts` and Rust code) to conditionally collect
and return input data only when requested, minimizing overhead and
memory allocation.
[[1]](diffhunk://#diff-ddf992f97afbcd8b2206b8d1faab7e7f9571ecfe50e2ab8e3da104001f39f0c3L73-R80)
[[2]](diffhunk://#diff-ddf992f97afbcd8b2206b8d1faab7e7f9571ecfe50e2ab8e3da104001f39f0c3L88-R101)
[[3]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR194-R200)
[[4]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL226-R233)
[[5]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR256)
[[6]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL262-R273)
[[7]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL288-R299)
[[8]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR330-R352)
[[9]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL348-R383)
[[10]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL368-R407)
[[11]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR432)
[[12]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL417-R475)
[[13]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL452-R489)
[[14]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bL461-R503)
[[15]](diffhunk://#diff-d81ca7875513ef544822c24671104ce52cd09409a41a796768aba507d87b3c0bR518)

Code cleanup and refactoring:

* Removed unused state and redundant code from `TaskIOService` related
to storing task-to-PID, task-to-input, and task-to-output mappings, as
well as unnecessary graph references and late subscriber emission logic.
[[1]](diffhunk://#diff-fc051b28c1ac25926033d157ec2278cc1cf2fcf7e16626eeedaf876c29fc9d62R42-R66)
[[2]](diffhunk://#diff-fc051b28c1ac25926033d157ec2278cc1cf2fcf7e16626eeedaf876c29fc9d62L91-L95)
[[3]](diffhunk://#diff-fc051b28c1ac25926033d157ec2278cc1cf2fcf7e16626eeedaf876c29fc9d62L104-L111)
* Updated imports and cleaned up constructor logic in
`task-io-service.ts` and `native-task-hasher-impl.ts` for clarity and
maintainability.
[[1]](diffhunk://#diff-ddf992f97afbcd8b2206b8d1faab7e7f9571ecfe50e2ab8e3da104001f39f0c3R16)
[[2]](diffhunk://#diff-fc051b28c1ac25926033d157ec2278cc1cf2fcf7e16626eeedaf876c29fc9d62L1-L2)

These changes collectively make task input tracking more efficient and
robust, especially in scenarios where Nx runs as a daemon or in
environments with no listeners for input notifications.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
2026-03-17 10:35:36 -04:00
MaxKless 61a8e333c6 fix(core): detect npm from package-lock.json before falling back to invoking PM (#34877)
## Current Behavior

When `detectPackageManager()` finds no lock file for bun, yarn, or pnpm,
it falls back to `detectInvokedPackageManager()` which checks
`npm_config_user_agent`. This causes misdetection when a workspace is
created with npm (has `package-lock.json`) but the parent process uses
pnpm — the detection picks up pnpm from the user agent instead of npm
from the lock file.

This has been causing **every nightly E2E run to fail since March 4th**
(when #34691 was merged), particularly in e2e-angular where `ng-add`
tests create npm workspaces but `installPackagesTask` incorrectly
invokes `pnpm install --no-frozen-lockfile`.

## Expected Behavior

`detectPackageManager()` should check for `package-lock.json` (npm's
lock file) before falling back to the invoking package manager
detection. This ensures that workspaces with a `package-lock.json` are
correctly identified as npm workspaces regardless of what package
manager invoked the current process.

## Related Issue(s)

Fixes the nightly E2E matrix failures in e2e-angular (`ng-add.test.ts`,
`plugin.test.ts`), e2e-nx-init, and e2e-workspace-create that have been
consistently failing since #34691 was merged.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 14:26:46 +00:00
Miroslav Jonaš 8d71d5b57b feat(js): add deps-sync generator (#34407)
This PR introduces the `deps-sync` generator which pairs with
`typescript-sync`generator to ensure internal dependencies are correctly
mapped via `devDependencies` of corresponding `package.json`.

## Current Behavior
When dependency to local package is created (via import for example) the
existing typescript-sync generator updates the tsconfig but package.json
is left untouched which might cause issues when referencing transitive
dependencies.

## Expected Behavior
The typescript-sync should be accompanied by deps-sync generator that
would update dependencies in package.json for workspaces.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2026-03-17 10:22:52 -04:00
Craigory Coppola de216f25ec fix(core): show continuous property in nx show target (#34867)
Add the continuous property to the nx show target output. Previously the
command showed cache and parallelism but not whether a target is
continuous.

Fixes NXC-4084

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
2026-03-16 23:16:09 -04:00
Jason Jean 1afb96b7f4 fix(gradle): always check disk cache for gradle project graph reports (#34873)
## Current Behavior

The Gradle plugin uses an in-memory `gradleCurrentConfigHash` variable
to decide whether to skip re-running `./gradlew nxProjectGraph`. Since
plugin workers shut down between graph computations, this variable
resets to `undefined` each time. The `??=` operator on the disk cache
read means it's only read once per worker lifetime, and the
`!gradleCurrentConfigHash` check always evaluates to `true` on fresh
workers — making the in-memory hash comparison dead code.

This means the caching logic works despite itself (via disk cache), but
is fragile and could cause unnecessary Gradle invocations or stale cache
hits if worker lifecycle assumptions change.

There was also another issue with hashing options when calculating the
project graph for nodes and dependencies. Options were not normalized
when hashing which you did not get deterministic hashing when calling
dependencies after createNodes.

## Expected Behavior

Always read the disk cache and compare hashes directly. If the hash
matches, skip running Gradle. If not, run Gradle and update the disk
cache. No in-memory state needed between worker restarts.

Options are normalized before hashing to ensure that regardless of
createNodes or dependencies, we will get a deterministic hash.

## Related Issue(s)

<!-- No specific issue — discovered during code review -->

---------

Co-authored-by: lourw <56288712+lourw@users.noreply.github.com>
2026-03-16 20:51:59 -04:00
nx-cloud[bot] 332671d2e4 chore(core): update nx to 22.6.0-rc.0 (#34872)
## Current Behavior

Nx packages are pinned to 22.6.0-beta.13.

## Expected Behavior

Nx packages updated to the latest pre-release 22.6.0-rc.0.

## Related Issue(s)

N/A - routine version bump

<!-- polygraph-session-start -->
---
[View session information on Nx Cloud
↗](https://staging.nx.app/orgs/62d013d4d26f260059f7765e/agent-sessions/update-00367)

**Related PRs:**
> - https://github.com/nrwl/nx-examples/pull/426
<!-- polygraph-session-end -->

Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
2026-03-16 23:43:02 +00:00
Jason Jean 35930fd0d9 fix(core): add .claude/settings.local.json to .gitignore (#34870)
## Current Behavior

When configuring AI agents via `nx polygraph`, a
`.claude/settings.local.json` file is created containing user-specific
settings. This file is not gitignored by Nx, so it can accidentally be
committed to the repository.

## Expected Behavior

`.claude/settings.local.json` should be gitignored by default, similar
to how `.claude/worktrees` is already handled.

## Changes

- Added a migration that adds `.claude/settings.local.json` to existing
workspaces' `.gitignore`
- Updated all three `.gitignore` templates so new workspaces include it
from the start
- Follows the same pattern as the existing
`add-claude-worktrees-to-git-ignore` migration
2026-03-16 17:11:27 -04:00
Jason Jean ded713ffd9 Revert "fix(core): ensure postTasksExecution fires on SIGINT for continuous tasks (#34623)" (#34869)
## Current Behavior

After #34623, when continuous tasks are killed (Ctrl+C), the nx process
stays alive during async cleanup instead of exiting immediately. This
causes the `RunningTasksService` SQLite entry to persist with a
still-alive PID.

When a new `nx` process starts during this cleanup window,
`is_task_running()` finds the stale entry, confirms the PID is still
alive (old process cleaning up), and creates a `SharedRunningTask` —
incorrectly printing:

```
Waiting for @nrwl/ocean:local-registry in another nx process
```

The root cause: #34623 removed `process.exit()` from SIGINT handlers in
`running-tasks.ts`, widening the window where the old process is alive
but the task is no longer actually running.

## Expected Behavior

After killing continuous tasks, a new `nx` invocation should not see
stale "Waiting for ... in another nx process" messages.

This reverts #34623 while a proper fix is designed that achieves both
goals: reliable `postTasksExecution` firing AND prompt DB cleanup.

## Related Issue(s)

Reverts #34623

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-16 16:51:46 -04:00
Victor Savkin e41ba65a94 fix(nx-cloud): download light client to tmp dir when outside nx workspace (#34805)
## Current Behavior
When running nx commands outside of an Nx workspace (no nx.json), the
light client is downloaded to .nx/cache/cloud relative to process.cwd().
This creates an unwanted .nx folder in whatever directory the user
happens to be in.

## Expected Behavior
When outside an Nx workspace, the light client is downloaded to a temp
directory (os.tmpdir()/nx-cloud-client/hash) where hash is derived from
the NX_CLOUD_API URL. This avoids polluting arbitrary directories with
.nx folders while ensuring different cloud instances get separate
directories.

When inside an Nx workspace, behavior is unchanged.
2026-03-16 18:55:18 +00:00
Jason Jean 6b8d5c9e95 chore(maven): bump maven plugin version to 0.0.16 (#34862)
## Current Behavior

The Maven plugin version is at 0.0.15.

## Expected Behavior

The Maven plugin version is bumped to 0.0.16 with a corresponding
migration entry for Nx 22.6.0-beta.14.

## Related Issue(s)

N/A - routine version bump.

### Changes

- Updated all pom.xml files to version 0.0.16
- Updated `mavenPluginVersion` constant in `versions.ts`
- Added migration entry in `migrations.json`
- Created migration file `update-pom-xml-version.ts` for 0.0.16
2026-03-16 09:58:29 -07:00
Leosvel Pérez Espinosa e9781bdd3b fix(js): track tsconfig files from dependency reference chain as inputs (#34848)
## Current Behavior

The `@nx/js/typescript` plugin adds dependency project `tsconfig.json`
files as inputs but doesn't resolve nested `projectReferences` within
those files. When `tsc -b` runs, it follows the full reference chain —
reading `tsconfig.lib.json`, `tsconfig.spec.json`,
`tsconfig.storybook.json`, etc. from dependencies — but these files
aren't declared as inputs, causing potential cache correctness issues.

## Expected Behavior

The plugin now walks the full project reference chain from external
dependencies and collects all distinct tsconfig relative paths (e.g.,
`tsconfig.lib.json`, `tsconfig.spec.json`, `cypress/tsconfig.json`).
These are emitted as `^{projectRoot}/...` input patterns, ensuring that
any tsconfig file read by `tsc -b` through the reference chain is
tracked as an input.

Key changes:
- Renamed `getExternalProjectReferenceConfigFiles` →
`getExternalProjectReferenceTsconfigPatterns` to reflect it now returns
input patterns instead of absolute paths
- Uses a worklist algorithm to traverse the reference chain
(breadth-first, cycle-safe)
- Collects relative paths per dependency project root, deduplicates, and
emits `^{projectRoot}/...` patterns
- For build targets, only tsconfig files reachable from the build
reference chain are included (not all refs from the solution tsconfig)
2026-03-16 12:57:55 -04:00
Miroslav Jonaš 9bebb04410 chore(repo): remove unused flags from ci.yaml (#34847)
These flags were added while we tested changes to the task API and
streaming. They are no longer needed.

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-03-16 12:39:53 -04:00
Leosvel Pérez Espinosa 5a799848bf fix(testing): infer task inputs from jest config file references (#34740)
## Current Behavior

The Jest plugin infers `test` target inputs from the preset file path
but ignores other config properties that reference files — transforms,
setup files, module name mappers, reporters, watch plugins, etc. Changes
to those files don't invalidate the test cache.

## Expected Behavior

All Jest config properties that reference files are resolved and
included as task inputs, matching Jest's merge semantics:

- **Replaced** (config wins over preset): `resolver`, `globalSetup`,
`globalTeardown`, `snapshotResolver`, `snapshotSerializers`,
`testResultsProcessor`, `runner`, `reporters`, `watchPlugins`
- **Concatenated** (preset + config): `setupFiles`, `setupFilesAfterEnv`
- **Deep merged** (config keys win): `moduleNameMapper`, `transform`

Also handles `jest-runner-`/`jest-watch-` prefix resolution, `<rootDir>`
in preset values, and Windows path normalization.

Adds a `useJestResolver` option to control whether jest-resolve is used
for input resolution, decoupled from `disableJestRuntime`. By default,
inputs are resolved using path-based classification (fast, no filesystem
calls beyond what's already done). When `useJestResolver` is enabled,
jest-resolve is used instead, which follows symlinks and honors custom
`moduleDirectories`/`modulePaths` — more accurate for workspace-linked
packages but slower due to filesystem probing per resolved path.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-16 11:53:20 -04:00
Leosvel Pérez Espinosa 3e4cfa0ed1 fix(core): ensure postTasksExecution fires on SIGINT for continuous tasks (#34623)
## Current Behavior

When pressing Ctrl+C on continuous tasks (e.g., `nx run app:serve`),
`postTasksExecution` never fires or fires with incomplete task results.
This affects any plugin or custom lifecycle hook relying on
`postTasksExecution` to perform cleanup, reporting, or post-run logic.

The issue manifests in non-TUI mode (`NX_TUI=false`) and in setups where
nx runs as a child of a package manager (pnpm/npm), which sends SIGTERM
shortly after SIGINT.

## Expected Behavior

`postTasksExecution` fires reliably on Ctrl+C with complete task
results, regardless of whether TUI is enabled or how the nx process is
invoked.

## Technical Details

Three independent issues caused the broken behavior:

**1. Initiating continuous task exits before orchestrator cleanup**

When the initiating task is continuous and exits with a signal code
(e.g., 130 from SIGINT), the `onExit` handler called
`process.exit(code)` synchronously — before the orchestrator's SIGINT
handler could run. Cleanup and `postTasksExecution` never executed.

Fixed by replacing `process.exit()` with proper task completion via
`handleContinuousTaskExit`, and ensuring initiating tasks are the sole
`onExit` callback (to avoid floating promises from
`exitCallbacks.forEach` not awaiting async callbacks).

**2. run-commands SIGINT handlers call `process.exit(130)`**

Every `nx:run-commands` task running in the main process registered a
SIGINT handler that called `process.exit(signalToCode('SIGINT'))`,
killing the process before the orchestrator's async cleanup could run.

Fixed by removing `process.exit()` from both SIGINT handlers in
`running-tasks.ts`. The `this.kill('SIGTERM')` call is kept for prompt
child termination. Cache-write safety is already guaranteed by
`postRunSteps` guards (`stopRequested`, `status !== 'stopped'`).

**3. `cleanup()` resolves prematurely on concurrent signals**

When pnpm/npm sends SIGTERM ~30ms after SIGINT, the SIGTERM handler saw
`cleanupDone = true` (set at the start of cleanup, before async work),
returned immediately, and its `.finally()` called `resolveStopPromise()`
before SIGINT's cleanup finished. `run()` returned with incomplete task
results.

Fixed by replacing the `cleanupDone` boolean with a stored promise.
Concurrent callers now await the same in-progress cleanup.

**Additional fixes:**

- SIGTERM/SIGHUP handlers now set `stopRequested = true` so externally
terminated tasks are correctly classified as `'interrupted'` rather than
`'fulfilled'`.
- Extracted shared exit-handling logic into `handleContinuousTaskExit`
to consolidate reason-determination between initiating and
non-initiating continuous tasks.
2026-03-16 11:52:26 -04:00
MaxKless b7732c0646 fix(core): skip analytics prompt for cloud commands (#34789)
## Current Behavior

The analytics prompt (`ensureAnalyticsPreferenceSet()`) and
`startAnalytics()` run for all commands, including cloud commands like
`download-cloud-client`. When a cloud command runs outside an Nx
workspace, `saveAnalyticsPreference()` creates an almost-empty `nx.json`
(`{ "analytics": true }`) in whatever directory you happen to be in.

## Expected Behavior

Cloud commands skip the analytics prompt and `startAnalytics()`
entirely, since they may run without a workspace and there is no
appropriate `nx.json` to write to.

## Related Issue(s)

Fixes the issue where running `nx download-cloud-client` outside a
workspace creates a spurious `nx.json`.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 11:51:04 -04:00
Copilot 23eec78754 chore(repo): remove .nx/workflows/sandboxing-config.yaml (#34860)
Removes the `.nx/workflows/sandboxing-config.yaml` file, which is no
longer needed in the repository.

## Changes

- **Deleted** `.nx/workflows/sandboxing-config.yaml` — no CI pipelines
or workflow files reference this file directly
- The only existing mention is in `astro-docs/.../sandboxing.mdoc`,
which is purely instructional documentation guiding users to create
their own config; no update needed

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> ## Goal
> Remove the workflow configuration file
`.nx/workflows/sandboxing-config.yaml` from the `nrwl/nx` repository.
> 
> ## Context
> - Repository: `nrwl/nx`
> - Base branch: `master`
> - File to remove: `.nx/workflows/sandboxing-config.yaml`
> - Source URL:
https://github.com/nrwl/nx/blob/master/.nx/workflows/sandboxing-config.yaml
> - Commit OID (reference provided by user):
`5dd12046c3d5bc5919abdb176be2d11f16c8f943`
> - Current contents:
>   ```yaml
>   exclude-reads:
>     - '**/node_modules/**'
>     - .nx/**/*
>     - .git/**/*
>     - '**/package.json'
>     - pnpm-workspace.yaml
>   exclude-writes:
>     - nx/**/*
>   ```
> 
> ## Requirements
> 1. Delete `.nx/workflows/sandboxing-config.yaml`.
> 2. Ensure there are no other required references to this file (e.g.,
in CI/workflows) that would break due to removal. If references exist,
update them accordingly.
> 3. Run the full validation suite prior to finalizing (`nx prepush`).
> 4. If validation fails, fix the issues by amending the current commit
(do not create additional commits solely for validation fixes).
> 
> ## Deliverable
> Open a pull request that removes
`.nx/workflows/sandboxing-config.yaml` (and any necessary reference
updates), including a clear description of the change and validation
performed.
> 
> The following is the prior conversation context from the user's chat
exploration (may be truncated):
> 
> User: please open a pr to remove this file
> 
> Assistant: I’m paused waiting for you to accept or reject the pull
request action in the confirmation dialog—once you respond there, I can
continue.
> 
> 


</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created from Copilot chat.*
>

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: FrozenPandaz <8104246+FrozenPandaz@users.noreply.github.com>
2026-03-16 11:39:09 -04:00
Jason Jean 9c9cb24248 fix(maven): use mutable lists for Maven session projects (#34834)
## Current Behavior

When using Maven plugins that fork executions (e.g. `maven-pmd-plugin`),
the Nx Maven batch executor crashes with
`java.lang.UnsupportedOperationException`. This happens because Kotlin's
`listOfNotNull()`, `mapNotNull()`, and `filter()` return immutable
lists, but Maven's `MojoExecutor.executeForkedExecutions` calls
`list.set()` on `session.projects`, which requires a mutable list.

## Expected Behavior

Maven plugins that fork executions (PMD, Checkstyle, etc.) should work
correctly with the Nx Maven batch executor. All lists assigned to
`session.projects` and `session.allProjects` are now wrapped with
`toMutableList()` to ensure Maven can mutate them as needed.

The fix is applied to both the Maven 3 and Maven 4 adapters.

## Related Issue(s)

Fixes #34758
2026-03-16 11:37:47 -04:00
Jack Hsu 760004bfc9 docs(nx-dev): document Netlify deployment architecture (#34859)
Document how nx.dev is deployed on Netlify and how requests are routed
between Framer (marketing), Next.js (blog/courses), and Astro (docs).

Includes:
- Request flow diagram and explanation
- Edge function configuration
- _redirects file organization
- Environment variables reference
- Common debugging tasks
- How to add redirects and rewrites

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
2026-03-16 11:34:11 -04:00
Jason Jean 6e54e7987b fix(gradle): handle project names containing .json substring (#34832)
## Current Behavior

The `@nx/gradle` plugin fails to parse the project graph when a Gradle
project name contains `.json` as a substring (e.g.
`org.acme.util.jsonutils`). The parsing logic in `processNxProjectGraph`
checks `includes('.json')` to find the JSON file path, but starts from
the task line itself. When the project name contains `.json`, the task
line matches immediately and gets used as a file path, causing an
`ENOENT` error.

## Expected Behavior

The plugin correctly skips the task line and finds the actual JSON file
path on the following line, regardless of whether the project name
contains `.json`.

Two changes:
1. Increment `index` after matching the task line to skip it before
searching for the JSON path
2. Use `endsWith('.json')` instead of `includes('.json')` for a more
precise match

## Related Issue(s)

Fixes #34768
2026-03-16 08:28:07 -07:00
Jason Jean 9466abab97 fix(webpack): bump fork-ts-checker-webpack-plugin to 9.1.0 (#34826)
## Summary
- Bumps `fork-ts-checker-webpack-plugin` from `7.2.13` to `9.1.0` in
root `package.json` and `packages/webpack/package.json`
- v7 depends on `memfs` v3 which uses a deprecated `fs.stat` API,
causing console warnings on Node 22+
- v9 uses updated `memfs` and has no breaking API changes for the
constructor pattern used by Nx

## Test plan
- [ ] Run Angular webpack app and verify no `fstat` deprecation warnings
- [ ] Run Node webpack build and verify no regressions
- [ ] Verify type checking still works via the plugin

Fixes #34404
2026-03-16 11:25:31 -04:00
Jason Jean daf272af4a fix(module-federation): use sslKey instead of sslCert for pathToKey (#34824)
## Summary
- `pathToKey` was incorrectly assigned
`this._options.devServerConfig.sslCert` instead of `sslKey` in all 4
Module Federation dev server plugins (Rspack, Rspack SSR, Angular,
Angular SSR)
- This caused SSL to break when using separate cert and key files

## Files Changed
-
`packages/module-federation/src/plugins/nx-module-federation-plugin/rspack/nx-module-federation-dev-server-plugin.ts`
-
`packages/module-federation/src/plugins/nx-module-federation-plugin/rspack/nx-module-federation-ssr-dev-server-plugin.ts`
-
`packages/module-federation/src/plugins/nx-module-federation-plugin/angular/nx-module-federation-dev-server-plugin.ts`
-
`packages/module-federation/src/plugins/nx-module-federation-plugin/angular/nx-module-federation-ssr-dev-server-plugin.ts`

## Test plan
- [ ] Verify SSL works with separate cert/key files in Module Federation
dev server (Rspack)
- [ ] Verify SSL works with separate cert/key files in Module Federation
dev server (Angular)

Fixes #34811
2026-03-16 15:20:47 +00:00
Jason Jean 410372311c fix(core): add null guards for runningTasksService in WASM fallback (#34825)
## Summary
- When native bindings fail to load (`IS_WASM = true`),
`runningTasksService` is `null`
- Two call sites in `task-orchestrator.ts` accessed it without null
checks, causing `Cannot read properties of null (reading
'addRunningTask')` during `nx serve`
- Added optional chaining (`?.`) at both sites, matching the existing
guard pattern already used elsewhere in the same file

## Test plan
- [ ] Run `nx serve` in an environment where native bindings are
unavailable (e.g. missing `@nx/nx-<platform>`)
- [ ] Verify no crash on `addRunningTask` or `removeRunningTask`

Fixes #34573
2026-03-16 11:19:19 -04:00
Leosvel Pérez Espinosa 5dd12046c3 fix(testing): infer dependency tsconfig files as playwright plugin inputs (#34803)
## Current Behavior

The Playwright plugin does not include `tsconfig*.json` files from
dependency projects as inputs. Playwright resolves these files when
running tests, leading to unexpected file reads that aren't tracked for
caching.

## Expected Behavior

When the `production` named input is used, the Playwright plugin infers
`^{projectRoot}/tsconfig*.json` as an input so dependency tsconfig files
are properly tracked. This is not needed for the `^default` branch since
it already includes all dependency files.
2026-03-16 10:54:53 -04:00
MaxKless 4fc976448b fix(core): add download-cloud-client to cloud command bypass list (#34788)
## Current Behavior

Running `npx nx@next download-cloud-client` outside an Nx workspace
fails with "The current directory isn't part of an Nx workspace" because
`download-cloud-client` is not in the `isNxCloudCommand` list in
`packages/nx/bin/nx.ts`. This means the process exits at the
`handleNoWorkspace` guard before ever reaching the handler fixed in
#34746.

## Expected Behavior

`download-cloud-client` runs successfully outside an Nx workspace, like
`login`, `logout`, `polygraph`, and other cloud commands that don't need
workspace context.

## Related Issue(s)

Follows up on #34728 and #34746 — `download-cloud-client` was added
before the cloud command bypass existed and was missed when the bypass
was introduced.
2026-03-16 23:16:01 +09:00
Caleb Ukle d060c69e56 fix(core): preserve params and options when expanding wildcard dependsOn targets (#34822)
## Summary

- `expandWildcardTargetConfiguration` was explicitly copying only
`projects` and `dependencies` when expanding glob patterns in
`dependsOn` target names, dropping `params` and `options`
- This meant `"params": "forward"` (and `"options": "forward"`) had no
effect when the `dependsOn` target used a wildcard like `"target":
"build*"`
- Fix uses object spread (`...dependencyConfig`) to preserve all
properties from the original dependency config

## Bug

Given this `project.json`:
```json
{
  "targets": {
    "build-all": {
      "executor": "nx:noop",
      "dependsOn": [
        {
          "target": "build*",
          "params": "forward"
        }
      ]
    }
  }
}
```

Running `nx run project:build-all --myParam=value` would correctly
resolve the wildcard to match targets like `build`, `build:test`,
`build:prod`, etc. — but `--myParam=value` was never forwarded to those
targets because `params: "forward"` was dropped during expansion.

## Root Cause

In `expandWildcardTargetConfiguration`
(`packages/nx/src/tasks-runner/utils.ts`), the matched targets were
mapped with only `target`, `projects`, and `dependencies`:

```typescript
return matchingTargets.map((t) => ({
    target: t,
    projects: dependencyConfig.projects,
    dependencies: dependencyConfig.dependencies,
    // params and options were missing!
}));
```

## Fix

Use object spread to carry over all properties:

```typescript
return matchingTargets.map((t) => ({
    ...dependencyConfig,
    target: t,
}));
```

## Test plan

- [x] Added test case verifying `params: "forward"` is preserved after
wildcard expansion
- [x] All 45 existing tests in `utils.spec.ts` continue to pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 08:26:58 -05:00
AI-JamesHenry f0aa006b83 fix(release): deduplicate projects in changelog when using filtered project list (#34851) 2026-03-16 15:36:34 +04:00
Benjamin Cabanes 01840adfcc fix(nx-dev): remove nx-cloud paths from Framer excluded URL rewrites (#34852)
The change removes Nx Cloud routes from the list of excluded URL rewrite
paths in the Framer edge rewrite configuration. This means requests to
those Nx Cloud paths will now be eligible for the rewrite behavior
instead of being skipped.
2026-03-16 06:13:54 -04:00
Miroslav Jonaš 008b0bce55 chore(repo): replace picocolors with native styleText for scripts (#34571)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-16 10:34:13 +01:00
AI-JamesHenry f8c821bd55 fix(release): include dependent projects in release commit message when using --projects filter (#34845) 2026-03-16 13:27:58 +04:00
AI-JamesHenry f40c4a5a17 fix(js): skip npm dist-tag add when no new version was resolved (#34843) 2026-03-15 21:20:57 +04:00
AI-JamesHenry 7fb8a91c14 fix(release): skip indirect patch bump for commit types with semverBump "none" (#34841) 2026-03-15 16:53:57 +04:00
AI-JamesHenry f1735bda36 fix(js): support bun-only environments in release-publish executor (#34835) 2026-03-14 20:38:38 +04:00
Jason Jean 343f95445d feat(core): add task and project count telemetry via performance lifecycle (#34821)
## Current Behavior

Telemetry only reports event duration via the `measureAndTrack` helper,
which uses a `[track] ` prefix convention. No task-level metrics (count,
cache hits, project count) are reported.

## Expected Behavior

- New event dimensions: `taskCount`, `projectCount`, `cachedTaskCount`
available for telemetry events
- `TaskTelemetryLifeCycle` reports task execution metrics (duration,
task count, project count, cached task count) — only runs on the main
CLI process, not on DTE agents
- `performance.measure()` with `detail: { track: true, ... }` replaces
the `measureAndTrack` / `[track] ` prefix pattern
- Perf observer automatically forwards detail entries matching known GA4
dimension keys
- `reportEvent` simplified to a pass-through — callers use
`customDimensions` keys directly
- `customDimensions` and `EventParameters` exported for use by callers
2026-03-13 20:35:35 -04:00
Joel Lefkowitz d6a7e109d7 fix(core): fix TUI help text layout (#34754)
## Current Behavior

The TUI help text format is currently malformed.

<img width="1181" height="191" alt="Screenshot 2026-03-07 at 11 13 40"
src="https://github.com/user-attachments/assets/e66316eb-600d-4456-920e-c9538c10e7e7"
/>

- The links overlap the plaintext
- Some of the plaintext has the link color
- The bullet points are on the same line

## Expected Behavior

Separation of links and plaintext with bullet points on separate lines.

### Changes

The text output of the TUI help section looks like this:

```txt
│                                                                                                                                                                                                                              │
│  Thanks for using Nx! To get the most out of this terminal UI, please check out the docs: https://nx.dev/terminal-uiIf you would prefer to not use the TUI, you can disable it by: - Adding the `--no-tui` flag to your      │
│  command.- Setting NX_TUI=false in your environment.                                                                                                                                                                         │
│  If you are finding Nx useful, please consider giving it a star on GitHub, it means a lot: https://github.com/nrwl/nx                                                                                                        │
│                                                                                                                                                                                                                              │
```

This PR corrects the layout to be more readable:

```txt
│                                                                                                                                                                                                                              │
│  Thanks for using Nx! To get the most out of this terminal UI, please check out the docs: https://nx.dev/terminal-ui                                                                                                         │
│                                                                                                                                                                                                                              │
│  If you would prefer to not use the TUI, you can disable it by:                                                                                                                                                              │
│  - Adding the `--no-tui` flag to your command.                                                                                                                                                                               │
│  - Setting `NX_TUI=false` in your environment.                                                                                                                                                                               │
│                                                                                                                                                                                                                              │
│  If you are finding Nx useful, please consider giving it a star on GitHub, it means a lot: https://github.com/nrwl/nx                                                                                                        │
│                                                                                                                                                                                                                              │
```

### Notes

The help link `https://nx.dev/terminal-ui` doesn't exist at the moment.
2026-03-13 18:41:37 -04:00
Jason Jean 3d1b1ec6b0 feat(core): prompt for analytics preference during workspace creation (#34818)
## Current Behavior

When a user creates a new workspace with `create-nx-workspace`, they are
not asked about analytics. The analytics prompt only appears later on
the first `nx` command run, or via the 22.6.0 migration.

## Expected Behavior

Users are prompted to opt in or out of usage analytics during
`create-nx-workspace`, so the preference is set from the start. The
prompt matches the style of other prompts in the flow (autocomplete with
Yes/No choices).

- **Preset flow**: The `analytics` property is set via the workspace
generator's `createNxJson`, so `nx.json` is properly formatted by
prettier through `formatFiles(tree)`
- **Template flow**: The `analytics` property is written directly to
`nx.json` (matching the pattern used by `setNeverConnectToCloud`)
- Supports `--analytics` CLI flag for non-interactive usage
- Skips the prompt in CI and non-interactive environments (defaults to
`false`)
2026-03-13 19:46:16 +00:00
Jason Jean 9db241cb17 chore(repo): update nx to 22.6.0-beta.13 (#34812)
Updating Nx from 22.6.0-beta.12 to 22.6.0-beta.13
2026-03-13 11:33:02 -04:00
Juri Strumpflohner 7428875252 docs(core): rewrite Nx vs Turborepo comparison page (#34792)
## Current Behavior

The Nx vs Turborepo comparison doc mixed setup complexity with advanced
features without a clear progression. Some sections were missing (code
generation comparison, cross-repo coordination, project graph
visualization). Images were in PNG format.

## Expected Behavior

- Progressive structure: starts with basics (onboarding, running tasks)
and moves to advanced capabilities (CI, AI, cross-repo); beats the
misconception that starting with Nx is more complex or you need to go
full in
- Overview table at top for quick comparison with links to sections (we
could collapse it if it gets too much 🤔)
- New sections: Running tasks (to emphasize how simple it is to run
tasks in an existing repo, again beating the misconceptions that are
around), Cross-repo coordination, CI throughput, Project graph
- Improved sections: Onboarding (incremental adoption story), Caching
(real benchmark configs), Code generation (acknowledges turbo gen)
- All images converted to AVIF
- Migration doc updated: removed beta/Nx 21 language for continuous
tasks

New docs:
- [comparison
doc](https://deploy-preview-34792--nx-docs.netlify.app/docs/guides/adopting-nx/nx-vs-turborepo)
- [migration doc (mostly
untouched)](https://deploy-preview-34792--nx-docs.netlify.app/docs/guides/adopting-nx/from-turborepo)

## Related Issue(s)

N/A - documentation improvement
2026-03-13 16:14:37 +01:00
Jason Jean 0d2d7cb881 fix(core): batch hashing, topological cache walk, and TUI batch fixes (#34798)
## Current Behavior

- `hashBatchTasks` hashes tasks one-by-one via `Promise.all` +
`hashTask` (singular), making N separate native hasher calls.
- Batch cache resolution rebuilds the entire remaining task graph each
wave via `removeTasksFromTaskGraph`, which is O(tasks) per wave and can
crash if `graph.dependencies[id]` is undefined.
- `postRunSteps` for cached batch results is deferred until all cache
resolution completes.
- TUI batch groups appear below "Waiting for task..." placeholders
because their status is based on nested task statuses, which may not
have transitioned yet.

## Expected Behavior

- `hashBatchTasks` uses `hashTasks` (plural) to batch all tasks into a
single native hasher call.
- Batch cache resolution uses a new `walkTaskGraph` util that walks
topologically via in-degree counters — only visits direct dependents per
wave instead of rebuilding the graph.
- `postRunSteps` runs incrementally per wave during cache resolution.
- TUI batch groups are treated as in-progress by existence (since
`start_batch` was called), only moving to completed when all nested
tasks are done.

## Related Issue(s)

Fixes crash: `Cannot read properties of undefined (reading 'filter')` in
`removeIdsFromTaskGraph` during batch cache resolution.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-13 13:26:41 +00:00
Jason Jean a72f0a4447 feat(core): centralize perf tracking and report metrics to telemetry (#34795)
## Current Behavior

- Performance tracking is scattered across multiple files with separate
`PerformanceObserver` instances (daemon server, plugin worker, nx.ts)
- Only `createProjectGraphAsync` duration is reported to analytics via a
dedicated `reportProjectGraphCreationEvent` function
- The Rust telemetry `flush()` has a race condition where closing event
channels before sending the flush request can cause the background
thread to exit before processing the flush
- The `default(timeout)` branch in the background sender does nothing
(`continue`)

## Expected Behavior

- A single centralized `PerformanceObserver` in `perf-logging.ts`
handles all performance measure reporting
- Any `performance.measure()` call prefixed with `[track]` is
automatically reported to telemetry (prefix stripped from display/event
name)
- `reportPerfEvent(name, duration)` is a generic function that works for
any perf measure
- Daemon-aware logging: uses `serverLogger` when running in the daemon,
`console.log` otherwise
- The telemetry flush race condition is fixed by sending the flush
request before closing channels
- The background sender's `default(timeout)` branch properly drains and
sends batches
- Duplicate drain logic is extracted into `enqueue_event`,
`enqueue_page_view`, and `drain_channels` helpers

### Currently tracked measures:
- `createProjectGraphAsync` — full project graph build time
- `{plugin}:createNodes` — per-plugin node creation time
- `{plugin}:createDependencies` — per-plugin dependency creation time

## Related Issue(s)

N/A

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-13 13:24:04 +00:00
Jason Jean e2d79e8dbe fix(nuxt): fix E2E test environment and lint issues (#34808)
## Current Behavior

Nuxt and rspack E2E tests fail due to multiple issues:
1. `NODE_ENV=test` leaking from Jest into build subprocesses, causing
nuxt to skip type-checking
2. ESLint errors on compiled `.vue.js` files containing `__VLS_*`
identifiers
3. Rspack multi-compiler array config tests fail without `NODE_ENV`
4. Remix vite version incompatible with vitest

Nuxt build E2E still fails due to TS6304 (`composite: true` +
`declaration: false` conflict in non-TS-solution workspaces) — that test
is skipped pending a proper fix.

## Expected Behavior

Nuxt lint, rspack, and remix E2E tests pass. Nuxt build test is skipped
with a TODO until the composite tsconfig issue is resolved.

## Related Issue(s)

Fixes CI E2E failures for nuxt, rspack, and remix.

## Changes

### Environment fixes
- **Strip `NODE_ENV` from E2E subprocess env** — Jest sets
`NODE_ENV=test` which leaked into nuxt build, causing it to skip
type-checking. Stripped globally in `getStrippedEnvironmentVariables()`.
- **Strip AI agent env vars** (`CLAUDECODE`, `CURSOR_TRACE_ID`, etc.) —
prevents the test runner's environment from leaking into e2e
subprocesses.
- **Pass `NODE_ENV` explicitly for rspack array config tests** — rspack
multi-compiler builds need `NODE_ENV` to determine build mode; pass it
via `runCLI` env option.

### Nuxt fixes
- **Add `**/*.vue.js` to ESLint flat config ignores** — compiled Vue
files contain `__VLS_*` identifiers that trigger lint errors.
- **Skip nuxt build e2e test** — pending fix for TS6304
composite/declaration conflict in non-TS-solution workspaces.

### Remix fix
- **Bump vite from `^5.0.0` to `^6.0.0`** — vitest dropped vite 5
support.

### E2E infra
- **Always print E2E workspace directory** — removed `isVerbose()` gate
so the path is always logged.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-13 08:30:23 -04:00
Steven Nance 6b59e74586 docs(nx-cloud): add DPE information exchange section to Okta SAML docs (#34794)
## Current Behavior

The Okta SAML doc ends with a vague "Contact your developer productivity
engineer" message. It doesn't specify what information needs to be
exchanged between the customer and DPE to enable SAML and SCIM. This
section was present in the old combined `auth-saml.md` but was lost
during the migration to separate Okta/Azure docs in astro-docs.

## Expected Behavior

The doc now has a clear "Information to exchange with your DPE" section
that outlines:
- **From your DPE** (provided up front): Nx Cloud App URL, JWT token,
and Organization ID for SCIM setup
- **Send back to your DPE**: SAML certificate and SAML entry point URL

This matches the pattern already used in the Azure SAML doc and assumes
SCIM will be configured.

## Related Issue(s)

N/A

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: llwt <llwt@users.noreply.github.com>
2026-03-12 22:28:49 +01:00
Jason Jean 926c603ec3 Revert "fix(core): gate tui-logger init behind NX_TUI env var (#34426)" (#34797)
## Current Behavior

The TUI debug pane (F12) opens but shows no log output. This is because
PR #34426 gated `tui_logger::init_logger()` behind `NX_TUI=true` in
`initialize_logger()`. However, `enable_logger()` uses `Once::call_once`
and is called from many places (WorkspaceContext, PseudoTerminal, etc.)
before yargs sets `process.env.NX_TUI = 'true'`. Since the first call
wins, the tui-logger layer is never registered and the debug pane stays
empty.

## Expected Behavior

The TUI debug pane (F12) displays log output when the TUI is active,
regardless of which code path calls `enable_logger()` first.

## Changes

- Always register `TuiTracingSubscriberLayer` in the global tracing
subscriber — it just buffers events with no thread overhead
- Defer `tui_logger::init_logger()` (which spawns the mover thread) to
the TUI lifecycle `__init`, where we know the TUI is actually in use
- Non-TUI contexts still avoid the background thread cost
2026-03-12 14:26:58 -04:00
Louie Weng 0bff3bc20e chore(gradle): ensure that version catalogue changes invalidate gradle hash (#34804)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

We do not track changes to `libs.versions.toml` files

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

Include `libs.versions.toml` files in the list of files that we track
when we hash. This ensures that changes to that file will trigger a
regeneration of the gradle project graph.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-03-12 14:26:44 -04:00
Caleb Ukle b43e693c3d fix(nx-dev): adding missing legacy route redirects (#34772)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 12:53:36 -04:00
Miroslav Jonaš 0e77802bd5 docs(nx-dev): add self-healing to manual DTE docs (#34802)
<!-- 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-03-12 14:50:54 +00:00
Jason Jean 08a2b164e4 chore(repo): update nx to 22.6.0-beta.12 (#34760)
Updating Nx from 22.6.0-beta.10 to 22.6.0-beta.12
2026-03-11 20:53:56 -04:00
Juri 4dee3d3646 docs(nx-dev): add auto-apply suggestions video to self-healing CI page 2026-03-11 20:54:27 +01:00
Leosvel Pérez Espinosa e206a0dc66 chore(repo): do not ignore relevant native files (#34793)
- Remove relevant native files entries from `.nxignore`, causing them to
be missing inputs.
- Remove stale entry from `.nxignore` used to workaround an issue that
was already solved
2026-03-11 18:21:36 +01:00
Caleb Ukle 592ea0602e docs(nx-cloud): add actions read permission to gh permission list (#34786) 2026-03-11 10:52:20 -05:00
MaxKless 003722dd1a chore(repo): switch to new polygraph plugin (#34790)
the mcp & skills have moved.
2026-03-12 00:42:00 +09:00
Jason Jean 4b1f420695 fix(nuxt): bump nuxt to 3.21.1 to resolve critical audit vulnerability (#34783)
## Current Behavior

The npm audit CI job fails with a critical vulnerability
(GHSA-r275-fr43-pm7q) in `simple-git < 3.29.0`, pulled in transitively
via `nuxt@3.17.6` → `@nuxt/devtools@2.6.2` → `simple-git@3.28.0`.

## Expected Behavior

The audit passes. Bumping `nuxt` from `^3.10.0` to `^3.21.1` pulls in
`@nuxt/devtools@3.2.3` → `simple-git@3.33.0`, which includes the fix.

## Related Issue(s)

Fixes the failing audit job:
https://github.com/nrwl/nx/actions/runs/22930179319/job/66549735267
2026-03-11 11:13:41 -04:00
Jason Jean ac400a12db chore(maven): bump maven plugin version to 0.0.15 (#34782)
## Current Behavior

The Maven plugin is at a previous version and needs to be updated.

## Expected Behavior

The Maven plugin version is bumped to `0.0.15`, with a corresponding
migration for users upgrading to Nx `22.6.0-beta.12`.

## Related Issue(s)

N/A
2026-03-11 10:35:33 -04:00
Jason Jean 444ddb5953 feat(maven): report external Maven dependencies in project graph (#34368)
## Current Behavior

The Maven plugin only reports dependencies between workspace projects.
External dependencies (Spring, JUnit, etc.) from Maven Central are
invisible — `nx graph` doesn't show the full dependency picture, and Nx
can't invalidate cache when an external dependency changes.

## Expected Behavior

External Maven dependencies now appear as full-fidelity external nodes
in the Nx project graph, with dependency edges between them, hashes for
cache correctness, and `externalDependencies` inputs on targets.

### External Nodes

External nodes use the naming convention `maven:groupId:artifactId`
(e.g., `maven:org.springframework:spring-core`) with:
- Type `"maven"`
- `groupId` and `artifactId` as separate fields
- Declared version (or `"managed"` if inherited from a parent POM)
- SHA-1 hash read from Maven's `.sha1` sidecar files in
`~/.m2/repository`

### External-to-External Edges

Edges between external nodes are derived by parsing POMs from the local
Maven repository. For example, `spring-boot-starter-web` → `spring-web`
→ `spring-core`. This gives `nx graph` a complete picture of the
transitive dependency tree.

### Cache Invalidation via externalDependencies Inputs

Every cacheable target now includes `{"externalDependencies":
["maven:groupId:artifactId", ...]}` in its inputs. This means Nx
invalidates cache when a resolved artifact changes — important for
SNAPSHOTs and version ranges where the dependency can change without
`pom.xml` changing.

### Changes

**Kotlin (maven-plugin)**
- `NxProjectAnalyzerMojo.kt`: Changed ResolutionScope to COMPILE for
full transitive resolution. Added `generateExternalNodes()` with
deduplication and SHA-1 hash reading. Added `generateExternalEdges()`
via POM parsing from ~/.m2. Embedded external nodes in createNodesResult
tuples, added project-to-external and external-to-external dependency
edges.
- `NxProjectAnalyzer.kt`: Uses `project.artifacts` (transitive) instead
of `project.dependencies` (direct only) for external deps. Added
`artifactFile` field for hash lookup. Collects external dep names and
passes them to target factory.
- `NxTargetFactory.kt`: Accepts externalDependencies list, threads it
through all target creation methods, and adds `{"externalDependencies":
[...]}` to every cacheable target's inputs.

**TypeScript**
- `dependencies.ts`: External sources/targets with `maven:` prefix pass
through as-is instead of rootToProjectMap lookup.
- `types.ts`: Added `externalNodes` field to `MavenAnalysisData`.

## Related Issue(s)

<!-- Feature parity with Gradle plugin for external dependency support
-->
2026-03-11 10:33:48 -04:00
Craigory Coppola 690466a869 fix(core): show json by default for agentic ai (#34780)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
`nx show *` doesn't default to JSON

## Expected Behavior
For agents, it defaults to JSON

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-03-11 10:23:55 -04:00
Leosvel Pérez Espinosa a358b19c15 fix(js): always infer dependentTasksOutputFiles for tsc build targets (#34784)
## Current Behavior

The `@nx/js/typescript` plugin only infers `dependentTasksOutputFiles`
for `tsc --build` targets when external project references are detected.
Otherwise it falls back to `^production`, which tracks dependency source
files.

This is semantically incorrect — `tsc --build` resolves dependencies
through build artifacts (`.d.ts` and `.tsbuildinfo`), never source
files, regardless of reference type (external refs, internal refs, or
ad-hoc task dependencies).

## Expected Behavior

`dependentTasksOutputFiles` is always inferred for `tsc --build` targets
since that's what tsc actually reads. The `^production` fallback is
removed as it was a proxy that's no longer needed.
2026-03-11 10:01:05 -04:00
Jason Jean dcba2bfc6a fix(core): ensure batch tasks always have hash for DTE (#34764)
## Current Behavior

After #34446, batch tasks with `depsOutputs` inputs had their hashing
deferred until after execution. This meant the streaming `endTasks`
callback fired with `task.hash = undefined`, which Cloud/DTE rejects.

## Expected Behavior

All batch tasks always have a valid hash when `endTasks` is called.
Tasks with `depsOutputs` get a preliminary hash upfront (based on
whatever outputs are on disk), then are re-hashed after execution with
fresh outputs for correct cache storage.

### How it works

1. **Phase 1** now hashes ALL root tasks at each level (not just
cache-eligible ones). Ineligible tasks get a preliminary hash so the
streaming callback always has something valid to send.
2. **Phase 2** runs the batch, then clears and re-hashes all tasks that
ran — outputs are fresh on disk, so depsOutputs tasks get correct final
hashes.
3. The re-hash logic is consolidated into a single block after both code
paths (cache-enabled and cache-skipped).

## Related Issue(s)

Fixes the undefined hash regression from #34446

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-03-11 18:51:55 +09:00
Jason Jean 9e5ba5a2cf fix(webpack): cap less version to <4.6.0 to avoid ESM incompatibility (#34781)
## Current Behavior

`@nx/webpack` specifies `"less": "^4.1.3"` as a dependency, which allows
`less@4.6.0` to be installed. However, `less@4.6.0` switched to ESM
(`"type": "module"`), and `less-loader@11.x` uses `require()` to load
it. This causes a runtime error:

```
class WebpackFileManager extends implementation.FileManager {
                                                ^
TypeError: Class extends value undefined is not a constructor or null
```

This breaks any React/webpack project that uses Less stylesheets (e.g.
the "should support global and css modules" e2e test).

## Expected Behavior

The `less` version range is capped to `>=4.1.3 <4.6.0`, preventing the
incompatible ESM-only version from being installed. Less stylesheets
compile correctly with webpack and less-loader.

## Related Issue(s)

N/A - discovered via e2e test failure
2026-03-10 22:10:26 -04:00
Louie Weng 4f67a056e9 chore(gradle): bump gradle project graph plugin version to 0.1.15 (#34779)
## Current Behavior

The gradle project graph plugin version is 0.1.14.

## Expected Behavior

The gradle project graph plugin version is bumped to 0.1.15 with a
corresponding migration.

## Related Issue(s)

N/A - Routine version bump.

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-10 16:56:02 -04:00
Louie Weng 6326b9c38c feat(gradle): add properties and wrappers to inputs (#34778)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
Gradle task inputs in Nx do not includ configuration files like
gradle.properties, gradle/wrapper/gradle-wrapper.jar, and
gradle/wrapper/gradle-wrapper.properties. When these files change, Nx's
cache does not invalidate, potentially causing builds to use stale
cached results despite configuration changes that affect build behavior.

## Expected Behavior
Gradle wrapper and properties files are now automatically included as
inputs for all Gradle tasks when they exist in the workspace. This
ensures that changes to Gradle version (via wrapper files) or build
configuration (via gradle.properties) properly invalidate Nx's task
cache, guaranteeing accurate incremental builds and cache hits.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: lourw <lourw@users.noreply.github.com>
2026-03-10 13:02:35 -07:00
Louie Weng dcd269fb2f fix(gradle): ensure that ci test target depends on take overrides into account (#34777)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior

When generating atomized CI test targets for Gradle projects, the
dependsOn entries do not respect targetNameOverrides or targetNamePrefix
configuration. This means that if a test task depends on other tasks
(like compileTestKotlin or classes), and those tasks have been renamed
via overrides or prefixed (e.g., gradle-compileTestKotlin), the
generated CI test targets reference the original, non-transformed task
names. This creates broken dependencies in the project graph.

## Expected Behavior

Atomized CI test targets now correctly apply both targetNameOverrides
and targetNamePrefix to their dependsOn entries. When a test task
depends on other tasks, the generated CI targets will reference the
properly transformed target names, ensuring dependency integrity
throughout the project graph. The fix passes these configuration
parameters through the entire CI target generation pipeline in
CiTargetsUtils.kt.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: lourw <lourw@users.noreply.github.com>
2026-03-10 15:48:47 -04:00
MaxKless 3945a740f8 fix(core): improve nx wrapper error message for malformed nx.json (#34736)
## Current Behavior

When `nx.json` exists but contains a syntax error (e.g. invalid JSON),
the nx wrapper shows:

```
[NX]: The "nx.json" file is required when running the nx wrapper.
```

This is misleading because the file exists — it's just malformed.

## Expected Behavior

The nx wrapper now distinguishes between a missing `nx.json` and a
malformed one:

- **Missing file**: `[NX]: The "nx.json" file is required when running
the nx wrapper.`
- **Parse error**: `[NX]: Failed to parse "nx.json": <actual error>. See
...`

The existence check is done upfront before attempting to parse, so the
`catch` block only handles parse errors.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 15:44:05 -04:00
Leosvel Pérez Espinosa f9cfd195d6 fix(js): add external project reference config files as inputs for tsc tasks (#34770)
## Current Behavior

The `@nx/js/typescript` plugin's `getInputs()` doesn't include tsconfig
files from external project references as task inputs. When `tsc
--build` follows a project reference to another Nx project (e.g.,
`../ui-common/tsconfig.lib.json`), it reads that tsconfig file, but the
dependency isn't declared. This means changes to an external reference's
tsconfig won't invalidate the dependent task's cache.

## Expected Behavior

External project reference config files (and their extended configs) are
declared as inputs for correct cache invalidation.

## Changes

- Replace `hasExternalProjectReferences` boolean check with
`getExternalProjectReferenceConfigFiles` that collects external ref
config file paths in a single traversal
- Add collected paths as inputs alongside `dependentTasksOutputFiles`
- Remove now-unused `hasExternalProjectReferences` (one traversal
instead of two when external refs exist)
2026-03-10 15:29:33 -04:00
Leosvel Pérez Espinosa 0589e67b7d fix(js): include transitive dep outputs in typecheck inputs (#34773)
## Current Behavior

The `@nx/js/typescript` plugin infers `dependentTasksOutputFiles:
'**/*.{d.ts,tsbuildinfo}'` as inputs for typecheck tasks, but only
tracks `.d.ts` outputs from **direct** dependencies. TypeScript project
references transitively read `.d.ts` files from the full dependency
chain, so when a project typechecks, it reads `.d.ts` outputs from
transitive deps too. These undeclared inputs can cause incorrect cache
hits.

## Expected Behavior

The inferred inputs for typecheck tasks include `.d.ts` and
`.tsbuildinfo` outputs from the entire transitive dependency graph by
setting `transitive: true` on the `dependentTasksOutputFiles` input.
2026-03-10 15:27:33 -04:00
Jason Jean 4593af3ea5 feat(core): persist analytics session ID across CLI invocations (#34763)
## Current Behavior

Each `nx` CLI invocation generates a new random session ID for GA4
analytics. This means GA4 cannot correlate multiple commands from the
same user working session, making active user tracking inaccurate.

## Expected Behavior

Session IDs are persisted in the SQLite metadata table with a 30-minute
timeout. Consecutive `nx` commands within that window share the same
session ID, enabling accurate GA4 active user tracking. After 30 minutes
of inactivity, a new session is automatically created.

## Related Issue(s)

N/A — internal analytics improvement
2026-03-10 09:34:57 -04:00
Chau Tran e7092db540 fix(core): misc graph changes with nx/graph 1.0.5 (#34761) 2026-03-10 18:57:46 +07:00
Jason Jean 556ff2d171 fix(webpack): update e2e snapshot for vitest reportsDirectory change (#34766)
## Current Behavior

The webpack legacy e2e test
(`e2e-webpack:e2e-ci--src/webpack.legacy.test.ts`) fails with a snapshot
mismatch because the `reportsDirectory` value changed from
`../coverage/app3224373` to `coverage/app3224373`.

## Expected Behavior

The snapshot should match the new `reportsDirectory` path format
introduced by #34720.

## Related Issue(s)

Fixes the e2e test breakage introduced by #34720 (`fix(vitest)!: resolve
reportsDirectory against workspace root`).
2026-03-09 22:21:04 +00:00
Colum Ferry 4bb7c76d45 feat(core): add analytics (#34144)
## Current Behavior

Nx CLI has no mechanism for collecting usage analytics, and users are
not prompted about their analytics preferences. There is no way for the
Nx team to understand which commands, generators, and features are most
used.

## Expected Behavior

This PR adds opt-in analytics collection to the Nx CLI with two main
components:

### 1. Analytics Prompt

Users are prompted for their analytics preference on first interactive
CLI run when `analytics` is not yet configured in `nx.json`:

- Only appears when `analytics` is undefined in `nx.json`
- Skipped in CI environments
- Skipped in non-interactive terminals (piped input/output)
- Stores the user's choice as a boolean (`true`/`false`) in the
`analytics` field of `nx.json`
- Defaults to `false` if the user cancels (Ctrl+C)
- Includes a migration (`update-22-6-0/enable-analytics-prompt`) for
existing workspaces

### 2. Analytics Collector

When analytics is enabled, the CLI collects usage data via a Rust-based
telemetry service and sends it to GA4. Data collected includes:

- **Commands run** (e.g. `build`, `test`, `generate`, `add`) — tracked
as page views
- **Command arguments** — with aggressive sanitization of sensitive
values (project names, file paths, URLs, credentials, free-form text are
all redacted; only boolean flags and safe enum values are preserved)
- **Generator and package names** for `nx add` and `nx generate` (as
custom dimensions)
- **Project graph creation duration**
- **Environment metadata**: Nx version, Node version, package manager,
OS, architecture, CI detection

### Workspace Identification

Each workspace is identified by a deterministic ID (used as the GA4
client ID) with the following priority:

1. **`nxCloudId`** (or `nxCloudAccessToken`) from `nx.json` — used
directly, most stable
2. **Git remote URL** (`git remote get-url origin`) — SHA-256 hashed for
privacy
3. **First commit SHA** (`git rev-list --max-parents=0 HEAD`) — used
directly as a fallback

Each user/machine is identified separately via `node-machine-id`.

### Privacy & Safety

- No project names, file paths, or other PII is collected
- Sensitive CLI arguments are redacted (see `SENSITIVE_ARGS_KEYS` list)
- Analytics is strictly opt-in (must be `true` in `nx.json`)
- Telemetry failures are silently ignored — never blocks or crashes the
CLI
- WASM builds are excluded (no native telemetry module available)
- The native telemetry functions are loaded with optional chaining to
prevent crashes when running against published Nx binaries that don't
include them yet

### Key Files

- `packages/nx/src/utils/analytics-prompt.ts` — Prompt logic and
workspace ID generation
- `packages/nx/src/analytics/analytics.ts` — Analytics collector, event
tracking, argument sanitization
- `packages/nx/src/native/telemetry/` — Rust telemetry service
(constants, service, mod)
- `packages/nx/src/utils/machine-id-cache.ts` — Machine ID for user
identification
- `packages/nx/src/migrations/update-22-6-0/enable-analytics-prompt.ts`
— Migration for existing workspaces

## Related Issue(s)

Closes NXC-3731
Closes NXC-3732
Closes NXC-3733
Closes NXC-3734

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Coly010 <Coly010@users.noreply.github.com>
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
2026-03-09 11:33:52 -04:00
Jason Jean b3d1b1a0db chore(gradle): bump gradle project graph plugin version to 0.1.14 (#34752)
https://claude.ai/code/session_01BMRoUBzqhTQL6WrQFiBxnr

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-09 11:27:54 -04:00
Steven Nance 654118adca fix(vitest)!: resolve reportsDirectory against workspace root (#34720)
## Current Behavior

When `reportsDirectory` is configured with `{workspaceRoot}` token in
`nx.json` targetDefaults:

```json
"@nx/vitest:test": {
  "options": {
    "reportsDirectory": "{workspaceRoot}/coverage/{projectRoot}"
  }
}
```

Coverage output lands in the wrong location. For example, with a project
at `apps/my-app`, coverage goes to `apps/my-app/coverage/apps/my-app/`
instead of the intended `coverage/apps/my-app/`.

## Expected Behavior

Coverage output should be written to
`<workspaceRoot>/coverage/apps/my-app/`.

## Root Cause

Nx's `resolveNxTokensInOptions` strips `{workspaceRoot}/` from option
values and replaces `{projectRoot}`, producing a workspace-root-relative
path (e.g. `coverage/apps/my-app`). The vitest executor then passed this
directly to vitest, which resolved it relative to the **project root** —
not the workspace root.

## Fix

Resolve non-absolute `reportsDirectory` paths against the workspace root
before passing them to vitest, so vitest writes coverage to the correct
location.

## Test Plan

- Added unit tests for the new `resolveReportsDirectory` helper
- Verified with a reproduction workspace that coverage now lands at the
correct path
2026-03-09 15:18:31 +00:00
Leosvel Pérez Espinosa 92335c4241 fix(gradle): exclude non-JS gradle sub-projects from eslint plugin (#34735)
## Current Behavior

The `@nx/eslint/plugin` infers a `lint` target for
`gradle-project-graph` because it contains a single `.ts` file
(`publish-maven.ts`), even though it's a Kotlin/Gradle project.
Similarly, the parent `gradle` project's `eslint .` scans into non-JS
sub-project directories unnecessarily.

## Expected Behavior

Non-JS Gradle sub-projects (`project-graph`, `batch-runner`) should not
have lint targets inferred by the ESLint plugin, and the parent `gradle`
project's lint should not scan into those directories.

## Changes

- Add `project-graph` and `batch-runner` to ESLint `ignorePatterns` in
`packages/gradle/.eslintrc.json`

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: leosvelperez <leosvelperez@users.noreply.github.com>
2026-03-09 10:32:38 +00:00
Jason Jean 6f9bfbd7ea fix(gradle): use object format for dependsOn instead of shorthand strings (#34715)
## Current Behavior

The Gradle plugin generates `dependsOn` entries using the shorthand
string format (e.g., `"projectName:taskName"`). This doesn't leverage
the full object syntax that Nx supports.

## Expected Behavior

`dependsOn` entries now use the object format:
- Same-project dependencies: `{ "target": "taskName" }`
- Cross-project dependencies: `{ "target": "taskName", "projects":
["proj1", "proj2"] }` with projects grouped by target name

This is more explicit, consistent with the CI targets code (which
already used object format), and enables the `projects` array for
grouping multiple project dependencies under a single target.

## Related Issue(s)

N/A - internal improvement

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-03-06 22:36:56 +00:00
Leosvel Pérez Espinosa 8f44377d98 fix(js): include tsbuildinfo in dependentTasksOutputFiles for tsc tasks (#34733)
## Current Behavior

The `@nx/js/typescript` plugin sets `dependentTasksOutputFiles:
'**/*.d.ts'` for tsc tasks with external project references. This misses
`.tsbuildinfo` files that `tsc --build` reads from referenced projects
for incremental compilation, which can lead to incorrect cache hits.

## Expected Behavior

`dependentTasksOutputFiles` uses the glob `**/*.{d.ts,tsbuildinfo}`,
ensuring all files read by `tsc --build` from dependencies are tracked
as inputs for correct cache invalidation.
2026-03-06 17:30:36 -05:00
Leosvel Pérez Espinosa 997d6d8953 fix(linter): add catalog: references when fixing missing dependencies (#34734)
## Current Behavior

When the `dependency-checks` ESLint rule auto-fixes missing dependencies
in a project's `package.json`, it resolves the version from the root
`package.json` or falls back to the installed version from the project
graph. This inserts explicit version strings even when the workspace
uses catalogs, breaking same-version policy.

## Expected Behavior

The fixer now checks catalogs before falling back to installed versions.
When exactly one catalog entry for a missing dependency satisfies the
installed version, the fixer inserts `catalog:` (default catalog) or
`catalog:<name>` (named catalog) instead of an explicit version.

Fallback chain: root `package.json` → catalog lookup → installed
version.

Edge cases handled:
- Package in multiple catalogs but only one satisfies → uses that one
- Package in multiple catalogs and multiple satisfy → falls back to
installed version
- `file:` and other protocol-based versions → exact string comparison
instead of semver
- No catalog manager or no catalog definitions → falls back to installed
version

Also caches the catalog manager instance and catalog definitions per
lint run instead of re-creating them per function call.
2026-03-06 17:30:09 -05:00
Jason Jean 043aaee475 fix(core): batch-safe hashing for maven and gradle (#34446)
## Current Behavior

In batch mode (Maven/Gradle), all task hashes are computed upfront in
`processScheduledBatch` before the batch executor runs any tasks. Tasks
with `dependentTasksOutputFiles` (aka `depsOutputs`) get hashed using
whatever dependency outputs happen to be on disk from a previous run.
This leads to:

- **False cache hits**: If a dependency's sources changed but its old
outputs are still on disk, the dependent task's hash matches a stale
cache entry and wrong results are served.
- **False cache misses**: On cold runs with no outputs on disk, the hash
is computed without dependency output content and never matches any
stored cache entry.

Non-batch mode doesn't have this problem because it uses lazy hashing —
tasks with `depsOutputs` are only hashed after their dependencies
complete and fresh outputs exist on disk.

## Expected Behavior

Batch mode hashes tasks topologically — each task is hashed only after
its dependencies have run and their outputs are on disk. This means
hashes are always computed against fresh outputs, eliminating both false
cache hits and false cache misses.

### How it works

`applyFromCacheOrRunBatch` now has two phases:

1. **Topological cache resolution** — Walk the **entire** batch task
graph level by level. At each level, partition root tasks into
cache-eligible vs ineligible. A task is **ineligible** for cache if it
has `depsOutputs` inputs AND any of its dependencies were not cached
(their outputs aren't on disk, so the hash would be wrong). Hash and
check cache for eligible tasks, then remove **all** roots from the graph
to expose the next level — even when some tasks are cache misses. This
ensures the walk continues past cache misses to find deeper cache hits.

2. **Run remaining tasks, then hash** — Rebuild a run graph from all
non-cached task IDs and run them through the batch executor. After the
batch completes, hash all tasks that ran. Since all outputs (including
from sibling batch tasks) are now fresh on disk, tasks with
`depsOutputs` get correct hashes on the first pass — no re-hash needed.

### Task history lifecycle fix

The batch streaming callback calls `endTasks` as tasks finish mid-batch,
but tasks haven't been hashed yet at that point (hash is deferred to
post-execution). Previously, `TaskHistoryLifeCycle` and
`LegacyTaskHistoryLifeCycle` eagerly snapshotted `task.hash` in
`endTasks`, which sent `undefined` to the native Rust layer causing a
"Missing field `hash`" crash.

**Fix:** Both lifecycles now store `TaskResult` references in `endTasks`
and defer building `TaskRun` objects until `endCommand`, when
`task.hash` is guaranteed to be set by the post-batch re-hash. The
streaming callback and `runBatch` return value also now use the original
task object reference (instead of spread copies) so that the hash
mutation from `hashBatchTasks` flows through to all stored references.

### Example: 3 tasks over 3 runs

Consider a batch with three tasks in a linear chain: **A → B → C**

- **Task A** — `lib:compile`. Inputs: source files only. No
`depsOutputs`.
- **Task B** — `app:compile`. Depends on A. Inputs: only `depsOutputs`
from Task A (e.g., `target/classes/**`). No source file inputs.
- **Task C** — `app:checkstyle`. Depends on B. Not cacheable.

Hash notation: `H(inputs…)` means the hash is a function of those
inputs.

---

#### Run 1 — Fresh (no outputs on disk, empty cache)

| Step | What happens |
|------|-------------|
| **Phase 1** | Roots = `[A]` (B depends on A, C depends on B). Hash A →
**H_A** |
| | Check cache: A = miss. A is added to `nonCachedTaskIds`. Remove all
roots. |
| **Phase 1, iter 2** | Roots = `[B]`. B has `depsOutputs` and A is
non-cached → B is **ineligible**. Added to `nonCachedTaskIds`. Remove
all roots. |
| **Phase 1, iter 3** | Roots = `[C]`. C has no `depsOutputs` →
eligible. Hash C → **H_C**. Cache miss. Added to `nonCachedTaskIds`.
Remove all roots. |
| **Phase 2** | Rebuild run graph from `nonCachedTaskIds` = {A, B, C}.
Run batch: all 3 tasks execute. A produces `target/classes/`. |
| | Hash all tasks post-execution: A → **H_A**, B → `H(A_outputs)` =
**H_B**, C → **H_C** |
| **Cache** | Store A as **H_A**, store B as **H_B**. C is not cached. |

> **Key:** B is hashed *after* the batch, when A's outputs already exist
on disk. The hash is correct on the first pass. C was still checked
against cache even though A and B were misses.

---

#### Run 2 — Warm (nothing changed, cache populated from Run 1)

| Step | What happens |
|------|-------------|
| **Phase 1, iter 1** | Roots = `[A]`. Hash A → **H_A** |
| | Check cache: A = **HIT**  → restore `target/classes/` to disk. |
| **Phase 1, iter 2** | Roots = `[B]`. B has `depsOutputs` but A is
cached (not in `nonCachedTaskIds`) → B is **eligible**. Hash B →
`H(A_outputs)` = **H_B** (A's outputs just restored!) |
| | Check cache: B = **HIT**  → restore B's outputs. |
| **Phase 1, iter 3** | Roots = `[C]`. Hash C → **H_C**. Not cacheable →
no hit. Added to `nonCachedTaskIds`. |
| **Phase 2** | Rebuild run graph from `nonCachedTaskIds` = {C}. Run
batch with just C. Hash C post-execution. |

> **Key:** Phase 1 restored A's outputs from cache *before* hashing B.
So B's hash matches Run 1's value → cache hit. The topological walk
peels the chain one level at a time: A → B → C.

---

#### Run 3 — Source changed (A's source modified, stale outputs from Run
2 still on disk)

| Step | What happens |
|------|-------------|
| **Phase 1, iter 1** | Roots = `[A]`. Hash A → `H(A_src')` = **H_A'**
(new hash!) |
| | Check cache: A = **miss** (H_A' not in cache). A added to
`nonCachedTaskIds`. |
| **Phase 1, iter 2** | Roots = `[B]`. B has `depsOutputs` and A is
non-cached → B is **ineligible**. Added to `nonCachedTaskIds`. |
| **Phase 1, iter 3** | Roots = `[C]`. C has no `depsOutputs` →
eligible. Hash C → **H_C**. Cache miss. Added to `nonCachedTaskIds`. |
| **Phase 2** | Run batch: all 3 tasks execute. A produces *new*
outputs. |
| | Hash all tasks post-execution: A → **H_A'**, B → `H(A_new_outputs)`
= **H_B'**, C → **H_C** |
| **Cache** | Store A as **H_A'**, store B as **H_B'**. C is not cached.
|

> **Key:** Because hashing happens after execution, B is always hashed
against A's *fresh* outputs. No stale hash, no re-hash needed. And C is
still checked against cache at every level, even when upstream tasks
miss.

---

#### Summary of hashes across runs

| Task | Run 1 (fresh) | Run 2 (warm) | Run 3 (src changed) |
|------|--------------|-------------|-------------------|
| **A** | miss → cache **H_A** | hit **H_A**  | miss → cache **H_A'** |
| **B** | miss → post-exec hash & cache **H_B** | hit **H_B**  | miss →
post-exec hash & cache **H_B'** |
| **C** | not cacheable → runs | not cacheable → runs | not cacheable →
runs |

### Maven plugin fixes

Several fixes to the Maven plugin to ensure correct batch behavior:

- **Propagate batch runner exit code failures**: Batch runner process
exit codes are now correctly propagated so task failures are reported
properly.
- **Use glob patterns for gitignored dependent task outputs**:
`depsOutputs` patterns like `target/classes` are now resolved using glob
patterns, fixing issues with `.gitignore`d output directories.
- **Fix inputs for `maven:test`**: Test task inputs now correctly
include test source files so hash changes when tests are modified.
- **Include test sources in `testCompile` task hash**: The `testCompile`
target now includes `src/test/java` in its inputs.

## Related Issue(s)

Related to #30949
2026-03-06 17:28:37 -05:00
Jack Hsu e50cc74212 docs(misc): add Vale as automated editor and a Claude skill to ensure style guide is followed (#34744)
This PR makes it much easier for everyone to contribute to our docs.

1. [Vale](https://vale.sh/) is installed via `mise` - This is our
automated editor.
2. Claude skill to invoke Vale and also follow
`astro-docs/STYLE_GUIDE.md` for things that Vale cannot pick up.
3. `CLAUDE.md` instruction to invoke the skill (2) whenever someone is
updating docs.

Demo: https://www.loom.com/share/415a9da056d3483da297fda61f7e7382
2026-03-06 15:39:33 -05:00
Victor Savkin d267145a82 fix(nx-cloud): allow download-cloud-client to work outside nx workspaces (#34746)
Remove the isNxCloudUsed guard that prevented the command from running
without an nx.json. Now gracefully falls back to default cloud URL when
not in an Nx workspace.

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:18:41 -05:00
Jason Jean 9097d9204a chore(repo): update nx to 22.6.0-beta.10 (#34748)
Updating Nx from 22.6.0-beta.9 to 22.6.0-beta.10
2026-03-06 15:00:56 -05:00
Philip Fulcher 18e47b3db9 docs(nx-dev): add siriusxm success story (#34745) 2026-03-06 14:15:40 -05:00
Rares Matei d6359d496c chore(repo): remove explicit tracing directory setting (#34749)
Remove NX_CLOUD_IO_TRACING_DIRECTORY environment variable.

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-03-06 19:02:20 +00:00
Leosvel Pérez Espinosa 60c72ced4f fix(core): add missing @nx/angular-rspack packages to nx packageGroup (#34743)
## Current Behavior

Running `nx migrate latest` updates all `@nx/*` packages except
`@nx/angular-rspack` and `@nx/angular-rspack-compiler`.

## Expected Behavior

`nx migrate latest` updates `@nx/angular-rspack` and
`@nx/angular-rspack-compiler` along with all other `@nx/*` packages.

## Related Issue(s)

Fixes #32772
2026-03-06 12:53:16 -05:00
Steven Nance 5359ac2792 fix(js): derive tsbuildinfo filename from iterated tsconfig, not outer config (#34738)
## Current Behavior

The `getOutputs()` function in the `@nx/js/typescript` plugin derives
`.tsbuildinfo` filenames from `config.basenameNoExt` (the outer
`ConfigContext`, which always corresponds to `tsconfig.json`) instead of
the currently iterated internal project reference. This causes all
internal references to produce `dist/tsconfig.tsbuildinfo` as the output
path instead of the correct filenames like
`dist/tsconfig.lib.tsbuildinfo` and `dist/tsconfig.spec.tsbuildinfo`.

This leads to:
- Cache misses because Nx looks for `dist/tsconfig.tsbuildinfo` which
doesn't exist
- Missing actual `.tsbuildinfo` files from cache outputs
- Potential race conditions in parallel `tsc --build` invocations

## Expected Behavior

The `.tsbuildinfo` filename should be derived from each individual
tsconfig's file path. For a project with:
- `tsconfig.lib.json` → `dist/tsconfig.lib.tsbuildinfo`
- `tsconfig.spec.json` → `dist/tsconfig.spec.tsbuildinfo`
- `cypress/tsconfig.json` → `cypress/dist/tsconfig.tsbuildinfo`

The fix changes the loop in `getOutputs()` to iterate over entries (path
+ data pairs) so the basename can be correctly derived from each
tsconfig's file path. Also fixes the `outFile` and no-outDir branches
which had similar issues using the outer config parameter instead of the
loop variable.

## Related Issue(s)

Fixes #34737
2026-03-06 17:39:18 +00:00
Victor Savkin 2cd3d58632 chore(repo): remove enterprise nx plugin (#34729)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: vsavkin <vsavkin@users.noreply.github.com>
2026-03-06 10:32:54 -05:00
Juri 23192694d2 docs(repo): refresh repository README
Rewrite README to reflect Nx's current capabilities: zero-config caching,
polyglot plugin system, CI distribution, AI-native tooling, and self-healing CI.
Remove stale badges (Gitter, Semantic Release). Point courses banner to nx.dev/courses.
2026-03-06 16:27:29 +01:00
Leosvel Pérez Espinosa fb8884e967 fix(vitest): handle zoneless Angular apps in vitest configuration generator (#34700)
## Current Behavior

When running `@nx/vitest:configuration` on an Angular project, the
generator always generates `import
'@analogjs/vitest-angular/setup-zone'` regardless of whether the app is
zoneless. Additionally, the setup file generation logic for Angular 21+
(which uses `setupTestBed()`) only exists in
`packages/angular/src/generators/utils/add-vitest.ts`
(`createAnalogSetupFile`), making the vitest generator not
self-contained.

## Expected Behavior

The vitest configuration generator should:
- Auto-detect whether an Angular project is zoneless (by checking
polyfills for apps, or zone.js dependency for libraries)
- Generate `setup-snapshots` instead of `setup-zone` for zoneless
projects
- Handle Angular 21+ `setupTestBed()` setup directly, without requiring
a separate function in the angular package
- Accept an explicit `zoneless` option to override auto-detection

## Related Issue(s)

Fixes #33983
2026-03-06 12:37:39 +01:00
Leosvel Pérez Espinosa 42f623e2c1 fix(vite): skip root-relative paths in nxViteTsPaths resolveId (#34694)
## Current Behavior

The `resolveId` hook in `nxViteTsPaths` processes all import paths,
including Vite root-relative paths (e.g. `/src/test-setup.ts`). In Vite,
`/foo` means "relative to project root," but `nxViteTsPaths` resolves it
via tsconfig's `baseUrl` (workspace root), producing wrong paths.

In an Angular standalone workspace with Vitest + Analog, generating a
library and running `nx test test-lib` fails with `Error: Need to call
TestBed.initTestEnvironment() first` because the library's
`src/test-setup.ts` resolves to the root app's file instead. The Analog
Angular compiler never compiled that file, so the transform returns
empty content and `setupTestBed()` never runs.

## Expected Behavior

`nxViteTsPaths` should skip `/`-prefixed paths and let Vite's built-in
resolver handle them. These are filesystem paths (absolute or
root-relative), not TypeScript import specifiers. Library tests should
work correctly with their own `test-setup.ts`.

## Related Issue(s)

Fixes #34300
2026-03-06 12:37:23 +01:00
Leosvel Pérez Espinosa 8bc7234dd5 feat(js): support configurable typecheck config name (#34675)
## Current Behavior

The TypeScript plugin's typecheck target inference was tied to
`tsconfig.json`, so users could not configure a different tsconfig file
name for typecheck inference.

## Expected Behavior

The plugin supports configuring `typecheck.configName`, which defaults
to `tsconfig.json`.
2026-03-06 11:46:51 +01:00
Victor Savkin 035e061ea2 fix(core): allow nx cloud commands to run outside of a workspace (#34728)
## Current Behavior

Running Nx Cloud commands (login, logout, polygraph, etc.) from outside
an Nx workspace fails with 'The current directory is not part of an Nx
workspace' because handleNoWorkspace exits before reaching the cloud
command handler. Additionally, polygraph was not in the isNxCloudCommand
list.

## Expected Behavior

Nx Cloud commands work regardless of whether you are inside an Nx
workspace, since they only delegate to the cloud client.

## Related Issue(s)

N/A

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 03:18:38 +00:00
Craigory Coppola 7b3598532c feat(core): add safe plugin cache write utilities with LRU eviction (#34503)
## Current Behavior

Plugin cache writes (`writeJsonFile`, `writeFileSync`) across the
codebase have inconsistent error handling:
- Some throw on failure, aborting project graph calculation entirely
- Some silently swallow errors, leaving corrupted cache files on disk
- No mechanism exists to recover from oversized or corrupted caches
- `nx-deps-cache.ts` retries 5 times then throws, crashing the graph

## Expected Behavior

- Plugin cache write failures **never** abort graph calculation — they
warn and continue
- A centralized `safeWritePluginCache` utility handles all hash-map
plugin caches with a 3-step strategy:
1. Attempt full write
2. On failure: evict oldest 50% of entries (LRU) and retry
3. On second failure: wipe cache file, log warning, return without
throwing
- `PluginCache<T>` class wraps cache data with a Proxy that
transparently tracks access order, enabling true LRU
eviction (not just insertion order)
- Access order is stored as a simple `string[]` array — front is oldest,
back is most recent
- Backward-compatible with 3 on-disk formats: legacy plain `Record`,
timestamp-based `{ entries, accessedAt }`, and
current `{ entries, accessOrder }`
- All plugin cache consumers migrated: package-json, js/lockfile,
dotnet, cypress, playwright, gradle, maven
- `nx-deps-cache.ts` changed from throw-after-retries to warn + cleanup
- New utilities exported via `@nx/devkit` internals for plugin authors

## Related Issue(s)

Fixes NXC-3833"

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
2026-03-05 20:56:21 -05:00
Jack Hsu cb6452ce93 fix(misc): address security CVE cluster (copy-webpack-plugin, koa, minimatch) (#34708)
## Current Behavior

Several security-related issues reported:

1. **copy-webpack-plugin** (#34632): `@nx/webpack` and `@nx/next` pin
`copy-webpack-plugin@^10.2.4` which transitively depends on
`serialize-javascript@^6.0.1` (vulnerable) and `fast-glob` (supply-chain
risk).

2. **koa** (#34621): `@nx/module-federation` transitively pulls
`koa@3.0.3` via `@module-federation/dts-plugin`, which is vulnerable to
CVE-2026-27959 (Host Header Injection, fixed in koa 3.1.2).

3. **css-minimizer-webpack-plugin**: `@nx/webpack` pins `^5.0.0` which
also depends on vulnerable `serialize-javascript@^6.0.1`.

4. **@module-federation/enhanced**: Versions `<2.1.0` transitively
install vulnerable `koa` via `dts-plugin`.

5. **Next.js**: Versions `16.0.x` are vulnerable to GHSA-9g9p-9gw9-jx7f
(Image Optimizer DoS) and GHSA-5f7q-jpqc-wp7h (PPR Resume memory
consumption).

6. **minimatch** (#34701): User reports minimatch vulnerability, but the
Nx pnpm catalog already pins the patched version `10.2.4`. No code
change needed — users should delete their lockfile and reinstall.

## Expected Behavior

1. **copy-webpack-plugin** bumped to `^14.0.0` which uses
`serialize-javascript@^7.0.3` (patched). Added `noErrorOnMissing: true`
to all 3 copy-webpack-plugin usage sites to handle the v14 breaking
change where missing glob patterns now throw errors by default.

2. **css-minimizer-webpack-plugin** bumped to `^8.0.0` which uses
`serialize-javascript@^7.0.3` (patched).

3. **koa** bumped to `^3.1.2` in `@nx/node` versions.ts.
`@module-federation/dts-plugin@2.1.0` completely removes koa dependency.

4. **@module-federation/enhanced**, **runtime**, **sdk** bumped to
`^2.1.0` across all packages (`@nx/module-federation`, `@nx/react`,
`@nx/angular`, `@nx/rspack`). Added `noErrorOnMissing` fix for
`@module-federation/enhanced` 2.x `runtime-library-control.plugin.ts`
compatibility.

5. **Next.js** bumped to `~16.1.6` and `eslint-config-next` to
`^16.1.6`.

6. **minimatch** — no change needed, already resolved.

### Migrations added (22.6.0-beta.10)
- `@nx/module-federation`: Bump MF packages to `^2.1.0`
- `@nx/react`: Bump `@module-federation/enhanced` to `^2.1.0`
- `@nx/angular`: Bump `@module-federation/enhanced` to `^2.1.0`
- `@nx/node`: Bump `koa` to `^3.1.2`
- `@nx/next`: Bump `next` to `~16.1.6`

### Skipped
- **esbuild** (`<=0.24.2`, moderate severity, dev server only): Fix
requires breaking change jump from `^0.19.2` to `0.25+`. Will address
separately.

## Testing

Created a fresh Nx workspace with all affected plugins to verify `npm
audit` is clean after changes:

- `@nx/next` (nextapp)
- `@nx/webpack` (webpackapp)
- `@nx/rspack` (shell, remote1, remote2 via Module Federation)
- `@nx/module-federation` (shell + remotes with MF config)
- `@nx/react` (MF host/remotes)
- `@nx/node` + koa (api)

```
├── apps
│   ├── api                  # @nx/node (koa)
│   ├── nextapp              # @nx/next
│   ├── remote1              # @nx/react + rspack + MF
│   ├── remote2              # @nx/react + rspack + MF
│   ├── shell                # @nx/react + rspack + MF (host)
│   └── webpackapp           # @nx/webpack
```

Post-change audit result — only remaining issue is esbuild (moderate,
skipped intentionally):

```
# npm audit report

esbuild  <=0.24.2
Severity: moderate
esbuild enables any website to send any requests to the development server
and read the response - https://github.com/advisories/GHSA-67mh-4wv8-2f99
fix available via `npm audit fix --force`
Will install esbuild@0.27.3, which is a breaking change

1 moderate severity vulnerability
```

## Related Issue(s)

Fixes #34632
Fixes #34621
Fixes #34701
2026-03-05 17:56:19 -05:00
Jack Hsu 9b4e9ff16e chore(misc): add .netlify to gitignore instead of nxignore (#34727)
This addresses feedback on PR #34726 - .netlify directories are build
artifacts and should be in .gitignore rather than .nxignore. This also
generalizes the pattern from `astro-docs/.netlify` to `.netlify` to
cover the root-level `.netlify/static/documentation` directory that was
causing duplicate project detection.

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-05 17:43:56 -05:00
Victor Savkin e31ac44e77 feat(core): add polygraph command to initiaze cross-repo sessions (#34722)
<!-- 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-03-05 22:24:42 +00:00
Jack Hsu b316ec1ff9 fix(core): surface clearer error when CNW hits SANDBOX_FAILED (#34724)
This PR surfaces install errors instead of silently failing without
anything useful printed. It also records `needs_input` in the AX flow as
`cancelled` so we account for cases where AI agents exit without
recording either success, complete, or cancelled.

BEFORE:

<img width="1288" height="381" alt="image"
src="https://github.com/user-attachments/assets/44fe9642-764e-452b-90be-f30c4a230be5"
/>

AFTER:

<img width="1279" height="850" alt="Screenshot 2026-03-05 at 12 30
25 PM"
src="https://github.com/user-attachments/assets/24c96584-2790-4dcb-8404-7e221c50876c"
/>

## Notes

1. **Remove `--silent` from all PM install commands** — since
`execAndWait` uses `exec()` (captures output in memory, never shown to
terminal), `--silent` just suppressed error info for no benefit
2. **Increase `maxBuffer`** from default 1MB to 10MB to prevent process
being killed when PMs emit verbose output
3. **Fallback error message** when both stderr and stdout are empty —
includes exit code and log file path
4. **Structured sandbox error** with exit code, log file, and actionable
hint
5. **Record telemetry stat** for AI agent `needs_input` flow (was
previously missing)
6. **Migrate from deprecated `CreateNxWorkspaceError`** to `CnwError` in
`execAndWait`

## Related Issue(s)

Fixes NXC-4035
2026-03-05 13:42:12 -05:00
Juri Strumpflohner a3c5f2716b feat(nx-dev): add YouTube channel callout to courses page (#34669)
## Current Behavior

[The courses
page](https://deploy-preview-34669--nx-dev.netlify.app/courses) has a
hero section with title and subtitle but no mention of the YouTube
channel for one-off educational videos.

## Expected Behavior

A subtle callout link with a YouTube icon appears below the hero
subtitle, directing users to the Nx YouTube channel for one-off
educational videos.

## Related Issue(s)

N/A
2026-03-05 16:31:46 +00:00
MaxKless 67ecf04773 docs(misc): add blog post on making Nx agent-ready (#34678)
## Current Behavior

No blog post covering the AX principles behind recent Nx CLI
improvements.

## Expected Behavior

New draft blog post (`docs/blog/2026-03-05-making-nx-agent-ready.md`)
covering:
- Why agentic experience (AX) matters for developer tools
- AX principles: context management, structured feedback, idempotency,
informative output
- The open question of human vs agent experience divergence
- A forward look at `nx connect` going agentic

The post is set to `draft: true` and scheduled for Thursday March 5th.

## Related Issue(s)

N/A — new content

---

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Juri <juri.strumpflohner@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: MaxKless <MaxKless@users.noreply.github.com>
2026-03-05 16:43:02 +01:00
Juri Strumpflohner bc45b92037 fix(core): add .claude/worktrees to gitignore (#34693)
## Current Behavior

When Claude Code creates worktrees under `.claude/worktrees/`, Nx picks
them up as workspace projects. This causes duplicate project errors that
break the project graph.

## Expected Behavior

`.claude/worktrees` is gitignored so worktree copies of the repo don't
interfere with Nx's project detection.
2026-03-05 15:08:17 +00:00
Leosvel Pérez Espinosa a5555d6f77 fix(core): prevent TUI panic when Nx Console is connected (#34718)
## Current Behavior

After the napi v2→v3 migration, running tasks with TUI enabled while Nx
Console is connected causes a panic: `there is no reactor running, must
be called from the context of a Tokio 1.x runtime`. This happens because
`end_command` is a sync NAPI callback that calls `end_running_tasks()`
which uses `tokio::spawn`, but napi v3 no longer wraps sync callbacks
with Tokio runtime context by default.

## Expected Behavior

Tasks complete without panic when Nx Console is connected and TUI is
enabled.
2026-03-05 07:44:57 -05:00
Jason Jean ce559a1714 chore(repo): update nx to 22.6.0-beta.9 (#34712)
Updating Nx from 22.6.0-beta.8 to 22.6.0-beta.9
2026-03-05 09:00:27 +01:00
Louie Weng a9678b2180 chore(repo): bump version to 22.6.0-beta.8 (#34706)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

Bump Nx version to 22.6.0-beta.8

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-03-04 20:47:16 +00:00
Leosvel Pérez Espinosa 320bfe5216 fix(js): normalize paths to posix format in typescript plugin (#34702)
## Current Behavior

On Windows, the `@nx/js` TypeScript plugin fails during project graph
creation with a path mismatch assertion error. The plugin uses
`path.join()` and `path.relative()` from Node's `path` module, which
produce backslash-separated paths on Windows. These are passed to
TypeScript's `readConfigFile` API, which has an internal inconsistency:
its parser normalizes paths to forward slashes for diagnostics but
retains the original (backslash) path on the source file, causing an
assertion failure when parse diagnostics are present.

Additionally, `posix.normalize()` was incorrectly relied upon to convert
backslashes to forward slashes — it only normalizes paths already in
POSIX format.

## Expected Behavior

The TypeScript plugin normalizes paths to POSIX format (forward slashes)
for TypeScript API compatibility and cache key consistency, working
correctly on both Windows and Unix systems.

## Related Issue(s)

Fixes #31232
2026-03-04 15:12:50 -05:00
Jack Hsu e11be5d0b0 docs(misc): clean up outdated version references (#34707)
## Current Behavior

Documentation contains version-conditional content and version
qualifiers referencing Nx versions older than 20 (e.g. "Nx 18+" / "Nx <
18" tabs, "Starting from Nx 11", "Since Nx 16", "Prior to Nx 18"). These
are no longer useful since the current version is 22.

## Expected Behavior

Remove conditional content gated on versions < 20. Keep only the modern
code path as the default. Also apply style guide fixes to all touched
pages (fix typos, remove self-referential language, replace
non-descriptive link text, remove AI-style phrases).

### Changes across 11 files:

**Version-conditional tabs removed:**
-
[`react-native/introduction`](https://deploy-preview-34707--nx-docs.netlify.app/docs/technologies/react/react-native/introduction)
— Removed "Nx < 18" tab, kept `nx add` as default
-
[`cypress/introduction`](https://deploy-preview-34707--nx-docs.netlify.app/docs/technologies/test-tools/cypress/introduction)
— Same

**Outdated version qualifiers removed:**
-
[`next-config-setup`](https://deploy-preview-34707--nx-docs.netlify.app/docs/technologies/react/next/guides/next-config-setup)
— Removed "Nx 15 and prior" section, dropped "Nx 16" qualifier
-
[`deploy-nextjs-to-vercel`](https://deploy-preview-34707--nx-docs.netlify.app/docs/technologies/react/guides/deploy-nextjs-to-vercel)
— Removed "Starting from Nx 11"
-
[`faster-builds-with-module-federation`](https://deploy-preview-34707--nx-docs.netlify.app/docs/technologies/module-federation/concepts/faster-builds-with-module-federation)
— Removed "Starting in Nx 14"
-
[`webpack-plugins`](https://deploy-preview-34707--nx-docs.netlify.app/docs/technologies/build-tools/webpack/guides/webpack-plugins)
— Removed "Prior to Nx 18" references
-
[`webpack-config-setup`](https://deploy-preview-34707--nx-docs.netlify.app/docs/technologies/build-tools/webpack/guides/webpack-config-setup)
— Removed "introduced in Nx 18"
-
[`use-environment-variables-in-react`](https://deploy-preview-34707--nx-docs.netlify.app/docs/technologies/react/guides/use-environment-variables-in-react)
— Removed "with the release of Nx 19"
-
[`environment-variables`](https://deploy-preview-34707--nx-docs.netlify.app/docs/reference/environment-variables)
— Removed "Workspaces created before Nx 18"
-
[`configure-inputs`](https://deploy-preview-34707--nx-docs.netlify.app/docs/guides/tasks--caching/configure-inputs)
— Removed "As of Nx 18"
-
[`configure-outputs`](https://deploy-preview-34707--nx-docs.netlify.app/docs/guides/tasks--caching/configure-outputs)
— Removed "As of Nx 18"

**Style guide fixes:** Fixed typos, removed self-referential language,
replaced non-descriptive link text ("here", "this page"), removed
AI-style phrases ("seamless", "comprehensive", "It's important to
note"), added missing Oxford comma.

## Related Issue(s)

Fixes DOC-437
2026-03-04 14:58:05 -05:00
Leosvel Pérez Espinosa f60f1c6b85 fix(angular): preserve skipLibCheck in tsconfig.json for standalone projects (#34695)
## Current Behavior

When creating a new Angular standalone project with Vitest
(`create-nx-workspace` → Angular → Standalone), running tests fails
with:

```
✘ [ERROR] TS2304: Cannot find name 'Disposable'. [plugin angular-compiler]

    node_modules/@vitest/spy/dist/index.d.ts:158:80:
      158 │ ...tends Procedure | Constructable = Procedure> extends Disposable {
```

For standalone (root) projects, `getRootTsConfigFileName` returns
`tsconfig.json` — the same file as the project tsconfig.
`getNeededCompilerOptionOverrides` compares the file against itself,
sees `skipLibCheck: true` already matches, and strips it. The result:
`skipLibCheck` disappears from the final `tsconfig.json`.

## Expected Behavior

Standalone Angular projects should have `skipLibCheck: true` in their
`tsconfig.json` (matching what Angular CLI generates), preventing type
errors from third-party `.d.ts` files like `@vitest/spy`.

## Related Issue(s)

Fixes #34164
2026-03-04 13:41:05 -05:00
Leosvel Pérez Espinosa feb8bd4b19 fix(js): strip catalogs from pruned pnpm lockfile (#34697)
## Current Behavior

When using `generatePackageJson: true`, Nx generates a pruned
`pnpm-lock.yaml` that includes the `catalogs:` section from the root
lockfile. Since the dist folder doesn't include `pnpm-workspace.yaml`,
pnpm 10.24.0+ throws `ERR_PNPM_LOCKFILE_CONFIG_MISMATCH` during `pnpm
install --frozen-lockfile`.

## Expected Behavior

The pruned lockfile strips the `catalogs` section since the dist folder
has no `pnpm-workspace.yaml` to define them.

## Related Issue(s)

Fixes #34337

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-04 13:40:10 -05:00
Rares Matei b05c565331 chore(repo): use absolute path for sandboxing signal files (#34704)
<!-- 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-03-04 13:38:06 -05:00
Copilot 32319ed11b fix(core): support tuple validation when schema items is an array (JSON Schema draft 07) (#34636)
`validateProperty` in `params.ts` did not handle the case where `items`
is an array — the JSON Schema draft 07 tuple validation form. This
caused false validation failures for schemas like Angular's
`@angular/build:unit-test`, which uses tuple `items` to type reporter
configurations:

```json
{
  "type": "array",
  "minItems": 1,
  "maxItems": 2,
  "items": [
    { "anyOf": [{ "type": "string" }, { "enum": ["junit", "html", ...] }] },
    { "type": "object" }
  ]
}
```

Options like `"reporters": [["junit", {"suiteName": "MyApp"}]]` would
incorrectly fail validation with _"Property 'reporters' does not match
the schema"_.

## Changes

- **`validateProperty`**: when `schema.items` is an array, validates
each element against its positional schema instead of passing the whole
array as a schema
- **`minItems`/`maxItems` enforcement**: array length is now validated
against `minItems` and `maxItems` constraints when present
- **`additionalItems` support**: rejects extra items when
`additionalItems: false`; validates against the `additionalItems` schema
when present; otherwise allows additional items (spec-compliant default)
- **`PropertyDescription` type**: added `minItems`, `maxItems`,
`additionalItems` fields; narrowed `items` from `any` to
`PropertyDescription | PropertyDescription[]`
- **Type guards**: added guards in `coerceType` and
`getPromptsForSchema` where `items.enum` was accessed without accounting
for the array form

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>Schema validation fails when `items` is a
list.</issue_title>
> <issue_description>### Current Behavior
> 
> In JSON Schema draft 07 (2018), the `"items"` property can be a list
and must be validated using tuple validation.
> 
> The `validateProperty` util in `packages/nx/src/utils/params.ts`
currently does not support this, causing validation to fail with the
error that the property does not match the schema (but it does).
> 
> ### Expected Behavior
> 
> A schema using JSON Schema draft 07 like
https://github.com/angular/angular-cli/blob/15794dc101fffd49818545c4ab015a3bf238b14a/packages/angular/build/src/builders/unit-test/schema.json
must be successfully parsed by the NX parser.
> 
> OR
> 
> The parser must give a clear error message that it does not support
the given schema.
> 
> ### GitHub Repo
> 
> https://github.com/Ionaru/schema7fail
> 
> ### Steps to Reproduce
> 
> In https://github.com/Ionaru/schema7fail:
> 
> 1. Run `npm install`
> 2. Run `nx test my-app`
> 
> ---
> 
> Clean repro:
>  
> 1. Create an NX project with an Angular application
> 2. In `project.json` add:
> ```json
>     "test": {
>       "executor": "@angular/build:unit-test",
>       "options": {
>         "reporters": [["junit", {"suiteName": "MyApp"}]]
>       }
>     }
> ```
> 3. Run `nx test <app_name>`
> 
> ### Nx Report
> 
> ```shell
> Node           : 24.14.0
> OS             : win32-x64
> Native Target  : x86_64-windows
> npm            : 11.11.0
> 
> nx                     : 22.3.3
> @nx/js                 : 22.3.3
> @nx/eslint             : 22.3.3
> @nx/workspace          : 22.3.3
> @nx/angular            : 22.3.3
> @nx/devkit             : 22.3.3
> @nx/eslint-plugin      : 22.3.3
> @nx/module-federation  : 22.3.3
> @nx/playwright         : 22.3.3
> @nx/plugin             : 22.3.3
> @nx/rspack             : 22.3.3
> @nx/vite               : 22.3.3
> @nx/vitest             : 22.3.3
> @nx/web                : 22.3.3
> @nx/webpack            : 22.3.3
> typescript             : 5.9.3
> ```
> 
> ### Failure Logs
> 
> ```shell
> NX   Property 'reporters' does not match the schema.
> {
>   "oneOf": [
>     {
>       "anyOf": [
>         {
>           "type": "string"
>         },
>         {
>           "enum": [
>             "default",
>             "verbose",
>             "dots",
>             "json",
>             "junit",
>             "tap",
>             "tap-flat",
>             "html"
>           ]
>         }
>       ]
>     },
>     {
>       "type": "array",
>       "minItems": 1,
>       "maxItems": 2,
>       "items": [
>         {
>           "anyOf": [
>             {
>               "type": "string"
>             },
>             {
>               "enum": [
>                 "default",
>                 "verbose",
>                 "dots",
>                 "json",
>                 "junit",
>                 "tap",
>                 "tap-flat",
>                 "html"
>               ]
>             }
>           ]
>         },
>         {
>           "type": "object"
>         }
>       ]
>     }
>   ]
> }'
> ```
> 
> ### Package Manager Version
> 
> _No response_
> 
> ### Operating System
> 
> - [x] macOS
> - [x] Linux
> - [x] Windows
> - [ ] Other (Please specify)
> 
> ### Additional Information
> 
> Linked to
https://github.com/angular/angular-cli/issues/32618</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes nrwl/nx#34631

<!-- START COPILOT CODING AGENT TIPS -->
---

 Let Copilot coding agent [set things up for
you](https://github.com/nrwl/nx/issues/new?title=+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AgentEnder <6933928+AgentEnder@users.noreply.github.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Copilot <Copilot@users.noreply.github.com>
2026-03-04 13:37:32 -05:00
Jason Jean db97a6897d fix(core): enable output prefixing for direct nx:run-commands path (#34670)
## Current Behavior

When `NX_PREFIX_OUTPUT=true` (e.g. `--output-style=stream`),
`nx:run-commands` tasks are forced out of the fast direct execution path
and into a slower forked process, because the direct path had no
mechanism to prefix output with project names.

## Expected Behavior

`nx:run-commands` tasks stay on the direct execution path even when
output prefixing is needed. The PTY's `quiet` mode suppresses direct
stdout writes, and the orchestrator intercepts output via `onOutput`
callbacks to prefix each line with the colored project name before
writing to stdout.

### Changes

- Allow `nx:run-commands` to use the direct execution path regardless of
prefix output setting
- When prefixing is enabled, suppress direct stream output and intercept
it via `onOutput` to add colored project-name prefixes
- Extract a shared `writePrefixedLines` utility used by both the direct
path and the existing `addPrefixTransformer` stream — eliminates
duplicated split/filter/prefix logic
- Use `os.EOL` instead of manual platform newline detection
- Hoist formatted prefix string outside the per-line callback for
efficiency
- Simplify boolean expressions (`streamOutput && !shouldPrefix` instead
of ternary, remove redundant guard)

## Related Issue(s)

N/A — performance improvement for streaming output mode.
2026-03-04 13:35:02 -05:00
Jack Hsu d61170c98d fix(misc): exclude .netlify paths from Framer proxy edge function (#34703)
## Current Behavior
Requests to `/.netlify/images?url=...` are intercepted by the Framer
proxy edge function and forwarded to Framer, returning broken images on
docs pages (e.g. sandboxing page).

## Expected Behavior
`/.netlify/*` requests pass through to Next.js, which rewrites them to
the astro-docs site where the actual images are hosted.

## Related Issue(s)
Fixes DOC-436
2026-03-04 12:57:17 -05:00
Jack Hsu bdfef86fb7 docs(core): add task sandboxing documentation (#34686)
This PR adds a new feature page for sandboxing.

- What task sandboxing is (hermetic task execution with IO tracing)
- Why hermeticity matters for caching correctness, with concrete Vite
examples
- How to investigate sandbox violations in the Nx Cloud UI (with
annotated screenshots)
- How to inspect declared inputs/outputs with `nx show target` and `nx
show project`
- How to enable sandboxing (`NX_CLOUD_IO_TRACING_DIRECTORY`) and
configure path exclusions

The page is listed under Orchestration & CI in the sidebar.

Preview:
https://deploy-preview-34686--nx-docs.netlify.app/docs/features/ci-features/sandboxing

<img width="396" height="658" alt="image"
src="https://github.com/user-attachments/assets/96dc802a-eb1a-4b4a-9df4-bde846ed2775"
/>


## Related Issue(s)

Closes DOC-429

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-03-04 16:28:26 +00:00
Jason Jean 4ce62bdc29 chore(repo): add sandbox exclusions for writes and reads (#34699)
## Current Behavior

The sandboxing config only excludes reads for `node_modules`, `.nx`,
`.git`, and `package.json` paths. No write exclusions are configured,
and `pnpm-workspace.yaml` is not excluded from reads.

## Expected Behavior

- The `nx/**/*` directory is excluded from write sandboxing, allowing
write operations to the nx directory during CI workflows.
- `pnpm-workspace.yaml` is excluded from read sandboxing, similar to
other workspace config files.

## Related Issue(s)

N/A
2026-03-04 10:20:30 -05:00
Jack Hsu dd1c729208 fix(core): restore CNW user flow to match v22.1.3 (#34671)
This PR brings our CNW experience back to previous state.

## Current Behavior

The CNW prompt flow diverges from v22.1.3 in several ways:
- Shows "Which starter do you want to use?" template prompt
- Cloud prompt says "Try the full Nx platform?" instead of caching
question
- Preset flow uses simplified cloud prompt instead of CI provider →
caching fallback
- Completion message shows box banner with `accessToken=undefined`
manual URL
- Setup message includes `github.com/new` link not present in v22.1.3

## Expected Behavior

Human-visible CNW flow identical to v22.1.3 while preserving:
- NDJSON AI output (agentic experience)
- `--template` flag (works via CLI, not surfaced in prompts)
- Telemetry, error handling, AI agent detection

Changes:
- Skip template prompt, go straight to preset/stack flow
- Restore "Would you like remote caching?" with v22.1.3 wording
- Restore CI provider → caching fallback prompt chain for preset flow
- Pass actual nxCloud to createEmptyWorkspace so cloud token is real
- Restore v22.1.3 completion message ("Your remote cache is almost
complete.")
- Restore v22.1.3 getNxCloudInfo signature with rawNxCloud URL
visibility
- Restore "Nx Cloud has been set up successfully" spinner text
- Remove github.com/new link from push message

## Related Issue(s)

Closes NXC-4020
2026-03-04 09:44:52 -05:00
Leosvel Pérez Espinosa 556fd83fe4 cleanup(core): remove stale tui snapshot (#34696)
Removes stale test snapshot diff for the TUI.
2026-03-04 14:13:02 +00:00
MaxKless 4377c8b00d fix(core): fall back to invoking PM in detection (#34691)
## Current Behavior
when you run things like `pnpm nx@latest init`, there might not be a
pnpm lockfile yet. So nx commands will detect the PM as `npm` by
default... even though the fact that the user is invoking the command
via `pnpm` is a strong signal that that's the PM they want to use.

## Expected Behavior
We fall back to detecting the invoking PM via env var if no lockfile
exists.
2026-03-04 19:29:13 +09:00
MaxKless 4b4da78800 feat(core): add Codex subagent support to configure-ai-agents (#34553)
## Summary
- Read generated `config.toml` from `nx-ai-agents-config` repo as single
source of truth for Codex config format
- Deep-merge into user's existing `.codex/config.toml` using
`@ltd/j-toml` (already in monorepo)
- Adjust MCP args dynamically for Nx version (`["nx", "mcp"]` for ≥22,
`["nx-mcp"]` for <22), preserving extra user args
- Respect `multi_agent = false` if explicitly set by user
- Copy `.codex/agents/` subagent TOML files alongside existing
`.agents/skills/`
- Add unit tests (7) and e2e tests (4) for the new functionality

## Test plan
- [ ] Unit tests: `nx test nx -- --testPathPatterns set-up-ai-agents`
- [ ] E2e tests: `nx e2e e2e-nx -- --testPathPatterns
configure-ai-agents` (codex agent describe block)
- [ ] Verify codex config merges correctly with existing user config
- [ ] Verify `multi_agent = false` is not overwritten on re-run

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 11:01:01 +01:00
Jason Jean a23eaa690e fix(testing): remove stale ci.yml from extras.test snapshot (#34690)
## Current Behavior

The `extras.test.ts` e2e test is flaking on master because the snapshot
file expects `.github/workflows/ci.yml` in the "general" task inputs,
but `create-nx-workspace --no-interactive` no longer generates it.

PR #34332 accidentally updated the snapshot to include this file — the
author likely ran tests against a cached workspace from before PR #34616
fixed the CI workflow generation bug.

## Expected Behavior

The snapshot should not include `.github/workflows/ci.yml` since
`create-nx-workspace` correctly skips CI workflow generation in
non-interactive mode (fixed in PR #34616).

## Related Issue(s)

Fixes the `e2e-nx:e2e-ci--src/extras.test.ts` CI flakiness on master.
2026-03-04 00:15:41 -05:00
Craigory Coppola f77897a54a chore(repo): add a few more excluded reads to sandboxing (#34687)
Excludes a few more dirs for sb
2026-03-03 20:28:07 -05:00
Craigory Coppola 92fb88787f fix(core): stabilizes project references in dependsOn and inputs when later plugins rename a project (#34332)
## Current Behavior
There's a bug currently where is a plugin returns a dependsOn dependency
or input that directly references another project by name, and a later
plugin renames that project, the dependsOn or input entry is left stale
and pointing at a now non-existent project.

## Expected Behavior
The old refs are kept up to date as the nodes get merged together

## AI Summary
This pull request introduces a new mechanism to handle project name
substitutions in the Nx project graph, ensuring that references to
project names in `inputs` and `dependsOn` blocks remain accurate even if
a plugin changes a project's name during graph construction. The main
addition is the `ProjectNameInNodePropsManager`, which tracks and
updates references when project names change. Several related
refactorings and improvements were made to integrate this manager into
the project configuration merging process.

**Project name substitution and consistency:**

* Added a new `ProjectNameInNodePropsManager` class to manage and apply
project name substitutions when project names change, ensuring that all
references in `inputs` and `dependsOn` blocks remain consistent.
* Integrated the `ProjectNameInNodePropsManager` into the
`mergeCreateNodesResults` function, registering substitutors for node
results, marking roots as dirty when names change, and applying
substitutions after merging.
[[1]](diffhunk://#diff-774ce1a4fd8ec0c898c989fb75e2d8c87a0055549bca680945bce50f717db117R518)
[[2]](diffhunk://#diff-774ce1a4fd8ec0c898c989fb75e2d8c87a0055549bca680945bce50f717db117L521-R551)
[[3]](diffhunk://#diff-774ce1a4fd8ec0c898c989fb75e2d8c87a0055549bca680945bce50f717db117R563-R571)

**API and function changes:**

* Modified `mergeProjectConfigurationIntoRootMap` to return an object
indicating whether a project name was changed, instead of just returning
void.
[[1]](diffhunk://#diff-774ce1a4fd8ec0c898c989fb75e2d8c87a0055549bca680945bce50f717db117L59-R62)
[[2]](diffhunk://#diff-774ce1a4fd8ec0c898c989fb75e2d8c87a0055549bca680945bce50f717db117R238-R244)

**Code organization and import cleanup:**

* Refactored imports in `project-configuration-utils.ts` for better
organization and to accommodate the new manager.
[[1]](diffhunk://#diff-774ce1a4fd8ec0c898c989fb75e2d8c87a0055549bca680945bce50f717db117L9-R25)
[[2]](diffhunk://#diff-774ce1a4fd8ec0c898c989fb75e2d8c87a0055549bca680945bce50f717db117R43-L43)

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
2026-03-03 17:22:29 -05:00
Jack Hsu ffa6959844 docs(misc): add /docs redirect (#34684)
The clean-up PR inverted the reverse proxy logic such that only the
defined paths are passed to Next.js. The default is to return Framer
pages. Since we also remove the Nextjs redirect, we lose the `/docs` one
that was missing from `_redirects` file.

https://github.com/nrwl/nx/pull/34672

This PR adds the redirect to fix it again.
2026-03-03 16:45:02 -05:00
Copilot c89dd93e45 fix(core): resolve false positive loop detection when running with Bun (#34640)
Bun includes extra consecutive async frames for async functions in call
stacks, causing `preventRecursionInGraphConstruction` to falsely detect
a recursive loop during normal project graph construction.

## Root Cause

`preventRecursionInGraphConstruction` uses `getCallSites().slice(2)` to
skip the top 2 frames (itself +
`buildProjectGraphAndSourceMapsWithoutDaemon`), then checks if
`buildProjectGraphAndSourceMapsWithoutDaemon` appears again in the
remaining frames.

In Bun, `buildProjectGraphAndSourceMapsWithoutDaemon` appears **twice
consecutively** due to async frame duplication — leaving one occurrence
after the slice, which incorrectly triggers the loop error.

**Node call stack (after `slice(2)`):**
```
#0 createProjectGraphAndSourceMapsAsync  ← clean
#1 createProjectGraphAsync
#2 runOne
```

**Bun call stack (after `slice(2)`):**
```
#0 buildProjectGraphAndSourceMapsWithoutDaemon  ← false positive!
#1 createProjectGraphAndSourceMapsAsync
#2 createProjectGraphAndSourceMapsAsync         ← Bun duplicates async frames
#3 createProjectGraphAsync
```

## Fix

Since the call stack recursion check does not work reliably under Bun,
`preventRecursionInGraphConstruction` now returns early when running
under Bun, detected via `'Bun' in globalThis` — consistent with the
existing Bun runtime detection pattern used elsewhere in the codebase
(e.g., `isolated-plugin.ts`). The original `slice(2)` logic is preserved
unchanged for Node.js and other runtimes.

```ts
export function preventRecursionInGraphConstruction() {
  // Bun's async stack traces include extra frames that cause false positives in the
  // recursion check below, so we skip the check when running under Bun.
  if ('Bun' in globalThis) {
    return;
  }
  // ... existing Node.js check ...
}
```

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>


----

*This section details on the original issue you should resolve*

<issue_title>getCallSites output differs between Node and Bun triggering
loop detection</issue_title>
<issue_description>### Current Behavior

Hey team,

I am trying to use Bun (1.3.5) instead of Node (v22.17.0) for running Nx
(v22.1.3) and bumped into the following error:

Command:
```bash
bunx --bun nx run api:build
```

Error:
```
 NX   Project graph construction cannot be performed due to a loop detected in the call stack. This can happen if 'createProjectGraphAsync' is called directly or indirectly during project graph construction.

To avoid this, you can add a check against "global.NX_GRAPH_CREATION" before calling "createProjectGraphAsync".
Call stack:
buildProjectGraphAndSourceMapsWithoutDaemon (/app/node_modules/.pnpm/nx@22.1.3_@swc-node+register@1.10.9_@swc+core@1.11.1_@swc+helpers@0.5.15__@swc+types@0.1.18_t_twtgkxomntuzxcyp4ewkmtxn2q/node_modules/nx/src/project-graph/project-graph.js:81:62)
createProjectGraphAndSourceMapsAsync (/app/node_modules/.pnpm/nx@22.1.3_@swc-node+register@1.10.9_@swc+core@1.11.1_@swc+helpers@0.5.15__@swc+types@0.1.18_t_twtgkxomntuzxcyp4ewkmtxn2q/node_modules/nx/src/project-graph/project-graph.js:274:31)
createProjectGraphAndSourceMapsAsync (/app/node_modules/.pnpm/nx@22.1.3_@swc-node+register@1.10.9_@swc+core@1.11.1_@swc+helpers@0.5.15__@swc+types@0.1.18_t_twtgkxomntuzxcyp4ewkmtxn2q/node_modules/nx/src/project-graph/project-graph.js:225:53)
createProjectGraphAsync (/app/node_modules/.pnpm/nx@22.1.3_@swc-node+register@1.10.9_@swc+core@1.11.1_@swc+helpers@0.5.15__@swc+types@0.1.18_t_twtgkxomntuzxcyp4ewkmtxn2q/node_modules/nx/src/project-graph/project-graph.js:222:45)
createProjectGraphAsync (/app/node_modules/.pnpm/nx@22.1.3_@swc-node+register@1.10.9_@swc+core@1.11.1_@swc+helpers@0.5.15__@swc+types@0.1.18_t_twtgkxomntuzxcyp4ewkmtxn2q/node_modules/nx/src/project-graph/project-graph.js:205:40)
runOne (/app/node_modules/.pnpm/nx@22.1.3_@swc-node+register@1.10.9_@swc+core@1.11.1_@swc+helpers@0.5.15__@swc+types@0.1.18_t_twtgkxomntuzxcyp4ewkmtxn2q/node_modules/nx/src/command-line/run/run-one.js:23:52)
runOne (/app/node_modules/.pnpm/nx@22.1.3_@swc-node+register@1.10.9_@swc+core@1.11.1_@swc+helpers@0.5.15__@swc+types@0.1.18_t_twtgkxomntuzxcyp4ewkmtxn2q/node_modules/nx/src/command-line/run/run-one.js:16:23)
Pass --verbose to see the stacktrace.
```

After some digging I found that the stack trace produced by
https://github.com/nrwl/nx/blob/691bb320ce1e9cc2872e1a1b364d3fdeb9e1ad0e/packages/nx/src/utils/call-sites.ts
differs between Node and Bun. When
https://github.com/nrwl/nx/blob/691bb320ce1e9cc2872e1a1b364d3fdeb9e1ad0e/packages/nx/src/project-graph/project-graph.ts#L422
is run, the produced function call tracing is:

Node (v22.17.0):
```
nrwl/nx#0 createProjectGraphAndSourceMapsAsync
nrwl/nx#1 createProjectGraphAsync
nrwl/nx#2 runOne
nrwl/nx#3 <anonymous>
nrwl/nx#4 <anonymous>
nrwl/nx#5 handleErrors
nrwl/nx#6 handler
```

Bun (1.3.5):
```
nrwl/nx#0 buildProjectGraphAndSourceMapsWithoutDaemon <- This entry causes Nx to detect a loop
nrwl/nx#1 createProjectGraphAndSourceMapsAsync
nrwl/nx#2 createProjectGraphAndSourceMapsAsync
nrwl/nx#3 createProjectGraphAsync
nrwl/nx#4 createProjectGraphAsync
nrwl/nx#5 runOne
nrwl/nx#6 runOne
```

This is not a Nx bug per-se, but wondering if this falls into the
efforts of supporting Bun into Nx (i.e.
https://nx.dev/blog/nx-19-5-adds-stackblitz-new-features-and-more#bun-and-pnpm-v9-support)?

I will cross post the above into the Bun repo too for input.

### Expected Behavior

Able to execute Nx commands with Bun

### GitHub Repo

_No response_

### Steps to Reproduce

1. Run bunx --bun nx run api:build


### Nx Report

```shell
NX_DAEMON=true bunx --bun nx --disableNxCache --disableRemoteCache --outputStyle dynamic-legacy report                       1 ✘  16:18:00 

 NX   Report complete - copy this into the issue template

Node           : 24.3.0
OS             : darwin-arm64
Native Target  : aarch64-macos
pnpm           : 9.6.0

nx                     : 22.1.3
@nx/js                 : 22.1.3
@nx/jest               : 22.1.3
@nx/eslint             : 22.1.3
@nx/workspace          : 22.1.3
@nx/cypress            : 22.1.3
@nx/devkit             : 22.1.3
@nx/esbuild            : 22.1.3
@nx/eslint-plugin      : 22.1.3
@nx/module-federation  : 22.1.3
@nx/nest               : 22.1.3
@nx/next               : 22.1.3
@nx/node               : 22.1.3
@nx/playwright         : 22.1.3
@nx/plugin             : 22.1.3
@nx/react              : 22.1.3
@nx/rollup             : 22.1.3
@nx/storybook          : 22.1.3
@nx/vite               : 22.1.3
@nx/vitest             : 22.1.3
@nx/web                : 22.1.3
@nx/webpack            : 22.1.3
@nx/docker             : 22.1.3
nx-cloud               : 19.1.0
@nrwl/nx-cloud         : 19.1.0
typescript             : 5.7.3
---------------------------------------
Registered Plugins:
@nxlv/python
---------------------------------------
Community plu...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes nrwl/nx#33997

<!-- START COPILOT CODING AGENT TIPS -->
---

 Let Copilot coding agent [set things up for you](https://github.com/nrwl/nx/issues/new?title=+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AgentEnder <6933928+AgentEnder@users.noreply.github.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Copilot <Copilot@users.noreply.github.com>
2026-03-03 16:13:45 -05:00
Craigory Coppola e4f2d399ad fix(core): skip writing deps cache if already up-to-date (#34582)
## Current Behavior
Frequent write cache calls during tasks hashing results in delays

## Expected Behavior
Cache is validated but only written when changed

## AI Summary
This pull request introduces an optimization to the project graph cache
writing logic, reducing unnecessary disk writes when serving repeated
requests with unchanged graphs. The main change is the addition of a
mechanism to track the cache file's modification time and only write to
disk if the file has been externally modified or not written yet by the
current process.

Optimizations to cache writing:

* Added `writeCacheIfStale` function in `nx-deps-cache.ts` to prevent
redundant cache writes by checking the cache file's modification time
before writing. This function is now used in the daemon's graph
recomputation logic, replacing the previous unconditional write.
[[1]](diffhunk://#diff-82bd1a5a7b7320ffc3233470f191782c054bf69a696dc16001d2c4b1d0b04963R285-R312)
[[2]](diffhunk://#diff-d5bf3c66e62cac1884a071bf07fd1991320a3e62b07bfc03af3b9557b714c892L17-R17)
[[3]](diffhunk://#diff-d5bf3c66e62cac1884a071bf07fd1991320a3e62b07bfc03af3b9557b714c892L136-R149)
* Introduced `lastWrittenCacheMtimeMs` variable to track the last
successful write's modification time, updated after each cache write.
[[1]](diffhunk://#diff-82bd1a5a7b7320ffc3233470f191782c054bf69a696dc16001d2c4b1d0b04963R202-R208)
[[2]](diffhunk://#diff-82bd1a5a7b7320ffc3233470f191782c054bf69a696dc16001d2c4b1d0b04963R256-R261)

Codebase updates:

* Updated imports in `nx-deps-cache.ts` to include `statSync` for file
modification time checks.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-03-03 16:08:06 -05:00
Copilot 42f73a5221 fix(core): interpolate {projectRoot} and {projectName} in {workspaceRoot} input patterns in native hasher (#34637)
`{projectRoot}` and `{projectName}` tokens inside `{workspaceRoot}/...`
input patterns were silently never substituted in the native Rust
hasher, causing the glob to match zero files and those inputs to be
entirely excluded from the cache hash — a silent cache correctness bug.

## Root Cause

In `gather_self_inputs` (`hash_planner.rs`), workspace filesets (those
starting with `{workspaceRoot}/`) were forwarded as-is to
`HashInstruction::WorkspaceFileSet`. When `globs_from_workspace_globs`
later stripped `{workspaceRoot}/`, the remaining `{projectRoot}/**/*.go`
was matched literally — which never exists on disk.

## Changes

- **`hash_planner.rs`**: Before storing workspace filesets in
`HashInstruction::WorkspaceFileSet`, replace `{projectRoot}` and
`{projectName}` with their actual values from the project graph node.
- **`planner.spec.ts`**: Added a test verifying that a pattern like
`{workspaceRoot}/{projectRoot}/**/*.go` correctly resolves to
`{workspaceRoot}/libs/parent/**/*.go` in the hash plan.

```json
// nx.json — this pattern now works correctly
"namedInputs": {
  "goSource": ["{workspaceRoot}/{projectRoot}/**/*.go"]
}
```

This pattern is documented as valid per the [Nx inputs
reference](https://nx.dev/docs/reference/inputs): `{projectRoot}` and
`{projectName}` can appear anywhere after the leading `{workspaceRoot}`.

> [!WARNING]
>
> <details>
> <summary>Firewall rules blocked me from connecting to one or more
addresses (expand for details)</summary>
>
> #### I tried to connect to the following addresses, but was blocked by
firewall rules:
>
> - `repo.gradle.org`
> - Triggering command: `/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
--add-opens=java.base/java.lang=ALL-UNNAMED
--add-opens=java.base/java.lang.invoke=ALL-UNNAMED
--add-opens=java.base/java.util=ALL-UNNAMED
--add-opens=java.prefs/java.util.prefs=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
--add-opens=java.base/java.util=ALL-UNNAMED
--add-opens=java.prefs/java.util.prefs=ALL-UNNAMED
--add-opens=java.base/java.nio.charset=ALL-UNNAMED
--add-opens=java.base/java.net=ALL-UNNAMED
--add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED
--add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED
-XX:MaxMetaspaceSize=384m -XX:&#43;HeapDumpOnOutOfMemoryError -Xms256m
-Xmx512m -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en` (dns
block)
> - `staging.nx.app`
> - Triggering command:
`/home/REDACTED/work/_temp/ghcca-node/node/bin/node node
./bin/post-install` (dns block)
> - Triggering command: `/opt/hostedtoolcache/node/24.13.1/x64/bin/node
/opt/hostedtoolcache/node/24.13.1/x64/bin/node
/home/REDACTED/work/nx/nx/node_modules/.pnpm/nx@22.6.0-beta.5_@swc-node&#43;register@1.11.1_@swc&#43;core@1.15.10_@swc&#43;helpers@0.5.18__@swc&#43;_a827ebc424be037fc154d90301143d4e/node_modules/nx/src/project-graph/plugins/isolation/plugin-worker
/tmp/plugin4368-16-420.890618.sock @nx/enterprise-cloud` (dns block)
> - Triggering command: `/opt/hostedtoolcache/node/24.13.1/x64/bin/node
/opt/hostedtoolcache/node/24.13.1/x64/bin/node
/home/REDACTED/work/nx/nx/node_modules/.pnpm/nx@22.6.0-beta.5_@swc-node&#43;register@1.11.1_@swc&#43;core@1.15.10_@swc&#43;helpers@0.5.18__@swc&#43;_a827ebc424be037fc154d90301143d4e/node_modules/nx/src/project-graph/plugins/isolation/plugin-worker
/tmp/plugin5770-16-420.874901.sock @nx/enterprise-cloud` (dns block)
>
> If you need me to access, download, or install something from one of
these locations, you can either:
>
> - Configure [Actions setup
steps](https://gh.io/copilot/actions-setup-steps) to set up my
environment, which run before the firewall is enabled
> - Add the appropriate URLs or hosts to the custom allowlist in this
repository's [Copilot coding agent
settings](https://github.com/nrwl/nx/settings/copilot/coding_agent)
(admins only)
>
> </details>

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>{projectRoot} not interpolated inside {workspaceRoot}
input patterns in native hasher</issue_title>
> <issue_description>## Current Behavior
> 
> When using a `{workspaceRoot}/{projectRoot}/**/*.go` pattern in target
inputs, the `{projectRoot}` token is **not interpolated** by the native
Rust hash planner. The pattern silently matches zero files, causing the
cache hash to exclude those files entirely.
> 
> ```json
> // nx.json
> "namedInputs": {
>   "gosourceUnfiltered": ["{workspaceRoot}/{projectRoot}/**/*.go"]
> }
> ```
> 
> ```json
> // target config
> "format": {
>   "inputs": ["gosourceUnfiltered", { "externalDependencies": [] }]
> }
> ```
> 
> The hash plan for this target contains **zero `.go` file inputs**:
> 
> ```
> Task: my-project:format:write
>   Inputs:
>     my-project:ProjectConfiguration
>     my-project:TsConfig
>     env:NX_CLOUD_ENCRYPTION_KEY
>     file:nx.json
>     file:.gitignore
>     // no .go files!
> ```
> 
> ## Root Cause
> 
> In the Rust hash planner (`hash_planner.rs`), fileset inputs are
partitioned based on their prefix:
> 
> ```rust
> .partition(|file_set| {
> file_set.starts_with("{projectRoot}/") ||
file_set.starts_with("!{projectRoot}/")
> });
> ```
> 
> A pattern starting with `{workspaceRoot}/` is classified as a
**workspace fileset**. It then flows to `hash_workspace_files.rs` where
`{workspaceRoot}/` is stripped via `strip_prefix("{workspaceRoot}/")`,
leaving `{projectRoot}/**/*.go`. But `{projectRoot}` is **never
substituted** with the actual project root, so the glob literally tries
to match paths starting with `{projectRoot}/` — which don't exist.
> 
> ## Expected Behavior
> 
> Per the [Nx docs on inputs](https://nx.dev/docs/reference/inputs):
> 
> > `{workspaceRoot}` should only appear in the beginning of an input
but **`{projectRoot}` and `{projectName}` can be specified later in the
input to interpolate the root or name of the project** into the input
location.
> 
> The native hasher should interpolate `{projectRoot}` (and
`{projectName}`) within `{workspaceRoot}` patterns before glob matching.
After stripping `{workspaceRoot}/` and interpolating `{projectRoot}`,
the pattern should become e.g. `packages/shared/go/middleware/**/*.go`
and correctly match files.
> 
> ## Impact
> 
> This is a **silent cache correctness issue**: targets using this
pattern appear to work but their cache hash doesn't include the matched
files. Changes to those files won't invalidate the cache. There's no
warning or error emitted.
> 
> ## Workaround
> 
> Use the literal path instead of `{projectRoot}` inside workspace-level
patterns:
> 
> ```js
> // In a createNodes plugin, instead of:
> inputs: ["{workspaceRoot}/{projectRoot}/**/*.go"]
> // Use:
> inputs: [`{workspaceRoot}/${actualProjectRoot}/**/*.go`]
> ```
> 
> ## Related
> 
> - nrwl/nx#34225 — Nested Project Files Excluded from Parent Project
Inputs (the reason `{workspaceRoot}/{projectRoot}` patterns are used in
the first place: to bypass project file filtering and include files from
nested sub-projects)
> 
> ## Environment
> 
> - **Nx version**: 22.6.0-beta.3
> - **OS**: macOS (Darwin 24.6.0)
> - **Package manager**: pnpm</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes nrwl/nx#34595

<!-- START COPILOT CODING AGENT TIPS -->
---

🔒 GitHub Advanced Security automatically protects Copilot coding agent
pull requests. You can protect all pull requests by enabling Advanced
Security for your repositories. [Learn more about Advanced
Security.](https://gh.io/cca-advanced-security)

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AgentEnder <6933928+AgentEnder@users.noreply.github.com>
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Copilot <Copilot@users.noreply.github.com>
2026-03-03 16:04:33 -05:00
Copilot 3fc52ead00 fix(core): resolve input files for targets with defaultConfiguration (#34638)
`nx show target inputs` returns empty results for any target with
`defaultConfiguration` set because
`HashPlanInspector.inspectTaskInputs()` keys results as
`project:target:config` (e.g. `my-app:build:local`), but the lookup was
using `project:target` — a guaranteed miss.

## Changes

- **`packages/nx/src/command-line/show/target.ts`**: In
`resolveInputFiles`, construct the plan lookup key using the explicitly
passed `configuration` first, then fall back to the target's
`defaultConfiguration`:

```ts
const targetConfig = graph.nodes[projectName]?.data?.targets?.[targetName];
const effectiveConfig = configuration ?? targetConfig?.defaultConfiguration;
const taskId = effectiveConfig
  ? `${projectName}:${targetName}:${effectiveConfig}`
  : `${projectName}:${targetName}`;
```

If the computed `taskId` is not found in the hash plan, an error is
thrown instead of silently returning empty results.

- **`showTargetInputsHandler`**: Now extracts the configuration from the
target string (`project:target:config`) or the `-c`/`--configuration`
flag and forwards it to `resolveInputFiles`.

- **`ShowTargetInputsOptions`** (`command-object.ts`): Added
`configuration?: string` so the type correctly reflects the prop.

- **`packages/nx/src/command-line/show/target.spec.ts`**: Added tests
covering:
  - Resolving input files when `defaultConfiguration` is set
  - Preferring an explicit `configuration` over `defaultConfiguration`
  - Throwing when the task ID is not found in the hash plan

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>nx show target inputs returns empty when target has
defaultConfiguration</issue_title>
> <issue_description>## Current Behavior
> 
> `nx show target inputs <project>:<target> --json` returns no files for
any target that has a `defaultConfiguration` set (directly or via
`targetDefaults`).
> 
> ```bash
> $ nx show target inputs card-api-lambda:build --json
> {
>   "project": "card-api-lambda",
>   "target": "build"
> }
> # Expected: files array with resolved input files
> ```
> 
> The `--check` flag also reports files as not being inputs when they
should be:
> 
> ```bash
> $ nx show target inputs card-api-lambda:build --check
packages/card/api/lambda/main.go
> ✗ packages/card/api/lambda/main.go is not an input for
card-api-lambda:build
> ```
> 
> Targets **without** `defaultConfiguration` on the same project resolve
correctly:
> 
> ```bash
> $ nx show target inputs card-api-lambda:generate-docs --json
> {
>   "project": "card-api-lambda",
>   "target": "generate-docs",
> "files": [ ".gitignore", "nx.json",
"packages/card/api/lambda/main.go", ... ]
> }
> ```
> 
> ## Root Cause
> 
> In `packages/nx/src/command-line/show/target.ts`, the
`resolveInputFiles` function constructs the lookup key as:
> 
> ```js
> const taskId = `${projectName}:${targetName}`;
> ```
> 
> But the native `HashPlanInspector.inspectTaskInputs()` returns results
keyed by the **full task ID including the default configuration**, e.g.
`card-api-lambda:build:local`.
> 
> When a target has `defaultConfiguration: "local"`, the plan result is
keyed as `project:target:local`, but the lookup searches for
`project:target` — which doesn't exist — so it falls through to the
empty default `{ files: [], ... }`.
> 
> Verified by calling `inspectTaskInputs` directly:
> 
> ```
> === build ===
> Task IDs in result: card-api-lambda:generate-docs,
card-api-lambda:build:local
> # lookup for "card-api-lambda:build" → miss → empty
> 
> === generate-docs ===
> Task IDs in result: card-api-lambda:generate-docs
> # lookup for "card-api-lambda:generate-docs" → hit → 142 files
> ```
> 
> ## Suggested Fix
> 
> The lookup key should account for the default configuration:
> 
> ```js
> const defaultConfig =
graph.nodes[projectName]?.data?.targets?.[targetName]?.defaultConfiguration;
> const taskId = defaultConfig
>   ? `${projectName}:${targetName}:${defaultConfig}`
>   : `${projectName}:${targetName}`;
> ```
> 
> ## Expected Behavior
> 
> `nx show target inputs` should resolve and display input files
regardless of whether the target has a `defaultConfiguration`.
> 
> ## Environment
> 
> - **Nx version**: 22.6.0-beta.3
> - **OS**: macOS (Darwin 24.6.0)
> - **Node**: v22
> - **Package manager**: pnpm</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes nrwl/nx#34594

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AgentEnder <6933928+AgentEnder@users.noreply.github.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Copilot <Copilot@users.noreply.github.com>
2026-03-03 16:00:07 -05:00
Jason Jean c5876ed0b1 feat(core): migrate napi-rs v2 to v3 (#34619)
## Current Behavior

napi-derive v2 has a bug where the type definition temp file doesn't get
cleaned between builds with different profiles (release vs dev), causing
duplicate class declarations in `index.d.ts`.

## Expected Behavior

With napi-rs v3, the temp file handling is fixed — each crate gets its
own folder, preventing duplicate declarations across build profiles.

## Related Issue(s)

N/A — internal build tooling improvement.

## Changes

### Dependency upgrades
- **Cargo.toml**: `napi` 2.x → 3.8.3, `napi-derive` 2.x → 3.5.2,
`napi-build` 1.x → 2.3.1
- **package.json**: `@napi-rs/cli` → 3.5.1 (was 3.0.0-alpha.56)

### External data sharing (Arc pattern)
- Replaced raw-pointer `StoredExternal<T>` wrapper with napi-recommended
`Arc<T>` pattern for shared ownership
- Read-only data: `External::new(Arc::new(data))` at creation, `Arc<T>`
in struct fields
- Mutable data (DB): `External::new(Arc::new(Mutex::new(data)))`,
`Arc<Mutex<T>>` in struct fields
- Constructors take `&External<Arc<T>>`, clone the Arc;
`#[napi(ts_arg_type)]` preserves TS types
- Deleted `types/external_compat.rs` (86 lines of unsafe code removed)

### API migrations
- `ThreadsafeFunction`: Accept directly as napi parameters instead of
building from `Function<'_>`
- Return-only structs: Use `#[napi(object, object_from_js = false)]` for
structs with `External<T>` fields
- `env.create_object()` → `Object::new(&env)`, `JsObject` → `PromiseRaw`
for async returns

### Watcher fix
- Deferred `Watchexec::default()` creation from constructor to `watch()`
inside napi's `spawn()` block
- `Watchexec::default()` internally calls `tokio::spawn()` which
requires a Tokio runtime in TLS — the `#[napi(constructor)]` runs on the
JS main thread with no runtime context
- Changed `napi::tokio::spawn(...)` to `spawn(...)` (napi's
`bindgen_prelude::spawn` which uses a static runtime, works from any
thread)
- Store `watch_exec` as `Arc<Mutex<Option<Arc<Watchexec>>>>` for lazy
initialization

### TUI fix
- Same Tokio runtime issue: `tokio::spawn(async {})` placeholder in
`Tui::new()` panicked on JS main thread
- Changed `task` field from `JoinHandle<()>` to
`Option<JoinHandle<()>>`, initialized as `None`
- Moved `tui.start()` (which calls `tokio::spawn`) inside napi's
`spawn()` async block
- Changed `napi::tokio::spawn(...)` to `spawn(...)` in lifecycle.rs

### Platform-specific watcher improvements
- Gated non-macOS watcher functions with `#[cfg(not(target_os =
"macos"))]`
- Added `rlib` to `crate-type` in Cargo.toml for cargo test
compatibility

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-03-03 15:17:24 -05:00
Leosvel Pérez Espinosa ff621875b3 cleanup(core): reduce misc allocations (#34647)
## Current Behavior

Several Rust native modules and one TS utility have unnecessary
allocations:
- `hash_planner.rs`: visited set uses `HashSet<String>` with
`to_string()` per insert; unnecessary `.collect::<Vec<_>>()` creating
temp vecs; no pre-allocation for dependency inputs
- `context.rs`: `update_files` allocates a String per map entry inside
retain loop
- `hash_workspace_files.rs`: `.clone()` before `.as_bytes()` — 2
needless heap allocs per file
- `find_matching_projects.rs`: collects HashMap keys into Vec + linear
search instead of O(1) lookup
- `validate_outputs.rs`: compiles regex on every call
- `project-configuration-utils.ts`: uses `JSON.parse(JSON.stringify())`
for deep cloning

## Expected Behavior

All unnecessary allocations removed:
- Borrow `&str` from project graph instead of cloning Strings into
visited set
- Use `Path::new()` once outside retain closure (also more correct —
component-boundary-aware matching)
- Call `.as_bytes()` directly without clone
- Use `HashMap::get_key_value()` for O(1) lookup
- Cache compiled regex with `LazyLock<Regex>`
- Use `structuredClone` (Node 18+) instead of JSON round-trip

See individual commits for detailed rationale per change.
2026-03-03 15:10:56 -05:00
Leosvel Pérez Espinosa 1a973bafad fix(angular-rspack): use relative path for postcss-cli-resources output (#34681)
## Current Behavior

When using `@nx/angular-rspack` with SCSS imports that reference
external assets (e.g., `flag-icon-css`), the `postcss-cli-resources`
plugin generates absolute filesystem paths in CSS `url()` values:

```css
.flag-icon-us{background-image:url(/some/project/path/dist/bug-demo/browser/media/us.2d0a1dd6.svg)}
```

This happens because `normalizeOutputPath()` makes `outputPath.media`
absolute, and this absolute path is passed directly as
`resourcesOutputPath` to `postcss-cli-resources`.

## Expected Behavior

CSS `url()` values contain relative paths:

```css
.flag-icon-us{background-image:url(media/us.2d0a1dd6.svg)}
```

## Related Issue(s)

Fixes #34092
2026-03-03 11:08:36 -05:00
Leosvel Pérez Espinosa e81f187768 fix(misc): use pathToFileURL for cross-platform path handling in postcss-cli-resources (#34676)
## Current Behavior

CSS `url()` references to fonts and assets fail to resolve on Windows
with webpack, rspack, and angular-rspack builds. The
`postcss-cli-resources` plugin passes Windows absolute paths (e.g.
`E:\dev\project\font.woff2`) to `new URL(path, 'file:///')`, which
misinterprets the drive letter (`E:`) as a URL protocol, stripping it
from `pathname` and producing unresolvable paths like
`\dev\project\font.woff2`.

## Expected Behavior

CSS `url()` references to fonts, images, and other assets resolve
correctly on all platforms.

## Related Issue(s)

Fixes #33052
2026-03-03 11:08:07 -05:00
Jack Hsu e01b5de8f4 docs(nx-dev): invert Framer proxy to default-proxy, keep only Next.js paths (#34672)
## Current Behavior

The Netlify edge function reads `NEXT_PUBLIC_FRAMER_REWRITES` env var to
determine which paths to proxy to Framer. As more pages move to Framer,
this growing allowlist becomes cumbersome to maintain.

## Expected Behavior

The edge function now proxies all requests to Framer by default. Only
paths explicitly listed in `nextjsPaths` and `excludedPath` are served
by Next.js. This removes the need for the `NEXT_PUBLIC_FRAMER_REWRITES`
env var (should be removed from Netlify dashboard manually).

Deleted 25 page files (pages router + app router) that are now served by
Framer: homepage, 404, enterprise/*, contact/*, solutions/*, community,
company, customers, nx-cloud, partners, brands, careers, java, react,
remote-cache, resources, webinar.

**Next.js paths kept:** `/blog/*`, `/courses/*`, `/pricing`, `/podcast`,
`/ai-chat`, `/changelog`, `/resources-library`, `/whitepaper-fast-ci`,
`/500`, `/api/*`, `/docs/*`

**Manual follow-up:** Remove `NEXT_PUBLIC_FRAMER_REWRITES` env var from
Netlify dashboard.

## Related Issue(s)

Closes DOC-431
2026-03-03 11:07:19 -05:00
Jack Hsu f42be4e9c5 fix(misc): fix broken nx.dev redirects and remove legacy redirect-rules files (#34673)
## Current Behavior

10 `nx.dev` URLs return 404 — broken links in CLI output, graph UI, and
cloud UI. Additionally:
- ~20 wildcard redirect rules silently broken because `:slug*` was never
converted to Netlify's `*`/`:splat` syntax
- Specific `/getting-started/` rules ordered after the wildcard
catch-all, sending users to the generic intro page instead of the
correct page (e.g., `/getting-started/editor-setup`)
- Redirect chain breaks where intermediate targets (e.g.,
`/nx-api/powerpack-*-cache` → `/nx-api/*-cache`) had no rule

## Expected Behavior

All documented `nx.dev` URLs resolve correctly. `_redirects` is the sole
source of truth — no more JS generator pipeline.

### Changes

1. **Fixed `_redirects`**:
   - Converted all `:slug*` to `*`/`:splat` (Netlify syntax)
- Reordered specific `/getting-started/` rules before the wildcard
catch-all
   - Added 13 new redirect rules for broken 404 URLs

2. **Fixed `astro-docs/netlify.toml`**:
   - Added 3 redirects for `/docs/` path typos (trailing-s, wrong path)

3. **Deleted legacy files** (-2,248 lines):
   - `redirect-rules.js`
   - `redirect-rules-docs-to-astro.js`
   - `redirect-rules.spec.js`
   - `scripts/generate-netlify-redirects.mjs`

## Related Issue(s)

Fixes DOC-428
2026-03-03 09:35:34 -05:00
Jason Jean 923fc45136 fix(gradle): tee batch runner output to stderr for terminal display (#34630)
## Current Behavior

When running Gradle batch tasks (e.g. `nx build my-gradle-project
--no-tui`), the terminal shows no task output. The Gradle build output
is captured into `ByteArrayOutputStream` for JSON results but never
forwarded to the terminal. Users can only see the output in Nx Cloud
task results.

## Expected Behavior

Gradle batch task output (build logs, test results, etc.) should be
visible in the terminal in real-time as the batch executes.

## Changes

- **`TeeOutputStream.kt`** (new): An `OutputStream` that writes to two
destinations simultaneously — captures output for JSON results while
also forwarding to `System.err` for terminal display.
- **`GradleRunner.kt`**: Wrap `setStandardOutput` and `setStandardError`
with `TeeOutputStream` to tee into `System.err` in both
`runBuildLauncher` and `runTestLauncher`.
- **`gradle-batch.impl.ts`**: Replace `PseudoTerminal` (which swallowed
all output with `quiet: true`) with `execSync` using `stdio: ['pipe',
'pipe', 'inherit']` so stderr flows directly to the terminal.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-03-02 13:10:39 -05:00
Jonathan Cammisuli 216f5cabed chore(core): enable polygraph ai tools for claude (#34667) 2026-03-02 11:51:50 -05:00
Jason Jean f48bf319bf fix(repo): remove redundant inputs override for build-base target (#34649)
## Current Behavior

The `build-base` target in `nx.json` has a manual `inputs` override of
`["production", "^production"]` which shadows the more accurate inputs
inferred by the `@nx/js/typescript` plugin.

## Expected Behavior

Let the `@nx/js/typescript` plugin infer the correct inputs for
`build-base` tasks, providing more accurate cache invalidation based on
the actual tsconfig project references.

## Lint Rule Changes

Removing the broad `inputs` override causes the `@nx/dependency-checks`
lint rule to flag a few dependencies as "unused" across three packages.
This happens because the rule uses the build target's inputs to
determine which files to scan for imports, and the narrower tsc-inferred
inputs don't cover certain files:

- **`packages/nx`**: `@napi-rs/wasm-runtime` — used in
`nx.wasi-browser.js`, a `.js` file outside tsconfig scope
- **`packages/angular`**: `@angular-devkit/core` — only referenced as a
string (for `ensurePackage()`, migrations config) and is a
`peerDependency`, not directly imported at runtime
- **`packages/angular-rspack-compiler`**: `semver` — used in
`patch/patch-angular-build.js`, a `.js` patch file outside tsconfig
scope

These are all legitimate dependencies. Adding the `.js` files to
tsconfig would require `allowJs` and pull non-TS scripts into the build
pipeline unnecessarily. Adding them to `ignoredDependencies` in each
package's `.eslintrc.json` is the correct fix.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-03-02 11:41:31 -05:00
Leosvel Pérez Espinosa 2c802ba486 fix(vitest): respect reporters from target options in vitest executor (#34663)
## Current Behavior

The vitest executor ignores `reporter`/`reporters` set in target options
(project.json). Both forms leak through as passthrough CLI args, causing
vitest's `resolveConfig` to override the `reporters` array (which
includes NxReporter), making the executor hang indefinitely.

Additionally, when the vite config uses `reporter` (singular), the
config value always overrides the `reporters` array passed as inline
options — again removing NxReporter.

## Expected Behavior

Target-level `reporter`/`reporters` options take priority over
config-file values. NxReporter is always preserved in the final
reporters array regardless of configuration source.

Priority chain: target `reporter` (singular) > target `reporters`
(plural) > config `reporter` (singular) > config `reporters` (plural).

## Related Issue(s)

Fixes #34495
2026-03-02 15:31:48 +00:00
MaxKless aa4f47e6be feat(core): add .nx/polygraph to gitignore in migration and caia (#34659) 2026-03-03 00:15:46 +09:00
James Henry 72eb1e0808 fix(core): update minimatch to 10.2.4 (#34660) 2026-03-02 16:34:42 +04:00
Jack Hsu 0975384d24 docs(js): expand TS solution migration guide with validated steps and edge cases (#34646)
This PR updates the guide for integrated -> TS solution setup with
additional information. It also adds `% llm_copy_prompt %}` and `{%
llm_only %}` components to provide instructions to the AI agent to
perform the migration.

Preview:
https://deploy-preview-34646--nx-docs.netlify.app/docs/technologies/typescript/guides/switch-to-workspaces-project-references

## Current Behavior

The migration guide covers basic steps but is missing several nuances
discovered in the ocean repo's convert-ts-solution generator and through
end-to-end validation against a real workspace.

## Expected Behavior

The guide covers all steps needed for a successful migration, including:
- .gitignore updates for out-tsc/dist/test-output artifacts
- Order of operations (install before removing paths)
- Root tsconfig cleanup (remove paths entirely, baseUrl, rootDir)
- Package.json self-export and nested path alias strategies
- Update build targets (remove @nx/js:tsc for non-buildable libs)
- Import path updates after package renaming
- Bundler config updates (webpack auto-detect, Jest resolver,
Vite/Vitest)
- Edge cases: circular deps, e2e projects, non-standard locations,
  tsconfig include/exclude patterns

Validated end-to-end against a test workspace with 5 libs and 2 React
apps (webpack+jest, vite+vitest) across 2 clean iterations.

## Screenshots

<img width="810" height="458" alt="Screenshot 2026-02-27 at 7 31 16 AM"
src="https://github.com/user-attachments/assets/80ad87db-6ebe-4f54-9995-65f2ee2c46f1"
/>
<img width="784" height="735" alt="Screenshot 2026-02-27 at 7 31 20 AM"
src="https://github.com/user-attachments/assets/a0a7fe26-7d7d-4225-94bb-f82e5fc49178"
/>

<img width="897" height="161" alt="Screenshot 2026-02-27 at 7 31 39 AM"
src="https://github.com/user-attachments/assets/3b410092-5fb4-420d-9909-ee83c34ff0d6"
/>

## Related Issue(s)

NXC-2950

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
2026-02-27 19:24:07 -05:00
Caleb Ukle 7370195070 fix(misc): boost CLI command reference search ranking (#34625)
## Current Behavior

Searching for CLI commands like `nx watch` on the docs site does not
surface the [Nx Commands reference
page](https://nx.dev/docs/reference/nx-commands) on the first page of
results, even when filtering by "References."

Root causes:
- **Term saturation**: "nx" appears 150+ times on the CLI reference page
(in every heading, usage block, and example), causing it to saturate and
contribute almost nothing to ranking differentiation.
- **Page length penalty**: The current `pageLength: 0.5` setting
actively penalizes long pages—the CLI reference is one of the longest on
the site.
- **No weight boost**: The CLI page had no `weight` set, while
generators/executors pages already get `weight: 2.0`.

## Expected Behavior

Searching for `nx watch`, `nx run-many`, or other CLI commands should
surface the Nx Commands reference page prominently in results.

This PR applies two quick-win tuning changes:
1. **`weight: 4`** on the CLI commands page entry — gives body text ~16×
impact (quadratic scaling), making it competitive with shorter pages
that mention commands incidentally. include the command name in the sub
headers for more improvement in relevancy search without impacting other
pages
2. **`termSaturation: 1.2`** (down from default 1.4) — makes highly
repeated terms like "nx" saturate faster so that the differentiating
term (e.g. "watch") carries more relative weight.

## Related Issue(s)

Addresses
[DOC-401](https://linear.app/nxdev/issue/DOC-401/investigate-boosting-cli-command-reference-pages-in-search)
2026-02-27 15:33:31 -06:00
Jason Jean 6309b63853 chore(repo): update nx to 22.6.0-beta.7 (#34651)
Updating Nx from 22.6.0-beta.6 to 22.6.0-beta.7
2026-02-27 16:11:55 -05:00
Caleb Ukle 3978674caa docs(nx-cloud): add ent release notes for 2026.01 (#34652) 2026-02-27 14:48:46 -06:00
Jason Jean eb498861c5 fix(repo): reset package.json files after local release (#34648)
## Current Behavior

When running `pnpm nx-release --local false`, the `nx release version`
step modifies `package.json` files for `angular-rspack`,
`angular-rspack-compiler`, `dotnet`, and `maven` (bumping versions and
resolving `workspace:*` protocols). The local release path (`--local
false`, non-CI) exits early before reaching the reset logic that the CI
path uses, leaving unstaged changes behind.

## Expected Behavior

After the release script completes (or is interrupted), the source
`package.json` files should be restored to their original state. The
version bumps are only needed in `dist/` for publishing — the source
files should stay at `0.0.1` with `workspace:*` protocols.

This PR:
- Extracts a shared `resetPackageJsons()` function used by both the
local and CI code paths
- Wraps the local release steps in `try/finally` so files are restored
even on errors
- Adds a `SIGINT` handler so files are restored on ctrl+C
2026-02-27 18:11:00 +00:00
Rares Matei 3c3d70339f chore(misc): add sandboxing config for Nx Cloud workflows (#34643)
Add sandboxing-config.yaml to exclude node_modules from reads in Nx
Cloud workflow sandboxing.

## Current Behavior
No sandboxing configuration exists for Nx Cloud workflows.

## Expected Behavior
Nx Cloud workflows use sandboxing config that excludes `node_modules/**`
from reads.

## Related Issue(s)
N/A

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2026-02-27 17:57:20 +00:00
Jack Hsu 352bebae07 docs(misc): fix redirects for /blog (#34650)
Fix bad redirect.
2026-02-27 12:22:23 -05:00
Omer 94cc841a40 cleanup(linter): reuse ESLint instance across child projects in plugin (#34645)
## Current Behavior

In `internalCreateNodesV2`, the ESLint plugin creates a **new `ESLint`
instance per child project root** to check whether the project has
non-ignored lintable files via `isPathIgnored`:

```ts
const eslint = new ESLint({
  cwd: join(context.workspaceRoot, projectRoot),
});
for (const file of lintableFilesPerProjectRoot.get(projectRoot) ?? []) {
  if (!(await eslint.isPathIgnored(...))) { ... }
}
```

In large monorepos, this means hundreds or thousands of ESLint
instantiations during graph calculation. Each instantiation involves
loading and resolving the ESLint configuration, which is the dominant
cost in the plugin.

## Expected Behavior

A single ESLint instance should be shared across all child projects
under the same ESLint config directory. Since `isPathIgnored` receives
**absolute paths**, the instance's `cwd` does not affect the result — it
only needs to resolve the correct config, which is the same for all
children of a given config root.

## Changes

* In `internalCreateNodesV2`: create one lazily-initialized shared
ESLint instance per config directory (the `configDir`), reused across
all child project roots
* Projects that have their own `.eslintignore` file fall back to a
per-project ESLint instance, preserving correct behavior for ESLint v8
(which resolves `.eslintignore` relative to `cwd`)

## Benchmark

Measured on a real monorepo with **1,609 projects** and a single root
ESLint config.

| | Before | After | Change |
|---|---|---|---|
| Cold run (avg of 3) | **~18,700ms** | **~7,300ms** | **-61%** |
| ESLint instances created | 1,457 | 8 | **-99.5%** |

### Breakdown

| Version | Run 1 | Run 2 | Run 3 |
|---|---|---|---|
| Baseline (upstream) | 23,482ms | 16,238ms | 16,389ms |
| With shared instance | 7,473ms | 7,012ms | 7,515ms |

### Methodology

* `NX_DAEMON=false` to prevent daemon caching
* ESLint plugin hash cache cleared between each cold run
* `performance.now()` instrumentation around `internalCreateNodesV2`
* Each measurement repeated 3 times
* **Verified same number of lint targets** (1,473) before and after — no
behavior change

### Why `configDir === '.'` is not excluded

The earlier revision excluded root-level ESLint configs (`configDir ===
'.'`) from the optimization as a conservative safety measure. However,
benchmarking showed this **completely disables the optimization** for
the most common monorepo setup (single root ESLint config), dropping
1,456 of 1,457 instances back to per-project instantiation with zero
measurable improvement.

The per-project `.eslintignore` check already handles the ESLint v8
concern: projects with their own `.eslintignore` still get dedicated
instances, while all others share one instance whose `cwd` correctly
resolves the root config and root `.eslintignore`.

## Related

This is the `createNodesV2` (Nx plugin) code path. The change does not
affect ESLint execution itself — only the graph calculation phase.
2026-02-27 18:08:57 +01:00
MaxKless c43671906b fix(core): update sourceRespository description of nx import (#34606)
we can also import local repos
2026-02-27 13:56:57 +00:00
Jason Jean 3a2c1a2876 chore(repo): update nx to 22.6.0-beta.6 (#34633)
Updating Nx from 22.6.0-beta.5 to 22.6.0-beta.6
2026-02-27 08:42:56 -05:00
Leosvel Pérez Espinosa 65d0f554f8 chore(repo): preserve x-prompt and requires in angular migrations upgrade script (#34641)
When latest >= next, the build-migrations script now preserves the
existing x-prompt and requires fields from the pre-release entry, rather
than skipping them, and adjusts the requires upper bound to the stable
version.
2026-02-27 08:41:11 -05:00
MaxKless 53c0123bef fix(maven): synchronize batch runner invoke() to prevent concurrent access (#34600)
## Current Behavior

The Maven batch runner uses a parallel thread pool to execute tasks.
When multiple tasks are independent roots in the task graph, they get
picked up by separate threads simultaneously. Both threads call
`invoke()` on the same shared `CachingResidentMavenInvoker` (Maven 4) or
`CachingMaven3Invoker` (Maven 3) instance concurrently.

Maven 4's `LookupInvoker.invoke()` is not thread-safe — it
snapshots/restores `System.getProperties()` and the thread context
classloader in a `finally` block, and all concurrent invocations share
the same resident `MavenContext`. This can cause deadlocks in Maven's
internal session machinery.

## Expected Behavior

Maven executions through the shared invoker are serialized via
`@Synchronized`, preventing concurrent access to non-thread-safe Maven
internals. The parallel thread pool still handles task graph management,
build state recording, and result emission concurrently.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 08:30:37 -05:00
Nelson Dominguez 8255c2867c fix(core): support canonical SSH URLs when extracting GitHub user/repo slug during nx release (#31684)
## Current Behavior

When running `nx release` with Git remotes configured as **canonical SSH
URLs**, the repository slug is extracted incorrectly.

Examples of affected remotes include:

- `ssh://git@ssh.github.com:443/org/repo.git`
- `ssh://git@gitlab.company.com:2222/group/subgroup/repo.git`

In these cases, the existing regex-based logic misinterprets the port
number as part of the repository path (for example: `443/org`). This
causes the release creation request to be built with an invalid
repository slug and results in `404 Not Found` errors from the GitHub or
GitLab APIs.

## Expected Behavior

`nx release` should correctly extract the repository slug from all valid
Git remote URL formats, regardless of whether the remote uses HTTPS,
SCP-style SSH, or fully qualified SSH URLs with explicit ports.

Valid examples should resolve to the correct slug:

- `ssh://git@ssh.github.com:443/org/repo.git` => `org/repo`
- `ssh://git@gitlab.company.com:2222/group/subgroup/repo.git`=>
`group/subgroup/repo`

Users should not need to modify their Git remote configuration in order
for `nx release` to work correctly.

## What’s Changed

- Introduced a shared utility
([`extractRepoSlug`](https://github.com/nrwl/nx/pull/31684/changes#diff-799188c178b8a084e86dbe9063ec88a7c324212a8ef79729777f56e4bf7f455cR29-R60))
to consistently extract repository slugs from Git remote URLs.
- Replaced provider-specific, regex-based parsing in:
  - `GithubRemoteReleaseClient`
  - `GitLabRemoteReleaseClient`
- Added support for:
  - HTTPS remotes
  - SCP-style SSH remotes (`git@host:org/repo.git`)
  - Fully qualified SSH URLs with custom ports
  - Arbitrarily nested GitLab group and subgroup paths
  - Self-hosted GitHub and GitLab instances via hostname matching
- Added comprehensive unit tests covering valid and invalid URL formats
for both providers.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #31682
2026-02-27 08:10:16 -05:00
Leosvel Pérez Espinosa af07e75d58 feat(core): use jemalloc with tuned decay timers for native module (#34444)
## Current Behavior

The Nx native module (Rust cdylib loaded by Node.js) uses the system
allocator. The daemon process retains a large RSS footprint after the
initial project graph build, even though most of that memory is no
longer in use. On macOS and Linux, the system allocator doesn't
aggressively return freed pages to the OS.

## Expected Behavior

The daemon's steady-state RSS drops significantly after graph build by
using jemalloc with tuned page purge timers. Peak RSS and wall time are
unaffected.

## Changes

Adds [tikv-jemallocator](https://github.com/tikv/jemallocator) as the
global allocator on Linux and macOS, with two compile-time settings:

- **`dirty_decay_ms:1000`** — returns freed pages to the OS after 1s
instead of the default 10s. Tuned to Nx's phase-separated workload
(graph build → idle → task execution), where transitions happen every
~30-60s. Benchmarked against 5s and 10s — both too slow to purge between
phases.
- **`muzzy_decay_ms:0`** — skips the lazy purge phase (`MADV_FREE`) and
goes straight to `MADV_DONTNEED`. Required on macOS and Linux ≥ 4.5
where `MADV_FREE` doesn't actually reduce RSS.

Windows and WASI continue using the system allocator. Windows is
excluded because `tikv-jemalloc-sys` fails to build with MSVC (spaces in
`cl.exe` path break the autoconf configure script). Tracked upstream in
[tikv/jemallocator#99](https://github.com/tikv/jemallocator/pull/99).

### Other Settings Considered

Tested narenas reduction, tcache_max, extent fit tuning, background
threads, and decay timer values. Only the decay timer configuration
improved steady-state RSS without wall time regression.
2026-02-26 23:47:47 -05:00
Jesse Zomer ac2ef1aaef fix(linter): allow for wildcards paths in enforce-module-boundaries rule (#34066)
closed #32190

## Current Behavior

eslint crashes when tsconfig.base.json path includes a * and you have an
import going to that project
## Expected Behavior
The plugin shouldn't crash and it should auto fix to a working import

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
https://github.com/nrwl/nx/issues/32190

Fixes #32190
2026-02-26 16:21:10 -05:00
Jason Jean 191054d876 chore(repo): update nx to 22.6.0-beta.5 (#34618)
Updating Nx from 22.6.0-beta.3 to 22.6.0-beta.5

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-02-26 15:40:35 -05:00
Eric Baer b8b6ed8b85 fix(testing): use surgical text replacement in Jest matcher alias migration (#34350)
## Current Behavior

The `replace-removed-matcher-aliases` migration uses `tsquery.replace()`
which reprints the entire AST through TypeScript's Printer. This causes
two problems:

1. **Syntax corruption**: Valid TypeScript files are mangled:
   - Destructuring patterns: `{ result }` becomes `{result}:`
   - Arrow functions: missing opening braces
   - Nested callbacks: collapsed/merged code blocks

2. **Unnecessary file changes**: Every test file is written back to disk
even when no matchers are replaced. This triggers `formatFiles()` to
reformat unchanged files, creating large whitespace-only diffs. In large
codebases, this can result in hundreds or thousands of files being
modified unnecessarily, making the migration PR difficult to review.

**Why I care a Lot**

I was running this on a multi-million-LOC monorepo and ran into two
issues:

* I got ~10k modified files with whitespace-only changes from the
removal of newlines. These changes couldn't be fixed with Prettier
because it didn't care about the number of newlines, so the diff was
unmergeable.
* I got ~8 files with malformed Typescript, causing commit hooks, CI,
etc. to fail without manual intervention.

## Expected Behavior

The migration should:
1. Only replace the deprecated matcher names (e.g., `toBeCalled` →
`toHaveBeenCalled`)
2. Preserve all surrounding code exactly as written
5. Only touch files that actually contain deprecated matchers

## Solution

Replace AST-reprinting with surgical text replacement:
- Use `tsquery.query()` to find matching AST nodes
- Collect text positions (start/end) for each node to replace
- Apply replacements in reverse order using string slicing
- Only write files that actually changed

This pattern is already used successfully in other Nx migrations (e.g.,
`rename-cy-exec-code-property.ts` in the Cypress package).

**Additional improvements:**
- Single AST parse with regex selector vs. 11 separate passes
- Quick string check skips parsing files without deprecated matchers
- New regression test covers complex patterns that triggered corruption

## Related Issue(s)

Fixes #32062

---------

Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
2026-02-26 15:20:15 -05:00
Jason Jean bdc61b5ad5 chore(repo): improve e2e test timeout handling and bump cache bust (#34383)
## Current Behavior

E2E tests may timeout without clear error messages, making it difficult
to diagnose test failures.

## Expected Behavior

E2E test utilities should provide better timeout handling and logging to
help diagnose test failures.

## Changes

- Add timeout handling to e2e test utilities (`runCLI` and
`runLernaCLI`)
- Add command logging to track execution time
- Improve timeout error messages with process output
- Bump cache bust value

## Related Issue(s)

CI stability and debugging improvements
2026-02-26 13:13:34 -05:00
Jason Weinzierl 31dad4109b fix(linter): support eslint v10 (#34534)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior

`@nx/eslint` relies on ESLint internals that changed in ESLint v10
(`use-at-your-own-risk`), which causes failures.

It looks like https://github.com/nrwl/nx/pull/24632 originally attempted
to use `loadESLint()` which would've been forward compatible with v10,
but it was later removed in https://github.com/nrwl/nx/pull/27404 in
favor of the `use-at-your-own-risk` import.

## Expected Behavior

`@nx/eslint` supports ESLint v10.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #34415
2026-02-26 12:35:59 -05:00
omasakun c3643126ef fix(core): make watch command work with all and initialRun specified (#32282)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

If you specify both the `all` and `initialRun` options when running `nx
watch`, `initialRun` have no effect.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

The command should be called once at the beginning even if there are no
file changes.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #32281
2026-02-26 11:45:51 -05:00
Anurag Agarwal dcfc2134d4 fix(maven): fix set the pom file without changing base directory (#34182)
## Current Behavior
nx-maven-plugin 0.0.12 is also changing the base directory along with
the pom file when the plugins like flatten-maven-plugin /
maven-shade-plugin produces pomFile in different directory other than
the base directory

## Expected Behavior
plugin should only update the pom file and not the base directory
location

## Related Issue(s)

Fixes #34181

https://github.com/mojohaus/flatten-maven-plugin/issues/50

Co-authored-by: anurag.ag <anuragagarwal561994@users.noreply.github.com>
2026-02-26 11:20:42 -05:00
Jack Hsu 77100fac5d docs(misc): add Requirements sections to all technology intro pages (#34613)
## Current Behavior

Technology introduction pages have inconsistent or missing version
requirements information. Some pages have no Requirements section,
others use ad-hoc formats (asides, bullet lists under Prerequisites),
and page titles follow different naming conventions ("Overview of the Nx
X Plugin", "Nx X Plugin Overview", "Introduction - X", etc.).

## Expected Behavior

Every technology introduction page now has a standardized Requirements
section with:
- A version support table using consistent semver range format
- Code-formatted package names in the `Package` column
- An intro sentence identifying the Nx plugin (e.g. "The `@nx/react`
plugin supports the following package versions.")
- A note linking to [code generation docs](/docs/features/generate-code)
for auto-installed packages
- Consistent **"X Plugin for Nx"** page title pattern across all intro
pages

Additional changes:
- Deleted the standalone Node.js/TypeScript compatibility page, inlining
its content into the respective plugin intro pages
- Created a proper introduction page for Angular Rsbuild (previously
linked directly to `createConfig` API reference)
- Added Requirements tables to Java, Gradle, and Maven pages with system
dependency versions
- Updated sidebar links and redirects for removed/moved pages
- Applied style guide fixes across all edited pages (removed "allows you
to", "easily", product possessives, etc.)

## Related Issue(s)

Fixes DOC-423
2026-02-26 10:42:51 -05:00
Leosvel Pérez Espinosa b1614d7504 feat(angular): add support for Angular v21.2 (#34592)
## Current Behavior

Nx doesn't support Angular v21.2.

## Expected Behavior

Nx should support Angular v21.2.
2026-02-26 10:23:53 -05:00
Caleb Ukle 0ae45cd445 docs(nx-plugin): document special schema options (#34615)
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: barbados-clemens <barbados-clemens@users.noreply.github.com>
2026-02-26 17:56:54 +09:00
Jason Jean fcf4660389 fix(core): preserve nxCloud=skip in non-interactive CNW mode (#34616)
## Current Behavior

After #34580, `determineNxCloudV2()` returns `'skip'` in non-interactive
mode, but the caller remaps it to `nxCloud = 'yes'` with
`skipCloudConnect = true`. This causes `setupCI()` to run and generate
`.github/workflows/ci.yml` in new workspaces — which didn't happen
before.

This breaks the `extras.test.ts` e2e snapshot test because
`.github/workflows/ci.yml` now appears in the expanded default task
inputs.

## Expected Behavior

Keep `nxCloud = 'skip'` when the cloud choice is `'skip'`, which
prevents CI file generation in non-interactive mode. This restores the
behavior prior to #34580.

## Related Issue(s)

Fixes the `extras.test.ts` e2e snapshot failure introduced by #34580.
2026-02-25 23:02:58 -05:00
Louie Weng 221ea40462 chore(gradle): bump version to 0.1.13 (#34614)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

Bump project graph plugin version.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 21:50:07 +00:00
Leosvel Pérez Espinosa 12812dc994 cleanup(core): cache compiled glob sets to avoid redundant recompilation (#34602)
## Current Behavior

`build_glob_set` recompiles identical glob pattern sets on every call,
even when the same set of patterns has been compiled before.

## Expected Behavior

Compiled `NxGlobSet` instances are cached in a static `DashMap` keyed by
sorted glob strings. Repeated calls with the same patterns return a
shared `Arc<NxGlobSet>` instead of recompiling. Profiling `nx run-many
-t build lint test --parallel 8` in the Nx repo measured 95.6% cache hit
rate (9,758 of 10,202 calls) with only 444 unique pattern sets, reducing
hashing-phase CPU by ~40%.
2026-02-25 15:52:30 -05:00
Jack Hsu 4c3812f731 fix(nx-dev): move redirects from Next.js config to Netlify _redirects (#34612)
## Current Behavior

All 1,200+ redirect rules are processed by the Next.js serverless
function via the `redirects()` config in `next.config.js`. Every
redirect request requires a cold start of the serverless function, which
contributed to the 10-minute outage reported in DOC-415.

## Expected Behavior

Redirects are handled at the Netlify CDN edge via a plain `_redirects`
file, which is faster and doesn't depend on the Next.js serverless
function being healthy.

- Converted all redirect rules from `redirect-rules.js` and
`redirect-rules-docs-to-astro.js` into Netlify `_redirects` format
(1,231 rules)
- Expanded Next.js regex group patterns (e.g. `/(l|latest)/...`) into
individual Netlify rules since Netlify doesn't support regex
- Converted `:path*` wildcards to Netlify `*`/`:splat` syntax
- Rewrites (Astro docs proxy) remain in `next.config.js` as they require
server-side processing
- Original JS redirect files kept for reference (can be removed in
follow-up)

## Related Issue(s)

Fixes DOC-415
2026-02-25 15:49:44 -05:00
Leosvel Pérez Espinosa 098a830e5d fix(core): use scoped cache key for unresolved npm imports in TargetProjectLocator (#34605)
## Current Behavior

`TargetProjectLocator.findProjectFromImport` stores `null` for
unresolved imports using a bare `importExpr` key, but
`findNpmProjectFromImport` looks up cache entries using
`${packageName}__${dirPath}`. The key mismatch means repeated lookups
for the same import+directory re-run the full resolution waterfall
(typescript + require.resolve) instead of returning the cached `null`.

## Expected Behavior

Store `null` for unresolved imports using the same
`${packageName}__${dirPath}` key that `findNpmProjectFromImport` uses
for lookups. Repeated lookups for already-failed imports skip the
expensive resolution steps.

Also removes an unused cache write for builtin module imports as a minor
cleanup.
2026-02-25 15:47:54 -05:00
Jason Jean f31e7a75be fix(core): handle FORCE_COLOR=0 with picocolors (#34520)
## Current Behavior

After migrating from chalk to picocolors (#34305), `FORCE_COLOR=0` no
longer disables colors. picocolors checks `!!env.FORCE_COLOR`, and since
`!!"0"` is `true` in JavaScript, it treats `FORCE_COLOR=0` as "enable
colors."

This breaks CI environments and tools like Homebrew that set
`FORCE_COLOR=0` to get plain text output.

## Expected Behavior

`FORCE_COLOR=0` should disable ANSI color output, matching the previous
chalk behavior and the [FORCE_COLOR spec](https://force-color.org/).

## Related Issue(s)

Fixes #34387

Upstream issue filed:
https://github.com/alexeyraspopov/picocolors/issues/100
2026-02-25 15:47:24 -05:00
Louie Weng 09c44a637a fix(gradle): use globs for dependent task output files (#34590)
## Current Behavior

When processing Gradle tasks, Nx tracks dependent task output files by
recording individual file paths for each output. This can lead to
incorrect cache invalidation behavior since we are prefixing the paths
unnecessarily. We will therefore never match.

## Expected Behavior

Nx now consolidates dependent task output files using glob patterns
based on file extensions (e.g., **/*.jar, **/*.class). This focuses on
the types of files produced rather than their specific paths. The
approach groups all output files by extension and generates a single
glob pattern per extension, reducing the complexity of input tracking
while maintaining correctness.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #Q-247
2026-02-25 15:02:34 -05:00
Louie Weng dc81b8bbd6 fix(gradle): ensure that atomized task targets have dependsOn (#34611)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

The dependsOn of atomized tasks should match the base non-atomized task.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes Q-174
2026-02-25 19:03:42 +00:00
Jack Hsu f7e46e33e9 feat(core): add explicit cloud opt-out to CNW (#34580)
## Current Behavior

The CNW cloud prompt is locked to auto-select deferred connection
(CLOUD-4255), always generating a short URL but never writing nxCloudId
to nx.json. Users have no explicit choice.


## Expected Behavior

The cloud prompt now offers three explicit choices:
- Yes: connect now, generate nxCloudId in nx.json, show strong
completion message
- Skip for now: deferred connection (no nxCloudId), still show short URL
and update README
- No: full opt-out, set neverConnectToCloud: true in nx.json, no URL, no
README update, no cloud messaging

**CLI args:** `--nxCloud=skip`, `--nxCloud=never` (new),
`--nxCloud=yes`. Non-interactive defaults to skip.

Closes CLOUD-4242
2026-02-25 13:56:03 -05:00
Jason Jean 5d64f726d6 chore(core): add tests for large directory file watching (#34601)
## Current Behavior

PR #34523 fixed the macOS file watcher issue (#34522) but did not
include comprehensive test coverage.

## Expected Behavior

Tests should verify that the fix works correctly and catch any future
regressions.

## Related Issue(s)

Adds test coverage for #34523 and #34522

---

## Changes

**TypeScript integration test**
(`packages/nx/src/native/tests/watcher.spec.ts`):

Added **"should detect file changes in large directory structures"** - a
comprehensive integration test that:
1. Creates 10,000+ directories simulating a monorepo-scale workspace
2. Starts a real `Watcher` instance
3. Creates and modifies files deep in the directory tree
4. Verifies that file change events are actually delivered

This test validates the actual behavior users care about - that file
watching works reliably in large repos - rather than testing
implementation details. It would catch any regression where events fail
to be delivered at scale.

## Testing

TypeScript integration test validates the actual bug fix - that file
events are delivered reliably in large directory structures with 10,000+
directories.
2026-02-25 13:36:48 -05:00
Nikola Kalinov 1e1a8a7a40 fix(vite): isPreview=true for Vite Preview server (#34597)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
Given the following Vite config:
```ts
import { defineConfig } from 'vite';

export default defineConfig((config) => {
  console.log(config);
  return {};
});
```

`npx nx preview` logs:
```ts
{
  mode: 'development',
  command: 'build',
  isSsrBuild: false,
  isPreview: false
}
```

## Expected Behavior
`npx nx preview` should log:
```ts
{
  mode: 'development',
  command: 'build',
  isSsrBuild: false,
  isPreview: true
}
```

## Related Issue(s)
https://github.com/vitejs/vite/issues/15694

Fixes #
2026-02-25 13:27:05 -05:00
Leosvel Pérez Espinosa 872b9c9045 fix(core): remove unused getTerminalOutput from BatchProcess (#34604)
## Current Behavior

`BatchProcess` accumulates all stdout/stderr output in
`terminalOutputChunks` and exposes it via `getTerminalOutput()`, but
nothing ever calls `getTerminalOutput()`. The accumulated strings are
unique allocations (created via `chunk.toString()`), not shared with the
output callbacks or `process.stdout.write`.

For verbose batched tasks (e.g., Maven/Gradle with hundreds of tasks),
this can hold tens to hundreds of MB for the entire batch duration.

## Expected Behavior

Remove the dead accumulation code. stdout/stderr chunks are still
forwarded to `process.stdout`/`process.stderr` and output callbacks as
before — only the unused storage is removed.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-02-25 12:39:17 -05:00
Jack Hsu 1e3f8e00f7 fix(js): remove redundant vite.config.ts generation for vitest projects (#34603)
## Current Behavior

When generating a library with vitest as the test runner and a non-vite
bundler (e.g. `tsc`), the js library generator creates two config files:
- `vitest.config.mts` (from the vitest `configurationGenerator`) with
`root: __dirname`
- `vite.config.ts` (from a second `createOrEditViteConfig` call) with
`root: import.meta.dirname`

The redundant `vite.config.ts` uses ESM-only `import.meta.dirname`
syntax, which causes TS1470 when the project targets CommonJS output:

```
vite.config.ts:5:9 - error TS1470: The 'import.meta' meta-property is not allowed in files which will build into CommonJS output.

5   root: import.meta.dirname,
          ~~~~~~~~~~~
```

## Expected Behavior

Only `vitest.config.mts` should be generated. The vitest
`configurationGenerator` already handles creating the correct config
file with `root: __dirname`. The second `createOrEditViteConfig` call
from `@nx/vite` was redundant and produced the conflicting file.

## Related Issue(s)

Fixes #34399
2026-02-25 12:27:22 -05:00
Philip Fulcher 919907cf0f docs(nx-dev): add foundations article (#34599) 2026-02-25 11:23:22 -06:00
Charlie Croom d5cd6a1a56 fix(core): use recursive FSEvents on macOS instead of non-recursive kqueue (#34523)
## Current Behavior

Since Nx 22.5.0, the daemon's native file watcher silently drops all
file change events on macOS in large monorepos (~5,250+ watched
directories). `nx watch`, `nx serve`, and any daemon-dependent file
watching is broken.

The root cause is that #34329 switched all watched paths to
`WatchedPath::non_recursive()`. On macOS, the `notify` crate uses
**kqueue** for non-recursive watches instead of **FSEvents**. kqueue
silently fails at scale due to vnode table pressure (`kern.num_vnodes ==
kern.maxvnodes`), causing the daemon to never detect file changes.

This is a **scale-dependent** bug: it works fine in small workspaces
(~30 directories) but breaks silently in large ones.

| | **Nx 22.4.5** | **Nx 22.5.0+** |
|---|---|---|
| **Small repo (~30 dirs)** | Works (FSEvents) | Works (~30 kqueue
watches) |
| **Large repo (~5,250+ dirs)** | Works (FSEvents) | **Broken** (kqueue
silently drops all events) |

## Expected Behavior

The macOS file watcher should detect file creates, modifications, and
deletions at any scale, matching the behavior of Nx 22.4.x.

## Fix

Use platform-conditional watch modes:
- **macOS:** Single recursive watch on the workspace root (uses FSEvents
natively)
- **Linux/Windows:** Non-recursive per-directory watches (preserves the
#33781 inotify fix)

On macOS, FSEvents handles recursive watching from a single root path,
so directory enumeration and dynamic registration are skipped entirely.
This also improves daemon startup time on macOS from ~10 minutes to <1
second in a 354-project monorepo.

### What changed in `watcher.rs`

1. **Initial pathset:** On macOS, watch only the root directory
recursively via FSEvents instead of enumerating all directories for
non-recursive kqueue watches.
2. **Dynamic directory registration (`on_action`):** Wrapped in
`#[cfg(not(target_os = "macos"))]` since FSEvents already watches the
full tree.

Linux/Windows behavior is completely unchanged.

### Why the event filter is fine as-is

We verified that with recursive FSEvents watches, macOS emits specific
`FileEventKind` variants (`Create(File)`, `Modify(Data(Content))`,
`Remove(File)`, `Modify(Name(Any))`) that the current
`watch_filterer.rs` already handles correctly. Zero events were rejected
by the catch-all. The `Modify(Any)` / `Create(Any)` variants are kqueue
artifacts that are not needed with FSEvents.

### Why kqueue fails silently

Apple's [File System Events Programming
Guide](https://developer.apple.com/library/archive/documentation/Darwin/Conceptual/FSEvents_ProgGuide/KernelQueues/KernelQueues.html)
explicitly recommends FSEvents over kqueue for large hierarchies: *"If
you are monitoring a large hierarchy of content, you should use file
system events instead."* kqueue requires `open(path, O_EVTONLY)` per
watched directory. Under vnode table pressure, the kernel recycles
vnodes with kqueue watches attached without notifying the watcher. There
is no error, no partial delivery, and no diagnostic signal.

## Tested on

- macOS 26.3 (Tahoe), Apple Silicon (arm64), APFS
- 354-project pnpm monorepo (~19,865 non-ignored directories)
- Verified: file modifications, file creates, and file deletes all
detected
- Daemon init time: ~10 min (with enumeration) -> <1s (with root-only
FSEvents watch)

## Related Issue(s)

Fixes #34522

Co-authored-by: Amp <amp@ampcode.com>
2026-02-25 09:45:31 -05:00
Caleb Ukle 700c98fcaf fix(nx-dev): correct interpolate sub command for cli reference (#34585)
also adding e2e for command hierarchy

<img width="823" height="362" alt="image"
src="https://github.com/user-attachments/assets/3db98945-4221-4bf3-8b92-9d5b25eb2444"
/>

<img width="778" height="349" alt="image"
src="https://github.com/user-attachments/assets/432ebd61-c34e-40f5-b95a-f8d73c682da4"
/>


![wm_2026-02-24T14-11-07@2x](https://github.com/user-attachments/assets/a3d635a3-b928-4ff2-a147-28f0873d26c2)
2026-02-25 14:30:34 +00:00
Colum Ferry df9eb0bf10 fix(release): add null-safe fallback for version in createGitTagValues (#34598)
## Current Behavior

When nx release runs with docker-configured projects (either via
explicit config or
@nx/docker plugin inference), git tags are created with the literal
string {version}
instead of the actual version number (e.g., v{version} instead of
v1.0.6, or
  app-3@{version} instead of app-3@1.0.0).

  This happens because:

1. If ANY project in a release group has docker config,
preferDockerVersion is auto-set to
  true for the ENTIRE group
2. createGitTagValues() then blindly selects
projectVersionData.dockerVersion, which is
  null for non-docker projects (or projects with no changes)
3. The interpolate() function receives null for {version} and returns
the literal
  placeholder unchanged

Commit messages are unaffected because createCommitMessageValues() only
uses newVersion and
already guards against null. The changelog code (changelog.ts:1117-1121)
also already has
  the correct null-safe pattern.

 ## Expected Behavior

When preferDockerVersion is true but dockerVersion is null, git tags
should fall back to
using newVersion instead of producing literal {version} placeholders.
When both versions
  are null, no tag should be created.

For mixed release groups (some projects have docker config, some don't),
the auto-enable
logic should use 'both' mode instead of true, which already has proper
null-safe checks for
   each version type.

 ## Changes

- shared.ts: Added null-safe fallback (??) in createGitTagValues() for
both independent and
fixed group code paths, plus a guard to skip tag creation when both
versions are null
- config.ts: Refined auto-enable logic to check whether ALL or only SOME
projects have
  docker config — mixed groups now get 'both' mode instead of true
- shared.spec.ts: Added 5 test cases covering null version fallback
scenarios for fixed
groups, independent groups, both-null, reverse fallback, and mixed
groups

 ## Related Issue(s)

  Fixes #34382
  Fixes #33890
  Fixes #34391
2026-02-25 09:10:01 -05:00
MaxKless 4e55f9aa32 docs(misc): update nx download stats (#34596)
## Current Behavior
The "7. Thriving Community" section on
`nx.dev/docs/guides/adopting-nx/from-turborepo` displayed an outdated Nx
download statistic (~5 million downloads per week).

## Expected Behavior
The "7. Thriving Community" section on
`nx.dev/docs/guides/adopting-nx/from-turborepo` now reflects the latest
Nx download statistic (~9 million downloads per week).

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: MaxKless <MaxKless@users.noreply.github.com>
2026-02-25 08:26:01 -05:00
Jack Hsu 127255aa96 fix(bundling): fix regression on process.env usage for webpack (#34583)
When we optimized the `process.env` values to not embed the full object
unnecessarily, we also regressed in cases where users do use
`process.env` instead of `process.env["NX_PUBLIC_FOO"]`.

## Current behavior

Users cannot use `process.env` and must access each key individuall.
Although the serializing the full object can bloat bundle sizes, we also
don't want to break existing apps unnecessarily.

## Expected behavior

Existing apps should continue to work as usual.

## Related issues

Fixes #34279
2026-02-25 08:16:05 -05:00
Kai Gritun b46de60cd9 fix(js): guard against undefined closest node in rehoistNodes (#34347)
## Current Behavior

When running `@nx/js:prune-lockfile` on a monorepo with transitive
dependencies that have multiple versions where neither version is
reachable from a direct dependency in package.json, the executor throws:

```
NX   An error occurred while creating pruned lockfile

Original error: Cannot read properties of undefined (reading 'name')

TypeError: Cannot read properties of undefined (reading 'name')
    at switchNodeToHoisted (node_modules/nx/src/plugins/js/lock-file/project-graph-pruning.js:165:31)
```

## Expected Behavior

The lockfile pruning should complete without crashing, even when some
transitive dependencies cannot be traced back to a direct dependency.

## Root Cause

In `rehoistNodes()`, when there are multiple nested nodes for a package,
the code finds the "closest" node by computing `pathLengthToIncoming()`
for each. However, when none of the nested nodes have a path to any
direct dependency in package.json, `pathLengthToIncoming()` returns
`undefined` for all of them. Since `undefined < Infinity` is `false` in
JavaScript, `closest` remains `undefined`, and then
`switchNodeToHoisted(undefined, ...)` crashes.

## Fix

Add a guard to only call `switchNodeToHoisted()` when a closest node was
actually found:

```typescript
if (closest) {
  switchNodeToHoisted(closest, builder, invBuilder);
}
```

This allows the pruning to continue - the nested nodes simply won't be
rehoisted if no closest node can be determined.

## Related Issue

Fixes #34322

## Test Added

Added a unit test that verifies `rehoistNodes()` doesn't crash when
nested nodes have no path to package.json dependencies.
2026-02-25 13:28:19 +01:00
Tomas Ptacek 736551590a fix(angular-rspack): exclude .json files from JS/TS regex patterns (#34195)
## Current Behavior

When importing a `package.json` file in an Angular application built
with `@nx/angular-rspack`, the build fails with a Babel syntax error if
the `package.json` contains `@angular/*` dependencies:

```
SyntaxError: /path/to/package.json: Missing semicolon. (2:10)

  1 | {
> 2 |     "name": "@org/app",
    |           ^
  3 |     "version": "4.0.2",
  4 |     "dependencies": {
  5 |         "@angular/platform-browser": "20.3.7",
```

This happens because the `JS_ALL_EXT_REGEX` pattern
`/\.[cm]?(js)[^x]?\??/` incorrectly matches `.json` files. When the JSON
file content contains `@angular` strings, the
`angular-partial-transform-loader` attempts to process it through Babel,
which fails because JSON is not valid JavaScript.

**Root cause:** The regex `[^x]?` (optional character that is NOT 'x')
allows `.json` to match because 'o' is not 'x'.

## Expected Behavior
- `.json` files should NOT match `JS_ALL_EXT_REGEX` or
`TS_ALL_EXT_REGEX`
- Importing `package.json` in Angular applications should work correctly
- All existing matches for `.js`, `.jsx`, `.mjs`, `.cjs` (and TypeScript
equivalents) should continue to work

## Related Issue(s)
https://github.com/nrwl/nx/issues/32649
2026-02-25 09:55:15 +00:00
MaxKless e031d024ef chore(repo): update @nx/graph to 1.0.4 (#34558) 2026-02-25 18:27:42 +09:00
Jason Jean d042483a3f chore(gradle): clean up project.json configurations (#34587)
## Current Behavior

The Gradle projects have redundant and inconsistent project.json
configurations:
- `batch-runner` has its own project.json with duplicate targets
- `project-graph` has duplicate test/lint/format targets
- Implicit dependency syntax is inconsistent between projects
- e2e project has unnecessary implicitDependencies

## Expected Behavior

Cleaner, more maintainable project structure:
- Consolidated batch-runner configuration into parent project
- Removed duplicate targets from project-graph
- Consistent implicit dependency syntax using project name format
(`:project-name`)
- Streamlined e2e project configuration

## Related Issue(s)

N/A - Internal cleanup
Closes Q-173

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
Co-authored-by: Louie Weng <56288712+lourw@users.noreply.github.com>
2026-02-24 22:50:08 -05:00
Berend de Boer 39f252df97 docs(misc): add link to new nx-knip plugin (#34011)
This allows you to run knip against your typescript and javascript
projects.

Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
2026-02-24 22:31:27 -05:00
Aude Planchamp 3f70561586 docs(misc): incorrect tsconfig inheritance described in TypeScript project references documentation (#34124)
## Current Behavior

Documentation bugfix on

https://nx.dev/docs/concepts/typescript-project-linking#set-up-typescript-project-references

In the section about setting up TypeScript project references, the
documentation currently states:

"Each project's tsconfig.lib.json file extends the project's
tsconfig.json file and adds references to the tsconfig.lib.json files of
project dependencies."

## Expected Behavior

In a standard Nx workspace configuration, tsconfig.lib.json extends the
workspace-level tsconfig.base.json, not the project-level tsconfig.json
(and the example provided just after is correct).

Suggested correction:

"Each project's tsconfig.lib.json file extends the workspace
tsconfig.base.json file and adds references to the tsconfig.lib.json
files of project dependencies."

## Related Issue(s)

Fixes  #34118

---------

Co-authored-by: Aude Planchamp <aude.planchamp@ekino.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
2026-02-24 22:31:13 -05:00
Miguel b72a203ed7 fix(release): allow null values in schema of dockerVersion (#34171)
<!-- 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 -->

TS file documents `projectVersionData.dockerVersion` as having type
`string | undefined`. However, code behaves differently. For instance,
[here](https://github.com/nrwl/nx/blob/e57848cea748b85f17e5fc704b901975a8424c4d/packages/nx/src/command-line/release/version/release-group-processor.ts#L128)
and
[here](https://github.com/nrwl/nx/blob/e57848cea748b85f17e5fc704b901975a8424c4d/packages/nx/src/command-line/release/utils/shared.ts#L294)
it is setting as and comparing against `null`.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

Schema has value that code sets (`null`)

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes https://github.com/nrwl/nx/issues/34172
2026-02-24 22:30:24 -05:00
Craigory Coppola 1ecf0fb6a7 fix(core): reject pending promises directly when plugin worker exits unexpectedly (#34588)
When a plugin worker process exits unexpectedly, the exit handler
previously sent synthetic `loadResult` messages to all pending response
handlers. If any handler was waiting for a different result type (e.g.
`createNodesResult`), the type validation would reject with a confusing
"Expected createNodesResult, got loadResult" error instead of surfacing
the actual cause.

Split response handlers into `onMessage` / `onError` callbacks so the
exit handler can reject each pending promise directly with a clear
"Plugin worker exited unexpectedly" error.

Also use unique transaction IDs for `load` messages (via `generateTxId`)
to avoid potential handler overwrites during worker restarts.

Fixes #34564
2026-02-24 17:44:40 -05:00
Jason Jean c743313078 chore(repo): update nx to 22.6.0-beta.3 (#34579)
Updating Nx from 22.6.0-beta.2 to 22.6.0-beta.3
2026-02-24 17:36:31 -05:00
Jason Jean 1bbe936513 chore(repo): disable CI continuous assignment (#34578)
## Summary

Testing CI behavior with continuous assignment disabled and cache bust
set to 4.

This is part of investigating flakiness potentially related to
continuous assignment in CI.

## Test plan

- Monitor CI execution behavior
- Compare with other test branches (bust=2, bust=3)
2026-02-24 17:34:24 -05:00
Jack Hsu c966e20746 docs(misc): dedupe and clean up getting started pages (#34521)
## Current Behavior

Documentation pages across Getting Started, How Nx Works, and Platform
Features sections contain:

1. Duplicated content — mental-model.mdoc has a ~70-line caching section
and a ~20-line DTE section that are near-verbatim
copies of how-caching-works.mdoc and distribute-task-execution.mdoc
respectively. remote-cache.mdoc re-explains local caching
 in its intro instead of linking to the canonical page.
2. Missing cross-reference links — Key concepts like "affected command",
"remote cache", "project graph", and "task pipeline
configuration" are mentioned without linking to their dedicated pages.
3. Style guide violations — Trust-undermining words ("simply", "just",
"straightforward"), anti-AI phrases ("Let's take",
"Whether you're..."), product possessives ("Nx's"), customer perspective
issues ("allows you to"), and em dashes appear
across Getting Started and How Nx Works pages.

## Expected Behavior

1. Content consolidation — mental-model.mdoc is trimmed by ~85 lines,
keeping the concept + images and linking to dedicated
pages for details. remote-cache.mdoc intro references the canonical
caching page. publish-conformance-rules-to-nx-cloud.mdoc
deduplicates its intro. maintain-typescript-monorepos.mdoc shortens its
inferred tasks re-explanation.
2. Cross-reference links added — First-mention links for affected,
remote cache, computation caching, task pipeline
configuration (in mental-model) and project graph (in self-healing-ci).
3. Style guide compliance — 18 fixes across 10 Getting Started and How
Nx Works pages, removing banned phrases and aligning
with the new STYLE_GUIDE.md.
4. Sidebar improvements — Cache Task Results added after Run Tasks in
Platform Features; Maintain TypeScript Monorepos moved
to first in KB > TypeScript.

## Pages changed
```
┌─────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────┐
│     Section     │                                              Pages                                              │
├─────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Getting Started │ intro, index, nx-cloud, ai-setup, start-with-existing-project                                   │
├─────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤
│ How Nx Works    │ mental-model, how-caching-works, task-pipeline-configuration, nx-plugins, nx-daemon             │
├─────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Features        │ remote-cache, self-healing-ci, maintain-typescript-monorepos, cache-task-results (sidebar only) │
├─────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Enterprise      │ publish-conformance-rules-to-nx-cloud                                                           │
└─────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────┘
```
2026-02-24 16:42:31 -05:00
Miroslav Jonaš f42976f852 fix(repo): remove chalk from e2e tests (#34570)
<!-- 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-02-24 15:39:27 -05:00
Leosvel Pérez Espinosa 6d90572257 fix(core): show the correct status for stopped continuous tasks (#34226)
## Current Behavior

### 1. Continuous tasks missing from `postTasksExecution` hook

When running continuous tasks (e.g., `nx serve app`) and stopping them
with Ctrl+C, the `postTasksExecution` lifecycle hook does not include
them in `taskResults`. This breaks plugins that rely on post-run
statistics (e.g., uploading task stats to DataDog).

### 2. Confusing TUI status when sibling continuous task exits

When multiple continuous tasks run together and one exits unexpectedly,
its sibling is marked as "failed" even though it was intentionally
terminated/stopped by the task orchestrator.

## Expected Behavior

1. All tasks, including continuous ones, are included in `taskResults`
for the `postTasksExecution` hook
2. Continuous tasks that are intentionally stopped (because dependent
tasks completed or during graceful shutdown) report as `success` with
`Stopped` display status
3. Continuous tasks that exit unexpectedly (crash) report as `failure`
4. TUI summary shows correct status: success when all tasks completed
successfully, square icon for stopped tasks

## Related Issue(s)

Fixes https://github.com/nrwl/nx/issues/33561

Supersedes:

- https://github.com/nrwl/nx/pull/33562
- https://github.com/nrwl/nx/pull/34132
2026-02-24 13:48:52 -05:00
Rares Matei f84ec34cbd chore(repo): enable signal file writing (#34572)
Add NX_CLOUD_IO_TRACING_DIRECTORY environment variable.

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes Q-245
2026-02-24 17:34:51 +00:00
Juri 566a370375 docs(core): move synthetic monorepos back to "How Nx Works" sidebar section 2026-02-24 18:02:29 +01:00
Juri Strumpflohner 4c1dd1e5ed docs(core): add synthetic monorepos page (#34565)
## Current Behavior

No documentation exists explaining the concept of synthetic monorepos —
how they bridge polyrepo and monorepo setups by connecting separate
repositories into a unified dependency graph.


https://deploy-preview-34565--nx-docs.netlify.app/docs/concepts/synthetic-monorepos

## Expected Behavior

New concept page under "How Nx Works" that explains:
- What synthetic monorepos are (unified graph across separate repos
without moving code)
- Why they matter for humans (visibility, cross-repo coordination) and
AI agents (seeing beyond repo boundaries)
- What they provide (cross-repo graph, actionable tooling, AI agent
enablement)
- How they serve as a gradual entry point toward deeper monorepo
adoption

## Related Issue(s)

N/A — new documentation page based on existing content from webinars and
internal knowledge.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: juristr <juristr@users.noreply.github.com>
2026-02-24 16:51:20 +01:00
Loëck Vézien 8f64e844c9 feat(core): add yarn berry catalog support (#34552)
## Current Behavior

The Nx catalog system (`catalog:` protocol) only supports pnpm
workspaces. Yarn Berry (v4+) introduced [catalog
support](https://yarnpkg.com/features/catalogs) in `.yarnrc.yml`, but Nx
does not recognize `catalog:` references in Yarn workspaces. This means
Yarn Berry users cannot benefit from Nx's catalog-aware dependency
resolution, validation, or version updating.

## Expected Behavior

Nx should support Yarn Berry's `catalog:` protocol, just like it does
for pnpm. With this PR:

- `catalog:` and `catalog:<name>` references in `package.json`
dependencies are correctly resolved against `.yarnrc.yml` definitions
for Yarn Berry workspaces
- Validation provides helpful error messages with suggestions (missing
catalog, missing package, duplicate default definitions)
- Catalog versions can be updated programmatically via
`updateCatalogVersions`
- The `CatalogManager` interface is generalized with
`getCatalogDefinitionFilePaths()` and `CatalogDefinitions` to support
multiple package managers cleanly

### Changes

- **`YarnCatalogManager`** — New manager that reads `catalog:` /
`catalogs:` from `.yarnrc.yml`, mirroring the pnpm implementation with
Yarn-specific config paths and error messages
- **`yarn-workspace.ts`** — Type definitions for Yarn's `.yarnrc.yml`
catalog structure (`YarnWorkspaceYaml`, `YarnCatalogEntry`)
- **`manager-factory.ts`** — Registers `YarnCatalogManager` for `yarn`
package manager
- **`manager.ts`** — Adds `getCatalogDefinitionFilePaths()` to the
`CatalogManager` interface; moves `formatCatalogError` here from
`types.ts` (runtime function doesn't belong with type-only exports)
- **`types.ts`** — Adds generic `CatalogDefinitions` interface so
consumers don't need to depend on package-manager-specific types
- **`pnpm-manager.ts`** — Implements the new
`getCatalogDefinitionFilePaths()` method; import cleanup
- **592-line test suite** covering parsing, resolution, validation
(named catalogs, default catalog, dual-definition errors, missing
catalogs/packages with suggestions), and `updateCatalogVersions`

### Context

I'm currently applying a patch on the compiled `@nx/devkit` package in
my Yarn Berry project to get catalog support while waiting for upstream
support:

<details>
<summary>Current workaround patch on <code>@nx/devkit</code></summary>

```diff
diff --git a/src/utils/catalog/manager-factory.js b/src/utils/catalog/manager-factory.js
index 6216749..d46e242 100644
--- a/src/utils/catalog/manager-factory.js
+++ b/src/utils/catalog/manager-factory.js
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
 exports.getCatalogManager = getCatalogManager;
 const devkit_exports_1 = require("nx/src/devkit-exports");
 const pnpm_manager_1 = require("./pnpm-manager");
+const yarn_manager_1 = require("./yarn-manager");
 /**
  * Factory function to get the appropriate catalog manager based on the package manager
  */
@@ -11,6 +12,8 @@ function getCatalogManager(workspaceRoot) {
     switch (packageManager) {
         case 'pnpm':
             return new pnpm_manager_1.PnpmCatalogManager();
+        case 'yarn':
+            return new yarn_manager_1.YarnCatalogManager();
         default:
             return null;
     }
diff --git a/src/utils/catalog/yarn-manager.js b/src/utils/catalog/yarn-manager.js
new file mode 100644
index 0000000..048fdec
--- /dev/null
+++ b/src/utils/catalog/yarn-manager.js
@@ -0,0 +1,102 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.YarnCatalogManager = void 0;
+const node_fs_1 = require("node:fs");
+const node_path_1 = require("node:path");
+const devkit_exports_1 = require("nx/src/devkit-exports");
+const devkit_internals_1 = require("nx/src/devkit-internals");
+class YarnCatalogManager {
+    constructor() {
+        this.name = 'yarn';
+        this.catalogProtocol = 'catalog:';
+    }
+    isCatalogReference(version) {
+        return version.startsWith(this.catalogProtocol);
+    }
+    parseCatalogReference(version) {
+        if (!this.isCatalogReference(version)) { return null; }
+        return { catalogName: undefined, isDefaultCatalog: true };
+    }
+    getCatalogDefinitions(treeOrRoot) {
+        if (typeof treeOrRoot === 'string') {
+            const p = (0, node_path_1.join)(treeOrRoot, '.yarnrc.yml');
+            if (!(0, node_fs_1.existsSync)(p)) { return null; }
+            return readYamlFileFromFs(p);
+        } else {
+            if (!treeOrRoot.exists('.yarnrc.yml')) { return null; }
+            return readYamlFileFromTree(treeOrRoot, '.yarnrc.yml');
+        }
+    }
+    resolveCatalogReference(treeOrRoot, packageName, version) {
+        if (!this.parseCatalogReference(version)) { return null; }
+        const config = this.getCatalogDefinitions(treeOrRoot);
+        if (!config || !config.catalog) { return null; }
+        return config.catalog[packageName] || null;
+    }
+    validateCatalogReference(treeOrRoot, packageName, version) {
+        if (!this.parseCatalogReference(version)) {
+            throw new Error(`Invalid catalog reference: "${version}"`);
+        }
+        const config = this.getCatalogDefinitions(treeOrRoot);
+        if (!config) { throw new Error('No .yarnrc.yml found'); }
+        if (!config.catalog) { throw new Error('No catalog in .yarnrc.yml'); }
+        if (!config.catalog[packageName]) {
+            throw new Error(`"${packageName}" not in .yarnrc.yml catalog`);
+        }
+    }
+    updateCatalogVersions() {}
+}
+exports.YarnCatalogManager = YarnCatalogManager;
+function readYamlFileFromFs(path) {
+    try { return (0, devkit_internals_1.readYamlFile)(path); }
+    catch (e) {
+        devkit_exports_1.output.warn({ title: 'Unable to parse .yarnrc.yml', bodyLines: [e.toString()] });
+        return null;
+    }
+}
+function readYamlFileFromTree(tree, path) {
+    const content = tree.read(path, 'utf-8');
+    const { load } = require('@zkochan/js-yaml');
+    try { return load(content, { filename: path }); }
+    catch (e) {
+        devkit_exports_1.output.warn({ title: 'Unable to parse .yarnrc.yml', bodyLines: [e.toString()] });
+        return null;
+    }
+}
```

</details>

This PR replaces that workaround with a proper TypeScript implementation
including full test coverage, named catalog support, helpful error
messages, and `updateCatalogVersions` support.

## Related Issue(s)

<!-- No existing issue found for Yarn Berry catalog support — this PR
introduces the feature -->
2026-02-24 10:34:40 -05:00
Jack Hsu 7f7bba633d fix(bundling): add docs link to generatePackageJson error message (#34562)
## Current Behavior

When users hit the `generatePackageJson: true` error with TS Solution
Setup, the error tells them to "unset the option" but gives no guidance
on the replacement workflow.

## Expected Behavior

The error message now includes a link to the pruning guide at
https://nx.dev/docs/technologies/node/guides/deploying-node-projects so
users can immediately find the migration steps.

## Related Issue(s)

Related #30146
2026-02-24 08:31:57 -05:00
Colum Ferry 1a15ea183a fix(js): use per-invocation cache in TS plugin to fix NX_ISOLATE_PLUGINS=false (#34566)
When plugin isolation is off, concurrent createNodesV2 invocations share
the same module instance. The module-level mutable `cache` variable
caused
invocation A's `finally` block to null it out while invocation B was
still
reading from it, resulting in "Cannot read properties of null (reading
'configContexts')".

Replace the shared mutable `cache` with a Symbol-keyed Map so each
invocation gets its own isolated cache. The tsconfig disk cache is
shared
across invocations with an idempotent initialization guard.

CLOSES NXC-3971
2026-02-24 12:51:54 +00:00
Juri Strumpflohner 8c0600225a docs(nx-dev): add 'A Monorepo Is NOT a Monolith' blog post (#34567)
## Summary
- Updated version of the classic "Misconceptions about Monorepos"
article
- New sections on AI compatibility, scaling strategies (affected,
caching, distribution, atomization), and `@nx/owners`
- Custom SVG diagrams for project graph illustrations (replacing old
Medium images)
- Authors: Victor Savkin, Juri Strumpflohner

## Test plan
- [ ] Verify blog post renders correctly on preview
- [ ] Check all images load (SVGs + avif)
- [ ] Verify internal doc links resolve
- [ ] Check TOC renders properly

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: juristr <juristr@users.noreply.github.com>
2026-02-24 13:46:52 +01:00
MaxKless 832355081b feat(core): add passthrough for nx-cloud apply-locally command (#34557)
## Current Behavior
folks had to type in `nx-cloud apply-locally`

## Expected Behavior
now `nx apply-locally` works

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-02-24 19:42:41 +09:00
MaxKless 1081c320ab feat(core): add --json flag for better AX to nx list (#34551)
### Current Behavior

nx list <plugin> shows generator/executor names and descriptions in text
format only. It does not show where the plugin or its
generators/executors are located on disk, and there is no
machine-readable output option.

  ### Expected Behavior

- nx list --json outputs all local and installed plugins with their
paths and capability types
- nx list <plugin> --json outputs detailed structured JSON including
resolved paths to each generator/executor implementation and schema
  - nx list <plugin> (text mode) now also shows the plugin's root path
2026-02-24 10:34:41 +01:00
Samuel Briole bdeeb036fb fix(bundling): skip unnecessary type-check in TS Solution Setup when skipTypeCheck is true (#34493)
## Current Behavior

In TS Solution Setup, the esbuild executor forces `runTypeCheck` even
when `skipTypeCheck: true` and `declaration: false`, due to the `||
options.isTsSolutionSetup` condition in `esbuild.impl.ts` (lines 139-140
and 195).

When `declaration: false`, this type check runs in `noEmit` mode with
`ignoreDiagnostics: true` — making it **completely pointless** (no
declarations emitted, no diagnostics reported). Its only observable
effect is writing a poisoned 19-byte tsbuildinfo file that causes race
conditions with `tsc --build`.

## Expected Behavior

When `skipTypeCheck: true` and `declaration: false`, the esbuild
executor should not run type checking at all. The `isTsSolutionSetup`
override should only force type checking when declarations actually need
to be generated.

## Fix

### Primary: Skip unnecessary type check (`esbuild.impl.ts`)

```diff
  // Non-watch mode (line 195)
- if (!options.skipTypeCheck || options.isTsSolutionSetup) {
+ if (!options.skipTypeCheck || (options.isTsSolutionSetup && options.declaration)) {

  // Watch mode (lines 139-140)
- options.isTsSolutionSetup
+ (options.isTsSolutionSetup && options.declaration)
```

Only force type checking in TS Solution Setup when declarations need to
be generated. This eliminates the pointless type check entirely.

### Defense-in-depth: Prevent tsbuildinfo in `noEmit` mode
(`run-type-check.ts`)

```diff
- : { noEmit: true };
+ : { noEmit: true, composite: false };
```

Setting `composite: false` alongside `noEmit: true` prevents TypeScript
from writing tsbuildinfo files, protecting against this class of bug
from any caller of `runTypeCheck`.

## Why This is Safe

| Scenario | Before | After |
|----------|--------|-------|
| `skipTypeCheck: false`, `declaration: false`, `isTsSolutionSetup:
true` | Runs type check (noEmit) | Still runs (`!false \|\| ...` = true)
|
| `skipTypeCheck: false`, `declaration: true`, `isTsSolutionSetup: true`
| Runs type check (emitDeclarationOnly) | Still runs |
| `skipTypeCheck: true`, `declaration: true`, `isTsSolutionSetup: true`
| normalize.ts overrides skipTypeCheck to false; runs type check | Still
runs (same normalization) |
| **`skipTypeCheck: true`, `declaration: false`, `isTsSolutionSetup:
true`** | **Runs pointless type check (noEmit + ignoreDiagnostics),
writes poisoned tsbuildinfo** | **Skipped entirely** |

The only behavior change is in the last row — the case where the type
check was doing nothing useful but causing harm.

## Related Issue(s)

Fixes #34492

---------

Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
2026-02-23 22:32:01 -05:00
Jack Hsu 4b1bf5f7ba docs(node): add pruning guide for Docker deployments (#34560)
## Current Behavior

Users migrating to Nx 20's TS Solution Setup lose `generatePackageJson`
support and have no documentation on the replacement prune workflow
(`prune-lockfile`, `copy-workspace-modules`). The error message tells
them to "unset the option" but doesn't explain what to do instead.

## Expected Behavior

A dedicated guide at
`/docs/technologies/node/guides/deploying-node-projects` covers the full
prune workflow: when to use pruning vs bundling, target configuration,
Dockerfile setup, and step-by-step migration from `generatePackageJson`.

Also updated the existing bundling guide to match the same structure
(intro table, cross-links, style guide compliance). The two articles are
sister guides covering the two ways to deploy Node.js apps: bundle
everything into a single file, or prune dependencies for a
`node_modules`-based install.

Cross-links added from the bundling guide and ci-deployment guide.

## Related Issue(s)

Closes #30146

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
2026-02-23 16:00:46 -05:00
Jack Hsu f935d9a7bf docs(release): document checkAllBranchesWhen type and behavior (#34515)
## Current Behavior
The `checkAllBranchesWhen` option is documented as type `string` with a
minimal description, which does not match the actual implementation.

## Expected Behavior
Document the correct type (`boolean | string[]`) and explain the default
branch resolution behavior, the three value modes (true, false,
string[]), and when this option is useful.

## Related Issue(s)
Closes DOC-414

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
2026-02-23 15:47:41 -05:00
Jason Jean dd325d790a fix(core): retry entire SQLite transaction on DatabaseBusy (#34533)
## Current Behavior

When multiple Nx processes (task hasher, daemon, workers) access the
SQLite database concurrently, the `NxDbConnection::transaction()` method
only retries the BEGIN step using the `retry_db_operation_when_busy!`
macro. Operations executed inside the transaction and the COMMIT are not
retried, so if the database is busy during those steps, the task crashes
with:

```
Error: DB transaction operation error: SqliteFailure(Error { code: DatabaseBusy, extended_code: 5 }, Some("database is locked"))
```

This is particularly common during parallel task hashing with continuous
tasks, where `TaskDetails.recordTaskDetails()` and `RunningTasksService`
compete for write access.

## Expected Behavior

The entire transaction (begin, execute, commit) is retried as a single
unit when any step encounters a `DatabaseBusy` error. If the database is
busy during the operation or commit, the transaction is automatically
rolled back (via drop) and retried with the same exponential backoff
used everywhere else.

## Related Issue(s)

<!-- No public issue linked -->
2026-02-23 12:28:58 -05:00
Jason Jean ed786fb12c chore(repo): upgrade fast-xml-parser to 5.3.7 to fix CVE-2026-25896 (#34555)
## Current Behavior

The NPM audit CI job is failing due to a critical XSS vulnerability
(CVE-2026-25896) in `fast-xml-parser` version 4.5.3.

## Expected Behavior

The NPM audit should pass with no critical vulnerabilities.

## Related Issue(s)

Fixes the failing NPM audit CI run:
https://github.com/nrwl/nx/actions/runs/22288455713

---

This PR upgrades `fast-xml-parser` from `^4.2.7` to `^5.3.7` to address
GHSA-m7jm-9gc2-mpf2, a critical XSS vulnerability that allows entity
encoding bypass via regex injection in DOCTYPE entity names.

The package is only used in
`scripts/documentation/internal-link-checker.ts` for parsing XML
sitemaps, so the risk of this upgrade is low.
2026-02-23 12:22:36 -05:00
Jason Jean cc5eeefde9 feat(core): add preferBatch executor option (#34293)
## Current Behavior

Batch mode is binary:
- `--batch` flag → batch ALL executors that support it
- No flag → batch NOTHING

This means users of gradle/maven must always remember to pass `--batch`
to get the performance benefits.

## Expected Behavior

Plugin authors can now set `preferBatch: true` in their executor config
to indicate batch mode should be used by default. Users can still
opt-out with `--no-batch`.

Three states:
- `--batch` → batch everything
- `--no-batch` → batch nothing  
- (not specified) → use each executor's `preferBatch` preference

| `--batch` flag | `preferBatch` | Result |
|----------------|---------------|--------|
| `true`         | any           | Batch  |
| `false`        | any           | No batch |
| not set        | `true`        | Batch  |
| not set        | `false`/undefined | No batch |

## Changes

- Added `preferBatch?: boolean` to `ExecutorJsonEntryConfig` and
`ExecutorConfig` interfaces
- Updated `--batch` default from `false` to `undefined` to allow
`preferBatch` to decide
- Modified batch scheduling logic to respect `preferBatch`
- Enabled `preferBatch: true` for gradle and maven executors
- Added 5 unit tests covering all `preferBatch` scenarios

## Related Issue(s)

<!-- Link any related issues here -->
2026-02-23 12:19:15 -05:00
Jason Jean cf53d15ae5 chore(repo): update nx to 22.6.0-beta.2 (#34556)
Updating Nx from 22.6.0-beta.1 to 22.6.0-beta.2

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-02-23 11:50:54 -05:00
Mathias Schopmans 7351e21150 fix(webpack): ensure safe process.env fallback replacement (#34464)
PR #30826 introduced a fallback definition for `process.env`:

```ts
{ 'process.env': '{}' }
```

Since `DefinePlugin` performs raw textual replacement, this can generate
invalid JavaScript when user code accesses environment variables via dot
notation:

```ts
process.env.SOME_KEY
```

becomes:

```js
{}.SOME_KEY
```

`{}` is parsed as a block statement (not an object literal), resulting
in:


> Unexpected token: punc (.)

This PR updates the fallback to a parenthesized object literal:

```ts
{ 'process.env': '({})' }
```

which produces valid output:

```js
({}).SOME_KEY
```

This preserves the intended bundle-size optimization while ensuring
syntactically correct output for standard `process.env.X` access
patterns.


## Related Issue(s)
Refs #30826
Fixes #34460 

//CC @Coly010 @coolassassin
2026-02-23 16:23:21 +00:00
MaxKless c5ab66152a feat(core): add AI agent mode to nx import (#34498)
## Current Behavior

`nx import` relies on interactive prompts (enquirer) and spinners (ora)
for user interaction. AI agents cannot parse this output or respond to
prompts, making `nx import` unusable in agent workflows.

## Expected Behavior

When `isAiAgent()` is true, `nx import` now:
- Skips all interactive prompts and spinners
- Emits NDJSON progress to stdout (`starting`, `cloning`, `filtering`,
`merging`, `detecting-plugins`, `complete`)
- Returns structured `needs_input` when required args are missing (all
at once to minimize round-trips)
- Returns structured `needs_input` for plugin selection when `--plugins`
flag is not provided
- Returns structured success/error results with hints and next steps
- Supports new `--plugins` flag (`skip`/`all`/comma-separated list)

Shared AI output types extracted from `init` into
`packages/nx/src/command-line/ai/ai-output.ts` for reuse across
commands.
2026-02-23 16:51:10 +01:00
MaxKless 1805301941 fix(misc): update maven & gradle icons to java duke icon (#34508)
duke is an official and open-source icon so we'll use it
<img width="1601" height="644" alt="image"
src="https://github.com/user-attachments/assets/3e95d94e-76d6-482a-ac09-bbe17a9f076a"
/>
<img width="1191" height="516" alt="image"
src="https://github.com/user-attachments/assets/151982fa-b497-4569-bf17-26f0d064d414"
/>

---------

Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-02-23 09:33:39 -05:00
MaxKless e8633e0299 fix(core): preserve existing source properties in claude plugin config (#34499)
## Current Behavior

Running `configure-ai-agents` overwrites the entire `source` object in
`extraKnownMarketplaces['nx-claude-plugins']`, removing any user-added
properties like `ref`.

## Expected Behavior

User-added source properties (e.g. `ref`) are preserved, while `source`
and `repo` are always set to the correct values.
2026-02-23 21:45:51 +09:00
Colum Ferry 6bcaa46864 fix(angular): use SASS indented syntax in nx-welcome component when style is sass (#34510)
The nx-welcome component inline styles were always using CSS/SCSS syntax
(with braces and semicolons) regardless of the selected style option.
When --style=sass is chosen, the component now correctly uses SASS
indented syntax (no braces or semicolons) matching the expected
behavior for the .sass file format.

Fixes #33489
2026-02-23 11:50:43 +00:00
MaxKless d545472da8 feat(core): improve AX of configure-ai-agents with auto-detection (#34496)
## Current Behavior

When `configure-ai-agents` is invoked from within an AI agent (e.g.
Claude Code), it either shows an interactive multi-select prompt (which
the agent can't interact with) or requires `--agents` and
`--no-interactive` flags to work correctly. This makes the experience
awkward when AI agents call the command as part of workspace setup.

## Expected Behavior

When an AI agent is detected (via environment variables like
`CLAUDECODE`), the command now:

1. **Auto-configures the detected agent** if it's not yet configured,
partially configured, or outdated — no prompts needed
2. **Auto-updates any other outdated agents** alongside the detected one
3. **Reports non-configured agents** with a suggested `nx
configure-ai-agents --agents ...` command
4. **Reports up-to-date status** if the detected agent is already fully
configured

When `--agents` is explicitly passed, detection is ignored entirely
(existing behavior preserved). `--check` mode also works with detection
— it checks the detected agent plus all other configured agents.

Additionally:
- Strips AI agent detection env vars (`CLAUDECODE`, `CLAUDE_CODE`,
`OPENCODE`, `GEMINI_CLI`, etc.) from e2e subprocess environments to
prevent the test runner's environment from leaking into tests
- Fixes e2e tests to use `AGENTS.md` (not `GEMINI.md`) for gemini
assertions, matching what the gemini generator actually creates for
fresh installations

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-02-23 20:45:35 +09:00
Leosvel Pérez Espinosa 025db33a75 cleanup(repo): avoid unnecessary project graph recomputations (#34423)
## Current Behavior

- Cypress `start-dev-server.ts` file creates a port lock file next to
the source code
- Native temp DB files created by tests are not properly ignored
- Astro config timestamp file is not ignored

These all trigger watch file change events, which cause the project
graph to be recomputed unnecessarily.

## Expected Behavior

Output files should not trigger watch file change events. The project
graph should not be recomputed unnecessarily.
2026-02-23 10:08:05 +00:00
Leosvel Pérez Espinosa 731db47fd7 fix(misc): bump minimatch to 10.2.1 to address CVE-2026-26996 (#34509)
## Current Behavior

Several Nx packages directly depend on a minimatch version with a
high-severity vulnerability
(https://github.com/advisories/GHSA-3ppc-4f35-3m26).

## Expected Behavior

Several Nx packages should depend directly on a minimatch version that
does not include the reported high-severity vulnerability.

Note: unsafe `minimatch` versions can still be pulled in transitively.
Upstream deps need to be updated, and then we need to update the Nx
packages to newer versions.

## Related Issue(s)

Fixes #34507

---------

Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
2026-02-23 09:42:07 +00:00
Copilot 4b6aea9f5e docs(core): clarify trailing slash requirement for inputs directory paths (#33664)
## Current Behavior

Users specifying directory paths in `inputs` without a trailing slash or
glob pattern find that files are not matched. For example,
`{projectRoot}/src` does not match any files, while `outputs` allows
naked directory paths without issue.

## Expected Behavior

Documentation clearly explains that directory paths in `inputs` require
a trailing slash or glob pattern:

```jsonc
{
  "inputs": [
    "{projectRoot}/src/",       // ✓ Works (trailing slash)
    "{projectRoot}/src/**/*",   // ✓ Works (glob pattern)
    "{projectRoot}/src"         // ✗ Does NOT work
  ]
}
```

### Changes

- **Reference doc** (`reference/inputs.mdoc`): Added "Directory Paths"
section explaining the requirement with examples
- **Guide** (`configure-inputs.mdoc`): Added callout warning at top
alerting users to this behavior
- Both docs note the difference from `outputs`, which do support naked
directory paths

## Related Issue(s)

Fixes
https://linear.app/nxdev/issue/NXC-2102/clarify-trailing-slash-requirement-for-inputs-in-directory-paths

Co-authored-by: Steven Nance <steven@nrwl.io>
2026-02-23 18:35:55 +09:00
Altan Stalker 0568059fb8 chore(repo): force nx-dev:prebuild-banner onto linux-extra-large (#34535)
Temp fix while scheduling is fixed for real

---------

Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
2026-02-20 18:24:33 -05:00
Craigory Coppola 3e5df300ab feat(core): add commands for debugging cache inputs / outputs (#34414)
## Current Behavior
There's not a great way to troubleshoot or test inputs and outputs
configurations on tasks.

## Expected Behavior
Adds `nx show target` to enable users to debug inputs and outputs. It
has flags `--inputs`, `--check-input`, `--outputs`, and `--check-output`
to list or test specific file patterns.

<img width="1077" height="198" alt="image"
src="https://github.com/user-attachments/assets/7ffd4502-1542-40e6-867c-37f70440a421"
/>

<img width="1077" height="105" alt="image"
src="https://github.com/user-attachments/assets/eb5b09e1-23fb-4710-9fb0-84f0c8e80c14"
/>

<img width="1077" height="538" alt="image"
src="https://github.com/user-attachments/assets/74c60668-285c-4195-ba77-6693a66e4897"
/>

<img width="1077" height="318" alt="image"
src="https://github.com/user-attachments/assets/ee228c43-98c2-441d-8b9d-277b38213e4d"
/>

<img width="1077" height="92" alt="image"
src="https://github.com/user-attachments/assets/8cca6553-4362-4662-b948-723abcc75671"
/>

<img width="1077" height="74" alt="image"
src="https://github.com/user-attachments/assets/6dc2188e-5dc5-4b5f-afd4-2a492a896e1d"
/>

---

<img width="492" height="430" alt="image"
src="https://github.com/user-attachments/assets/ae22bb2e-92c2-4c75-ad6f-b9cccda3def4"
/>

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
2026-02-20 17:35:50 -05:00
Jack Hsu 221ed882fa fix(misc): prevent nxCloudId from being generated for new workspaces (#34532)
## Current Behavior

When creating a new workspace using `create-nx-workspace` with the
"custom" preset flow, an `nxCloudId` is generated and added to
`nx.json`. This happens even though the onboarding flow is supposed to
handle Cloud setup separately via a short URL.

## Expected Behavior

New workspaces created via `create-nx-workspace` should not have
`nxCloudId` set in `nx.json`. Instead, a short URL is provided for users
to finish Cloud onboarding on their own. The `nxCloud: 'skip'` option is
now passed for the custom flow to prevent the ID from being generated.

E2E tests are updated to verify that `nxCloudId` is undefined in the
generated `nx.json` across all workspace presets.

## Related Issue(s)

N/A - internal fix for workspace creation behavior.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-02-20 19:56:58 +00:00
Craigory Coppola df8a2c43f4 fix(core): commands shouldn't hang when passing --help (#34506)
## Current Behavior
`--help` on commands that hit yargs help are hanging

## Expected Behavior
It doesn't hang. This contains a quick fix in adding the process.exit
call, but also adds the unref needed to maintain previous working
behavior. We'll need to investigate long term if additional areas keep
commands alive, but adding this unref theoretically allows removing the
process.exit calls from `nx show`

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
2026-02-20 13:49:21 -05:00
Jason Jean 391d23c65e chore(repo): re-enable e2e tests disabled by api-extractor issue (#34519)
## Current Behavior

14 e2e test suites were disabled (`xdescribe`) due to an ESM import
issue in `@microsoft/api-extractor@7.57.0` (see
https://github.com/qmhc/unplugin-dts/issues/461).

## Expected Behavior

With the upstream issue resolved, all 14 e2e test suites are re-enabled
(`describe`) and should pass normally.

## Related Issue(s)

Reverts #34516
2026-02-20 12:17:20 -05:00
Jason Jean 092cea6073 chore(repo): update nx to 22.6.0-beta.1 (#34527)
Updating Nx from 22.5.0-beta.5 to 22.6.0-beta.1
2026-02-20 11:21:34 -05:00
Jack Hsu de2dc7ca13 fix(nextjs): reset daemon client after project graph creation in withNx (#34518)
## Current Behavior

Running `nx test` for Next.js projects causes Jest to hang with:
```
Jest did not exit one second after the test run has completed.
```

This happens because `next/jest` loads `next.config.js`, which calls
`withNx` → `createProjectGraphAsync()`. The daemon client socket
connection is left open, keeping the Node.js event loop alive and
preventing Jest from exiting. Non-Next.js projects are unaffected since
they don't trigger this code path.

## Expected Behavior

Jest exits cleanly after tests complete for Next.js projects, without
needing `forceExit: true`.

## Fix

Pass `resetDaemonClient: true` to `createProjectGraphAsync()` in
`packages/next/plugins/with-nx.ts`. This tells the project graph
function to call `daemonClient.reset()` after fetching the graph, which
closes the socket and allows Jest to exit.

### Verification

| Scenario | Before | After |
|----------|--------|-------|
| `nx test next-app` | Hangs | Exits cleanly |
| `NX_DAEMON=false nx test next-app` | Exits cleanly | Exits cleanly |
| Direct `npx jest` | Exits cleanly | Exits cleanly |
| Non-Next.js `nx test react-lib` | Exits cleanly | Exits cleanly |

## Related Issue(s)

Fixes #32880
2026-02-20 11:08:50 -05:00
Jason Jean 5236e02308 chore(maven): bump maven plugin version to 0.0.14 (#34505)
## Current Behavior

The Maven plugin is on version `0.0.13`.

## Expected Behavior

The Maven plugin is bumped to version `0.0.14`, with a migration
generated for Nx `22.6.0-beta.1`.
2026-02-19 18:27:14 -05:00
Altan Stalker 8e1d873edc chore(core): enable nx cloud verbose logging (#34524)
## Current Behavior
Agents are silent and hard to diagnose

## Expected Behavior
Agents should print debug logs without making all of Nx print debug logs
2026-02-19 22:41:35 +00:00
Jason Jean 4ca3ee97c3 chore(repo): disable e2e tests broken by @microsoft/api-extractor@7.57.0 (#34516)
## Current Behavior

14 e2e tests are failing across master with "Failed to process project
graph" errors. The root cause is `@microsoft/api-extractor@7.57.0` which
has a broken ESM export (`ConsoleMessageId`). When `@nx/vite/plugin` or
`@nx/vitest` plugins load `vite.config.mts` files, they transitively
import api-extractor which crashes.

## Expected Behavior

Broken e2e tests are disabled via `xdescribe` so they no longer block
CI. Tests should be re-enabled once the upstream api-extractor ESM issue
is fixed.

## Disabled Tests

| Project | Test File |
|---------|-----------|
| e2e-vite | `vite.test.ts`, `vite-legacy.test.ts`,
`vite-ts-solution.test.ts` |
| e2e-vue | `vue.test.ts`, `vue-legacy.test.ts`,
`vue-ts-solution.test.ts` |
| e2e-js | `js-ts-solution.test.ts` |
| e2e-web | `web-vite.test.ts` |
| e2e-react | `react-vite.test.ts`, `react-ts-solution.test.ts` |
| e2e-next | `next-ts-solutions.test.ts` |
| e2e-release | `release-publishable-libraries.test.ts`,
`release-publishable-libraries-ts-solution.test.ts` |
| e2e-storybook | `storybook-nested.test.ts` |

## Related Issue(s)

Upstream: https://github.com/qmhc/unplugin-dts/issues/461
2026-02-19 19:16:57 +00:00
Jason Jean e6ad74afed chore(maven): upgrade maven-shade-plugin to 3.6.0 (#34514)
## Current Behavior

The `maven-shade-plugin` at version 3.5.0 intermittently fails on CI
with:

```
Could not replace original artifact with shaded artifact!
```

This is a file-locking race condition where the plugin fails to
atomically replace the original JAR with the shaded JAR.

## Expected Behavior

Upgrading to 3.6.0 resolves the intermittent CI failures by using
improved file-handling logic with better retry behavior during the
artifact replacement step.

## Related Issue(s)

N/A - fixes intermittent CI flakiness in `maven-batch-runner` builds.
2026-02-19 10:28:14 -08:00
Ondrej Kelle 79f41e54af feat(core): use static_vcruntime to avoid msvcrt dependency (#19781)
Closes #19779

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

## Current Behavior
When targeting Windows the resulting binary (nx.dll) dynamically links
against Microsoft Visual C++ runtime (msvcrt140.dll). This means nx
won't be able to run on Windows systems without this runtime installed.

## Expected Behavior
I'd like to avoid this dependency by linking the runtime statically into
the nx binary. (This is also how e.g. cargo.exe for Windows is built.)

## Related Issue(s)

Fixes #19779

---------

Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
2026-02-19 09:07:17 -05:00
Leosvel Pérez Espinosa 4f9be499b4 fix(core): reduce terminal output duplication and allocations in task runner (#34427)
## Current Behavior

Terminal output in the task runner is accumulated via repeated string
concatenation (`terminalOutput += chunk`). Each `+=` on a growing string
causes V8 to allocate a new, larger string and copy the old contents,
resulting in O(n²) allocation behavior for tasks with large output.
Additionally, `PseudoTtyProcess.onExit` didn't pass `terminalOutput` to
its callbacks, forcing callers like `TaskOrchestrator` to duplicate
output accumulation logic with a separate `onOutput` listener.

## Expected Behavior

- Terminal output is collected in `string[]` arrays and joined once at
the end, reducing intermediate allocations from O(n²) to O(n)
- `PseudoTtyProcess.onExit` now passes `terminalOutput` as a second
argument, matching the signature of other `RunningTask` implementations
- `TaskOrchestrator` no longer needs a special code path for
`PseudoTtyProcess` — unified `onExit` handling for all task types
- `tui-summary-life-cycle` accumulates output in chunks during execution
and stores the finalized string on task completion, allowing chunk
arrays to be GC'd
- `SeriallyRunningTasks` and `RunningNodeProcess` similarly switched to
chunk-based accumulation
- `BatchProcess` and `NodeChildProcessWithNonDirectOutput` lazily join
and cache their terminal output
2026-02-18 19:05:41 -05:00
Caleb Ukle 42b534366d docs(nx-dev): tech intro page structure improvements (#34450)
Work on making a tech intro pages more consistent with each other and
focus on "answering the 80%" for the given technology.

Focusing on 
- Angular
- Maven/Gradle
- react
- TS
- Vite
- Vitest/Jest

The changes are based around answering the following, where each
"category" of page might have a different set of depth for the answer.

1. Why do I want to use this plugin?
- Plugins are considered fully optional and are aimed at providing
better DX for a technology, such as inferred setup, generators,
migrations.
- some plugins (like TSC) might have special call outs in some of this,
but generally the same for all plugins.
2. How do I use this plugin in my workspace?
  - also pretty commonly the "same" for all plugins in terms of "setup"
- where they differ is mostly for frameworks, e.g. Angular, React.
You're looking at setting up a project to use these tools
- For Build/Test tools you're looking at adding to an existing project,
or converting from one to another.
- Build/Test tools are "means to an end", so should callout if the goal
is tool + framework in a "new" context point to the framework based
plugin page. Otherwise, show adding to an existing project like React
project.
3. What do I need to know about using this plugin?
- understanding finer details of a plugin options, e.g. buildable &
publishable
  - extra generators for the plugin. e.g. "convert-to-swc"
- generally I like the idea of having a "CI considerations" where we
talk about CI setups that can help, e.g. options or batch mode etc.


closes DOC-407

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: barbados-clemens <barbados-clemens@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2026-02-18 22:25:23 +00:00
Caleb Ukle 7528cc51fa fix(nx-dev): update breadcrumb links to match sidebar (#34500)
navigation of the breadcrumbs could lead to confusing state since they
were based around the folder structure.

Breadcrumbs are now based around the sidebar structure so they match the
hierarchy of content.

Note: I left the existing index file based route pages in place in case
there are any links people have booked marked/linked to in other
locations. these will get cleaned up when we finally rewrite all the
URLs to their new content locations
2026-02-18 22:23:00 +00:00
Caleb Ukle bbb1baa631 fix(nx-dev): widen search dialog (#34504) 2026-02-18 22:02:18 +00:00
Leosvel Pérez Espinosa 91b350efa8 fix(core): skip stale recomputations and prevent lost file changes in daemon (#34424)
## Current Behavior

When file changes arrive rapidly, the daemon triggers multiple
concurrent project graph recomputations that all run to completion —
wasting CPU/memory on redundant work and returning stale results.

Additionally, after processing file changes, the daemon clears all
tracked files indiscriminately. Files that changed mid-recomputation are
silently lost and never reflected in the project graph until another
unrelated file change arrives.

## Expected Behavior

Stale recomputations detect when a newer one has started and exit early,
chaining to the newer promise so callers always get the freshest result.

File change tracking now uses versioned maps. Each batch of file watcher
events gets a unique version, and only files matching the snapshotted
version are cleared after processing. Files that changed
mid-recomputation are preserved and picked up by the next cycle.
2026-02-18 16:18:43 -05:00
Jason Jean 50ca951540 fix(repo): fix e2e CI failures from Node 22.12 incompatibility (#34501)
## Current Behavior

Two categories of e2e CI failures were observed in run
https://github.com/nrwl/nx/actions/runs/22127884259:

1. **`e2e-nx-init` and `e2e-js` fail on Node 22.12.0** with:
   ```
error eslint-visitor-keys@5.0.0: The engine "node" is incompatible with
this module.
   Expected version "^20.19.0 || ^22.13.0 || >=24". Got "22.12.0"
   ```
Node 22.12.0 is one minor version short of the `^22.13.0` range required
by `eslint-visitor-keys@5.0.0`.

2. **`e2e-nx` tests fail because `[isolated-plugin]` / `[plugin-worker]`
verbose messages leak into captured stdout**, causing:
- `JSON.parse(runCLI('show project --json'))` to throw `SyntaxError:
Unexpected token 'i', "[isolated-p"...`
- `expect(runCLI('show projects')).toEqual('')` to fail with worker
spawn noise
- The `@nx/workspace:infer-targets` test to unexpectedly find
`@nx/remix` in output (from a worker spawn message)

Root cause: in `isolated-plugin.ts`, the plugin worker's stdout was
piped directly to `process.stdout`, so `[plugin-worker]` verbose
messages written by the worker ended up in the stdout captured by
`runCLI` in e2e tests.

## Expected Behavior

1. The CI matrix uses a Node 22.x version that satisfies `^22.13.0`.

2. Plugin worker verbose/diagnostic messages go to `process.stderr` (not
`process.stdout`), so they don't contaminate output captured by `runCLI`
in e2e tests. Both worker stdout and stderr now pipe to
`process.stderr`, and the max listener bump is consolidated to `+2` on
stderr.

## Related Issue(s)

N/A — identified from CI run
https://github.com/nrwl/nx/actions/runs/22127884259
2026-02-18 15:48:03 -05:00
MaxKless 18bfb0bc4a fix(maven): write output after each task in batch mode to ensure correct files are cached (#34400)
## Current Behavior
When running in maven 4 batch mode, the build state is recorded only
after the full batch is done.
This means that nx caching records the state of a task before build
state is recorded to disk.
When running another maven task that depends on this partially recorded
cache, the build state file is missing and we get errors.

## Expected Behavior
build state should be recorded after every task is done and before nx
caching can kick in. This way we can ensure that nx cache is correct.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 11:22:18 -05:00
Eric Baer 3b0fb81570 feat(devkit): add NX_SKIP_FORMAT environment variable to skip Prettier formatting (#34336)
## Current Behavior

When running generators or migrations, Nx automatically skips Prettier
formatting if no root Prettier config is detected (added in #30426).
However, there's no way to explicitly skip Prettier formatting when a
config IS present but the user wants to bypass it for specific
operations.

This can be needed when:
- When running something like Oxfmt that may treat prettier slightly
differently, even with the same config (this is the main thing I ran
into)
- Running migrations where Prettier reformatting causes unintended side
effects (e.g., breaking `eslint-disable` comments)
- Temporarily disabling formatting for debugging purposes
- Using a formatter that coexists with Prettier in the workspace but
should take precedence for certain files

## Expected Behavior

Users can set `NX_SKIP_FORMAT=true` to explicitly skip Prettier
formatting in generators and migrations, regardless of whether Prettier
is configured. TSConfig path sorting (controlled by
`sortRootTsconfigPaths` or `NX_FORMAT_SORT_TSCONFIG_PATHS`) continues to
work independently.

```bash
NX_SKIP_FORMAT=true nx migrate --run-migrations
NX_SKIP_FORMAT=true nx g @nx/react:app my-app
```

## Related Issue(s)

Related to #30403 and #30426. This enhancement adds explicit user
control for cases where auto-detection of Prettier configuration isn't
sufficient.
2026-02-18 09:56:43 -05:00
Craigory Coppola bdbc14902e feat(core): add --otp to top-level nx release command and detect EOTP errors (#34473)
## Current Behavior
When publish fails due to missing OTP code, its not clear as a user who
is using the top level command what to do next.

## Expected Behavior
Add the --otp flag to the top-level `nx release` command so users can
provide a one-time password for 2FA-enabled registries when running the
full release orchestration (version + changelog + publish).

When publish fails due to an expired or missing OTP (EOTP error),
display a helpful warning listing affected projects and the exact
command to re-run the publish step in isolation with a new OTP.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-02-18 08:05:40 -05:00
MaxKless dc6716828b feat(core): improve codex support for configure-ai-agents (#34488)
Codex has only basic MCP/AGENTS.md support right now. Also, because it
used to have only global-level config files, we had some extra logic
around configuring that.

We want Codex to get the latest skills too (they don't support custom
subagents yet, though) and use project-level config files that they now
support.
2026-02-18 20:14:27 +09:00
MaxKless 8de74d0984 feat(core): implement configure-ai-agents outdated message after tasks (#34463)
After running nx build (or any task), the daemon now shows a hint if
your AI agent configuration is outdated: "Your AI agent configuration is
outdated. Run nx configure-ai-agents to update."

The daemon computes and caches the full agent configuration status
(fully configured, outdated, partially configured, non-configured) using
latest Nx from npm, so the check is always against the newest available
configuration. Running nx configure-ai-agents resets the daemon's cache
so the message disappears on the next build.

  Key changes

- Daemon agent status endpoint: New GET_CONFIGURE_AI_AGENTS_STATUS /
RESET_CONFIGURE_AI_AGENTS_STATUS message types. The handler fires off
computation in the background and returns immediately (never blocks the
request). Results are cached for the daemon's lifetime.
- Shared latest-nx module: Extracted the "install nx@latest to tmp"
logic from nx-console-operations into daemon/server/latest-nx.ts so both
Nx Console and AI agents handlers share a single cached installation.
Includes a race-condition guard (in-flight promise deduplication).
- Post-task outdated hint: run-command.ts queries the daemon after task
execution and prints a single dim line if agents are outdated.
- Daemon reset from configure-ai-agents: The CLI sets NX_DAEMON=false
for configure-ai-agents, so we bypass daemonClient.enabled() and use
isServerAvailable() directly to reach an already-running daemon. The
socket is closed in a finally block so the process exits cleanly.
- Async editor detection (Rust): Made isEditorInstalled,
canInstallNxConsoleForEditor, installNxConsole, and related napi
functions async so they run on the libuv thread pool instead of blocking
Node's event loop. This prevents the daemon from stalling for ~3.5s when
checking editor extensions.
- output.logRawLine: New helper that prints a single line without the NX
prefix.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-02-18 18:53:41 +09:00
MaxKless b89c3084e7 feat(core): automatically set up ai agents in cnw/init when run from within an ai agent (#34469)
We want to minimize prompts for people and agents. But we also want to
help them by setting up nx config for them so their agents can work
optimally.
If they're executing `nx init` or `create-nx-workspace` from within an
agent, it's a reasonable assumption that they'll want the best AI config
for that specific agent - so we set it up for them.
2026-02-18 18:10:13 +09:00
Simon Heather bdf6d257b7 docs(core): add cacheKeyPrefix option to s3 remote cache options (#34157)
This pull request updates the documentation for the S3 cache plugin to
add the missing `cacheKeyPrefix` setting.

Text taken from https://github.com/nrwl/nx/pull/31395

Fixes #34147

---------

Co-authored-by: Caleb Ukle <caleb@nrwl.io>
Co-authored-by: Caleb Ukle <caleb.ukle+github@pm.me>
Co-authored-by: Simon Heather <simon.heather@yulife.com>
2026-02-17 22:24:35 -05:00
Leosvel Pérez Espinosa 678cd321f5 fix(core): replace buggy ignore-files trie with correct path-component gitignore matching (#34447)
## Current Behavior

The `nx watch` file watcher uses the `ignore-files` and
`watchexec-filterer-ignore` crates to handle `.gitignore` matching.
These crates use a trie-based approach that has a bug with
path-component matching — certain gitignore patterns (e.g., prefix
patterns) don't match correctly, causing files that should be ignored to
trigger unnecessary watch events.

## Expected Behavior

Gitignore patterns are matched correctly using per-directory `Gitignore`
instances from the `ignore` crate — the same crate already used by the
file walker. Each `.gitignore` file is scoped to its directory, and
matching is done deepest-first so that nested gitignores take priority —
matching standard git behavior.

### What changed

- Replaced `ignore-files` + `watchexec-filterer-ignore` with direct use
of `ignore::gitignore::{Gitignore, GitignoreBuilder}`, aligning the
watcher with the approach already used by the file walker
- Each `.gitignore` is now compiled as a standalone instance tied to its
parent directory
- Gitignore evaluation walks deepest-first; first match wins
- `.nxignore` matching now uses `matched_path_or_any_parents` for
correct ancestor checking
- `create_filter` is now synchronous (no longer `async`) since the new
approach doesn't need async I/O
- Removed 2 crate dependencies (`ignore-files`,
`watchexec-filterer-ignore`)
2026-02-17 18:30:22 -05:00
Jay Bell a0e34557f9 fix(core): use workspace root for path resolution when baseUrl is not set (#34453)
## Current Behavior
                                                            
When a project-level `tsconfig.json` (e.g., `apps/aurora/tsconfig.json`)
inherits `paths` via `extends` from `tsconfig.base.json` at the
workspace root and no explicit `baseUrl` is set, Nx incorrectly resolves
`./`-prefixed path mappings relative to the project tsconfig directory
instead of the workspace root where the paths were defined.
This causes errors when loading TypeScript config files (e.g.,
`rspack.config.ts`) that import workspace libraries using path aliases:
  NX   Cannot find module './libs/plugins/rspack/src'

`@swc-node/register`'s `readDefaultTsConfig` auto-sets `baseUrl` to
`dirname(tsConfigPath)` (the project directory) when not explicitly
configured, causing SWC to rewrite imports to incorrect relative paths
during transpilation.

  ## Expected Behavior

Path aliases defined in `tsconfig.base.json` (e.g.,
`"@trellis/plugins/rspack": ["./libs/plugins/rspack/src/index.ts"]`)
should resolve relative to the workspace root when no `baseUrl` is
configured.

This is needed so that when using `tsgo` and needing to prefix all paths
with `./` (no more `baseUrl` allowed) the paths are still resolved from
the right spot.

I tested this fix against our codebase on the branch I was trying to
switch to tsgo on and it seemed to work.

  ## Related Issue(s)

Fixes
https://discord.com/channels/1143497901675401286/1471627045694865581
2026-02-17 18:22:07 -05:00
Altan Stalker 5c7c9dd5fa chore(core): enable continuous assignment (#34471)
## Current Behavior
Continuous assignment is not enabled

## Expected Behavior
Continuous assignment is enabled
2026-02-17 18:17:15 -05:00
Juri Strumpflohner 4f31277f4f docs(repo): update CONTRIBUTING.md with Discord link (#34461)
## Current Behavior

CONTRIBUTING.md contains an outdated "How to Get Started Video" section
and references Stack Overflow for general questions.

## Expected Behavior

Remove outdated video section and point users to the Discord community
instead of Stack Overflow for general questions.

## Related Issue(s)

N/A
2026-02-17 18:15:38 -05:00
Colum Ferry c16377af25 feat(misc): use caret range for swc dependencies in pnpm catalog (#34487)
Use a range for the swc dependencies

Fixes #34472
2026-02-17 18:14:17 -05:00
Craigory Coppola 130cec466f fix(core): avoid blocking event loop during TUI PTY resize (#34385)
When switching from inline mode to full-screen TUI (or during window
resize), the PTY resize operation reparsed ALL raw terminal output
through a new vt100 parser synchronously on the event loop. For tasks
with large output, this caused a noticeable hang.

Add `resize_async()` which moves the expensive reparse to a background
thread using a snapshot-and-replay pattern:
1. Quick snapshot of raw output (brief read lock)
2. Expensive reparse on background thread (no locks held)
3. Quick swap with replay of any new output (brief write lock)

A generation counter prevents stale resizes from overwriting newer ones.

Also combine two separate O(n) scrollback processing calls in inline
mode into a single pass.
2026-02-17 18:07:43 -05:00
Copilot 65b94a1293 chore(repo): update copyright year to 2026 and refresh README description (#34437)
## Current Behavior

Copyright year shows 2017-2025 and README uses older tagline.

## Expected Behavior

Copyright reflects current year 2026 and README uses updated repository
description.

## Changes

- **LICENSE**: Updated copyright year from `2017-2025` to `2017-2026`
- **README.md**: Replaced heading and description
- New heading: "The Monorepo Platform that amplifies both developers and
AI agents. Nx optimizes your builds, scales your CI, and fixes failed
PRs automatically. Ship in half the time."
  - Removed redundant description line below heading

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> ## Update License and README
> 
> Please make the following changes:
> 
> 1. **Update LICENSE file**: Change the copyright year from `2017-2025`
to `2017-2026`
>    - File: `LICENSE`
> - Line 3: Update `Copyright (c) 2017-2025 Narwhal Technologies Inc.`
to `Copyright (c) 2017-2026 Narwhal Technologies Inc.`
> 
> 2. **Update README.md description**: Replace the current description
with the repository's official description
>    - File: `README.md`
> - Line 22: Change the heading from `# Smart Monorepos · Fast Builds`
to `# The Monorepo Platform that amplifies both developers and AI
agents. Nx optimizes your builds, scales your CI, and fixes failed PRs
automatically. Ship in half the time.`
> - Line 24: Remove the current description line: `Get to green PRs in
half the time. Nx optimizes your builds, scales your CI, and fixes
failed PRs. Built for developers and AI agents.`
> 
> The new README should have the repository description as the main
heading, followed immediately by the "Create a new Nx workspace with"
section.


</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created from Copilot chat.*
>

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: FrozenPandaz <8104246+FrozenPandaz@users.noreply.github.com>
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
2026-02-17 16:45:38 -05:00
Leosvel Pérez Espinosa 896a3f31ad fix(core): gate tui-logger init behind NX_TUI env var (#34426)
## Current Behavior

`tui_logger::init_logger()` and `TuiTracingSubscriberLayer` are
initialized unconditionally in `initialize_logger()`. This spawns a
background thread, causing unnecessary allocation churn and increased
processing.

## Expected Behavior

`tui_logger` is only initialized when `NX_TUI=true`, avoiding the
background thread and allocation overhead for all non-TUI contexts.
2026-02-17 16:40:48 -05:00
Caleb Ukle ea81b52442 chore(nx-dev): condense redirect rules (#34452)
## Current Behavior

We have ~1,657 redirect rules across `redirect-rules.js` and
`redirect-rules-docs-to-astro.js`, getting close to Netlify's limit and
we need room for more as the Astro migration continues.

## Expected Behavior

Reduced to **1,139 rules** (~31% reduction) by:

- Resolving duplicate/conflicting source paths across sections
- Flattening multi-hop redirect chains to point directly to final
destinations
- Consolidating groups of individual rules into wildcard patterns
(tutorials, CLI, helm, concepts, recipes, etc.)
- Removing old 2022-era sections (`schemaUrls`, `overviewUrls`,
`packagesIndexes`, `packagesDocuments`) whose destinations chain 3-5
hops deep and are long superseded by newer redirects
- made sure old links in nx code base still have redirects (will update
in future PR)

Build, tests, and internal link check all pass with no issues.

## Related Issue(s)

Fixes DOC-403

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: barbados-clemens <barbados-clemens@users.noreply.github.com>
2026-02-17 11:21:33 -06:00
Caleb Ukle edc9cc5af8 fix(nx-dev): use shared preview url for netlify deploy (#34467)
nextjs and astro should route to same preview deployments now


![wm_2026-02-16T19-40-04](https://github.com/user-attachments/assets/593c011a-ff78-4412-9bf0-ad186157f5d4)
2026-02-17 11:21:24 -06:00
Jack Hsu 2fa93afe2b feat(core): add agentic mode to nx init (#34418)
## Current Behavior

When AI agents (Claude Code, Cursor, Windsurf, etc.) run `nx init`, the
command works but:
- Uses interactive prompts that AI agents can't handle
- Outputs human-readable text that AI agents must parse
- Doesn't provide structured progress or error information

## Expected Behavior

When `nx init` detects an AI agent (via environment variables like
`CLAUDE_CODE=1`), it should:
- Skip interactive prompts and use sensible defaults
- Output structured NDJSON for progress updates, success, and errors
- Include detailed context for AI agents to understand and fix issues

## Changes

This PR adds agentic mode to `nx init`:

### `nx init` Changes
- Detect AI agents via `isAiAgent()` native function
- Auto-defaults: `interactive=false`, `nxCloud=false`, auto-detect `.nx`
installation
- NDJSON output with `type: progress|success|error`
- Error logs written to `.nx/ai-errors/` with full context
- Cursor restoration escape sequence skipped for AI agents (prevents
NDJSON corruption)

### Output Format
```jsonl
{"type":"progress","step":"starting","message":"Initializing Nx..."}
{"type":"success","nxVersion":"22.5.0","projectsDetected":1,"pluginsInstalled":["@nx/vite"]}
```

Or on error:
```jsonl
{"type":"error","message":"Failed to install","code":"INSTALL_ERROR","errorLogPath":".nx/ai-errors/nx-init-error-2025-01-15T10-30-00.log"}
```
2026-02-18 01:54:04 +09:00
MaxKless ee22084d1a fix(core): only pull configure-ai-agents from latest if local version is not latest (#34484)
## Current Behavior
we pull from latest all the time even if the current version is already
latest

## Expected Behavior
we can skip this extra work sometimes
2026-02-17 16:30:34 +00:00
Jack Hsu ca2fc0fa85 fix(misc): rewrite Framer URLs to nx.dev in HTML responses (#34445)
## Current Behavior

Pages proxied from Framer contain canonical URLs pointing to the Framer
domain (`ready-knowledge-238309.framer.app`), causing duplicate indexing
issues in search engines.

## Expected Behavior

Canonical URLs and other references in Framer-proxied pages now point to
`nx.dev`, ensuring proper SEO indexing.

### Implementation

Consolidated all Framer logic into a single Netlify edge function
(`rewrite-framer-urls.ts`) that:

1. Checks if the request path matches a Framer-proxied path (using
`FRAMER_REWRITES` env var)
2. Fetches directly from Framer (using `FRAMER_URL` env var)
3. Rewrites all Framer URLs to `nx.dev` in the HTML response (handles
`<link rel="canonical">`, `og:url`, etc.)
4. For non-Framer paths, passes through to Next.js

The edge function uses the `accept: ['text/html']` config to only run on
HTML requests, matching the pattern from `track-page-requests.ts` in
astro-docs.

The Next.js middleware has been removed since all Framer routing is now
handled by the edge function.

### Environment Variables

The edge function expects these env vars in Netlify (already added
previously):
- `NEXT_PUBLIC_FRAMER_URL`: e.g.,
`https://ready-knowledge-238309.framer.app`
- `NEXT_PUBLIC_FRAMER_REWRITES`: comma-separated list of paths, e.g.,
`/pricing,/enterprise`

## Demo

1. Go to https://deploy-preview-34445--nx-dev.netlify.app/
2. View source and look for canonical
3. See it is nx.dev not framer domain

<img width="1347" height="161" alt="image"
src="https://github.com/user-attachments/assets/d4a515da-e39f-41cb-a7b4-668fe0bedbbd"
/>


## Related Issue(s)

Closes CLOUD-4148
2026-02-17 10:53:46 -05:00
Steven Nance 0c14bcbe55 fix(release): remove unnecessary number from release return type (#34481)
## Current Behavior

The `release` function returned by `createAPI` has a return type of
`Promise<NxReleaseVersionResult | number>`. The `| number` union member
is inaccurate since the function always returns
`NxReleaseVersionResult`, which can mislead consumers of the
programmatic API.

## Expected Behavior

The return type is narrowed to `Promise<NxReleaseVersionResult>`,
accurately reflecting what the function actually returns and giving API
consumers correct type information.

Co-authored-by: Andreas Hörnicke <andreas.hoernicke@contentful.com>
2026-02-17 15:10:11 +00:00
MaxKless 08d899a2d2 docs(misc): update nx-mcp reference and tweak ai docs for skills (#34468)
we changed the default options of the nx mcp so we need to update docs
to reflect it
2026-02-17 22:23:39 +09:00
MaxKless 0d4160e968 docs(nx-dev): add MCP to skills blog post (#34428)
Blog post draft about the evolution from MCP tools to agent skills.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: MaxKless <MaxKless@users.noreply.github.com>
Co-authored-by: Juri Strumpflohner <juri.strumpflohner@gmail.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2026-02-17 09:28:34 +00:00
Craigory Coppola 8c15428f43 feat(core): support dependency filesets with ^{projectRoot} syntax (#34310)
## Current Behavior
`^` and `dependencies: true` only work for fileset inputs

## Expected Behavior
Adds support for inputs of the form `^{projectRoot}/**/*.ts` as
syntactic sugar for specifying a fileset input that should be collected
from dependency projects.

Previously, only named inputs could use the `^` prefix to include
dependencies (e.g., `^production`). Now filesets can also use this
syntax directly without needing to define a named input first.

Examples:
- `^{projectRoot}/**/*.ts` - include .ts files from all dependencies
- `^{workspaceRoot}/tools/**/*` - include workspace tools from
dependencies
- `{ fileset: '{projectRoot}/**/*.ts', dependencies: true }` - object
form

Detection is deterministic: if the string after `^` starts with
`{projectRoot}` or `{workspaceRoot}`, it's treated as a dependency
fileset; otherwise, it's treated as a named input reference.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-02-16 21:25:55 -05:00
Leosvel Pérez Espinosa 59b12edcb6 fix(core): prevent staggered and duplicate lines in dynamic output (#34462)
## Current Behavior

- In some `nx run-many` executions, terminal output can appear staggered
or visually misaligned instead of updating cleanly in place.
- For `run-many` cases that end up running a single task (especially
when TUI is not active), the spinner/status line can be rendered twice.

## Expected Behavior

- Dynamic terminal output updates should remain stable and aligned, with
clean in-place refreshes.
- Single-task `run-many` should display a single spinner/status line
with no duplicate rendering.
2026-02-16 17:31:07 +00:00
Juri dd3b79ebf4 fix(core): handle Ctrl+C gracefully in configure-ai-agents
Add uncaughtException handler for ERR_USE_AFTER_CLOSE to prevent
ugly stack trace when pressing Ctrl+C during enquirer prompts
(Node 24 stricter readline behavior). Matches existing pattern
used in nx init and create-nx-workspace.
2026-02-16 12:57:17 +01:00
Jason Jean d64e41dd99 fix(repo): revert sudo for global npm install in publish workflow (#34451)
## Current Behavior

The publish workflow uses `sudo npm install -g npm@11.5.2` which was
added in #34409. This causes issues with OIDC token permissions in the
release pipeline since `sudo` runs as a different user context.

## Expected Behavior

The publish workflow should use `npm install -g npm@11.5.2` without
`sudo`, matching the standard approach used elsewhere and avoiding
permission context issues during release.

## Related Issue(s)

Reverts #34409
2026-02-13 15:53:32 -05:00
Jack Hsu f5769f0bfb docs(misc): minor fixes for docs (#34449)
1. Consistent punctuation on intro page (periods at end of bullet
points).
2. Adjust AI detection for edge function.
2026-02-13 15:44:31 -05:00
Craigory Coppola 292a21319d feat(core): add --stdin to affected options (#34435)
- **feat(core): add `--stdin` to affected options**
- **fix(core): use newline-delimited stdin and add TTY guard for --stdin
option**

Supercedes #28770

Co-authored-by: @aaronccasanova

---------

Co-authored-by: Aaron Casanova <aaron.casanova@shopify.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-02-13 15:17:38 -05:00
Jason Jean c5e1bedca2 fix(repo): replace addnab/docker-run-action with direct docker run (#34448)
## Current Behavior

The publish workflow uses `addnab/docker-run-action@v3` which is based
on `docker:20.10` (Docker API 1.41). GitHub's `ubuntu-24.04` runners now
ship Docker Engine 28.x which requires minimum API version 1.44, causing
all 4 Linux Docker builds to fail:

```
docker: Error response from daemon: client version 1.41 is too old. Minimum supported API version is 1.44, please upgrade your client to a newer version.
```

Failed run: https://github.com/nrwl/nx/actions/runs/21961139962

## Expected Behavior

Linux Docker builds (x86_64-gnu, x86_64-musl, aarch64-gnu, aarch64-musl)
complete successfully using the host's modern Docker CLI.

https://github.com/nrwl/nx/actions/runs/21996143819

## Related Issue(s)

The `addnab/docker-run-action` repo is abandoned (last release March
2021, last commit May 2021) with open issues about this exact problem.
2026-02-13 12:42:30 -05:00
Jack Hsu c0540c8846 docs(misc): improve AX for getting started pages (#34410)
## Current Behavior

Getting started pages had overlapping content and unclear focus:
- `installation.mdoc` mentioned CNW and tutorials (belongs elsewhere)
- `start-new-project.mdoc` had manual setup option (belongs on
add-to-existing)
- `start-with-existing-project.mdoc` duplicated CI examples, editor
buttons, Nx Cloud walkthrough
- `intro.mdoc` mixed messaging - some "challenges" were polyrepo
problems

## Expected Behavior

Each page is now focused with no duplication:

### intro.mdoc
- Clear problem/solution structure following Turborepo's approach
- Problem: concise (builds get slow as codebase scales)
- Solution: caching, task orchestration, affected commands
- **Removed**: Polyrepo problems from challenge list (code sharing, lost
context)
- **Removed**: Lengthy deepdive callouts
- Net reduction: 67 deletions, 14 insertions

### installation.mdoc
- Global install (npm/brew/choco/apt) + verification step
- Local install (`nx init`) for existing repos
- Update instructions
- **Removed**: CNW mention, tutorials section, "More Documentation"

### start-new-project.mdoc
- Option 1: Create locally with templates (`create-nx-workspace`)
- Option 2: Create via Nx Cloud (browser-based)
- **Removed**: Manual setup option (that's for existing projects)
- Updated terminology: "presets" → "templates"

### start-with-existing-project.mdoc
- Focused: `nx init` → run tasks → see caching → explore graph
- Links to other pages instead of duplicating content
- **Removed**: CI config examples, Nx Cloud walkthrough, editor buttons

### editor-setup.mdoc
- Added problem hook explaining why editor integration matters
- Clarified Neovim is community-maintained

### ai-setup.mdoc
- Added problem hook about AI hallucination without workspace context
- Explained MCP acronym (Model Context Protocol)
- Clarified "Ralph Wiggum loop" terminology

### sidebar.mts
- Reordered to match natural flow: Installation → Start New/Add Existing
→ Editor/AI

## Related Issue(s)

Closes DOC-405

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
2026-02-13 11:09:21 -05:00
Craigory Coppola ece9b5bf4a fix(core): remove shellapi from winapi featureset to minimize AV false positives (#34208)
## Current Behavior
There's a chance that windows can falsely flag our native binaries as a
threat. We do not use the shellapi feature from winapi.

## Expected Behavior
We hope that removing this API doesn't break things, and the threat
messaging goes away

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #https://github.com/nrwl/nx/issues/34186

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-02-12 19:32:58 -05:00
Craigory Coppola 51420790c3 chore(repo): improve copy-built-package script (#34432)
Makes copy-built-package script a bit more ergonomic and discoverable.
Adds some interactive UI for picking package / repo if they are not
specified.
2026-02-12 16:49:51 -05:00
Craigory Coppola c407de6e2b fix(core): hitting [1] or [2] should remove pinned panes if they match the current task (#34433)
## Current Behavior

Pressing `[1]` or `[2]` on a task that's already pinned to that slot
**focuses** the pane instead of unpinning it. This means there's no way
to unpin a single pane via keyboard — you can only nuke everything with
`[0]`.

This regression was introduced in #34175, which fixed a real problem:
pressing Enter on an already-pinned task would unpin it, leaving focus
on an invisible pane (a ghost pane — you're staring at nothing but the
TUI thinks you're looking at output). The fix was to make the "already
pinned to this pane" branch focus instead of unpin. The problem is that
`[1]`, `[2]`, and Enter all flowed through the same function
(`assign_current_task_to_pane`), so changing behavior for Enter changed
it for everyone. One lock got swapped out, and every door started
behaving the same way.

## Expected Behavior

`[1]` and `[2]` are **toggles**: press once to pin, press again to
unpin. Enter is a **display** action: show me this task's output and put
my cursor there — if it's already visible somewhere, just take me to it.

After this change:

- **`[1]` / `[2]`** on an already-pinned task → unpins it (pane
disappears, layout adjusts, focus returns to task list if no panes
remain)
- **Enter** on an already-pinned task → focuses whichever pane it's in
(even if it's in pane 2 and you'd normally expect pane 1)
- **Init** (startup restore of pinned tasks) → pure assignment, no
toggling, no focusing

## Approach

The old code had one function trying to serve three masters. Rather than
adding a flag parameter (`should_toggle: bool`) — which would just be a
boolean that lies about its intentions at every call site — the function
was split along the actual semantic boundaries:

| Function | Used by | "Already pinned here" behavior |
|---|---|---|
| `toggle_current_task_in_pane` | `[1]` / `[2]` keys | **Unpin** (toggle
off) |
| `assign_current_task_to_pane` | `init()` | No-op (task is where it
should be) |
| `display_and_focus_current_task_in_terminal_pane` | Enter key |
**Focus** the existing pane |

The shared logic — exiting spacebar mode, moving a task between panes,
fresh-pinning — lives in two small helpers (`exit_spacebar_and_pin`,
`move_or_pin_selection`) that both `toggle` and `assign` delegate to.
The only code that differs is the "what do we do when it's already
here?" branch, which is exactly the part that *should* differ.

**Why not keep one function with a mode parameter?** Because the three
behaviors aren't variations of the same action — they're genuinely
different user intents. A toggle is "I changed my mind." A focus is
"Take me there." An assignment is "Put this here." Encoding that as an
enum parameter just moves the branching somewhere less obvious and makes
the call sites harder to read. The function names now document the
intent at the point of use, and there's no shared state to accidentally
couple.

**Why does Enter check all panes, not just pane 0?** Because if you
pinned a task to pane 2 via `[2]` and then press Enter on it, the least
surprising thing is to jump to where it already lives — not to silently
duplicate it into pane 1 or ignore you. The task is already on screen;
Enter means "show me."

## Related Issue(s)

Fixes the regression introduced by #34175.
2026-02-12 16:21:29 -05:00
Jack Hsu 950265fc8c feat(misc): lock in CNW variant 2 with deferred connection (#34416)
## Current Behavior

CNW (Create Nx Workspace) has A/B testing logic that randomly selects
between variants 0, 1, and 2 for the Nx Cloud connection flow. Each
variant shows different prompts and banners.

## Expected Behavior

Lock in variant 2 as the permanent behavior:
- **No cloud prompt** - users are not asked about Nx Cloud during
workspace creation
- **Deferred connection** - no `nxCloudId` is written to `nx.json` (uses
`skipCloudConnect: true`)
- **Variant 2 banner** - shows "Enable remote caching and automatic
fixes when CI fails" with a link to complete setup later

### Changes
- Simplified `ab-testing.ts` - removed caching, random selection;
`getFlowVariant()` always returns `'2'`
- `shouldShowCloudPrompt()` always returns `false`
- `determineNxCloudV2()` returns `'github'` with `skipCloudConnect:
true` for deferred connection
- Removed variant 1 banner logic from `messages.ts`
- Updated tests to reflect the locked-in behavior

## Demo

https://www.loom.com/share/7f688eed6052428cbe91dd9db837cbbd

## Related Issue(s)

Closes CLOUD-4255

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-12 16:05:28 -05:00
Benjamin Cabanes 28fea0db0c docs(nx-dev): replace global ID with deterministic target ID for HBST (#34431)
Simplified form targeting by replacing the global `reactHubspotForm` ID
with a deterministic `targetId` that incorporates portal, form, and
calendly IDs. This improves scalability and avoids potential ID
collisions.
2026-02-12 14:03:02 -05:00
Benjamin Cabanes b5c3663126 docs(nx-dev): add back inline script (#34429) 2026-02-12 12:56:17 -05:00
MaxKless a757f40d83 fix(maven): correctly map between maven locators and nx project names (#34366) 2026-02-13 01:42:31 +09:00
Jack Hsu 7a4e052533 chore(misc): add banner content monitor workflow (#34417)
## Current Behavior

When banner content changes in Framer, the nx-docs and nx-dev sites need
to be manually redeployed to pick up the new content.

## Expected Behavior

A scheduled workflow monitors the banner URL every 15 minutes and
automatically triggers Netlify production deploys when content changes.

## How it works

1. Fetches `BANNER_URL` content (from repository variable)
2. Computes SHA256 hash
3. Compares to cached hash from previous run
4. If different → triggers both Netlify deploys, updates cache
5. If unchanged → no-op

## Required Setup

1. **Repository variable** (`Settings → Secrets and variables → Actions
→ Variables`):
   - `BANNER_URL` = Framer banner API URL

2. **Repository secret** (`Settings → Secrets and variables → Actions →
Secrets`):
   - `NETLIFY_AUTH_TOKEN` = Netlify personal access token

## Related Issue(s)

Fixes DOC-405
2026-02-12 10:37:47 -05:00
Juri 9a57042cbe docs(nx-dev): add Nx AI agent skills blog post 2026-02-12 16:28:57 +01:00
Steven Nance 754b01a066 feat(core): add negation pattern support for plugin include/exclude (#34160)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
Negation patterns are ignored in plugin configuration for the `include`
and `exclude` properties.

## Expected Behavior

- Negation patterns should work in the same way that they do for other
`include`/`exclude` configurations

**Example: Excluding all e2e projects except one**

```jsonc
// nx.json
{
  "plugins": [
    {
      "plugin": "@nx/jest/plugin",
      "exclude": ["**/*-e2e/**/*", "!**/toolkit-workspace-e2e/**/*"],
    },
  ],
}
```

This will exclude all e2e projects except `toolkit-workspace-e2e`.

**Example: Including packages except legacy ones**

```jsonc
// nx.json
{
  "plugins": [
    {
      "plugin": "@nx/vite/plugin",
      "include": ["packages/**/*", "!packages/legacy/**/*"],
    },
  ],
}
```

**How negation patterns work:**

- Patterns are processed in order from first to last
- A pattern starting with `!` removes files from the match set
- A pattern without `!` adds files to the match set
- The last matching pattern determines if a file is included
- If the first pattern is a negation, all files are matched initially

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-12 16:03:20 +01:00
Colum Ferry 5a1735b041 feat(misc): update PLUGIN.md files to help agents verification (#34379)
## Current Behavior
There is currently no plugin.md file for Gradle.
Other plugin.md files can be improved

## Expected Behavior
Add plugin.md file for Gradle to aid with verification with Agents.
Add plugin.md file for Vite for workspaces that have not migrated to
@nx/vitest.

## Related Issue(s)

CLOSES NXC-3843

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Coly010 <Coly010@users.noreply.github.com>
2026-02-12 12:25:10 +01:00
Josh VanAllen a0ff232ad7 feat(testing): add cacheDir option to playwright executor (#34413)
## Current Behavior

The Playwright executor does not support configuring a custom cache
directory for Playwright's internal cache (browser binaries, etc.).
Users who need to control where Playwright stores its cache, for example
in CI environments with specific disk constraints like not being able to
DTE tasks or shared caching setups, have no way to set this through the
executor configuration.

## Expected Behavior

A new `cacheDir` option is available on the Playwright executor. When
provided, it sets the `PWTEST_CACHE_DIR` environment variable on the
forked Playwright process, allowing users to control where Playwright
stores its internal cache.
  ```json
  {
    "targets": {
      "e2e": {
        "executor": "@nx/playwright:playwright",
        "options": {
          "cacheDir": "/tmp/playwright-cache"
        }
      }
    }
  }
  ```

## Related Issue(s)

Replaces #34397

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 17:36:17 -05:00
Jack Hsu 17f2f1bdc6 docs(misc): clarify security email usage in SECURITY.md (#34411)
## Current Behavior

The SECURITY.md file does not clarify what types of reports should be
sent to the security email, leading to reports about outdated
dependencies and vulnerability scanner output.

## Expected Behavior

The file now clarifies that the security email is for demonstrable,
verified vulnerabilities in the Nx codebase itself, not for:
- Outdated dependency reports
- Dependencies with CVEs that don't directly affect Nx
- General vulnerability scanner output

## Related Issue(s)

Fixes NXC-3898

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-11 15:08:20 -05:00
Jason Jean e48f2f3a8c fix(repo): use sudo for global npm install in publish workflow (#34409)
## Current Behavior

The `npm install -g npm@11.5.2` step in the publish workflow fails with
`EACCES: permission denied, mkdir '/usr/local/share/man/man5'` on newer
GitHub Actions runner images.

## Expected Behavior

The global npm install step completes successfully regardless of runner
image permissions on `/usr/local/share/man/`.

## Related Issue(s)

This is a known issue with GitHub Actions runners:
https://github.com/actions/runner-images/issues/9644
2026-02-11 14:49:43 -05:00
Colum Ferry 7785eae516 feat(core): extract sandbox detection into reusable utility (#34408)
Add isSandbox() utility that checks for sandbox environment variables
(SANDBOX_RUNTIME, GEMINI_SANDBOX, CODEX_SANDBOX, CURSOR_SANDBOX) and
use it to disable the daemon and plugin isolation in sandbox
environments.
2026-02-11 18:33:17 +00:00
Miroslav Jonaš 28c5d95964 fix(nx-dev): clarify project linking for workspaces (#34405)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: meeroslav <meeroslav@users.noreply.github.com>
2026-02-11 16:34:13 +00:00
Caleb Ukle 0e53c3f3d5 fix(nx-dev): add missing nx-cloud intro in sidebar (#34403) 2026-02-11 15:36:12 +00:00
Colum Ferry 6bf8c4693f feat(core): handle agentic sandboxing (#34402)
## Current Behavior
Running ai agents in sandbox mode causes issues with Nx's daemon and
plugin isolation

## Expected Behavior
Running ai agents in sandbox mode should work

## Related Issue(s)

CLOSES NXA-828
2026-02-11 15:12:49 +00:00
Philip Fulcher 8e3cff657b docs(nx-dev): add broadcom success story (#34393) 2026-02-11 09:36:42 -05:00
Colum Ferry 15d508e814 feat(core): add nxVersion to meta in shortUrl for cnw (#34401)
## Current Behavior
We do not include NxVersion when creating short urls.

## Expected Behavior
Include NxVersion when creating short urls. 

## Related Issue(s)

CLOSES NXC-3879
2026-02-11 09:27:03 -05:00
Craigory Coppola 6570674abf fix(core): handle dangling symlinks during cache restore (#34396)
When cache outputs include both glob patterns and directory patterns
containing symlinks, the cache restore fails with EEXIST (os error 17).
This happens because `fs_extra::remove_items` silently skips dangling
symlinks (since `is_dir()`/`is_file()` follow links and return false),
leaving stale symlinks that cause `symlink()` to fail.

The fix makes symlink creation idempotent by checking for and removing
any existing symlink at the destination before creating a new one, using
`symlink_metadata()` which correctly detects dangling symlinks.

Fixes #34013
2026-02-10 17:41:18 -05:00
Jason Jean 2d71d9ac65 fix(maven): use module-level variable for cache transfer between createNodes and createDependencies (#34386)
## Current Behavior

The Maven plugin's `createNodes` and `createDependencies` functions both
independently compute a hash of all pom.xml directories, then use that
hash to look up cached Maven analysis data from disk. When Maven
projects have `<includes>` or `<excludes>` in their plugin config, the
hash can differ between the two calls, causing `createDependencies` to
fail to find the data that `createNodes` stored.

## Expected Behavior

`createDependencies` reliably receives the Maven analysis data from
`createNodes` regardless of hash differences, by reading it from a
module-level variable instead of re-hashing and looking it up from disk.

This matches the pattern already used by the Gradle plugin
(`getCurrentGradleReport`).

## Related Issue(s)
2026-02-10 16:27:51 -05:00
Leosvel Pérez Espinosa f43d2028ff fix(core): make runtime cache key deterministic (#34390)
## Current Behavior

Runtime cache keys could be nondeterministic because the order of
environment variables varied, leading to inconsistent cache hits across
runs.

## Expected Behavior

Runtime cache keys are deterministic regardless of the insertion order
of env variables, improving cache stability.
2026-02-10 15:45:30 -05:00
Leosvel Pérez Espinosa 6c9f0cb46d fix(core): avoid dropping unrelated continuous deps in makeAcyclic (#34389)
## Current Behavior

Cycles in the task graph could remove unrelated `continuousDependencies`
when the cycle exists only in `dependencies`, leading to missing
continuous task edges.

## Expected Behavior

Cycle removal only removes the specific cyclic edge from the list where
it appears, preserving unrelated continuous dependencies.
2026-02-10 15:44:26 -05:00
Caleb Ukle bd13929de8 fix(nx-dev): improve plugin registry visibility (#34395)
- **fix(nx-dev): make sure "plugin registry" shows up in search**
- search ranking will be re-evaled after we work through more content
updates
<img width="768" height="1406" alt="image"
src="https://github.com/user-attachments/assets/e7ca2aff-7daf-417b-ad96-ba6722480432"
/>

- **docs(nx-dev): add plugin registry to footer**
<img width="1076" height="405" alt="image"
src="https://github.com/user-attachments/assets/4c93c5ec-1f64-4cd6-8f88-9347d5009ac9"
/>
2026-02-10 13:13:54 -06:00
Brett Burley f5a7ea1606 fix(core): clean up stale socket files before listening (#34236)
## Current Behavior

When running Nx tasks in CI environments (e.g., Buildkite) where the
host's /tmp is mounted to containers, intermittent EADDRINUSE errors
occur in PseudoIPCServer.init(). This happens because:

1. PseudoIPCServer doesn't clean up its Unix socket file before calling
listen()
2. ForkedProcessTaskRunner.createPseudoTerminal() instantiates
PseudoTerminal directly instead of using the createPseudoTerminal()
helper, bypassing shutdown callback registration

When a new container starts with the same PID as a previous run (PID
recycling), it generates the same socket path and hits EADDRINUSE
because the stale socket file still exists.

## Expected Behavior

No EADDRINUSE errors should occur. The PseudoIPCServer should
defensively remove any stale socket file before attempting to listen,
similar to how the daemon server handles this.

## Related Issue(s)

Fixes #34233
2026-02-10 13:36:34 -05:00
Benjamin Cabanes 9c42292ed8 docs(nx-dev): remove Cookiebot & GA integration, migrate all events to GTM (#34384)
Streamlined analytics tracking by removing Cookiebot and direct GA
(gtag.js) integrations. Consolidated event logging through GTM's
dataLayer for consistency and maintenance simplicity.
2026-02-10 12:17:47 -05:00
Altan Stalker ec0f51ed75 chore(core): enable cloud experimental polling (#34394)
Updated CI behavior
2026-02-10 12:05:20 -05:00
Leosvel Pérez Espinosa 5ae53ecae8 fix(core): use a consistent batch id between scheduler and task runner (#34392)
## Current Behavior

Batch IDs are generated in two places: the task scheduler uses an
incremental counter (`executorName N`) while the forked process task
runner generates its own using the process PID (`executorName-pid`).
This means the batch ID registered in metrics doesn't match the one used
everywhere else.

## Expected Behavior

Batch IDs are only created by the task scheduler. The forked process
task runner uses the scheduler-assigned ID to ensure consistency across
the system.
2026-02-10 11:33:14 -05:00
MaxKless 5066511576 fix(core): make sure that mcp args aren't overridden when running configure-ai-agents (#34381)
## Current Behavior
right now if users modify their mcp params like `--minimal`, we will
override them on `configure-ai-agents`

## Expected Behavior
We want to bring users up to latest without overriding their valid
configurations
2026-02-10 14:24:09 +01:00
Benjamin Staneck 0b6961b0d7 feat(core): update formatting of agent rules documentation (#33356) 2026-02-10 22:22:41 +09:00
Caleb Ukle 089e111fcc docs(nx-cloud): update info about GH permissions (#34380)
https://deploy-preview-34380--nx-docs.netlify.app/docs/enterprise/single-tenant/custom-github-app#configure-permissions-for-the-github-app

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: barbados-clemens <barbados-clemens@users.noreply.github.com>
2026-02-09 12:09:17 -06:00
Jason Jean 93fddd14de chore(repo): update nx to 22.5.0-beta.5 (#34371)
Updating Nx from 22.5.0-beta.4 to 22.5.0-beta.5
2026-02-09 13:00:52 -05:00
Kai Gritun f1873b27a8 fix(core): use --lockfile-only for Bun updateLockFile (#34375)
## Current Behavior

The Bun package manager config uses `--frozen-lockfile` for
`updateLockFile`:

```typescript
updateLockFile: 'bun install --frozen-lockfile',
```

However, `--frozen-lockfile` **prevents** changes to the lockfile,
causing `nx release` to fail when trying to update the lockfile after
version bumps.

## Expected Behavior

Use `--lockfile-only` which generates/updates the lockfile without
installing dependencies:

```typescript
updateLockFile: 'bun install --lockfile-only',
```

This is consistent with other package managers:
- npm: `npm install --package-lock-only`
- pnpm: `pnpm install --lockfile-only`
- yarn berry: `yarn install --mode update-lockfile`

## Background

When Bun support was added in PR #22602 (April 2024), `--lockfile-only`
didn't exist in Bun. Bun has since added this flag.

Closes #34344

Co-authored-by: Kai Gritun <kai@kaigritun.com>
2026-02-09 10:08:19 -05:00
Jason Jean 81c157d063 fix(repo): align pnpm version in CI workflows with package.json (#34370)
## Current Behavior

Several GitHub Actions workflows hardcode pnpm version `10.11.1`, while
`package.json` specifies `pnpm@10.28.2` in the `packageManager` field.
This causes CI failures with:

```
Error: Multiple versions of pnpm specified:
  - version 10.11.1 in the GitHub Action config with the key "version"
  - version pnpm@10.28.2 in the package.json with the key "packageManager"
```

## Expected Behavior

All pnpm version references across CI workflows should match the
`packageManager` field in `package.json` (`10.28.2`).

## Related Issue(s)

N/A — Fixing CI breakage from version mismatch.

## Changes

Updated pnpm version from `10.11.1` → `10.28.2` in:
- `.github/workflows/npm-audit.yml` — `pnpm/action-setup` version
- `.github/workflows/publish.yml` — `PNPM_VERSION` env var and FreeBSD
install
- `.github/workflows/issue-notifier.yml` — `pnpm/action-setup` version
- `.github/workflows/generate-embeddings.yml` — `pnpm/action-setup`
version
2026-02-06 19:45:33 -05:00
Jason Jean 1f5520ebb1 fix(core): add missing FileType import for Windows watcher build (#34369)
## Current Behavior

The Windows build (`aarch64-pc-windows-msvc`) fails to compile with:

```
error[E0433]: failed to resolve: use of undeclared type `FileType`
  --> packages\nx\src\native\watch\types.rs:128:55
```

The `FileType` type is used inside a `#[cfg(target_os = "windows")]`
block but was not imported.

## Expected Behavior

The Windows build compiles successfully. The `FileType` import is scoped
inside the `#[cfg(target_os = "windows")]` block (matching the existing
pattern in the macOS block) so there are no unused imports on any
platform.

## Related Issue(s)

N/A — build breakage discovered during CI publish workflow.
2026-02-06 18:47:18 -05:00
Jason Jean 0aef1ef26f fix(core): reduce daemon inotify watch count by upgrading watchexec (#34329)
## Current Behavior

The daemon's file watcher uses watchexec 3.0.1 which hardcodes
`RecursiveMode::Recursive` when registering inotify watches. This means
**every** directory gets an inotify watch — including all of
`node_modules`, `.git`, and other ignored trees.

On a typical workspace with a large `node_modules`, this can consume
thousands of inotify watches, eating kernel memory and CPU. The
`WatchFilterer` only filters **events** after watches are already
registered — the watches themselves are never prevented.

## Expected Behavior

Only non-ignored directories (workspace source code) get inotify
watches. Ignored directories like `node_modules`, `.git`, `.nx/cache`,
`.nx/workspace-data`, and `.yarn/cache` are skipped entirely at the
watch registration level.

This dramatically reduces:
- **inotify watch count** (from thousands to hundreds)
- **Memory usage** (each watch consumes kernel memory)
- **CPU overhead** (fewer watches = less kernel bookkeeping)

### How it works

- Upgraded watchexec 3.0.1 → 8.0.1 which supports
`WatchedPath::non_recursive()`
- Added `create_watch_walker()` using `ignore::WalkBuilder` (same
pattern as `walker.rs`) to enumerate only non-ignored directories
- Each directory is watched with `NonRecursive` mode — like putting
security cameras only in the rooms you care about instead of every room
in the building
- New directories created at runtime are dynamically added to the watch
set via the `on_action` handler
- Event-level filtering via `WatchFilterer` is unchanged — same behavior
for gitignore/nxignore patterns

### macOS Support for Dynamic Directory Registration

The initial implementation worked on Linux and Windows but failed tests
on macOS because macOS FSEvents doesn't always provide the same
`FileEventKind` tags as Linux inotify or Windows ReadDirectoryChangesW.

**Three changes to support macOS:**

1. **watcher.rs**: On macOS, check all events for directory creation
(not just events with specific FileEventKind tags) and verify via
filesystem
2. **types.rs**: Filter directory events from JavaScript callbacks on
macOS (similar to Windows behavior)
3. **watch_filterer.rs**: Allow macOS directory events (`Create(Folder)`
and `Modify(Metadata)`) through the filter so the action handler can
register them

All changes use `#[cfg(target_os = "macos")]` for compile-time
conditional compilation, so Linux/Windows behavior is completely
unchanged and there's zero runtime overhead.

### Additional notes

- Pinned `serde` to `<1.0.220` because serde 1.0.220+ moved `__private`
to `serde_core`, breaking `swc_common 0.31.22`
- No TypeScript changes — the napi interface is identical
- `watch_filterer.rs`, `types.rs`, `utils.rs` required no changes (APIs
are compatible)

## Related Issue(s)
Fixes https://github.com/nrwl/nx/issues/33781
Fixes https://github.com/nrwl/nx-console/issues/2468
<!-- No specific issue linked yet -->

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 17:21:19 -05:00
Louie Weng 693f75149a chore(repo): re-enable gradle e2e tests (#34357)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

Re-enabling tests and putting back kotlin e2e tests for gradle.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-02-06 13:24:48 -08:00
Craigory Coppola 5e4dd04b66 fix(core): only detect flaky tasks for cacheable tasks (#33994)
## Current Behavior

Flaky task detection warns about all tasks that have different exit
codes for the same hash, including non-cacheable tasks. This is
misleading because the flaky task warning message points users to Nx
Cloud's flaky task retry feature, which is only relevant for cached
tasks.

## Expected Behavior

Flaky task detection should only consider tasks where `task.cache ===
true`, making the warning more meaningful and avoiding noise for
non-cacheable tasks.

## Related Issue(s)

N/A - Internal improvement

## Changes Made

###
`packages/nx/src/tasks-runner/life-cycles/task-history-life-cycle.ts`
1. Added `cacheable: boolean` to the `TaskRun` interface
2. In `endTasks`, now tracks `cacheable: taskResult.task.cache === true`
for each task
3. In `endCommand`, filters to only check flaky tasks among cacheable
tasks

###
`packages/nx/src/tasks-runner/life-cycles/task-history-life-cycle-old.ts`
1. Added `cacheableHashes: Set<string>` to track which task hashes are
cacheable
2. In `endTasks`, tracks cacheable tasks by adding their hash to the set
3. In `endCommand`, only checks for flaky tasks among cacheable task
hashes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 15:39:43 -05:00
Louie Weng 8280910e48 docs(gradle): add compat table and target name prefix (#34359)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

A new version compatibility table is added to help users understand
which versions of the Gradle plugin work with which versions of the Nx
plugin. The targetNamePrefix configuration option is now documented with
an explanation of its use case in polyglot workspaces where target name
collisions may occur.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-02-06 12:35:33 -08:00
Craigory Coppola 37a69f0c9d feat(core): eagerly shutdown plugins that don't provide later hooks (#34253)
# Plugin Isolation Architecture

## 1. Plugin Loading Flow

### 1a. Entry Point - Isolation Decision

```mermaid
flowchart TD
    Start([getPlugins called]) --> CheckIsolation{Isolation<br/>enabled?}
    CheckIsolation -->|Yes| LoadIsolated[loadIsolatedNxPlugin]
    CheckIsolation -->|No| LoadInProcess[loadNxPluginInProcess]
    LoadIsolated --> IsolatedPath([See: Isolated Loading])
    LoadInProcess --> InProcessPath([See: In-Process Loading])
```

### 1b. Isolated Plugin Loading

```mermaid
flowchart TD
    subgraph Main["Main Process"]
        Start([loadIsolatedNxPlugin]) --> CheckCache{In cache?}
        CheckCache -->|Yes| ReturnCached([Return cached promise])
        CheckCache -->|No| StaticLoad[IsolatedPlugin.load]
        StaticLoad --> Resolve[resolveNxPlugin<br/>find plugin path]
        Resolve --> SpawnWorker[spawn child process]
    end

    SpawnWorker -.->|"start process"| WorkerStart

    subgraph Worker["Worker Process (plugin-worker.ts)"]
        WorkerStart([process starts]) --> CreateServer[create Unix socket server]
        CreateServer --> Listen[listen for connections]
        Listen --> WaitForConnect[wait for main process]
        WaitForConnect --> HandleLoad[receive 'load' message]

        subgraph InProcess["In-Process Loading (same as 1c)"]
            HandleLoad --> RequirePlugin[require plugin module]
            RequirePlugin --> NormalizePlugin[normalizeNxPlugin]
        end

        NormalizePlugin --> SendLoadResult[send 'loadResult'<br/>with hook capabilities]
        SendLoadResult --> WaitForMessages[wait for hook messages<br/>or socket close]
        WaitForMessages --> HandleHook{message<br/>received?}
        HandleHook -->|hook message| ExecuteHook[call plugin.hook]
        ExecuteHook --> SendResult[send result]
        SendResult --> WaitForMessages
        HandleHook -->|socket closed| Cleanup[cleanup & exit]
    end

    subgraph Main2["Main Process (continued)"]
        ConnectSocket[connect via<br/>Unix socket] --> SendLoad[send 'load' message]
        SendLoad --> WaitLoad[wait for 'loadResult']
        WaitLoad --> SetupHooks[setupHooks<br/>create lifecycle manager]
        SetupHooks --> CheckGraphHooks{Has graph<br/>phase hooks?}
        CheckGraphHooks -->|No| EarlyShutdown[socket.end<br/>shutdown worker]
        CheckGraphHooks -->|Yes| KeepAlive[keep worker alive]
        EarlyShutdown --> Done([Plugin ready])
        KeepAlive --> Done
    end

    SpawnWorker --> ConnectSocket
    SendLoadResult -.->|"loadResult"| WaitLoad
    EarlyShutdown -.->|"socket close"| Cleanup
```

### 1c. In-Process Plugin Loading

```mermaid
flowchart TD
    Start([loadNxPluginInProcess]) --> Resolve[resolveNxPlugin]
    Resolve --> Require[require plugin module]
    Require --> Normalize[normalizeNxPlugin<br/>wrap hooks]
    Normalize --> Done([Plugin ready])
```

## 2. Hook Execution Flow

### 2a. Isolated Hook Execution

```mermaid
flowchart TD
    Start([hook called<br/>e.g. createNodes]) --> EnsureAlive{_alive?}
    EnsureAlive -->|No| Restart[spawnAndConnect<br/>restart worker]
    Restart --> SetAlive[_alive = true]
    SetAlive --> EnsureAlive

    EnsureAlive -->|Yes| EnterHook[lifecycle.enterHook<br/>increment session count]
    EnterHook --> SendRequest[sendRequest<br/>over socket]
    SendRequest --> WaitResponse[wait for response<br/>with timeout]

    WaitResponse --> CheckSuccess{success?}
    CheckSuccess -->|No| ExitHookError[lifecycle.exitHook]
    ExitHookError --> ThrowError[throw error]

    CheckSuccess -->|Yes| ExitHook[lifecycle.exitHook]
    ExitHook --> CheckShutdown{should<br/>shutdown?}
    CheckShutdown -->|Yes| Shutdown[shutdown worker]
    CheckShutdown -->|No| Return([return result])
    Shutdown --> Return
```

### 2b. Shutdown Decision Logic

```mermaid
flowchart TD
    Start([exitHook called]) --> IsLastHook{Last hook<br/>in phase?}
    IsLastHook -->|No| NoShutdown1([return false])

    IsLastHook -->|Yes| CheckSessions{sessionCount<br/>== 0?}
    CheckSessions -->|No| NoShutdown2([return false<br/>other callers active])

    CheckSessions -->|Yes| CheckLaterPhases{Has later<br/>active phases?}
    CheckLaterPhases -->|Yes| NoShutdown3([return false<br/>needed later])
    CheckLaterPhases -->|No| YesShutdown([return true<br/>safe to shutdown])
```

## 3. Developer Workflow: Adding/Modifying Plugin Hooks

### Step 1: Design Public API

```mermaid
flowchart TD
    A1[public-api.ts] --> A2[Define context type<br/>e.g. MyHookContext]
    A2 --> A3[Export new types]
    A3 --> A4[loaded-nx-plugin.ts]
    A4 --> A5[Add hook to<br/>LoadedNxPlugin interface]
```

### Step 2: Define Message Types

```mermaid
flowchart TD
    B1[messaging.ts] --> B2[Add entry to PluginMessageDefs]
    B2 --> B3[Define payload and result types]
    B3 --> B4[Add to MESSAGE_TYPES array]
    B4 --> B5[Add to RESULT_TYPES array]
```

The messaging system uses a unified `DefineMessages` pattern. To add a
new message:

```typescript
// In PluginMessageDefs, add a new entry:
type PluginMessageDefs = DefineMessages<{
  // ... existing messages ...

  myHook: {
    payload: {
      context: MyHookContext;
    };
    result:
      | { success: true; data: MyResultData }
      | { success: false; error: Error };
  };
}>;
```

The individual message/result types (`PluginWorkerMyHookMessage`,
`PluginWorkerMyHookResult`)
are automatically derived. Export them if needed for external use:

```typescript
export type PluginWorkerMyHookMessage = MessageOf<PluginMessageDefs, 'myHook'>;
export type PluginWorkerMyHookResult = ResultOf<PluginMessageDefs, 'myHook'>;
```

### Step 3: Handle in Worker Process

```mermaid
flowchart TD
    C1[plugin-worker.ts] --> C2[Add handler in<br/>consumeMessage]
    C2 --> C3["Call plugin.myHook()"]
    C3 --> C4[Return result payload]
```

Handlers return just the result payload - the infrastructure wraps it
automatically:

```typescript
// In consumeMessage handlers:
myHook: async ({ context }) => {
  try {
    const data = await plugin.myHook(context);
    return { success: true as const, data };
  } catch (e) {
    return { success: false as const, error: createSerializableError(e) };
  }
},
```

### Step 4: Update Load Result

```mermaid
flowchart TD
    D1[messaging.ts] --> D2[Add hasMyHook to<br/>load.result in PluginMessageDefs]
    D2 --> D3[plugin-worker.ts]
    D3 --> D4[Populate hasMyHook<br/>in load handler]
```

### Step 5: Wire Up IsolatedPlugin

```mermaid
flowchart TD
    E1[isolated-plugin.ts] --> E2[Add hook property<br/>to class]
    E2 --> E3[Update LoadResultPayload<br/>type export]
    E3 --> E4[Add to registeredHooks<br/>array in setupHooks]
    E4 --> E5[Add wrapped hook<br/>implementation]
    E5 --> E6["wrap('myHook', async (ctx) => {<br/>  sendRequest('myHook', { context: ctx })<br/>})"]
```

### Step 6: Update Lifecycle Phases (if needed)

```mermaid
flowchart TD
    F1{New phase<br/>needed?} -->|Yes| F2[plugin-lifecycle-manager.ts]
    F2 --> F3[Add phase to<br/>HOOKS_BY_PHASE]
    F1 -->|No| F4[Add hook to existing<br/>phase array in HOOKS_BY_PHASE]
```

### Step 7: Add Tests

```mermaid
flowchart TD
    G1[isolated-plugin.spec.ts] --> G2[Test hook registration]
    G2 --> G3[Test hook execution]
    G3 --> G4[Test restart behavior]
    G4 --> G5[plugin-lifecycle-manager.spec.ts]
    G5 --> G6[Test phase transitions<br/>with new hook]
    G6 --> G7[Test shutdown decisions]
```

## File Reference

| File | Purpose |
| ----------------------------- |
------------------------------------------------------------- |
| `../public-api.ts` | Public types exported to plugin authors |
| `../loaded-nx-plugin.ts` | Interface definition for loaded plugins |
| `messaging.ts` | Message type definitions for worker communication |
| `plugin-worker.ts` | Worker process - receives messages, calls plugin
functions |
| `isolated-plugin.ts` | Main class - spawns worker, sends messages,
manages lifecycle |
| `plugin-lifecycle-manager.ts` | Tracks phases, decides when to
shutdown |
| `load-isolated-plugin.ts` | Caching layer for isolated plugins |
| `../get-plugins.ts` | Entry point - decides isolation mode |

## Lifecycle Phases

```
LOADED → [graph] → [pre-task] → {tasks run} → [post-task]
           │           │                           │
           │           └── preTasksExecution ──────┤
           │                                       │
           ├── createNodes                         │
           ├── createDependencies                  │
           └── createMetadata                      │
                                                   │
                                    postTasksExecution
```

**Shutdown rules:**

- Plugin shuts down after its last active phase completes
- If only `postTasksExecution`: shutdown immediately after load, restart
when needed
- Concurrent callers tracked via session count (ref counting)

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
2026-02-06 14:54:10 -05:00
Caleb Ukle 359c7fbf4e fix(nx-dev): include nx cli examples on refs page (#34367)
![wm_2026-02-06T11-26-54@2x](https://github.com/user-attachments/assets/c2814268-e921-420b-a9e2-2b90c8a89526)

https://deploy-preview-34367--nx-docs.netlify.app/docs/reference/nx-commands#nx-add

cli examples are included in the generated page now

fixes: DOC-402
2026-02-06 12:39:00 -05:00
James Henry 0d2882e1c0 fix(core): use picocolors instead of chalk in the nx package (#34305) 2026-02-06 19:17:52 +04:00
Colum Ferry 541498f58a feat(core): update cnw messaging (#34364)
CLOSES CLOUD-4235
2026-02-06 10:04:42 -05:00
Colum Ferry b85ac155cf feat(js): update swc/cli to 0.8.0 (#34365)
Update `@swc/cli` to 0.8.0 which uses chokidar v5
2026-02-06 08:47:56 -05:00
MaxKless 5f42ccc3e9 docs(maven): minor tweaks and include targetNamePrefix (#34362)
this makes the maven documentation more complete for the latest changes
2026-02-06 13:30:44 +00:00
Louie Weng 8fdfc523a8 chore(gradle): bump gradle version to 0.1.12 (#34250)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

Gradle plugin to 0.1.12

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-02-05 15:32:01 -08:00
Louie Weng a44a27b29a feat(gradle): add debug env var to gradle batch executor (#34259)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

- No way to run the batch executor in debug mode
- Any flags passed into the nx gradle batch command get forwarded into
the `gradlew` command.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

- Allow for an env variable to be set for debug flags so that the batch
runner jar can be run with a debugger hooked in.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #NXC-3797

---------

Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
2026-02-05 22:26:02 +00:00
Leosvel Pérez Espinosa 0cac182b73 fix(core): avoid crash when pane area is out of bounds during resize (#34343)
## Current Behavior

In some resize/background scenarios, the TUI could crash while rendering
output panes, displaying a "Scrollbar area is empty" message.

## Expected Behavior

The TUI remains stable during resizes and backgrounding. Output panes no
longer crash when a scrollbar would render in an invalid area.
2026-02-05 17:11:47 -05:00
Louie Weng b69ff4ceb9 chore(repo): fix broken disablement command (#34355)
<!-- 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-02-05 17:11:01 -05:00
Louie Weng 4337d2e928 fix(gradle): use gradle project name when resolving dependent tasks (#34331)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

When Nx processes Gradle subprojects, task dependencies reference the
wrong project names. For a subproject structure like :app or :lib:core,
the generated task dependencies use only the simple project name (app or
core) instead of the full build tree path (:app or :lib:core). This
causes dependent tasks to be generated with incorrect project
references, breaking the task graph for multi-project Gradle builds.

## Expected Behavior
Task dependencies should use the full Gradle build tree path for
subprojects. When a task in :app depends on a task in :lib, the
dependency should be correctly referenced as :lib:taskName.

The fix introduces a getNxProjectName() utility function that correctly
resolves the Nx project name based on the Gradle project's
buildTreePath, and applies it consistently across all dependency
resolution logic in ProjectUtils.kt and
TaskUtils.kt. New tests verify the fix works for both single and nested
subproject structures.

Also removed --rerun-tasks from the batch and non batch runners, we
found that during parallel task executions with the non batch runner,
the flag would cause cache conflicts that would fail tasks.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-02-05 13:44:05 -08:00
Leosvel Pérez Espinosa a0be6adcf8 fix(core): track all task outputs regardless of path depth (#34321)
## Current Behavior

When a task has outputs at different path depths, some outputs may not
be tracked. This causes:

- Deleted output files not being detected
- Cache restoration being skipped with message "existing outputs match
the cache, left as is"
- Files not being restored even though they exist in cache

## Expected Behavior

All task outputs are tracked regardless of their path depth, ensuring:

- Deleted outputs are correctly detected
- Cache restoration happens when outputs are missing
2026-02-05 16:41:39 -05:00
Leosvel Pérez Espinosa dffdfa694c fix(core): disable ignore filters for outputs expansion (#34316)
## Current Behavior

When a task output directory contains a nested `.gitignore` that hides
its contents, Nx can treat the outputs as already present and skip
restoring them from cache. This can result in generated files being
missing from disk, even though the cache entry is valid.

## Expected Behavior

Nx should restore cached outputs regardless of ignore rules inside the
output directory.

## Related Issue(s)

Fixes #32620
2026-02-05 16:37:14 -05:00
Louie Weng a40ff52db0 chore(gradle): temporarily disable e2e tests (#34351)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

Disabling Gradle e2e tests until foojay toolchain service back online.

Ensures that gradle within the Nx repo uses mise to download java
toolchain, but gradle workspaces within the e2e environments download
their own toolchain.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #Q-175
2026-02-05 16:32:59 -05:00
Philip Fulcher bd41ce5692 docs(nx-dev): add feb 2026 webinar (#34352) 2026-02-05 16:24:18 -05:00
Philip Fulcher 1f09a5d1a5 docs(nx-dev): remove errant author from article (#34349) 2026-02-05 14:58:04 -05:00
Jack Hsu 0b99f560d4 feat(core): add AI agent detection and NDJSON output for CNW (#34320)
AI agents are detected via environment variables (CLAUDECODE, OPENCODE)
and receive NDJSON streaming output, non-interactive mode, structured
JSON results with explicit GitHub setup instructions.

Related NXC-3628

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 14:57:39 -05:00
Caleb Ukle 866d5b1e9b fix(nx-dev): use right URL for the given netlify context (#34348) 2026-02-05 13:19:17 -05:00
Caleb Ukle d4ad62f522 fix(nx-dev): fix og images wrong URL for embeds (#34346)
fixes: DOC-399

next used the VERCEL_URL by default for metadataBase. this is not
present in netlify. so resolve to netlify URLs if VERCEL_URL is not
present so metadata links are correct.

working on PR:
<img width="704" height="875" alt="image"
src="https://github.com/user-attachments/assets/4edd194e-27d6-46ce-bdd2-7da4ab70d482"
/>
<img width="976" height="204" alt="image"
src="https://github.com/user-attachments/assets/63ea32f5-20cf-4df2-96a1-a1bc2ff59411"
/>



https://6984ce9d1bad30000873247d--nx-dev.netlify.app/blog/nx-2026-roadmap
2026-02-05 17:45:12 +00:00
Louie Weng 3aff575002 docs(gradle): add reference to batch mode (#34271)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

Add documentation for batch mode. Remove references to removed custom
overrides for intTest.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-02-05 08:07:05 -08:00
Jonathan Cammisuli 7df9d96dac feat(core): add command to download cloud client (#34333)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->
The only way to download the cloud client is to run a specific cloud
command or a task with cloud configured.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
A new command is added where we only download the cloud client with:
```
nx download-cloud-client
```

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-02-05 10:42:55 -05:00
Caleb Ukle 05fb208fda docs(node): add bundling guide (#34244)
add guide to clarify various ways to bundle a node app for different
bundlers

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: barbados-clemens <barbados-clemens@users.noreply.github.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2026-02-05 15:25:42 +00:00
Philip Fulcher 33f031be04 docs(nx-dev): add 2026 roadmap article (#34327) 2026-02-05 08:47:23 -06:00
James Henry 0e11f95163 chore(repo): add dependabot config to try and remove false positives (#34341) 2026-02-05 18:26:46 +04:00
Juri Strumpflohner 2b07ac22c2 feat(core): improve AI agent rules for CLAUDE.md generation (#34304)
## Summary

Updates the generated CLAUDE.md content with improved guidance for AI
agents working with Nx workspaces.

**Changes:**
- Add "nx-workspace skill first" for workspace navigation
- Add "prefix nx commands with package manager" rule
- Add "NEVER guess CLI flags" rule
- Add "Scaffolding & Generators" section (invoke nx-generate skill
first)
- Add "When to use nx_docs" guidance (USE for advanced config, DON'T USE
for basic syntax)

## Why These Changes

Based on testing with repeated scaffolding tasks, agents were:

| Issue | Fix |
|-------|-----|
| Calling `nx_docs` for basic generator syntax | Added clear guidance on
when to use/not use nx_docs |
| Guessing CLI flags incorrectly (e.g., `nx sync --apply`) | Added
"never guess flags" rule |
| Using global nx CLI causing version mismatches | Added package manager
prefix rule |
| Not invoking nx-generate skill on scaffolding tasks | Added explicit
"Scaffolding & Generators" section |

## Related

Companion PR with skill improvements:
https://github.com/nrwl/nx-ai-agents-config/pull/26

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-02-05 21:17:59 +09:00
Jason Jean efa364f73d fix(core): preserve task selection when unrelated tasks finish (#34328)
## Current Behavior

In the TUI, when any standalone task finishes,
`handle_standalone_task_finished` unconditionally switches the user's
selection to another in-progress task — even if the finished task wasn't
the one the user had selected. This causes the selection to jump
unexpectedly while the user is watching a different task.

## Expected Behavior

Selection should only change when the task the user is actively viewing
finishes. If an unrelated background task finishes, the user's selection
should remain on whatever they chose.

## Related Issue(s)

N/A — discovered during development testing.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-02-04 21:23:22 -05:00
Craigory Coppola 82265ff157 fix(core): allow overriding daemon logging settings (#34324)
## Current Behavior
NX_NATIVE_LOGGING is hardcoded and can't be customized on the daemon
server

## Expected Behavior
Log settings can be customized by changing them in the env of the first
command to spawn the daemon

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-02-04 21:12:23 -05:00
Jason Jean 015ff1a45b chore(repo): update nx to 22.5.0-beta.4 (#34334)
Updating Nx from 22.5.0-beta.3 to 22.5.0-beta.4
2026-02-05 00:27:21 +00:00
Caleb Ukle cdb4bb2acc fix(nx-dev): exclude large native deps from build bundle (#34335)
we were including native binaries in the final netlify function build
for nextjs which was fine until we reach the limit of 250mb causing a
failure to upload the function (AWS imposed lambda limit)

Now we strip out any deps we know we don't need for the app which are
dev deps and not runtime required.
2026-02-04 19:09:25 -05:00
Jason Jean 3c7f94e3d5 chore(repo): align canary and PR release versions with next (#34330)
## Current Behavior

- Canary releases calculate their base version by incrementing the minor
of `nx@latest`, or using `nx@next` major when majors differ
- PR releases always use `0.0.0` as their base version (e.g.
`0.0.0-pr-1234-abc1234`)
- This means canary and PR versions don't clearly relate to the current
beta release line

## Expected Behavior

- Both canary and PR releases derive their base version directly from
`nx@next`
- If next is `22.5.0-beta.5`, then:
  - Canary: `22.5.0-canary.20260204-abc1234`
  - PR: `22.5.0-pr.1234.abc1234`
- All prerelease channels now share the same base version, making it
clear which release line they belong to

## Related Issue(s)

N/A - internal improvement to release infrastructure
2026-02-04 16:36:44 -05:00
Jason Jean ef55f97df3 feat(maven): bump maven plugin version to 0.0.13 (#34318)
## Current Behavior

The Maven plugin version is `0.0.12` across all pom.xml files and the
versions.ts constant. The `bump-maven-version` generator does not update
the `batch-runner-adapters` pom files, causing version mismatches.

## Expected Behavior

The Maven plugin version is bumped to `0.0.13` in **all** pom.xml files
(including batch-runner-adapters), with a migration created for users
upgrading to Nx `22.5.0-beta.4`. The bump generator now includes the
batch-runner-adapters pom files so future bumps won't miss them.

### Changes
- Updated version from `0.0.12` to `0.0.13` in all pom.xml files (root,
maven, maven-plugin, shared, batch-runner, batch-runner-adapters,
maven3-adapter, maven4-adapter)
- Updated `mavenPluginVersion` constant in
`packages/maven/src/utils/versions.ts`
- Added `update-0-0-13` migration entry in
`packages/maven/migrations.json` targeting Nx `22.5.0-beta.4`
- Created migration file
`packages/maven/src/migrations/0-0-13/update-pom-xml-version.ts`
- Fixed `bump-maven-version` generator to include
`batch-runner-adapters` pom files
2026-02-04 14:06:18 -05:00
Jack Hsu 5880551c6a chore(repo): update docs readme with guiding principles (#34319)
This PR adds info for how we structure the docs so when we write them we
know where to put things, what to write, etc.
2026-02-04 13:40:08 -05:00
Jason Jean fb6c2982e6 fix(misc): improve freebsd build reliability with better error handling and disk cleanup (#34326)
## Current Behavior

The FreeBSD build in CI can fail silently or with unclear error messages
when:
- Disk space runs low during the build process
- The build command fails without proper error propagation
- Unnecessary files consume valuable disk space

## Expected Behavior

With these changes:
- Additional disk space is freed by removing docs/astro-docs/nx-dev
directories before building
- Build exit codes are properly captured and propagated
- Disk usage is logged after the build completes for debugging purposes
- Build failures are clearly reported with explicit error messages

This improves reliability and makes it easier to diagnose issues when
they occur.

## Related Issue(s)

<!-- No specific issue, general CI improvement -->
2026-02-04 13:34:52 -05:00
Caleb Ukle 81944b02c5 feat(nx-dev): reformat sidebar into topics (#34265)
Sidebar contains everything about the docs which is overhelming and hard
to find information a person could be looking for.

instead we move into "topics" to section the sidebar based on intention
of content, making it easier to have a journey through the docs or jump
straight to the content someone could be looking for.

This doesn't change any routes of pages, just hard links into the
sidebar (instead of autogenerate based on dir) in the future once we
solidify where everything will live, we can come back and rearrange
files to reuse the autogenerate dirs.


![wm_2026-02-04T10-48-54](https://github.com/user-attachments/assets/6add05b3-b929-492f-a54e-fde6c2929973)

![wm_2026-02-04T10-50-10](https://github.com/user-attachments/assets/e9c89621-c287-4c9a-8d14-692bd1a73739)
<img width="1385" height="1003" alt="image"
src="https://github.com/user-attachments/assets/4bbee4fe-2f7e-4d7a-ad6b-45f6064c897a"
/>

![wm_2026-02-04T11-03-15@2x](https://github.com/user-attachments/assets/10561621-5c80-4843-a7c4-a1272adf7b8e)

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: barbados-clemens <barbados-clemens@users.noreply.github.com>
2026-02-04 11:57:47 -06:00
Jack Hsu c1cc626e52 docs(misc): filter out non-existing image requests and also only track HTML page views when requested with "Accept: text/html" (#34317)
We're getting requests to `favicon.svg.md` that are being tracked,
ignore these. Also for the server page views, we should only count them
if `text/html` is in the accept header. Browsers will send these, and AI
agents, curl, etc. do not. This allows us to compare browser traffic vs
AI/curl traffic more accurately.
2026-02-04 10:38:25 -05:00
Jason Jean 6f3d38ad3c fix(core): handle EPIPE errors gracefully in daemon socket writes (#34311)
## Current Behavior

When a client disconnects while the daemon is writing a response, a
`socket.write` call triggers an EPIPE error. The old error handler used
`console.error`, which caused the error to propagate through
`respondWithErrorAndExit` and crash the daemon process via
`process.exit(1)`. The client would then see an `internalDaemonError`
and permanently disable the daemon via `markDaemonAsDisabled`, requiring
`nx reset` to recover.

Additionally, disconnected sockets were not cleaned up from the file
watcher and project graph listener registries on socket error events,
only on `close` events. This left a window where the daemon could
attempt to write to dead sockets during notifications.

## Expected Behavior

When a client disconnects mid-response:
- The `socket.write` callback logs the error gracefully via
`serverLogger` instead of `console.error`
- The daemon process stays alive and continues serving other clients
- The `socket.on('error')` handler cleans up registered file watcher and
project graph listener sockets immediately, matching the existing
`close` handler behavior
- The daemon is never permanently disabled due to EPIPE errors

## Related Issue(s)

<!-- No linked issue -->
2026-02-04 09:43:59 -05:00
MaxKless d0e4a92738 fix(core): tweak configure-ai-agents messaging (#34307)
### Current Behavior
The nx configure-ai-agents command output is minimal - just "AI agents
set up successfully" with a
bullet list of agent names. Users don't understand what was actually
configured (plugin vs MCP,
skills, which files were created/modified).
### Expected Behavior
Clearer feedback about what gets configured for each agent:
Selection prompt improvements:
- Agents needing updates show (update available) tag
- Footer always shows result state: what will be configured
- Agent-specific descriptions (e.g., "Installs Nx plugin (MCP + skills +
agents). Updates
CLAUDE.md.")
Post-configuration output:
- Compact summary per agent showing what was set up
- Example: Claude Code: Nx plugin (MCP + skills + agents) + CLAUDE.md
Claude .mcp.json cleanup:
- When configuring Claude, removes nx-mcp from .mcp.json since it's now
handled by the plugin
- Deletes the file entirely if nx-mcp was the only entry

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-02-04 09:43:38 -05:00
Jack Hsu 2ea6961e0d fix(core): fix CNW git amend and README marker handling (#34306)
This PR fixes two issues:
1. When the README changes are amended, there's an edge case where we
don't have a commit to amend (e.g. `--skipGit`), and this fails the
entire CNW flow.
2. When user opts out of Cloud, we strip the entire `<!-- BEGIN:
nx-cloud -->` block in README rather than just the comments and leaving
the content.

Closes NXC-3812

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 08:50:42 -05:00
Benjamin Cabanes 1195565b96 docs(nx-dev): update Nx Cloud & Home features, add new components (#34149) 2026-02-03 18:04:01 -05:00
Craigory Coppola 7478cbb59d feat(core): add initial impl of task io service (#34205)
## Current Behavior
There's not an easy to use service to track PIDs being registered to nx
tasks

## Expected Behavior
There's a service to track this stuff

## Related Issue(s)
2026-02-03 17:42:17 -05:00
Jason Jean 79d878f240 fix(core): prevent command injection in getNpmPackageVersion (#34309)
## Current Behavior

The `getNpmPackageVersion` function in
`packages/workspace/src/generators/utils/get-npm-package-version.ts`
uses `execSync` with direct string interpolation of the `packageName`
parameter. When a user runs `create-nx-workspace` with a custom
`--preset` value that doesn't match a known preset, the value flows
unsanitized into a shell command:

```js
execSync(`npm view ${packageName}... version --json`)
```

This allows arbitrary command execution via shell metacharacters (e.g.,
`--preset='pkg$(malicious command)'`).

## Expected Behavior

User-supplied package names are validated against a strict npm package
name regex before being passed to any shell command. The function now
uses `execFileSync` with an args array instead of `execSync` with string
interpolation, providing defense in depth:

1. **Input validation** — rejects anything that isn't a valid npm
package name
2. **Safe execution** — arguments are passed as an array so Node.js
handles escaping, rather than concatenating into a raw shell string
2026-02-03 16:18:43 -05:00
Caleb Ukle b198606bef docs(nx-cloud): add screenshots for cache troubleshoot guide (#34296) 2026-02-03 15:35:34 -05:00
Craigory Coppola 1cb6c0b14c fix(core): nx should show help for run-one when using project short names (#34303)
## Current Behavior
Given a project name like `:foo`, you can run tasks like `nx test foo`
(note `foo` vs `:foo`), but passing --help throws an error

## Expected Behavior
`--help` works the same with the shortname vs full name

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-02-03 14:56:23 -05:00
Jason Jean 3f6bdc7ff7 fix(maven): include pom.xml and ancestor pom files as inputs for all targets (#34291)
## Current Behavior

- `pom.xml` is only included as an input when a mojo uses default inputs
- Mojos with specific input configurations (like
`maven-compiler-plugin:compile`) don't get `pom.xml` in their inputs
- Parent `pom.xml` files aren't tracked as inputs

This can lead to stale cache hits when:
1. `pom.xml` changes but a mojo has specific input config
2. A parent `pom.xml` changes (affecting inherited properties,
dependency versions, plugin config)

## Expected Behavior

- Every target should include its own `pom.xml` as an input
- Every target should include ancestor `pom.xml` files (within the
workspace) as inputs
- Cache should invalidate when any relevant `pom.xml` changes

## Related Issue(s)

N/A - discovered during code review

## Changes

- **CacheConfig.kt**: Removed `pom.xml` from `defaultInputs` (now always
added explicitly)
- **MojoAnalyzer.kt**: Added `workspaceRoot` parameter and logic to walk
up the parent chain, adding all in-workspace ancestor `pom.xml` files as
inputs
- **NxProjectAnalyzerMojo.kt**: Pass `workspaceRoot` to `MojoAnalyzer`
2026-02-03 14:26:11 -05:00
Jason Jean cc4ec68bce chore(repo): update nx to 22.5.0-beta.3 (#34295)
Updating Nx from 22.5.0-beta.2 to 22.5.0-beta.3
2026-02-03 14:20:30 -05:00
Juri 01d2f64b90 docs(nx-dev): add autonomous AI workflows blog post
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 17:26:28 +01:00
James Henry 39d8a9a6ac chore(repo): update to pnpm@10.28.2 and clean up pnpm config (#34298) 2026-02-03 17:48:01 +04:00
Jack Hsu 4f02c6b56e docs(misc): ignore /docs/og/*.png.md paths (#34289)
This PR excludes `/docs/og/*` paths from assets tracking. This is likely
added by some crawler and is additional compute/noise that we don't care
about.

<img width="1354" height="86" alt="image"
src="https://github.com/user-attachments/assets/6bfda816-0d26-46b3-b432-ae96e5976c37"
/>
2026-02-02 13:32:06 -05:00
Jack Hsu 3f77cd5927 fix(nx-dev): fix double-counting and exclude assets from page tracking (#34286)
## Current Behavior

1. **track-asset-requests** runs twice per request due to redundant path
patterns:
   ```typescript
   path: ["/*.txt", "/**/*.txt", "/*.md", "/**/*.md"]
   ```
The `/**/*` pattern already matches root level files, so `/*` is
redundant.

2. **track-page-requests** runs on many asset requests even though it
only tracks HTML page views:
   - Font files: `/docs/fonts/*.woff2`, `/docs/*.woff`
   - Images: `/docs/*.svg`, `/docs/*.png`, `/docs/og/*`
   - Pagefind search index: `/docs/pagefind/*`

## Expected Behavior

1. Asset tracking should fire only once per request
2. Page tracking should exclude all non-HTML assets at Netlify level
(zero compute)

## Changes

### track-asset-requests.ts
Simplified path patterns:
```typescript
path: ["/**/*.txt", "/**/*.md"]
```

### track-page-requests.ts
Added comprehensive exclusions:

| Category | Exclusions |
|----------|------------|
| Images | `.svg`, `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.ico`,
`/images/*`, `/og/*` |
| Fonts | `/fonts/*`, `.woff`, `.woff2` |
| Search | `/pagefind/*` |

## Related Issue(s)

Fixes DOC-395
2026-02-02 12:17:32 -05:00
Jason Jean 89aa25e5d0 fix(core): resolve daemon client reconnect queue deadlock (#34284)
## Current Behavior

When the daemon dies while processing a request, the reconnect logic
adds the retry back to the promise-based queue. However, the original
request is still blocked in the queue waiting for a response that will
never come (the socket is dead). This creates a deadlock:

1. Original request (`fn1`) is blocked awaiting a promise that will
never resolve
2. Retry request (`fn2`) is queued but can't execute until `fn1`
completes
3. `fn1` can't complete because it's waiting on the dead socket

## Expected Behavior

When reconnecting after daemon death, the retry should resolve the
pending promise that the original queue entry is waiting on, allowing
the queue to proceed normally.

## Related Issue(s)

<!-- No specific issue, discovered during development -->

## Solution

Instead of re-queuing the retry through `sendToDaemonViaQueue` (which
adds to the end of the queue), we now call `sendMessageToDaemon`
directly. This resolves the pending promise that `fn1` is waiting on,
allowing it to complete naturally and the queue to proceed.

Also removed the now-unused `decrementQueueCounter` method from
`PromisedBasedQueue`.
2026-02-02 12:05:33 -05:00
Jack Hsu cdd735dc63 feat(nx-dev): add server-side page view tracking for docs (#34283)
## Current Behavior

Only markdown and text file requests are tracked server-side via the
`track-asset-requests` edge function. HTML page views are not tracked on
the server, missing requests from AI tools and curl.

## Expected Behavior

Track all doc page views server-side with a new edge function that:
- Sends `server_page_view` events to GA4 with `content_type` param to
differentiate HTML/markdown/text
- Uses Netlify's `excludedPath` config for efficient path filtering
(zero compute for excluded paths)
- Skips non-HTML requests via Accept header check

### Changes

| File | Change |
|------|--------|
| `track-page-requests.ts` | **NEW** - Edge function for HTML page view
tracking on `/docs/*` |
| `track-asset-requests.ts` | Changed event name to `server_page_view`,
added `content_type` param |
| `add-link-headers.ts` | Refactored to use `excludedPath` config
instead of runtime path checks |
| `netlify.toml` | Added edge function declaration for
`track-page-requests` |

### GA Event Schema

```javascript
{
  name: 'server_page_view',
  params: {
    content_type: 'text/html' | 'text/markdown' | 'text/plain',
    file_extension: '.html' | '.md' | '.txt',
    is_ai_tool: 'true' | 'false',
    // ... other params
  }
}
```

## Other Notes

This PR also removes the edge function entries from `netlify.toml` since
it's auto detected from `astro-docs/netlify/edge-functions`. This makes
all the configuration in the actual `.ts` file, not duplicated in the
`netlify.toml` file.

## Related Issue(s)

Closes DOC-395

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 11:15:01 -05:00
Juri Strumpflohner f59c15a410 docs(core): update AI pages and include new info about configure-ai-agents command (#34257)
changes to:
-
https://deploy-preview-34257--nx-docs.netlify.app/docs/getting-started/ai-setup
-
https://deploy-preview-34257--nx-docs.netlify.app/docs/features/enhance-ai
-
https://deploy-preview-34257--nx-docs.netlify.app/docs/reference/nx-mcp

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2026-02-02 15:58:32 +00:00
Louie Weng 94319c7531 fix(gradle): enforce that only one gradle task can be passed into gradle executor (#34269)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

The Gradle executor accepts a taskName option that *should not* contain
multiple space-separated tasks. When multiple tasks are provided, the
batch runner misinterprets the space-separated string as containing
project names rather than treating it as a single task argument, leading
to execution errors and confusion.

This only occurs if the taskName is manually overridden and should not
occur when task names are generated by the project graph plugin.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

The Gradle executor now validates that taskName contains only a single
task without spaces. If multiple tasks are passed, it throws a clear
error message: "Task '[taskName]' contains spaces. Only a single Gradle
task is allowed per executor invocation." This prevents the batch runner
from misinterpreting the task name and provides immediate feedback to
users about the correct usage.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-02-02 10:14:08 -05:00
Louie Weng 35bc17e4fe fix(gradle): ensure that batch output is not overriden for atomized targets (#34268)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior

When running Gradle tasks in batch mode with atomized targets, if the
same task appears multiple times in the output (which happens when tasks
are atomized and executed separately), the batch runner only captures
the output from the last execution. Previous executions' output gets
overwritten because the splitOutputPerTask function replaces the entire
output for each task name it encounters.

## Expected Behavior

All output from a task should be preserved, even when the task appears
multiple times in the batch output. When the same task name is
encountered multiple times, the outputs should be concatenated rather
than replaced, ensuring developers can see the complete execution
history for atomized targets.



## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-02-02 10:12:27 -05:00
Copilot d65dcfa806 docs(dotnet): fix build target dependsOn example (#34206)
## Plan for fixing .NET incremental builds documentation

- [x] Update the "Target dependencies" section to remove `restore` from
the `dependsOn` array
- [x] Add explanation about why `restore` cannot be run through Nx for
solutions using custom frameworks
- [x] Improve explanation clarity based on review feedback
- [x] Verify the "Target configuration" section is consistent with the
changes
- [x] Complete code review and address feedback
- [x] Run security checks (no issues found)
- [x] Address PR feedback: Use JSX `<Aside>` component with import
statement instead of Markdoc tag

## Summary

Successfully fixed the documentation issue in the .NET incremental
builds guide. The changes made:

1. **Removed `restore` from the build target's `dependsOn` array** - The
documentation now correctly shows `"dependsOn": ["^build"]` instead of
`"dependsOn": ["restore", "^build"]`, matching the actual implementation
in the plugin code.

2. **Added a clear explanation** - Included an aside box explaining why
`restore` is not in the `dependsOn` array: because Nx requires NuGet
package restoration to be completed before running any tasks, and
including it would create a circular dependency.

3. **Verified consistency** - Checked that the "Target configuration"
section already showed the correct configuration, ensuring all
documentation is now consistent.

4. **Used correct Starlight component** - Changed from Markdoc `{% aside
%}` tag to JSX `<Aside>` component with proper import statement per
Starlight documentation standards.

The changes align with the actual implementation in
`packages/dotnet/analyzer/Utilities/TargetBuilder.Build.cs` where the
build target's `dependsOn` is set to `[$"^{targetName}"]` (line 56).

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>docs(dotnet): implied conflict in dependsOn of inferred
build task</issue_title>
> <issue_description>### Documentation issue
> 
> <!-- (Update "[ ]" to "[x]" to check a box) -->
> 
> - [ ] Reporting a typo
> - [ ] Reporting a documentation bug
> - [ ] Documentation improvement
> - [x] Documentation feedback
> 
> <!--
> If your issue is not regarding the documentation, please choose an
issue type:
>   https://github.com/nrwl/nx/issues/new/choose
> -->
> 
> ### Is there a specific documentation page you are reporting?
> 
>
https://nx.dev/docs/technologies/dotnet/guides/incremental-builds#target-dependencies
> 
> ### Additional context or description
> 
> The code sample provides in this doc includes `"dependsOn":
["restore", "^build"]`, but the automatically inferred `build` target
from this plugin does not actually include the `restore` target in the
dependsOn array. I assume this is by design? The docs seem to confuse it
a bit.
> </issue_description>
> 
> <agent_instructions>Remove the `restore` target from the dependsOn
block. Add a small explanation that we can't run restore through Nx
because Nx requires restore to have been completed prior to running
tasks if the solution uses a custom framework</agent_instructions>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes nrwl/nx#34150

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AgentEnder <6933928+AgentEnder@users.noreply.github.com>
2026-02-02 08:55:21 -06:00
iceThief (민찬기) e35dcd2050 fix(core): handle multibyte UTF-8 characters in socket message consumption (#34151)
## Current Behavior

When socket data chunks split a multibyte UTF-8 character (e.g., CJK
characters like Korean, Chinese, Japanese) at an arbitrary byte
boundary, `Buffer.toString()` decodes incomplete byte sequences as
replacement characters (�), causing message corruption.

This can occur when:
- File paths contain non-ASCII characters
- Project names include multibyte characters
- Any JSON message contains international text

## Expected Behavior

Multibyte UTF-8 characters should be properly decoded even when split
across multiple socket data chunks. The fix uses Node.js `StringDecoder`
which buffers incomplete multibyte sequences until the remaining bytes
arrive.

## Related Issue(s)

Fixes socket message corruption for paths/names containing multibyte
characters.
2026-02-01 22:09:53 -05:00
Caleb Ukle 251121530d fix(nx-dev): make headers and table options linkable (#34267)
- fix(nx-dev): always link headers regardless of mdoc or markdown
content source (generated vs static file)
- fix(nx-dev): make option/property columns in table linkable
- the table column header is matched on `options`, `option`,
`properties`, and property` (case insensitive)



https://github.com/user-attachments/assets/7250b9d5-1030-4ebc-9e21-0a05f295bbf5


Note bc mdoc and `renderMarkdown` go through 2 different rendering
pipelines, this logic must bc within the markdoc config and rehype
(markdown) processing logic. tried to shared logic where I could

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:15:46 -05:00
Jack Hsu ec110b7bff feat(core): add decorative banners for Nx Cloud CNW completion message (#34270)
## Current Behavior

After completing the CNW (Create Nx Workspace) flow with Nx Cloud, users
see a plain text completion message with a link to finish setup.

## Expected Behavior

Users now see one of four completion message variants controlled by
`NX_CNW_FLOW_VARIANT`:
- **Variant 0**: Plain link (control) - always used for enterprise URLs
- **Variant 1**: "Try the full Nx platform" decorative ASCII banner
- **Variant 2**: "Unlock 70% faster CI" decorative ASCII banner
- **Variant 3**: "Reclaim your team's focus" decorative ASCII banner

Key changes:
- Added enterprise URL detection (non-standard Nx Cloud URLs always get
variant 0)
- Locked the cloud prompt to always show "Try the full Nx platform?" (no
longer varies by flow variant)
- Flow variant now only affects the completion banner, not the prompt
- Added `snapshot.nx.app` to standard Nx Cloud hosts
- Removed variant 2 auto-connect behavior (all variants now prompt)

## Screenshots
Variant 0:
<img width="1392" height="1065" alt="variant0"
src="https://github.com/user-attachments/assets/0b18686e-1481-4fc0-995e-1577052887ff"
/>

Variant 1:
<img width="1392" height="1065" alt="variant1"
src="https://github.com/user-attachments/assets/e5909e2e-e1d8-4d04-9721-ba6186d06891"
/>

Variant 2:
<img width="1392" height="1065" alt="variant2"
src="https://github.com/user-attachments/assets/7e9f819f-e3c0-44d7-9760-0cab1d5dd9ac"
/>

Variant 3:
<img width="1392" height="1065" alt="variant3"
src="https://github.com/user-attachments/assets/83f0499f-d807-4dc8-9390-c7eec93590a9"
/>


## Related Issue(s)

Closes CLOUD-4147

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:24:18 +00:00
Louie Weng bd627f1096 chore(repo): enable batch mode (#34245)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

Enable Gradle executor to run tasks in batch mode in CI.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-01-30 21:25:51 +00:00
Victor Savkin e3eedf9e94 docs(misc): update the docs to use more direct language (#34264)
<!-- 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-01-30 14:43:59 -05:00
Jack Hsu eb678bfa59 docs(misc): push toc to the right side to match the alignment of the left sidebar (#34266)
This PR aligns the TOC to the right side of the page so the spacing is
more balanced.

<img width="2672" height="1527" alt="image"
src="https://github.com/user-attachments/assets/ef00906a-3788-47fb-a71d-230b3d69c201"
/>

Similar to other docs like React:

<img width="2672" height="1527" alt="image"
src="https://github.com/user-attachments/assets/e9a1cd81-f716-4e09-b148-be80a93b7aee"
/>


---

## Other screen widths

1400px:

<img width="1424" height="1025" alt="Screenshot 2026-01-30 at 12 11
57 PM"
src="https://github.com/user-attachments/assets/d9fc5029-4942-4e32-b2f2-4c66ebcb21df"
/>

1000px (TOC hidden):


<img width="1145" height="1019" alt="Screenshot 2026-01-30 at 12 12
10 PM"
src="https://github.com/user-attachments/assets/ce1cd890-193a-42d7-bb4f-3259146845a8"
/>
2026-01-30 12:42:13 -05:00
Jack Hsu dc8839365f docs(misc): reduce memory footprint of nx-dev build (#34258)
We're showing over 8 GB of memory usage on Netlify, and 11+ GB on
Agents. Let's test out a few ways to reduce the memory footprint.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 10:53:21 -05:00
Richard Roncancio 3f05214013 chore(release): Improve release performance (#33866) 2026-01-30 18:15:49 +04:00
Colum Ferry 86de174d86 fix(testing): preload vitest/node to prevent race condition on Node 24 (#34261)
Preload vitest/node ESM module early in
buildViteTargets/buildVitestTargets
functions before parallel processing occurs. This prevents the
ERR_INTERNAL_ASSERTION error that occurs when multiple vitest.config
files
are processed in parallel on Node 24+.

Fixes #34028
Fixes #33091
2026-01-30 13:33:11 +00:00
Jason Jean d9f2ed0d44 fix(testing): add timeout to runCommandUntil to prevent hanging tests (#34148)
## Current Behavior

The `runCommandUntil` e2e utility function waits indefinitely for the
expected output to appear. If the output never appears (e.g., server
fails to start, different output format, port conflict), the test hangs
forever, causing CI jobs to run for hours before being killed.

## Expected Behavior

The function should timeout after a configurable duration and fail with
a clear error message showing what output was received.

## Related Issue(s)

Fixes hanging e2e tests observed in CI (e.g.,
`e2e-node:e2e-ci--src/node-server.test.ts` hung for 1h 21m).

## Changes

- Added optional `timeout` parameter to `runCommandUntil` opts (default:
5 seconds)
- On timeout: kills the process, logs the collected output, and rejects
with a clear error
- Existing call sites work unchanged; tests needing more startup time
can pass `{ timeout: 30000 }`

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: FrozenPandaz <8104246+FrozenPandaz@users.noreply.github.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-01-30 00:18:26 -05:00
Jack Hsu c331bf949c fix(nx-dev): fix internal link check caching and remaining /launch-nx link (#34255)
## Current Behavior

1. The `check-links` task cache inputs only included `sitemap.xml` (the
index
file) and `sitemap-index.xml`, but not the actual `sitemap-0.xml` files
that
contain the URL data. This meant that when pages were added or removed,
the
cache wasn't properly invalidated - the check-links task would return a
   cached "passing" result even when broken links existed.

2. The `/launch-nx` page was removed in #34183 but one link in
`astro-docs/src/content/docs/reference/Nx Cloud/release-notes.mdoc`
still
pointed to it. This link was masked by being in the `validate-links.ts`
   ignore list.

## Expected Behavior

1. The `check-links` task cache is invalidated when sitemap URLs change
by
   using glob patterns (`sitemap*.xml`) to include all sitemap files.

2. All links point to valid pages. The `/launch-nx` link now redirects
to
   `/blog/launch-nx-week-recap`.

## Changes

- **astro-docs/release-notes.mdoc**: Updated `/launch-nx` link to
`/blog/launch-nx-week-recap`
- **astro-docs/validate-links.ts**: Removed `/launch-nx` from ignore
list (no longer needed)
- **nx-dev/project.json**: Fixed cache inputs to use `sitemap*.xml` glob
patterns

## Related Issue(s)

Fixes DOC-385

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 17:10:47 -05:00
Colum Ferry 3d62c8b5c1 fix(vite): handle sophisticated vite plugins (#34242)
## Current Behavior
With Vite now providing additional options (environments etc) for
framework authors, vite.config files can be much more simple for the
user.
However, this often assumes that the `root` property will be set and
provided during `Vite CLI` invocation.

When we run `resolveConfig` to determine inputs and outputs, we do not
set this `root` and expect the user to have it in their vite config
file.
For some plugins/frameworks such as Tanstack Start - this causes the
plugin to error.

The `isBuildable` conditions is also not inclusive enough and can skip
projects that should be marked as buildable.

## Expected Behavior
Ensure that sophisticated vite plugins are supported with Nx

## Related Issue(s)

CLOSES NXC-3637
2026-01-29 14:28:31 +00:00
Jack Hsu 9c3a9d7e13 feat(core): add Nx Cloud connect URL to template README (#34249)
## Current Behavior
Template-generated workspaces use a generic link in the README instead
of a per-workspace short link for Nx Cloud setup.

## Expected Behavior
When users opt into Nx Cloud (or are auto-connected via variant 2), the
template README is updated with a personalized connect URL section that
helps them finish setting up their workspace.

---
BEFORE: 

<img width="762" height="460" alt="image"
src="https://github.com/user-attachments/assets/58900071-1727-49d1-aa19-279c488b5037"
/>


AFTER: 

<img width="1032" height="599" alt="image"
src="https://github.com/user-attachments/assets/a6e3a122-5807-4ba2-90dd-441e41a3280e"
/>

---

## Related Issue(s)
Closes NXC-3783

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 09:21:20 -05:00
Drew Teachout e189dcc101 fix(core): do not throw error if worker.stdout is not instanceof socket (#34224)
deno worker.stdout is a Readable/Writeable. To provide better deno
support an error should not be thrown

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->
If `worker.stdout` or `worker.stderr` are not instanceof Socket then nx
throws an error. This is problematic in Deno where `stdout` and `stderr`
are Readable/Writable and not Socket.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
`startupPluginWorker` function should work in Deno runtime

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

- https://github.com/denoland/deno/issues/31961
- https://github.com/oven-sh/bun/issues/26505

---------

Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
2026-01-29 07:21:09 -05:00
Craigory Coppola 4f4b9dc048 fix(core): improve plugin worker error messages and lifecycle timeouts (#34251)
## Current Behavior
Plugin workers occasionally fall over during the start up steps. 

## Expected Behavior
Improves some issues with the error handling when loading plugin workers
and adds some more logs to help understand what's went wrong here.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
2026-01-29 01:57:54 -05:00
Jason Jean fdabc14892 chore(repo): update nx to 22.5.0-beta.2 (#34252)
Updating Nx from 22.5.0-beta.1 to 22.5.0-beta.2

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-01-29 00:57:04 -05:00
Louie Weng da3b00f961 chore(core): edit project graph aggregate error message (#34248)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

Remove redundantly placed period and make error message construction
more readable when facing AggregateError

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes NXC-3766

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-01-28 22:19:44 +00:00
Jason Jean 8d5b316fdd chore(repo): update nx to 22.5.0-beta.1 (#34234)
Updating Nx from 22.5.0-beta.0 to 22.5.0-beta.1

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-01-28 16:39:46 -05:00
Jack Hsu b53fd7c7f5 docs(misc): add 1% scroll depth tracking to docs and non-docs pages (#34246)
This PR adds scroll depth event at the 10% level so we can filter out
users who actually engages with the page versus those who maybe land on
homepage just to get to docs.

<img width="2672" height="1527" alt="Screenshot 2026-01-28 at 4 00
54 PM"
src="https://github.com/user-attachments/assets/ad9be5ae-442c-48eb-9c1e-70f0b872563b"
/>


Also fix the scroll tracker for astro-docs.

<img width="2672" height="1527" alt="Screenshot 2026-01-28 at 4 00
54 PM"
src="https://github.com/user-attachments/assets/e4a24ef2-b6aa-4f5a-b10b-972b73385fee"
/>

<img width="2672" height="1527" alt="Screenshot 2026-01-28 at 2 39
09 PM"
src="https://github.com/user-attachments/assets/833c71f1-7a96-4637-bb6e-3b21227053f0"
/>


## Related Issue(s)
Closes CLOUD-4211

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 16:25:12 -05:00
Louie Weng 5a424fa8df fix(gradle): use tooling api compatible flags (#34247)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

--rerun is not tooling api compatible and therefore will break usage of
the batch executor. Replaced the flag with --rerun-tasks.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-01-28 21:23:21 +00:00
Jason Jean cf043b7e20 feat(maven): load Maven classes at runtime for version-agnostic batch execution (#34180)
## Current Behavior

The Maven batch runner bundles Maven 4 classes at compile time, which:
- Creates a large JAR file (~50+ MB)
- Only works with Maven 4
- Has classloader conflicts with Maven's own SLF4J

## Expected Behavior

The batch runner loads Maven classes at runtime from `MAVEN_HOME`,
which:
- Creates a small JAR (~2 MB) with no bundled Maven dependencies
- Works with both Maven 3.x and Maven 4.x
- Avoids classloader conflicts by isolating Maven in its own ClassRealm
- Outputs clean Maven-style logs (`[INFO]`, `[WARNING]`, etc.)

## Implementation

### Architecture

```
batch-runner.jar (NO Maven dependencies)
├── MavenClassRealm         → Loads Maven JARs from MAVEN_HOME at runtime
├── ResidentMavenExecutor   → Maven 4 executor (reflection-based)
├── CachingMaven3Invoker    → Maven 3 executor (reflection-based)
└── nx-maven-adapters/      → Pre-compiled adapter JARs (embedded as resources)
    ├── batch-runner-adapters-maven3.jar
    └── batch-runner-adapter-maven4.jar
```

### Key Changes

1. **Removed compile-time Maven dependencies** from batch-runner module
2. **Created batch-runner-adapters** modules for Maven 3 and Maven 4
specific code
3. **Implemented MavenClassRealm** to load Maven JARs from MAVEN_HOME at
runtime
4. **Implemented reflection-based executors** that load adapter JARs
into ClassRealm
5. **Fixed SLF4J logging** with System.out redirection for clean
Maven-style output
6. **Added shared module** for BuildStateManager, BuildStateApplier, and
BuildStateRecorder

### Benefits

- **Version agnostic**: Same JAR works with Maven 3.x and 4.x
- **Graph caching**: Project dependency graph built once, reused across
tasks
- **Build state persistence**: compile → package → install works
correctly
- **No classloader conflicts**: Maven's classes isolated in their own
ClassRealm
- **Clean output**: Standard Maven log format without SLF4J noise

## Related Issue(s)

N/A - Internal refactoring for better Maven version support

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-01-28 14:13:31 -05:00
Jack Hsu 7276e4cff7 docs(misc): content negotiation for LLM-friendly docs access (#34239)
## Current Behavior
LLMs and CLI tools must explicitly request the `.md` URL suffix to get
raw markdown content from documentation pages.

## Expected Behavior
When a client requests a docs page with `Accept: text/markdown` header,
the edge function rewrites to serve the `.md` version directly (no
redirect). This enables LLM tools to get markdown content by requesting
the standard URL.

Behavior:
- `Accept: text/markdown` → serves .md content (via rewrite, no
redirect)
- Default (browsers) → serves HTML with Link headers (unchanged)

Examples:
```
curl -H 'Accept: text/markdown' https://deploy-preview-34239--nx-docs.netlify.app/docs/getting-started/intro
curl -H 'Accept: text/markdown' https://deploy-preview-34239--nx-docs.netlify.app/docs/getting-started/tutorials/angular-monorepo-tutorial
```

Uses Netlify Edge Function rewrite (returns URL object) instead of
redirect for single-request response that works with all HTTP clients.

## Related Issue(s)
Closes DOC-389

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 09:06:10 -05:00
Caleb Ukle f9258c9d82 fix(nx-dev): update dead links across nx-dev UI libraries (#34238)
## Current Behavior

broken links

## Expected Behavior

links aren't broken any more and are updated to expected pages from docs
page.

## Related Issue(s)

Fixes DOC-391
2026-01-27 15:51:55 -06:00
Jack Hsu 8b245f1b0a feat(nx-dev): add llms-full.txt and HTTP Link headers for LLM discovery (#34232)
This PR adds:
1. `llms-full.txt` that is a full copy of our docs in markdown.
2. HTTP `Link` headers to our docs HTML pages so that they point to the
`.md` (markdown) version, and also to `llms.txt` and `llms-full.txt`.

The `llms-full.txt` is currently at 2.7 MB, which is much less than
other sites that are up to 5MB or more.

<img width="569" height="35" alt="Screenshot 2026-01-27 at 12 11 10 PM"
src="https://github.com/user-attachments/assets/61be02b4-2813-4c39-951c-d831af83e823"
/>

First 100 lines of `llms-full.txt`:

````
# Nx Documentation

> Complete Nx documentation compiled into a single file for LLM consumption.

Nx is a powerful, open source, technology-agnostic build platform designed to efficiently manage codebases of any scale. From small single projects to large enterprise monorepos, Nx provides intelligent task execution, caching, and CI optimization.

This file was generated from 503 documentation pages.
Individual pages are available at: https://nx.dev/docs/{slug}.md


# Quickstart

---
<!-- source: https://nx.dev/docs/quickstart.md -->
## Quickstart with Nx

Get up and running with Nx in just a few minutes by following these simple steps.

{% steps %}

1. Install the Nx CLI

   Installing Nx globally is **optional** - you can use `npx` to run Nx commands without installing it globally, especially if you're working with Node.js projects.

   {% tabs syncKey="install-method" %}
   {% tabitem label="npm" %}

   ```shell
   npm add --global nx
   ```

   **Note:** You can also use Yarn, pnpm, or Bun

   {% /tabitem %}
   {% tabitem label="Homebrew (macOS, Linux)" %}

   ```shell
   brew install nx
   ```

   {% /tabitem %}
   {% tabitem label="Chocolatey (Windows)" %}

   ```shell
   choco install nx
   ```

   {% /tabitem %}
   {% tabitem label="apt (Ubuntu)" %}

   ```shell
   sudo add-apt-repository ppa:nrwl/nx
   sudo apt update
   sudo apt install nx
   ```

   {% /tabitem %}
   {% /tabs %}

2. Start fresh or add to existing project

   For JavaScript-based projects you can **start with a new workspace** using the following command:

   ```shell
   npx create-nx-workspace@latest
   ```

   **Add to an existing project: (recommended also for non-JS projects)**

   ```shell
   npx nx@latest init
   ```

   **Get the complete experience:**
   For a fully integrated development workflow with AI-powered CI features, [start directly from Nx Cloud](https://cloud.nx.app/get-started).

   Learn more: [Start New Project](/docs/getting-started/start-new-project) • [Add to Existing](/docs/getting-started/start-with-existing-project) • [Complete Nx Experience](https://cloud.nx.app/get-started)

3. Run Your First Commands

   Nx provides powerful task execution with built-in caching. Here are some essential commands:

   **Run a task for a single project:**

   ```shell
   nx build my-app
   nx test my-lib
   ```

   **Run tasks for multiple projects:**

   ```shell
   nx run-many -t build test lint
   ```

   Learn more: [Run Tasks](/docs/features/run-tasks) • [Cache Task Results](/docs/features/cache-task-results)

4. What's next?

   Now that you've experienced the Nx basics, choose how you want to continue:

````


## Related Issue(s)
Closes DOC-236

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-01-27 16:01:48 -05:00
Benjamin Cabanes a092ed06c4 docs(nx-dev): migrate color variants to contrast design (#34077)
This design refresh emphasizes the contrast variant aesthetic across all
hero sections, pricing cards, and primary call-to-actions.

- Change 47 instances of variant="primary" to variant="contrast"
- Update ui-courses to use variant="secondary" for GitHub link
- Prefer high-contrast inverted style for primary CTAs
- Maintain proper visual hierarchy with secondary actions
- Replace all slate-* classes with zinc-* equivalents (1,158 instances)
- Replace all sky-* classes with blue-* equivalents (210 instances)
- Update opacity variants, gradients, rings, and borders
- Maintain full dark mode compatibility
2026-01-27 20:54:33 +00:00
MaxKless de67da4bc6 chore(repo): add update ai agents configuration for a bunch of ai agents (#34231)
this sets the nx repo up with the latest and greatest
2026-01-27 13:50:42 -05:00
MaxKless f89ccb091f fix(core): hide already-installed nx packages from suggestion list during nx import (#34227)
## Current Behavior
If something is in `package.json#dependencies`, we still suggest it to
be `nx add`-ed during `nx import`

## Expected Behavior
If a plugin is already installed, we don't suggest it anymore
2026-01-27 13:23:42 -05:00
Colum Ferry 2bd8ef3f72 feat(js): bump swc to latest versions (#34215)
## Current Behavior
SWC versions are a few minors behind.

## Expected Behavior
SWC versions are up to date and are being managed via PNPM Catalogs
2026-01-27 17:14:55 +00:00
MaxKless 0e8893faea feat(core): improve configure-ai-agents to copy nx skills/subagents/plugins (#34176)
## Current Behavior
The `configure-ai-agents` command sets up rules files (CLAUDE.md,
AGENTS.md, GEMINI.md) and MCP configurations for AI coding agents, but
doesn't provide extensibility artifacts like commands, skills, or
subagents.
## Expected Behavior
The command now:
- **Adds OpenCode** as a new supported agent with project-level MCP
config
- **Configures Claude plugin** via marketplace settings
(`.claude/settings.json` with `extraKnownMarketplaces`)
- **Copies extensibility artifacts** (commands, skills, subagents) from
`nrwl/nx-ai-agents-config` repo for non-Claude agents
- **Caches the config repo** in
`/tmp/nx-ai-agents-config/<commit-hash>/` with automatic cleanup of old
versions
### Agent Distribution Matrix
| Agent | Rules | MCP Config | Commands | Skills | Subagents | Plugin |
|-------|-------|------------|----------|--------|-----------|--------|
| Claude | CLAUDE.md | .mcp.json | - | - | - | ✓ (marketplace) |
| OpenCode | AGENTS.md | opencode.json | ✓ | ✓ | ✓ | - |
| Copilot | AGENTS.md | Nx Console | ✓ | ✓ | ✓ | - |
| Cursor | AGENTS.md | Nx Console | ✓ | ✓ | - | - |
| Gemini | GEMINI.md | .gemini/settings.json | ✓ | ✓ | - | - |

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: MaxKless <MaxKless@users.noreply.github.com>
Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
2026-01-27 16:10:28 +00:00
Jason Jean f9ab939c74 chore(repo): update nx to 22.5.0-beta.0 (#34209)
Updating Nx from 22.4.0-beta.5 to 22.5.0-beta.0
2026-01-27 09:52:22 -05:00
Colum Ferry 7a446e4979 fix(web): ensure vitest config file is created (#34216)
`@nx/web:app` generator is incorrectly calling `createOrEditViteConfig`
when bundler != vite and unitTestRunner = vitest.

Ensure it is using the correct file
2026-01-27 09:41:21 +00:00
Jack Hsu 0f33fa6c40 feat(core): add variant 2 to CNW cloud prompts with promo message (#34223)
This PR uses three variants for CNW for prompting for Cloud/platform
connection.

- Variant 0: Shows "Try the full Nx platform?" prompt → platform-setup
completion
- Variant 1: Shows "Would you like remote caching..." prompt →
cache-setup completion
- Variant 2: No prompt → platform-promo completion with "Want faster
builds?"

Both template and custom flows use the same messages and prompts.


### Skip (all) -- No changes


<img width="1392" height="994" alt="cnw_all_skip_completion"
src="https://github.com/user-attachments/assets/1579df01-fc36-4324-bc74-19efc18f78af"
/>

### Variant 0 (full platform)

Template prompt:

<img width="1392" height="994" alt="cnw_template_variant_0_prompt"
src="https://github.com/user-attachments/assets/d02e9ddc-fd25-4f6f-ac61-6f6d518fe338"
/>

Template completion:

<img width="1392" height="994" alt="cnw_template_variant_0_completion"
src="https://github.com/user-attachments/assets/12e459a0-962c-4724-82fa-a4f5a3b6fbd8"
/>

Custom prompt:

<img width="1392" height="994" alt="cnw_custom_variant_0_prompt"
src="https://github.com/user-attachments/assets/8143832a-a85e-43b4-81eb-15074c223476"
/>

Custom completion:

<img width="1392" height="994" alt="cnw_custom_variant_0_completion"
src="https://github.com/user-attachments/assets/036ef6f9-4977-459a-bbd2-502670a12d01"
/>

### Variant 1 (remote cache)

Template prompt:

<img width="1392" height="994" alt="cnw_template_variant_1_prompt"
src="https://github.com/user-attachments/assets/f69b540f-7e48-46ae-99e5-f53657176424"
/>

Template completion:

<img width="1392" height="994" alt="cnw_template_variant_1_completion"
src="https://github.com/user-attachments/assets/04c17847-b0f2-4e37-b7d2-5556aa14f510"
/>

Custom prompt:

<img width="1392" height="994" alt="cnw_custom_variant_1_prompt"
src="https://github.com/user-attachments/assets/abdb38b2-611e-458b-85c0-7b89cd1b827e"
/>


Custom completion:

<img width="1348" height="950" alt="cnw_custom_variant_1_completion"
src="https://github.com/user-attachments/assets/e7d35ddb-e8ac-4ad1-889e-9fff89538900"
/>

## Variant 2 (no prompt)

Template completion:

<img width="1392" height="994" alt="cnw_template_variant_2_completion"
src="https://github.com/user-attachments/assets/07050989-a56c-46eb-a0c3-8730916856ff"
/>

Custom completion:

<img width="1392" height="994" alt="cnw_custom_variant_2_completion"
src="https://github.com/user-attachments/assets/94000828-37b2-4059-a13b-0d36972cfd3e"
/>

## Related Issue(s)

Closes CLOUD-4189

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 16:30:05 -05:00
Jack Hsu e108dac1bb Revert "Revert "feat(core): add A/B testing variant 1 to skip cloud p…rompt in CNW (#34106)" (#34191) (#34204)
This reverts commit f016664557.
2026-01-26 14:58:26 -05:00
Colum Ferry 3fcd2008ef fix(react): remove file-loader dependency and update svgr migration (#34218)
## Current Behavior
The migration for svgr requires using file-loader which is unmaintained.

## Expected Behavior
Use asset/resource instead of file-loader

## Related Issue(s)

CLOSES NXC-3667
2026-01-26 16:56:18 +00:00
Jason Jean 75f36edb8f fix(core): fall back to node_modules when tmp has noexec (#34207)
## Summary

- When `/tmp` is mounted with `noexec`, loading native modules from the
cache fails silently and causes Nx to hang indefinitely
- This adds a fallback to load from `node_modules` when permission
errors occur

## Problem

Users with `/tmp` mounted with `noexec` (a common security hardening
practice) experience Nx hanging forever, even for simple commands like
`nx --version`.

The root cause:
1. Nx copies native `.node` files to `/tmp` to avoid Windows file
locking issues
2. On `noexec` mounts, execution fails with `EACCES`/`EPERM`
3. The error wasn't caught, leading to broken native bindings and
infinite loops

## Solution

Catch permission errors when loading from the cache and fall back to the
original `node_modules` location. This:
- Works automatically without user config
- Preserves Windows file locking fix (only falls back when needed)
- No error messages for users

Closes #33991
2026-01-23 17:32:15 -05:00
Miguel 3672e1a3ea fix(devkit): allow null values in JSON schema validation (#34167)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

Schema validation (done, for instance, when calling an executor) fails
when an option has value "null" and schema accepts null values. I had it
in a custom executor for `nx-release-publish`, that understands that
`nxReleaseVersionData` is implicitly passed, so I define its schema:
```json
"newVersion": {
  "type": ["string", "null"],
  "description": "The new version of the project, null if no changes detected"
}
```

My code calls `getReleaseClient().releaseVersion(options)`, which gets
me a `projectsVersionData` object with version info. It contains `null`
values (allowed). I then pass it, and ends up in:
```typescript
 // nx/src/tasks-runner/task-orchestrator.ts:531-539                                                                                                                                                                      
  const combinedOptions = combineOptionsForExecutor(                                                                                                                                                                       
      task.overrides,  // ← Contains nxReleaseVersionData with null values                                                                                                                                                 
      task.target.configuration,                                                                                                                                                                                           
      targetConfiguration,                                                                                                                                                                                                 
      schema,           // ← Schema from executor                                                                                                                                                                          
      task.target.project,                                                                                                                                                                                                 
      relativeCwd,                                                                                                                                                                                                         
      isVerbose                                                                                                                                                                                                            
  );
```

which fails inside:
```typescript
 // nx/src/utils/params.js:126-201                                                                                                                                                                                        
  function validateObject(opts, schema, definitions) {                                                                                                                                                                     
      // Line 191-200: Iterate through all properties                                                                                                                                                                      
      Object.keys(opts).forEach((p) => {                                                                                                                                                                                   
          validateProperty(                                                                                                                                                                                                
              p,                              // "nxReleaseVersionData"                                                                                                                                                    
              opts[p],                        // { foo: { newVersion: null, ... }}                                                                                                                                         
              (schema.properties ?? {})[p],   // schema for nxReleaseVersionData                                                                                                                                           
              definitions                                                                                                                                                                                                  
          );                                                                                                                                                                                                               
      });                                                                                                                                                                                                                  
  }
  ```

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR -->

`null` values should be considered, as they are valid in JSON schemas. It was probably not considered, because we never think that `typeof null === "object"`, but it's unfortunately the case.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is merged. -->

I will create one

Fixes https://github.com/nrwl/nx/issues/34169
2026-01-23 15:09:09 -05:00
Jack Hsu bc9b3229c6 feat(js): add NX_PREFER_NODE_STRIP_TYPES to use Node's strip types feature instead of transpilation for TypeScript files (#34202)
Transpiling through SWC or ts-node is slow compared to the native type
stripping that Node.js provides.

This PR adds `NX_PREFER_NODE_STRIP_TYPES` to allow users to use Node.js
built-in TypeScript support. There are some features that need
transpilation that won't work with type stripping:

- Enum declarations
- namespace with runtime code
- legacy module with runtime code
- parameter properties
- path aliases

See: https://nodejs.org/api/typescript.html#full-typescript-support

The speed-up is significant. My test workspace went from 22s to ~2s to
compute from cold cache.

Demo: https://www.loom.com/share/ce1db29e501b46d58109ffeec8a7a649

In the future we should enable this by default, and users have to turn
it off to use SWC/ts-node.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: jaysoo <jaysoo@users.noreply.github.com>
2026-01-23 14:13:00 -05:00
Leosvel Pérez Espinosa a15881db1a feat(core): display batch tasks in the tui (#33695)
Adds support for Batch tasks and displays them in the TUI.

---------

Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
2026-01-23 13:48:04 -05:00
Jack Hsu 1dd4262336 docs(misc): collect usage data on .md and .txt files (#34203)
Right now GA only collects page views. This PR allow us to see which
`.md` files are being used. There are mostly useful for AI agents to
fetch without using too many tokens. We want to track usage so we can
see what techniques to guide agents actually work.

## Related Issue(s)
Closes DOC-386

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 12:38:17 -05:00
Jack Hsu 687357c82e fix(core): cloud commands are noop when not connected rather than errors (#34193)
This PR makes it so Cloud commands like `npx nx record` and `npx nx
fix-ci` still work without `nxCloudId`. We'll log a warning so that
`ci.yml` using these commands will still work. The warning let's users
know that these do not work without being connected.

Closes #NXC-3753
2026-01-23 12:29:41 -05:00
Mark Lindsey 3d1e544812 chore(repo): commit lint mention that commits should be lowercase (#34199)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
Commit linting does not mention requirement for commit message to be
lowercase.
<!-- This is the behavior we have today -->

## Expected Behavior
Hook message should instruct user to use all lowercase for commit
message.
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-01-23 11:19:42 -05:00
Jack Hsu 9bb69383b8 fix(core): consolidate GitHub URL messaging when gh push fails (#34196)
When `gh repo create` fails, users see two redundant messages. This
consolidates them into a single message with the helpful `?name=...`
parameter in the GitHub URL.

BEFORE: (We show `Could not push. Push repo to complete setup.` and then
`Push your repo (https://github.com/new)...` again at the end)

<img width="1209" height="560" alt="image"
src="https://github.com/user-attachments/assets/e22c012c-8e0f-4ff9-a6c2-de8267b69b5d"
/>

AFTER: (Only show `Could not push` as an info log, and then complete
setup is shown only once at the end)

<img width="1250" height="591" alt="image"
src="https://github.com/user-attachments/assets/3dc075c7-4418-4373-a07a-1b95f71b3b87"
/>

Closes NXC-3754

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 11:18:23 -05:00
Jonathan Cammisuli 1831ace87e docs(nx-dev): update docs to include SELF_HEALING.md information (#34200) 2026-01-23 15:12:03 +00:00
Mark Lindsey 478afc7046 docs(nx-dev): add bitbucket to self healing docs (#34198)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
Self-healing docs only reference being supported for GitHub, Azure, and
GitLab.
<!-- This is the behavior we have today -->

## Expected Behavior
We should show instructions for all currently supported vcs providers,
including Bitbucket.
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-01-23 14:48:04 +00:00
JamesHenry 1299d044eb chore(repo): remove --auto-apply-fixes, it is set in Nx Cloud UI 2026-01-23 15:41:24 +04:00
Craigory Coppola 273a474047 fix(core): handle resizing a bit better for inline_tui (#34006)
## Current Behavior
Resizing the TUI while in inline view kinda breaks things. Its
unfortunate, I'm not sure there's a ton to be done, but this PR explores
some solutions

## Expected Behavior
The TUI is less sensitive to resize events with inline mode

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-01-22 18:54:56 -05:00
Jason Jean f016664557 Revert "feat(core): add A/B testing variant 1 to skip cloud prompt in CNW (#34106)" (#34191)
## Current Behavior

The create-nx-workspace (CNW) command includes A/B testing variant 1
which skips the cloud prompt under certain conditions.

## Expected Behavior

Revert to the previous behavior where the cloud prompt flow is
consistent without the A/B testing variant.

## Related Issue(s)

This reverts commit 2039a5e119 from PR
#34106.
2026-01-22 16:14:21 -05:00
Jason Jean 81bb7d3d17 fix(nx-dev): update broken /launch-nx links (#34192)
## Current Behavior

The internal link checker reports 3 broken links pointing to
`/launch-nx`:
- `/blog/2024-02-05-nx-18-project-crystal.md`
- `/blog/2024-02-15-launch-week-recap.md`
- `/changelog/18_0_0.md`

The `/launch-nx` page was a temporary page for the Nx 18 launch event in
February 2024 and no longer exists.

## Expected Behavior

All internal links should point to valid pages. Links to the old launch
page now redirect to the Launch Nx Week recap blog post.

## Related Issue(s)

Fixes the internal link checker errors.
2026-01-22 21:14:01 +00:00
Craigory Coppola 587659cb03 fix(core): move tui to parking lot rwlock to avoid hang (#34187)
## Current Behavior
There's a hard to reproduce hang that happens occasionally when running
the TUI

## Expected Behavior
We think this should fix it

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-01-22 15:49:58 -05:00
Benjamin Cabanes 05de3ff450 docs(nx-dev): remove Nx Conf and Advent of Code pages (#34183)
Removed Nx Conf and Advent of Code pages, associated UI components, and
references from the configuration files. Simplified package structure by
removing `@nx/nx-dev-ui-conference` package.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: bcabanes <bcabanes@users.noreply.github.com>
2026-01-22 12:10:12 -05:00
Jack Hsu 8fee466a1f chore(misc): remove banner.json files and add to gitignore (#34185)
The `banner.json` was committed as a fallback if we're not using Framer
to control the banner yet on nx.dev. Now that it is verified we can
remove the committed file.

Closes DOC-381

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 11:26:10 -05:00
MaxKless d6572f3de4 fix(core): clean up daemon workspace data directory on nx reset --onl… (#34174)
Previously, `nx reset --onlyDaemon` would only stop the daemon process
but not clean up the daemon files in `.nx/workspace-data/d`. This change
ensures the daemon workspace data directory is also removed when using
the `--onlyDaemon` flag, consistent with the behavior of a full reset.

Co-authored-by: MaxKless <MaxKless@users.noreply.github.com>
2026-01-22 16:29:08 +09:00
Jack Hsu 367986dea1 docs(core): update version on releases reference page (#34178)
This PR updates the releases page so v22 is included.

Closes #DOC-382

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-01-21 15:58:00 -05:00
Caleb Ukle 6ae0fd1217 docs(nx-dev): make sure cli nested sub commands are parsed (#34179)
commands like nx release and nx show were missing sub commands. make
sure they are now correctly parsed.

<img width="224" height="162" alt="image"
src="https://github.com/user-attachments/assets/f51c12f2-9926-44a1-a3c0-57de565434bd"
/>


also, fixed the "getting help" for plugin docs being rendered
incorrectly. and add double dash `--` to the plugin option docs to match
rest of docs.


<img width="874" height="634" alt="image"
src="https://github.com/user-attachments/assets/be7a58a3-0ea0-41d5-8ba6-98cfa0210a95"
/>
2026-01-21 20:26:30 +00:00
Jason Jean 1b12e1fc6f fix(core): improve TUI task selection and pane focus behavior (#34175)
## Current Behavior

1. When running a task with dependencies (e.g., `nx serve app` where app
depends on app2:serve), the initiating task might not be selected on
startup. Additionally, the auto-select logic could switch selection to
the initiating task at any time when it started running - even minutes
later - which felt "random" to the user.

2. When pressing Enter on an already-pinned task, it would unpin the
task, causing the pane to disappear while focus remained on it
(invisible-but-focused state).

## Expected Behavior

1. The initiating task (the one the user actually requested) should be
selected during init, and selection should never unexpectedly change
later when tasks start.

2. Pressing Enter on an already-pinned task should focus the pane, not
unpin it.

## Changes

- **Select initiating task during init**: Moved initiating task
selection to `init()` in app.rs. This only applies in `RunOne` mode
since in `RunMany` there's no single initiating task to prioritize.
Removed the "switch to initiating task" logic from `start_tasks()` in
tasks_list.rs.
- **Focus pane on Enter**: Changed behavior so pressing Enter on an
already-pinned task focuses the pane instead of unpinning it.

## Related Issue(s)

N/A - discovered during TUI testing
2026-01-21 14:02:43 -05:00
Jason Jean fc8072930a chore(repo): update nx to 22.4.0-beta.5 (#34162)
Updating Nx from 22.4.0-beta.4 to 22.4.0-beta.5
2026-01-21 13:04:47 -05:00
Juri b6ed6cbca8 docs(nx-dev): blog post about vertical and horizontal continuity with agents 2026-01-21 17:00:54 +01:00
Tomas Ptacek 162fca17c4 fix(module-federation): dev server handler accumulation (#34152)
# Current Behavior
The `beforeCompile` hook is registered inside the `watchRun` hook,
causing a new handler to be added on every recompilation. This leads to
handler accumulation, where setup operations (building static remotes,
starting file server, starting proxies) are triggered multiple times
during watch mode.

# Expected Behavior
Hooks should be registered once, outside of other hooks, to prevent
accumulation. Setup operations should only run once, not on every
recompilation.

# Related Issue(s)
Fixes https://github.com/nrwl/nx/issues/34141
2026-01-21 15:09:39 +00:00
James Henry e57848cea7 chore(repo): update self-healing ci docs (#34126)
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: JamesHenry <JamesHenry@users.noreply.github.com>
2026-01-21 08:17:16 -05:00
Tomas Ptacek 5f51a4b268 fix(angular-rspack): stats serialization and configuration (#34155)
# Current Behavior
1. **Performance bottleneck**: `statsValue.toJson()` is called with no
options, causing full serialization of all stats data on every build.
This is expensive and unnecessary when only budget checking is needed.
2. **Redundant work**: Budget checking code runs even when no budgets
are configured or when targeting server platform.
3. **User stats config ignored**: Custom stats configuration provided
via `rspackConfigOverrides` is not respected by the stats logger.
4. **Double serialization**: `rspackStatsLogger` calls `stats.toJson()`
without passing the stats options, ignoring user preferences.

# Expected Behavior
1. Only serialize what's needed for budget checking (`assets` and
`chunks`), significantly reducing overhead.
2. Early exit when budgets are not configured or on server platform,
skipping expensive `toJson()` entirely.
3. User's stats configuration is merged with defaults and respected
throughout the build output.
4. `rspackStatsLogger` uses the provided `statOptions` when serializing
stats.

# Related Issue(s)
Fixes https://github.com/nrwl/nx/issues/34145
2026-01-21 09:31:55 +00:00
Jack Hsu 2039a5e119 feat(core): add A/B testing variant 1 to skip cloud prompt in CNW (#34106)
## Current Behavior
CNW always shows the "Try the full Nx platform?" prompt and connects to
Nx Cloud to generate an onboarding URL with a token.

## Expected Behavior
For A/B testing variant 1:
- Skip cloud prompt
- Skip connectToNxCloudForTemplate() - no nxCloudId in nx.json
- Skip readNxCloudToken() - no misleading spinner
- Use GitHub flow for URL generation (accessToken: null)
- Show github.com/new hint when user hasn't pushed

Also fixes:
- Expired cache file bug: now deletes with unlinkSync() instead of
ignoring, which caused 50-50 randomization after 1-week expiry
- Adds variant-X to short URL meta property for cloud analytics

## Related Issue(s)
Closes NXC-3628

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 16:44:00 -05:00
Jason Jean 411e2e2453 chore(repo): update nx to 22.4.0-beta.4 (#34138)
Updating Nx from 22.4.0-beta.3 to 22.4.0-beta.4
2026-01-20 16:41:29 -05:00
Craigory Coppola f4bb7d734f chore(repo): remove .deb file from accidental commit and update gitignore (#34161)
removes accidentally committed file
2026-01-20 16:37:48 -05:00
Jason Jean e586896470 fix(core): prioritize nx installation path in getNxRequirePaths (#34158)
## Current Behavior

`getNxRequirePaths` returns paths in the order `[root,
getNxInstallationPath(root)]`, which means the workspace root is checked
first when resolving modules.

## Expected Behavior

The nx installation path (`.nx/installation`) should be prioritized and
checked first before falling back to the workspace root. This ensures
that modules from the nx installation directory take precedence.

## Related Issue(s)

N/A
2026-01-20 18:54:08 +00:00
Tomas Ptacek d2335186ac feat(angular-rspack): add tailwind and postcss config to component stylesheet bundler (#34153)
# Current Behavior
The Angular Rspack compiler's `ComponentStylesheetBundler` does not
receive Tailwind or PostCSS configuration. This means Tailwind
directives (like `@apply`, `@tailwind`) in component stylesheets are not
processed, resulting in broken styles.

# Expected Behavior
Component stylesheets should support Tailwind CSS and PostCSS
configurations, matching the behavior of the standard Angular CLI build
process.

# Related Issue(s)
https://github.com/nrwl/nx/issues/34098
2026-01-20 15:15:39 +00:00
Tomas Ptacek 2d7e24ce68 fix(angular-rspack): handler accumulation and watchOptions for double rebuilds (#34154)
# Current Behavior
1. **Handler accumulation**: The `compilation`, `beforeCompile`, and
`done` hooks are registered inside `watchRun`, causing new handlers to
be added on every rebuild cycle. This leads to performance degradation
and duplicate operations during watch mode.
2. **Double rebuilds**: Rapid filesystem events (e.g., editor
backup/swap files) trigger multiple rebuilds because there's no
aggregation timeout configured.
3. **No watchOptions configuration**: Users cannot customize watcher
behavior (aggregateTimeout, ignored patterns, etc.).

# Expected Behavior
1. Hooks should be registered once outside of `watchRun` to prevent
accumulation. Shared state is used to pass data between watch cycles and
compilation hooks.
2. A default `aggregateTimeout: 50` batches rapid filesystem events to
prevent double rebuilds.
3. Users can provide custom `watchOptions` to configure watcher
behavior, with user options taking precedence over defaults.

# Related Issue(s)
https://github.com/nrwl/nx/issues/34142#issuecomment-3767571208
2026-01-20 14:22:07 +00:00
Leosvel Pérez Espinosa 6bb82c0c2e fix(core): establish cpu baseline when possible to improve measurement accuracy (#34120)
## Current Behavior

New task processes show 0% CPU on their first measurement because no
baseline exists. Accurate readings only appear on the second collection
cycle (~1s later).

## Expected Behavior

New task processes get accurate CPU readings on their first measurement.
The collector establishes CPU baselines for newly registered processes
~250ms before collection, giving `sysinfo` enough time to calculate
accurate CPU deltas.

## Technical Details: Baselining & Collection Flow

The collection loop runs in 4 phases:

```
 T=0ms      T=750ms      T=1000ms    T=1750ms     T=2000ms  
   |           |             |           |            |
   v           v             v           v            v
Collect → Sleep(750ms) → Baseline → Sleep(250ms) → Collect → ...
```

1. **Collect**: Refresh all processes and gather metrics
2. **Post-collection sleep**: Wait until baseline time (interval -
250ms)
3. **Baseline**: Bulk CPU refresh for newly registered PIDs (if any)
4. **Pre-collection sleep**: Wait 250ms for accurate CPU delta
calculation
2026-01-19 11:53:07 -05:00
Craigory Coppola 0137ea2dc9 fix(core): avoid panic when inline tui can't init (#34135)
## Current Behavior
if inline tui init fails, we panic

## Expected Behavior
If inline tui init fails, inline mode is disabled. We show the reason
its disabled when someone tries to use it.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2026-01-17 23:38:00 -05:00
Jason Jean 6754cde03a fix(core): drain stdin on exit to prevent escape sequence leakage (#34134)
## Current Behavior

When exiting the TUI (especially via Ctrl+C), escape sequences leak to
the terminal:
```
^[]11;rgb:2121/2121/2121^[\^[[55;1R^[[?62;22;52c
```

This happens because the TUI queries terminal background color via OSC
11 to detect dark/light mode. The terminal responds with an escape
sequence, but if the program exits before fully consuming the response,
it appears in the terminal output.

## Expected Behavior

Clean terminal state after TUI exits, with no escape sequence artifacts.

## Related Issue(s)

N/A - discovered during development

## Solution

Added `drain_stdin()` function that polls and consumes any pending
terminal events before disabling raw mode. This clears any lingering OSC
responses (like the background color query response) before the terminal
is restored.

```rust
fn drain_stdin() {
    use std::time::Duration;
    while crossterm::event::poll(Duration::from_millis(5)).unwrap_or(false) {
        let _ = crossterm::event::read();
    }
}
```

The 5ms timeout is long enough to catch pending responses but short
enough not to noticeably delay exit.
2026-01-17 16:30:38 +00:00
Jason Jean 4202f2c760 fix(core): prevent task hashing when project graph has errors (#34116)
## Current Behavior

When the daemon encounters a project graph error during task hashing, it
extracts the partial project graph from the error and continues hashing
tasks. This can produce incorrect hashes since the graph is incomplete.

## Expected Behavior

The error should be thrown immediately, preventing any hashing attempts
with an invalid project graph. This ensures we don't produce incorrect
task hashes that could lead to cache issues.

## Related Issue(s)

N/A - Bug fix discovered during development
2026-01-16 18:30:35 -05:00
Philip Fulcher 323554b335 docs(nx-dev): fix metric number in header image for article (#34131) 2026-01-16 19:54:46 +00:00
Philip Fulcher 643a6be8be docs(nx-dev): add caseware success story article (#34127) 2026-01-16 14:31:26 -05:00
Colum Ferry 6092966ce4 feat(core): add PLUGIN.md files to testing-tools (#34125)
Add PLUGIN.md files to test related plugins

Closes NXA-789
2026-01-16 18:40:10 +00:00
Jason Jean 25442271ab chore(repo): update nx to 22.4.0-beta.3 (#34108)
Updating Nx from 22.4.0-beta.1 to 22.4.0-beta.3

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-01-16 09:28:53 -05:00
Colum Ferry e343bd27d8 feat(bundling): replace rollup-plugin-postcss with inlined version (#34110)
## Current Behavior
The `rollup-plugin-postcss` has not released a new version in 4 years.
The deps it depends on are outdated and starting to cause problems with
peer-dep conflicts.

## Expected Behavior
Recreate the plugin within the `@nx/rollup` package to maintain the
functionality/behaviour and manage dependencies ourselves.

## Related Issue(s)

Closes NXC-3644

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: Coly010 <Coly010@users.noreply.github.com>
2026-01-16 13:14:42 +00:00
Leosvel Pérez Espinosa 07a68baab9 fix(vitest): prevent config double-merge causing array duplication (#34113)
## Current Behavior

Running `nx test` with Vitest browser mode fails with error:

> "The browser configuration must have a 'name' property"

Array configs like `browser.instances` and `reporters` get duplicated,
breaking tests.

## Expected Behavior

Vitest browser mode and array configurations work correctly without
duplication.

## Related Issue(s)

Fixes #33591
2026-01-16 09:51:03 +00:00
Leosvel Pérez Espinosa 7e12359221 feat(angular): add support for angular v21.1 (#34057)
## Current Behavior

Angular v21.1 is not supported.

## Expected Behavior

Angular v21.1 should be supported.
2026-01-15 15:05:50 -05:00
Nicolas Beaussart 21d1555d78 feat(core): add OpenCode AI agent detection (#34072)
## Current Behavior

Nx's AI agent detection currently identifies Claude Code, Repl.it, and
Cursor AI agents via environment variables, but does not detect
OpenCode.

## Expected Behavior

Nx should also detect when running under OpenCode AI agent by checking
for the `OPENCODE` environment variable, which OpenCode sets to `1` when
active.

## Related Issue(s)

N/A - Feature addition to improve AI agent detection coverage.

## Changes

- Added `is_opencode_ai()` function in
`packages/nx/src/native/utils/ai.rs`
- Updated `is_ai_agent()` to include OpenCode detection
- Added corresponding unit tests
2026-01-15 11:28:23 -05:00
MaxKless 1b12b392b7 fix(core): only run nx console background check if daemon is active (#33917) 2026-01-16 00:15:26 +09:00
MaxKless fe0757c981 chore(repo): update agents.md and claude.md (#34112) 2026-01-16 00:14:41 +09:00
Steven Nance 88098cc5f7 fix(core): ensure consistent yarn optional dependency hashing (#34104)
## Current Behavior

For optional packages that are not installed when using yarn, we
currently add the package version to the key for the hash. NPM and PNPM
do not do this.

The results in inconsistent hashes for package dependencies when running
in different environments. For example, trying to use the cache created
in CI on linux on a mac where native dependencies are used.

**yarn on arm mac**

_note how the key has the version in it for the linux and x64 versions
that are not installed_
```
$ nx test foo | grep @nx/nx-
...
        "npm:@nx/nx-darwin-x64@22.3.3": "14042642002999097748",
        "npm:@nx/nx-linux-x64-gnu@22.3.3": "12169496858981304476",
        "npm:@nx/nx-darwin-arm64": "1683411334940043113", 
```

**npm on arm mac**

```
$ nx test foo | grep @nx/nx-
...
        "npm:@nx/nx-darwin-x64": "14042642002999097748",
        "npm:@nx/nx-darwin-arm64": "1683411334940043113",
"9980946580833020728",
        "npm:@nx/nx-linux-x64-gnu": "12169496858981304476",
```


**pnpm on arm mac**
```
$ nx test foo | grep @nx/nx-
...
        "npm:@nx/nx-darwin-arm64": "1683411334940043113",
        "npm:@nx/nx-darwin-x64": "14042642002999097748",
        "npm:@nx/nx-linux-x64-gnu": "12169496858981304476",
```


## Expected Behavior

Optional dependencies should be handled the same way from a hashing
perspective as installed dependencies.

**yarn on arm mac**

```
$ nx test foo | grep @nx/nx-
...
        "npm:@nx/nx-darwin-x64": "14042642002999097748",
        "npm:@nx/nx-linux-x64-gnu": "12169496858981304476",
        "npm:@nx/nx-darwin-arm64": "1683411334940043113", 
```
2026-01-15 10:06:12 +01:00
Philip Fulcher acb4d3cb3d docs(nx-dev): changed pinned posts (#34109) 2026-01-14 19:35:38 -06:00
Miroslav Jonaš 95300a20cc fix(core): improve buildExplicitTypeScriptDependnecies performance (#33963)
On test repo reduces the
`nx/js/dependencies-and-lockfile:createDependencies`:
- from `10506ms`
- to `3665ms`

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-01-14 22:10:33 +01:00
Leosvel Pérez Espinosa 45f2ae303a fix(core): upgrade sysinfo to 0.37.2 and fix cpu measurement accuracy (#34101)
## Current Behavior

CPU metrics collection can report inaccurate values where:

- Individual processes show inflated CPU usage
- Total CPU aggregation across all processes exceeds the system's
maximum available CPU
- This leads to confusing and misleading metrics data  

## Expected Behavior

CPU metrics accurately reflect actual resource usage:

- Process CPU values are accurate
- Total CPU aggregation stays within system limits
- Metrics data is reliable and trustworthy

### Additional Notes

- **Root cause**: When registering a new process, we established a CPU
baseline by refreshing only that single process via `sysinfo`.
Internally, `sysinfo` calculates CPU% as `(process_cpu_time_delta /
wall_time_delta) * 100`. Refreshing a single process updates the wall
time reference but leaves the CPU time baselines of other processes
unchanged. In the next metrics collection, these other processes appear
to have consumed their CPU time over a shorter wall time period (based
on the last baseline), resulting in inflated percentages (e.g., 200%+
for single-threaded processes).
- This PR also improves initialization performance by only loading
necessary system data (processes, CPU, memory) instead of all system
information
- Upgrades `sysinfo` dependency to v0.37.2, which includes upstream CPU
measurement improvements.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-01-14 19:40:22 +01:00
Jack Hsu 4816c5514a feat(nx-dev): add scroll depth tracking for marketing pages (#34105)
## Current Behavior

Marketing pages (homepage, /react, /java, etc.) do not track scroll
depth. Only docs pages have scroll tracking via the ScrollableContent
component.

## Expected Behavior

Marketing pages now track scroll depth and fire scroll_0, scroll_25,
scroll_50, scroll_75, scroll_90 events to Google Analytics, matching the
existing docs page behavior.

## Related Issue(s)
Closes DOC-376

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 12:36:12 -05:00
Colum Ferry 42b1f144da fix(misc): deprecate setup-tailwind generators (#34097)
The setup-tailwind generators are deprecated as generating Tailwind
configuration is no longer maintained. This adds deprecation metadata
to generators.json and runtime warnings when the generators are invoked.

Affected packages: @nx/angular, @nx/react, @nx/next, @nx/remix, @nx/vue

These generators will be removed in Nx 23.

For adding Tailwind support, people can follow the official Tailwind
guides. We also
- updated our
[angular](https://nx.dev/docs/technologies/angular/guides/using-tailwind-css-with-angular-projects)
and
[react](https://nx.dev/docs/technologies/react/guides/using-tailwind-css-in-react)
docs and have a [blog
post](https://nx.dev/blog/setup-tailwind-4-angular-nx-workspace) about
it with more info.

Closes NXC-3714
2026-01-14 16:52:27 +00:00
Craigory Coppola d6b01597e4 fix(core): only init inline view if able to run (#34094)
## Current Behavior
The inline tui runs some terminal escape codes to check cursor position,
these break when stdin isn't a tty (like in a git hook)

## Expected Behavior
The inline tui is disabled if stdin isn't a tty

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-01-14 11:26:13 -05:00
Jason Jean 60e0fcde01 fix(maven): include migrations.json in published package (#34086)
## Current Behavior

The `migrations.json` file in the `@nx/maven` package is not included in
the `files` array in `package.json`. This means when the package is
published to npm, the migrations file is not included, preventing users
from running migrations.

## Expected Behavior

The `migrations.json` file should be included in the published package
so that Nx can discover and run migrations when users upgrade.

## Related Issue(s)

N/A - discovered during development
2026-01-14 10:20:02 -05:00
Leosvel Pérez Espinosa e1bb85254c chore(core): exclude handwritten files from native build outputs (#34099)
Exclude some handwritten files from the native build outputs. When those
files are updated in isolation, the build can replace them with stale
cached outputs. This is because they are not inputs of the native
builds, but are incorrectly stored as outputs of the native builds.
2026-01-14 10:08:36 -05:00
Colum Ferry 85deb8bc66 chore(core): update minimatch to latest version (#34063)
Update Minimatch to v10

Closes NXC-3661
2026-01-14 10:02:52 -05:00
Colum Ferry 574d841837 chore(core): update to latest version of tsquery (#34067)
Update to latest version of TSQuery (v6).

Closes NXC-3660

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
2026-01-14 09:08:40 -05:00
8264 changed files with 267683 additions and 288590 deletions
+3
View File
@@ -1,3 +1,6 @@
[env]
JEMALLOC_SYS_WITH_MALLOC_CONF = "dirty_decay_ms:1000,muzzy_decay_ms:0"
[build]
target-dir = 'dist/target'
-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,301 @@
---
name: diagnose-sandbox-report
description: >
Diagnose Nx sandbox violations from a sandbox report. Use when asked to
"diagnose sandbox", "analyze sandbox report", "investigate sandbox violations",
"check violations", when given a sandbox report JSON file or URL to investigate,
or when the user pastes a staging.nx.app sandbox-report URL. Also trigger when
discussing unexpected reads/writes in Nx task execution. Guides structured
investigation of why tasks read/write undeclared files, determines root causes,
and recommends fixes.
argument-hint: '<sandbox-report.json or URL> [--filter <file|pattern|list>]'
allowed-tools: Bash, Read, Grep, Glob
---
# Diagnose Sandbox Report
## Overview
Sandbox violations occur when an Nx task reads files not declared as inputs or writes files not declared as outputs.
**Unexpected reads** are one of:
1. **Missing input** (most likely) — the process legitimately needs this file. Understand what the process does and why the access makes sense, then declare it as an input.
2. **Potential sandboxing gap** (last resort) — the access is irrelevant to correctness and should be filtered/ignored by the sandbox. Only conclude this after exhausting every possibility for it being a missing input.
**Unexpected writes** follow the same logic:
1. **Missing output** (most likely) — the process legitimately produces this file.
2. **Potential sandboxing gap** (last resort) — same as above.
The default assumption is that an unexpected access IS a missing declaration. The investigation's job is to understand WHY the process accesses the file — not to find reasons it shouldn't.
## Critical Rules
1. **NEVER read the sandbox report JSON directly** — these files are too large for the Read tool (50K+ tokens). Do NOT use `Read`, `cat`, `head`, `python3`, or `jq` on the raw report. All report parsing is handled by the script.
2. **ALWAYS run the context-gathering script as the very first step** — no manual parsing, no ad-hoc python/jq on the report file. The script does everything deterministically.
3. If the script fails, **report the error and stop**. Do not attempt manual parsing as a fallback.
4. **Identify the inferring plugin BEFORE proposing any fix** — check `inference.plugin` in the script output or run `jq '.targets.<target>.metadata' <detail-file>`. Fixing the wrong plugin wastes entire investigation rounds.
5. **Verify hypotheses empirically before committing to them** — see Principle 4 and the Phase 2 instrumentation guidance.
## Workflow
### Phase 0: Input
User provides one of:
- Path to a sandbox report JSON file
- A URL to a sandbox report — pass it directly to the script, it handles downloading
- A task ID + CIPE URL (fetch report via MCP if available)
- Inline violation data
If a task ID is provided but no report, ask the user for the report file.
**Filtering**: Most invocations will focus on specific files, not the entire report. The user may specify:
- A single file: `e2e.log`
- A comma-separated list: `apps/nx-cloud/e2e.log,apps/nx-cloud/build/client/assets/main.js`
- A glob pattern: `*.tsbuildinfo`, `apps/nx-cloud/build/**`
- A directory prefix: `apps/nx-cloud/build/client/assets`
When the user specifies files to focus on, pass them via `--filter` to the script. When they don't specify a filter and the report has many violations, summarize the groupings (by directory, extension) and ask which group(s) to investigate first rather than trying to investigate everything at once.
### Phase 1: Deterministic Pre-Processing
Run the context-gathering script **immediately** — this is the first tool call after reading the user's input.
Call it exactly as shown — do NOT append `2>&1` or `2>/dev/null` (the script manages its own stderr internally). Run in the **foreground** (no `run_in_background`) with a **3-minute timeout** — reports can be large and the script runs the task + multiple nx commands:
```bash
npx tsx ${CLAUDE_SKILL_DIR}/scripts/gather-sandbox-context.ts <report.json or URL> [--filter <pattern>] [--workspace <path>]
```
Pass `--filter` when the user wants to focus on specific files or patterns. The script filters violations before all downstream processing (grouping, validation, classification), so the output only contains relevant data.
The script produces two outputs:
**stdout** (~3-5KB compact brief) — everything needed to start investigating:
- `summary`: violation counts (total, filtered, confirmed vs undeclared)
- `undeclaredFiles`: the actual file paths that are true violations
- `grouping`: violations grouped by directory and extension
- `commands`: processes with violations (pid, cmd, executable, arguments, counts) — no full file lists
- `classificationSummary`: counts per category (cross-project, build artifacts, config files, etc.)
- `crossProjectDependencyCheck`: whether cross-project file owners are in the task's dependency chain
- `staleDeclarations`: grouped analysis of expectedInputsNotRead / expectedOutputsNotWritten
- `dependentTasksOutputFiles`: extracted from target inputs config and named inputs — shows what dep output globs are declared (critical for cross-project violations)
- `executorInfo`: executor name and resolved source path in `node_modules` — read this file to understand how the tool is invoked
- `checkSample`: results of `--check` on up to 5 undeclared files (catches false positives early)
- `inference` + `pluginRegistration`: plugin metadata
- `verificationCommands`: pre-built `--check` commands with the correct task ref
- `detailFile`: path to the full detail JSON
**detail file** (`/tmp/sandbox-diagnosis-detail-<project>-<target>.json`) — full data for drill-down. Structure:
- `processTree.processTree`: array of `{pid, cmd, parentPid}` entries
- `processTree.processPidToCmd`: `{ "pid": "command string" }` map
- `processTree.readsByPid`: `{ "pid": ["file1", "file2"] }` — violated reads grouped by PID
- `processTree.writesByPid`: `{ "pid": ["file1", "file2"] }` — violated writes grouped by PID
- `targetConfig`: full target configuration (executor, options, inputs, outputs, dependsOn)
- `projectConfig`: full project configuration
- `resolvedInputs`: `{ files: [...], depOutputs: [...], runtime: [...], environment: [...] }`
- `resolvedOutputs`: `{ outputPaths: [...], expandedOutputs: [...] }`
- `validation`: `{ reads: { confirmed: [...], undeclared: [...] }, writes: { ... } }`
- `classification`: `{ reads: { crossProject, buildArtifacts, configFiles, ... }, writes: { ... } }`
Read the brief output — it has everything to start. Use `jq` on the detail file only when you need to drill into specific sections. When querying the detail file, use the structure above — do not guess the schema. Do NOT use Python, ad-hoc scripts, or the Read tool on the detail file — only `jq`.
For reports with many violations, use `--filter` to narrow scope. When investigating without a filter, use the `grouping` data to identify patterns and prioritize — don't try to trace every file individually.
If `summary.undeclaredReads` and `summary.undeclaredWrites` are both 0, all violations were resolved by the script's validation against resolved inputs/outputs. Report this to the user — no further investigation needed.
The `commands` array pre-parses each process — use `executable` and `arguments` to identify the tool without re-parsing `cmd`. When many files share the same root cause, group them under one finding using a glob pattern or count (e.g., "88 `.d.ts` files matching `packages/nx/dist/**/*.d.ts`").
### Phase 2: Command Analysis — the core investigation
**This is the most important phase.** The goal is to determine with 100% certainty why each process reads or writes each violated file. Do not classify violations from file names or paths alone — trace the actual causal chain from command → config → file access.
#### Step 1: Understand the command
The brief's `commands` array pre-parses each process. Use the `executable` and `arguments` fields directly — don't re-parse `cmd`. Identify:
- The tool (from `executable`)
- The arguments (target files/dirs, config flags, extensions — from `arguments`)
- The working directory (from executor options or project root)
#### Step 2: Trace why the command accesses each violated file
For each violated file, establish the **exact causal chain** that leads the command to read or write it. The approach is the same regardless of tool:
1. Identify the tool's config file (usually in the project root or workspace root)
2. Read the config and trace file references: `includes`, `extends`, `presets`, entry points, plugins
3. Follow the reference chain until you can explain exactly why the violated file is accessed
Common causal patterns:
- **Config chain walk-up**: tool reads config, config extends another, chain reaches the violated file (e.g., tsconfig `extends`, eslint config chain, jest preset chain)
- **Directory traversal**: tool scans a directory for matching files and reads everything, including files it won't process (e.g., jest-haste-map scanning `.next/`, eslint reading `.d.ts` alongside `.ts`)
- **Dependency resolution**: tool resolves imports/requires and follows the dependency graph to files outside the project (e.g., esbuild/vite/webpack resolving workspace packages to their dist outputs)
- **Plugin/transformer loading**: tool loads plugins or transformers that read additional files (e.g., ts-jest loading tsconfig for TypeScript compilation)
For any tool, read its source code in `node_modules` to understand its file discovery behavior. Don't assume — trace the actual code.
**You must be able to explain the full path:** e.g., "eslint loads `.eslintrc.json` → configures `@typescript-eslint/parser` → parser resolves `parserOptions.project` → walks up to find `tsconfig.json` → reads it." If you can't trace the full path, keep investigating — do not guess.
**When theoretical analysis is inconclusive, verify empirically.** For difficult cases, instrument `node_modules` with interceptors to capture real stack traces. For example, patch `fs.readFileSync` in the tool's entry point to log stack traces when the violated file is accessed. A confirmed stack trace is worth more than multiple rounds of code reading.
#### Step 3: Confirm the violation with `--check`
**This step is mandatory — do not skip it.** The script already runs `--check` on a sample of up to 5 undeclared files (see `checkSample` in the brief). Review those results first — if the sample files are confirmed as inputs/outputs, the corresponding violations are false positives.
For files not in the sample, use the pre-generated commands from `verificationCommands` in the brief:
```bash
npx nx show target inputs <project>:<target> --check <violated-read-files>
npx nx show target outputs <project>:<target> --check <violated-write-files>
```
If the commands fail because output files don't exist (e.g., the script's task run timed out), run the task first with `verificationCommands.runTask`.
If `--check` shows the file IS already an input/output, the violation is a false positive from the script's static analysis. If it confirms the file is NOT an input/output, proceed to classification.
#### Step 4: Classify
With the causal chain established and the violation confirmed, classify into one of these categories:
1. **Missing input/output** (most common) — the process legitimately needs this file. Understand why:
- **Direct dependency** — the tool needs this file to do its job (e.g., tsc reads referenced tsconfigs, eslint loads config chain)
- **Transitive dependency** — a config file references another file that references this one (e.g., jest preset → resolver → module). Trace the full chain.
- **Directory traversal side effect** — the tool reads all files in a directory even if it only processes some (e.g., eslint reads `.d.ts` files while linting `.ts`). Still a legitimate access from the tool's perspective.
2. **Bad tool configuration** — the tool accesses a file it shouldn't because its scope is too broad. The fix is fixing the tool's config, NOT adding an input. Investigate:
- Is the command targeting too broad a directory? (e.g., `eslint .` instead of `eslint src/`)
- Is a config file missing ignore/exclude rules? (e.g., eslint processing a file type it should skip)
- Is a plugin inferring a target for a project that doesn't match? (e.g., eslint target on a non-JS project)
- Is an env var causing the tool to behave differently?
3. **Potential sandboxing gap** (last resort) — the access is genuinely irrelevant to correctness (PID files, temp sockets, dev server logs that no task consumes). Only conclude this after exhausting categories 1 and 2.
### Phase 3: Deep Investigation
For violations that aren't immediately obvious, investigate further:
#### If the target is inferred by a plugin
1. Identify which plugin from `inference.plugin` in the brief output, or `nx show project --json` metadata
2. Read the plugin's `createNodesV2` implementation to understand inference logic
3. Determine if this project should have this target at all
4. Check if the plugin has `include`/`exclude` patterns in `nx.json` that should filter this project
5. **Check for input override layers**`project.json`, `package.json`, or `nx.json` `targetDefaults` may override plugin-inferred inputs, rendering plugin-level fixes invisible. Check all three before concluding a plugin fix is sufficient.
#### If violations come from a subprocess
1. Trace the process tree: which parent spawned the subprocess?
2. Why does the subprocess exist? (dev server for e2e, worker thread, build tool subprocess)
3. What environment does the subprocess inherit? (env vars, cwd)
4. Does the subprocess access files in a different project's directory?
#### If violations involve config file reference chains
1. Read the config file (jest.config, tsconfig, .eslintrc)
2. Trace all file references: `preset`, `extends`, `references`, `setupFiles`, `resolver`, `moduleNameMapper`, `transform`, etc.
3. Recursively resolve references (preset → preset → files)
4. Determine which referenced files are not declared as task inputs
#### If violations involve dependency task outputs
1. Check `dependsOn` to understand task dependency chain
2. Check `dependentTasksOutputFiles` glob pattern — is it too narrow?
3. Compare the glob against actual file types the tool reads from dependencies (e.g., `**/*.d.ts` missing `.tsbuildinfo`)
#### Generalizability analysis
After diagnosing the root cause, determine scope:
1. Is this violation specific to this project, or does it affect all projects using this tool/plugin?
2. What conditions trigger it? (specific config, specific tool version, specific project structure)
3. Should the fix be per-project (declarative input) or systemic (plugin improvement)?
4. If the plugin can be made smarter to infer the correct inputs, that's preferable to manual declarations.
### Phase 4: Output
**You MUST present findings using the structured format below before proceeding to any implementation discussion.** Do not use free-form narrative — the structure ensures completeness and makes findings reviewable.
Present findings grouped by category:
```
=== Sandbox Violation Diagnosis: {project}:{target} ===
## Summary
Unexpected reads: N total → M validated as declared → K true violations
Unexpected writes: N total → M validated as declared → K true violations
## Findings
### [MISSING INPUT] {short description}
Files: {file list or pattern}
Process: PID {pid} — {command}
Why: {why the process legitimately needs this file}
Scope: {project-specific or affects all projects using this tool/plugin}
Fix: {where/how to add the input declaration — consider both declarative (add input) and systemic (improve plugin inference) options}
### [MISSING OUTPUT] {short description}
Files: {file list or pattern}
Process: PID {pid} — {command}
Why: {why the process produces this file}
Scope: {project-specific or affects all projects using this tool/plugin}
Fix: {where/how to add the output declaration}
### [BAD TOOL CONFIG] {short description}
Files: {file list or pattern}
Process: PID {pid} — {command}
Why: {why the tool accesses files it shouldn't — config too broad, missing ignore, etc.}
Fix: {specific tool config change}
### [POTENTIAL SANDBOXING GAP] {short description}
Files: {file list or pattern}
Process: PID {pid} — {command}
Why: {why this access is irrelevant to correctness}
Evidence: {proof that categories 1-2 were exhausted}
### [INVESTIGATE] {short description}
Files: {file list or pattern}
Notes: {what's known, what needs more info}
Question: {what to ask the user or team}
## Stale Declarations
expectedInputsNotRead: {count and details if relevant}
expectedOutputsNotWritten: {count and details if relevant}
## Verification Plan
For each fix, provide the exact commands to verify:
1. Run the task so output files exist on disk: `npx nx <target> <project> --skip-nx-cache`
2. Check each violation file is now an input: `npx nx show target <project>:<target> inputs --check <space-separated files>`
3. For plugin-level fixes: build the plugin, patch node_modules, then verify with steps 1-2
```
## Principles
1. **Missing declaration is the default.** Most unexpected accesses are legitimate — the process needs the file, it just wasn't declared. Start from this assumption and investigate to understand WHY the access happens.
2. **The command is the unit of analysis.** Don't classify files in isolation. Understand what the command does and whether each file access makes sense given that command's purpose.
3. **Trace the full chain.** Plugin inference → target config → executor → command → file access. The root cause is often several layers removed from the symptom.
4. **Empirical over theoretical.** When code analysis produces a hypothesis, verify it before acting. Instrument `node_modules`, capture stack traces, run with debug flags. Wrong theories waste entire investigation rounds.
5. **Be thorough.** Read plugin source code, config files, executor implementations. Don't guess based on file names alone.
6. **Potential sandboxing gaps are last resort.** Only conclude this after exhausting missing declaration and bad tool config. The access must be genuinely irrelevant to correctness.
7. **Verify claims about Nx behavior in source code.** Any assertion about how Nx works must be traced to the actual implementation. Do not reason from theory or assumptions.
8. **Prefer systemic fixes over per-project declarations.** If a plugin can be improved to infer correct inputs for all projects, that's better than adding manual input declarations to each project.
## Delegating to Subagents
When the investigation is complex and requires parallel research, you can delegate to subagents. Follow this pattern:
1. **Run the context-gathering script yourself first.** The brief output (~3-5KB) is the shared context all subagents need.
2. **Include the brief output in each subagent prompt** along with the specific question to investigate. Subagents should NOT run the script again or try to parse the raw report.
3. **Give subagents the detail file path** so they can `jq` specific sections (process tree, resolved inputs, etc.) without re-running the script.
4. **Each subagent should answer one focused question**, e.g., "Why does PID 12345 (eslint) read `tsconfig.base.json`? Trace the full causal chain from the eslint config."
5. **Subagents must still follow the skill principles** — trace full causal chains, verify empirically, use `--check`, don't guess from file names. Include these instructions in the subagent prompt.
6. **Synthesize subagent results yourself** using the structured Phase 4 output format. Do not delegate the final classification.
## Reference
For the sandbox report data model and field definitions, see `references/data-model.md`.
@@ -0,0 +1,92 @@
# Sandbox Report Data Model
## Raw Report Structure (JSON)
```typescript
interface SandboxReport {
taskId: string; // "project:target" or "project:target:configuration"
sandboxReportId: string;
inputs: string[]; // declared input patterns (globs or paths)
outputs: string[]; // declared output patterns
filesRead: FileAccessEntry[]; // all files actually read
filesWritten: FileAccessEntry[]; // all files actually written
unexpectedReads?: FileAccessEntry[]; // reads not matching any input pattern
unexpectedWrites?: FileAccessEntry[]; // writes not matching any output pattern
expectedInputsNotRead?: string[]; // declared inputs never accessed
expectedOutputsNotWritten?: string[]; // declared outputs never written
processTree?: ProcessTreeEntry[]; // process hierarchy with commands
}
interface FileAccessEntry {
path: string; // workspace-relative file path
pid: number; // process ID that accessed the file
}
interface ProcessTreeEntry {
pid: number;
cmd: string; // full command string
parentPid?: number; // parent process (absent for root)
}
```
## Violation Computation
Violations are computed by `findUnexpectedFiles()` using `minimatch`:
- A file is "unexpected" if it does NOT match any declared pattern
- Patterns without wildcards also match as directory prefixes (`pattern + '/'`)
- If `unexpectedReads`/`unexpectedWrites` are pre-computed in the report, those are used directly
## Nx CLI Commands for Context
### `nx show target <project:target> --json`
Returns: executor, command, options (merged with configuration), inputs (configured, not resolved), outputs, dependsOn, cache, parallelism, configurations, metadata.
### `nx show target inputs <project:target> --json`
Returns resolved input files (requires files to exist on disk — task must have run):
```json
{
"files": ["workspace-relative paths..."],
"runtime": ["node version checks..."],
"environment": ["ENV_VAR_NAMES..."],
"depOutputs": ["dependency output paths..."],
"external": ["external package names..."]
}
```
### `nx show target inputs <project:target> --check <files...>`
Validates specific files against declared inputs. Exit code 0 = match, 1 = no match.
Categories: `files`, `environment`, `runtime`, `external`, `depOutputs`.
Also detects directory matches (directory containing N input files).
### `nx show target outputs <project:target> --json`
Returns:
```json
{
"outputPaths": ["configured output paths..."],
"expandedOutputs": ["glob-expanded actual paths..."],
"unresolvedOutputs": ["{options.key} patterns that couldn't resolve..."]
}
```
### `nx show target outputs <project:target> --check <files...>`
Validates specific files against declared outputs. Same exit code behavior as inputs.
### `nx show project <project> --json`
Returns full project config. Key fields for sandbox analysis:
- `targets[name].metadata.plugin` — which plugin inferred the target
- `targets[name].metadata.technologies` — what tech the target uses
- `root` — project root directory
### `nx graph --view=tasks --targets=<target> --focus=<project> --print --file=stdout`
Returns task dependency graph with task IDs, dependencies, and roots.
@@ -0,0 +1,846 @@
#!/usr/bin/env npx tsx
/**
* gather-sandbox-context: Parse sandbox report + gather Nx task context
* Produces structured JSON for the diagnose-sandbox-report skill
*
* Usage: npx tsx gather-sandbox-context.ts <report.json or URL> [--filter <pattern>] [--workspace <path>]
*/
import { readFileSync, writeFileSync, existsSync } from 'fs';
import { resolve, basename, extname, dirname } from 'path';
import { execSync, execFileSync } from 'child_process';
import { minimatch } from 'minimatch';
// --- CLI argument parsing ---
interface Args {
reportFile: string;
filter: string | null;
workspaceRoot: string;
}
function parseArgs(): Args {
const args = process.argv.slice(2);
let reportFile = '';
let filter: string | null = null;
let workspaceRoot = process.cwd();
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case '--filter':
filter = args[++i];
break;
case '--workspace':
workspaceRoot = args[++i];
break;
case '--help':
case '-h':
console.error(
'Usage: gather-sandbox-context <report.json or URL> [--filter <pattern>] [--workspace <path>]'
);
process.exit(1);
default:
if (args[i].startsWith('-')) {
console.error(`Unknown option: ${args[i]}`);
process.exit(1);
}
reportFile = args[i];
}
}
if (!reportFile) {
console.error(
'Usage: gather-sandbox-context <report.json or URL> [--filter <pattern>] [--workspace <path>]'
);
process.exit(1);
}
return { reportFile, filter, workspaceRoot };
}
// --- Types ---
interface FileAccessEntry {
path: string;
pid: number;
}
interface ProcessTreeEntry {
pid: number;
cmd: string;
parentPid?: number;
}
interface SandboxReport {
taskId: string;
unexpectedReads?: FileAccessEntry[];
unexpectedWrites?: FileAccessEntry[];
expectedInputsNotRead?: string[];
expectedOutputsNotWritten?: string[];
filesRead?: FileAccessEntry[];
filesWritten?: FileAccessEntry[];
processTree?: ProcessTreeEntry[];
}
// --- Helpers ---
function downloadUrl(url: string): string {
const tmpPath = `/tmp/sandbox-report-${Date.now()}.json`;
try {
execFileSync('curl', ['-sL', '-o', tmpPath, url], { stdio: 'pipe' });
} catch {
console.error(`Error: Failed to download report from URL: ${url}`);
process.exit(1);
}
return tmpPath;
}
function runNxCommand(
args: string[],
workspaceRoot: string,
timeoutMs = 30000
): string | null {
try {
return execFileSync('npx', ['nx', ...args], {
cwd: workspaceRoot,
timeout: timeoutMs,
stdio: ['pipe', 'pipe', 'pipe'],
encoding: 'utf-8',
});
} catch {
return null;
}
}
function safeJsonParse<T>(str: string | null, fallback: T): T {
if (!str) return fallback;
try {
return JSON.parse(str);
} catch {
return fallback;
}
}
function filterEntries(
entries: FileAccessEntry[],
filterStr: string | null
): FileAccessEntry[] {
if (!filterStr) return entries;
const patterns = filterStr.split(',').map((p) => p.trim());
return entries.filter((entry) =>
patterns.some((pattern) => {
if (
pattern.includes('*') ||
pattern.includes('?') ||
pattern.includes('[')
) {
// Glob pattern — if no slashes, match against basename
if (!pattern.includes('/')) {
return minimatch(basename(entry.path), pattern);
}
return minimatch(entry.path, pattern);
}
// Literal: exact match or directory prefix
return entry.path === pattern || entry.path.startsWith(pattern + '/');
})
);
}
function groupByDirPrefix(
paths: string[],
depth = 3
): { prefix: string; count: number }[] {
const groups: Record<string, number> = {};
for (const p of paths) {
const prefix = p.split('/').slice(0, depth).join('/');
groups[prefix] = (groups[prefix] || 0) + 1;
}
return Object.entries(groups)
.map(([prefix, count]) => ({ prefix, count }))
.sort((a, b) => b.count - a.count);
}
function groupByExtension(paths: string[]): { ext: string; count: number }[] {
const groups: Record<string, number> = {};
for (const p of paths) {
const ext = extname(p) || '(no ext)';
groups[ext] = (groups[ext] || 0) + 1;
}
return Object.entries(groups)
.map(([ext, count]) => ({ ext, count }))
.sort((a, b) => b.count - a.count);
}
function classifyFiles(
undeclared: string[],
projectRoot: string,
projectRoots: Record<string, string>
) {
const projects = Object.entries(projectRoots).map(([project, root]) => ({
project,
root,
}));
const isBuildArtifact = (f: string) =>
f.startsWith('dist/') ||
f.startsWith('build/') ||
f.startsWith('out-tsc/') ||
f.startsWith('.next/') ||
f.includes('/node_modules/.cache/') ||
f.endsWith('.tsbuildinfo') ||
f.includes('/dist/') ||
f.includes('/build/output/');
const configBasenames = new Set(['nx.json', 'project.json', 'package.json']);
const configPrefixes = [
'tsconfig',
'jest.config',
'jest.preset',
'.eslintrc',
'eslint.config',
'playwright.config',
'webpack.config',
'vite.config',
'babel.config',
'.babelrc',
'rollup.config',
];
const isConfigFile = (f: string) => {
const b = basename(f);
return (
configBasenames.has(b) ||
configPrefixes.some((prefix) => b.startsWith(prefix))
);
};
const isEnvFile = (f: string) => {
const b = basename(f);
return b === '.env' || b.startsWith('.env.');
};
const classified = undeclared.map((f) => {
const inProjectRoot = projectRoot !== '' && f.startsWith(projectRoot + '/');
const owner = projects.find((p) => f.startsWith(p.root + '/'));
return {
path: f,
inProjectRoot,
ownerProject: owner?.project ?? null,
isBuildArtifact: isBuildArtifact(f),
isConfigFile: isConfigFile(f),
isEnvFile: isEnvFile(f),
};
});
return {
crossProject: classified
.filter((c) => !c.inProjectRoot)
.map((c) => ({ path: c.path, owner: c.ownerProject })),
buildArtifacts: classified
.filter((c) => c.isBuildArtifact)
.map((c) => c.path),
configFiles: classified.filter((c) => c.isConfigFile).map((c) => c.path),
envFiles: classified.filter((c) => c.isEnvFile).map((c) => c.path),
inProjectRoot: classified.filter((c) => c.inProjectRoot).map((c) => c.path),
outsideProjectRoot: classified
.filter((c) => !c.inProjectRoot)
.map((c) => c.path),
total: undeclared.length,
};
}
function validateViolations(
violations: string[],
resolvedFiles: Set<string>
): { confirmed: string[]; undeclared: string[] } {
const confirmed: string[] = [];
const undeclared: string[] = [];
const seen = new Set<string>();
for (const f of violations) {
if (seen.has(f)) continue;
seen.add(f);
if (resolvedFiles.has(f)) {
confirmed.push(f);
} else {
undeclared.push(f);
}
}
return { confirmed, undeclared };
}
function validateOutputViolations(
violations: string[],
resolvedOutputs: string[]
): { confirmed: string[]; undeclared: string[] } {
const outputSet = new Set(resolvedOutputs);
const outputDirs = resolvedOutputs.map((o) => o + '/');
const confirmed: string[] = [];
const undeclared: string[] = [];
const seen = new Set<string>();
for (const f of violations) {
if (seen.has(f)) continue;
seen.add(f);
if (outputSet.has(f) || outputDirs.some((d) => f.startsWith(d))) {
confirmed.push(f);
} else {
undeclared.push(f);
}
}
return { confirmed, undeclared };
}
function extractCommands(
processTree: ProcessTreeEntry[],
readsByPid: Record<string, string[]>,
writesByPid: Record<string, string[]>
) {
const pidToCmd: Record<string, string> = {};
for (const entry of processTree) {
pidToCmd[String(entry.pid)] = entry.cmd;
}
return processTree
.filter(
(entry) =>
(readsByPid[String(entry.pid)]?.length ?? 0) > 0 ||
(writesByPid[String(entry.pid)]?.length ?? 0) > 0
)
.map((entry) => {
const parts = entry.cmd.split(' ');
const exe = parts[0].split('/').pop() ?? parts[0];
return {
pid: entry.pid,
cmd: entry.cmd,
parentPid: entry.parentPid ?? null,
parentCmd: entry.parentPid
? (pidToCmd[String(entry.parentPid)] ?? null)
: null,
unexpectedReadCount: readsByPid[String(entry.pid)]?.length ?? 0,
unexpectedWriteCount: writesByPid[String(entry.pid)]?.length ?? 0,
unexpectedReads: readsByPid[String(entry.pid)] ?? [],
unexpectedWrites: writesByPid[String(entry.pid)] ?? [],
executable: exe,
arguments: parts.slice(1).join(' '),
};
})
.sort(
(a, b) =>
b.unexpectedReadCount +
b.unexpectedWriteCount -
(a.unexpectedReadCount + a.unexpectedWriteCount)
);
}
function resolveExecutorSource(
executor: string | undefined,
workspaceRoot: string
): { executor: string; sourcePath: string } {
if (
!executor ||
executor === 'null' ||
executor.includes('nx:run-commands')
) {
return { executor: executor ?? '', sourcePath: '' };
}
const lastColon = executor.lastIndexOf(':');
const pkg = executor.substring(0, lastColon);
const name = executor.substring(lastColon + 1);
try {
const result = execFileSync(
'node',
[
'-e',
`
try {
const pkg = require('${pkg}/package.json');
const executors = pkg.executors || pkg.builders;
if (executors) {
const p = require.resolve('${pkg}/' + executors);
const dir = require('path').dirname(p);
const json = require(p);
const impl = json.executors?.['${name}']?.implementation ||
json.builders?.['${name}']?.implementation;
if (impl) console.log(require.resolve(dir + '/' + impl));
}
} catch(e) {}
`,
],
{
cwd: workspaceRoot,
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
timeout: 10000,
}
).trim();
return { executor, sourcePath: result };
} catch {
return { executor, sourcePath: '' };
}
}
function extractDepTaskOutputFiles(
targetConfig: any,
workspaceRoot: string
): { dependentTasksOutputFiles: any[]; namedInputs: string[] } {
const inputs: any[] = targetConfig?.inputs ?? [];
const depOutputs: any[] = [];
const namedInputs: string[] = [];
for (const input of inputs) {
if (
typeof input === 'object' &&
input !== null &&
'dependentTasksOutputFiles' in input
) {
depOutputs.push({
glob: input.dependentTasksOutputFiles,
transitive: input.transitive ?? false,
});
} else if (
typeof input === 'string' &&
!input.startsWith('{') &&
!input.startsWith('^') &&
!input.includes('/') &&
!input.includes('.')
) {
namedInputs.push(input);
}
}
// Resolve named inputs from nx.json
const nxJsonPath = resolve(workspaceRoot, 'nx.json');
if (existsSync(nxJsonPath) && namedInputs.length > 0) {
try {
const nxJson = JSON.parse(readFileSync(nxJsonPath, 'utf-8'));
for (const name of namedInputs) {
const namedDef = nxJson.namedInputs?.[name] ?? [];
for (const entry of namedDef) {
if (
typeof entry === 'object' &&
entry !== null &&
'dependentTasksOutputFiles' in entry
) {
depOutputs.push({
glob: entry.dependentTasksOutputFiles,
transitive: entry.transitive ?? false,
fromNamedInput: name,
});
}
}
}
} catch {
// ignore nx.json parse errors
}
}
return { dependentTasksOutputFiles: depOutputs, namedInputs };
}
function analyzeStaleDeclarations(
expectedInputsNotRead: string[],
expectedOutputsNotWritten: string[]
) {
const classifyPattern = (value: string) => {
if (/[*{]/.test(value)) return 'glob';
if (value.startsWith('^')) return 'depOutput';
return 'file';
};
const groupByType = (items: string[]) => {
const groups: Record<string, string[]> = {};
for (const item of items) {
const type = classifyPattern(item);
(groups[type] ??= []).push(item);
}
return Object.entries(groups).map(([type, values]) => ({
type,
count: values.length,
samples: values.slice(0, 3),
}));
};
return {
expectedInputsNotRead: expectedInputsNotRead.length,
expectedOutputsNotWritten: expectedOutputsNotWritten.length,
staleInputsByType: groupByType(expectedInputsNotRead),
staleOutputsByType: groupByType(expectedOutputsNotWritten),
};
}
// --- Main ---
async function main() {
const args = parseArgs();
let reportPath = args.reportFile;
// Handle URL inputs
if (reportPath.startsWith('http')) {
reportPath = downloadUrl(reportPath);
}
if (!existsSync(reportPath)) {
console.error(`Error: Report file not found: ${reportPath}`);
process.exit(1);
}
reportPath = resolve(reportPath);
process.chdir(args.workspaceRoot);
// Phase 1: Parse report (single read)
let report: SandboxReport;
try {
report = JSON.parse(readFileSync(reportPath, 'utf-8'));
} catch {
console.error(`Error: Report file is not valid JSON: ${reportPath}`);
process.exit(1);
}
if (!report.taskId) {
console.error('Error: Report file has no .taskId field');
process.exit(1);
}
const [project, target, config] = report.taskId.split(':');
const taskRef = config
? `${project}:${target}:${config}`
: `${project}:${target}`;
const unexpectedReads = report.unexpectedReads ?? [];
const unexpectedWrites = report.unexpectedWrites ?? [];
// Apply filter
const filteredReads = filterEntries(unexpectedReads, args.filter);
const filteredWrites = filterEntries(unexpectedWrites, args.filter);
const readPaths = filteredReads.map((e) => e.path);
const writePaths = filteredWrites.map((e) => e.path);
// Build pid → files maps
const readsByPid: Record<string, string[]> = {};
const writesByPid: Record<string, string[]> = {};
for (const entry of filteredReads) {
(readsByPid[String(entry.pid)] ??= []).push(entry.path);
}
for (const entry of filteredWrites) {
(writesByPid[String(entry.pid)] ??= []).push(entry.path);
}
// Phase 2: Gather Nx task context (run task + parallel nx commands)
runNxCommand(['run', taskRef], args.workspaceRoot, 120000);
const [
targetConfigStr,
projectConfigStr,
resolvedInputsStr,
resolvedOutputsStr,
graphResult,
] = await Promise.all([
runNxCommand(['show', 'target', taskRef, '--json'], args.workspaceRoot),
runNxCommand(['show', 'project', project, '--json'], args.workspaceRoot),
runNxCommand(
['show', 'target', 'inputs', taskRef, '--json'],
args.workspaceRoot
),
runNxCommand(
['show', 'target', 'outputs', taskRef, '--json'],
args.workspaceRoot
),
(() => {
const graphPath = `/tmp/sandbox-project-graph-${Date.now()}.json`;
runNxCommand(['graph', '--file', graphPath], args.workspaceRoot);
try {
return readFileSync(graphPath, 'utf-8');
} catch {
return '{"graph":{"nodes":{}}}';
}
})(),
]);
const targetConfig = safeJsonParse(targetConfigStr, {} as any);
const projectConfig = safeJsonParse(projectConfigStr, {} as any);
const resolvedInputs = safeJsonParse(resolvedInputsStr, {} as any);
const resolvedOutputs = safeJsonParse(resolvedOutputsStr, {} as any);
const projectGraph = safeJsonParse(graphResult, {
graph: { nodes: {} },
} as any);
// Phase 3: Validate violations
const resolvedInputFiles = new Set([
...(resolvedInputs.files ?? []),
...(resolvedInputs.depOutputs ?? []),
]);
const resolvedOutputFiles = [
...(resolvedOutputs.outputPaths ?? []),
...(resolvedOutputs.expandedOutputs ?? []),
];
const checkInputs = validateViolations(readPaths, resolvedInputFiles);
const checkOutputs = validateOutputViolations(
writePaths,
resolvedOutputFiles
);
// Phase 3.5: Sample --check verification
let checkSampleInputs: any = {};
let checkSampleOutputs: any = {};
const sampleReadFiles = checkInputs.undeclared.slice(0, 5);
if (sampleReadFiles.length > 0) {
const result = runNxCommand(
[
'show',
'target',
'inputs',
taskRef,
'--check',
...sampleReadFiles,
'--json',
],
args.workspaceRoot
);
checkSampleInputs = safeJsonParse(result, {});
}
const sampleWriteFiles = checkOutputs.undeclared.slice(0, 5);
if (sampleWriteFiles.length > 0) {
const result = runNxCommand(
[
'show',
'target',
'outputs',
taskRef,
'--check',
...sampleWriteFiles,
'--json',
],
args.workspaceRoot
);
checkSampleOutputs = safeJsonParse(result, {});
}
// Phase 4: File classification
const projectRoots: Record<string, string> = {};
for (const [name, node] of Object.entries(projectGraph.graph?.nodes ?? {})) {
projectRoots[name] = (node as any).data?.root ?? name;
}
const taskProjectRoot = projectRoots[project] ?? '';
const readClassification = classifyFiles(
checkInputs.undeclared,
taskProjectRoot,
projectRoots
);
const writeClassification = classifyFiles(
checkOutputs.undeclared,
taskProjectRoot,
projectRoots
);
// Phase 5: Command extraction
const processTree = report.processTree ?? [];
const commands = extractCommands(processTree, readsByPid, writesByPid);
// Phase 6: Inference detection
const targetMeta = projectConfig.targets?.[target]?.metadata ?? {};
const inference = {
isInferred: 'plugin' in targetMeta || 'technologies' in targetMeta,
plugin: targetMeta.plugin ?? null,
technologies: targetMeta.technologies ?? null,
description: targetMeta.description ?? null,
};
let pluginRegistration: any = {};
const nxJsonPath = resolve(args.workspaceRoot, 'nx.json');
if (inference.plugin && existsSync(nxJsonPath)) {
try {
const nxJson = JSON.parse(readFileSync(nxJsonPath, 'utf-8'));
const plugins = (nxJson.plugins ?? []).map((p: any) =>
typeof p === 'string' ? { plugin: p, options: {} } : p
);
pluginRegistration =
plugins.find((p: any) => p.plugin === inference.plugin) ?? {};
} catch {
// ignore
}
}
// Phase 6.5: dependentTasksOutputFiles + executor resolution
const depTaskOutputs = extractDepTaskOutputFiles(
targetConfig,
args.workspaceRoot
);
const executorInfo = resolveExecutorSource(
targetConfig.executor ?? targetConfig.command,
args.workspaceRoot
);
// Phase 7: Cross-project dependency check
const dependsOn = (targetConfig.dependsOn ?? []).map((d: any) =>
typeof d === 'string' ? d : (d.target ?? '')
);
const checkCrossProject = (classification: typeof readClassification) => {
const owners = [
...new Set(
classification.crossProject
.map((c) => c.owner)
.filter((o): o is string => o !== null)
),
];
return owners.map((owner) => ({
project: owner,
isDependency: dependsOn.some(
(d: string) =>
d === owner ||
d === `${owner}:build` ||
d === `^${owner}:build` ||
d.includes(`^${owner}`)
),
files: classification.crossProject
.filter((c) => c.owner === owner)
.map((c) => c.path),
}));
};
const crossProjectDeps = {
reads: checkCrossProject(readClassification),
writes: checkCrossProject(writeClassification),
};
// Phase 8: Stale declarations
const staleDeclarations = analyzeStaleDeclarations(
report.expectedInputsNotRead ?? [],
report.expectedOutputsNotWritten ?? []
);
// Assemble outputs
const detailFile = `/tmp/sandbox-diagnosis-detail-${taskRef.replace(/[/:@]/g, '-')}.json`;
const detail = {
processTree: {
processTree,
processPidToCmd: Object.fromEntries(
processTree.map((e) => [String(e.pid), e.cmd])
),
readsByPid,
writesByPid,
},
targetConfig,
projectConfig,
resolvedInputs,
resolvedOutputs,
validation: { reads: checkInputs, writes: checkOutputs },
classification: { reads: readClassification, writes: writeClassification },
report: {
taskId: report.taskId,
totalFilesRead: report.filesRead?.length ?? 0,
totalFilesWritten: report.filesWritten?.length ?? 0,
totalUnexpectedReads: unexpectedReads.length,
totalUnexpectedWrites: unexpectedWrites.length,
expectedInputsNotRead: report.expectedInputsNotRead ?? [],
expectedOutputsNotWritten: report.expectedOutputsNotWritten ?? [],
},
commands,
crossProjectDependencyCheck: crossProjectDeps,
staleDeclarations,
inference,
pluginRegistration,
dependentTasksOutputFiles: depTaskOutputs,
executorInfo,
};
writeFileSync(detailFile, JSON.stringify(detail, null, 2));
// Brief to stdout
const brief = {
task: {
ref: taskRef,
project,
target,
configuration: config ?? null,
projectRoot: taskProjectRoot,
},
summary: {
unexpectedReads: unexpectedReads.length,
unexpectedWrites: unexpectedWrites.length,
filteredReads: filteredReads.length,
filteredWrites: filteredWrites.length,
filterApplied: args.filter !== null,
filterPattern: args.filter,
confirmedReads: checkInputs.confirmed.length,
undeclaredReads: checkInputs.undeclared.length,
confirmedWrites: checkOutputs.confirmed.length,
undeclaredWrites: checkOutputs.undeclared.length,
},
undeclaredFiles: {
reads: checkInputs.undeclared,
writes: checkOutputs.undeclared,
},
grouping: {
readsByDirectory: groupByDirPrefix(readPaths),
writesByDirectory: groupByDirPrefix(writePaths),
byExtension: {
readsByExt: groupByExtension(readPaths),
writesByExt: groupByExtension(writePaths),
},
},
commands: commands.map(
({
pid,
cmd,
parentCmd,
executable,
arguments: args,
unexpectedReadCount,
unexpectedWriteCount,
}) => ({
pid,
cmd,
parentCmd,
executable,
arguments: args,
unexpectedReadCount,
unexpectedWriteCount,
})
),
checkSample: {
inputs: checkSampleInputs,
outputs: checkSampleOutputs,
},
classificationSummary: {
reads: {
crossProject: readClassification.crossProject.length,
buildArtifacts: readClassification.buildArtifacts.length,
configFiles: readClassification.configFiles.length,
envFiles: readClassification.envFiles.length,
inProjectRoot: readClassification.inProjectRoot.length,
outsideProjectRoot: readClassification.outsideProjectRoot.length,
},
writes: {
crossProject: writeClassification.crossProject.length,
buildArtifacts: writeClassification.buildArtifacts.length,
configFiles: writeClassification.configFiles.length,
envFiles: writeClassification.envFiles.length,
inProjectRoot: writeClassification.inProjectRoot.length,
outsideProjectRoot: writeClassification.outsideProjectRoot.length,
},
},
crossProjectDependencyCheck: crossProjectDeps,
staleDeclarations,
dependentTasksOutputFiles: depTaskOutputs.dependentTasksOutputFiles,
executorInfo,
inference,
pluginRegistration,
verificationCommands: {
checkInputs: `npx nx show target inputs ${taskRef} --check <files...>`,
checkOutputs: `npx nx show target outputs ${taskRef} --check <files...>`,
runTask: `npx nx run ${taskRef} --skip-nx-cache`,
},
detailFile,
};
console.log(JSON.stringify(brief, null, 2));
}
main().catch((err) => {
console.error(`Script failed: ${err.message}`);
process.exit(1);
});
+12
View File
@@ -30,5 +30,17 @@
"enableAllProjectMcpServers": true,
"env": {
"BASH_MAX_TIMEOUT_MS": "1800000"
},
"extraKnownMarketplaces": {
"nx-claude-plugins": {
"source": {
"source": "github",
"repo": "nrwl/nx-ai-agents-config",
"ref": "experimental"
}
}
},
"enabledPlugins": {
"nx@nx-claude-plugins": true
}
}
@@ -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.
@@ -0,0 +1,399 @@
---
name: multi-version-compliance
description: >
Apply or review multi-version support compliance for first-party Nx
plugins. Primary entry point: a Linear task ID (NXC-XXXX) from the
"Multi-version supported across plugins" milestone — the task carries the
resolved support window, findings, and "Needs human decision" items. Falls
back to self-discovery when no task exists. Use when asked to "fix
multi-version compliance for @nx/X", "do NXC-XXXX", "review this
compliance PR", or when working on a branch / PR titled "multi-version
support compliance for @nx/X". Covers the canonical shape
(assertSupportedPackageVersion, all-generators-enforce-floor.spec.ts,
peer dep alignment, requires-gate auditing, user-pin preservation,
executor / inferred-plugin feature gating).
argument-hint: '[<NXC-XXXX> | @nx/<plugin> | review #<PR>]'
allowed-tools: Bash, Read, Edit, Write, Glob, Grep, Agent, mcp__linear-server__get_issue, mcp__linear-server__list_comments, mcp__linear-server__get_milestone, mcp__linear-server__list_issues
---
# Multi-version compliance for Nx plugins
## What this is
The `nx migrate --first-party-only` flag lets users upgrade Nx without
dragging the managed third-party ecosystems (Angular, Cypress, Playwright,
Jest, Vitest, ESLint, etc.) along. For that to be safe, every first-party
plugin must keep working across its declared support window — not silently
fall through to the latest install constants on older workspaces, not
silently break on newer ones.
**Source-of-truth split:**
| Source | Owns |
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Linear milestone "Multi-version supported across plugins" (project NXC-4072) | What's wrong per plugin, the resolved support window, open human decisions. Per-plugin tasks NXC-4381..NXC-4410 (P1P29). |
| This skill | How to implement the canonical shape, code-level anti-patterns, gotchas, findings doc shape (no-task case). |
The skill is the gap-closer: it accepts a Linear task, parses it, drives
the fix. When no task exists for the plugin, fix mode runs discovery in
Phase 12 and produces a findings doc that mirrors a Linear task body —
so the user can file it as a new task before proceeding.
**Reference PRs (the canonical shape):**
- `#35587``@nx/angular` — merged. Set the precedent. Introduced
`throwForUnsupportedVersion`.
- `#35642``@nx/playwright` — merged. Generalized the shared helpers
into `@nx/devkit/internal`. Established executor / runtime feature-
gating.
- `#35670``@nx/cypress` — merged. Added `excludeGenerators` to the
parameterized test helper.
- `#35671``@nx/vitest` — open at time of writing. Demonstrates
"drop phantom peer-range claim" and "declared floor < effective floor"
patterns.
Before citing any PR by number, verify state — these go stale:
`gh pr view <N> --repo nrwl/nx --json state`. Verify any unmerged PR's
contents via `gh pr diff <N> --repo nrwl/nx`.
## Entry points
| Invocation | Mode | Behavior |
| ----------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `multi-version-compliance <NXC-XXXX>` | Fix (primary) | Fetch task, surface findings + decisions in Phase 2, wait for user OK before Phase 3 edits. |
| `multi-version-compliance` (no arg) | Ask for task ID | Prompt for NXC-XXXX. |
| `multi-version-compliance @nx/<plugin>` (bare plugin) | Fix (task lookup) | Look up the per-plugin task in milestone NXC-4072. If found, confirm with user and enter fix mode. If not found, run discovery in Phase 12 (rubric against code), present findings, suggest filing as a new task before any edits. |
| `multi-version-compliance review #<N>` | Review | Fetch PR, derive Linear task from branch name if possible, compare diff vs. task findings (or run pure code-level review if no task). |
**Stop-after-Phase-2 (audit-equivalent):** if you want findings without
edits, decline to approve at the end of Phase 2. The skill stops, no
branch, no commits.
**On a branch matching `nxc-NNNN` with no explicit arg:** before
asking the user, suggest "Use NXC-NNNN?" inferred from the branch name.
## Linear-fetching protocol
Before any code-level work in Linear-driven mode, the skill MUST:
1. **Check Linear MCP availability.** If `mcp__linear-server__get_issue`
isn't available (MCP server not installed / not connected), tell the
user and fall through to the no-task discovery path (fix mode Phase 1
step 2). Don't pretend to fetch.
2. **Fetch the task.** `mcp__linear-server__get_issue id="NXC-XXXX"`.
If the call errors (invalid ID, network), halt and ask the user to
verify the ID.
3. **Verify shape.** Confirm:
- Title matches `[multi-version][P##] \`@nx/<plugin>\` — multi-version support compliance`(per-plugin) or`[multi-version][W#] ...` (cross-cutting). If the pattern doesn't match, halt and ask the user to confirm this is the right task.
- Status. `Done` → ask whether re-audit or follow-up. `Canceled` → halt and ask.
4. **Read description sections.** Every per-plugin task has:
- **Plugin:** — path, upstream support, peerDep declarations, per-major install map, paired secondaries.
- **Needs human decision** — open items blocking implementation.
- **Findings** — `(high|medium|low)` items with `[file:line]` and a suggested fix per item.
- **Verification checklist** — Sections A (Support window declarations) / B (Generator inputs) / C (Generator outputs) / D (Migrations) / E (Runtime) / F (Out-of-window UX).
5. **Fetch comments.** `mcp__linear-server__list_comments issueId="..."`.
Audits attached as files / linked uploads may carry additional
context.
6. **Surface "Needs human decision" as a batch.** Restate every decision
item in chat. The user can resolve all, defer some, or override.
Block until the user has acknowledged the set — don't proceed silently.
7. **Translate findings → code changes.** Map each finding to a canonical
pattern in `references/canonical-shape.md`. The Linear task's
suggested fix is the authoritative scope; the skill verifies it
conforms to the canonical shape and flags any deviation.
8. **Run the AF checklist** against the final code state. The task's
checklist is the agreed scope. The skill verifies code-level
conformance.
**Default to the task's resolved support window.** Don't re-derive it
from code unless the user explicitly overrides. If the user overrides:
restate the new window and confirm before applying.
**Don't expand scope beyond the task's Findings without asking.** If you
spot a new issue mid-fix: stop, present it, ask whether to (a) add it to
this PR, (b) defer as a follow-up, or (c) update the Linear task as a
comment.
## Mode workflows
### Fix mode (primary)
**Phase 1 — Read.**
1. If a Linear task ID was provided, fetch it per the Linear-fetching
protocol. If only a plugin name was provided, look up the per-plugin
task in milestone NXC-4072.
2. **No task case.** If no task exists for this plugin: run discovery
instead — apply the policy ladder for the support window
(Rule 1: upstream LTS for Angular/React/ESLint/Next/Expo; Rule 2:
N & N-1; widen to existing supported set if larger), inventory the
plugin's code against the AF rubric, find the effective floor by
walking imports, classify all results as new findings. The skill is
producing audit-quality output for a plugin that wasn't ticketed.
3. If on a branch matching `nxc-NNNN`, read recent commits to understand
prior scope decisions.
4. Read `references/canonical-shape.md` and `references/anti-patterns.md`.
**Phase 2 — Align.**
5. **(task case)** Surface every "Needs human decision" item from the
task as a batch. Wait for resolutions.
6. **(task case)** Restate the Findings list with severity tags. Confirm
scope.
7. **(no-task case)** Surface findings discovered from the rubric
inventory + decisions the rubric surfaces (floor raise/drop, peer
declarations, optional-vs-required peer, one-sided gates, etc.).
Suggest filing them as a new Linear task in milestone NXC-4072
before proceeding to Phase 3.
8. **User OK gate.** Wait for explicit "proceed" before Phase 3.
Declining stops the skill — no branch, no edits. (This is the
audit-equivalent.)
**Phase 3 — Implement** (per `canonical-shape.md`).
9. Branch from `master` if needed using the repo's `nxc-NNNN` convention.
10. Order: any shared-helper extension lands first; plugin changes land
after. Commit/PR titling defers to the user's conventions.
11. For each Finding category, apply the canonical pattern:
- Section A → peer ranges + version map + install constants. Every
third-party package the plugin **invokes at runtime** (TS import,
executor spawning the CLI binary, or inferred-plugin emitting a
target with `command: '<bin>'`) gets a peer entry. Default to
`optional: true` via `peerDependenciesMeta` for gated surfaces
(executor opt-in, inferred plugin gated on config file presence).
Non-optional peers are reserved for packages every workspace using
the plugin needs.
- Section B → generator entry asserts, `keepExistingVersions`,
fresh-install branch.
- Section C → templates, schema stubs with runtime throws,
version-map coverage.
- Section D → `requires` gates per package per AND-semantics; split
mixed entries; retain intentional pre-floor entries. **Default to
bilateral bounds** (`>=N <M`) when writing a cross-major gate.
One-sided gates (`<N` with no lower, `>=N` with no upper) need a
justified reason (legacy cleanup, undefined source, v0→v1 bridge)
— record the reason in the findings doc or as a code comment.
- Section E → executor and inferred-plugin feature gates.
- Section F → below-floor throw via shared util.
- **Cross-cutting:** if the fix changes runtime behavior, update any
in-codebase docs (`astro-docs/`, `docs/`, inline `.md`) that
describe the changed behavior. Docs that contradict the code are a
correctness bug, not a PR-body concern.
12. If during implementation you spot something not in the task's
Findings: stop, surface it, ask whether to (a) add to this PR, (b)
defer as a follow-up, or (c) update the Linear task as a comment.
**Phase 4 — Tests** (same commit as Phase 3 usually).
13. Add `all-generators-enforce-floor.spec.ts` — parameterized via
`assertGeneratorsEnforceVersionFloor`. This exercises every
generator's floor assert and is the high-value spec.
14. Footgun: assert calls must be in place in every generator BEFORE
running the parameterized spec, or every untouched generator fails
and you'll restart.
15. Optional: a per-plugin `assert-supported-<pkg>-version.spec.ts`
with the 5 canonical cases. The shared `assertSupportedPackageVersion`
already has full coverage in devkit, so this is mostly symmetry
across the PR series — skip unless the user asks.
**Phase 5 — Verify locally.**
16. `npx nx test <plugin> --testPathPattern="all-generators-enforce-floor"`
(add `assert-supported-` if you added the optional wrapper spec).
17. `npx nx test <plugin> --testPathPattern="<modified-generator>"` per
touched generator.
18. `npx nx format`.
**Phase 6 — Hand off.** Code changes complete. The user drives
commit/push/PR per their own conventions (loaded globally from
`~/.claude/memory/workflow/git/`). This skill does not enforce PR title,
body, commit shape, or related-issues format.
### Review mode
1. **Fetch PR.** `gh pr view <N> --repo nrwl/nx` and
`gh pr diff <N> --repo nrwl/nx`. For a local branch:
`git diff master...HEAD`.
2. **Derive the Linear task.** Branch name `nxc-NNNN``NXC-NNNN`. If
no match: ask the user.
3. **Fetch the task** (if derivable). Compare diff vs. task Findings:
every Finding addressed; nothing extra without justification. Flag
scope drift.
**If no task and the user has none:** skip task-comparison; run pure
code-level review against `canonical-shape.md` and `anti-patterns.md`.
4. **Code-level checks.** Run the "Code-level verification (review-mode
lens)" section of `canonical-shape.md`. Cross-reference
`anti-patterns.md`. For each finding, anchor at `file:line` and cite
which reference PR / file demonstrates the correct pattern.
**Scope:** code, configs, migrations, and in-codebase docs that claim
runtime behavior. NOT PR title / body / commit shape — those defer to
the user's PR conventions.
5. **Classify each finding.**
- **Only two inline categories:** `[blocker]` and `[non-blocker]`. No
"open question," "ask," or other inline tags. Questions for the
author surface in the closing "Open questions for author" block,
drawn from non-blocker findings — list each question once.
- **Severity is independent of scope-drift.** A finding can be both a
blocker AND not in the Linear task. Flag it as a blocker in the
code-level section AND list it under "in PR but not in Linear task"
in scope drift. Don't hedge with "in this PR or follow-up?" — if
it's a blocker, the answer is "this PR."
- **Group related non-blockers.** When multiple non-blockers describe
symptoms of one blocker (e.g., five symptoms of a single
`version-utils.ts` duplication), list them as sub-bullets under
the blocker with "(resolved when §X is fixed)" rather than as N
separate top-level non-blockers.
- **Be terse on passes.** A section with no findings gets a single
summary line ("Pass — all 7 generator entries assert at first
statement"), not a per-file enumeration. Detail is reserved for
blockers and non-blockers. The reviewer's audience skims for
actionable items; passing checks should not eat reading budget.
6. **Output.** Markdown checklist of blockers / non-blockers anchored at
`file:line`, followed by the structured verdict block from
`canonical-shape.md` §"Verdict template". The verdict block is the
skimmable index — produce it, don't substitute a free-form prose
summary. Do not post via `gh pr review` unless the user explicitly
asks.
## Which references to load (context hygiene)
| Mode | Required | Optional |
| ---------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| Fix | `canonical-shape.md`, `anti-patterns.md` | `gotchas.md` (effective floor, ecosystem lockstep, cypress inline tree), `examples.md` (when copying a pattern) |
| Review | `anti-patterns.md`, `canonical-shape.md` (especially the "Code-level verification" section) | `gotchas.md` (cross-plugin coordination, lockstep), `examples.md` (when citing) |
| "What is compliance?" answer | none | answer from SKILL.md alone |
References are ~100500 lines each. Don't pull all of them just because
you're invoked. Match the load to the mode.
## Critical rules (apply in every mode)
1. **Linear task is the source of truth for scope** (ratified decisions
and Findings).
- (a) Don't produce a parallel scope document. The task IS the scope.
Fix mode runs against the task as input — drift checks, new
findings, and decisions feed back to the task (via comments or as
deferred items), not into a competing source of truth.
- (b) Don't expand a fix beyond the task's Findings without
surfacing the new issue.
- (c) Don't second-guess the task's resolved support window without
an explicit user override.
2. **Do not create or duplicate shared helpers.** They live in
`@nx/devkit/internal` (`assertSupportedPackageVersion`,
`getInstalledPackageVersion`, `getDeclaredPackageVersion`,
`throwForUnsupportedVersion`, `normalizeSemver`, `isNonSemverDistTag`)
and `@nx/devkit/internal-testing-utils`
(`assertGeneratorsEnforceVersionFloor`). Reject any local
re-implementation (`cleanVersion`, `getInstalled<Pkg>VersionRuntime`,
private `throwBelowFloor`, etc.). See `canonical-shape.md`.
3. **Above-ceiling is silent fallthrough.** Do not warn, do not throw,
do not branch. Reject `throwAboveWindow`, `warnAboveCeiling`,
`versions()` with `switch + throw default:`. The only throw is below
the declared floor.
4. **`keepExistingVersions: true` is for generators only.** Migration
generators (`src/migrations/`) are exempt — their job is to bump.
Do not flag missing flags in migration code.
5. **Floor assert is the first statement in the function doing the
actual work.** Wrapper/internal split (cypress, playwright): in
`*Internal`. Single-function generators (angular): in the function
itself. Not conditional, not inside an install branch, not after a
tree read.
6. **Phase 12 never writes, never branches.** Discovery, finding
classification, and decision-surfacing happen on the current branch
with no edits. Any working artifact (e.g., a findings doc for a
no-task case, multi-plugin scratch notes) goes in `tmp/` (gitignored)
and stays uncommitted. No `TRIAGE-REPORT.md` / `AUDIT.md` at repo
root. Branch creation and edits start at Phase 3, after the user OK.
7. **PR / commit conventions are out of scope.** Title format, body shape,
commit-message structure, related-issues handling, push flags, etc.
are governed by the user's global memory (`pr-creation-shorthand.md`,
`push-conventions.md`, `explain-before-committing.md`,
`chore-not-fix-non-prod.md`). Don't enforce or flag these from this
skill — defer to whatever the user's conventions resolve to at PR time.
## Findings doc template (Phase 2 output, used when no Linear task exists)
When fix mode hits the no-task case (Phase 1 step 2), produce
`tmp/<plugin>-findings.md` shaped to mirror a Linear task body so the
user can file it as a new task in milestone NXC-4072 before proceeding
to Phase 3.
For plugins managing multiple primary packages, repeat the install-map
/ decisions / findings bullets per primary.
```md
# @nx/<plugin> — multi-version support compliance findings
> No Linear task in milestone NXC-4072. This doc is filing-ready —
> create the task with this body before proceeding to fix.
## Plugin
- Path: packages/<plugin>
- Upstream support: <official policy if any, else "no formal LTS">
- peerDep declarations: <list>
- Per-major install (`<file>` branches on installed `<package>` major):
- v<N-1>: <constants>
- v<N>: <constants> (default)
- Paired secondaries: <list of ecosystem-locked siblings>
## Needs human decision
1. <decision 1 — e.g., raise floor to vN.0.0 vs keep current>
2. <decision 2 — e.g., drop ^1.0.0 from peer (no v1 install lane)>
## Findings
- **(high) <one-line summary>** [file:line]
_Suggested fix_: <one-line>
- **(medium) ...**
- **(low) ...**
## Verification checklist (AF)
### A. Support window declarations
- [ ] peerDep ranges match the support window
- [ ] Version map / runtime branching covers every supported major
- [ ] Every third-party package the plugin **invokes at runtime** has a peerDep entry. "Invokes" = TS import/`require` OR executor spawns its CLI binary OR inferred plugin emits a target whose `command` invokes its CLI (look for `externalDependencies: ['<pkg>']` in emitted target inputs). Packages the generator installs for the user to consume independently (ESLint plugins loaded by the user's eslintrc, `@types/*`) don't need peer-declaration.
- [ ] Peers needed only when a user opts into a specific surface (executor opt-in, inferred plugin gated on config file presence, opt-in preset) are declared **optional** via `peerDependenciesMeta: { "<pkg>": { "optional": true } }`. Required-non-optional peers are reserved for packages every workspace using the plugin needs.
### B. Generator inputs
- [ ] Generators don't overwrite installed third-party versions
- [ ] `addDependenciesToPackageJson` passes `keepExistingVersions=true` or branches on detected version
- [ ] Fresh-install path installs the latest supported version
### C. Generator outputs
- [ ] Templates compile and run on every supported version
- [ ] Generated `project.json` target shape valid on every major
- [ ] Default option values valid on every major
- [ ] Version map covers every managed third-party dep — no gaps
- [ ] Schema accepts union of options; runtime throws when inapplicable
### D. Migrations (migrations.json + packageJsonUpdates)
- [ ] Cross-major `packageJsonUpdates` declare `requires` per bumped package
- [ ] `requires` ranges are bilateral (`>=N <M`) by default. One-sided ranges (`<N` with no lower, `>=N` with no upper) are intentional (legacy cleanup, undefined source major, v0→v1 bridge) — flagged in "Needs human decision" or noted in the Findings.
- [ ] Every migration declares `requires` against the touched package
- [ ] Nx-only migrations have no third-party `requires`
- [ ] No silent gap in `packageJsonUpdates` across the support window
### E. Runtime
- [ ] Executors branch on installed version where behavior diverges
- [ ] Inferred plugin (createNodes/V2) parses configs across every major
### F. Out-of-window UX
- [ ] Below-floor: throws via shared util naming package + installed + floor; no silent fall-through
## Out-of-scope (deferred follow-ups)
- <e.g., consolidate ... across plugins — separate PR>
```
## References
See "Which references to load" near the top. Don't pull all of them.
@@ -0,0 +1,329 @@
# Anti-patterns
Patterns to reject in your own work and flag in reviews. Each entry: what it looks like, why it's wrong, what to do instead, and a reference.
## 1. Local re-implementation of the shared helpers
**Looks like:** A new file in the plugin defining any of:
- `throwBelowFloor` / `throwAboveWindow` / `assertVersion` / `checkMinimumVersion` — duplicates `throwForUnsupportedVersion` / `assertSupportedPackageVersion`.
- `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)`.
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
**Looks like:** `if (major > maxKnown) throw …`, `if (major > maxKnown) logger.warn …`, `versions()` with a `switch + throw default:`, any `warnAboveCeiling` / `throwAboveWindow` helper.
**Why wrong:** Explicit policy. Above-ceiling falls through silently to `latestVersions`. Throwing breaks users on newer versions of third-party packages, which is the opposite of the initiative's intent. The angular reference implementation does not warn or branch above the highest known major, and every subsequent plugin compliance PR follows that convention.
**Do instead:** `versionMap[major] ?? latestVersions`. Below-floor is caught by the generator-level assert; the `versions()` function is just a lookup.
**Reference:** Compliant — `packages/cypress/src/utils/versions.ts` after `#35670` rewrite. Anti-pattern (before fix) — same file before `#35670` had `switch + throw default:`.
## 3. Hardcoded third-party version in generator body
**Looks like:**
```ts
addDependenciesToPackageJson(tree, {}, { rspack: '^1.1.10' });
```
in a generator.
**Why wrong:** Bypasses the `versions(tree)` routing and the install-lane logic. New majors will not be picked up; older workspaces get the wrong version.
**Do instead:** Route through `versions(tree)` and reference the per-major entry. If you genuinely have a version that's the same across all majors, still put it in the map for consistency.
## 4. Init generator overwriting pinned versions
**Looks like:** `addDependenciesToPackageJson(tree, …, …, undefined, options.keepExistingVersions)` where the schema default is `false`. Or no fifth argument at all (defaults to `false`).
**Known-incomplete reference:** `@nx/angular`'s `init/schema.json` currently has `default: false` and `init.ts` passes `options.keepExistingVersions` directly — PR `#35587` did not fix this. The angular init generator therefore still has this bug. Flagging it in a non-angular compliance PR is correct; fixing it in passing during another angular PR is also appropriate.
**Why wrong:** Generators bump packages = the user's pinned version is silently overwritten on re-run. Bumping is the job of migrations, not generators.
**Do instead:** Pass `keepExistingVersions: true` (positional 5th arg) or `options.keepExistingVersions ?? true`. Flip the schema default to `true`.
**Reference:** Compliant — `packages/cypress/src/generators/init/init.ts` and `init/schema.json` after `#35670`. Anti-pattern — the same files before `#35670` had schema default `false`.
## 5. `requires` gate on an Nx-only migration
**Looks like:**
```json
{
"update-unit-test-runner-option": {
"requires": { "@angular/core": ">=21.0.0" },
"description": "Update 'vitest' unit test runner option to 'vitest-analog' in generator defaults."
}
}
```
when the migration only writes to `nx.json`.
**Why wrong:** The migration applies regardless of third-party version — it's rewriting an Nx-owned generator default. The gate causes pre-v21 workspaces with the stale default to silently skip the migration and stay broken.
**Do instead:** Remove the `requires` entry entirely. Nx-only migrations have no third-party gate.
**Reference:** Anti-pattern (before fix) — `packages/angular/migrations.json` `update-unit-test-runner-option`. Fix — `#35587` removed the gate.
## 6. Cross-major `packageJsonUpdates` with no `requires`
**Looks like:**
```json
{
"21.3.0": {
"packages": {
"jest": { "version": "^30.0.0" }
}
}
}
```
with no `requires` gate, when this is a v29 → v30 bump.
**Why wrong:** The bump fires for every workspace — including workspaces already on v30 (idempotent best case) or workspaces on v28 or below (which would silently land on v30 without going through any v29 → v30 codemods). Source-major gate ensures the bump only fires for workspaces actually in the source range.
**Do instead:**
```json
{
"21.3.0": {
"requires": { "jest": ">=29.0.0 <30.0.0" },
"packages": { "jest": { "version": "^30.0.0" } }
}
}
```
**Reference:** Compliant — `packages/angular/migrations.json` MF entries after `#35587`. Anti-pattern examples on master at time of writing — `@nx/jest` `21.3.0`, `@nx/eslint` `20.7.0`, `@nx/vite` `20.5.0` (verify by inspecting each plugin's `migrations.json` for cross-major `packageJsonUpdates` entries lacking `requires`).
## 7. Gating ecosystem-locked siblings on the primary's major alone
**Looks like:** A migration that bumps `@ngrx/store` from v18 to v19 with `requires: { "@angular/core": ">=19.0.0" }` only — no `@ngrx/store` entry.
**Why wrong:** `@ngrx/store` is independent of `@angular/core` versioning. A workspace can be on `@angular/core: 19` without having `@ngrx/store: 18` (might not use ngrx at all, or might be on v17). Gating on `@angular/core` fires the migration in workspaces where it has nothing to do.
**Do instead:** Add the sibling to `requires`: `{ "@angular/core": ">=19.0.0", "@ngrx/store": ">=18.0.0 <19.0.0" }`. For Angular ecosystem siblings: `@angular/cli`, `@angular/ssr`, `@angular-devkit/build-angular` (v20+) are peer-locked via `@angular/core` and don't need their own gate. `@ngrx/*`, `@angular-eslint/*`, `zone.js`, `jest-preset-angular` are independent and do.
**How to verify pairing:** read the sibling package's `peerDependencies` at the version range being bumped from. If it pins the primary's major, the primary's `requires` covers it. If it doesn't, the sibling is independent and needs its own gate.
## 8. Peer dep claiming a major with no install branch (phantom claim)
**Looks like:**
```json
{
"peerDependencies": {
"vitest": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0"
}
}
```
when `versions.ts` has no v1 entry and no `isVitestV1` branch anywhere.
**Why wrong:** The plugin advertises support for a version it doesn't honor. v1 workspaces silently fall through to v4 install constants.
**Do instead:** Drop the unsupported major from the peer. `vitest: "^2.0.0 || ^3.0.0 || ^4.0.0"`. If the support is desired, add the install lane.
**Reference:** Pattern demonstrated in open PR `#35671` (`@nx/vitest`) — drops `^1.0.0` from the `vitest` peer because there's no v1 install lane in the plugin's `versions.ts`. Inspect via `gh pr diff 35671 --repo nrwl/nx -- packages/vitest/package.json`. Verify state first.
**Related — drop EOL major (different reasoning, same action):** the major HAS an install lane but is EOL upstream (e.g., Storybook's official policy is "top 3 majors only"; v7 is EOL). Drop it from the peer because it's upstream-unsupported, not because the plugin doesn't honor it. Concrete example: NXC-4406 calls out dropping Storybook v7 from `@nx/storybook`'s peer per Storybook's top-3-majors policy.
## 8a. PeerDep range wider than the runtime dep pin
**Looks like:**
```json
{
"peerDependencies": {
"@typescript-eslint/parser": "^6.0.0 || ^7.0.0 || ^8.0.0"
},
"dependencies": {
"@typescript-eslint/parser": "^8.0.0"
}
}
```
The plugin's own runtime dep pins ^8, but the peer claims ^6/^7/^8.
**Why wrong:** Distinct from §8 — here the install lane exists (in `dependencies`), but the lane only ships one major. The peer is over-promising relative to what the plugin actually runs against. A workspace on ^6 will satisfy the peer but won't get a compatible runtime once `@typescript-eslint/parser@^8` resolves.
**Do instead:** Tighten the peer to the actually-supported runtime range, or widen the runtime dep + add the install/branch lanes for the additional majors.
**Reference:** NXC-4388 (`@nx/eslint-plugin`).
## 9. Top-level `require()` of an optional peer in an executor
**Looks like:**
```ts
// at the top of executor.impl.ts
const cypress = require('cypress');
```
**Why wrong:** When cypress is absent (not yet installed, peer mismatch, etc.), the executor throws `MODULE_NOT_FOUND` at module load time, before any user-friendly error. Especially bad for deprecated executors that should fail with a deprecation message.
**Do instead:** `require` inside the function body, after the version detection / clear error.
## 10. Anything-but-`requires` as substitute for `requires`
**Looks like (variant A — `incompatibleWith` standing in):**
```json
{
"21.0.0-source-bump": {
"incompatibleWith": { "@angular-devkit/build-angular": "<21.0.0" },
"packages": { "...": { "version": "..." } }
}
}
```
to "gate" a bump to v21+ source workspaces.
**Looks like (variant B — runtime per-package guard):**
```ts
// inside the migration function body
const installed = getInstalledVersion('@typescript-eslint/parser');
if (gte(installed, '8.0.0') && lt(installed, '8.13.0')) {
// run the migration
}
return; // otherwise skip
```
with no `requires` block on the migration entry in `migrations.json`.
**Why wrong:** Neither approach is a source-major gate.
- `incompatibleWith` blocks running on workspaces that have the matching version — it doesn't gate to a source-major range. A workspace on `@angular-devkit/build-angular: 22.0.0` will still pass the `incompatibleWith` check.
- A runtime per-package guard runs the migration _body_ on every workspace and skips internally. The migration record still appears as "executed" to the migrate runner, and any side effects (logging, partial work) leak. The `nx migrate` runner uses `requires` as the source-major filter; bypassing it means the migration isn't filtered at the right layer.
**Do instead:** `requires: { "<pkg>": ">=N.0.0 <(N+1).0.0" }` — the actual source-major gate at the migration-entry level. Drop the in-body guard once the `requires` is in place.
**Reference:** Anti-pattern (variant B) — `@nx/eslint` `update-typescript-eslint-v8.13.0` (NXC-4387) has runtime `gte('8.0.0') + lt('8.13.0')` per-package guards but no `requires` block. `@nx/jest` similar with `incompatibleWith` (NXC-4391).
## 11. Naming a specific plugin in shared helper docstrings
**Looks like:** A JSDoc in `assert-generators-enforce-version-floor.ts` referencing `migrate-to-cypress-11` as the example use case for `excludeGenerators`.
**Why wrong:** The helper is shared across plugins. Naming one plugin in its docstring is leaky.
**Do instead:** Generic phrasing — "generators that must run below the floor by design (e.g., migrators that lift sub-floor workspaces onto a supported version)".
**Reference:** an early draft of `#35670`'s test helper had the plugin-specific JSDoc; the merged version uses generic phrasing.
## 12. Both schema `"default": true` AND `options.keepExistingVersions ?? true`
**Looks like:**
```json
{ "keepExistingVersions": { "default": true } }
```
combined with
```ts
addDependenciesToPackageJson(tree, , , undefined, options.keepExistingVersions ?? true);
```
**Why wrong:** Two sources of truth. Either the schema default does the job (and `options.keepExistingVersions` will always be `true`) or the `?? true` fallback handles it (and the schema default is redundant).
**Do instead:** Pick one. Schema default is sufficient when the call site uses `options.keepExistingVersions` directly. The `?? true` fallback is only needed if the schema can be bypassed (programmatic invocation without schema validation).
## 13. Manual `RegExp` matching in tests instead of substring `toThrow`
**Looks like:**
```ts
.rejects.toThrow(new RegExp(`Unsupported version of \\\`${packageName}\\\` detected`));
```
**Why wrong:** Escape bugs. The backtick and the `${}` are easy to get wrong. The shared helper uses substring matching for a reason.
**Do instead:**
```ts
.rejects.toThrow(`Unsupported version of \`${packageName}\` detected`);
```
**Reference:** see how `assertGeneratorsEnforceVersionFloor` itself does the match in `packages/nx/src/internal-testing-utils/assert-generators-enforce-version-floor.ts` (grep for `Unsupported version of`).
## 14. Validating only at install sites instead of generator entry
**Looks like:** A `if (installedVersion < floor) throw …` check guarding only the `addDependenciesToPackageJson` call inside a generator, while the rest of the generator runs unconditionally.
**Why wrong:** The generator may write configuration or templates incompatible with the sub-floor third-party version before reaching the install branch. The assert must be at the entry point so nothing else runs.
**Do instead:** `assertSupportedXVersion(tree)` as the first statement in the generator's working function (`*Internal` for plugins with the wrapper/internal split; the function itself for single-function generators). The install branch can then assume the floor is met.
## 15. Per-major version aliases alongside the bundle map
**Looks like:**
```ts
export const vitestV4Version = '~4.1.0';
export const vitestV3Version = '^3.0.0';
export const vitestV2Version = '^2.1.8';
export const vitestVersion = vitestV4Version;
export const vitestV4CoverageV8Version = '~4.1.0';
export const vitestV3CoverageV8Version = '^3.0.5';
// ...etc
const versionMap = {
3: { vitestVersion: '^3.0.0', vitestCoverageV8Version: '^3.0.5' },
4: { vitestVersion: '~4.1.0', vitestCoverageV8Version: '~4.1.0' },
};
```
**Why wrong:** The aliases (`vitestV3Version`, `vitestV3CoverageV8Version`, etc.) duplicate the `versionMap` entries. They drift over time — someone bumps the map but forgets the alias (or vice versa), and the plugin starts installing one version via generators and another via tests/runtime. Also: every dropped major (e.g., when raising the floor) becomes three or four delete lines instead of one map entry.
**Do instead:** Keep the bundle pattern — the per-major `versionMap` is the only place those values live. Stable (cross-version-identical) deps stay as top-level `export const`s; varying deps are accessed via `versions(tree).<key>` or directly from the top-level `latestVersions` bundle.
**Reference:** Open PR `#35671` initially carried `vitestV2Version` / `vitestV3Version` / `vitestV4Version` aliases. A follow-up commit (`chore(testing): adopt cypress version-resolution pattern in @nx/vitest`) dropped them in favor of the bundle pattern. Inspect via `gh pr view 35671 --repo nrwl/nx --json commits`.
## 16. Declared floor below the effective floor
**Looks like:** `peerDependencies` lists `"vitest": "^2.0.0 || ^3.0.0 || ^4.0.0"` and `versions.ts` has a `versionMap` entry for `2`, but somewhere in the plugin's executor / runtime / plugin code there's an import of a third-party API that only exists in v3+:
```ts
// In a runtime helper used by the executor:
import { getRelevantTestSpecifications } from 'vitest/node';
// ^ This API only exists in vitest >= 3.0.0.
```
**Why wrong:** A workspace on v2 will pass the floor assert (peer + versionMap claim support), then crash at runtime with `getRelevantTestSpecifications is not a function`. The peer is lying.
**Do instead:** Raise the floor to the lowest major where every called third-party API exists. Drop the now-unsupported entries from `versionMap`, `peerDependencies`, and the per-major version aliases (if any). The `assert-supported-<pkg>-version.spec.ts` sub-floor test now covers the dropped major.
**Reference:** Open PR `#35671`'s second commit (`fix(testing): drop vitest v2 support from @nx/vitest`) — originally proposed a v2 floor matching the lowest install lane, then raised to v3 after audit caught the `getRelevantTestSpecifications` usage. Inspect via `gh pr view 35671 --repo nrwl/nx --json commits`.
## 17. Creating a branch during Phase 12 (discovery / read-only)
**Looks like:** `git checkout -b <some-branch>` before the user has approved Phase 3 edits.
**Why wrong:** Phase 12 produces findings, not commits. Creating a branch creates pressure to commit something. Any working artifact (e.g., `tmp/<plugin>-findings.md` for the no-task case) goes in `tmp/` (gitignored) — for the user to read and scope from, not to commit.
**Do instead:** Run Phase 12 on the current branch (typically `master`). Output to `tmp/<plugin>-findings.md` if you wrote one. Branch creation belongs in Phase 3, after explicit user approval to proceed with edits.
@@ -0,0 +1,655 @@
# Canonical shape
What a compliant plugin looks like. Don't deviate without a documented reason.
The first half of this file ("How to write") describes the canonical structure
you produce in fix mode. The tail section ("Code-level verification") is the
review-mode lens — markers to look for in a diff.
## Shared helpers (already merged — use, don't duplicate)
### `@nx/devkit/internal`
Source: `packages/devkit/src/utils/version-floor.ts` and `packages/devkit/src/utils/installed-version.ts`.
```ts
// version-floor.ts
function throwForUnsupportedVersion(
packageName: string,
installedVersion: string,
floor: string
): never;
function assertSupportedPackageVersion(
tree: Tree,
packageName: string,
minSupportedVersion: string
): void;
```
```ts
// installed-version.ts
function getInstalledPackageVersion(packageName: string): string | null;
function getDeclaredPackageVersion(
tree: Tree,
packageName: string,
latestKnownVersion?: string
): string | null;
const NON_SEMVER_DIST_TAGS = ['latest', 'next'] as const;
function isNonSemverDistTag(version: string): version is NonSemverDistTag;
function normalizeSemver(version: string): string | null;
```
When to use which:
| Context | Function |
| -------------------------------- | ----------------------------------------------------------------------------------- |
| Generator entry (assert floor) | `assertSupportedPackageVersion(tree, pkg, floor)` |
| Generator-time version branching | `getDeclaredPackageVersion(tree, pkg, latestKnownVersion)` |
| Executor / runtime / preset | `getInstalledPackageVersion(pkg)` |
| Anywhere | `isNonSemverDistTag`, `normalizeSemver` |
| Never | `throwForUnsupportedVersion` directly — it's an implementation detail of the assert |
### `@nx/devkit/internal-testing-utils`
Source: `packages/nx/src/internal-testing-utils/assert-generators-enforce-version-floor.ts`.
```ts
function assertGeneratorsEnforceVersionFloor(options: {
packageRoot: string;
packageName: string;
subFloorVersion: string;
excludeGenerators?: string[];
}): void;
```
Behavior: reads `generators.json` from `packageRoot`, iterates every entry, loads its factory, calls it against a tree with `{ [packageName]: subFloorVersion }` in `package.json`, expects a throw matching `Unsupported version of \`${packageName}\` detected`.
`excludeGenerators` is only for intentional sub-floor migrators (e.g., `migrate-to-cypress-11`). Comment the reason next to the array.
## Finding the existing floor (audit input)
When auditing a plugin you haven't touched before, the floor may not be declared in one place. Check, in order of authority:
1. **`minSupported<Pkg>Version` constant in `versions.ts`** — if it exists, that's the declared floor.
2. **`peerDependencies` lowest range in `package.json`** — what the plugin advertises supporting.
3. **Lowest major in `versionMap` / `backwardCompatibleVersions` / `supportedVersions`** — what the plugin has install lanes for.
4. **Lowest `packageJsonUpdates` entry that touches the third-party package** — historical evidence of the supported range.
5. **Highest API requirement in the plugin's own code (the _effective_ floor).** Grep for every `import` / `require` from the third-party package and identify which APIs are called. Cross-reference each against the third-party's changelog. The plugin's effective floor is the lowest major where **all** called APIs exist. **This trumps the declared floor** — if `versions.ts` claims v2 but the plugin imports an API only available in v3+, the declared floor is wrong.
These should agree. When they don't, the disagreement is the finding (phantom peer claim, drifted versionMap, declared floor below effective floor).
**Worked example:** During open PR `#35671`, the audit initially landed on a `v2.0.0` floor (matching the lowest install lane). Then a follow-up commit dropped the floor to `v3.0.0` after noticing the plugin's atomization code calls `getRelevantTestSpecifications`, which is a vitest v3+ API. Lesson: step 5 above is not optional. Always check what APIs the plugin's own runtime code uses — `versions()` having a v2 lane doesn't mean the plugin actually works on v2.
## The plugin wrapper (one per plugin)
Path: `packages/<plugin>/src/utils/assert-supported-<pkg>-version.ts`.
Two canonical shapes:
### Single-major floor (most plugins)
```ts
import { type Tree } from '@nx/devkit';
import { assertSupportedPackageVersion } from '@nx/devkit/internal';
import { minSupportedCypressVersion } from './versions';
export function assertSupportedCypressVersion(tree: Tree): void {
assertSupportedPackageVersion(tree, 'cypress', minSupportedCypressVersion);
}
```
Reference: `packages/cypress/src/utils/assert-supported-cypress-version.ts`, `packages/playwright/src/utils/assert-supported-playwright-version.ts`.
### Floor derived from supported-versions list (angular)
```ts
import { type Tree } from '@nx/devkit';
import { assertSupportedPackageVersion } from '@nx/devkit/internal';
import { supportedVersions } from './backward-compatible-versions';
const minSupportedAngularMajor = Math.min(...supportedVersions);
export function assertSupportedAngularVersion(tree: Tree): void {
assertSupportedPackageVersion(
tree,
'@angular/core',
`${minSupportedAngularMajor}.0.0`
);
}
```
Reference: `packages/angular/src/utils/assert-supported-angular-version.ts`.
Pick the shape that matches whether the plugin already has a `supportedVersions` list (angular does; cypress/playwright/vitest don't).
### Plugins managing multiple primary packages
`@nx/jest` manages `jest`, `ts-jest`, `@types/jest`. `@nx/eslint` manages `eslint`, `@typescript-eslint/parser`, `@typescript-eslint/eslint-plugin`, `eslint-config-prettier`. The canonical wrapper signature takes one package; with multiple, decisions are needed:
- **Gate on the primary only** when the others are peer-locked (e.g., angular's strategy with `@angular/core` covering `@angular/cli`, `@angular/ssr`, etc.). This is sufficient when the siblings' peer-deps tie them to the primary's major.
- **Gate on each independently** when the siblings can be installed at any major regardless of the primary (typescript-eslint pair vs eslint; ts-jest vs jest). In that case, the wrapper makes multiple `assertSupportedPackageVersion` calls in sequence:
```ts
export function assertSupportedJestVersion(tree: Tree): void {
assertSupportedPackageVersion(tree, 'jest', minSupportedJestVersion);
// ts-jest is independent — declare and assert it separately.
assertSupportedPackageVersion(tree, 'ts-jest', minSupportedTsJestVersion);
}
```
When in doubt: read each sibling's `peerDependencies` block at the version range being supported. If it pins the primary, it's covered by the primary's gate. If it doesn't (or pins something else), it needs its own.
## Skip writing the install constant when the package is already detected
Init generators that add the third-party package to `package.json` should NOT overwrite an already-installed minor/patch. The `keepExistingVersions: true` flag handles this at the `addDependenciesToPackageJson` level. But for code paths that compute the version to write (e.g., picking the major-specific value from `versionMap`), the rule is the same: read what's installed first; only write the fresh-install constant when nothing is detected.
Reference: `packages/cypress/src/generators/init/init.ts` `updateDependencies` — calls `getInstalledCypressVersion(tree)` first, then routes through `versions(tree)` which short-circuits to existing-version paths. The `keepExistingVersions ?? true` flag at the `addDependenciesToPackageJson` call site is the final safety net.
## The versions module
Path: `packages/<plugin>/src/utils/versions.ts`.
### Required exports
```ts
// Plain string, no caret. Used as the floor for assertSupportedPackageVersion.
export const minSupportedCypressVersion = '13.0.0';
// Fresh-install constants — may be HIGHER than minSupported when the feature
// surface at the floor is incomplete. Playwright peer is ^1.36.0 but
// fresh-install is ^1.37.0 so blob reporter + merge-reports work out of the
// box.
export const playwrightVersion = '^1.37.0';
// Optional: feature-gate thresholds for runtime/executor code.
export const minPlaywrightVersionForBlobReports = '1.37.0';
```
### Stable deps stay top-level; per-major-varying deps go in the bundle
A plugin typically manages one primary package whose version map drives several siblings. Deps that vary per major go into a typed bundle; deps that are version-stable stay as plain `export const`s.
```ts
// Stable across all supported majors of the primary → plain exports.
export const viteVersion = '^8.0.0';
export const jsdomVersion = '^27.1.0';
export const vitePluginReactVersion = '^6.0.0';
// Vary with the primary's major → bundle.
export const vitestVersion = '~4.1.0';
export const vitestCoverageV8Version = '~4.1.0';
export const vitestCoverageIstanbulVersion = '~4.1.0';
type VitestVersions = {
vitestVersion: string;
vitestCoverageV8Version: string;
vitestCoverageIstanbulVersion: string;
};
// latestVersions reuses the top-level exports so import { vitestVersion }
// stays valid for the fresh-install path while versions(tree).vitestVersion
// is the route-aware version.
const latestVersions: VitestVersions = {
vitestVersion,
vitestCoverageV8Version,
vitestCoverageIstanbulVersion,
};
type CompatVersions = 3;
const versionMap: Record<CompatVersions, VitestVersions> = {
3: {
vitestVersion: '^3.0.0',
vitestCoverageV8Version: '^3.0.5',
vitestCoverageIstanbulVersion: '^3.0.5',
},
};
```
**Do not** keep per-major aliases like `vitestV3Version = '^3.0.0'` alongside the bundle — they duplicate the map entries and drift over time. See `anti-patterns.md` §15.
### The `versions(tree)` function
Falls through to latest on unknown majors — no `switch + throw default:`.
```ts
export function versions(tree: Tree): VitestVersions {
const installedVitestVersion = getInstalledVitestVersion(tree);
if (!installedVitestVersion) {
return latestVersions;
}
const vitestMajorVersion = major(installedVitestVersion);
return versionMap[vitestMajorVersion as CompatVersions] ?? latestVersions;
}
```
### The `getInstalled<Pkg>Version(tree?)` helper
Optional `tree` parameter — with tree, reads declared from `package.json` via `getDeclaredPackageVersion` (handles dist-tag normalization, semver cleaning); without tree, routes through `getInstalledPackageVersion` from `@nx/devkit/internal` (FS resolution via `getNxRequirePaths()`).
**Do not open-code the tree-branch.** `getDeclaredPackageVersion` already centralizes the dist-tag list (`isNonSemverDistTag`) and the `clean(v) ?? coerce(v)?.version ?? null` chain (`normalizeSemver`). Local re-implementations drift when devkit's constants change. See `anti-patterns.md` §1.
```ts
import { type Tree } from '@nx/devkit';
import {
getDeclaredPackageVersion,
getInstalledPackageVersion,
} from '@nx/devkit/internal';
import { major } from 'semver';
export function getInstalledVitestVersion(tree?: Tree): string | null {
if (!tree) {
return getInstalledPackageVersion('vitest');
}
return getDeclaredPackageVersion(tree, 'vitest');
}
export function getInstalledVitestMajorVersion(tree?: Tree): number | null {
const installedVitestVersion = getInstalledVitestVersion(tree);
return installedVitestVersion ? major(installedVitestVersion) : null;
}
```
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
The assert goes in the function that does the actual work — first statement of the function body, before any other tree access or sub-generator call. (The assert itself reads the tree, of course; the rule is that nothing else in the generator runs against an unsupported version.)
### Plugins with a `<gen>` / `<gen>Internal` split (cypress, playwright)
The public wrapper merges defaults and delegates. Assert lives in `*Internal`:
```ts
// Public wrapper — no assert, just default merging.
export async function cypressInitGenerator(tree: Tree, options: Schema) {
return cypressInitGeneratorInternal(tree, { addPlugin: false, ...options });
}
// Working function — assert is the first statement.
export async function cypressInitGeneratorInternal(
tree: Tree,
options: Schema
) {
assertSupportedCypressVersion(tree);
updateProductionFileset(tree);
// ...
}
```
Reference: `packages/cypress/src/generators/init/init.ts`, `packages/playwright/src/generators/init/init.ts`.
### Plugins with a single-function generator (angular)
No wrapper/internal split. The function itself asserts:
```ts
export async function angularInitGenerator(tree: Tree, options: Schema) {
assertSupportedAngularVersion(tree);
// ...
}
```
Reference: `packages/angular/src/generators/init/init.ts`.
### Double-asserts are established convention, not an edge case
When `configurationGenerator` calls `initGenerator` internally, both call their respective `assertSupportedXVersion`. This is the angular precedent (29 `generators.json` entries → 58 assert call sites). The assert is idempotent (one tree read + one semver comparison) and the parameterized floor spec treats every entry point as independent — both must throw on sub-floor. Don't refactor away.
## User-pin preservation
### `addDependenciesToPackageJson` call sites
Every call from a generator (NOT a migration) must pass `keepExistingVersions: true` as the fifth positional argument or via the `?? true` pattern.
```ts
// Pattern A — explicit at the call site
addDependenciesToPackageJson(
tree,
{},
{ 'eslint-plugin-cypress': pkgVersions.eslintPluginCypressVersion },
undefined,
true
);
// Pattern B — driven by schema (init generators only)
addDependenciesToPackageJson(
tree,
{},
devDependencies,
undefined,
options.keepExistingVersions ?? true
);
```
Reference: `packages/cypress/src/generators/init/init.ts`, `packages/cypress/src/utils/add-linter.ts`, `packages/angular/src/generators/add-linting/lib/add-angular-eslint-dependencies.ts`.
### init schema
```json
{
"keepExistingVersions": {
"type": "boolean",
"x-priority": "internal",
"description": "Keep existing dependencies versions",
"default": true
}
}
```
Reference (on master): `packages/cypress/src/generators/init/schema.json`, `packages/playwright/src/generators/init/schema.json`. (`@nx/vitest` follows the same pattern in its open PR — verify via `gh pr diff 35671`.)
## Migrations.json gates
Three categories of migration:
| Category | Touches | `requires` |
| -------------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------- |
| Nx-only | `nx.json`, executor options, generator defaults | none |
| Codemod | source files / config tied to a third-party major | `{ "<pkg>": ">=N <N+1" }` (or open upper bound for legacy-cleanup codemods) |
| `packageJsonUpdates` cross-major | bumps `<pkg>` from major N to N+1 | `{ "<pkg>": ">=N.0.0 <(N+1).0.0" }` (source-major gate) |
| `packageJsonUpdates` same-major | bumps minor/patch | none required |
### Sibling packages
Ecosystem-locked siblings whose peer-on-the-primary covers them: no separate `requires`. Examples in Angular: `@angular/cli`, `@angular/ssr`, `@angular-devkit/build-angular` (from v20+, NOT v19).
Independent siblings: each needs its own `requires` entry. Examples in Angular: `@ngrx/*`, `@angular-eslint/*`, `zone.js`, `jest-preset-angular`.
### Reference examples
- `packages/angular/migrations.json` `20.2.0-module-federation`, `22.2.0`, `22.6.0-module-federation` — Module Federation entries gating on `@module-federation/enhanced` source range. Added in `#35587`.
- `@nx/vitest`'s `update-22-1-0` and `update-22-3-2` migrations gating on `vitest: ">=4.0.0"` (Vitest-4-specific AI-instructions) — pattern proposed in open PR `#35671`. Inspect via `gh pr diff 35671 --repo nrwl/nx -- packages/vitest/migrations.json`.
- `packages/angular/migrations.json` `update-unit-test-runner-option` — Nx-only migration with the over-gating `@angular/core` `requires` **removed** in `#35587`.
## Test specs
### Parameterized floor spec (one per plugin)
Path: `packages/<plugin>/src/utils/all-generators-enforce-floor.spec.ts`.
```ts
import { assertGeneratorsEnforceVersionFloor } from '@nx/devkit/internal-testing-utils';
import { join } from 'node:path';
describe('@nx/<plugin> generators enforce supported version floor', () => {
assertGeneratorsEnforceVersionFloor({
packageRoot: join(__dirname, '..', '..'),
packageName: '<pkg>',
subFloorVersion: '~<floor-minus-one>',
// Required only when a generator must run below the floor by design.
// excludeGenerators: ['migrate-to-cypress-11'],
});
});
```
Pick `subFloorVersion` such that `lt(coerce(it).version, floor)` is true. No pre-release identifiers. Reference values used in the repo: `~18.2.0` (angular, v19 floor), `~12.17.0` (cypress, v13 floor), `~1.35.0` (playwright, v1.36 floor).
### Plugin assert spec (optional, one per plugin)
Path: `packages/<plugin>/src/utils/assert-supported-<pkg>-version.spec.ts`.
`assertSupportedPackageVersion` is already fully tested in `@nx/devkit`,
so a per-plugin spec mostly re-tests the shared helper. The early
compliance PRs (`#35587` angular onward) ship one for symmetry, but it
isn't required. If you add one, five canonical cases is the shape used:
```ts
describe('assertSupportedCypressVersion', () => {
it('throws when cypress is below the supported floor');
it('does not throw when cypress is not installed (fresh-install path)');
it('does not throw when cypress is `latest`');
it('does not throw when cypress is `next`');
it('does not throw when cypress is within the supported window');
});
```
Reference: `packages/angular/src/utils/assert-supported-angular-version.spec.ts` (originally landed in `#35587`).
### Test message matching
Use substring match on the error message:
```ts
.rejects.toThrow(`Unsupported version of \`${packageName}\` detected`)
```
Not a hand-rolled RegExp (avoid escape bugs). Reference: see how the shared `assertGeneratorsEnforceVersionFloor` itself does the match — search for `Unsupported version of` in `packages/nx/src/internal-testing-utils/assert-generators-enforce-version-floor.ts`.
## Standardized error format
```
Unsupported version of `<pkg>` detected.
Installed: <declared-range-as-written-in-package.json>
Supported: >= <floor>
Update `<pkg>` to <floor> or higher.
```
Two notes:
- The `Installed:` line preserves the **original declared range** (e.g., `~18.2.0`), not the cleaned semver. `assertSupportedPackageVersion` passes `declared` through to `throwForUnsupportedVersion`.
- Do not add an "above ceiling" branch to this message. Above-ceiling is silent fallthrough.
## Peer dep alignment
### What belongs in `peerDependencies`
The test: _would the plugin still work if this package were absent from the workspace, with the plugin's code paths unchanged?_
A package is required-peer if **any** of these is true:
- The plugin's TypeScript imports / `require`s it (executor, preset, runtime helper).
- The plugin's executor spawns its CLI binary (`spawn('cypress')`, etc.).
- The plugin's inferred plugin (`createNodes`/`createNodesV2`) **emits a target whose `command` invokes the package's CLI** (e.g., `command: 'rspack build'` → `@rspack/cli` is required). The `externalDependencies: ['<pkg>']` declaration in such targets is itself an admission of the runtime dependency.
A package is **not** required-peer when:
- The plugin's generator installs it into the user's workspace for the user to consume independently, and no plugin code (TypeScript, executor binary spawn, or inferred-plugin emitted command) ever invokes it. Example: ESLint plugins written into the user's eslintrc — `@nx/cypress` installs `eslint-plugin-cypress`, but its lint executor uses generic ESLint loading; the cypress plugin is loaded by ESLint per the user's config, not by `@nx/cypress`. Example: `@types/*` packages installed for the user's TS compilation but never imported by plugin code.
**Ecosystem-signal peer** (Angular's full `@angular/*` peer list) → judgment call, not a compliance requirement. Documents lockstep compatibility but isn't enforced by the multi-version rules.
### Required vs. optional peer
Most Nx plugin peers should be **optional** (`peerDependenciesMeta: { "<pkg>": { "optional": true } }`):
- Required peer: every user of the plugin needs this package, regardless of which surface they use. Example: `@angular-devkit/core`, `rxjs` in `@nx/angular` — every Angular Nx workspace uses them.
- **Optional peer (the common case for inferred-plugin / executor surfaces):** the package is only needed when the user opts into a specific surface — an executor they have to write into `project.json`, an inferred plugin gated on the presence of a config file, a preset that auto-injects. Users who don't use that surface shouldn't see an unmet-peer warning. Examples: `@playwright/test` in `@nx/playwright`, `cypress` in `@nx/cypress`, `vitest` / `vite` in `@nx/vitest`, `@angular/build` / `@angular-devkit/build-angular` / `ng-packagr` in `@nx/angular`.
For `@rspack/cli` / `@rspack/core` in `@nx/rspack`: both surfaces (executor, inferred plugin) are gated — executor opt-in via `project.json`, inferred plugin gated on `rspack.config.{ts,js}` presence. Compliance fix should peer-declare both with `optional: true`.
Concrete examples:
| Package | Plugin | Plugin invokes? | Peer? | Optional? |
| ----------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----------------------------------------------------- |
| `cypress` | `@nx/cypress` | yes (executor spawns binary; inferred plugin emits `cypress run` commands) | **yes** | optional (executor + inferred plugin are both opt-in) |
| `@playwright/test` | `@nx/playwright` | yes (executor + preset + inferred plugin) | **yes** | optional |
| `vitest` | `@nx/vitest` | yes (executor + inferred plugin emits `vitest` commands) | **yes** | optional |
| `@rspack/cli` | `@nx/rspack` | yes — inferred plugin emits `command: 'rspack build'` (`packages/rspack/src/plugins/plugin.ts:182,196`). Not imported in TS, but invoked via emitted CLI target. | **yes** | optional (both surfaces gated) |
| `@angular-devkit/core` | `@nx/angular` | yes (used by every Angular Nx workspace) | **yes** | **not optional** |
| `@angular/build` | `@nx/angular` | yes (only when user uses the @angular/build builder) | **yes** | optional |
| `eslint-plugin-cypress` | `@nx/cypress` | no (generator writes it into user's eslintrc; ESLint loads it, not the plugin) | **no** | n/a |
| `@types/node` | various | no (generator install only; types are build-time) | **no** | n/a |
### Range / version alignment
For packages that ARE peer-declared: the range must match the install lanes the code ships. If the code has no `isV1Installed` branch and no v1 entry in `versionMap`, do not list `^1.0.0` in the peer range.
Reference: open PR `#35671` (`@nx/vitest`) drops `^1.0.0` from the `vitest` peer range because there is no v1 install lane. Inspect via `gh pr diff 35671 --repo nrwl/nx -- packages/vitest/package.json`. Verify state — may have merged or closed since.
## Executor / runtime feature gating
Features introduced after the floor must gate at call time on the installed version, not on the floor. Use `getInstalledPackageVersion` + `lt` from `semver`.
```ts
import { getInstalledPackageVersion } from '@nx/devkit/internal';
import { lt } from 'semver';
import { minPlaywrightVersionForBlobReports } from './versions';
const installed = getInstalledPackageVersion('@playwright/test');
if (installed && lt(installed, minPlaywrightVersionForBlobReports)) {
throw new Error(
`The "@nx/playwright:merge-reports" executor requires "@playwright/test" version ${minPlaywrightVersionForBlobReports} or greater (the version that introduced the "blob" reporter and the "merge-reports" CLI). You are currently using version ${installed}.`
);
}
```
Reference: `packages/playwright/src/executors/merge-reports/merge-reports.impl.ts`, `packages/playwright/src/utils/preset.ts`. Both added in `#35642`.
Two distinct cases:
- **Auto-injected feature** (preset's auto-blob in CI): skip injection silently when installed < threshold. Only throw when the user explicitly opted in (`generateBlobReports: true`) on an unsupported version.
- **Direct invocation** (executor CLI subcommand): throw immediately with a clear "requires >= X.Y.Z (the version that introduced …)" message.
## File-layout summary
For plugin `@nx/<plugin>` managing `<pkg>` with floor `X.Y.Z`:
```
packages/<plugin>/
src/
utils/
versions.ts # add minSupportedXVersion
assert-supported-<pkg>-version.ts # NEW — 7-line wrapper
assert-supported-<pkg>-version.spec.ts # OPTIONAL — 5 cases (mostly re-tests shared helper)
all-generators-enforce-floor.spec.ts # NEW — parameterized
generators/
<each>/
<each>.ts # assert as first statement
init/
schema.json # keepExistingVersions default: true
init.ts # keepExistingVersions ?? true
executors/ # feature-gate via getInstalledPackageVersion
plugins/ # same
migrations.json # tighten / remove `requires` gates per audit
package.json # align peer; add "semver": "catalog:" if newly used
```
---
# Code-level verification (review-mode lens)
In review mode, walk these markers against the diff.
Output rules:
- **Inline categories are `[blocker]` and `[non-blocker]` only.** No "open question," "ask," or other ad-hoc tags. Author-directed questions emerge from non-blocker findings and surface in the closing "Open questions for author" block.
- **For each blocker / non-blocker:** anchor at `file:line` and cite which reference PR / file demonstrates the correct pattern. Cross-reference `anti-patterns.md` when the finding matches a numbered pattern.
- **Sections without findings get a single summary line**, not a per-file enumeration. `"Pass — all 7 generator entries assert at first statement"` is right; listing seven file:lines is wrong. Reviewer time is spent on actionable items; passing checks should not eat reading budget.
- **Produce the verdict block** at the end (see §"Verdict template"). The block is the skimmable index — produce it, don't substitute a free-form summary.
Scope:
- **In scope:** the diff's code, configs, schemas, migrations, and **in-codebase documentation that describes runtime behavior** (e.g., `.mdoc` / `.md` files under `astro-docs/` or `docs/` that claim how the plugin behaves). A docs claim that contradicts the code is a correctness issue and belongs here.
- **Out of scope:** PR title, PR body shape, commit message format, related-issues section, branch naming. Defer to the user's PR/commit conventions (loaded globally from `~/.claude/memory/workflow/git/`). Don't flag PR/commit shape in this skill's review output.
## 1. Peer dep & install constants
- [blocker] `package.json` peer dep range matches the install lanes implemented in `versions.ts`. No phantom version claims. → If `versionMap` has no v1 entry, peer must not list `^1.0.0`. Reference correction: `#35671` (`@nx/vitest`). Anti-pattern: §8.
- [blocker] **Declared floor matches the effective floor.** Grep every `import` / `require` from the third-party package in plugin code. If any imported API only exists at version >N, the declared floor must be >=N. Anti-pattern: §16. Reference correction: `#35671` second commit raised vitest from v2 to v3 after catching a `getRelevantTestSpecifications` import (v3+ only).
- [blocker] Fresh-install constant exposes the **full feature surface**, not just the peer floor. → Playwright peer stayed `^1.36.0` but fresh-install moved to `^1.37.0` because blob reporter + `merge-reports` CLI both require 1.37. Reference: `#35642` `packages/playwright/src/utils/versions.ts`.
- [blocker] No per-major version aliases (`<pkg>V3Version`, `<pkg>V4Version`, etc.) alongside a `versionMap` — pick one source of truth. Anti-pattern: §15. Reference: `#35671`'s third commit dropped these aliases.
- [blocker] `versions.ts` exports `minSupportedXVersion = 'X.Y.Z'` as a plain string (no caret, no range markers). The wrapper passes this verbatim to `assertSupportedPackageVersion`.
- [blocker] `versionMap[major]` lookup is `versionMap[major] ?? latestVersions`. No `switch + throw default:` or other above-ceiling throw. Anti-pattern: §2. Reference correction: `#35670` (`@nx/cypress` `versions()` rewrite).
- [blocker] Every third-party package the **plugin invokes at runtime** has a `peerDependencies` entry. "Invokes" covers: (a) TypeScript `import`/`require`, (b) executor spawning the package's CLI binary, (c) inferred-plugin (`createNodes`/`createNodesV2`) emitting a target whose `command` invokes the package's CLI (the `externalDependencies: ['<pkg>']` field on such targets confirms the dependency). See §"Peer dep alignment" for the full categorization.
- **Don't flag** packages the plugin's generator installs into the user's workspace for the user to consume independently, with no plugin codepath invoking them (e.g., ESLint plugins like `eslint-plugin-cypress` that ESLint loads from the user's eslintrc; `@types/*` packages).
- Plugins flagged at time of writing for actually-invoked packages without a peer entry: `@nx/webpack`, `@nx/rollup`, `@nx/angular-rspack-compiler` (primary listed under `dependencies`); `@nx/jest`, `@nx/nest`, `@nx/module-federation`, `@nx/react`, `@nx/vue`, `@nx/expo`, `@nx/react-native`, `@nx/node`, `@nx/js` (verify per plugin — TS imports, binary spawns, AND inferred-plugin emitted commands all count).
- [blocker] Peers that are only used when the user opts into a specific surface (executor opt-in, inferred plugin gated on config file presence, opt-in preset) are declared **optional** via `peerDependenciesMeta: { "<pkg>": { "optional": true } }`. Pattern is established across reference plugins — `@playwright/test`, `cypress`, `vitest`, `vite`, `@angular/build`, `ng-packagr` are all optional. Required-non-optional peers (`@angular-devkit/core`, `rxjs` in `@nx/angular`) are reserved for packages every workspace using the plugin needs. See §"Required vs. optional peer".
- [non-blocker] If the PR introduces `semver` usage in the plugin, `package.json` `dependencies` lists `"semver": "catalog:"`. Reference: `#35642` added it to `packages/playwright/package.json`.
## 2. Generator entry points
- [blocker] **Every** entry in `generators.json` has its working function calling `assertSupported<Pkg>Version(tree)` as the **first statement** — before any tree reads, writes, or sub-generator calls. For wrapper/internal-split plugins (cypress, playwright): assert is in `*Internal`. For single-function generators (angular): in the function itself. Anti-pattern: §14.
- [blocker] Plugin wrapper file `assert-supported-<pkg>-version.ts` imports `assertSupportedPackageVersion` from `@nx/devkit/internal`. No direct call to `throwForUnsupportedVersion`. No bespoke `throwBelowFloor` / `throwAboveWindow` / `assertVersion` / local `cleanVersion = clean(v) ?? coerce(v)?.version` helpers (use `normalizeSemver` / `getInstalledPackageVersion` / `getDeclaredPackageVersion`). Anti-pattern: §1. Concrete example: PR `#35676` introduces a local `cleanVersion` and `getInstalledRsbuildVersionRuntime` — both already exist as shared helpers.
- [blocker] If `all-generators-enforce-floor.spec.ts` uses `excludeGenerators`, each excluded name has a code comment explaining why the generator must run sub-floor (e.g., `migrate-to-cypress-11` lifts v8v10 workspaces onto v11).
- [non-blocker] Double-assert chains (`configurationInternal` calls `initInternal`, both assert) are OK. Idempotent. Don't refactor away.
## 3. Generator outputs
- [blocker] Templates the generator writes (project files, configs, schemas) compile and run on every major in the support window. Verify with a quick mental walk: for each template referenced from the generator, identify any per-major-version conditional and confirm it's accurate.
- [blocker] Generated `project.json` target shape (executor, options, schema) is valid on every supported major. If the executor's option schema differs across the support window, the generator branches or uses the union shape.
- [blocker] Default option values are valid on every supported major. A default that's only valid above a specific major must be conditional.
- [blocker] Version map covers every managed third-party dep. If the runtime later branches on a sibling's version (e.g., `@vitest/ui`), the version map must have an entry for that sibling per major — no gaps where the generator picks a constant the runtime then can't reconcile.
- [blocker] Generator schema accepts the **union of options across the support window**. Options removed in a newer major still validate at schema level (with description-notice); runtime throws when inapplicable on the installed major. See `gotchas.md` §"Schema-level deprecated-option stubs with runtime throws".
## 4. `keepExistingVersions` (user-pin preservation)
- [blocker] Every `addDependenciesToPackageJson` call from a **generator** passes `keepExistingVersions: true` (positionally as the 5th arg) or `options.keepExistingVersions ?? true`.
- [blocker] `init/schema.json` has `"keepExistingVersions": { "default": true }`. Not `false`. Not absent. **Known gap:** `@nx/angular`'s init schema currently has `default: false` and was NOT addressed in `#35587` — flagging in a non-angular PR is correct; fixing in passing in an angular PR is also correct. Anti-pattern: §4.
- [blocker] Linter / sub-generator helpers (`add-linter.ts`, `add-angular-eslint-dependencies.ts`, equivalents) also pass `true`.
- [non-blocker] If both schema `"default": true` AND `options.keepExistingVersions ?? true` are present, that's two sources of truth. Anti-pattern: §12.
- **Migration generators are exempt.** Do not flag missing flags in code under `src/migrations/`.
## 5. `migrations.json` gates
- [blocker] Every `packageJsonUpdates` entry that bumps across a major version has `requires: { "<pkg>": ">=N.0.0 <(N+1).0.0" }`. Source-major gate, not target. Anti-pattern: §6. Reference: `#35587` Module Federation entries.
- **Read the actual range strings; don't tick this by counting split entries.**
- [non-blocker / ask author] One-sided gates (`<X` with no lower bound, or `>=Y` with no upper bound) may be intentional or accidental. Legitimate cases: legacy-cleanup codemods that should apply on every source major below the target; a v0→v1 bridge where every v0.x workspace should migrate; bumping a package introduced at vN from `undefined`. Illegitimate cases: a v1→v2 bump expressed as `<2.x` would fire for v0 workspaces too; a `>=N` with no upper bound would fire for future majors. **When you see a one-sided gate, ask the author to confirm intent** — don't auto-flag as blocker.
- [blocker] Codemod migrations that only make sense at/above a specific third-party major have a `requires` entry. Open upper bound is intentional when the codemod cleans up legacy flags. Runtime per-package guards (`gte`/`lt` inside the migration body) are NOT a substitute for `requires`.
- [blocker] **Nx-only migrations have NO `requires` gate.** A migration that only writes to `nx.json`, executor options, or generator defaults applies regardless of third-party version. Anti-pattern: §5. Reference correction: `#35587` removed the over-gating `@angular/core: >=21.0.0` from `update-unit-test-runner-option`.
- [blocker] For independent siblings (Angular: `@ngrx/*`, `@angular-eslint/*`, `zone.js`, `jest-preset-angular`), gating on the primary's major is **not sufficient** — each needs its own `requires` entry. Anti-pattern: §7. Verify pairing by reading the sibling's `peerDependencies` at the version range being bumped from.
- [blocker] A single `packageJsonUpdates` entry must not mix mutually-exclusive cross-major bumps under one `requires` (AND-semantics). Split into separate entries each with its own gate. Concrete example: React PR's `22.3.4` entry mixed `react-router 7.12.0` (cross-major) with `react-router-dom 6.30.3` (v6 patch) — must split.
- [non-blocker] `incompatibleWith` is not a substitute for `requires`. Anti-pattern: §10. If you see `incompatibleWith` standing in for a source-major gate, ask for a `requires` instead.
- [non-blocker] Sibling `packageJsonUpdates` entries within the same block that depend on a peer's post-bump version are fine — tier-1 chaining evaluates against post-bump state. Reference: Storybook 21.2.0 chains on the prior 21.1.0 bump.
- [non-blocker] Pre-floor `packageJsonUpdates` entries targeting source majors below the current support floor are intentionally retained for users on older Nx versions. Don't _add_ a bridge entry without explicit decision, and don't _remove_ a legitimately-pre-floor entry mid-audit.
## 6. Executor / runtime / inferred-plugin feature gating
- [blocker] Executor code that invokes a CLI subcommand or uses an API introduced after the floor calls `getInstalledPackageVersion('<pkg>')` + `lt(installed, threshold)` from `semver` and throws a clear "requires >= X.Y.Z (the version that introduced …)" message. Reference: `packages/playwright/src/executors/merge-reports/merge-reports.impl.ts` (`#35642`).
- [blocker] Preset / config builders that auto-inject feature-version-coupled config skip injection silently when installed < threshold, and only throw when the user **explicitly** opted in on an unsupported version. Reference: `packages/playwright/src/utils/preset.ts` (`#35642` — `generateBlobReports` logic).
- [blocker] **Inferred plugins** (`createNodes`/`createNodesV2`) parse configs across every major in the support window. The plugin emits the same target shape regardless of the installed major (or branches if shapes diverge). Don't hardcode helper imports against one major.
- [blocker] **Above-ceiling is silent fallthrough.** No warn, no throw, no branch. Anti-pattern: §2.
- [non-blocker] Executors don't enforce the plugin floor. Floor enforcement is generator-only. Don't suggest adding an executor-level floor assert unless the user asks.
- [non-blocker] `require('<pkg>')` for optional peers should live inside the function body, after version detection. Anti-pattern: §9.
## 7. Tests
- [blocker] `all-generators-enforce-floor.spec.ts` exists at `packages/<plugin>/src/utils/all-generators-enforce-floor.spec.ts`, calls `assertGeneratorsEnforceVersionFloor` from `@nx/devkit/internal-testing-utils`. This is the parameterized spec that exercises every generator's floor assert.
- [blocker] `subFloorVersion` is a semver range where `lt(coerce(it).version, floor)` is true. No pre-release identifiers. Reference values: `~18.2.0` (angular, v19 floor), `~12.17.0` (cypress, v13 floor), `~1.35.0` (playwright, v1.36 floor).
- [non-blocker] Plugins establishing the pattern (`#35587` angular) ship a `assert-supported-<pkg>-version.spec.ts` with the 5 canonical cases (sub-floor / fresh-install / `latest` / `next` / in-range). The underlying `assertSupportedPackageVersion` already has full coverage in `@nx/devkit`, so the per-plugin spec largely re-tests the shared helper. Useful for symmetry across the PR series but not required — don't block on missing.
- [non-blocker] Runtime/executor feature-gate throw tests are nice-to-have, not required — reference PRs (`#35587`, `#35642`, `#35670`) do not have them today.
- [non-blocker] Error message matching uses substring (`toThrow('Unsupported version of \`<pkg>\` detected')`) instead of hand-rolled `RegExp`. Anti-pattern: §13.
- [non-blocker] FS-side helper migrated to `getInstalledPackageVersion`; tree-side helper may stay inline. The two helpers' `null` vs. fallback semantics differ. Reference: `#35670` `packages/cypress/src/utils/versions.ts` rewrite.
## Open questions to raise (when missing from the PR / Linear task)
1. **Floor:** deliberate raise from the previous declared peer, or matching the existing peer? If raise: do sub-floor users get a `packageJsonUpdates` bridge or manual bump?
2. **Peer-range tightening:** dropping a major because there's no install lane (legitimate, `#35671` pattern) or because tests fail (regression risk — investigate)?
3. **`requires` removals on Nx-only migrations:** genuinely Nx-only, or sneaking through a third-party-touching change?
4. **Pruned migrations gaps:** if floor is being raised by N+ majors and prior `packageJsonUpdates` entries were removed, do sub-floor users have any auto-bump path? `git log --all -- packages/<plugin>/migrations.json`.
5. **Runtime feature gates:** threshold verified against third-party release notes, or guessed?
6. **Sibling classification:** ecosystem-locked vs. independent. Read the sibling's `peerDependencies` at the bumped-from range. `@angular-devkit/build-angular` is the gotcha — peer-locked from v20+, NOT v19.
7. **Cross-plugin coordination:** if the plugin pins a third-party that another plugin also manages (e.g., `@nx/cypress` pinning vite for cypress v13/v14+; `@nx/vite` supporting vite v5v8), confirm the windows stay aligned. If `@nx/vite` drops v5, `@nx/cypress` carries an orphaned install lane.
## Verdict template
```
Blockers: <N>
Non-blockers: <N>
1. Peer dep & install constants: [pass | <findings>]
2. Generator entry points: [pass | <findings>]
3. Generator outputs: [pass | <findings>]
4. keepExistingVersions: [pass | <findings>]
5. Migration gates: [pass | <findings>]
6. Executor / runtime / inferred-plugin: [pass | <findings>]
7. Tests: [pass | <findings>]
Open questions for author: [list]
Scope drift vs. Linear task (if applicable):
- Findings in task NOT addressed: <list>
- Changes in PR NOT in task: <list>
```
@@ -0,0 +1,115 @@
# Examples & references
Concrete files, commits, and PRs to grep when you need a model.
## Reference PRs (in order of arrival)
| PR | Plugin | Branch | State | Why notable |
| -------- | ---------------- | ---------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `#35587` | `@nx/angular` | `nxc-4381` | merged | First compliance PR. Established `throwForUnsupportedVersion`, the `assertSupported*Version` wrapper pattern, the `all-generators-enforce-floor.spec.ts` shape, the MF `requires`-gate pattern, the Nx-only-migration over-gate removal pattern. |
| `#35642` | `@nx/playwright` | `nxc-4398` | merged | Generalized the helpers into `version-floor.ts`/`installed-version.ts`. Added `assertGeneratorsEnforceVersionFloor` in `internal-testing-utils`. Established executor/runtime feature-gating pattern (blob reporter / `merge-reports`). Demonstrated the "fresh-install constant higher than peer floor" pattern. |
| `#35670` | `@nx/cypress` | `nxc-4384` | merged | Established `excludeGenerators` in the shared test helper for intentional sub-floor migrators (`migrate-to-cypress-11`). Demonstrated `versions()` switch-to-fallthrough rewrite. Demonstrated keeping the tree-side inline helper while migrating only the FS side to the shared helper. |
| `#35671` | `@nx/vitest` | `nxc-4408` | open at time of writing | Three commits. (1) Establishes "drop phantom peer-range claim" (removes `^1.0.0` from peer); migration `requires` tightening for Vitest-4-only AI-instructions migrations. (2) Raises floor v2 → v3 after audit catches `getRelevantTestSpecifications` import (v3+ API) — establishes the **effective-floor-vs-declared-floor** pattern (see `anti-patterns.md` §16). (3) Adopts the cypress version-resolution pattern (bundle-of-varying-deps `versions(tree)`, `getInstalled<Pkg>Version(tree?)`, no per-major aliases — see `anti-patterns.md` §15). To inspect: `gh pr view 35671 --repo nrwl/nx --json commits` then `gh pr diff 35671`. Verify state — may have merged or closed. |
Always verify state with `gh pr view <N> --repo nrwl/nx --json state` before citing — this table goes stale.
## Reference commits (for `git show` inspection)
When the same change exists as both a pre-squash branch commit AND a merged squash on master, prefer the merged squash — it's the authoritative final state. Pre-squash SHAs are listed because they're easier to read in isolation (smaller diffs) when investigating one specific aspect.
**Merged on master (authoritative):**
| SHA | Subject |
| ------------ | ---------------------------------------------------------------------------- |
| `75578724fa` | `cleanup(core): add throwForUnsupportedVersion util to @nx/devkit/internal` |
| `484ce6e5d5` | `fix(angular): multi-version support compliance (#35587)` |
| `78f908d015` | `cleanup(angular): adopt shared version-floor helpers` |
| `e2ef134645` | `fix(testing): multi-version support compliance for @nx/playwright (#35642)` |
| `5d8b1bab7e` | `cleanup(devkit): allow excluding generators from version floor test helper` |
| `bc35b484e3` | `fix(testing): multi-version support compliance for @nx/cypress (#35670)` |
Pre-squash branch SHAs are available via `gh pr view <N> --json commits` even after the branch is deleted; useful when inspecting one specific aspect of a merged PR in isolation. Example:
```bash
gh pr view 35642 --repo nrwl/nx --json commits | jq -r '.commits[] | "\(.oid[:10]) \(.messageHeadline)"'
```
## Shared helpers — current locations
| File | Exports |
| ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `packages/devkit/src/utils/version-floor.ts` | `throwForUnsupportedVersion` (internal-only), `assertSupportedPackageVersion` |
| `packages/devkit/src/utils/installed-version.ts` | `getInstalledPackageVersion`, `getDeclaredPackageVersion`, `isNonSemverDistTag`, `normalizeSemver`, `NON_SEMVER_DIST_TAGS` |
| `packages/devkit/internal.ts` | re-exports from above (this is `@nx/devkit/internal`) |
| `packages/nx/src/internal-testing-utils/assert-generators-enforce-version-floor.ts` | `assertGeneratorsEnforceVersionFloor` |
| `packages/devkit/internal-testing-utils.ts` | re-exports `assertGeneratorsEnforceVersionFloor` (this is `@nx/devkit/internal-testing-utils`) |
## Per-plugin compliant files (grep for the pattern)
### `@nx/angular` (most extensive — has the `supportedVersions` list pattern)
- `packages/angular/src/utils/assert-supported-angular-version.ts` — wrapper using `Math.min(...supportedVersions)`
- `packages/angular/src/utils/assert-supported-angular-version.spec.ts` — the canonical 5-case spec
- `packages/angular/src/utils/all-generators-enforce-floor.spec.ts` — the parameterized floor spec
- `packages/angular/src/generators/add-linting/lib/add-angular-eslint-dependencies.ts``keepExistingVersions: true` pattern
- `packages/angular/migrations.json` — MF `requires` gates and the over-gate removal
### `@nx/playwright` (the helpers were generalized here)
- `packages/playwright/src/utils/assert-supported-playwright-version.ts` — wrapper using a `minSupportedPlaywrightVersion` constant
- `packages/playwright/src/utils/all-generators-enforce-floor.spec.ts`
- `packages/playwright/src/utils/preset.ts` — runtime feature gating (blob reporter)
- `packages/playwright/src/executors/merge-reports/merge-reports.impl.ts` — executor feature gating
- `packages/playwright/src/utils/versions.ts``minSupportedPlaywrightVersion`, `minPlaywrightVersionForBlobReports`, `playwrightVersion = '^1.37.0'` (fresh install higher than peer)
- `packages/playwright/src/utils/add-linter.ts``keepExistingVersions: true` in linter helper
- `packages/playwright/package.json` — peer `^1.36.0` (unchanged), added `"semver": "catalog:"`
### `@nx/cypress` (excludeGenerators + versions() rewrite)
- `packages/cypress/src/utils/assert-supported-cypress-version.ts`
- `packages/cypress/src/utils/all-generators-enforce-floor.spec.ts` — uses `excludeGenerators: ['migrate-to-cypress-11']` with code comment
- `packages/cypress/src/utils/versions.ts``versions()` rewritten to `versionMap[major] ?? latestVersions`; `getInstalledCypressVersion` FS-path migrated to shared helper, tree-path kept inline
## Finding current work-in-progress
To enumerate all compliance PRs (merged + open) without relying on out-of-tree tracking docs:
```bash
# Open + merged compliance PRs
gh pr list --repo nrwl/nx --search "multi-version compliance" --state all --limit 30 \
--json number,title,state,headRefName,author
# Just open ones
gh pr list --repo nrwl/nx --search "multi-version compliance" --state open
```
This is the authoritative list. Plugins covered to date can be derived by inspecting which packages each merged PR touched.
To check which plugins still have known anti-patterns (e.g., phantom peer claims, missing floor assert), grep on master:
```bash
# Plugins WITHOUT an assert-supported-<pkg>-version wrapper
for d in packages/*/src/utils; do
pkg=$(dirname "$d" | xargs basename)
if [ ! -f "$d/assert-supported-$pkg-version.ts" ] && \
[ ! -f "$d/assert-supported-${pkg/_/-}-version.ts" ]; then
echo "$pkg: no assert-supported wrapper"
fi
done
# Plugins missing the parameterized floor spec
find packages -name "all-generators-enforce-floor.spec.ts" -not -path "*/dist/*"
```
Cross-reference with the third-party packages each plugin manages (peer deps in `package.json`).
## How to use these examples
When auditing a new plugin, before writing anything:
1. Read the PR body of `#35642` (`@nx/playwright`) — it's the most comprehensive description of the canonical shape.
2. Read the four files from `@nx/playwright`: `versions.ts`, `assert-supported-playwright-version.ts`, `all-generators-enforce-floor.spec.ts`, and `preset.ts`. Five minutes.
3. If your plugin has a `migrations.json` of any complexity, also read `packages/angular/migrations.json` MF entries and the `update-unit-test-runner-option` entry for the gate patterns.
4. If the plugin has runtime feature gates, also read `packages/playwright/src/executors/merge-reports/merge-reports.impl.ts`.
When reviewing a compliance PR, the diff should look very similar to one of these reference PRs. Differences should be justifiable by the plugin's specifics (different floor, different feature gates, different migration shape) — not by departing from the canonical patterns.
@@ -0,0 +1,241 @@
# Gotchas & edge cases
Non-obvious behavior. Load these into your model before auditing or reviewing.
## `latest` / `next` dist-tags
When a workspace declares `"<pkg>": "latest"` or `"next"` in its `package.json`:
- `assertSupportedPackageVersion` no-ops via `isNonSemverDistTag` (NON_SEMVER_DIST_TAGS = `['latest', 'next']`). The floor check is skipped entirely.
- `getDeclaredPackageVersion` falls back to the cleaned `latestKnownVersion` argument (if provided) or returns `null`.
- `versions(tree)` returns `latestVersions` (the fresh-install path).
Tests must include `latest` and `next` cases. Both are no-ops; neither throws.
## pnpm `catalog:` references
Declared versions may be `"catalog:default"`, `"catalog:typescript"`, etc. (since pnpm 9.5):
- `getDependencyVersionFromPackageJson` (via the catalog manager in devkit) resolves these before the helper sees them. Don't call `clean`/`coerce` on raw values.
- `normalizeSemver` behavior on a raw `catalog:` string is not explicitly tested (open question — verify if you encounter it).
Reference: PR `#35459` (`fix(misc): resolve pnpm catalog: refs in version lookups`) — landed catalog ref handling.
## Fresh-install path (package not declared)
When `<pkg>` is missing from the workspace's `package.json` entirely:
- `assertSupportedPackageVersion` no-ops.
- `versions(tree)` returns `latestVersions`.
- The generator proceeds with the fresh-install constant (e.g., `playwrightVersion = '^1.37.0'`).
This is intentional — the generator is being run on a new workspace or one that's adding this package for the first time.
## Error message preserves declared range, not cleaned semver
```
Installed: ~18.2.0
Supported: >= 19.0.0
```
`Installed:` shows what's in `package.json` verbatim. Don't try to normalize it in the error message — it tells the user exactly what they typed, which helps them find it.
The argument flow: `assertSupportedPackageVersion` calls `throwForUnsupportedVersion(packageName, declared, minSupportedVersion)` with the raw `declared` value.
## Cypress's `getCypressVersionFromTree` stays inline
The shared `getDeclaredPackageVersion` falls back to `latestKnownVersion` when the declared value is `latest`/`next` or missing. Cypress's tree path returns `null` on missing. These semantics differ enough that the helper can't be consolidated without changing behavior.
The FS path (`getCypressVersionFromFileSystem`) was migrated to `getInstalledPackageVersion` (better resolution for pnpm strict / nested installs). The tree path stayed inline.
Reference: `packages/cypress/src/utils/versions.ts` after `#35670`.
## Double-asserts are fine
When `configurationInternal` calls `initInternal` (or any generator chain), both call their respective `assertSupportedXVersion(tree)`. The assert is idempotent and cheap (one tree read + one semver comparison). Don't refactor away.
The `assertGeneratorsEnforceVersionFloor` test treats both entry points as separate generators and asserts each throws — which is what we want.
## Angular ecosystem lockstep — what `@angular/core >=N` covers
Peer-locked to `@angular/core` (one `requires` on the primary is sufficient):
- `@angular/cli`
- `@angular/ssr`
- `@angular-devkit/build-angular` **from v20+** (NOT v19 — `@angular-devkit/build-angular@19` does not peer-on `@angular/core`)
- `@angular/material`, `@angular/cdk`, all `@angular/*` framework packages
- `@schematics/angular`
Independent (need their own `requires`):
- `@ngrx/*`
- `@angular-eslint/*`
- `zone.js`
- `jest-preset-angular`
- `karma`, `karma-*`
- `protractor` (deprecated)
- `tailwindcss` and CSS-tooling siblings
Always verify pairing at the actual version range being bumped from — `@angular-devkit/build-angular` is the classic gotcha (peers on `@angular/core` in some versions, not others).
## Pruned migrations leave no trace in `migrations.json`
Older `packageJsonUpdates` entries (e.g., `12.x` migrations) are removed during normal Nx version cleanup waves. They don't show up in the current `migrations.json` but their absence is meaningful — users on an old floor have no auto-bump path to the new floor.
Check via:
```sh
git log --all --oneline -p -- packages/<plugin>/migrations.json | head -200
git log --all --diff-filter=D --name-only -- packages/<plugin>/migrations.json
```
When raising a floor by N+ majors, decide whether to:
1. Add a `packageJsonUpdates` entry bridging sub-floor → floor (the user gets auto-bumped on `nx migrate`).
2. Leave the gap (the user sees the floor-assert error and has to bump manually).
The Cypress v12 → v13 gap was left intentionally — users get the assert error and bump manually. Don't add a bridge entry without explicit agreement.
## Pre-floor `packageJsonUpdates` entries are intentionally retained
Distinct from the pruned-history case above: a plugin may carry `packageJsonUpdates` entries targeting source majors **below** the current support floor. Example: `@nx/react-native`'s entries `20.3.0` and `21.4.0` target RN versions below the current ~0.79.3 floor. These are intentionally retained for users on older Nx versions that supported older RN.
Don't _add_ a bridge entry without explicit decision. Don't _remove_ a legitimately-pre-floor entry as part of a compliance pass. The W1/W4 audit window only covers entries that target source majors **inside** the current support window.
## `subFloorVersion` must satisfy `lt(clean(it), floor)`
For the parameterized floor spec, pick a value that's actually below the floor after `clean()`. Examples:
| Floor | Valid `subFloorVersion` | Invalid |
| -------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `19.0.0` | `~18.2.0`, `^18.0.0`, `18.2.0` | `~19.0.0-beta.0` (clean strips the pre-release; in some cases this still satisfies `lt`, but it's confusing — avoid pre-release) |
| `13.0.0` | `~12.17.0`, `^12.0.0` | `^13.0.0-rc.0` |
| `1.36.0` | `~1.35.0`, `^1.35.0` | `1.36.0-beta.5` |
Use a stable minor-or-patch range below floor. Don't use pre-release identifiers.
## Declared floor vs. effective floor
The declared floor (peer dep + `minSupported<Pkg>Version` + `versionMap` lowest entry) is what the plugin advertises. The **effective floor** is the lowest major where every third-party API the plugin's code actually calls is available. When they diverge, the declared floor is lying.
How this happens: someone bumps the plugin to use a new API (e.g., `getRelevantTestSpecifications` introduced in vitest v3) without raising the floor. The plugin compiles, generators pass tests against the latest install lane, but workspaces on sub-effective-floor versions crash at runtime with `... is not a function`.
How to detect: in the audit's runtime/executor inventory step, every `import` / `require` from the third-party package goes into a list. Cross-reference each named export against the third-party's release notes / API docs. The effective floor is the highest "introduced in" version across that list.
How to fix: raise the declared floor to match the effective floor. Drop the now-unsupported entries from `versionMap`, peer, and any per-major aliases. The parameterized floor spec's `subFloorVersion` shifts up accordingly.
Reference: open PR `#35671` (`@nx/vitest`) — proposed v2 floor initially; raised to v3 in a follow-up commit after spotting `getRelevantTestSpecifications` usage. See `anti-patterns.md` §16.
## `versions()` fall-through above ceiling, not throw
Before `#35670`, cypress's `versions()` had a `switch + throw default:`. This is wrong for two reasons:
1. New majors that don't yet have a `versionMap` entry should silently use `latestVersions` — the plugin hasn't been updated to know about them, but the user should still be able to use them.
2. Below-floor is already caught by the generator-level assert. The `versions()` throw is redundant for the in-range/sub-floor case, and wrong for the above-ceiling case.
The pattern is always:
```ts
return versionMap[major as CompatVersions] ?? latestVersions;
```
## Tier-1 chaining in `packageJsonUpdates`
Within a single `packageJsonUpdates` entry (single block), if entry A bumps package X and entry B has `requires: { X: ">=N" }` that depends on the post-bump value of X, B's gate evaluates against the post-bump state. This is **deliberate design**, not a bug.
Concrete example: Storybook's `21.2.0-migrate-storybook-v9` migration is gated on `storybook >=9.0.0` even though the prior state was v8 — the sibling `packageJsonUpdates` `21.1.0` bumps Storybook to v9 first, so the v9 gate evaluates against post-bump state.
This means you can have one block bump X then chain a sibling bump gated on X's new version, without splitting into separate `packageJsonUpdates` keys.
## Cross-plugin coordination of shared third-party windows
Some third-party packages are managed by multiple Nx plugins. Concrete example:
- `@nx/cypress` pins `vite` v5 (for cypress v13) and v6 (for cypress v14+).
- `@nx/vite` supports `vite` v5v8.
If `@nx/vite` drops v5 from its supported window, `@nx/cypress`'s v5 pin becomes an orphaned install lane — workspaces using both plugins are now in conflict.
When raising / lowering a third-party's support window in one plugin, check every other plugin that manages the same package. The Linear milestone tasks call this out per-plugin (e.g., NXC-4384 cypress flags vite coordination with NXC-4407 vite).
### Sibling declaration consistency
When the same third-party package appears in multiple plugins, the declaration _kind_ (peerDependencies vs dependencies vs devDependencies) should be consistent unless the plugins genuinely have different roles for the package. Concrete inconsistency on master at time of writing: `@module-federation/enhanced ^2.3.3` is in `dependencies` in `@nx/module-federation` but `peerDependencies` in `@nx/rspack`. Pick one rule per package across the plugin family and document the exception when one plugin must differ.
## Plugin must own its primary third-party's pin
A plugin's install constants for its primary third-party must live in the plugin's own `packages/<plugin>/src/utils/versions.ts` — not in another plugin. Cross-plugin imports of install constants create governance drift (the owning plugin can't change the pin without breaking the borrower).
Example anti-pattern: `@nx/esbuild`'s `esbuild` install constant living in `@nx/js`. Flagged in NXC-4386.
## Schema-level deprecated-option stubs with runtime throws
Established Angular pattern (also called out in NXC-4391 jest, NXC-4395 next, NXC-4408 vitest): when an option is deprecated/removed in a newer third-party major but the plugin still supports an older major where it's valid, **retain the option in the schema with a description-notice and throw at runtime when inapplicable to the installed major**.
This keeps the schema accepting the union of options across the support window. Runtime branches on installed version and throws a clear message if the user passes an option that's only valid on a major they're not running.
Reference: search the angular generators for `removed in Angular vN` style schema descriptions paired with `assertSupportedAngularVersion`-aware option handling.
## Known-incomplete plugins
These were touched by a compliance PR but the work is incomplete. Useful for review and for future PRs.
- **`@nx/angular` init `keepExistingVersions`**: `packages/angular/src/generators/init/schema.json` has `default: false` and `packages/angular/src/generators/init/init.ts` passes `options.keepExistingVersions` directly (no `?? true`). PR `#35587` fixed `add-linting` but NOT the init generator. Flag in non-angular PRs as a reference to the pattern; fix in passing in any future angular PR. (Verify state on current master before citing.)
- **`@nx/jest` peer-dep block missing entirely.** When adopting the floor assert, add `peerDependencies` first declaring `jest` / `ts-jest` / `@types/jest` ranges. Without the peer block, `getDependencyVersionFromPackageJson` for `jest` may return `undefined` on installed workspaces because pnpm catalog refs and certain other patterns rely on the peer being declared.
- **Cypress v12→v13 migration gap**: when `#35670` raised the floor to v13, prior v12-cleanup `packageJsonUpdates` entries were already pruned. Decision was to leave it — v12 workspaces see the assert error and bump manually. Reference for the "raise floor, no bridge" pattern.
- **`getInstalled<Pkg>Version` consolidation deferred**: each plugin still has its own near-identical helper (the FS-side has been migrated to the shared helper in some plugins, but a full unification across cypress/playwright/vitest/next/expo/angular is pending). Don't bundle that refactor into a compliance PR.
## `migrate-to-cypress-11` and other intentional sub-floor migrators
A generator whose purpose is to lift sub-floor workspaces onto a supported version must run on sub-floor workspaces. If it had the floor assert, it could never run.
For these generators:
- Do NOT add `assertSupportedXVersion(tree)` to them.
- Keep their existing version checks (e.g., `assertMinimumCypressVersion(8)` in `migrate-to-cypress-11`).
- Add them to `excludeGenerators` in `all-generators-enforce-floor.spec.ts` with a code comment explaining why.
There are usually 0 or 1 of these per plugin. Greater than 1 is suspicious — review carefully.
## `getInstalledPackageVersion` vs. `require('<pkg>/package.json')`
Bare `require('<pkg>/package.json')` resolves from the plugin's own install location, which in pnpm strict mode or nested installs may not match the workspace's resolved version. `readModulePackageJson` (used by `getInstalledPackageVersion`) goes through `getNxRequirePaths()` for correct workspace-rooted resolution.
Anywhere you read an installed version at runtime: prefer `getInstalledPackageVersion('<pkg>')`. Don't `require('<pkg>/package.json')`.
## "Above ceiling" is NOT in the task spec
Repeating because this gets re-introduced: above-ceiling handling is explicitly out of scope for these compliance tasks. If you find yourself adding it, you've drifted from the spec.
The behavior we want above the highest known major: silent fall-through to `latestVersions`. The plugin will be updated to add a `versionMap` entry for the new major in a future PR. Until then, the user gets the latest install constants and may run into incompatibilities, which is the existing pre-compliance behavior. We are NOT trying to detect future majors and warn — that's a different feature.
## Decisions you cannot make alone
Pause and ask when:
- **Peer-range drop:** dropping a major from the peer might be a regression if tests pass on that version. Verify whether the absence of an install lane reflects "we never supported it" (legitimate drop) or "we shipped support and quietly broke it" (regression — investigate before dropping).
- **Floor raise without a bridging migration:** raising the floor by N+ majors means users on the lowest sub-floor major see the assert error and must manually bump. Confirm with the user: acceptable, or add a `packageJsonUpdates` bridge?
- **`requires` removal on a borderline migration:** the diff says the migration is Nx-only (no third-party config touched), but it reads a config file that only exists at certain third-party versions. The third-party dependency is indirect but real. Don't remove the gate without verifying.
- **Peer floor and fresh-install constant diverge** (playwright pattern — peer `^1.36.0`, fresh-install `^1.37.0`). Confirm the gap is justified by feature surface (1.37 introduced the blob reporter + merge-reports CLI) and not an oversight.
- **Ecosystem-locked vs. independent sibling classification:** before adding or removing a sibling's `requires` entry, read its `peerDependencies` block at the version range being bumped from. `@angular-devkit/build-angular` is the gotcha — only peer-locked to `@angular/core` from v20+.
- **Pruned migration gap:** the lowest sub-floor major has no auto-bump path because prior `packageJsonUpdates` entries were removed during cleanup waves. Decide: add a bridge entry, or accept the manual bump? `git log --diff-filter=D -- packages/<plugin>/migrations.json` reveals the gap.
- **New plugin doesn't fit the canonical shape** (manages multiple primary packages with different floors, runs partially as a Nx-internal-only plugin, etc.). Ask before improvising — see `canonical-shape.md` §"Plugins managing multiple primary packages" for the established multi-primary pattern.
- **Test fails on `latest`/`next` despite the assert being a no-op.** The no-op behavior is intentional, but if the generator downstream of the assert can't handle the unresolved range, that's a real bug — not something to paper over by tightening the assert.
## Per-plugin decision log
These were decided once for the reference PRs (#35587, #35642, #35670) — apply them as defaults unless explicitly contradicted by the user for a new plugin:
- **Executors do NOT enforce the plugin floor.** Generator-only. Executors gate per-feature, not per-floor.
- **Above-ceiling: silent fall-through to `latestVersions`.** No warn, no throw, no branch.
- **Init generators preserve user pins** via `keepExistingVersions: true` (schema default) and the `?? true` safety net at the call site.
- **Skip writing the install constant when the package is already detected** (cypress + angular pattern — preserves the user's installed minor/patch).
- **Shared helpers stay in `@nx/devkit/internal`** — not part of the public devkit surface. (The W2 ticket originally proposed adding `throwForUnsupportedVersion` to the public devkit API; the implementation landed under `/internal` instead, matching how other version-related helpers ship.)
- **Consolidation of per-plugin `getInstalled<Pkg>Version` helpers is deferred** — don't bundle that refactor into a compliance PR.
Plugin-specific decisions that may be pending or have settled differently (check the live PR state via `gh pr list --repo nrwl/nx --search "multi-version compliance"`):
- `@nx/jest` — needs a `peerDependencies` block for `jest`/`ts-jest`/`@types/jest` before the floor assert can rely on `getDependencyVersionFromPackageJson`.
- `@nx/eslint` — historically gated on an ESLint v8 EOL decision. If you're touching it, confirm the decision is settled.
- `@nx/eslint-plugin` — historically coupled to the eslint v8 decision (typescript-eslint v6/v7 only support eslint v8). Confirm before proceeding.
- `@nx/rspack` / `@nx/rsbuild` — there is an open PR (`#35676` at time of writing). Inspect for the local-helper-duplication anti-pattern (`anti-patterns.md` §1).
+100
View File
@@ -0,0 +1,100 @@
---
name: nx-docs-style-check
description: Check modified Nx documentation pages against the astro-docs style guide. Auto-trigger after writing or editing docs content in the nx repo. Also trigger on "check style", "style guide", "docs review", "validate docs". Should run as a final step whenever docs files are modified. IMPORTANT: anytime astro-docs/**/*.mdoc files are modified, this should always run automatically without being asked.
allowed-tools: Read, Glob, Grep
---
# Nx docs style check
You are a documentation editor for Nx. Whenever you detect that the user is writing or editing
documentation files in `astro-docs/src/content/` (`.mdoc`, `.mdx`, `.md`), automatically run this
check and fix any issues. Do not wait to be asked.
## Phase 1: Information architecture audit
Read `astro-docs/STYLE_GUIDE.md` (the "Information architecture" section) and
`astro-docs/sidebar.mts` to understand where the page lives in the sidebar hierarchy.
For every new or moved page, evaluate against ALL FIVE principles. These are non-negotiable:
### 1. Progressive disclosure ("journey" rule)
- Is this for the first 30 minutes (Getting Started), first 30 days (Features), or forever (Reference)?
- Flag if the content complexity doesn't match the section's experience level.
### 2. Category homogeneity ("scan" rule)
- Look at sibling pages in the same sidebar section.
- Do they all share the same content type (concepts, tasks, or products)?
- Flag if this page mixes types that siblings don't.
### 3. Type-based navigation ("intent" rule)
- Is this a learning page (narrative/guide) or a lookup page (reference/API)?
- Flag if it's in the wrong category (e.g., a reference page in a guides section).
### 4. Pen and paper test ("theory" rule)
- Can the page be explained using only pen and paper (no terminal needed)?
- YES = belongs in "How Nx Works" (architecture/concepts)
- NO (needs terminal/code examples) = belongs in "Platform Features" or "Technologies"
- Flag if a concept page has terminal output, CLI commands, or code-heavy examples.
### 5. Universal vs. specific ("placement" rule)
- Does this feature apply to every Nx user?
- YES = "Platform Features"
- NO (only React/Angular/etc. users) = "Technologies"
- Flag if a technology-specific page is in Platform Features or vice versa.
## Phase 2: Style validation
### Step 1: Run Vale and fix errors
Run `nx run astro-docs:vale` to check the modified files.
- **errors** — fix these automatically. Edit the file to resolve the violation.
- **warnings** — fix these automatically when the fix is unambiguous (e.g., sentence case headings).
For ambiguous cases, suggest the fix and ask.
- **suggestions** — mention them to the user but do not auto-fix.
### Step 2: Fix issues Vale doesn't catch
Read `astro-docs/STYLE_GUIDE.md` and check for that things that Vale may have missed.
### Handling false positives
Use inline Vale comments to suppress legitimate exceptions:
```markdown
<!-- vale Nx.Headings = NO -->
## extractLicenses
<!-- vale Nx.Headings = YES -->
```
Common cases where suppression is appropriate:
- **CLI option headings** (e.g., `## extractLicenses`) — camelCase by design.
Prefer wrapping in backticks first (`## \`extractLicenses\``).
- **Product possessives in historical/migration context** (e.g., "Angular's original schematic system")
- **Terminology in migration docs** (e.g., explaining what "schematics" were before being renamed)
Do NOT suppress rules just to avoid fixing real violations.
## Output summary
After fixing, report what you did:
```
## Style check results
### Information architecture: [PASS/FAIL]
[List any violations or confirm all five principles pass]
### Vale: [X errors fixed, Y warnings fixed, Z suggestions noted]
[Summary of changes made]
### Manual fixes: [list of additional fixes applied]
```
@@ -0,0 +1,151 @@
---
name: nx-gradle-plugin-version-bump
description: Bump the dev.nx.gradle.project-graph plugin version. Use when updating the Gradle project graph plugin version across the codebase, creating the migration files, and updating migrations.json.
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
---
# Gradle Plugin Version Bump
Bumps the `dev.nx.gradle.project-graph` plugin to a new version. This is a recurring task that touches 5 files in an identical pattern every time.
## Required Inputs
Collect these values from the master branch before starting:
1. `NEW_VERSION` - the version we want to bump to
Example: OLD_VERSION: 0.1.15 => NEW_VERSION: 0.1.16
You can find this value by looking at the `OLD_VERSION` specified in `packages/gradle/project-graph/build.gradle.kts` in the `version` field.
The NEW_VERSION will be the `OLD_VERSION` + 1.
2. `NX_MIGRATION_VERSION` - the version of Nx that will trigger our version bump migration
Example: OLD_VERSION: 22.7.0-beta.0 => NEW_VERSION: 22.7.0-beta.1
You can find this value by looking at the `nx` version in `package.json` under `devDependencies`. The NEW_VERSION will be the `OLD_VERSION` + 1.
3. `MIGRATION_FOLDER` - the folder name under `packages/gradle/src/migrations/` that will contain our migration files
Example: NEW_VERSION: 22.7.0-beta.1 => MIGRATION_FOLDER: 22-7-0
Take the version and replace all the dots with hyphens and remove the `beta` or `rc` suffix.
## Steps
### 1. Update the version constant
**File:** `packages/gradle/src/utils/versions.ts`
Change `gradleProjectGraphVersion` to the new version:
```ts
export const gradleProjectGraphVersion = 'NEW_VERSION';
```
### 2. Update build.gradle.kts
**File:** `packages/gradle/project-graph/build.gradle.kts`
Update the `version` on line 13:
```kotlin
version = "NEW_VERSION"
```
### 3. Create migration TypeScript file
**File:** `packages/gradle/src/migrations/MIGRATION_FOLDER/change-plugin-version-NEW_VERSION.ts`
Determine the previous version by reading the current `gradleProjectGraphVersion` from `packages/gradle/src/utils/versions.ts` before modifying it.
Template:
```ts
import { Tree, readNxJson } from '@nx/devkit';
import { hasGradlePlugin } from '../../utils/has-gradle-plugin';
import { addNxProjectGraphPlugin } from '../../generators/init/gradle-project-graph-plugin-utils';
import { updateNxPluginVersionInCatalogsAst } from '../../utils/version-catalog-ast-utils';
/* Change the plugin version to NEW_VERSION
*/
export default async function update(tree: Tree) {
const nxJson = readNxJson(tree);
if (!nxJson) {
return;
}
if (!hasGradlePlugin(tree)) {
return;
}
const gradlePluginVersionToUpdate = 'NEW_VERSION';
// Update version in version catalogs using AST-based approach to preserve formatting
await updateNxPluginVersionInCatalogsAst(tree, gradlePluginVersionToUpdate);
// Then update in build.gradle(.kts) files
await addNxProjectGraphPlugin(tree, gradlePluginVersionToUpdate);
}
```
### 4. Create migration documentation file
**File:** `packages/gradle/src/migrations/MIGRATION_FOLDER/change-plugin-version-NEW_VERSION.md`
Replace `PREV_VERSION` with the version that was current before this bump.
Template:
````md
#### Change dev.nx.gradle.project-graph to version NEW_VERSION
Change dev.nx.gradle.project-graph to version NEW_VERSION in build file
#### Sample Code Changes
##### Before
\```text title="build.gradle"
plugins {
id "dev.nx.gradle.project-graph" version "PREV_VERSION"
}
\```
##### After
\```text title="build.gradle"
plugins {
id "dev.nx.gradle.project-graph" version "NEW_VERSION"
}
\```
````
### 5. Add migration entry to migrations.json
**File:** `packages/gradle/migrations.json`
Add a new entry at the end of the `generators` object (before the closing `}`), following the existing pattern:
```json
"change-plugin-version-NEW_VERSION": {
"version": "NX_MIGRATION_VERSION",
"cli": "nx",
"description": "Change dev.nx.gradle.project-graph to version NEW_VERSION in build file",
"factory": "./src/migrations/MIGRATION_FOLDER/change-plugin-version-NEW_VERSION"
}
```
The migration key uses the version with hyphens replacing dots (e.g., `0-1-16`).
## Verification
Run:
```bash
nx run-many -t test,build,lint -p gradle
```
## Commit Convention
```
chore(gradle): bump gradle project graph plugin version to NEW_VERSION
```
## Final Verification
Take a look at the most recent Gradle version bump PR and compare your changes to that. You should not be touching more or less files than
the most recent version bump PR. If you do, ask for more information and stop all changes.
-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:
+480
View File
@@ -0,0 +1,480 @@
---
name: ci-watcher
description: Polls Nx Cloud CI pipeline and self-healing status. Returns structured state when actionable. Spawned by /nx-cloud-ci-monitor command to monitor CI Attempt status.
model: fast
---
# CI Watcher Subagent
You are a CI monitoring subagent responsible for polling Nx Cloud CI Attempt status and self-healing state. You report status back to the main agent - you do NOT make apply/reject decisions.
## Your Responsibilities
1. Poll CI status using the `ci_information` MCP tool
2. Implement exponential backoff between polls
3. Return structured state when an actionable condition is reached
4. Track iteration count and elapsed time
5. Output status updates based on verbosity level
## Input Parameters (from Main Agent)
The main agent may provide these optional parameters in the prompt:
| Parameter | Description |
| ------------------- | -------------------------------------------------------- |
| `branch` | Branch to monitor (auto-detected if not provided) |
| `expectedCommitSha` | Commit SHA that should trigger a new CI Attempt |
| `previousCipeUrl` | CI Attempt URL before the action (to detect change) |
| `subagentTimeout` | Polling timeout in minutes (default: 60) |
| `verbosity` | Output level: minimal, medium, verbose (default: medium) |
When `expectedCommitSha` or `previousCipeUrl` is provided, you must detect whether a new CI Attempt has spawned.
## MCP Tool Reference
### `ci_information`
**Input:**
```json
{
"branch": "string (optional, defaults to current git branch)",
"select": "string (optional, comma-separated field names)",
"pageToken": "number (optional, 0-based pagination for long strings)"
}
```
**Output:**
```json
{
"cipeStatus": "NOT_STARTED | IN_PROGRESS | SUCCEEDED | FAILED | CANCELED | TIMED_OUT",
"cipeUrl": "string",
"branch": "string",
"commitSha": "string | null",
"failedTaskIds": "string[]",
"verifiedTaskIds": "string[]",
"selfHealingEnabled": "boolean",
"selfHealingStatus": "NOT_STARTED | IN_PROGRESS | COMPLETED | FAILED | NOT_EXECUTABLE | null",
"verificationStatus": "NOT_STARTED | IN_PROGRESS | COMPLETED | FAILED | NOT_EXECUTABLE | null",
"userAction": "NONE | APPLIED | REJECTED | APPLIED_LOCALLY | APPLIED_AUTOMATICALLY | null",
"failureClassification": "string | null",
"taskOutputSummary": "string | null",
"suggestedFixReasoning": "string | null",
"suggestedFixDescription": "string | null",
"suggestedFix": "string | null",
"shortLink": "string | null",
"couldAutoApplyTasks": "boolean | null",
"confidence": "number | null",
"confidenceReasoning": "string | null"
}
```
**Select Parameter:**
| Usage | Returns |
| --------------- | ----------------------------------------------------------- |
| No `select` | Formatted overview (truncated, not recommended for polling) |
| Single field | Raw value with pagination for long strings |
| Multiple fields | Object with requested field values |
**Field Sets for Efficient Polling:**
```yaml
WAIT_FIELDS:
'cipeUrl,commitSha,cipeStatus'
# Minimal fields for detecting new CI Attempt
LIGHT_FIELDS:
'cipeStatus,cipeUrl,branch,commitSha,selfHealingStatus,verificationStatus,userAction,failedTaskIds,verifiedTaskIds,selfHealingEnabled,failureClassification,couldAutoApplyTasks,shortLink,confidence,confidenceReasoning'
# Status fields for determining actionable state
HEAVY_FIELDS:
'taskOutputSummary,suggestedFix,suggestedFixReasoning,suggestedFixDescription'
# Large content fields - fetch only when returning to main agent
```
## Initial Wait
Before first poll, wait based on context:
- **Fresh start (no expected CIPE):** Wait 60 seconds to allow CI to start
- **Expecting new CIPE:** Wait 30 seconds (action already triggered)
**IMPORTANT:** Always run sleep in foreground, NOT as background command.
```bash
sleep 60 # or 30 if expecting new CIPE (FOREGROUND, not background)
```
## Two-Phase Operation
The subagent operates in one of two modes depending on input:
### Mode 1: Fresh Start (no `expectedCommitSha` or `previousCipeUrl`)
Normal polling - process whatever CIPE is returned by `ci_information`.
### Mode 2: Wait-for-New-CIPE (when `expectedCommitSha` or `previousCipeUrl` provided)
**CRITICAL**: When expecting a new CIPE, the subagent must **completely ignore** the old/stale CIPE. Do NOT process its status, do NOT return actionable states based on it.
#### Phase A: Wait Mode
1. Start a **new-CIPE timeout** timer (default: 30 minutes)
2. On each poll of `ci_information`:
- Check if CIPE is NEW:
- `cipeUrl` differs from `previousCipeUrl`**new CIPE detected**
- `commitSha` matches `expectedCommitSha`**correct CIPE detected**
- If still OLD CIPE: **ignore all status fields**, just wait and poll again
- Do NOT return `fix_available`, `ci_success`, etc. based on old CIPE!
3. Output wait status (see below)
4. If timeout (30 min) reached → return `no_new_cipe`
#### Phase B: Normal Polling (after new CIPE detected)
Once new CIPE is detected:
1. Clear the new-CIPE timeout
2. Switch to normal polling mode
3. Process the NEW CIPE's status normally
4. Return when actionable state reached
### Wait Mode Output
While in wait mode, output clearly that you're waiting (not processing):
```
[CI Monitor] ═══════════════════════════════════════════════════════
[CI Monitor] WAIT MODE - Expecting new CI Attempt
[CI Monitor] Expected SHA: <expectedCommitSha>
[CI Monitor] Previous CI Attempt: <previousCipeUrl>
[CI Monitor] ═══════════════════════════════════════════════════════
[CI Monitor] Polling... (elapsed: 0m 30s)
[CI Monitor] Still seeing previous CI Attempt (ignoring): <oldCipeUrl>
[CI Monitor] Polling... (elapsed: 1m 30s)
[CI Monitor] Still seeing previous CI Attempt (ignoring): <oldCipeUrl>
[CI Monitor] Polling... (elapsed: 2m 30s)
[CI Monitor] ✓ New CI Attempt detected! URL: <newCipeUrl>, SHA: <newCommitSha>
[CI Monitor] Switching to normal polling mode...
```
### Why This Matters (Context Preservation)
**The problem**: Stale CIPE data can be very large:
- `taskOutputSummary`: potentially thousands of characters of build/test output
- `suggestedFix`: entire patch files
- `suggestedFixReasoning`: detailed explanation
If subagent returns stale CIPE data to main agent, it **pollutes main agent's context** with useless information (we already processed that CIPE). This wastes valuable context window.
**Without wait mode:**
1. Poll `ci_information` → get old CIPE with huge data
2. Return to main agent with all that stale data
3. Main agent's context gets polluted with useless info
4. Main agent has to process/ignore it anyway
**With wait mode:**
1. Poll `ci_information` → get old CIPE → **ignore it, don't return**
2. Keep waiting internally (stale data stays in subagent)
3. New CIPE appears → switch to normal mode
4. Return to main agent with only the NEW, relevant CIPE data
## Polling Loop
### Subagent State Management
Maintain internal accumulated state across polls:
```
accumulated_state = {}
```
### Call `ci_information` MCP Tool
**Wait Mode (expecting new CI Attempt):**
```
ci_information({
branch: "<branch_name>",
select: "cipeUrl,commitSha,cipeStatus"
})
```
Only fetch minimal fields needed to detect CI Attempt change. Do NOT fetch heavy fields - stale data wastes context.
**Normal Mode (processing CI Attempt):**
```
ci_information({
branch: "<branch_name>",
select: "cipeStatus,cipeUrl,branch,commitSha,selfHealingStatus,verificationStatus,userAction,failedTaskIds,verifiedTaskIds,selfHealingEnabled,failureClassification,couldAutoApplyTasks,shortLink,confidence,confidenceReasoning"
})
```
Merge response into `accumulated_state` after each poll.
### Analyze Response
**If in Wait Mode** (expecting new CIPE):
1. Check if CIPE is new (see Two-Phase Operation above)
2. If old CIPE → **ignore status**, output wait message, poll again
3. If new CIPE → switch to normal mode, continue below
**If in Normal Mode**:
Based on the response, decide whether to **keep polling** or **return to main agent**.
### Keep Polling When
Continue polling (with backoff) if ANY of these conditions are true:
| Condition | Reason |
| --------------------------------------- | ---------------------------------------- |
| `cipeStatus == 'IN_PROGRESS'` | CI still running |
| `cipeStatus == 'NOT_STARTED'` | CI hasn't started yet |
| `selfHealingStatus == 'IN_PROGRESS'` | Self-healing agent working |
| `selfHealingStatus == 'NOT_STARTED'` | Self-healing not started yet |
| `failureClassification == 'FLAKY_TASK'` | Auto-rerun in progress |
| `userAction == 'APPLIED_AUTOMATICALLY'` | New CI Attempt spawning after auto-apply |
When `couldAutoApplyTasks == true`:
- `verificationStatus` = `NOT_STARTED`, `IN_PROGRESS` → keep polling (verification still in progress)
- `verificationStatus` = `COMPLETED` → return `fix_auto_applying` (auto-apply will happen, main agent spawns wait mode subagent)
- `verificationStatus` = `FAILED`, `NOT_EXECUTABLE` → return `fix_available` (auto-apply won't happen, needs manual action)
### Exponential Backoff
Between polls, wait with exponential backoff:
| Poll Attempt | Wait Time |
| ------------ | ----------------- |
| 1st | 60 seconds |
| 2nd | 90 seconds |
| 3rd+ | 120 seconds (cap) |
Reset to 60 seconds when state changes significantly.
**IMPORTANT:** Run sleep in foreground (NOT as background command). Background sleep causes "What should Claude do?" prompts when completed.
```bash
# Example backoff - run in FOREGROUND
sleep 60 # First wait
sleep 90 # Second wait
sleep 120 # Third and subsequent waits (capped)
```
### Fetch Heavy Fields on Actionable State
Before returning to main agent, fetch heavy fields if the status requires them:
| Status | Heavy Fields Needed |
| ------------------- | ------------------------------------------------------------------------------ |
| `ci_success` | None |
| `fix_auto_applying` | None |
| `fix_available` | `taskOutputSummary,suggestedFix,suggestedFixReasoning,suggestedFixDescription` |
| `fix_failed` | `taskOutputSummary` |
| `no_fix` | `taskOutputSummary` |
| `environment_issue` | None |
| `no_new_cipe` | None |
| `polling_timeout` | None |
| `cipe_canceled` | None |
| `cipe_timed_out` | None |
```
# Example: fetching heavy fields for fix_available
ci_information({
branch: "<branch_name>",
select: "taskOutputSummary,suggestedFix,suggestedFixReasoning,suggestedFixDescription"
})
```
Merge response into `accumulated_state`, then return merged state to main agent.
**Pagination:** Heavy string fields return first page only. If `hasMore` indicated, include in return format so main agent knows more content available.
### Return to Main Agent When
Return immediately with structured state if ANY of these conditions are true:
| Status | Condition |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ci_success` | `cipeStatus == 'SUCCEEDED'` |
| `fix_auto_applying` | `selfHealingStatus == 'COMPLETED'` AND `couldAutoApplyTasks == true` AND `verificationStatus == 'COMPLETED'` |
| `fix_available` | `selfHealingStatus == 'COMPLETED'` AND `suggestedFix != null` AND (`couldAutoApplyTasks != true` OR `verificationStatus` in (`FAILED`, `NOT_EXECUTABLE`)) |
| `fix_failed` | `selfHealingStatus == 'FAILED'` |
| `environment_issue` | `failureClassification == 'ENVIRONMENT_STATE'` |
| `no_fix` | `cipeStatus == 'FAILED'` AND (`selfHealingEnabled == false` OR `selfHealingStatus == 'NOT_EXECUTABLE'`) |
| `no_new_cipe` | `expectedCommitSha` or `previousCipeUrl` provided, but no new CI Attempt detected after 30 min |
| `polling_timeout` | Subagent has been polling for > configured timeout (default 60 min) |
| `cipe_canceled` | `cipeStatus == 'CANCELED'` |
| `cipe_timed_out` | `cipeStatus == 'TIMED_OUT'` |
## Subagent Timeout
Track elapsed time. If you have been polling for more than **60 minutes** (configurable via main agent), return with `status: polling_timeout`.
## Return Format
When returning to the main agent, provide a structured response with accumulated state:
```
## CI Monitor Result
**Status:** <status>
**Iterations:** <count>
**Elapsed:** <minutes>m <seconds>s
### CI Attempt Details
- **Status:** <cipeStatus>
- **URL:** <cipeUrl>
- **Branch:** <branch>
- **Commit:** <commitSha>
- **Failed Tasks:** <failedTaskIds>
- **Verified Tasks:** <verifiedTaskIds>
### Self-Healing Details
- **Enabled:** <selfHealingEnabled>
- **Status:** <selfHealingStatus>
- **Verification:** <verificationStatus>
- **User Action:** <userAction>
- **Classification:** <failureClassification>
- **Confidence:** <confidence>
- **Confidence Reasoning:** <confidenceReasoning>
### Fix Information (if available)
- **Short Link:** <shortLink>
- **Description:** <suggestedFixDescription>
- **Reasoning:** <suggestedFixReasoning>
### Task Output Summary (first page)
<taskOutputSummary>
[MORE_CONTENT_AVAILABLE: taskOutputSummary, pageToken: 1]
### Suggested Fix (first page)
<suggestedFix>
[MORE_CONTENT_AVAILABLE: suggestedFix, pageToken: 1]
```
### Pagination Indicators
When a heavy field has more content available, append indicator:
```
[MORE_CONTENT_AVAILABLE: <fieldName>, pageToken: <nextPage>]
```
Main agent can fetch additional pages if needed using:
```
ci_information({ select: "<fieldName>", pageToken: <nextPage> })
```
Fields that may have pagination:
- `taskOutputSummary` (reverse pagination - page 0 = most recent)
- `suggestedFix` (forward pagination - page 0 = start)
- `suggestedFixReasoning`
### Return Format for `no_new_cipe`
When returning with `status: no_new_cipe`, include additional context:
```
## CI Monitor Result
**Status:** no_new_cipe
**Iterations:** <count>
**Elapsed:** <minutes>m <seconds>s
### Expected CI Attempt Not Found
- **Expected Commit SHA:** <expectedCommitSha>
- **Previous CI Attempt URL:** <previousCipeUrl>
- **Last Seen CI Attempt URL:** <cipeUrl>
- **Last Seen Commit SHA:** <commitSha>
- **New CI Attempt Timeout:** 30 minutes (exceeded)
### Likely Cause
CI workflow failed before Nx tasks could run (e.g., install step, checkout, auth).
Check your CI provider logs for the commit <expectedCommitSha>.
### Last Known CI Attempt State
- **Status:** <cipeStatus>
- **Branch:** <branch>
```
## Status Reporting (Verbosity-Controlled)
Output is controlled by the `verbosity` parameter from the main agent:
| Level | What to Output |
| --------- | ----------------------------------------------------------------- |
| `minimal` | No intermediate output. Only return final result when actionable. |
| `medium` | Output only on significant state changes (not every poll). |
| `verbose` | Output detailed phase information after every poll. |
### Minimal Verbosity
No output during polling. Poll silently and return when done.
### Medium Verbosity (Default)
Output **only when state changes significantly** to save context tokens:
- `cipeStatus` changes (e.g., IN_PROGRESS → FAILED)
- `selfHealingStatus` changes (e.g., IN_PROGRESS → COMPLETED)
- New CI Attempt detected (in wait mode)
Format: single line, no decorators:
```
[CI Monitor] CI: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 4m
```
### Verbose Verbosity
Output detailed phase box after every poll:
```
[CI Monitor] ─────────────────────────────────────────────────────
[CI Monitor] Iteration <N> | Elapsed: <X>m <Y>s
[CI Monitor]
[CI Monitor] CI Status: <cipeStatus>
[CI Monitor] Self-Healing: <selfHealingStatus>
[CI Monitor] Verification: <verificationStatus>
[CI Monitor] Classification: <failureClassification>
[CI Monitor]
[CI Monitor] → <human-readable phase description>
[CI Monitor] ─────────────────────────────────────────────────────
```
### Phase Descriptions (for verbose output)
| Status Combo | Description |
| ----------------------------------------------------------------------------------------- | ------------------------------------------- |
| `cipeStatus: IN_PROGRESS` | "CI running..." |
| `cipeStatus: NOT_STARTED` | "Waiting for CI to start..." |
| `cipeStatus: FAILED` + `selfHealingStatus: NOT_STARTED` | "CI failed. Self-healing starting..." |
| `cipeStatus: FAILED` + `selfHealingStatus: IN_PROGRESS` | "CI failed. Self-healing generating fix..." |
| `cipeStatus: FAILED` + `selfHealingStatus: COMPLETED` + `verificationStatus: IN_PROGRESS` | "Fix generated! Verification running..." |
| `cipeStatus: FAILED` + `selfHealingStatus: COMPLETED` + `verificationStatus: COMPLETED` | "Fix ready! Verified successfully." |
| `cipeStatus: FAILED` + `selfHealingStatus: COMPLETED` + `verificationStatus: FAILED` | "Fix generated but verification failed." |
| `cipeStatus: FAILED` + `selfHealingStatus: FAILED` | "Self-healing could not generate a fix." |
| `cipeStatus: SUCCEEDED` | "CI passed!" |
## Important Notes
- You do NOT make apply/reject decisions - that's the main agent's job
- You do NOT perform git operations
- You only poll and report state
- Respect the `verbosity` parameter for output (default: medium)
- If `ci_information` returns an error, wait and retry (count as failed poll)
- Track consecutive failures - if 5 consecutive failures, return with `status: error`
- When expecting new CI Attempt, track the 30-minute new-CI-Attempt timeout separately from the main polling timeout
+428
View File
@@ -0,0 +1,428 @@
# CI Monitor Command
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
## Context
- **Current Branch:** !`git branch --show-current`
- **Current Commit:** !`git rev-parse --short HEAD`
- **Remote Status:** !`git status -sb | head -1`
## User Instructions
**Important:** If user provides specific instructions, respect them over default behaviors described below.
## Configuration Defaults
| Setting | Default | Description |
| ------------------------- | ------------- | ------------------------------------------------------------------- |
| `--max-cycles` | 10 | Maximum CIPE cycles before timeout |
| `--timeout` | 120 | Maximum duration in minutes |
| `--verbosity` | medium | Output level: minimal, medium, verbose |
| `--branch` | (auto-detect) | Branch to monitor |
| `--subagent-timeout` | 60 | Subagent polling timeout in minutes |
| `--fresh` | false | Ignore previous context, start fresh |
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
## Nx Cloud Connection Check
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
### Step 0: Verify Nx Cloud Connection
1. **Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
2. **If `nx.json` missing OR neither property exists** → exit with:
```
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
```
3. **If connected** → continue to main loop
## Session Context Behavior
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
- **For a completely clean slate:** Exit Claude Code and restart `claude`
## Default Behaviors by Status
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
| Status | Default Behavior |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ci_success` | Exit with success. Log "CI passed successfully!" |
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
| `polling_timeout` | Subagent polling timeout reached. Exit with timeout. |
| `cipe_canceled` | CIPE was canceled. Exit with canceled status. |
| `cipe_timed_out` | CIPE timed out. Exit with timeout status. |
| `error` | Increment `no_progress_count`. If >= 3 → exit with circuit breaker. Otherwise wait 60s and loop. |
### Fix Available Decision Logic
When subagent returns `fix_available`, main agent compares `failedTaskIds` vs `verifiedTaskIds`:
#### Step 1: Categorize Tasks
1. **Verified tasks** = tasks in both `failedTaskIds` AND `verifiedTaskIds`
2. **Unverified tasks** = tasks in `failedTaskIds` but NOT in `verifiedTaskIds`
3. **E2E tasks** = unverified tasks where target contains "e2e" (task format: `<project>:<target>` or `<project>:<target>:<config>`)
4. **Verifiable tasks** = unverified tasks that are NOT e2e
#### Step 2: Determine Path
| Condition | Path |
| --------------------------------------- | ---------------------------------------- |
| No unverified tasks (all verified) | Apply via MCP |
| Unverified tasks exist, but ALL are e2e | Apply via MCP (treat as verified enough) |
| Verifiable tasks exist | Local verification flow |
#### Step 3a: Apply via MCP (fully/e2e-only verified)
- Call `update_self_healing_fix({ shortLink, action: "APPLY" })`
- Record `last_cipe_url`, spawn subagent in wait mode
#### Step 3b: Local Verification Flow
When verifiable (non-e2e) unverified tasks exist:
1. **Detect package manager:**
- `pnpm-lock.yaml` exists → `pnpm nx`
- `yarn.lock` exists → `yarn nx`
- Otherwise → `npx nx`
2. **Run verifiable tasks in parallel:**
- Spawn `general` subagents to run each task concurrently
- Each subagent runs: `<pm> nx run <taskId>`
- Collect pass/fail results from all subagents
3. **Evaluate results:**
| Result | Action |
| ------------------------- | ---------------------------- |
| ALL verifiable tasks pass | Apply via MCP |
| ANY verifiable task fails | Apply-locally + enhance flow |
4. **Apply-locally + enhance flow:**
- Run `nx apply-locally <shortLink>`
- Enhance the code to fix failing tasks
- Run failing tasks again to verify fix
- If still failing → increment `local_verify_count`, loop back to enhance
- If passing → commit and push, record `expected_commit_sha`, spawn subagent in wait mode
5. **Track attempts** (wraps step 4):
- Increment `local_verify_count` after each enhance cycle
- If `local_verify_count >= local_verify_attempts` (default: 3):
- Get code in commit-able state
- Commit and push with message indicating local verification failed
- Report to user:
```
[ci-monitor] Local verification failed after <N> attempts. Pushed to CI for final validation. Failed: <taskIds>
```
- Record `expected_commit_sha`, spawn subagent in wait mode (let CI be final judge)
#### Commit Message Format
```bash
git commit -m "fix(<projects>): <brief description>
Failed tasks: <taskId1>, <taskId2>
Local verification: passed|enhanced|failed-pushing-to-ci"
```
### Unverified Fix Flow (No Verification Attempted)
When `verificationStatus` is `FAILED`, `NOT_EXECUTABLE`, or fix has `couldAutoApplyTasks != true` with no verification:
- Analyze fix content (`suggestedFix`, `suggestedFixReasoning`, `taskOutputSummary`)
- If fix looks correct → apply via MCP
- If fix needs enhancement → use Apply Locally + Enhance Flow above
- If fix is wrong → reject via MCP, fix from scratch, commit, push
### Auto-Apply Eligibility
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
### Apply vs Reject vs Apply Locally
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
### Apply Locally + Enhance Flow
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
2. Make additional changes as needed
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Reject + Fix From Scratch Flow
When the fix is completely wrong:
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
2. Fix the issue from scratch locally
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Environment Issue Handling
When `failureClassification == 'ENVIRONMENT_STATE'`:
1. Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`
2. New CIPE spawns automatically (no local git operations needed)
3. Loop to poll for new CIPE with `previousCipeUrl` set
### No-New-CIPE Handling
When `status == 'no_new_cipe'`:
This means the expected CIPE was never created - CI likely failed before Nx tasks could run.
1. **Report to user:**
```
[ci-monitor] No CI attempt for <sha> after 10 min. Check CI provider for pre-Nx failures (install, checkout, auth). Last CI attempt: <previousCipeUrl>
```
2. **If user configured auto-fix attempts** (e.g., `--auto-fix-workflow`):
- Detect package manager: check for `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`
- Run install to update lockfile:
```bash
pnpm install # or npm install / yarn install
```
- If lockfile changed:
```bash
git add pnpm-lock.yaml # or appropriate lockfile
git commit -m "chore: update lockfile"
git push origin $(git branch --show-current)
```
- Record new commit SHA, loop to poll with `expectedCommitSha`
3. **Otherwise:** Exit with `no_new_cipe` status, providing guidance for user to investigate
## Exit Conditions
Exit the monitoring loop when ANY of these conditions are met:
| Condition | Exit Type |
| ------------------------------------------- | ---------------- |
| CI passes (`cipeStatus == 'SUCCEEDED'`) | Success |
| Max CIPE cycles reached | Timeout |
| Max duration reached | Timeout |
| 3 consecutive no-progress iterations | Circuit breaker |
| No fix available and local fix not possible | Failure |
| No new CIPE and auto-fix not configured | Pre-CIPE failure |
| User cancels | Cancelled |
## Main Loop
### Step 1: Initialize Tracking
```
cycle_count = 0
start_time = now()
no_progress_count = 0
local_verify_count = 0
last_state = null
last_cipe_url = null
expected_commit_sha = null
```
### Step 2: Spawn Subagent
Spawn the `ci-watcher` subagent to poll CI status:
**Fresh start (first spawn, no expected CIPE):**
```
Task(
agent: "ci-watcher",
prompt: "Monitor CI for branch '<branch>'.
Subagent timeout: <subagent-timeout> minutes.
New-CIPE timeout: <new-cipe-timeout> minutes.
Verbosity: <verbosity>."
)
```
**After action that triggers new CIPE (wait mode):**
```
Task(
agent: "ci-watcher",
prompt: "Monitor CI for branch '<branch>'.
Subagent timeout: <subagent-timeout> minutes.
New-CIPE timeout: <new-cipe-timeout> minutes.
Verbosity: <verbosity>.
WAIT MODE: A new CIPE should spawn. Ignore old CIPE until new one appears.
Expected commit SHA: <expected_commit_sha>
Previous CIPE URL: <last_cipe_url>"
)
```
### Step 3: Handle Subagent Response
When subagent returns:
1. Check the returned status
2. Look up default behavior in the table above
3. Check if user instructions override the default
4. Execute the appropriate action
5. **If action expects new CIPE**, update tracking (see Step 3a)
6. If action results in looping, go to Step 2
### Step 3a: Track State for New-CIPE Detection
After actions that should trigger a new CIPE, record state before looping:
| Action | What to Track | Subagent Mode |
| ----------------------------- | --------------------------------------------- | ------------- |
| Fix auto-applying | `last_cipe_url = current cipeUrl` | Wait mode |
| Apply via MCP | `last_cipe_url = current cipeUrl` | Wait mode |
| Apply locally + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Reject + fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Fix failed + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| No fix + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Environment rerun | `last_cipe_url = current cipeUrl` | Wait mode |
| No-new-CIPE + auto-fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
- Subagent will **completely ignore** the old/stale CIPE
- Subagent will only wait for new CIPE to appear
- Subagent will NOT return to main agent with stale CIPE data
- Once new CIPE detected, subagent switches to normal polling
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
### Step 4: Progress Tracking
After each action:
- If state changed significantly → reset `no_progress_count = 0`
- If state unchanged → `no_progress_count++`
- On new CI attempt detected → reset `local_verify_count = 0`
## Status Reporting
Based on verbosity level:
| Level | What to Report |
| --------- | -------------------------------------------------------------------------- |
| `minimal` | Only final result (success/failure/timeout) |
| `medium` | State changes + periodic updates ("Cycle N \| Elapsed: Xm \| Status: ...") |
| `verbose` | All of medium + full subagent responses, git outputs, MCP responses |
## User Instruction Examples
Users can override default behaviors:
| Instruction | Effect |
| ------------------------------------------------ | --------------------------------------------- |
| "never auto-apply" | Always prompt before applying any fix |
| "always ask before git push" | Prompt before each push |
| "reject any fix for e2e tasks" | Auto-reject if `failedTaskIds` contains e2e |
| "apply all fixes regardless of verification" | Skip verification check, apply everything |
| "if confidence < 70, reject" | Check confidence field before applying |
| "run 'nx affected -t typecheck' before applying" | Add local verification step |
| "auto-fix workflow failures" | Attempt lockfile updates on pre-CIPE failures |
| "wait 45 min for new CIPE" | Override new-CIPE timeout (default: 10 min) |
## Error Handling
| Error | Action |
| ------------------------ | ------------------------------------------------------------------------------------- |
| Git rebase conflict | Report to user, exit |
| `nx apply-locally` fails | Report to user, attempt manual patch or exit |
| MCP tool error | Retry once, if fails report to user |
| Subagent spawn failure | Retry once, if fails exit with error |
| No new CIPE detected | If `--auto-fix-workflow`, try lockfile update; otherwise report to user with guidance |
| Lockfile auto-fix fails | Report to user, exit with guidance to check CI logs |
## Example Session
### Example 1: Normal Flow with Self-Healing (medium verbosity)
```
[ci-monitor] Starting CI monitor for branch 'feature/add-auth'
[ci-monitor] Config: max-cycles=5, timeout=120m, verbosity=medium
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] CI attempt: IN_PROGRESS | Self-Healing: NOT_STARTED | Elapsed: 1m
[CI Monitor] CI attempt: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 3m
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 5m
[ci-monitor] Fix available! Verification: COMPLETED
[ci-monitor] Applying fix via MCP...
[ci-monitor] Fix applied in CI. Waiting for new CI attempt...
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] New CI attempt detected!
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 8m
[ci-monitor] CI passed successfully!
[ci-monitor] Summary:
- Total cycles: 2
- Total time: 12m 34s
- Fixes applied: 1
- Result: SUCCESS
```
### Example 2: Pre-CI Failure (medium verbosity)
```
[ci-monitor] Starting CI monitor for branch 'feature/add-products'
[ci-monitor] Config: max-cycles=5, timeout=120m, auto-fix-workflow=true
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 2m
[ci-monitor] Applying fix locally, enhancing, and pushing...
[ci-monitor] Committed: abc1234
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] Waiting for new CI attempt... (expected SHA: abc1234)
[CI Monitor] ⚠️ CI attempt timeout (10 min). Returning no_new_cipe.
[ci-monitor] Status: no_new_cipe
[ci-monitor] --auto-fix-workflow enabled. Attempting lockfile update...
[ci-monitor] Lockfile updated. Committed: def5678
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] New CI attempt detected!
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 18m
[ci-monitor] CI passed successfully!
[ci-monitor] Summary:
- Total cycles: 3
- Total time: 22m 15s
- Fixes applied: 1 (self-healing) + 1 (lockfile)
- Result: SUCCESS
```
+437
View File
@@ -0,0 +1,437 @@
---
name: ci-monitor
description: Monitor Nx Cloud CI pipeline and handle self-healing fixes automatically. Checks for Nx Cloud connection before starting.
---
# CI Monitor Command
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
## Context
- **Current Branch:** !`git branch --show-current`
- **Current Commit:** !`git rev-parse --short HEAD`
- **Remote Status:** !`git status -sb | head -1`
## User Instructions
$ARGUMENTS
**Important:** If user provides specific instructions, respect them over default behaviors described below.
## Configuration Defaults
| Setting | Default | Description |
| ------------------------- | ------------- | ------------------------------------------------------------------- |
| `--max-cycles` | 10 | Maximum CIPE cycles before timeout |
| `--timeout` | 120 | Maximum duration in minutes |
| `--verbosity` | medium | Output level: minimal, medium, verbose |
| `--branch` | (auto-detect) | Branch to monitor |
| `--subagent-timeout` | 60 | Subagent polling timeout in minutes |
| `--fresh` | false | Ignore previous context, start fresh |
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
Parse any overrides from `$ARGUMENTS` and merge with defaults.
## Nx Cloud Connection Check
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
### Step 0: Verify Nx Cloud Connection
1. **Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
2. **If `nx.json` missing OR neither property exists** → exit with:
```
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
```
3. **If connected** → continue to main loop
## Session Context Behavior
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
- **For a completely clean slate:** Exit Claude Code and restart `claude`
## Default Behaviors by Status
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
| Status | Default Behavior |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ci_success` | Exit with success. Log "CI passed successfully!" |
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
| `polling_timeout` | Subagent polling timeout reached. Exit with timeout. |
| `cipe_canceled` | CIPE was canceled. Exit with canceled status. |
| `cipe_timed_out` | CIPE timed out. Exit with timeout status. |
| `error` | Increment `no_progress_count`. If >= 3 → exit with circuit breaker. Otherwise wait 60s and loop. |
### Fix Available Decision Logic
When subagent returns `fix_available`, main agent compares `failedTaskIds` vs `verifiedTaskIds`:
#### Step 1: Categorize Tasks
1. **Verified tasks** = tasks in both `failedTaskIds` AND `verifiedTaskIds`
2. **Unverified tasks** = tasks in `failedTaskIds` but NOT in `verifiedTaskIds`
3. **E2E tasks** = unverified tasks where target contains "e2e" (task format: `<project>:<target>` or `<project>:<target>:<config>`)
4. **Verifiable tasks** = unverified tasks that are NOT e2e
#### Step 2: Determine Path
| Condition | Path |
| --------------------------------------- | ---------------------------------------- |
| No unverified tasks (all verified) | Apply via MCP |
| Unverified tasks exist, but ALL are e2e | Apply via MCP (treat as verified enough) |
| Verifiable tasks exist | Local verification flow |
#### Step 3a: Apply via MCP (fully/e2e-only verified)
- Call `update_self_healing_fix({ shortLink, action: "APPLY" })`
- Record `last_cipe_url`, spawn subagent in wait mode
#### Step 3b: Local Verification Flow
When verifiable (non-e2e) unverified tasks exist:
1. **Detect package manager:**
- `pnpm-lock.yaml` exists → `pnpm nx`
- `yarn.lock` exists → `yarn nx`
- Otherwise → `npx nx`
2. **Run verifiable tasks in parallel:**
- Spawn `general` subagents to run each task concurrently
- Each subagent runs: `<pm> nx run <taskId>`
- Collect pass/fail results from all subagents
3. **Evaluate results:**
| Result | Action |
| ------------------------- | ---------------------------- |
| ALL verifiable tasks pass | Apply via MCP |
| ANY verifiable task fails | Apply-locally + enhance flow |
4. **Apply-locally + enhance flow:**
- Run `nx apply-locally <shortLink>`
- Enhance the code to fix failing tasks
- Run failing tasks again to verify fix
- If still failing → increment `local_verify_count`, loop back to enhance
- If passing → commit and push, record `expected_commit_sha`, spawn subagent in wait mode
5. **Track attempts** (wraps step 4):
- Increment `local_verify_count` after each enhance cycle
- If `local_verify_count >= local_verify_attempts` (default: 3):
- Get code in commit-able state
- Commit and push with message indicating local verification failed
- Report to user:
```
[ci-monitor] Local verification failed after <N> attempts. Pushed to CI for final validation. Failed: <taskIds>
```
- Record `expected_commit_sha`, spawn subagent in wait mode (let CI be final judge)
#### Commit Message Format
```bash
git commit -m "fix(<projects>): <brief description>
Failed tasks: <taskId1>, <taskId2>
Local verification: passed|enhanced|failed-pushing-to-ci"
```
### Unverified Fix Flow (No Verification Attempted)
When `verificationStatus` is `FAILED`, `NOT_EXECUTABLE`, or fix has `couldAutoApplyTasks != true` with no verification:
- Analyze fix content (`suggestedFix`, `suggestedFixReasoning`, `taskOutputSummary`)
- If fix looks correct → apply via MCP
- If fix needs enhancement → use Apply Locally + Enhance Flow above
- If fix is wrong → reject via MCP, fix from scratch, commit, push
### Auto-Apply Eligibility
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
### Apply vs Reject vs Apply Locally
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
### Apply Locally + Enhance Flow
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
2. Make additional changes as needed
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Reject + Fix From Scratch Flow
When the fix is completely wrong:
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
2. Fix the issue from scratch locally
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Environment Issue Handling
When `failureClassification == 'ENVIRONMENT_STATE'`:
1. Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`
2. New CIPE spawns automatically (no local git operations needed)
3. Loop to poll for new CIPE with `previousCipeUrl` set
### No-New-CIPE Handling
When `status == 'no_new_cipe'`:
This means the expected CIPE was never created - CI likely failed before Nx tasks could run.
1. **Report to user:**
```
[ci-monitor] No CI attempt for <sha> after 10 min. Check CI provider for pre-Nx failures (install, checkout, auth). Last CI attempt: <previousCipeUrl>
```
2. **If user configured auto-fix attempts** (e.g., `--auto-fix-workflow`):
- Detect package manager: check for `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`
- Run install to update lockfile:
```bash
pnpm install # or npm install / yarn install
```
- If lockfile changed:
```bash
git add pnpm-lock.yaml # or appropriate lockfile
git commit -m "chore: update lockfile"
git push origin $(git branch --show-current)
```
- Record new commit SHA, loop to poll with `expectedCommitSha`
3. **Otherwise:** Exit with `no_new_cipe` status, providing guidance for user to investigate
## Exit Conditions
Exit the monitoring loop when ANY of these conditions are met:
| Condition | Exit Type |
| ------------------------------------------- | ---------------- |
| CI passes (`cipeStatus == 'SUCCEEDED'`) | Success |
| Max CIPE cycles reached | Timeout |
| Max duration reached | Timeout |
| 3 consecutive no-progress iterations | Circuit breaker |
| No fix available and local fix not possible | Failure |
| No new CIPE and auto-fix not configured | Pre-CIPE failure |
| User cancels | Cancelled |
## Main Loop
### Step 1: Initialize Tracking
```
cycle_count = 0
start_time = now()
no_progress_count = 0
local_verify_count = 0
last_state = null
last_cipe_url = null
expected_commit_sha = null
```
### Step 2: Spawn Subagent
Spawn the `ci-watcher` subagent to poll CI status:
**Fresh start (first spawn, no expected CIPE):**
```
Task(
agent: "ci-watcher",
prompt: "Monitor CI for branch '<branch>'.
Subagent timeout: <subagent-timeout> minutes.
New-CIPE timeout: <new-cipe-timeout> minutes.
Verbosity: <verbosity>."
)
```
**After action that triggers new CIPE (wait mode):**
```
Task(
agent: "ci-watcher",
prompt: "Monitor CI for branch '<branch>'.
Subagent timeout: <subagent-timeout> minutes.
New-CIPE timeout: <new-cipe-timeout> minutes.
Verbosity: <verbosity>.
WAIT MODE: A new CIPE should spawn. Ignore old CIPE until new one appears.
Expected commit SHA: <expected_commit_sha>
Previous CIPE URL: <last_cipe_url>"
)
```
### Step 3: Handle Subagent Response
When subagent returns:
1. Check the returned status
2. Look up default behavior in the table above
3. Check if user instructions override the default
4. Execute the appropriate action
5. **If action expects new CIPE**, update tracking (see Step 3a)
6. If action results in looping, go to Step 2
### Step 3a: Track State for New-CIPE Detection
After actions that should trigger a new CIPE, record state before looping:
| Action | What to Track | Subagent Mode |
| ----------------------------- | --------------------------------------------- | ------------- |
| Fix auto-applying | `last_cipe_url = current cipeUrl` | Wait mode |
| Apply via MCP | `last_cipe_url = current cipeUrl` | Wait mode |
| Apply locally + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Reject + fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Fix failed + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| No fix + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Environment rerun | `last_cipe_url = current cipeUrl` | Wait mode |
| No-new-CIPE + auto-fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
- Subagent will **completely ignore** the old/stale CIPE
- Subagent will only wait for new CIPE to appear
- Subagent will NOT return to main agent with stale CIPE data
- Once new CIPE detected, subagent switches to normal polling
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
### Step 4: Progress Tracking
After each action:
- If state changed significantly → reset `no_progress_count = 0`
- If state unchanged → `no_progress_count++`
- On new CI attempt detected → reset `local_verify_count = 0`
## Status Reporting
Based on verbosity level:
| Level | What to Report |
| --------- | -------------------------------------------------------------------------- |
| `minimal` | Only final result (success/failure/timeout) |
| `medium` | State changes + periodic updates ("Cycle N \| Elapsed: Xm \| Status: ...") |
| `verbose` | All of medium + full subagent responses, git outputs, MCP responses |
## User Instruction Examples
Users can override default behaviors:
| Instruction | Effect |
| ------------------------------------------------ | --------------------------------------------- |
| "never auto-apply" | Always prompt before applying any fix |
| "always ask before git push" | Prompt before each push |
| "reject any fix for e2e tasks" | Auto-reject if `failedTaskIds` contains e2e |
| "apply all fixes regardless of verification" | Skip verification check, apply everything |
| "if confidence < 70, reject" | Check confidence field before applying |
| "run 'nx affected -t typecheck' before applying" | Add local verification step |
| "auto-fix workflow failures" | Attempt lockfile updates on pre-CIPE failures |
| "wait 45 min for new CIPE" | Override new-CIPE timeout (default: 10 min) |
## Error Handling
| Error | Action |
| ------------------------ | ------------------------------------------------------------------------------------- |
| Git rebase conflict | Report to user, exit |
| `nx apply-locally` fails | Report to user, attempt manual patch or exit |
| MCP tool error | Retry once, if fails report to user |
| Subagent spawn failure | Retry once, if fails exit with error |
| No new CIPE detected | If `--auto-fix-workflow`, try lockfile update; otherwise report to user with guidance |
| Lockfile auto-fix fails | Report to user, exit with guidance to check CI logs |
## Example Session
### Example 1: Normal Flow with Self-Healing (medium verbosity)
```
[ci-monitor] Starting CI monitor for branch 'feature/add-auth'
[ci-monitor] Config: max-cycles=5, timeout=120m, verbosity=medium
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] CI attempt: IN_PROGRESS | Self-Healing: NOT_STARTED | Elapsed: 1m
[CI Monitor] CI attempt: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 3m
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 5m
[ci-monitor] Fix available! Verification: COMPLETED
[ci-monitor] Applying fix via MCP...
[ci-monitor] Fix applied in CI. Waiting for new CI attempt...
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] New CI attempt detected!
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 8m
[ci-monitor] CI passed successfully!
[ci-monitor] Summary:
- Total cycles: 2
- Total time: 12m 34s
- Fixes applied: 1
- Result: SUCCESS
```
### Example 2: Pre-CI Failure (medium verbosity)
```
[ci-monitor] Starting CI monitor for branch 'feature/add-products'
[ci-monitor] Config: max-cycles=5, timeout=120m, auto-fix-workflow=true
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 2m
[ci-monitor] Applying fix locally, enhancing, and pushing...
[ci-monitor] Committed: abc1234
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] Waiting for new CI attempt... (expected SHA: abc1234)
[CI Monitor] ⚠️ CI attempt timeout (10 min). Returning no_new_cipe.
[ci-monitor] Status: no_new_cipe
[ci-monitor] --auto-fix-workflow enabled. Attempting lockfile update...
[ci-monitor] Lockfile updated. Committed: def5678
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] New CI attempt detected!
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 18m
[ci-monitor] CI passed successfully!
[ci-monitor] Summary:
- Total cycles: 3
- Total time: 22m 15s
- Fixes applied: 1 (self-healing) + 1 (lockfile)
- Result: SUCCESS
```
+228
View File
@@ -0,0 +1,228 @@
---
name: nx-generate
description: Generate code using nx generators. USE WHEN scaffolding code or transforming existing code - for example creating libraries or applications, or anything else that is boilerplate code or automates repetitive tasks. ALWAYS use this first when generating code with Nx instead of calling MCP tools or running nx generate immediately.
---
# Run Nx Generator
Nx generators are powerful tools that scaffold projects, make automated code migrations or automate repetitive tasks in a monorepo. They ensure consistency across the codebase and reduce boilerplate work.
This skill applies when the user wants to:
- Create new projects like libraries or applications
- Scaffold features or boilerplate code
- Run workspace-specific or custom generators
- Do anything else that an nx generator exists for
## Generator Discovery Flow
### Step 1: List Available Generators
Use the Nx CLI to discover available generators:
- List all generators for a plugin: `npx nx list @nx/react`
- View available plugins: `npx nx list`
This includes:
- Plugin generators (e.g., `@nx/react:library`, `@nx/js:library`)
- Local workspace generators (defined in the repo's own plugins)
### Step 2: Match Generator to User Request
Based on the user's request, identify which generator(s) could fulfill their needs. Consider:
- What artifact type they want to create (library, application, etc.)
- Which framework or technology stack is relevant
- Whether they mentioned specific generator names
**IMPORTANT**: When both a local workspace generator and an external plugin generator could satisfy the request, **always prefer the local workspace generator**. Local generators are customized for the specific repo's patterns and conventions.
It's possible that the user request is something that no Nx generator exists for whatsoever. In this case, you can stop using this skill and try to help the user another way. HOWEVER, the burden of proof for this is high. Before aborting, carefully consider each and every generator that's available. Look into details for any that could be related in any way before making this decision.
## Pre-Execution Checklist
Before running any generator, complete these steps:
### 1. Fetch Generator Schema
Use the `--help` flag to understand all available options:
```bash
npx nx g @nx/react:library --help
```
Pay attention to:
- Required options that must be provided
- Optional options that may be relevant to the user's request
- Default values that might need to be overridden
### 2. Read Generator Source Code
Understanding what the generator actually does helps you:
- Know what files will be created/modified
- Understand any side effects (updating configs, installing deps, etc.)
- Identify options that might not be obvious from the schema
To find generator source code:
- For plugin generators: Use `node -e "console.log(require.resolve('@nx/<plugin>/generators.json'));"` to find the generators.json, then locate the source from there
- If that fails, read directly from `node_modules/<plugin>/generators.json`
- For local generators: They are typically in `tools/generators/` or a local plugin directory. You can search the repo for the generator name to find it.
### 2.5 Reevaluate if the generator is right
Once you have built up an understanding of what the selected generator does, reconsider: Is this the right generator to service the user request?
If not, it's okay to go back to the Generator Discovery Flow and select a different generator before proceeding. If you do, make sure to go through the entire pre-execution checklist once more.
### 3. Understand Repo Context
Before generating, examine the target area of the codebase:
- Look at similar existing artifacts (other libraries, applications, etc.)
- Identify patterns and conventions used in the repo
- Note naming conventions, file structures, and configuration patterns
- Try to match these patterns when configuring the generator
For example, if similar libraries are using a specific test runner, build tool or linter, try to match that if possible.
If projects or other artifacts are organized with a specific naming convention, try to match it.
### 4. Validate Required Options
Ensure all required options have values:
- Map the user's request to generator options
- Infer values from context where possible
- Ask the user for any critical missing information
## Execution
Keep in mind that you might have to prefix things with npx/pnpx/yarn if the user doesn't have nx installed globally.
Many generators will behave differently based on where they are executed. For example, first-party nx library generators use the cwd to determine the directory that the library should be placed in. This is highly important.
### Consider Dry-Run (Optional)
Running with `--dry-run` first is strongly encouraged but not mandatory. Use your judgment:
- For complex generators or unfamiliar territory: do a dry-run first
- For simple, well-understood generators: may proceed directly
- Dry-run shows file names and created/deleted/modified markers, but not content
- There are cases where a generator does not support dry-run (for example if it had to install an npm package) - in that case --dry-run might fail. Don't be discouraged but simply move on to running the generator for real and iterating from there.
### Running the Generator
Execute the generator with:
```bash
nx generate <generator-name> <options> --no-interactive
```
**CRITICAL**: Always include `--no-interactive` to prevent prompts that would hang the execution.
Example:
```bash
nx generate @nx/react:library --name=my-utils --no-interactive
```
### Handling Generator Failures
If the generator fails:
1. **Diagnose the error** - Read the error message carefully
2. **Identify the cause** - Missing options, invalid values, conflicts, etc.
3. **Attempt automatic fix** - Adjust options or resolve conflicts
4. **Retry** - Run the generator again with corrected options
Common failure reasons:
- Missing required options
- Invalid option values
- Conflicting with existing files
- Missing dependencies
- Generator doesn't support certain flag combinations
## Post-Generation
### 1. Modify Generated Code (If Needed)
Generators provide a starting point, but the output may need adjustment to match the user's specific requirements:
- Add or modify functionality as requested
- Adjust imports, exports, or configurations
- Integrate with existing code patterns in the repo
### 2. Format Code
Run formatting on all generated/modified files:
```bash
nx format --fix
```
Languages other than javascript/typescript might need other formatting invocations too.
### 3. Run Verification
Verify that the generated code works correctly. What this looks like will vary depending on the type of generator and the targets available.
If the generator created a new project, run its targets directly
Use your best judgement to determine what needs to be verified.
Example:
```bash
nx lint <new-project>
nx test <new-project>
nx build <new-project>
```
### 4. Handle Verification Failures
When verification fails:
**If scope is manageable** (a few lint errors, minor type issues):
- Fix the issues
- Re-run verification to confirm
**If issues are extensive** (many errors, complex problems):
- Attempt simple, obvious fixes first
- If still failing, escalate to the user with:
- Description of what was generated
- What verification is failing
- What you've attempted to fix
- Remaining issues that need user input
## Error Handling
### Generator Failures
- Check the error message for specific causes
- Verify all required options are provided
- Check for conflicts with existing files
- Ensure the generator name and options are correct
### Missing Options
- Consult the generator schema for required fields
- Infer values from context when reasonable
- Ask the user for values that cannot be inferred
## Key Principles
1. **Local generators first** - Always prefer workspace/local generators over external plugin generators when both could work
2. **Understand before running** - Read both the schema AND the source code to fully understand what will happen
3. **No prompts** - Always use `--no-interactive` to prevent hanging
4. **Generators are starting points** - Modify the output as needed to fully satisfy the user's requirements
5. **Verify changes work** - Don't just generate; ensure the code builds, lints, and tests pass
6. **Be proactive about fixes** - Don't just report errors; attempt to resolve them automatically when possible
7. **Match repo patterns** - Study existing similar code in the repo and match its conventions
+9
View File
@@ -0,0 +1,9 @@
---
name: nx-plugins
description: Find and add Nx plugins. USE WHEN user wants to discover available plugins, install a new plugin, or add support for a specific framework or technology to the workspace.
---
## Finding and Installing new plugins
- List plugins: `pnpm nx list`
- Install plugins `pnpm nx add <plugin>`. Example: `pnpm nx add @nx/react`.
+58
View File
@@ -0,0 +1,58 @@
---
name: nx-run-tasks
description: Helps with running tasks in an Nx workspace. USE WHEN the user wants to execute build, test, lint, serve, or run any other tasks defined in the workspace.
---
You can run tasks with Nx in the following way.
Keep in mind that you might have to prefix things with npx/pnpx/yarn if the user doesn't have nx installed globally. Look at the package.json or lockfile to determine which package manager is in use.
For more details on any command, run it with `--help` (e.g. `nx run-many --help`, `nx affected --help`).
## Understand which tasks can be run
You can check those via `nx show project <projectname> --json`, for example `nx show project myapp --json`. It contains a `targets` section which has information about targets that can be run. You can also just look at the `package.json` scripts or `project.json` targets, but you might miss out on inferred tasks by Nx plugins.
## Run a single task
```
nx run <project>:<task>
```
where `project` is the project name defined in `package.json` or `project.json` (if present).
## Run multiple tasks
```
nx run-many -t build test lint typecheck
```
You can pass a `-p` flag to filter to specific projects, otherwise it runs on all projects. You can also use `--exclude` to exclude projects, and `--parallel` to control the number of parallel processes (default is 3).
Examples:
- `nx run-many -t test -p proj1 proj2` — test specific projects
- `nx run-many -t test --projects=*-app --exclude=excluded-app` — test projects matching a pattern
- `nx run-many -t test --projects=tag:api-*` — test projects by tag
## Run tasks for affected projects
Use `nx affected` to only run tasks on projects that have been changed and projects that depend on changed projects. This is especially useful in CI and for large workspaces.
```
nx affected -t build test lint
```
By default it compares against the base branch. You can customize this:
- `nx affected -t test --base=main --head=HEAD` — compare against a specific base and head
- `nx affected -t test --files=libs/mylib/src/index.ts` — specify changed files directly
## Useful flags
These flags work with `run`, `run-many`, and `affected`:
- `--skipNxCache` — rerun tasks even when results are cached
- `--verbose` — print additional information such as stack traces
- `--nxBail` — stop execution after the first failed task
- `--configuration=<name>` — use a specific configuration (e.g. `production`)
+186
View File
@@ -0,0 +1,186 @@
---
name: nx-workspace
description: "Explore and understand Nx workspaces. USE WHEN answering any questions about the nx workspace, the projects in it or tasks to run. EXAMPLES: 'What projects are in this workspace?', 'How is project X configured?', 'What targets can I run?', 'What's affected by my changes?', 'Which projects depend on library Y?', or any questions about Nx workspace structure, project configuration, or available tasks."
---
# Nx Workspace Exploration
This skill provides read-only exploration of Nx workspaces. Use it to understand workspace structure, project configuration, available targets, and dependencies.
Keep in mind that you might have to prefix commands with `npx`/`pnpx`/`yarn` if nx isn't installed globally. Check the lockfile to determine the package manager in use.
## Listing Projects
Use `nx show projects` to list projects in the workspace.
```bash
# List all projects
nx show projects
# Filter by pattern (glob)
nx show projects --projects "apps/*"
nx show projects --projects "shared-*"
# Filter by project type
nx show projects --type app
nx show projects --type lib
nx show projects --type e2e
# Filter by target (projects that have a specific target)
nx show projects --withTarget build
nx show projects --withTarget e2e
# Find affected projects (changed since base branch)
nx show projects --affected
nx show projects --affected --base=main
nx show projects --affected --type app
# Combine filters
nx show projects --type lib --withTarget test
nx show projects --affected --exclude="*-e2e"
# Output as JSON
nx show projects --json
```
## Project Configuration
Use `nx show project <name> --json` to get the full resolved configuration for a project.
**Important**: Do NOT read `project.json` directly - it only contains partial configuration. The `nx show project` command returns the full resolved config including inferred targets from plugins.
You can read the full project schema at `node_modules/nx/schemas/project-schema.json` to understand nx project configuration options.
```bash
# Get full project configuration
nx show project my-app --json
# Extract specific parts from the JSON
nx show project my-app --json | jq '.targets'
nx show project my-app --json | jq '.targets.build'
nx show project my-app --json | jq '.targets | keys'
# Check project metadata
nx show project my-app --json | jq '{name, root, sourceRoot, projectType, tags}'
```
## Target Information
Targets define what tasks can be run on a project.
```bash
# List all targets for a project
nx show project my-app --json | jq '.targets | keys'
# Get full target configuration
nx show project my-app --json | jq '.targets.build'
# Check target executor/command
nx show project my-app --json | jq '.targets.build.executor'
nx show project my-app --json | jq '.targets.build.command'
# View target options
nx show project my-app --json | jq '.targets.build.options'
# Check target inputs/outputs (for caching)
nx show project my-app --json | jq '.targets.build.inputs'
nx show project my-app --json | jq '.targets.build.outputs'
# Find projects with a specific target
nx show projects --withTarget serve
nx show projects --withTarget e2e
```
## Workspace Configuration
Read `nx.json` directly for workspace-level configuration.
You can read the full project schema at `node_modules/nx/schemas/nx-schema.json` to understand nx project configuration options.
```bash
# Read the full nx.json
cat nx.json
# Or use jq for specific sections
cat nx.json | jq '.targetDefaults'
cat nx.json | jq '.namedInputs'
cat nx.json | jq '.plugins'
cat nx.json | jq '.generators'
```
Key nx.json sections:
- `targetDefaults` - Default configuration applied to all targets of a given name
- `namedInputs` - Reusable input definitions for caching
- `plugins` - Nx plugins and their configuration
- ...and much more, read the schema or nx.json for details
## Affected Projects
Find projects affected by changes in the current branch.
```bash
# Affected since base branch (auto-detected)
nx show projects --affected
# Affected with explicit base
nx show projects --affected --base=main
nx show projects --affected --base=origin/main
# Affected between two commits
nx show projects --affected --base=abc123 --head=def456
# Affected apps only
nx show projects --affected --type app
# Affected excluding e2e projects
nx show projects --affected --exclude="*-e2e"
# Affected by uncommitted changes
nx show projects --affected --uncommitted
# Affected by untracked files
nx show projects --affected --untracked
```
## Common Exploration Patterns
### "What's in this workspace?"
```bash
nx show projects
nx show projects --type app
nx show projects --type lib
```
### "How do I build/test/lint project X?"
```bash
nx show project X --json | jq '.targets | keys'
nx show project X --json | jq '.targets.build'
```
### "What depends on library Y?"
```bash
# Find projects that may depend on Y by searching for imports
# (Nx doesn't have a direct "dependents" command via CLI)
grep -r "from '@myorg/Y'" --include="*.ts" --include="*.tsx" apps/ libs/
```
### "What configuration options are available?"
```bash
cat node_modules/nx/schemas/nx-schema.json | jq '.properties | keys'
cat node_modules/nx/schemas/project-schema.json | jq '.properties | keys'
```
### "Why is project X affected?"
```bash
# Check what files changed
git diff --name-only main
# See which project owns those files
nx show project X --json | jq '.root'
```
-1
View File
@@ -1 +0,0 @@
node_modules
-101
View File
@@ -1,101 +0,0 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"env": {
"node": true
},
"ignorePatterns": ["**/*.ts", "**/test-output"],
"plugins": ["@typescript-eslint", "@nx"],
"extends": ["plugin:storybook/recommended"],
"rules": {
"@typescript-eslint/explicit-module-boundary-types": "off",
"no-restricted-imports": [
"error",
{
"paths": [
{
"name": "create-nx-workspace",
"message": "Please import utils from nx or @nx/devkit instead."
},
{
"name": "node-fetch",
"message": "Please default to native fetch instead of 'node-fetch'."
}
]
}
],
"@typescript-eslint/no-restricted-imports": [
"error",
{
"patterns": [
{
"group": ["nx/src/plugins/js*"],
"message": "Imports from 'nx/src/plugins/js' are not allowed. Use '@nx/js' instead"
},
{
"group": ["**/native-bindings", "**/native-bindings.js", ""],
"message": "Direct imports from native-bindings.js are not allowed. Import from index.js instead."
}
]
}
],
"storybook/no-uninstalled-addons": [
"error",
{
"ignore": ["@nx/react/plugins/storybook"],
"packageJsonLocation": "../../package.json"
}
]
},
"overrides": [
{
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
"rules": {}
},
{
"files": ["**/executors/**/schema.json", "**/generators/**/schema.json"],
"rules": {
"@nx/workspace/valid-schema-description": "error"
}
},
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"checkDynamicDependenciesExceptions": [".*"],
"allow": [],
"depConstraints": [
{
"sourceTag": "*",
"onlyDependOnLibsWithTags": ["*"]
}
]
}
],
"@nx/workspace/valid-command-object": "error"
}
},
{
"files": ["pnpm-lock.yaml"],
"parser": "./tools/eslint-rules/raw-file-parser.js",
"rules": {
"@nx/workspace/ensure-pnpm-lock-version": [
"error",
{
"version": "9.0"
}
]
}
},
{
"files": ["*.ts"],
"rules": {
"@angular-eslint/prefer-standalone": "off"
}
}
]
}
+438
View File
@@ -0,0 +1,438 @@
description = "Monitor Nx Cloud CI pipeline and handle self-healing fixes automatically. Checks for Nx Cloud connection before starting."
prompt = """
# CI Monitor Command
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
## Context
- **Current Branch:** !`git branch --show-current`
- **Current Commit:** !`git rev-parse --short HEAD`
- **Remote Status:** !`git status -sb | head -1`
## User Instructions
{{args}}
**Important:** If user provides specific instructions, respect them over default behaviors described below.
## Configuration Defaults
| Setting | Default | Description |
| ------------------------- | ------------- | ------------------------------------------------------------------- |
| `--max-cycles` | 10 | Maximum CIPE cycles before timeout |
| `--timeout` | 120 | Maximum duration in minutes |
| `--verbosity` | medium | Output level: minimal, medium, verbose |
| `--branch` | (auto-detect) | Branch to monitor |
| `--subagent-timeout` | 60 | Subagent polling timeout in minutes |
| `--fresh` | false | Ignore previous context, start fresh |
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
Parse any overrides from `{{args}}` and merge with defaults.
## Nx Cloud Connection Check
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
### Step 0: Verify Nx Cloud Connection
1. **Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
2. **If `nx.json` missing OR neither property exists** → exit with:
```
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
```
3. **If connected** → continue to main loop
## Session Context Behavior
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
- **For a completely clean slate:** Exit Claude Code and restart `claude`
## Default Behaviors by Status
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
| Status | Default Behavior |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ci_success` | Exit with success. Log "CI passed successfully!" |
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
| `polling_timeout` | Subagent polling timeout reached. Exit with timeout. |
| `cipe_canceled` | CIPE was canceled. Exit with canceled status. |
| `cipe_timed_out` | CIPE timed out. Exit with timeout status. |
| `error` | Increment `no_progress_count`. If >= 3 → exit with circuit breaker. Otherwise wait 60s and loop. |
### Fix Available Decision Logic
When subagent returns `fix_available`, main agent compares `failedTaskIds` vs `verifiedTaskIds`:
#### Step 1: Categorize Tasks
1. **Verified tasks** = tasks in both `failedTaskIds` AND `verifiedTaskIds`
2. **Unverified tasks** = tasks in `failedTaskIds` but NOT in `verifiedTaskIds`
3. **E2E tasks** = unverified tasks where target contains "e2e" (task format: `<project>:<target>` or `<project>:<target>:<config>`)
4. **Verifiable tasks** = unverified tasks that are NOT e2e
#### Step 2: Determine Path
| Condition | Path |
| --------------------------------------- | ---------------------------------------- |
| No unverified tasks (all verified) | Apply via MCP |
| Unverified tasks exist, but ALL are e2e | Apply via MCP (treat as verified enough) |
| Verifiable tasks exist | Local verification flow |
#### Step 3a: Apply via MCP (fully/e2e-only verified)
- Call `update_self_healing_fix({ shortLink, action: "APPLY" })`
- Record `last_cipe_url`, spawn subagent in wait mode
#### Step 3b: Local Verification Flow
When verifiable (non-e2e) unverified tasks exist:
1. **Detect package manager:**
- `pnpm-lock.yaml` exists → `pnpm nx`
- `yarn.lock` exists → `yarn nx`
- Otherwise → `npx nx`
2. **Run verifiable tasks in parallel:**
- Spawn `general` subagents to run each task concurrently
- Each subagent runs: `<pm> nx run <taskId>`
- Collect pass/fail results from all subagents
3. **Evaluate results:**
| Result | Action |
| ------------------------- | ---------------------------- |
| ALL verifiable tasks pass | Apply via MCP |
| ANY verifiable task fails | Apply-locally + enhance flow |
4. **Apply-locally + enhance flow:**
- Run `nx apply-locally <shortLink>`
- Enhance the code to fix failing tasks
- Run failing tasks again to verify fix
- If still failing → increment `local_verify_count`, loop back to enhance
- If passing → commit and push, record `expected_commit_sha`, spawn subagent in wait mode
5. **Track attempts** (wraps step 4):
- Increment `local_verify_count` after each enhance cycle
- If `local_verify_count >= local_verify_attempts` (default: 3):
- Get code in commit-able state
- Commit and push with message indicating local verification failed
- Report to user:
```
[ci-monitor] Local verification failed after <N> attempts. Pushed to CI for final validation. Failed: <taskIds>
```
- Record `expected_commit_sha`, spawn subagent in wait mode (let CI be final judge)
#### Commit Message Format
```bash
git commit -m "fix(<projects>): <brief description>
Failed tasks: <taskId1>, <taskId2>
Local verification: passed|enhanced|failed-pushing-to-ci"
```
### Unverified Fix Flow (No Verification Attempted)
When `verificationStatus` is `FAILED`, `NOT_EXECUTABLE`, or fix has `couldAutoApplyTasks != true` with no verification:
- Analyze fix content (`suggestedFix`, `suggestedFixReasoning`, `taskOutputSummary`)
- If fix looks correct → apply via MCP
- If fix needs enhancement → use Apply Locally + Enhance Flow above
- If fix is wrong → reject via MCP, fix from scratch, commit, push
### Auto-Apply Eligibility
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
### Apply vs Reject vs Apply Locally
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
### Apply Locally + Enhance Flow
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
2. Make additional changes as needed
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Reject + Fix From Scratch Flow
When the fix is completely wrong:
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
2. Fix the issue from scratch locally
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Environment Issue Handling
When `failureClassification == 'ENVIRONMENT_STATE'`:
1. Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`
2. New CIPE spawns automatically (no local git operations needed)
3. Loop to poll for new CIPE with `previousCipeUrl` set
### No-New-CIPE Handling
When `status == 'no_new_cipe'`:
This means the expected CIPE was never created - CI likely failed before Nx tasks could run.
1. **Report to user:**
```
[ci-monitor] No CI attempt for <sha> after 10 min. Check CI provider for pre-Nx failures (install, checkout, auth). Last CI attempt: <previousCipeUrl>
```
2. **If user configured auto-fix attempts** (e.g., `--auto-fix-workflow`):
- Detect package manager: check for `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`
- Run install to update lockfile:
```bash
pnpm install # or npm install / yarn install
```
- If lockfile changed:
```bash
git add pnpm-lock.yaml # or appropriate lockfile
git commit -m "chore: update lockfile"
git push origin $(git branch --show-current)
```
- Record new commit SHA, loop to poll with `expectedCommitSha`
3. **Otherwise:** Exit with `no_new_cipe` status, providing guidance for user to investigate
## Exit Conditions
Exit the monitoring loop when ANY of these conditions are met:
| Condition | Exit Type |
| ------------------------------------------- | ---------------- |
| CI passes (`cipeStatus == 'SUCCEEDED'`) | Success |
| Max CIPE cycles reached | Timeout |
| Max duration reached | Timeout |
| 3 consecutive no-progress iterations | Circuit breaker |
| No fix available and local fix not possible | Failure |
| No new CIPE and auto-fix not configured | Pre-CIPE failure |
| User cancels | Cancelled |
## Main Loop
### Step 1: Initialize Tracking
```
cycle_count = 0
start_time = now()
no_progress_count = 0
local_verify_count = 0
last_state = null
last_cipe_url = null
expected_commit_sha = null
```
### Step 2: Spawn Subagent
Spawn the `ci-watcher` subagent to poll CI status:
**Fresh start (first spawn, no expected CIPE):**
```
Task(
agent: "ci-watcher",
prompt: "Monitor CI for branch '<branch>'.
Subagent timeout: <subagent-timeout> minutes.
New-CIPE timeout: <new-cipe-timeout> minutes.
Verbosity: <verbosity>."
)
```
**After action that triggers new CIPE (wait mode):**
```
Task(
agent: "ci-watcher",
prompt: "Monitor CI for branch '<branch>'.
Subagent timeout: <subagent-timeout> minutes.
New-CIPE timeout: <new-cipe-timeout> minutes.
Verbosity: <verbosity>.
WAIT MODE: A new CIPE should spawn. Ignore old CIPE until new one appears.
Expected commit SHA: <expected_commit_sha>
Previous CIPE URL: <last_cipe_url>"
)
```
### Step 3: Handle Subagent Response
When subagent returns:
1. Check the returned status
2. Look up default behavior in the table above
3. Check if user instructions override the default
4. Execute the appropriate action
5. **If action expects new CIPE**, update tracking (see Step 3a)
6. If action results in looping, go to Step 2
### Step 3a: Track State for New-CIPE Detection
After actions that should trigger a new CIPE, record state before looping:
| Action | What to Track | Subagent Mode |
| ----------------------------- | --------------------------------------------- | ------------- |
| Fix auto-applying | `last_cipe_url = current cipeUrl` | Wait mode |
| Apply via MCP | `last_cipe_url = current cipeUrl` | Wait mode |
| Apply locally + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Reject + fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Fix failed + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| No fix + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Environment rerun | `last_cipe_url = current cipeUrl` | Wait mode |
| No-new-CIPE + auto-fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
- Subagent will **completely ignore** the old/stale CIPE
- Subagent will only wait for new CIPE to appear
- Subagent will NOT return to main agent with stale CIPE data
- Once new CIPE detected, subagent switches to normal polling
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
### Step 4: Progress Tracking
After each action:
- If state changed significantly → reset `no_progress_count = 0`
- If state unchanged → `no_progress_count++`
- On new CI attempt detected → reset `local_verify_count = 0`
## Status Reporting
Based on verbosity level:
| Level | What to Report |
| --------- | -------------------------------------------------------------------------- |
| `minimal` | Only final result (success/failure/timeout) |
| `medium` | State changes + periodic updates ("Cycle N \\| Elapsed: Xm \\| Status: ...") |
| `verbose` | All of medium + full subagent responses, git outputs, MCP responses |
## User Instruction Examples
Users can override default behaviors:
| Instruction | Effect |
| ------------------------------------------------ | --------------------------------------------- |
| "never auto-apply" | Always prompt before applying any fix |
| "always ask before git push" | Prompt before each push |
| "reject any fix for e2e tasks" | Auto-reject if `failedTaskIds` contains e2e |
| "apply all fixes regardless of verification" | Skip verification check, apply everything |
| "if confidence < 70, reject" | Check confidence field before applying |
| "run 'nx affected -t typecheck' before applying" | Add local verification step |
| "auto-fix workflow failures" | Attempt lockfile updates on pre-CIPE failures |
| "wait 45 min for new CIPE" | Override new-CIPE timeout (default: 10 min) |
## Error Handling
| Error | Action |
| ------------------------ | ------------------------------------------------------------------------------------- |
| Git rebase conflict | Report to user, exit |
| `nx apply-locally` fails | Report to user, attempt manual patch or exit |
| MCP tool error | Retry once, if fails report to user |
| Subagent spawn failure | Retry once, if fails exit with error |
| No new CIPE detected | If `--auto-fix-workflow`, try lockfile update; otherwise report to user with guidance |
| Lockfile auto-fix fails | Report to user, exit with guidance to check CI logs |
## Example Session
### Example 1: Normal Flow with Self-Healing (medium verbosity)
```
[ci-monitor] Starting CI monitor for branch 'feature/add-auth'
[ci-monitor] Config: max-cycles=5, timeout=120m, verbosity=medium
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] CI attempt: IN_PROGRESS | Self-Healing: NOT_STARTED | Elapsed: 1m
[CI Monitor] CI attempt: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 3m
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 5m
[ci-monitor] Fix available! Verification: COMPLETED
[ci-monitor] Applying fix via MCP...
[ci-monitor] Fix applied in CI. Waiting for new CI attempt...
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] New CI attempt detected!
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 8m
[ci-monitor] CI passed successfully!
[ci-monitor] Summary:
- Total cycles: 2
- Total time: 12m 34s
- Fixes applied: 1
- Result: SUCCESS
```
### Example 2: Pre-CI Failure (medium verbosity)
```
[ci-monitor] Starting CI monitor for branch 'feature/add-products'
[ci-monitor] Config: max-cycles=5, timeout=120m, auto-fix-workflow=true
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 2m
[ci-monitor] Applying fix locally, enhancing, and pushing...
[ci-monitor] Committed: abc1234
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] Waiting for new CI attempt... (expected SHA: abc1234)
[CI Monitor] ⚠️ CI attempt timeout (10 min). Returning no_new_cipe.
[ci-monitor] Status: no_new_cipe
[ci-monitor] --auto-fix-workflow enabled. Attempting lockfile update...
[ci-monitor] Lockfile updated. Committed: def5678
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] New CI attempt detected!
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 18m
[ci-monitor] CI passed successfully!
[ci-monitor] Summary:
- Total cycles: 3
- Total time: 22m 15s
- Fixes applied: 1 (self-healing) + 1 (lockfile)
- Result: SUCCESS
```"""
+437
View File
@@ -0,0 +1,437 @@
---
name: ci-monitor
description: Monitor Nx Cloud CI pipeline and handle self-healing fixes automatically. Checks for Nx Cloud connection before starting.
---
# CI Monitor Command
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
## Context
- **Current Branch:** !`git branch --show-current`
- **Current Commit:** !`git rev-parse --short HEAD`
- **Remote Status:** !`git status -sb | head -1`
## User Instructions
$ARGUMENTS
**Important:** If user provides specific instructions, respect them over default behaviors described below.
## Configuration Defaults
| Setting | Default | Description |
| ------------------------- | ------------- | ------------------------------------------------------------------- |
| `--max-cycles` | 10 | Maximum CIPE cycles before timeout |
| `--timeout` | 120 | Maximum duration in minutes |
| `--verbosity` | medium | Output level: minimal, medium, verbose |
| `--branch` | (auto-detect) | Branch to monitor |
| `--subagent-timeout` | 60 | Subagent polling timeout in minutes |
| `--fresh` | false | Ignore previous context, start fresh |
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
Parse any overrides from `$ARGUMENTS` and merge with defaults.
## Nx Cloud Connection Check
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
### Step 0: Verify Nx Cloud Connection
1. **Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
2. **If `nx.json` missing OR neither property exists** → exit with:
```
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
```
3. **If connected** → continue to main loop
## Session Context Behavior
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
- **For a completely clean slate:** Exit Claude Code and restart `claude`
## Default Behaviors by Status
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
| Status | Default Behavior |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ci_success` | Exit with success. Log "CI passed successfully!" |
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
| `polling_timeout` | Subagent polling timeout reached. Exit with timeout. |
| `cipe_canceled` | CIPE was canceled. Exit with canceled status. |
| `cipe_timed_out` | CIPE timed out. Exit with timeout status. |
| `error` | Increment `no_progress_count`. If >= 3 → exit with circuit breaker. Otherwise wait 60s and loop. |
### Fix Available Decision Logic
When subagent returns `fix_available`, main agent compares `failedTaskIds` vs `verifiedTaskIds`:
#### Step 1: Categorize Tasks
1. **Verified tasks** = tasks in both `failedTaskIds` AND `verifiedTaskIds`
2. **Unverified tasks** = tasks in `failedTaskIds` but NOT in `verifiedTaskIds`
3. **E2E tasks** = unverified tasks where target contains "e2e" (task format: `<project>:<target>` or `<project>:<target>:<config>`)
4. **Verifiable tasks** = unverified tasks that are NOT e2e
#### Step 2: Determine Path
| Condition | Path |
| --------------------------------------- | ---------------------------------------- |
| No unverified tasks (all verified) | Apply via MCP |
| Unverified tasks exist, but ALL are e2e | Apply via MCP (treat as verified enough) |
| Verifiable tasks exist | Local verification flow |
#### Step 3a: Apply via MCP (fully/e2e-only verified)
- Call `update_self_healing_fix({ shortLink, action: "APPLY" })`
- Record `last_cipe_url`, spawn subagent in wait mode
#### Step 3b: Local Verification Flow
When verifiable (non-e2e) unverified tasks exist:
1. **Detect package manager:**
- `pnpm-lock.yaml` exists → `pnpm nx`
- `yarn.lock` exists → `yarn nx`
- Otherwise → `npx nx`
2. **Run verifiable tasks in parallel:**
- Spawn `general` subagents to run each task concurrently
- Each subagent runs: `<pm> nx run <taskId>`
- Collect pass/fail results from all subagents
3. **Evaluate results:**
| Result | Action |
| ------------------------- | ---------------------------- |
| ALL verifiable tasks pass | Apply via MCP |
| ANY verifiable task fails | Apply-locally + enhance flow |
4. **Apply-locally + enhance flow:**
- Run `nx apply-locally <shortLink>`
- Enhance the code to fix failing tasks
- Run failing tasks again to verify fix
- If still failing → increment `local_verify_count`, loop back to enhance
- If passing → commit and push, record `expected_commit_sha`, spawn subagent in wait mode
5. **Track attempts** (wraps step 4):
- Increment `local_verify_count` after each enhance cycle
- If `local_verify_count >= local_verify_attempts` (default: 3):
- Get code in commit-able state
- Commit and push with message indicating local verification failed
- Report to user:
```
[ci-monitor] Local verification failed after <N> attempts. Pushed to CI for final validation. Failed: <taskIds>
```
- Record `expected_commit_sha`, spawn subagent in wait mode (let CI be final judge)
#### Commit Message Format
```bash
git commit -m "fix(<projects>): <brief description>
Failed tasks: <taskId1>, <taskId2>
Local verification: passed|enhanced|failed-pushing-to-ci"
```
### Unverified Fix Flow (No Verification Attempted)
When `verificationStatus` is `FAILED`, `NOT_EXECUTABLE`, or fix has `couldAutoApplyTasks != true` with no verification:
- Analyze fix content (`suggestedFix`, `suggestedFixReasoning`, `taskOutputSummary`)
- If fix looks correct → apply via MCP
- If fix needs enhancement → use Apply Locally + Enhance Flow above
- If fix is wrong → reject via MCP, fix from scratch, commit, push
### Auto-Apply Eligibility
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
### Apply vs Reject vs Apply Locally
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
### Apply Locally + Enhance Flow
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
2. Make additional changes as needed
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Reject + Fix From Scratch Flow
When the fix is completely wrong:
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
2. Fix the issue from scratch locally
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Environment Issue Handling
When `failureClassification == 'ENVIRONMENT_STATE'`:
1. Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`
2. New CIPE spawns automatically (no local git operations needed)
3. Loop to poll for new CIPE with `previousCipeUrl` set
### No-New-CIPE Handling
When `status == 'no_new_cipe'`:
This means the expected CIPE was never created - CI likely failed before Nx tasks could run.
1. **Report to user:**
```
[ci-monitor] No CI attempt for <sha> after 10 min. Check CI provider for pre-Nx failures (install, checkout, auth). Last CI attempt: <previousCipeUrl>
```
2. **If user configured auto-fix attempts** (e.g., `--auto-fix-workflow`):
- Detect package manager: check for `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`
- Run install to update lockfile:
```bash
pnpm install # or npm install / yarn install
```
- If lockfile changed:
```bash
git add pnpm-lock.yaml # or appropriate lockfile
git commit -m "chore: update lockfile"
git push origin $(git branch --show-current)
```
- Record new commit SHA, loop to poll with `expectedCommitSha`
3. **Otherwise:** Exit with `no_new_cipe` status, providing guidance for user to investigate
## Exit Conditions
Exit the monitoring loop when ANY of these conditions are met:
| Condition | Exit Type |
| ------------------------------------------- | ---------------- |
| CI passes (`cipeStatus == 'SUCCEEDED'`) | Success |
| Max CIPE cycles reached | Timeout |
| Max duration reached | Timeout |
| 3 consecutive no-progress iterations | Circuit breaker |
| No fix available and local fix not possible | Failure |
| No new CIPE and auto-fix not configured | Pre-CIPE failure |
| User cancels | Cancelled |
## Main Loop
### Step 1: Initialize Tracking
```
cycle_count = 0
start_time = now()
no_progress_count = 0
local_verify_count = 0
last_state = null
last_cipe_url = null
expected_commit_sha = null
```
### Step 2: Spawn Subagent
Spawn the `ci-watcher` subagent to poll CI status:
**Fresh start (first spawn, no expected CIPE):**
```
Task(
agent: "ci-watcher",
prompt: "Monitor CI for branch '<branch>'.
Subagent timeout: <subagent-timeout> minutes.
New-CIPE timeout: <new-cipe-timeout> minutes.
Verbosity: <verbosity>."
)
```
**After action that triggers new CIPE (wait mode):**
```
Task(
agent: "ci-watcher",
prompt: "Monitor CI for branch '<branch>'.
Subagent timeout: <subagent-timeout> minutes.
New-CIPE timeout: <new-cipe-timeout> minutes.
Verbosity: <verbosity>.
WAIT MODE: A new CIPE should spawn. Ignore old CIPE until new one appears.
Expected commit SHA: <expected_commit_sha>
Previous CIPE URL: <last_cipe_url>"
)
```
### Step 3: Handle Subagent Response
When subagent returns:
1. Check the returned status
2. Look up default behavior in the table above
3. Check if user instructions override the default
4. Execute the appropriate action
5. **If action expects new CIPE**, update tracking (see Step 3a)
6. If action results in looping, go to Step 2
### Step 3a: Track State for New-CIPE Detection
After actions that should trigger a new CIPE, record state before looping:
| Action | What to Track | Subagent Mode |
| ----------------------------- | --------------------------------------------- | ------------- |
| Fix auto-applying | `last_cipe_url = current cipeUrl` | Wait mode |
| Apply via MCP | `last_cipe_url = current cipeUrl` | Wait mode |
| Apply locally + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Reject + fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Fix failed + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| No fix + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Environment rerun | `last_cipe_url = current cipeUrl` | Wait mode |
| No-new-CIPE + auto-fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
- Subagent will **completely ignore** the old/stale CIPE
- Subagent will only wait for new CIPE to appear
- Subagent will NOT return to main agent with stale CIPE data
- Once new CIPE detected, subagent switches to normal polling
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
### Step 4: Progress Tracking
After each action:
- If state changed significantly → reset `no_progress_count = 0`
- If state unchanged → `no_progress_count++`
- On new CI attempt detected → reset `local_verify_count = 0`
## Status Reporting
Based on verbosity level:
| Level | What to Report |
| --------- | -------------------------------------------------------------------------- |
| `minimal` | Only final result (success/failure/timeout) |
| `medium` | State changes + periodic updates ("Cycle N \| Elapsed: Xm \| Status: ...") |
| `verbose` | All of medium + full subagent responses, git outputs, MCP responses |
## User Instruction Examples
Users can override default behaviors:
| Instruction | Effect |
| ------------------------------------------------ | --------------------------------------------- |
| "never auto-apply" | Always prompt before applying any fix |
| "always ask before git push" | Prompt before each push |
| "reject any fix for e2e tasks" | Auto-reject if `failedTaskIds` contains e2e |
| "apply all fixes regardless of verification" | Skip verification check, apply everything |
| "if confidence < 70, reject" | Check confidence field before applying |
| "run 'nx affected -t typecheck' before applying" | Add local verification step |
| "auto-fix workflow failures" | Attempt lockfile updates on pre-CIPE failures |
| "wait 45 min for new CIPE" | Override new-CIPE timeout (default: 10 min) |
## Error Handling
| Error | Action |
| ------------------------ | ------------------------------------------------------------------------------------- |
| Git rebase conflict | Report to user, exit |
| `nx apply-locally` fails | Report to user, attempt manual patch or exit |
| MCP tool error | Retry once, if fails report to user |
| Subagent spawn failure | Retry once, if fails exit with error |
| No new CIPE detected | If `--auto-fix-workflow`, try lockfile update; otherwise report to user with guidance |
| Lockfile auto-fix fails | Report to user, exit with guidance to check CI logs |
## Example Session
### Example 1: Normal Flow with Self-Healing (medium verbosity)
```
[ci-monitor] Starting CI monitor for branch 'feature/add-auth'
[ci-monitor] Config: max-cycles=5, timeout=120m, verbosity=medium
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] CI attempt: IN_PROGRESS | Self-Healing: NOT_STARTED | Elapsed: 1m
[CI Monitor] CI attempt: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 3m
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 5m
[ci-monitor] Fix available! Verification: COMPLETED
[ci-monitor] Applying fix via MCP...
[ci-monitor] Fix applied in CI. Waiting for new CI attempt...
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] New CI attempt detected!
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 8m
[ci-monitor] CI passed successfully!
[ci-monitor] Summary:
- Total cycles: 2
- Total time: 12m 34s
- Fixes applied: 1
- Result: SUCCESS
```
### Example 2: Pre-CI Failure (medium verbosity)
```
[ci-monitor] Starting CI monitor for branch 'feature/add-products'
[ci-monitor] Config: max-cycles=5, timeout=120m, auto-fix-workflow=true
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 2m
[ci-monitor] Applying fix locally, enhancing, and pushing...
[ci-monitor] Committed: abc1234
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] Waiting for new CI attempt... (expected SHA: abc1234)
[CI Monitor] ⚠️ CI attempt timeout (10 min). Returning no_new_cipe.
[ci-monitor] Status: no_new_cipe
[ci-monitor] --auto-fix-workflow enabled. Attempting lockfile update...
[ci-monitor] Lockfile updated. Committed: def5678
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] New CI attempt detected!
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 18m
[ci-monitor] CI passed successfully!
[ci-monitor] Summary:
- Total cycles: 3
- Total time: 22m 15s
- Fixes applied: 1 (self-healing) + 1 (lockfile)
- Result: SUCCESS
```
+228
View File
@@ -0,0 +1,228 @@
---
name: nx-generate
description: Generate code using nx generators. USE WHEN scaffolding code or transforming existing code - for example creating libraries or applications, or anything else that is boilerplate code or automates repetitive tasks. ALWAYS use this first when generating code with Nx instead of calling MCP tools or running nx generate immediately.
---
# Run Nx Generator
Nx generators are powerful tools that scaffold projects, make automated code migrations or automate repetitive tasks in a monorepo. They ensure consistency across the codebase and reduce boilerplate work.
This skill applies when the user wants to:
- Create new projects like libraries or applications
- Scaffold features or boilerplate code
- Run workspace-specific or custom generators
- Do anything else that an nx generator exists for
## Generator Discovery Flow
### Step 1: List Available Generators
Use the Nx CLI to discover available generators:
- List all generators for a plugin: `npx nx list @nx/react`
- View available plugins: `npx nx list`
This includes:
- Plugin generators (e.g., `@nx/react:library`, `@nx/js:library`)
- Local workspace generators (defined in the repo's own plugins)
### Step 2: Match Generator to User Request
Based on the user's request, identify which generator(s) could fulfill their needs. Consider:
- What artifact type they want to create (library, application, etc.)
- Which framework or technology stack is relevant
- Whether they mentioned specific generator names
**IMPORTANT**: When both a local workspace generator and an external plugin generator could satisfy the request, **always prefer the local workspace generator**. Local generators are customized for the specific repo's patterns and conventions.
It's possible that the user request is something that no Nx generator exists for whatsoever. In this case, you can stop using this skill and try to help the user another way. HOWEVER, the burden of proof for this is high. Before aborting, carefully consider each and every generator that's available. Look into details for any that could be related in any way before making this decision.
## Pre-Execution Checklist
Before running any generator, complete these steps:
### 1. Fetch Generator Schema
Use the `--help` flag to understand all available options:
```bash
npx nx g @nx/react:library --help
```
Pay attention to:
- Required options that must be provided
- Optional options that may be relevant to the user's request
- Default values that might need to be overridden
### 2. Read Generator Source Code
Understanding what the generator actually does helps you:
- Know what files will be created/modified
- Understand any side effects (updating configs, installing deps, etc.)
- Identify options that might not be obvious from the schema
To find generator source code:
- For plugin generators: Use `node -e "console.log(require.resolve('@nx/<plugin>/generators.json'));"` to find the generators.json, then locate the source from there
- If that fails, read directly from `node_modules/<plugin>/generators.json`
- For local generators: They are typically in `tools/generators/` or a local plugin directory. You can search the repo for the generator name to find it.
### 2.5 Reevaluate if the generator is right
Once you have built up an understanding of what the selected generator does, reconsider: Is this the right generator to service the user request?
If not, it's okay to go back to the Generator Discovery Flow and select a different generator before proceeding. If you do, make sure to go through the entire pre-execution checklist once more.
### 3. Understand Repo Context
Before generating, examine the target area of the codebase:
- Look at similar existing artifacts (other libraries, applications, etc.)
- Identify patterns and conventions used in the repo
- Note naming conventions, file structures, and configuration patterns
- Try to match these patterns when configuring the generator
For example, if similar libraries are using a specific test runner, build tool or linter, try to match that if possible.
If projects or other artifacts are organized with a specific naming convention, try to match it.
### 4. Validate Required Options
Ensure all required options have values:
- Map the user's request to generator options
- Infer values from context where possible
- Ask the user for any critical missing information
## Execution
Keep in mind that you might have to prefix things with npx/pnpx/yarn if the user doesn't have nx installed globally.
Many generators will behave differently based on where they are executed. For example, first-party nx library generators use the cwd to determine the directory that the library should be placed in. This is highly important.
### Consider Dry-Run (Optional)
Running with `--dry-run` first is strongly encouraged but not mandatory. Use your judgment:
- For complex generators or unfamiliar territory: do a dry-run first
- For simple, well-understood generators: may proceed directly
- Dry-run shows file names and created/deleted/modified markers, but not content
- There are cases where a generator does not support dry-run (for example if it had to install an npm package) - in that case --dry-run might fail. Don't be discouraged but simply move on to running the generator for real and iterating from there.
### Running the Generator
Execute the generator with:
```bash
nx generate <generator-name> <options> --no-interactive
```
**CRITICAL**: Always include `--no-interactive` to prevent prompts that would hang the execution.
Example:
```bash
nx generate @nx/react:library --name=my-utils --no-interactive
```
### Handling Generator Failures
If the generator fails:
1. **Diagnose the error** - Read the error message carefully
2. **Identify the cause** - Missing options, invalid values, conflicts, etc.
3. **Attempt automatic fix** - Adjust options or resolve conflicts
4. **Retry** - Run the generator again with corrected options
Common failure reasons:
- Missing required options
- Invalid option values
- Conflicting with existing files
- Missing dependencies
- Generator doesn't support certain flag combinations
## Post-Generation
### 1. Modify Generated Code (If Needed)
Generators provide a starting point, but the output may need adjustment to match the user's specific requirements:
- Add or modify functionality as requested
- Adjust imports, exports, or configurations
- Integrate with existing code patterns in the repo
### 2. Format Code
Run formatting on all generated/modified files:
```bash
nx format --fix
```
Languages other than javascript/typescript might need other formatting invocations too.
### 3. Run Verification
Verify that the generated code works correctly. What this looks like will vary depending on the type of generator and the targets available.
If the generator created a new project, run its targets directly
Use your best judgement to determine what needs to be verified.
Example:
```bash
nx lint <new-project>
nx test <new-project>
nx build <new-project>
```
### 4. Handle Verification Failures
When verification fails:
**If scope is manageable** (a few lint errors, minor type issues):
- Fix the issues
- Re-run verification to confirm
**If issues are extensive** (many errors, complex problems):
- Attempt simple, obvious fixes first
- If still failing, escalate to the user with:
- Description of what was generated
- What verification is failing
- What you've attempted to fix
- Remaining issues that need user input
## Error Handling
### Generator Failures
- Check the error message for specific causes
- Verify all required options are provided
- Check for conflicts with existing files
- Ensure the generator name and options are correct
### Missing Options
- Consult the generator schema for required fields
- Infer values from context when reasonable
- Ask the user for values that cannot be inferred
## Key Principles
1. **Local generators first** - Always prefer workspace/local generators over external plugin generators when both could work
2. **Understand before running** - Read both the schema AND the source code to fully understand what will happen
3. **No prompts** - Always use `--no-interactive` to prevent hanging
4. **Generators are starting points** - Modify the output as needed to fully satisfy the user's requirements
5. **Verify changes work** - Don't just generate; ensure the code builds, lints, and tests pass
6. **Be proactive about fixes** - Don't just report errors; attempt to resolve them automatically when possible
7. **Match repo patterns** - Study existing similar code in the repo and match its conventions
+9
View File
@@ -0,0 +1,9 @@
---
name: nx-plugins
description: Find and add Nx plugins. USE WHEN user wants to discover available plugins, install a new plugin, or add support for a specific framework or technology to the workspace.
---
## Finding and Installing new plugins
- List plugins: `pnpm nx list`
- Install plugins `pnpm nx add <plugin>`. Example: `pnpm nx add @nx/react`.
+58
View File
@@ -0,0 +1,58 @@
---
name: nx-run-tasks
description: Helps with running tasks in an Nx workspace. USE WHEN the user wants to execute build, test, lint, serve, or run any other tasks defined in the workspace.
---
You can run tasks with Nx in the following way.
Keep in mind that you might have to prefix things with npx/pnpx/yarn if the user doesn't have nx installed globally. Look at the package.json or lockfile to determine which package manager is in use.
For more details on any command, run it with `--help` (e.g. `nx run-many --help`, `nx affected --help`).
## Understand which tasks can be run
You can check those via `nx show project <projectname> --json`, for example `nx show project myapp --json`. It contains a `targets` section which has information about targets that can be run. You can also just look at the `package.json` scripts or `project.json` targets, but you might miss out on inferred tasks by Nx plugins.
## Run a single task
```
nx run <project>:<task>
```
where `project` is the project name defined in `package.json` or `project.json` (if present).
## Run multiple tasks
```
nx run-many -t build test lint typecheck
```
You can pass a `-p` flag to filter to specific projects, otherwise it runs on all projects. You can also use `--exclude` to exclude projects, and `--parallel` to control the number of parallel processes (default is 3).
Examples:
- `nx run-many -t test -p proj1 proj2` — test specific projects
- `nx run-many -t test --projects=*-app --exclude=excluded-app` — test projects matching a pattern
- `nx run-many -t test --projects=tag:api-*` — test projects by tag
## Run tasks for affected projects
Use `nx affected` to only run tasks on projects that have been changed and projects that depend on changed projects. This is especially useful in CI and for large workspaces.
```
nx affected -t build test lint
```
By default it compares against the base branch. You can customize this:
- `nx affected -t test --base=main --head=HEAD` — compare against a specific base and head
- `nx affected -t test --files=libs/mylib/src/index.ts` — specify changed files directly
## Useful flags
These flags work with `run`, `run-many`, and `affected`:
- `--skipNxCache` — rerun tasks even when results are cached
- `--verbose` — print additional information such as stack traces
- `--nxBail` — stop execution after the first failed task
- `--configuration=<name>` — use a specific configuration (e.g. `production`)
+186
View File
@@ -0,0 +1,186 @@
---
name: nx-workspace
description: "Explore and understand Nx workspaces. USE WHEN answering any questions about the nx workspace, the projects in it or tasks to run. EXAMPLES: 'What projects are in this workspace?', 'How is project X configured?', 'What targets can I run?', 'What's affected by my changes?', 'Which projects depend on library Y?', or any questions about Nx workspace structure, project configuration, or available tasks."
---
# Nx Workspace Exploration
This skill provides read-only exploration of Nx workspaces. Use it to understand workspace structure, project configuration, available targets, and dependencies.
Keep in mind that you might have to prefix commands with `npx`/`pnpx`/`yarn` if nx isn't installed globally. Check the lockfile to determine the package manager in use.
## Listing Projects
Use `nx show projects` to list projects in the workspace.
```bash
# List all projects
nx show projects
# Filter by pattern (glob)
nx show projects --projects "apps/*"
nx show projects --projects "shared-*"
# Filter by project type
nx show projects --type app
nx show projects --type lib
nx show projects --type e2e
# Filter by target (projects that have a specific target)
nx show projects --withTarget build
nx show projects --withTarget e2e
# Find affected projects (changed since base branch)
nx show projects --affected
nx show projects --affected --base=main
nx show projects --affected --type app
# Combine filters
nx show projects --type lib --withTarget test
nx show projects --affected --exclude="*-e2e"
# Output as JSON
nx show projects --json
```
## Project Configuration
Use `nx show project <name> --json` to get the full resolved configuration for a project.
**Important**: Do NOT read `project.json` directly - it only contains partial configuration. The `nx show project` command returns the full resolved config including inferred targets from plugins.
You can read the full project schema at `node_modules/nx/schemas/project-schema.json` to understand nx project configuration options.
```bash
# Get full project configuration
nx show project my-app --json
# Extract specific parts from the JSON
nx show project my-app --json | jq '.targets'
nx show project my-app --json | jq '.targets.build'
nx show project my-app --json | jq '.targets | keys'
# Check project metadata
nx show project my-app --json | jq '{name, root, sourceRoot, projectType, tags}'
```
## Target Information
Targets define what tasks can be run on a project.
```bash
# List all targets for a project
nx show project my-app --json | jq '.targets | keys'
# Get full target configuration
nx show project my-app --json | jq '.targets.build'
# Check target executor/command
nx show project my-app --json | jq '.targets.build.executor'
nx show project my-app --json | jq '.targets.build.command'
# View target options
nx show project my-app --json | jq '.targets.build.options'
# Check target inputs/outputs (for caching)
nx show project my-app --json | jq '.targets.build.inputs'
nx show project my-app --json | jq '.targets.build.outputs'
# Find projects with a specific target
nx show projects --withTarget serve
nx show projects --withTarget e2e
```
## Workspace Configuration
Read `nx.json` directly for workspace-level configuration.
You can read the full project schema at `node_modules/nx/schemas/nx-schema.json` to understand nx project configuration options.
```bash
# Read the full nx.json
cat nx.json
# Or use jq for specific sections
cat nx.json | jq '.targetDefaults'
cat nx.json | jq '.namedInputs'
cat nx.json | jq '.plugins'
cat nx.json | jq '.generators'
```
Key nx.json sections:
- `targetDefaults` - Default configuration applied to all targets of a given name
- `namedInputs` - Reusable input definitions for caching
- `plugins` - Nx plugins and their configuration
- ...and much more, read the schema or nx.json for details
## Affected Projects
Find projects affected by changes in the current branch.
```bash
# Affected since base branch (auto-detected)
nx show projects --affected
# Affected with explicit base
nx show projects --affected --base=main
nx show projects --affected --base=origin/main
# Affected between two commits
nx show projects --affected --base=abc123 --head=def456
# Affected apps only
nx show projects --affected --type app
# Affected excluding e2e projects
nx show projects --affected --exclude="*-e2e"
# Affected by uncommitted changes
nx show projects --affected --uncommitted
# Affected by untracked files
nx show projects --affected --untracked
```
## Common Exploration Patterns
### "What's in this workspace?"
```bash
nx show projects
nx show projects --type app
nx show projects --type lib
```
### "How do I build/test/lint project X?"
```bash
nx show project X --json | jq '.targets | keys'
nx show project X --json | jq '.targets.build'
```
### "What depends on library Y?"
```bash
# Find projects that may depend on Y by searching for imports
# (Nx doesn't have a direct "dependents" command via CLI)
grep -r "from '@myorg/Y'" --include="*.ts" --include="*.tsx" apps/ libs/
```
### "What configuration options are available?"
```bash
cat node_modules/nx/schemas/nx-schema.json | jq '.properties | keys'
cat node_modules/nx/schemas/project-schema.json | jq '.properties | keys'
```
### "Why is project X affected?"
```bash
# Check what files changed
git diff --name-only main
# See which project owns those files
nx show project X --json | jq '.root'
```
+478
View File
@@ -0,0 +1,478 @@
---
description: Polls Nx Cloud CI pipeline and self-healing status. Returns structured state when actionable. Spawned by /nx-cloud-ci-monitor command to monitor CI Attempt status.
---
# CI Watcher Subagent
You are a CI monitoring subagent responsible for polling Nx Cloud CI Attempt status and self-healing state. You report status back to the main agent - you do NOT make apply/reject decisions.
## Your Responsibilities
1. Poll CI status using the `ci_information` MCP tool
2. Implement exponential backoff between polls
3. Return structured state when an actionable condition is reached
4. Track iteration count and elapsed time
5. Output status updates based on verbosity level
## Input Parameters (from Main Agent)
The main agent may provide these optional parameters in the prompt:
| Parameter | Description |
| ------------------- | -------------------------------------------------------- |
| `branch` | Branch to monitor (auto-detected if not provided) |
| `expectedCommitSha` | Commit SHA that should trigger a new CI Attempt |
| `previousCipeUrl` | CI Attempt URL before the action (to detect change) |
| `subagentTimeout` | Polling timeout in minutes (default: 60) |
| `verbosity` | Output level: minimal, medium, verbose (default: medium) |
When `expectedCommitSha` or `previousCipeUrl` is provided, you must detect whether a new CI Attempt has spawned.
## MCP Tool Reference
### `ci_information`
**Input:**
```json
{
"branch": "string (optional, defaults to current git branch)",
"select": "string (optional, comma-separated field names)",
"pageToken": "number (optional, 0-based pagination for long strings)"
}
```
**Output:**
```json
{
"cipeStatus": "NOT_STARTED | IN_PROGRESS | SUCCEEDED | FAILED | CANCELED | TIMED_OUT",
"cipeUrl": "string",
"branch": "string",
"commitSha": "string | null",
"failedTaskIds": "string[]",
"verifiedTaskIds": "string[]",
"selfHealingEnabled": "boolean",
"selfHealingStatus": "NOT_STARTED | IN_PROGRESS | COMPLETED | FAILED | NOT_EXECUTABLE | null",
"verificationStatus": "NOT_STARTED | IN_PROGRESS | COMPLETED | FAILED | NOT_EXECUTABLE | null",
"userAction": "NONE | APPLIED | REJECTED | APPLIED_LOCALLY | APPLIED_AUTOMATICALLY | null",
"failureClassification": "string | null",
"taskOutputSummary": "string | null",
"suggestedFixReasoning": "string | null",
"suggestedFixDescription": "string | null",
"suggestedFix": "string | null",
"shortLink": "string | null",
"couldAutoApplyTasks": "boolean | null",
"confidence": "number | null",
"confidenceReasoning": "string | null"
}
```
**Select Parameter:**
| Usage | Returns |
| --------------- | ----------------------------------------------------------- |
| No `select` | Formatted overview (truncated, not recommended for polling) |
| Single field | Raw value with pagination for long strings |
| Multiple fields | Object with requested field values |
**Field Sets for Efficient Polling:**
```yaml
WAIT_FIELDS:
'cipeUrl,commitSha,cipeStatus'
# Minimal fields for detecting new CI Attempt
LIGHT_FIELDS:
'cipeStatus,cipeUrl,branch,commitSha,selfHealingStatus,verificationStatus,userAction,failedTaskIds,verifiedTaskIds,selfHealingEnabled,failureClassification,couldAutoApplyTasks,shortLink,confidence,confidenceReasoning'
# Status fields for determining actionable state
HEAVY_FIELDS:
'taskOutputSummary,suggestedFix,suggestedFixReasoning,suggestedFixDescription'
# Large content fields - fetch only when returning to main agent
```
## Initial Wait
Before first poll, wait based on context:
- **Fresh start (no expected CIPE):** Wait 60 seconds to allow CI to start
- **Expecting new CIPE:** Wait 30 seconds (action already triggered)
**IMPORTANT:** Always run sleep in foreground, NOT as background command.
```bash
sleep 60 # or 30 if expecting new CIPE (FOREGROUND, not background)
```
## Two-Phase Operation
The subagent operates in one of two modes depending on input:
### Mode 1: Fresh Start (no `expectedCommitSha` or `previousCipeUrl`)
Normal polling - process whatever CIPE is returned by `ci_information`.
### Mode 2: Wait-for-New-CIPE (when `expectedCommitSha` or `previousCipeUrl` provided)
**CRITICAL**: When expecting a new CIPE, the subagent must **completely ignore** the old/stale CIPE. Do NOT process its status, do NOT return actionable states based on it.
#### Phase A: Wait Mode
1. Start a **new-CIPE timeout** timer (default: 30 minutes)
2. On each poll of `ci_information`:
- Check if CIPE is NEW:
- `cipeUrl` differs from `previousCipeUrl`**new CIPE detected**
- `commitSha` matches `expectedCommitSha`**correct CIPE detected**
- If still OLD CIPE: **ignore all status fields**, just wait and poll again
- Do NOT return `fix_available`, `ci_success`, etc. based on old CIPE!
3. Output wait status (see below)
4. If timeout (30 min) reached → return `no_new_cipe`
#### Phase B: Normal Polling (after new CIPE detected)
Once new CIPE is detected:
1. Clear the new-CIPE timeout
2. Switch to normal polling mode
3. Process the NEW CIPE's status normally
4. Return when actionable state reached
### Wait Mode Output
While in wait mode, output clearly that you're waiting (not processing):
```
[CI Monitor] ═══════════════════════════════════════════════════════
[CI Monitor] WAIT MODE - Expecting new CI Attempt
[CI Monitor] Expected SHA: <expectedCommitSha>
[CI Monitor] Previous CI Attempt: <previousCipeUrl>
[CI Monitor] ═══════════════════════════════════════════════════════
[CI Monitor] Polling... (elapsed: 0m 30s)
[CI Monitor] Still seeing previous CI Attempt (ignoring): <oldCipeUrl>
[CI Monitor] Polling... (elapsed: 1m 30s)
[CI Monitor] Still seeing previous CI Attempt (ignoring): <oldCipeUrl>
[CI Monitor] Polling... (elapsed: 2m 30s)
[CI Monitor] ✓ New CI Attempt detected! URL: <newCipeUrl>, SHA: <newCommitSha>
[CI Monitor] Switching to normal polling mode...
```
### Why This Matters (Context Preservation)
**The problem**: Stale CIPE data can be very large:
- `taskOutputSummary`: potentially thousands of characters of build/test output
- `suggestedFix`: entire patch files
- `suggestedFixReasoning`: detailed explanation
If subagent returns stale CIPE data to main agent, it **pollutes main agent's context** with useless information (we already processed that CIPE). This wastes valuable context window.
**Without wait mode:**
1. Poll `ci_information` → get old CIPE with huge data
2. Return to main agent with all that stale data
3. Main agent's context gets polluted with useless info
4. Main agent has to process/ignore it anyway
**With wait mode:**
1. Poll `ci_information` → get old CIPE → **ignore it, don't return**
2. Keep waiting internally (stale data stays in subagent)
3. New CIPE appears → switch to normal mode
4. Return to main agent with only the NEW, relevant CIPE data
## Polling Loop
### Subagent State Management
Maintain internal accumulated state across polls:
```
accumulated_state = {}
```
### Call `ci_information` MCP Tool
**Wait Mode (expecting new CI Attempt):**
```
ci_information({
branch: "<branch_name>",
select: "cipeUrl,commitSha,cipeStatus"
})
```
Only fetch minimal fields needed to detect CI Attempt change. Do NOT fetch heavy fields - stale data wastes context.
**Normal Mode (processing CI Attempt):**
```
ci_information({
branch: "<branch_name>",
select: "cipeStatus,cipeUrl,branch,commitSha,selfHealingStatus,verificationStatus,userAction,failedTaskIds,verifiedTaskIds,selfHealingEnabled,failureClassification,couldAutoApplyTasks,shortLink,confidence,confidenceReasoning"
})
```
Merge response into `accumulated_state` after each poll.
### Analyze Response
**If in Wait Mode** (expecting new CIPE):
1. Check if CIPE is new (see Two-Phase Operation above)
2. If old CIPE → **ignore status**, output wait message, poll again
3. If new CIPE → switch to normal mode, continue below
**If in Normal Mode**:
Based on the response, decide whether to **keep polling** or **return to main agent**.
### Keep Polling When
Continue polling (with backoff) if ANY of these conditions are true:
| Condition | Reason |
| --------------------------------------- | ---------------------------------------- |
| `cipeStatus == 'IN_PROGRESS'` | CI still running |
| `cipeStatus == 'NOT_STARTED'` | CI hasn't started yet |
| `selfHealingStatus == 'IN_PROGRESS'` | Self-healing agent working |
| `selfHealingStatus == 'NOT_STARTED'` | Self-healing not started yet |
| `failureClassification == 'FLAKY_TASK'` | Auto-rerun in progress |
| `userAction == 'APPLIED_AUTOMATICALLY'` | New CI Attempt spawning after auto-apply |
When `couldAutoApplyTasks == true`:
- `verificationStatus` = `NOT_STARTED`, `IN_PROGRESS` → keep polling (verification still in progress)
- `verificationStatus` = `COMPLETED` → return `fix_auto_applying` (auto-apply will happen, main agent spawns wait mode subagent)
- `verificationStatus` = `FAILED`, `NOT_EXECUTABLE` → return `fix_available` (auto-apply won't happen, needs manual action)
### Exponential Backoff
Between polls, wait with exponential backoff:
| Poll Attempt | Wait Time |
| ------------ | ----------------- |
| 1st | 60 seconds |
| 2nd | 90 seconds |
| 3rd+ | 120 seconds (cap) |
Reset to 60 seconds when state changes significantly.
**IMPORTANT:** Run sleep in foreground (NOT as background command). Background sleep causes "What should Claude do?" prompts when completed.
```bash
# Example backoff - run in FOREGROUND
sleep 60 # First wait
sleep 90 # Second wait
sleep 120 # Third and subsequent waits (capped)
```
### Fetch Heavy Fields on Actionable State
Before returning to main agent, fetch heavy fields if the status requires them:
| Status | Heavy Fields Needed |
| ------------------- | ------------------------------------------------------------------------------ |
| `ci_success` | None |
| `fix_auto_applying` | None |
| `fix_available` | `taskOutputSummary,suggestedFix,suggestedFixReasoning,suggestedFixDescription` |
| `fix_failed` | `taskOutputSummary` |
| `no_fix` | `taskOutputSummary` |
| `environment_issue` | None |
| `no_new_cipe` | None |
| `polling_timeout` | None |
| `cipe_canceled` | None |
| `cipe_timed_out` | None |
```
# Example: fetching heavy fields for fix_available
ci_information({
branch: "<branch_name>",
select: "taskOutputSummary,suggestedFix,suggestedFixReasoning,suggestedFixDescription"
})
```
Merge response into `accumulated_state`, then return merged state to main agent.
**Pagination:** Heavy string fields return first page only. If `hasMore` indicated, include in return format so main agent knows more content available.
### Return to Main Agent When
Return immediately with structured state if ANY of these conditions are true:
| Status | Condition |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ci_success` | `cipeStatus == 'SUCCEEDED'` |
| `fix_auto_applying` | `selfHealingStatus == 'COMPLETED'` AND `couldAutoApplyTasks == true` AND `verificationStatus == 'COMPLETED'` |
| `fix_available` | `selfHealingStatus == 'COMPLETED'` AND `suggestedFix != null` AND (`couldAutoApplyTasks != true` OR `verificationStatus` in (`FAILED`, `NOT_EXECUTABLE`)) |
| `fix_failed` | `selfHealingStatus == 'FAILED'` |
| `environment_issue` | `failureClassification == 'ENVIRONMENT_STATE'` |
| `no_fix` | `cipeStatus == 'FAILED'` AND (`selfHealingEnabled == false` OR `selfHealingStatus == 'NOT_EXECUTABLE'`) |
| `no_new_cipe` | `expectedCommitSha` or `previousCipeUrl` provided, but no new CI Attempt detected after 30 min |
| `polling_timeout` | Subagent has been polling for > configured timeout (default 60 min) |
| `cipe_canceled` | `cipeStatus == 'CANCELED'` |
| `cipe_timed_out` | `cipeStatus == 'TIMED_OUT'` |
## Subagent Timeout
Track elapsed time. If you have been polling for more than **60 minutes** (configurable via main agent), return with `status: polling_timeout`.
## Return Format
When returning to the main agent, provide a structured response with accumulated state:
```
## CI Monitor Result
**Status:** <status>
**Iterations:** <count>
**Elapsed:** <minutes>m <seconds>s
### CI Attempt Details
- **Status:** <cipeStatus>
- **URL:** <cipeUrl>
- **Branch:** <branch>
- **Commit:** <commitSha>
- **Failed Tasks:** <failedTaskIds>
- **Verified Tasks:** <verifiedTaskIds>
### Self-Healing Details
- **Enabled:** <selfHealingEnabled>
- **Status:** <selfHealingStatus>
- **Verification:** <verificationStatus>
- **User Action:** <userAction>
- **Classification:** <failureClassification>
- **Confidence:** <confidence>
- **Confidence Reasoning:** <confidenceReasoning>
### Fix Information (if available)
- **Short Link:** <shortLink>
- **Description:** <suggestedFixDescription>
- **Reasoning:** <suggestedFixReasoning>
### Task Output Summary (first page)
<taskOutputSummary>
[MORE_CONTENT_AVAILABLE: taskOutputSummary, pageToken: 1]
### Suggested Fix (first page)
<suggestedFix>
[MORE_CONTENT_AVAILABLE: suggestedFix, pageToken: 1]
```
### Pagination Indicators
When a heavy field has more content available, append indicator:
```
[MORE_CONTENT_AVAILABLE: <fieldName>, pageToken: <nextPage>]
```
Main agent can fetch additional pages if needed using:
```
ci_information({ select: "<fieldName>", pageToken: <nextPage> })
```
Fields that may have pagination:
- `taskOutputSummary` (reverse pagination - page 0 = most recent)
- `suggestedFix` (forward pagination - page 0 = start)
- `suggestedFixReasoning`
### Return Format for `no_new_cipe`
When returning with `status: no_new_cipe`, include additional context:
```
## CI Monitor Result
**Status:** no_new_cipe
**Iterations:** <count>
**Elapsed:** <minutes>m <seconds>s
### Expected CI Attempt Not Found
- **Expected Commit SHA:** <expectedCommitSha>
- **Previous CI Attempt URL:** <previousCipeUrl>
- **Last Seen CI Attempt URL:** <cipeUrl>
- **Last Seen Commit SHA:** <commitSha>
- **New CI Attempt Timeout:** 30 minutes (exceeded)
### Likely Cause
CI workflow failed before Nx tasks could run (e.g., install step, checkout, auth).
Check your CI provider logs for the commit <expectedCommitSha>.
### Last Known CI Attempt State
- **Status:** <cipeStatus>
- **Branch:** <branch>
```
## Status Reporting (Verbosity-Controlled)
Output is controlled by the `verbosity` parameter from the main agent:
| Level | What to Output |
| --------- | ----------------------------------------------------------------- |
| `minimal` | No intermediate output. Only return final result when actionable. |
| `medium` | Output only on significant state changes (not every poll). |
| `verbose` | Output detailed phase information after every poll. |
### Minimal Verbosity
No output during polling. Poll silently and return when done.
### Medium Verbosity (Default)
Output **only when state changes significantly** to save context tokens:
- `cipeStatus` changes (e.g., IN_PROGRESS → FAILED)
- `selfHealingStatus` changes (e.g., IN_PROGRESS → COMPLETED)
- New CI Attempt detected (in wait mode)
Format: single line, no decorators:
```
[CI Monitor] CI: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 4m
```
### Verbose Verbosity
Output detailed phase box after every poll:
```
[CI Monitor] ─────────────────────────────────────────────────────
[CI Monitor] Iteration <N> | Elapsed: <X>m <Y>s
[CI Monitor]
[CI Monitor] CI Status: <cipeStatus>
[CI Monitor] Self-Healing: <selfHealingStatus>
[CI Monitor] Verification: <verificationStatus>
[CI Monitor] Classification: <failureClassification>
[CI Monitor]
[CI Monitor] → <human-readable phase description>
[CI Monitor] ─────────────────────────────────────────────────────
```
### Phase Descriptions (for verbose output)
| Status Combo | Description |
| ----------------------------------------------------------------------------------------- | ------------------------------------------- |
| `cipeStatus: IN_PROGRESS` | "CI running..." |
| `cipeStatus: NOT_STARTED` | "Waiting for CI to start..." |
| `cipeStatus: FAILED` + `selfHealingStatus: NOT_STARTED` | "CI failed. Self-healing starting..." |
| `cipeStatus: FAILED` + `selfHealingStatus: IN_PROGRESS` | "CI failed. Self-healing generating fix..." |
| `cipeStatus: FAILED` + `selfHealingStatus: COMPLETED` + `verificationStatus: IN_PROGRESS` | "Fix generated! Verification running..." |
| `cipeStatus: FAILED` + `selfHealingStatus: COMPLETED` + `verificationStatus: COMPLETED` | "Fix ready! Verified successfully." |
| `cipeStatus: FAILED` + `selfHealingStatus: COMPLETED` + `verificationStatus: FAILED` | "Fix generated but verification failed." |
| `cipeStatus: FAILED` + `selfHealingStatus: FAILED` | "Self-healing could not generate a fix." |
| `cipeStatus: SUCCEEDED` | "CI passed!" |
## Important Notes
- You do NOT make apply/reject decisions - that's the main agent's job
- You do NOT perform git operations
- You only poll and report state
- Respect the `verbosity` parameter for output (default: medium)
- If `ci_information` returns an error, wait and retry (count as failed poll)
- Track consecutive failures - if 5 consecutive failures, return with `status: error`
- When expecting new CI Attempt, track the 30-minute new-CI-Attempt timeout separately from the main polling timeout
+18
View File
@@ -0,0 +1,18 @@
# This configuration is here to prevent false positive alerts for __fixtures__.
# We are intentionally disabling the PR opening feature.
version: 2
updates:
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: 'weekly'
open-pull-requests-limit: 0
exclude-paths:
- '**/__fixtures__/**'
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'weekly'
open-pull-requests-limit: 0
+437
View File
@@ -0,0 +1,437 @@
---
description: Monitor Nx Cloud CI pipeline and handle self-healing fixes automatically. Checks for Nx Cloud connection before starting.
argument-hint: '[instructions] [--max-cycles N] [--timeout MINUTES] [--verbosity minimal|medium|verbose] [--branch BRANCH] [--fresh] [--auto-fix-workflow] [--new-cipe-timeout MINUTES]'
---
# CI Monitor Command
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
## Context
- **Current Branch:** !`git branch --show-current`
- **Current Commit:** !`git rev-parse --short HEAD`
- **Remote Status:** !`git status -sb | head -1`
## User Instructions
${input:args}
**Important:** If user provides specific instructions, respect them over default behaviors described below.
## Configuration Defaults
| Setting | Default | Description |
| ------------------------- | ------------- | ------------------------------------------------------------------- |
| `--max-cycles` | 10 | Maximum CIPE cycles before timeout |
| `--timeout` | 120 | Maximum duration in minutes |
| `--verbosity` | medium | Output level: minimal, medium, verbose |
| `--branch` | (auto-detect) | Branch to monitor |
| `--subagent-timeout` | 60 | Subagent polling timeout in minutes |
| `--fresh` | false | Ignore previous context, start fresh |
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
Parse any overrides from `${input:args}` and merge with defaults.
## Nx Cloud Connection Check
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
### Step 0: Verify Nx Cloud Connection
1. **Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
2. **If `nx.json` missing OR neither property exists** → exit with:
```
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
```
3. **If connected** → continue to main loop
## Session Context Behavior
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
- **For a completely clean slate:** Exit Claude Code and restart `claude`
## Default Behaviors by Status
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
| Status | Default Behavior |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ci_success` | Exit with success. Log "CI passed successfully!" |
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
| `polling_timeout` | Subagent polling timeout reached. Exit with timeout. |
| `cipe_canceled` | CIPE was canceled. Exit with canceled status. |
| `cipe_timed_out` | CIPE timed out. Exit with timeout status. |
| `error` | Increment `no_progress_count`. If >= 3 → exit with circuit breaker. Otherwise wait 60s and loop. |
### Fix Available Decision Logic
When subagent returns `fix_available`, main agent compares `failedTaskIds` vs `verifiedTaskIds`:
#### Step 1: Categorize Tasks
1. **Verified tasks** = tasks in both `failedTaskIds` AND `verifiedTaskIds`
2. **Unverified tasks** = tasks in `failedTaskIds` but NOT in `verifiedTaskIds`
3. **E2E tasks** = unverified tasks where target contains "e2e" (task format: `<project>:<target>` or `<project>:<target>:<config>`)
4. **Verifiable tasks** = unverified tasks that are NOT e2e
#### Step 2: Determine Path
| Condition | Path |
| --------------------------------------- | ---------------------------------------- |
| No unverified tasks (all verified) | Apply via MCP |
| Unverified tasks exist, but ALL are e2e | Apply via MCP (treat as verified enough) |
| Verifiable tasks exist | Local verification flow |
#### Step 3a: Apply via MCP (fully/e2e-only verified)
- Call `update_self_healing_fix({ shortLink, action: "APPLY" })`
- Record `last_cipe_url`, spawn subagent in wait mode
#### Step 3b: Local Verification Flow
When verifiable (non-e2e) unverified tasks exist:
1. **Detect package manager:**
- `pnpm-lock.yaml` exists → `pnpm nx`
- `yarn.lock` exists → `yarn nx`
- Otherwise → `npx nx`
2. **Run verifiable tasks in parallel:**
- Spawn `general` subagents to run each task concurrently
- Each subagent runs: `<pm> nx run <taskId>`
- Collect pass/fail results from all subagents
3. **Evaluate results:**
| Result | Action |
| ------------------------- | ---------------------------- |
| ALL verifiable tasks pass | Apply via MCP |
| ANY verifiable task fails | Apply-locally + enhance flow |
4. **Apply-locally + enhance flow:**
- Run `nx apply-locally <shortLink>`
- Enhance the code to fix failing tasks
- Run failing tasks again to verify fix
- If still failing → increment `local_verify_count`, loop back to enhance
- If passing → commit and push, record `expected_commit_sha`, spawn subagent in wait mode
5. **Track attempts** (wraps step 4):
- Increment `local_verify_count` after each enhance cycle
- If `local_verify_count >= local_verify_attempts` (default: 3):
- Get code in commit-able state
- Commit and push with message indicating local verification failed
- Report to user:
```
[ci-monitor] Local verification failed after <N> attempts. Pushed to CI for final validation. Failed: <taskIds>
```
- Record `expected_commit_sha`, spawn subagent in wait mode (let CI be final judge)
#### Commit Message Format
```bash
git commit -m "fix(<projects>): <brief description>
Failed tasks: <taskId1>, <taskId2>
Local verification: passed|enhanced|failed-pushing-to-ci"
```
### Unverified Fix Flow (No Verification Attempted)
When `verificationStatus` is `FAILED`, `NOT_EXECUTABLE`, or fix has `couldAutoApplyTasks != true` with no verification:
- Analyze fix content (`suggestedFix`, `suggestedFixReasoning`, `taskOutputSummary`)
- If fix looks correct → apply via MCP
- If fix needs enhancement → use Apply Locally + Enhance Flow above
- If fix is wrong → reject via MCP, fix from scratch, commit, push
### Auto-Apply Eligibility
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
### Apply vs Reject vs Apply Locally
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
### Apply Locally + Enhance Flow
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
2. Make additional changes as needed
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Reject + Fix From Scratch Flow
When the fix is completely wrong:
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
2. Fix the issue from scratch locally
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Environment Issue Handling
When `failureClassification == 'ENVIRONMENT_STATE'`:
1. Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`
2. New CIPE spawns automatically (no local git operations needed)
3. Loop to poll for new CIPE with `previousCipeUrl` set
### No-New-CIPE Handling
When `status == 'no_new_cipe'`:
This means the expected CIPE was never created - CI likely failed before Nx tasks could run.
1. **Report to user:**
```
[ci-monitor] No CI attempt for <sha> after 10 min. Check CI provider for pre-Nx failures (install, checkout, auth). Last CI attempt: <previousCipeUrl>
```
2. **If user configured auto-fix attempts** (e.g., `--auto-fix-workflow`):
- Detect package manager: check for `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`
- Run install to update lockfile:
```bash
pnpm install # or npm install / yarn install
```
- If lockfile changed:
```bash
git add pnpm-lock.yaml # or appropriate lockfile
git commit -m "chore: update lockfile"
git push origin $(git branch --show-current)
```
- Record new commit SHA, loop to poll with `expectedCommitSha`
3. **Otherwise:** Exit with `no_new_cipe` status, providing guidance for user to investigate
## Exit Conditions
Exit the monitoring loop when ANY of these conditions are met:
| Condition | Exit Type |
| ------------------------------------------- | ---------------- |
| CI passes (`cipeStatus == 'SUCCEEDED'`) | Success |
| Max CIPE cycles reached | Timeout |
| Max duration reached | Timeout |
| 3 consecutive no-progress iterations | Circuit breaker |
| No fix available and local fix not possible | Failure |
| No new CIPE and auto-fix not configured | Pre-CIPE failure |
| User cancels | Cancelled |
## Main Loop
### Step 1: Initialize Tracking
```
cycle_count = 0
start_time = now()
no_progress_count = 0
local_verify_count = 0
last_state = null
last_cipe_url = null
expected_commit_sha = null
```
### Step 2: Spawn Subagent
Spawn the `ci-watcher` subagent to poll CI status:
**Fresh start (first spawn, no expected CIPE):**
```
Task(
agent: "ci-watcher",
prompt: "Monitor CI for branch '<branch>'.
Subagent timeout: <subagent-timeout> minutes.
New-CIPE timeout: <new-cipe-timeout> minutes.
Verbosity: <verbosity>."
)
```
**After action that triggers new CIPE (wait mode):**
```
Task(
agent: "ci-watcher",
prompt: "Monitor CI for branch '<branch>'.
Subagent timeout: <subagent-timeout> minutes.
New-CIPE timeout: <new-cipe-timeout> minutes.
Verbosity: <verbosity>.
WAIT MODE: A new CIPE should spawn. Ignore old CIPE until new one appears.
Expected commit SHA: <expected_commit_sha>
Previous CIPE URL: <last_cipe_url>"
)
```
### Step 3: Handle Subagent Response
When subagent returns:
1. Check the returned status
2. Look up default behavior in the table above
3. Check if user instructions override the default
4. Execute the appropriate action
5. **If action expects new CIPE**, update tracking (see Step 3a)
6. If action results in looping, go to Step 2
### Step 3a: Track State for New-CIPE Detection
After actions that should trigger a new CIPE, record state before looping:
| Action | What to Track | Subagent Mode |
| ----------------------------- | --------------------------------------------- | ------------- |
| Fix auto-applying | `last_cipe_url = current cipeUrl` | Wait mode |
| Apply via MCP | `last_cipe_url = current cipeUrl` | Wait mode |
| Apply locally + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Reject + fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Fix failed + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| No fix + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Environment rerun | `last_cipe_url = current cipeUrl` | Wait mode |
| No-new-CIPE + auto-fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
- Subagent will **completely ignore** the old/stale CIPE
- Subagent will only wait for new CIPE to appear
- Subagent will NOT return to main agent with stale CIPE data
- Once new CIPE detected, subagent switches to normal polling
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
### Step 4: Progress Tracking
After each action:
- If state changed significantly → reset `no_progress_count = 0`
- If state unchanged → `no_progress_count++`
- On new CI attempt detected → reset `local_verify_count = 0`
## Status Reporting
Based on verbosity level:
| Level | What to Report |
| --------- | -------------------------------------------------------------------------- |
| `minimal` | Only final result (success/failure/timeout) |
| `medium` | State changes + periodic updates ("Cycle N \| Elapsed: Xm \| Status: ...") |
| `verbose` | All of medium + full subagent responses, git outputs, MCP responses |
## User Instruction Examples
Users can override default behaviors:
| Instruction | Effect |
| ------------------------------------------------ | --------------------------------------------- |
| "never auto-apply" | Always prompt before applying any fix |
| "always ask before git push" | Prompt before each push |
| "reject any fix for e2e tasks" | Auto-reject if `failedTaskIds` contains e2e |
| "apply all fixes regardless of verification" | Skip verification check, apply everything |
| "if confidence < 70, reject" | Check confidence field before applying |
| "run 'nx affected -t typecheck' before applying" | Add local verification step |
| "auto-fix workflow failures" | Attempt lockfile updates on pre-CIPE failures |
| "wait 45 min for new CIPE" | Override new-CIPE timeout (default: 10 min) |
## Error Handling
| Error | Action |
| ------------------------ | ------------------------------------------------------------------------------------- |
| Git rebase conflict | Report to user, exit |
| `nx apply-locally` fails | Report to user, attempt manual patch or exit |
| MCP tool error | Retry once, if fails report to user |
| Subagent spawn failure | Retry once, if fails exit with error |
| No new CIPE detected | If `--auto-fix-workflow`, try lockfile update; otherwise report to user with guidance |
| Lockfile auto-fix fails | Report to user, exit with guidance to check CI logs |
## Example Session
### Example 1: Normal Flow with Self-Healing (medium verbosity)
```
[ci-monitor] Starting CI monitor for branch 'feature/add-auth'
[ci-monitor] Config: max-cycles=5, timeout=120m, verbosity=medium
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] CI attempt: IN_PROGRESS | Self-Healing: NOT_STARTED | Elapsed: 1m
[CI Monitor] CI attempt: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 3m
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 5m
[ci-monitor] Fix available! Verification: COMPLETED
[ci-monitor] Applying fix via MCP...
[ci-monitor] Fix applied in CI. Waiting for new CI attempt...
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] New CI attempt detected!
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 8m
[ci-monitor] CI passed successfully!
[ci-monitor] Summary:
- Total cycles: 2
- Total time: 12m 34s
- Fixes applied: 1
- Result: SUCCESS
```
### Example 2: Pre-CI Failure (medium verbosity)
```
[ci-monitor] Starting CI monitor for branch 'feature/add-products'
[ci-monitor] Config: max-cycles=5, timeout=120m, auto-fix-workflow=true
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 2m
[ci-monitor] Applying fix locally, enhancing, and pushing...
[ci-monitor] Committed: abc1234
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] Waiting for new CI attempt... (expected SHA: abc1234)
[CI Monitor] ⚠️ CI attempt timeout (10 min). Returning no_new_cipe.
[ci-monitor] Status: no_new_cipe
[ci-monitor] --auto-fix-workflow enabled. Attempting lockfile update...
[ci-monitor] Lockfile updated. Committed: def5678
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] New CI attempt detected!
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 18m
[ci-monitor] CI passed successfully!
[ci-monitor] Summary:
- Total cycles: 3
- Total time: 22m 15s
- Fixes applied: 1 (self-healing) + 1 (lockfile)
- Result: SUCCESS
```
+437
View File
@@ -0,0 +1,437 @@
---
name: ci-monitor
description: Monitor Nx Cloud CI pipeline and handle self-healing fixes automatically. Checks for Nx Cloud connection before starting.
---
# CI Monitor Command
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
## Context
- **Current Branch:** !`git branch --show-current`
- **Current Commit:** !`git rev-parse --short HEAD`
- **Remote Status:** !`git status -sb | head -1`
## User Instructions
$ARGUMENTS
**Important:** If user provides specific instructions, respect them over default behaviors described below.
## Configuration Defaults
| Setting | Default | Description |
| ------------------------- | ------------- | ------------------------------------------------------------------- |
| `--max-cycles` | 10 | Maximum CIPE cycles before timeout |
| `--timeout` | 120 | Maximum duration in minutes |
| `--verbosity` | medium | Output level: minimal, medium, verbose |
| `--branch` | (auto-detect) | Branch to monitor |
| `--subagent-timeout` | 60 | Subagent polling timeout in minutes |
| `--fresh` | false | Ignore previous context, start fresh |
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
Parse any overrides from `$ARGUMENTS` and merge with defaults.
## Nx Cloud Connection Check
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
### Step 0: Verify Nx Cloud Connection
1. **Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
2. **If `nx.json` missing OR neither property exists** → exit with:
```
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
```
3. **If connected** → continue to main loop
## Session Context Behavior
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
- **For a completely clean slate:** Exit Claude Code and restart `claude`
## Default Behaviors by Status
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
| Status | Default Behavior |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ci_success` | Exit with success. Log "CI passed successfully!" |
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
| `polling_timeout` | Subagent polling timeout reached. Exit with timeout. |
| `cipe_canceled` | CIPE was canceled. Exit with canceled status. |
| `cipe_timed_out` | CIPE timed out. Exit with timeout status. |
| `error` | Increment `no_progress_count`. If >= 3 → exit with circuit breaker. Otherwise wait 60s and loop. |
### Fix Available Decision Logic
When subagent returns `fix_available`, main agent compares `failedTaskIds` vs `verifiedTaskIds`:
#### Step 1: Categorize Tasks
1. **Verified tasks** = tasks in both `failedTaskIds` AND `verifiedTaskIds`
2. **Unverified tasks** = tasks in `failedTaskIds` but NOT in `verifiedTaskIds`
3. **E2E tasks** = unverified tasks where target contains "e2e" (task format: `<project>:<target>` or `<project>:<target>:<config>`)
4. **Verifiable tasks** = unverified tasks that are NOT e2e
#### Step 2: Determine Path
| Condition | Path |
| --------------------------------------- | ---------------------------------------- |
| No unverified tasks (all verified) | Apply via MCP |
| Unverified tasks exist, but ALL are e2e | Apply via MCP (treat as verified enough) |
| Verifiable tasks exist | Local verification flow |
#### Step 3a: Apply via MCP (fully/e2e-only verified)
- Call `update_self_healing_fix({ shortLink, action: "APPLY" })`
- Record `last_cipe_url`, spawn subagent in wait mode
#### Step 3b: Local Verification Flow
When verifiable (non-e2e) unverified tasks exist:
1. **Detect package manager:**
- `pnpm-lock.yaml` exists → `pnpm nx`
- `yarn.lock` exists → `yarn nx`
- Otherwise → `npx nx`
2. **Run verifiable tasks in parallel:**
- Spawn `general` subagents to run each task concurrently
- Each subagent runs: `<pm> nx run <taskId>`
- Collect pass/fail results from all subagents
3. **Evaluate results:**
| Result | Action |
| ------------------------- | ---------------------------- |
| ALL verifiable tasks pass | Apply via MCP |
| ANY verifiable task fails | Apply-locally + enhance flow |
4. **Apply-locally + enhance flow:**
- Run `nx apply-locally <shortLink>`
- Enhance the code to fix failing tasks
- Run failing tasks again to verify fix
- If still failing → increment `local_verify_count`, loop back to enhance
- If passing → commit and push, record `expected_commit_sha`, spawn subagent in wait mode
5. **Track attempts** (wraps step 4):
- Increment `local_verify_count` after each enhance cycle
- If `local_verify_count >= local_verify_attempts` (default: 3):
- Get code in commit-able state
- Commit and push with message indicating local verification failed
- Report to user:
```
[ci-monitor] Local verification failed after <N> attempts. Pushed to CI for final validation. Failed: <taskIds>
```
- Record `expected_commit_sha`, spawn subagent in wait mode (let CI be final judge)
#### Commit Message Format
```bash
git commit -m "fix(<projects>): <brief description>
Failed tasks: <taskId1>, <taskId2>
Local verification: passed|enhanced|failed-pushing-to-ci"
```
### Unverified Fix Flow (No Verification Attempted)
When `verificationStatus` is `FAILED`, `NOT_EXECUTABLE`, or fix has `couldAutoApplyTasks != true` with no verification:
- Analyze fix content (`suggestedFix`, `suggestedFixReasoning`, `taskOutputSummary`)
- If fix looks correct → apply via MCP
- If fix needs enhancement → use Apply Locally + Enhance Flow above
- If fix is wrong → reject via MCP, fix from scratch, commit, push
### Auto-Apply Eligibility
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
### Apply vs Reject vs Apply Locally
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
### Apply Locally + Enhance Flow
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
2. Make additional changes as needed
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Reject + Fix From Scratch Flow
When the fix is completely wrong:
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
2. Fix the issue from scratch locally
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Environment Issue Handling
When `failureClassification == 'ENVIRONMENT_STATE'`:
1. Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`
2. New CIPE spawns automatically (no local git operations needed)
3. Loop to poll for new CIPE with `previousCipeUrl` set
### No-New-CIPE Handling
When `status == 'no_new_cipe'`:
This means the expected CIPE was never created - CI likely failed before Nx tasks could run.
1. **Report to user:**
```
[ci-monitor] No CI attempt for <sha> after 10 min. Check CI provider for pre-Nx failures (install, checkout, auth). Last CI attempt: <previousCipeUrl>
```
2. **If user configured auto-fix attempts** (e.g., `--auto-fix-workflow`):
- Detect package manager: check for `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`
- Run install to update lockfile:
```bash
pnpm install # or npm install / yarn install
```
- If lockfile changed:
```bash
git add pnpm-lock.yaml # or appropriate lockfile
git commit -m "chore: update lockfile"
git push origin $(git branch --show-current)
```
- Record new commit SHA, loop to poll with `expectedCommitSha`
3. **Otherwise:** Exit with `no_new_cipe` status, providing guidance for user to investigate
## Exit Conditions
Exit the monitoring loop when ANY of these conditions are met:
| Condition | Exit Type |
| ------------------------------------------- | ---------------- |
| CI passes (`cipeStatus == 'SUCCEEDED'`) | Success |
| Max CIPE cycles reached | Timeout |
| Max duration reached | Timeout |
| 3 consecutive no-progress iterations | Circuit breaker |
| No fix available and local fix not possible | Failure |
| No new CIPE and auto-fix not configured | Pre-CIPE failure |
| User cancels | Cancelled |
## Main Loop
### Step 1: Initialize Tracking
```
cycle_count = 0
start_time = now()
no_progress_count = 0
local_verify_count = 0
last_state = null
last_cipe_url = null
expected_commit_sha = null
```
### Step 2: Spawn Subagent
Spawn the `ci-watcher` subagent to poll CI status:
**Fresh start (first spawn, no expected CIPE):**
```
Task(
agent: "ci-watcher",
prompt: "Monitor CI for branch '<branch>'.
Subagent timeout: <subagent-timeout> minutes.
New-CIPE timeout: <new-cipe-timeout> minutes.
Verbosity: <verbosity>."
)
```
**After action that triggers new CIPE (wait mode):**
```
Task(
agent: "ci-watcher",
prompt: "Monitor CI for branch '<branch>'.
Subagent timeout: <subagent-timeout> minutes.
New-CIPE timeout: <new-cipe-timeout> minutes.
Verbosity: <verbosity>.
WAIT MODE: A new CIPE should spawn. Ignore old CIPE until new one appears.
Expected commit SHA: <expected_commit_sha>
Previous CIPE URL: <last_cipe_url>"
)
```
### Step 3: Handle Subagent Response
When subagent returns:
1. Check the returned status
2. Look up default behavior in the table above
3. Check if user instructions override the default
4. Execute the appropriate action
5. **If action expects new CIPE**, update tracking (see Step 3a)
6. If action results in looping, go to Step 2
### Step 3a: Track State for New-CIPE Detection
After actions that should trigger a new CIPE, record state before looping:
| Action | What to Track | Subagent Mode |
| ----------------------------- | --------------------------------------------- | ------------- |
| Fix auto-applying | `last_cipe_url = current cipeUrl` | Wait mode |
| Apply via MCP | `last_cipe_url = current cipeUrl` | Wait mode |
| Apply locally + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Reject + fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Fix failed + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| No fix + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Environment rerun | `last_cipe_url = current cipeUrl` | Wait mode |
| No-new-CIPE + auto-fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
- Subagent will **completely ignore** the old/stale CIPE
- Subagent will only wait for new CIPE to appear
- Subagent will NOT return to main agent with stale CIPE data
- Once new CIPE detected, subagent switches to normal polling
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
### Step 4: Progress Tracking
After each action:
- If state changed significantly → reset `no_progress_count = 0`
- If state unchanged → `no_progress_count++`
- On new CI attempt detected → reset `local_verify_count = 0`
## Status Reporting
Based on verbosity level:
| Level | What to Report |
| --------- | -------------------------------------------------------------------------- |
| `minimal` | Only final result (success/failure/timeout) |
| `medium` | State changes + periodic updates ("Cycle N \| Elapsed: Xm \| Status: ...") |
| `verbose` | All of medium + full subagent responses, git outputs, MCP responses |
## User Instruction Examples
Users can override default behaviors:
| Instruction | Effect |
| ------------------------------------------------ | --------------------------------------------- |
| "never auto-apply" | Always prompt before applying any fix |
| "always ask before git push" | Prompt before each push |
| "reject any fix for e2e tasks" | Auto-reject if `failedTaskIds` contains e2e |
| "apply all fixes regardless of verification" | Skip verification check, apply everything |
| "if confidence < 70, reject" | Check confidence field before applying |
| "run 'nx affected -t typecheck' before applying" | Add local verification step |
| "auto-fix workflow failures" | Attempt lockfile updates on pre-CIPE failures |
| "wait 45 min for new CIPE" | Override new-CIPE timeout (default: 10 min) |
## Error Handling
| Error | Action |
| ------------------------ | ------------------------------------------------------------------------------------- |
| Git rebase conflict | Report to user, exit |
| `nx apply-locally` fails | Report to user, attempt manual patch or exit |
| MCP tool error | Retry once, if fails report to user |
| Subagent spawn failure | Retry once, if fails exit with error |
| No new CIPE detected | If `--auto-fix-workflow`, try lockfile update; otherwise report to user with guidance |
| Lockfile auto-fix fails | Report to user, exit with guidance to check CI logs |
## Example Session
### Example 1: Normal Flow with Self-Healing (medium verbosity)
```
[ci-monitor] Starting CI monitor for branch 'feature/add-auth'
[ci-monitor] Config: max-cycles=5, timeout=120m, verbosity=medium
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] CI attempt: IN_PROGRESS | Self-Healing: NOT_STARTED | Elapsed: 1m
[CI Monitor] CI attempt: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 3m
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 5m
[ci-monitor] Fix available! Verification: COMPLETED
[ci-monitor] Applying fix via MCP...
[ci-monitor] Fix applied in CI. Waiting for new CI attempt...
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] New CI attempt detected!
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 8m
[ci-monitor] CI passed successfully!
[ci-monitor] Summary:
- Total cycles: 2
- Total time: 12m 34s
- Fixes applied: 1
- Result: SUCCESS
```
### Example 2: Pre-CI Failure (medium verbosity)
```
[ci-monitor] Starting CI monitor for branch 'feature/add-products'
[ci-monitor] Config: max-cycles=5, timeout=120m, auto-fix-workflow=true
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 2m
[ci-monitor] Applying fix locally, enhancing, and pushing...
[ci-monitor] Committed: abc1234
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] Waiting for new CI attempt... (expected SHA: abc1234)
[CI Monitor] ⚠️ CI attempt timeout (10 min). Returning no_new_cipe.
[ci-monitor] Status: no_new_cipe
[ci-monitor] --auto-fix-workflow enabled. Attempting lockfile update...
[ci-monitor] Lockfile updated. Committed: def5678
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] New CI attempt detected!
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 18m
[ci-monitor] CI passed successfully!
[ci-monitor] Summary:
- Total cycles: 3
- Total time: 22m 15s
- Fixes applied: 1 (self-healing) + 1 (lockfile)
- Result: SUCCESS
```
+228
View File
@@ -0,0 +1,228 @@
---
name: nx-generate
description: Generate code using nx generators. USE WHEN scaffolding code or transforming existing code - for example creating libraries or applications, or anything else that is boilerplate code or automates repetitive tasks. ALWAYS use this first when generating code with Nx instead of calling MCP tools or running nx generate immediately.
---
# Run Nx Generator
Nx generators are powerful tools that scaffold projects, make automated code migrations or automate repetitive tasks in a monorepo. They ensure consistency across the codebase and reduce boilerplate work.
This skill applies when the user wants to:
- Create new projects like libraries or applications
- Scaffold features or boilerplate code
- Run workspace-specific or custom generators
- Do anything else that an nx generator exists for
## Generator Discovery Flow
### Step 1: List Available Generators
Use the Nx CLI to discover available generators:
- List all generators for a plugin: `npx nx list @nx/react`
- View available plugins: `npx nx list`
This includes:
- Plugin generators (e.g., `@nx/react:library`, `@nx/js:library`)
- Local workspace generators (defined in the repo's own plugins)
### Step 2: Match Generator to User Request
Based on the user's request, identify which generator(s) could fulfill their needs. Consider:
- What artifact type they want to create (library, application, etc.)
- Which framework or technology stack is relevant
- Whether they mentioned specific generator names
**IMPORTANT**: When both a local workspace generator and an external plugin generator could satisfy the request, **always prefer the local workspace generator**. Local generators are customized for the specific repo's patterns and conventions.
It's possible that the user request is something that no Nx generator exists for whatsoever. In this case, you can stop using this skill and try to help the user another way. HOWEVER, the burden of proof for this is high. Before aborting, carefully consider each and every generator that's available. Look into details for any that could be related in any way before making this decision.
## Pre-Execution Checklist
Before running any generator, complete these steps:
### 1. Fetch Generator Schema
Use the `--help` flag to understand all available options:
```bash
npx nx g @nx/react:library --help
```
Pay attention to:
- Required options that must be provided
- Optional options that may be relevant to the user's request
- Default values that might need to be overridden
### 2. Read Generator Source Code
Understanding what the generator actually does helps you:
- Know what files will be created/modified
- Understand any side effects (updating configs, installing deps, etc.)
- Identify options that might not be obvious from the schema
To find generator source code:
- For plugin generators: Use `node -e "console.log(require.resolve('@nx/<plugin>/generators.json'));"` to find the generators.json, then locate the source from there
- If that fails, read directly from `node_modules/<plugin>/generators.json`
- For local generators: They are typically in `tools/generators/` or a local plugin directory. You can search the repo for the generator name to find it.
### 2.5 Reevaluate if the generator is right
Once you have built up an understanding of what the selected generator does, reconsider: Is this the right generator to service the user request?
If not, it's okay to go back to the Generator Discovery Flow and select a different generator before proceeding. If you do, make sure to go through the entire pre-execution checklist once more.
### 3. Understand Repo Context
Before generating, examine the target area of the codebase:
- Look at similar existing artifacts (other libraries, applications, etc.)
- Identify patterns and conventions used in the repo
- Note naming conventions, file structures, and configuration patterns
- Try to match these patterns when configuring the generator
For example, if similar libraries are using a specific test runner, build tool or linter, try to match that if possible.
If projects or other artifacts are organized with a specific naming convention, try to match it.
### 4. Validate Required Options
Ensure all required options have values:
- Map the user's request to generator options
- Infer values from context where possible
- Ask the user for any critical missing information
## Execution
Keep in mind that you might have to prefix things with npx/pnpx/yarn if the user doesn't have nx installed globally.
Many generators will behave differently based on where they are executed. For example, first-party nx library generators use the cwd to determine the directory that the library should be placed in. This is highly important.
### Consider Dry-Run (Optional)
Running with `--dry-run` first is strongly encouraged but not mandatory. Use your judgment:
- For complex generators or unfamiliar territory: do a dry-run first
- For simple, well-understood generators: may proceed directly
- Dry-run shows file names and created/deleted/modified markers, but not content
- There are cases where a generator does not support dry-run (for example if it had to install an npm package) - in that case --dry-run might fail. Don't be discouraged but simply move on to running the generator for real and iterating from there.
### Running the Generator
Execute the generator with:
```bash
nx generate <generator-name> <options> --no-interactive
```
**CRITICAL**: Always include `--no-interactive` to prevent prompts that would hang the execution.
Example:
```bash
nx generate @nx/react:library --name=my-utils --no-interactive
```
### Handling Generator Failures
If the generator fails:
1. **Diagnose the error** - Read the error message carefully
2. **Identify the cause** - Missing options, invalid values, conflicts, etc.
3. **Attempt automatic fix** - Adjust options or resolve conflicts
4. **Retry** - Run the generator again with corrected options
Common failure reasons:
- Missing required options
- Invalid option values
- Conflicting with existing files
- Missing dependencies
- Generator doesn't support certain flag combinations
## Post-Generation
### 1. Modify Generated Code (If Needed)
Generators provide a starting point, but the output may need adjustment to match the user's specific requirements:
- Add or modify functionality as requested
- Adjust imports, exports, or configurations
- Integrate with existing code patterns in the repo
### 2. Format Code
Run formatting on all generated/modified files:
```bash
nx format --fix
```
Languages other than javascript/typescript might need other formatting invocations too.
### 3. Run Verification
Verify that the generated code works correctly. What this looks like will vary depending on the type of generator and the targets available.
If the generator created a new project, run its targets directly
Use your best judgement to determine what needs to be verified.
Example:
```bash
nx lint <new-project>
nx test <new-project>
nx build <new-project>
```
### 4. Handle Verification Failures
When verification fails:
**If scope is manageable** (a few lint errors, minor type issues):
- Fix the issues
- Re-run verification to confirm
**If issues are extensive** (many errors, complex problems):
- Attempt simple, obvious fixes first
- If still failing, escalate to the user with:
- Description of what was generated
- What verification is failing
- What you've attempted to fix
- Remaining issues that need user input
## Error Handling
### Generator Failures
- Check the error message for specific causes
- Verify all required options are provided
- Check for conflicts with existing files
- Ensure the generator name and options are correct
### Missing Options
- Consult the generator schema for required fields
- Infer values from context when reasonable
- Ask the user for values that cannot be inferred
## Key Principles
1. **Local generators first** - Always prefer workspace/local generators over external plugin generators when both could work
2. **Understand before running** - Read both the schema AND the source code to fully understand what will happen
3. **No prompts** - Always use `--no-interactive` to prevent hanging
4. **Generators are starting points** - Modify the output as needed to fully satisfy the user's requirements
5. **Verify changes work** - Don't just generate; ensure the code builds, lints, and tests pass
6. **Be proactive about fixes** - Don't just report errors; attempt to resolve them automatically when possible
7. **Match repo patterns** - Study existing similar code in the repo and match its conventions
+9
View File
@@ -0,0 +1,9 @@
---
name: nx-plugins
description: Find and add Nx plugins. USE WHEN user wants to discover available plugins, install a new plugin, or add support for a specific framework or technology to the workspace.
---
## Finding and Installing new plugins
- List plugins: `pnpm nx list`
- Install plugins `pnpm nx add <plugin>`. Example: `pnpm nx add @nx/react`.
+58
View File
@@ -0,0 +1,58 @@
---
name: nx-run-tasks
description: Helps with running tasks in an Nx workspace. USE WHEN the user wants to execute build, test, lint, serve, or run any other tasks defined in the workspace.
---
You can run tasks with Nx in the following way.
Keep in mind that you might have to prefix things with npx/pnpx/yarn if the user doesn't have nx installed globally. Look at the package.json or lockfile to determine which package manager is in use.
For more details on any command, run it with `--help` (e.g. `nx run-many --help`, `nx affected --help`).
## Understand which tasks can be run
You can check those via `nx show project <projectname> --json`, for example `nx show project myapp --json`. It contains a `targets` section which has information about targets that can be run. You can also just look at the `package.json` scripts or `project.json` targets, but you might miss out on inferred tasks by Nx plugins.
## Run a single task
```
nx run <project>:<task>
```
where `project` is the project name defined in `package.json` or `project.json` (if present).
## Run multiple tasks
```
nx run-many -t build test lint typecheck
```
You can pass a `-p` flag to filter to specific projects, otherwise it runs on all projects. You can also use `--exclude` to exclude projects, and `--parallel` to control the number of parallel processes (default is 3).
Examples:
- `nx run-many -t test -p proj1 proj2` — test specific projects
- `nx run-many -t test --projects=*-app --exclude=excluded-app` — test projects matching a pattern
- `nx run-many -t test --projects=tag:api-*` — test projects by tag
## Run tasks for affected projects
Use `nx affected` to only run tasks on projects that have been changed and projects that depend on changed projects. This is especially useful in CI and for large workspaces.
```
nx affected -t build test lint
```
By default it compares against the base branch. You can customize this:
- `nx affected -t test --base=main --head=HEAD` — compare against a specific base and head
- `nx affected -t test --files=libs/mylib/src/index.ts` — specify changed files directly
## Useful flags
These flags work with `run`, `run-many`, and `affected`:
- `--skipNxCache` — rerun tasks even when results are cached
- `--verbose` — print additional information such as stack traces
- `--nxBail` — stop execution after the first failed task
- `--configuration=<name>` — use a specific configuration (e.g. `production`)
+186
View File
@@ -0,0 +1,186 @@
---
name: nx-workspace
description: "Explore and understand Nx workspaces. USE WHEN answering any questions about the nx workspace, the projects in it or tasks to run. EXAMPLES: 'What projects are in this workspace?', 'How is project X configured?', 'What targets can I run?', 'What's affected by my changes?', 'Which projects depend on library Y?', or any questions about Nx workspace structure, project configuration, or available tasks."
---
# Nx Workspace Exploration
This skill provides read-only exploration of Nx workspaces. Use it to understand workspace structure, project configuration, available targets, and dependencies.
Keep in mind that you might have to prefix commands with `npx`/`pnpx`/`yarn` if nx isn't installed globally. Check the lockfile to determine the package manager in use.
## Listing Projects
Use `nx show projects` to list projects in the workspace.
```bash
# List all projects
nx show projects
# Filter by pattern (glob)
nx show projects --projects "apps/*"
nx show projects --projects "shared-*"
# Filter by project type
nx show projects --type app
nx show projects --type lib
nx show projects --type e2e
# Filter by target (projects that have a specific target)
nx show projects --withTarget build
nx show projects --withTarget e2e
# Find affected projects (changed since base branch)
nx show projects --affected
nx show projects --affected --base=main
nx show projects --affected --type app
# Combine filters
nx show projects --type lib --withTarget test
nx show projects --affected --exclude="*-e2e"
# Output as JSON
nx show projects --json
```
## Project Configuration
Use `nx show project <name> --json` to get the full resolved configuration for a project.
**Important**: Do NOT read `project.json` directly - it only contains partial configuration. The `nx show project` command returns the full resolved config including inferred targets from plugins.
You can read the full project schema at `node_modules/nx/schemas/project-schema.json` to understand nx project configuration options.
```bash
# Get full project configuration
nx show project my-app --json
# Extract specific parts from the JSON
nx show project my-app --json | jq '.targets'
nx show project my-app --json | jq '.targets.build'
nx show project my-app --json | jq '.targets | keys'
# Check project metadata
nx show project my-app --json | jq '{name, root, sourceRoot, projectType, tags}'
```
## Target Information
Targets define what tasks can be run on a project.
```bash
# List all targets for a project
nx show project my-app --json | jq '.targets | keys'
# Get full target configuration
nx show project my-app --json | jq '.targets.build'
# Check target executor/command
nx show project my-app --json | jq '.targets.build.executor'
nx show project my-app --json | jq '.targets.build.command'
# View target options
nx show project my-app --json | jq '.targets.build.options'
# Check target inputs/outputs (for caching)
nx show project my-app --json | jq '.targets.build.inputs'
nx show project my-app --json | jq '.targets.build.outputs'
# Find projects with a specific target
nx show projects --withTarget serve
nx show projects --withTarget e2e
```
## Workspace Configuration
Read `nx.json` directly for workspace-level configuration.
You can read the full project schema at `node_modules/nx/schemas/nx-schema.json` to understand nx project configuration options.
```bash
# Read the full nx.json
cat nx.json
# Or use jq for specific sections
cat nx.json | jq '.targetDefaults'
cat nx.json | jq '.namedInputs'
cat nx.json | jq '.plugins'
cat nx.json | jq '.generators'
```
Key nx.json sections:
- `targetDefaults` - Default configuration applied to all targets of a given name
- `namedInputs` - Reusable input definitions for caching
- `plugins` - Nx plugins and their configuration
- ...and much more, read the schema or nx.json for details
## Affected Projects
Find projects affected by changes in the current branch.
```bash
# Affected since base branch (auto-detected)
nx show projects --affected
# Affected with explicit base
nx show projects --affected --base=main
nx show projects --affected --base=origin/main
# Affected between two commits
nx show projects --affected --base=abc123 --head=def456
# Affected apps only
nx show projects --affected --type app
# Affected excluding e2e projects
nx show projects --affected --exclude="*-e2e"
# Affected by uncommitted changes
nx show projects --affected --uncommitted
# Affected by untracked files
nx show projects --affected --untracked
```
## Common Exploration Patterns
### "What's in this workspace?"
```bash
nx show projects
nx show projects --type app
nx show projects --type lib
```
### "How do I build/test/lint project X?"
```bash
nx show project X --json | jq '.targets | keys'
nx show project X --json | jq '.targets.build'
```
### "What depends on library Y?"
```bash
# Find projects that may depend on Y by searching for imports
# (Nx doesn't have a direct "dependents" command via CLI)
grep -r "from '@myorg/Y'" --include="*.ts" --include="*.tsx" apps/ libs/
```
### "What configuration options are available?"
```bash
cat node_modules/nx/schemas/nx-schema.json | jq '.properties | keys'
cat node_modules/nx/schemas/project-schema.json | jq '.properties | keys'
```
### "Why is project X affected?"
```bash
# Check what files changed
git diff --name-only main
# See which project owns those files
nx show project X --json | jq '.root'
```
+101
View File
@@ -0,0 +1,101 @@
name: Banner Content Monitor
on:
schedule:
- cron: '*/15 * * * *'
workflow_dispatch: # Allow manual trigger
permissions: {}
env:
BANNER_URL: ${{ vars.BANNER_URL }}
jobs:
check-and-deploy:
if: ${{ github.repository_owner == 'nrwl' }}
runs-on: ubuntu-latest
steps:
- name: Fetch banner content and compute hash
id: banner
run: |
if [ -z "$BANNER_URL" ]; then
echo "BANNER_URL is not set"
exit 1
fi
# Fetch content and compute hash
CONTENT_HASH=$(curl -sf "$BANNER_URL" | sha256sum | cut -d' ' -f1)
if [ -z "$CONTENT_HASH" ]; then
echo "Failed to fetch banner content"
exit 1
fi
echo "current_hash=$CONTENT_HASH" >> $GITHUB_OUTPUT
echo "Current banner hash: $CONTENT_HASH"
- name: Restore cached hash
id: cache
uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: .banner-hash
key: banner-content-hash-
restore-keys: |
banner-content-hash-
- name: Compare hashes
id: compare
run: |
CURRENT_HASH="${{ steps.banner.outputs.current_hash }}"
if [ -f .banner-hash ]; then
CACHED_HASH=$(cat .banner-hash)
echo "Cached hash: $CACHED_HASH"
else
CACHED_HASH=""
echo "No cached hash found"
fi
if [ "$CURRENT_HASH" != "$CACHED_HASH" ]; then
echo "changed=true" >> $GITHUB_OUTPUT
echo "Banner content has changed!"
else
echo "changed=false" >> $GITHUB_OUTPUT
echo "Banner content unchanged"
fi
- name: Setup Node
if: steps.compare.outputs.changed == 'true'
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
node-version: '24'
- name: Trigger Netlify deploys
if: steps.compare.outputs.changed == 'true'
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
run: |
npm install -g netlify-cli
echo "Triggering nx-docs deploy..."
netlify deploy --trigger --prod -s nx-docs
echo "Triggering nx-dev deploy..."
netlify deploy --trigger --prod -s nx-dev
echo "Triggering nrwl-blog deploy..."
netlify deploy --trigger --prod -s nrwl-blog
echo "All deploys triggered successfully"
- name: Save new hash to cache
if: steps.compare.outputs.changed == 'true'
run: |
echo "${{ steps.banner.outputs.current_hash }}" > .banner-hash
- name: Update cache
if: steps.compare.outputs.changed == 'true'
uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: .banner-hash
key: banner-content-hash-${{ github.run_id }}
+59 -22
View File
@@ -13,11 +13,16 @@ env:
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
NX_CLOUD_ENABLE_METRICS_COLLECTION: 'true'
PNPM_HOME: ~/.pnpm
# 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), pulling pnpm 11 and breaking install.
COREPACK_DEFAULT_TO_LATEST: '0'
jobs:
main-linux:
runs-on: ubuntu-latest
env:
NX_BATCH_MODE: 'true'
NX_E2E_CI_CACHE_KEY: e2e-github-linux
NX_DAEMON: 'true'
NX_PERF_LOGGING: 'false'
@@ -27,8 +32,9 @@ jobs:
NX_CI_EXECUTION_ENV: 'linux'
NX_CLOUD_NO_TIMEOUTS: 'true'
NX_ALLOW_NON_CACHEABLE_DTE: 'true'
NX_CLOUD_USE_NEW_TASK_APIS: 'true'
NX_CLOUD_USE_NEW_STREAM_OUTPUT: 'true'
NX_CLOUD_EXPERIMENTAL_POLLING: 'true'
NX_CLOUD_CONTINUOUS_ASSIGNMENT: 'false'
NX_CLOUD_VERBOSE_LOGGING: 'true'
steps:
- name: Checkout
@@ -37,25 +43,21 @@ jobs:
fetch-depth: 0
filter: tree:0
- name: Set verbose logging from debug mode
if: runner.debug == '1'
run: echo "NX_VERBOSE_LOGGING=true" >> "$GITHUB_ENV"
- name: Fetch Master
run: git fetch origin master:master
if: ${{ github.event_name == 'pull_request' }}
- name: Set SHAs
uses: nrwl/nx-set-shas@1859e66a83ac9be0dceecbd9a023702e27ac47f4 # v4.3.3
uses: nrwl/nx-set-shas@310288c04d90696f9f1bc27c5e3caea6642b53d4 # v5.0.0
with:
main-branch-name: 'master'
- name: Start CI Run
run: npx nx-cloud@next start-ci-run --auto-apply-fixes="*format:check*,*sync:check*,*conformance:check*,*format-native*,*lint-native*,*lint*,*astro-docs:validate-links*" --distribute-on="./.nx/workflows/dynamic-changesets.yaml" --stop-agents-after="e2e"
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y ca-certificates lsof libvips-dev libglib2.0-dev libgirepository1.0-dev
- name: Install Chrome
uses: browser-actions/setup-chrome@2dbff04819ebbfd5c974947148805a825b8a07fd # v2.1.0
run: npx nx-cloud@next start-ci-run --distribute-on="./.nx/workflows/dynamic-changesets.yaml" --stop-agents-after="e2e"
- name: Setup dev tools with mise
uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3
@@ -65,35 +67,48 @@ jobs:
corepack enable
corepack prepare --activate
- name: Get pnpm store directory
id: pnpm-cache
run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Cache pnpm store
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Setup Gradle
uses: gradle/actions/setup-gradle@48b5f213c81028ace310571dc5ec0fbbca0b2947 # v4.4.3
- name: Install project dependencies
run: |
pnpm install --frozen-lockfile
pnpm playwright install --with-deps
run: pnpm install --frozen-lockfile
- name: Restore .NET analyzer projects
run: dotnet restore nx.sln
- name: Nx Report
run:
run:
pnpm nx report
- name: Run Checks/Lint/Test/Build
run: |
pids=()
pnpm nx-cloud record -- nx format:check &
pnpm nx record -- nx format:check &
pids+=($!)
pnpm nx-cloud record -- nx sync:check
pnpm nx record -- nx sync:check
pids+=($!)
pnpm nx-cloud record -- nx-cloud conformance:check
pnpm nx build workspace-plugin && pnpm nx record -- pnpm nx-cloud conformance:check
pids+=($!)
pnpm nx run-many -t check-imports check-lock-files check-codeowners --parallel=1 --no-dte &
pids+=($!)
pnpm nx affected --targets=lint,test,test-kt,build,e2e,e2e-ci,format-native,lint-native &
pnpm nx affected --targets=lint,test,build,e2e,e2e-ci,format-native,lint-native,gradle:build-ci,vale,run &
pids+=($!)
for pid in "${pids[@]}"; do
@@ -101,7 +116,7 @@ jobs:
done
timeout-minutes: 100
- name: Fix CI
run: pnpm nx-cloud fix-ci
run: pnpm nx fix-ci
if: failure()
main-macos:
@@ -131,6 +146,10 @@ jobs:
fetch-depth: 0
filter: tree:0
- name: Set verbose logging from debug mode
if: runner.debug == '1'
run: echo "NX_VERBOSE_LOGGING=true" >> "$GITHUB_ENV"
- name: Fetch Master
run: git fetch origin master:master
if: ${{ github.event_name == 'pull_request' }}
@@ -144,7 +163,7 @@ jobs:
corepack prepare --activate
- name: Set SHAs
uses: nrwl/nx-set-shas@1859e66a83ac9be0dceecbd9a023702e27ac47f4 # v4.3.3
uses: nrwl/nx-set-shas@310288c04d90696f9f1bc27c5e3caea6642b53d4 # v5.0.0
with:
main-branch-name: 'master'
@@ -277,12 +296,30 @@ jobs:
~/Library/Caches/Homebrew
key: nrwl-nx-homebrew-packages
- name: Get pnpm store directory
if: steps.check-changes.outputs.has_changes == 'true'
id: pnpm-cache-macos
run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Cache pnpm store
if: steps.check-changes.outputs.has_changes == 'true'
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ${{ steps.pnpm-cache-macos.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install project dependencies
if: steps.check-changes.outputs.has_changes == 'true'
run: |
pnpm install --frozen-lockfile
pnpm playwright install --with-deps
- name: Restore .NET packages
if: steps.check-changes.outputs.has_changes == 'true'
run: dotnet restore nx.sln
- name: Run E2E Tests for macOS
if: steps.check-changes.outputs.has_changes == 'true'
run: |
+28 -19
View File
@@ -13,6 +13,10 @@ on:
env:
CYPRESS_CACHE_FOLDER: ${{ github.workspace }}/.cypress
# 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), pulling pnpm 11 and breaking install.
COREPACK_DEFAULT_TO_LATEST: '0'
permissions: {}
jobs:
@@ -30,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 }})
@@ -79,10 +78,14 @@ jobs:
id: brew-install-python-setuptools
run: brew install python-setuptools
- name: Install packages
run: |
pnpm install --frozen-lockfile
pnpm playwright install --with-deps
- name: Install pnpm packages
run: pnpm install --frozen-lockfile
- name: Install Playwright
run: pnpm playwright install --with-deps
- name: Restore .NET packages
run: dotnet restore nx.sln
- name: Homebrew cache directory path
if: ${{ matrix.os == 'macos-latest' }}
@@ -164,10 +167,14 @@ jobs:
corepack enable
corepack prepare --activate
- name: Install packages
run: |
pnpm install --frozen-lockfile
pnpm playwright install --with-deps
- name: Install pnpm packages
run: pnpm install --frozen-lockfile
- name: Install Playwright
run: pnpm playwright install --with-deps
- name: Restore .NET packages
run: dotnet restore nx.sln
- name: Cleanup
if: ${{ matrix.os == 'ubuntu-latest' }}
@@ -327,7 +334,7 @@ jobs:
NX_NATIVE_LOGGING: 'false'
NX_E2E_RUN_E2E: 'true'
NX_CLOUD_NO_TIMEOUTS: 'true'
NX_E2E_SKIP_CLEANUP: 'true'
NX_E2E_SKIP_GLOBAL_CLEANUP: 'true'
NODE_OPTIONS: --max_old_space_size=8192
SELECTED_PM: ${{ matrix.package_manager }}
npm_config_registry: http://localhost:4872
@@ -352,7 +359,7 @@ jobs:
NX_E2E_VERBOSE_LOGGING: 'true'
NX_NATIVE_LOGGING: 'false'
NX_E2E_RUN_E2E: 'true'
NX_E2E_SKIP_CLEANUP: 'true'
NX_E2E_SKIP_GLOBAL_CLEANUP: 'true'
NODE_OPTIONS: --max_old_space_size=8192
SELECTED_PM: 'npm'
npm_config_registry: http://localhost:4872
@@ -397,7 +404,7 @@ jobs:
if: ${{ always() && github.repository_owner == 'nrwl' && github.event_name != 'workflow_dispatch' }}
runs-on: ubuntu-latest
needs: e2e
timeout-minutes: 10
timeout-minutes: 15
outputs:
message: ${{ steps.process-json.outputs.slack_message }}
proj_duration: ${{ steps.process-json.outputs.slack_proj_duration }}
@@ -425,8 +432,10 @@ jobs:
combined=$(jq -sc . outputs/*/matrix.json)
echo "combined=$combined" >> $GITHUB_OUTPUT
- name: Process results with TypeScript script
- name: Process results and collect failure details
id: process-json
env:
GH_TOKEN: ${{ github.token }}
run: |
echo '${{ steps.combine-json.outputs.combined }}' | npx tsx .github/workflows/nightly/process-result.ts
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d # v4.1.0
id: pnpm-install
with:
version: 10.11.1
version: 11.2.2
run_install: false
- name: Get pnpm store directory
+3 -4
View File
@@ -20,7 +20,7 @@ jobs:
- uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d # v4.1.0
with:
version: 10.11.1
version: 11.2.2
- name: Use Node.js ${{ matrix.node_version }}
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
@@ -64,7 +64,6 @@ jobs:
id: slack
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1
with:
webhook: ${{ secrets.SLACK_ISSUES_REPORT_URL }}
webhook-type: incoming-webhook
payload: ${{ steps.collect.outputs.SLACK_MESSAGE }}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_ISSUES_REPORT_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
+2 -1
View File
@@ -17,9 +17,10 @@ jobs:
if: ${{ github.repository_owner == 'nrwl' }}
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@7de207be1d3ce97a9abe6ff1306222982d1ca9f9 # v5.0.1
- uses: dessant/lock-threads@6548363a2d763e3a4a3a0dc04ca4a10481d8e536 # v6.0.0
id: lockthreads
with:
process-only: 'issues, prs'
github-token: ${{ github.token }}
issue-inactive-days: "30" # Lock issues after 30 days of being closed
pr-inactive-days: "5" # Lock closed PRs after 5 days. This ensures that issues that stem from a PR are opened as issues, rather than comments on the recently merged PR.
@@ -0,0 +1,415 @@
import { exec } from 'child_process';
import { execSync } from 'child_process';
const MAX_CONCURRENCY = 8;
interface MatrixResult {
project: string;
codeowners: string;
node_version: number | string;
package_manager: string;
os: string;
os_name: string;
os_timeout: number;
is_golden?: boolean;
status: 'success' | 'failure' | 'cancelled';
duration: number;
}
const REPO = process.env.GITHUB_REPOSITORY || 'nrwl/nx';
const RUN_ID = process.env.GITHUB_RUN_ID || '0';
function gh(args: string): string {
try {
return execSync(`gh ${args}`, {
encoding: 'utf-8',
timeout: 60_000,
maxBuffer: 10 * 1024 * 1024,
stdio: ['pipe', 'pipe', 'pipe'],
}).trim();
} catch {
return '';
}
}
function ghAsync(args: string): Promise<string> {
return new Promise((resolve) => {
exec(
`gh ${args}`,
{ encoding: 'utf-8', timeout: 60_000, maxBuffer: 10 * 1024 * 1024 },
(err, stdout) => resolve(err ? '' : (stdout || '').trim())
);
});
}
async function ghParallel<T>(
items: T[],
fn: (item: T) => string,
concurrency = MAX_CONCURRENCY
): Promise<Map<T, string>> {
const results = new Map<T, string>();
const queue = [...items];
async function worker() {
while (queue.length > 0) {
const item = queue.shift()!;
results.set(item, await ghAsync(fn(item)));
}
}
await Promise.all(
Array.from({ length: Math.min(concurrency, items.length) }, () => worker())
);
return results;
}
function extractJestBlocks(raw: string): string {
const lines = raw
.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /gm, '')
.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '')
.split('\n');
const blocks: string[] = [];
let capturing = false;
for (const line of lines) {
if (line.startsWith(' FAIL ')) capturing = true;
if (capturing) blocks.push(line);
if (line.startsWith('Ran all test suites')) capturing = false;
}
return blocks.slice(0, 50).join('\n');
}
function extractTestFiles(block: string): string[] {
const matches = block.match(/FAIL\s+\S+\s+(src\/[^\s]+\.test\.ts)/g) || [];
return [...new Set(matches.map((m) => m.replace(/FAIL\s+\S+\s+/, '')))];
}
// Extract a normalized error signature for a test file from a Jest block.
// Used to distinguish different root causes for the same test file across runs.
function extractErrorSignature(block: string, testFile: string): string {
const lines = block.split('\n');
let afterBullet = false;
let inFile = false;
for (const l of lines) {
if (l.includes('FAIL') && l.includes(testFile)) { inFile = true; continue; }
if (inFile && /●/.test(l)) { afterBullet = true; continue; }
if (!inFile || !afterBullet) continue;
const trimmed = l.trim();
if (!trimmed) continue;
// Skip generic "Command failed" and warnings — find the actual error
if (/^Command failed:|^warning /i.test(trimmed)) continue;
// Normalize dynamic parts
return trimmed
.replace(/\/tmp\/[^\s]+/g, '<tmpdir>')
.replace(/\/Users\/[^\s]+/g, '<path>')
.replace(/\/home\/[^\s]+/g, '<path>')
.replace(/[a-z]+\d{5,}/gi, '<id>')
.replace(/\d{4}-\d{2}-\d{2}T[\d:._Z-]+/g, '<ts>')
.replace(/\d+\.\d+\.\d+/g, '<ver>')
.trim();
}
return '';
}
// Extract signatures for all test files in a block
function extractSignatures(
block: string,
testFiles: string[]
): Map<string, string> {
const sigs = new Map<string, string>();
for (const tf of testFiles) {
sigs.set(tf, extractErrorSignature(block, tf));
}
return sigs;
}
function extractBlockForFile(fullBlock: string, testFile: string): string {
const lines = fullBlock.split('\n');
const result: string[] = [];
let capturing = false;
for (const line of lines) {
if (line.includes('FAIL') && line.includes(testFile)) capturing = true;
else if (capturing && line.match(/^ FAIL /)) capturing = false;
if (capturing) result.push(line);
}
return result.slice(0, 20).join('\n');
}
export interface JobLink {
combo: string;
url: string;
}
export interface FailureDetailsResult {
report: string;
goldenJobLinks: Map<string, JobLink[]>; // project -> [{combo, url}]
}
/**
* Collects detailed failure information for golden projects.
* Called by process-result.ts when golden failures exist.
* Returns Slack mrkdwn report + job links for the summary section.
*/
export async function collectFailureDetails(
combined: MatrixResult[],
failedGoldenProjectNames: string[]
): Promise<FailureDetailsResult> {
const projectNames = failedGoldenProjectNames;
if (projectNames.length === 0) {
return { report: '', goldenJobLinks: new Map() };
}
// Group failures by project for combo info
const failuresByProject = new Map<string, MatrixResult[]>();
for (const r of combined) {
if (r.is_golden && (r.status === 'failure' || r.status === 'cancelled')) {
if (!failuresByProject.has(r.project))
failuresByProject.set(r.project, []);
failuresByProject.get(r.project)!.push(r);
}
}
// 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])}]'`
);
const failedJobs: Array<{
id: number;
name: string;
project: string;
combo: string;
}> = failedJobsRaw ? JSON.parse(failedJobsRaw) : [];
const jobsToFetch: Array<{ id: number; project: string }> = [];
for (const project of projectNames) {
const seen = new Set<string>();
for (const job of failedJobs.filter((j) => j.project === project)) {
const key = job.combo.split('/').slice(0, 2).join('/');
if (!seen.has(key)) {
seen.add(key);
jobsToFetch.push({ id: job.id, project: job.project });
}
}
}
const logResults = await ghParallel(
jobsToFetch,
(job) => `api repos/${REPO}/actions/jobs/${job.id}/logs`
);
// Keep per-combo logs separate AND a merged block per project
interface ComboLog {
combo: string;
block: string;
testFiles: string[];
signatures: Map<string, string>; // testFile -> error signature
}
const projectComboLogs = new Map<string, ComboLog[]>();
const projectLogs = new Map<string, string>(); // merged block for backwards compat
for (const [job, raw] of logResults) {
if (!raw) continue;
const cleaned = raw
.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /gm, '')
.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
const block = extractJestBlocks(raw);
const testFiles = extractTestFiles(cleaned);
const sigs = extractSignatures(cleaned, testFiles);
const combo =
failedJobs.find((j) => j.id === job.id)?.combo || 'unknown';
if (!projectComboLogs.has(job.project))
projectComboLogs.set(job.project, []);
projectComboLogs.get(job.project)!.push({
combo,
block,
testFiles,
signatures: sigs,
});
projectLogs.set(
job.project,
(projectLogs.get(job.project) || '') + '\n' + block
);
}
// Step 2: Build distinct failures per project — each (testFile, signature, combos) is a "failure"
interface DistinctFailure {
testFile: string;
signature: string;
combos: string[];
block: string; // the Jest block from the first combo that has this signature
}
const projectDistinctFailures = new Map<string, DistinctFailure[]>();
for (const project of projectNames) {
const comboLogs = projectComboLogs.get(project) || [];
const seen = new Map<string, DistinctFailure>(); // "testFile|signature" -> failure
for (const cl of comboLogs) {
for (const tf of cl.testFiles) {
const sig = cl.signatures.get(tf) || '';
const key = `${tf}|${sig}`;
if (seen.has(key)) {
seen.get(key)!.combos.push(cl.combo);
} else {
seen.set(key, {
testFile: tf,
signature: sig,
combos: [cl.combo],
block: extractBlockForFile(cl.block, tf),
});
}
}
}
projectDistinctFailures.set(project, [...seen.values()]);
}
// Step 3: Format report
const lines: string[] = ['', '🔍 *Failure Details*', ''];
const sorted = [...projectNames].sort(
(a, b) =>
(failuresByProject.get(b)?.length || 0) -
(failuresByProject.get(a)?.length || 0) || a.localeCompare(b)
);
for (const project of sorted) {
const projResults = failuresByProject.get(project) || [];
const distinctFailures = projectDistinctFailures.get(project) || [];
const block = projectLogs.get(project) || '';
const pms = [...new Set(projResults.map((r) => r.package_manager))];
const pattern =
pms.length === 1
? `${pms[0]}-only`
: pms.length >= 3
? 'all PMs'
: pms.join('+');
const uniqueCombos = [
...new Set(
failedJobs.filter((j) => j.project === project).map((j) => j.combo)
),
];
lines.push('———————————————————————————');
lines.push(`*${project}* — ${projResults.length} combos (${pattern})`);
lines.push('');
if (distinctFailures.length > 0) {
for (const failure of distinctFailures) {
const comboStr = failure.combos.join(', ');
lines.push(`📋 \`${failure.testFile}\` (${comboStr})`);
if (failure.block) {
lines.push('```');
lines.push(failure.block);
lines.push('```');
}
}
const summaryMatch = block.match(/^Test Suites:.*$/m);
if (summaryMatch) lines.push(`_${summaryMatch[0]}_`);
} else {
// No Jest blocks — find which step failed and extract its error output
const firstJob = failedJobs.find((j) => j.project === project);
if (firstJob) {
// Get the failed step name from the jobs API
const stepsRaw = gh(
`run view ${RUN_ID} --repo ${REPO} --json jobs --jq '[.jobs[] | select(.databaseId == ${firstJob.id})][0].steps[] | select(.conclusion == "failure") | .name'`
);
const failedStep = stepsRaw || 'unknown step';
// Get the log and extract error lines
const raw = gh(`api repos/${REPO}/actions/jobs/${firstJob.id}/logs`);
const cleaned = raw
.split('\n')
.map((l) => l.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /, ''))
.map((l) => l.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, ''));
// Extract the failed Nx task output block (❌ > nx run <task> ... until next ##[group] or NX summary)
const failedTaskBlock: string[] = [];
let capturingTask = false;
for (const l of cleaned) {
if (/❌.*> nx run /i.test(l)) {
capturingTask = true;
failedTaskBlock.push(l);
continue;
}
if (capturingTask) {
if (/^##\[group\]|NX.*Running target/i.test(l) || failedTaskBlock.length >= 15) {
capturingTask = false;
} else {
failedTaskBlock.push(l);
}
}
}
// Extract the Nx failure summary block ("Running target...failed" + "Failed tasks:" + task list)
const nxFailureBlock: string[] = [];
let capturingNx = false;
for (const l of cleaned) {
if (/NX.*Running target.*failed/i.test(l)) capturingNx = true;
if (capturingNx) {
nxFailureBlock.push(l);
if (/^Hint:/i.test(l.trim()) || nxFailureBlock.length >= 10) {
capturingNx = false;
}
}
}
// Fallback: if no Nx blocks or task blocks found, extract generic error lines
let fallbackErrors: string[] = [];
if (nxFailureBlock.length === 0 && failedTaskBlock.length === 0) {
fallbackErrors = cleaned.filter((l) => {
const t = l.trim();
if (t.length < 10) return false;
if (/warning|warn\b|deprecated|orphan|Node\.js 20|FORCE_JAVASCRIPT|\* \[new branch\]|\* \[new tag\]/i.test(t)) return false;
return (
/error TS\d+:|^Error:|^\s*error\b[:\s]|ERR!|ERESOLVE|##\[error\]/i.test(t) ||
/Cannot find module|ENOENT|EACCES|permission denied/i.test(t) ||
/Segmentation fault|killed|OOM|out of memory/i.test(t) ||
/command not found|No such file or directory/i.test(t) ||
/Process completed with exit code [^0]/i.test(t)
);
}).slice(0, 5);
}
// Combine: Nx summary first, then task output, then fallback errors
const relevantErrors = [
...nxFailureBlock,
...(failedTaskBlock.length > 0 ? ['', ...failedTaskBlock] : []),
...(fallbackErrors.length > 0 ? ['', ...fallbackErrors] : []),
];
lines.push(`⚠️ Tests did not run — failed at step: *${failedStep}*`);
if (relevantErrors.length > 0) {
lines.push('```');
lines.push(relevantErrors.join('\n'));
lines.push('```');
}
lines.push(`Failing combos: ${uniqueCombos.join(', ')}`);
} else {
lines.push('⏱️ No job data available');
}
}
lines.push('');
}
// Build job links for the summary section
const runUrl = `https://github.com/${REPO}/actions/runs/${RUN_ID}`;
const goldenJobLinks = new Map<string, JobLink[]>();
for (const project of projectNames) {
const projectJobs = failedJobs.filter((j) => j.project === project);
goldenJobLinks.set(
project,
projectJobs.map((j) => ({
combo: j.combo,
url: `${runUrl}/job/${j.id}`,
}))
);
}
return { report: lines.join('\n'), goldenJobLinks };
}
+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.12.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) {
+63 -31
View File
@@ -1,5 +1,6 @@
import * as fs from 'fs';
import { MatrixItem } from './process-matrix';
import { collectFailureDetails, JobLink } from './analyze-failures';
interface MatrixResult extends MatrixItem {
status: 'success' | 'failure' | 'cancelled';
@@ -52,42 +53,31 @@ function processResults(combined: MatrixResult[]): ProcessedResults {
const otherFailingCount = uniqueFailedOtherProjects.size;
result += `\n🌟 *Golden Projects*`;
result += `\n✅ Passing: ${goldenPassingCount}`;
result += `\n❌ Failing: ${goldenFailingCount}`;
result += `\n✅ Passing: ${goldenPassingCount} | ❌ Failing: ${goldenFailingCount}`;
if (failedGoldenProjects.length > 0) {
result += `\n\n🚨 *Failed Golden Projects*\n\`\`\``;
result += `\n| Failed project |`;
result += `\n|--------------------------------|`;
let lastProject: string | undefined;
result += `\n\n🚨 *Failed Golden Projects*`;
// Project names listed here — combo links added later by main() with job data
const seenProjects = new Set<string>();
failedGoldenProjects.forEach(matrix => {
const project = matrix.project !== lastProject ? matrix.project : '';
if (project) {
result += `\n| ${project.padEnd(30)} |`;
lastProject = matrix.project;
if (!seenProjects.has(matrix.project)) {
seenProjects.add(matrix.project);
result += `\n\n*${matrix.project}*`;
// Placeholder — main() will replace with linked combos
result += `\n {{COMBOS:${matrix.project}}}`;
}
});
result += `\n\`\`\``;
}
result += `\n\n🔧 *Other Projects*`;
result += `\n✅ Passing: ${otherPassingCount}`;
result += `\n❌ Failing: ${otherFailingCount}`;
// Failed Other Projects Table (if any)
if (failedRegularProjects.length > 0) {
result += `\n\n⚠️ *Failed Other Projects*\n\`\`\``;
result += `\n| Failed project |`;
result += `\n|--------------------------------|`;
let lastProject: string | undefined;
failedRegularProjects.forEach(matrix => {
const project = matrix.project !== lastProject ? matrix.project : '';
if (project) {
result += `\n| ${project.padEnd(30)} |`;
lastProject = matrix.project;
}
if (otherFailingCount > 0) {
const otherProjectCounts = new Map<string, number>();
failedRegularProjects.forEach(m => {
otherProjectCounts.set(m.project, (otherProjectCounts.get(m.project) || 0) + 1);
});
result += `\n\`\`\``;
const otherSummary = [...otherProjectCounts.entries()]
.map(([p, c]) => `${p} (${c})`)
.join(', ');
result += `\n\n⚠️ *Failed Other Projects:* ${otherSummary}`;
}
if (failedProjects.length === 0) {
@@ -180,7 +170,7 @@ function setOutput(key: string, value: string) {
}
}
try {
async function main() {
const combinedInput = process.argv[2]
? process.argv[2]
: fs.readFileSync(0, 'utf-8').trim();
@@ -188,10 +178,52 @@ try {
const combined: MatrixResult[] = JSON.parse(combinedInput);
const results = processResults(combined);
// Collect detailed failure info if golden failures exist
if (results.has_golden_failures === 'true') {
try {
const failedProjects = [
...new Set(
combined
.filter((c) => c.is_golden && (c.status === 'failure' || c.status === 'cancelled'))
.map((c) => c.project)
),
];
const { report, goldenJobLinks } = await collectFailureDetails(combined, failedProjects);
// Replace combo placeholders in the summary with linked combos
for (const [project, links] of goldenJobLinks) {
const placeholder = `{{COMBOS:${project}}}`;
const linkedCombos =
links.length > 0
? links.map((l) => ` · <${l.url}|${l.combo}>`).join('\n')
: ' (no job data)';
results.slack_message = results.slack_message.replace(
placeholder,
linkedCombos
);
}
// Remove any unreplaced placeholders (if collectFailureDetails didn't have data for a project)
results.slack_message = results.slack_message.replace(
/ \{\{COMBOS:[^}]+\}\}/g,
' (no job data)'
);
if (report) {
results.slack_message += '\n\n' + report;
}
} catch (e) {
console.error('Failed to collect failure details (brief report will still be posted):', e);
results.slack_message += '\n\n⚠️ _Failed to collect detailed failure information_';
}
}
Object.entries(results).forEach(([key, value]) => {
setOutput(key, value);
});
} catch (error) {
}
main().catch((error) => {
console.error('Error processing results:', error);
process.exit(1);
}
});
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
- uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d # v4.1.0
with:
version: 10.11.1 # Aligned with root package.json (pnpm/action-setup will helpfully error if out of sync)
version: 11.2.2 # Aligned with root package.json (pnpm/action-setup will helpfully error if out of sync)
- name: Run a security audit
run: pnpm dlx audit-ci --critical --report-type summary
+151 -77
View File
@@ -21,8 +21,9 @@ env:
DEBUG: napi:*
NX_RUN_GROUP: ${{ github.run_id }}-${{ github.run_attempt }}
CYPRESS_INSTALL_BINARY: 0
NODE_VERSION: 22.16.0
PNPM_VERSION: 10.11.1 # Aligned with root package.json (pnpm/action-setup will helpfully error if out of sync)
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
jobs:
# We first need to determine the version we are releasing, and if we need a custom repo or ref to use for the git checkout in subsequent steps.
@@ -129,6 +130,7 @@ jobs:
- host: windows-latest
setup: |-
choco install openjdk --version=21.0.0 -y
choco install dotnet-9.0-sdk -y
rustup target add aarch64-pc-windows-msvc
build: |
export JAVA_HOME="C:\Program Files\OpenJDK\jdk-21"
@@ -147,22 +149,23 @@ jobs:
build: |
set -e
apt-get update
apt-get install -y curl ca-certificates git xz-utils gpg
# Install Java 21
apt-get install -y openjdk-21-jdk
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH="$JAVA_HOME/bin:$PATH"
java --version
# Install mise from the signed apt repo
install -dm 755 /etc/apt/keyrings
curl -fsSL https://mise.jdx.dev/gpg-key.pub | gpg --dearmor -o /etc/apt/keyrings/mise-archive-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/mise-archive-keyring.gpg arch=$(dpkg --print-architecture)] https://mise.jdx.dev/deb stable main" > /etc/apt/sources.list.d/mise.list
apt-get update
apt-get install -y mise
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt-get install -y nodejs=22.16.0-1nodesource1
# Provision Node, Java, .NET, Maven, corepack from mise.toml
cd /build
mise trust mise.toml
mise install
eval "$(mise env -s bash)"
export PATH="/usr/local/bin:$PATH"
node --version
npm --version
npm i -g pnpm@${PNPM_VERSION} --force
pnpm --version
corepack enable
corepack prepare --activate
pnpm install --frozen-lockfile
rustup target add x86_64-unknown-linux-gnu
@@ -173,28 +176,32 @@ jobs:
build: |
bash -c "
set -e
# mise's core node/java backends don't ship musl binaries and fall back to
# compile-from-source on Alpine, which fails. Install via apk + tarball instead.
echo 'https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories
apk add --no-cache curl xz openjdk21
apk add --no-cache curl xz openjdk21 build-base lld dotnet9-sdk
# Set up Java 21
# Java 21
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk
export PATH=\"\$JAVA_HOME/bin:\$PATH\"
java --version
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
# .NET 9 SDK (needed by @nx/dotnet plugin)
dotnet --version
# Node.js musl build from unofficial-builds.nodejs.org
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
echo Node: \$(node -v)
echo NPM: \$(npm -v)
npm i -g pnpm@\${PNPM_VERSION} --force
# Install PNPM
npm i -g pnpm@${PNPM_VERSION} --force
pnpm --version
# Help clang find GCC runtime (crtbeginS.o, libgcc) and use lld for jemalloc build
GCC_DIR=\$(dirname \$(find /usr/lib/gcc -name crtbeginS.o | head -1))
export CFLAGS=\"\${CFLAGS} -fuse-ld=lld --gcc-install-dir=\${GCC_DIR}\"
# Install deps and run native build
pnpm install --frozen-lockfile
rustup target add x86_64-unknown-linux-musl
pnpm nx run-many --verbose --target=build-native -- --target=x86_64-unknown-linux-musl
@@ -216,22 +223,30 @@ jobs:
build: |
set -e
apt-get update
apt-get install -y curl ca-certificates git xz-utils gpg
# Install Java 21
apt-get install -y openjdk-21-jdk
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH="$JAVA_HOME/bin:$PATH"
java --version
# Install mise from the signed apt repo
install -dm 755 /etc/apt/keyrings
curl -fsSL https://mise.jdx.dev/gpg-key.pub | gpg --dearmor -o /etc/apt/keyrings/mise-archive-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/mise-archive-keyring.gpg arch=$(dpkg --print-architecture)] https://mise.jdx.dev/deb stable main" > /etc/apt/sources.list.d/mise.list
apt-get update
apt-get install -y mise
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt-get install -y nodejs=22.16.0-1nodesource1
# Provision Node, Java, .NET, Maven, corepack from mise.toml
cd /build
mise trust mise.toml
mise install
eval "$(mise env -s bash)"
export PATH="/usr/local/bin:$PATH"
node --version
npm --version
# Help clang find GCC runtime (crtbeginS.o, libgcc) and use lld for jemalloc build
export CFLAGS="${CFLAGS} -fuse-ld=lld --gcc-toolchain=/usr/aarch64-unknown-linux-gnu"
npm i -g pnpm@${PNPM_VERSION} --force
pnpm --version
# Build jemalloc with 64 KiB allocator page so the binary works on aarch64
# Linux kernels with 4K/16K/64K pages (Asahi, Ampere, Graviton, etc.).
export JEMALLOC_SYS_WITH_LG_PAGE=16
corepack enable
corepack prepare --activate
pnpm install --frozen-lockfile
rustup target add aarch64-unknown-linux-gnu
@@ -259,28 +274,38 @@ jobs:
build: |
bash -c "
set -e
# mise's core node/java backends don't ship musl binaries and fall back to
# compile-from-source on Alpine, which fails. Install via apk + tarball instead.
echo 'https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories
apk add --no-cache curl xz openjdk21
apk add --no-cache curl xz openjdk21 build-base lld dotnet9-sdk
# Set up Java 21
# Java 21
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk
export PATH=\"\$JAVA_HOME/bin:\$PATH\"
java --version
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
# .NET 9 SDK (needed by @nx/dotnet plugin)
dotnet --version
# 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/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
echo Node: \$(node -v)
echo NPM: \$(npm -v)
npm i -g pnpm@\${PNPM_VERSION} --force
# Install PNPM
npm i -g pnpm@${PNPM_VERSION} --force
pnpm --version
# Help clang find GCC runtime (crtbeginS.o, libgcc) and use lld for jemalloc build
GCC_DIR=\$(dirname \$(find /aarch64-linux-musl-cross/lib/gcc -name crtbeginS.o | head -1))
export CFLAGS=\"\${CFLAGS} -fuse-ld=lld --gcc-install-dir=\${GCC_DIR}\"
# Build jemalloc with 64 KiB allocator page so the binary works on aarch64
# Linux kernels with 4K/16K/64K pages (Asahi, Ampere, Graviton, etc.).
export JEMALLOC_SYS_WITH_LG_PAGE=16
# Install deps and run native build
pnpm install --frozen-lockfile
rustup target add aarch64-unknown-linux-musl
pnpm nx run-many --verbose --target=build-native -- --target=aarch64-unknown-linux-musl
@@ -289,13 +314,14 @@ jobs:
target: aarch64-pc-windows-msvc
setup: |-
choco install openjdk --version=21.0.0 -y
choco install dotnet-9.0-sdk -y
rustup target add aarch64-pc-windows-msvc
build: |
export JAVA_HOME="C:\Program Files\OpenJDK\jdk-21"
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
@@ -303,6 +329,10 @@ jobs:
repository: ${{ needs.resolve-required-data.outputs.repo || github.repository }}
ref: ${{ needs.resolve-required-data.outputs.ref || github.ref }}
- name: Set verbose logging from debug mode
if: runner.debug == '1'
run: echo "NX_VERBOSE_LOGGING=true" >> "$GITHUB_ENV"
- name: Setup dev tools with mise
uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3
if: ${{ !matrix.settings.docker }}
@@ -324,11 +354,6 @@ jobs:
target/
key: ${{ matrix.settings.target }}-cargo-registry
- uses: goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406 # v2.2.1
if: ${{ matrix.settings.target == 'armv7-unknown-linux-gnueabihf' }}
with:
version: 0.10.0
- name: Setup toolchain
run: ${{ matrix.settings.setup }}
if: ${{ matrix.settings.setup }}
@@ -354,12 +379,27 @@ jobs:
architecture: x86
- name: Build in docker
uses: addnab/docker-run-action@4f65fabd2431ebc8d299f8e5a018d79a769ae185 # v3
if: ${{ matrix.settings.docker }}
with:
image: ${{ matrix.settings.docker }}
options: --user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build
run: ${{ matrix.settings.build }}
shell: bash
env:
BUILD_SCRIPT: ${{ matrix.settings.build }}
run: |
SCRIPT_FILE=$(mktemp)
echo "$BUILD_SCRIPT" > "$SCRIPT_FILE"
docker run --rm \
--user 0:0 \
-e NODE_VERSION \
-e PNPM_VERSION \
-e NX_GRADLE_PROJECT_GRAPH_TIMEOUT \
-e NX_VERBOSE_LOGGING \
-v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db \
-v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache \
-v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index \
-v ${{ github.workspace }}:/build \
-v "$SCRIPT_FILE:/build-script.sh" \
-w /build \
${{ matrix.settings.docker }} \
bash /build-script.sh
- name: Build
run: ${{ matrix.settings.build }}
@@ -393,24 +433,22 @@ 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'
with:
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
environment_variables: DEBUG RUSTUP_IO_THREADS CI PLAYWRIGHT_BROWSERS_PATH NODE_VERSION NX_GRADLE_DISABLE NX_DOTNET_DISABLE NODE_OPTIONS
shell: bash
run: |
env
whoami
sudo pkg install -y -f node libnghttp2 www/npm git openjdk17
sudo npm install --location=global --ignore-scripts pnpm@10.11.1
# Set up Java 17
export JAVA_HOME=/usr/local/openjdk17
export PATH="$JAVA_HOME/bin:$PATH"
java --version
sudo pkg install -y -f node libnghttp2 www/npm git ca_root_nss
sudo npm install --location=global --ignore-scripts pnpm@11.2.2
curl https://sh.rustup.rs -sSf --output rustup.sh
sh rustup.sh -y --profile minimal --default-toolchain stable
source "$HOME/.cargo/env"
@@ -466,17 +504,46 @@ jobs:
pnpm store prune || true
rm -rf ~/.npm || true
rm -rf ~/.pnpm-store || true
# Remove Rust build artifacts if any
rm -rf ~/.cargo/registry || true
rm -rf ~/.cargo/git || true
# Clean Rust extras but keep registry/git (needed for cargo to resolve deps)
rm -rf ~/.rustup/toolchains/*/share || true
# Remove other development tool caches
rm -rf ~/.cache/* || true
# Remove unnecessary workspace directories
rm -rf docs astro-docs nx-dev || true
echo "Checking disk space after cleanup"
df -h
# Disable core dumps - OOM'd Node processes write multi-GB core files that fill the disk
ulimit -c 0
echo "Building FreeBSD bindings"
pnpm nx run-many --verbose --outputStyle stream --target=build-native -- --target=x86_64-unknown-freebsd
BUILD_EXIT=0
pnpm nx run-many --verbose --outputStyle stream --target=build-native -- --target=x86_64-unknown-freebsd || BUILD_EXIT=$?
echo "=== Disk usage after build ==="
df -h
if [ "$BUILD_EXIT" -ne 0 ]; then
echo "Build failed with exit code $BUILD_EXIT"
echo "=== Disk usage by top-level directories ==="
du -sh /* 2>/dev/null | sort -rh | head -20
echo "=== Disk usage in home directory ==="
du -sh ~/* 2>/dev/null | sort -rh | head -20
echo "=== Disk usage in workspace ==="
du -sh /home/runner/work/nx/nx/* 2>/dev/null | sort -rh | head -20
echo "=== Disk usage in .nx ==="
du -sh /home/runner/work/nx/nx/.nx/* 2>/dev/null | sort -rh | head -20
echo "=== Disk usage in cargo/rustup ==="
du -sh ~/.cargo/* ~/.rustup/* 2>/dev/null | sort -rh | head -20
echo "=== Core dumps ==="
find / -name "*.core" -o -name "core.*" -o -name "core" 2>/dev/null | head -10
exit $BUILD_EXIT
fi
echo "Build succeeded"
echo "Cleaning up"
pnpm nx reset
rm -rf node_modules
@@ -514,6 +581,10 @@ jobs:
repository: ${{ needs.resolve-required-data.outputs.repo || github.repository }}
ref: ${{ needs.resolve-required-data.outputs.ref || github.ref }}
- name: Set verbose logging from debug mode
if: runner.debug == '1'
run: echo "NX_VERBOSE_LOGGING=true" >> "$GITHUB_ENV"
- name: Setup dev tools with mise
uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3
@@ -528,6 +599,9 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Restore .NET packages
run: dotnet restore nx.sln
- name: Download all artifacts
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
@@ -545,19 +619,19 @@ 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 }}
NX_VERBOSE_LOGGING: true
run: |
echo ""
# 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
+60 -3
View File
@@ -25,7 +25,14 @@ jest.debug.config.js
/nx-dev/nx-dev/public/documentation
/nx-dev/nx-dev/public/tutorials
/nx-dev/nx-dev/public/images/open-graph
**/tests/temp-db
/nx-dev/nx-dev/public/robots.txt
/nx-dev/nx-dev/public/sitemap-0.xml
/nx-dev/nx-dev/public/sitemap.xml
# Banner JSON files are generated during static builds
/nx-dev/nx-dev/lib/banner.json
/astro-docs/src/content/banner.json
**/tests/temp-db*
# Issues scraper creates these files, stored by github's cache
/scripts/issues-scraper/cached
@@ -67,7 +74,7 @@ dependency-reduced-pom.xml
*.wasm
/wasi-sdk*
vite.config.*.timestamp*
*.config.timestamp*
storybook-static
@@ -76,6 +83,7 @@ storybook-static
.kotlin
.claude/settings.local.json
.claude/scheduled_tasks.lock
CLAUDE.local.md
.cursor/mcp.json
@@ -99,6 +107,7 @@ node_modules/
*.ntvs*
*.njsproj
*.sln
!/nx.sln
*.sw?
.specstory/**
.cursorindexingignore
@@ -110,7 +119,8 @@ node_modules/
# Upstream docs local configuration (machine-specific)
.upstreamdocs.local.json
astro-docs/.netlify
# Netlify build artifacts
.netlify
coverage
@@ -123,6 +133,42 @@ packages/angular-rspack/README.md
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
@@ -133,3 +179,14 @@ test-results
# .NET build output
/packages/dotnet/analyzer/bin
/packages/dotnet/analyzer/obj
/packages/dotnet/analyzer.Tests/bin
/packages/dotnet/analyzer.Tests/obj
/*.deb
.nx/polygraph
.claude/worktrees
.nx/self-healing
e2e/**/*.d.ts
e2e/**/*.d.ts.map
.nx/migrate-runs
-9
View File
@@ -1,9 +0,0 @@
{
"mcpServers": {
"nx-mcp": {
"type": "stdio",
"command": "npx",
"args": ["nx", "mcp"]
}
}
}
+3
View File
@@ -0,0 +1,3 @@
{
"experimentalPolygraph": true
}
+27 -3
View File
@@ -5,16 +5,21 @@ 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'
common-init-steps: &common-init-steps
- name: Checkout
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/checkout/main.yaml'
uses: 'nrwl/nx-cloud-workflows/v6/workflow-steps/checkout/main.yaml'
- name: Cache restore
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/cache/main.yaml'
uses: 'nrwl/nx-cloud-workflows/v6/workflow-steps/cache/main.yaml'
inputs:
key: 'pnpm-lock.yaml'
paths: ~/.local/share/pnpm/store
@@ -22,7 +27,7 @@ common-init-steps: &common-init-steps
# reads mise.toml and installs toolchains needed for repo
- name: Setup toolchains
uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/install-mise/main.yaml'
uses: 'nrwl/nx-cloud-workflows/v6/workflow-steps/install-mise/main.yaml'
- name: Verify toolchain versions
script: |
@@ -36,6 +41,17 @@ common-init-steps: &common-init-steps
- name: Install system deps
script: |
# apt mirror+file failover: tab-separated; printf preserves \t (YAML heredocs don't).
printf 'https://archive.ubuntu.com/ubuntu/\tpriority:1\n' | sudo tee /etc/apt/apt-mirrors.txt > /dev/null
printf 'https://security.ubuntu.com/ubuntu/\tpriority:2\n' | sudo tee -a /etc/apt/apt-mirrors.txt > /dev/null
printf 'http://azure.archive.ubuntu.com/ubuntu/\tpriority:3\n' | sudo tee -a /etc/apt/apt-mirrors.txt > /dev/null
# Retries=0: mirror+file already retries via failover; apt-level retries multiply stall on a dead mirror.
sudo tee /etc/apt/apt.conf.d/80-nx-mirror-failover > /dev/null <<'EOF'
Acquire::http::Timeout "5";
Acquire::https::Timeout "5";
Acquire::Retries "0";
EOF
sudo sed -i 's|http://archive.ubuntu.com/ubuntu|mirror+file:/etc/apt/apt-mirrors.txt|g; s|http://security.ubuntu.com/ubuntu|mirror+file:/etc/apt/apt-mirrors.txt|g' /etc/apt/sources.list
sudo apt-get update
sudo apt-get install -y ca-certificates lsof libvips-dev libglib2.0-dev libgirepository1.0-dev zip unzip
@@ -52,11 +68,19 @@ common-init-steps: &common-init-steps
script: |
cargo fetch
- name: Install hyperfine
script: |
cargo install hyperfine
- name: Setup gradle
script: |
./gradlew wrapper
./gradlew --version
- name: Restore .NET analyzer projects
script: |
dotnet restore nx.sln
- name: Configure git metadata (needed for lerna smoke tests)
script: |
git config --global user.email test@test.com
+19 -31
View File
@@ -1,14 +1,10 @@
distribute-on:
default: auto linux-large, 3 linux-extra-large
extra-small-changeset: 6 linux-large, 3 linux-extra-large
small-changeset: 6 linux-large, 4 linux-extra-large
medium-changeset: 6 linux-large, 5 linux-extra-large
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:
- nx
- workspace
@@ -23,41 +19,33 @@ assignment-rules:
- agent: linux-extra-large
parallelism: 1
- projects:
- e2e-release
- e2e-angular
- e2e-react
- e2e-next
- e2e-web
- e2e-eslint
- e2e-remix
- e2e-cypress
- e2e-docker
- e2e-js
- 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: 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:*
run-on:
- agent: linux-large
parallelism: 1
# These projects should not need to be isolated.
- projects:
- nx-dev
- astro-docs
targets:
- build*
run-on:
+28
View File
@@ -0,0 +1,28 @@
exclude-reads:
- packages/nx/src/native/*.node
- packages/nx/dist/src/native/*.node
- 'dist/target/**'
exclude-writes:
- '**/.swc/**'
- 'dist/target/**'
task-exclusions:
- target: lint
exclude-reads:
- '**/dist/**/*.json'
# TODO: populate-local-registry-storage is doing too much — it reads all build
# outputs and writes version-bumped packages across the entire workspace during
# nx-release. We're reworking this task to have more focused I/O and will fix
# the inputs/outputs properly after that.
- project: '@nx/nx-source'
target: populate-local-registry-storage
exclude-reads:
- '**'
exclude-writes:
- '**'
- project: graph-client
target: build-client
exclude-reads:
- '**/*.stories.{js,jsx,ts,tsx,mdx}'
- '**/*.{spec,test}.{js,jsx,ts,tsx}'
+1
View File
@@ -1,3 +1,4 @@
benchmarks/packages
nx-dev/**/jest.config.js
.next
_files
+479
View File
@@ -0,0 +1,479 @@
---
description: Polls Nx Cloud CI pipeline and self-healing status. Returns structured state when actionable. Spawned by /nx-cloud-ci-monitor command to monitor CI Attempt status.
mode: subagent
---
# CI Watcher Subagent
You are a CI monitoring subagent responsible for polling Nx Cloud CI Attempt status and self-healing state. You report status back to the main agent - you do NOT make apply/reject decisions.
## Your Responsibilities
1. Poll CI status using the `ci_information` MCP tool
2. Implement exponential backoff between polls
3. Return structured state when an actionable condition is reached
4. Track iteration count and elapsed time
5. Output status updates based on verbosity level
## Input Parameters (from Main Agent)
The main agent may provide these optional parameters in the prompt:
| Parameter | Description |
| ------------------- | -------------------------------------------------------- |
| `branch` | Branch to monitor (auto-detected if not provided) |
| `expectedCommitSha` | Commit SHA that should trigger a new CI Attempt |
| `previousCipeUrl` | CI Attempt URL before the action (to detect change) |
| `subagentTimeout` | Polling timeout in minutes (default: 60) |
| `verbosity` | Output level: minimal, medium, verbose (default: medium) |
When `expectedCommitSha` or `previousCipeUrl` is provided, you must detect whether a new CI Attempt has spawned.
## MCP Tool Reference
### `ci_information`
**Input:**
```json
{
"branch": "string (optional, defaults to current git branch)",
"select": "string (optional, comma-separated field names)",
"pageToken": "number (optional, 0-based pagination for long strings)"
}
```
**Output:**
```json
{
"cipeStatus": "NOT_STARTED | IN_PROGRESS | SUCCEEDED | FAILED | CANCELED | TIMED_OUT",
"cipeUrl": "string",
"branch": "string",
"commitSha": "string | null",
"failedTaskIds": "string[]",
"verifiedTaskIds": "string[]",
"selfHealingEnabled": "boolean",
"selfHealingStatus": "NOT_STARTED | IN_PROGRESS | COMPLETED | FAILED | NOT_EXECUTABLE | null",
"verificationStatus": "NOT_STARTED | IN_PROGRESS | COMPLETED | FAILED | NOT_EXECUTABLE | null",
"userAction": "NONE | APPLIED | REJECTED | APPLIED_LOCALLY | APPLIED_AUTOMATICALLY | null",
"failureClassification": "string | null",
"taskOutputSummary": "string | null",
"suggestedFixReasoning": "string | null",
"suggestedFixDescription": "string | null",
"suggestedFix": "string | null",
"shortLink": "string | null",
"couldAutoApplyTasks": "boolean | null",
"confidence": "number | null",
"confidenceReasoning": "string | null"
}
```
**Select Parameter:**
| Usage | Returns |
| --------------- | ----------------------------------------------------------- |
| No `select` | Formatted overview (truncated, not recommended for polling) |
| Single field | Raw value with pagination for long strings |
| Multiple fields | Object with requested field values |
**Field Sets for Efficient Polling:**
```yaml
WAIT_FIELDS:
'cipeUrl,commitSha,cipeStatus'
# Minimal fields for detecting new CI Attempt
LIGHT_FIELDS:
'cipeStatus,cipeUrl,branch,commitSha,selfHealingStatus,verificationStatus,userAction,failedTaskIds,verifiedTaskIds,selfHealingEnabled,failureClassification,couldAutoApplyTasks,shortLink,confidence,confidenceReasoning'
# Status fields for determining actionable state
HEAVY_FIELDS:
'taskOutputSummary,suggestedFix,suggestedFixReasoning,suggestedFixDescription'
# Large content fields - fetch only when returning to main agent
```
## Initial Wait
Before first poll, wait based on context:
- **Fresh start (no expected CIPE):** Wait 60 seconds to allow CI to start
- **Expecting new CIPE:** Wait 30 seconds (action already triggered)
**IMPORTANT:** Always run sleep in foreground, NOT as background command.
```bash
sleep 60 # or 30 if expecting new CIPE (FOREGROUND, not background)
```
## Two-Phase Operation
The subagent operates in one of two modes depending on input:
### Mode 1: Fresh Start (no `expectedCommitSha` or `previousCipeUrl`)
Normal polling - process whatever CIPE is returned by `ci_information`.
### Mode 2: Wait-for-New-CIPE (when `expectedCommitSha` or `previousCipeUrl` provided)
**CRITICAL**: When expecting a new CIPE, the subagent must **completely ignore** the old/stale CIPE. Do NOT process its status, do NOT return actionable states based on it.
#### Phase A: Wait Mode
1. Start a **new-CIPE timeout** timer (default: 30 minutes)
2. On each poll of `ci_information`:
- Check if CIPE is NEW:
- `cipeUrl` differs from `previousCipeUrl`**new CIPE detected**
- `commitSha` matches `expectedCommitSha`**correct CIPE detected**
- If still OLD CIPE: **ignore all status fields**, just wait and poll again
- Do NOT return `fix_available`, `ci_success`, etc. based on old CIPE!
3. Output wait status (see below)
4. If timeout (30 min) reached → return `no_new_cipe`
#### Phase B: Normal Polling (after new CIPE detected)
Once new CIPE is detected:
1. Clear the new-CIPE timeout
2. Switch to normal polling mode
3. Process the NEW CIPE's status normally
4. Return when actionable state reached
### Wait Mode Output
While in wait mode, output clearly that you're waiting (not processing):
```
[CI Monitor] ═══════════════════════════════════════════════════════
[CI Monitor] WAIT MODE - Expecting new CI Attempt
[CI Monitor] Expected SHA: <expectedCommitSha>
[CI Monitor] Previous CI Attempt: <previousCipeUrl>
[CI Monitor] ═══════════════════════════════════════════════════════
[CI Monitor] Polling... (elapsed: 0m 30s)
[CI Monitor] Still seeing previous CI Attempt (ignoring): <oldCipeUrl>
[CI Monitor] Polling... (elapsed: 1m 30s)
[CI Monitor] Still seeing previous CI Attempt (ignoring): <oldCipeUrl>
[CI Monitor] Polling... (elapsed: 2m 30s)
[CI Monitor] ✓ New CI Attempt detected! URL: <newCipeUrl>, SHA: <newCommitSha>
[CI Monitor] Switching to normal polling mode...
```
### Why This Matters (Context Preservation)
**The problem**: Stale CIPE data can be very large:
- `taskOutputSummary`: potentially thousands of characters of build/test output
- `suggestedFix`: entire patch files
- `suggestedFixReasoning`: detailed explanation
If subagent returns stale CIPE data to main agent, it **pollutes main agent's context** with useless information (we already processed that CIPE). This wastes valuable context window.
**Without wait mode:**
1. Poll `ci_information` → get old CIPE with huge data
2. Return to main agent with all that stale data
3. Main agent's context gets polluted with useless info
4. Main agent has to process/ignore it anyway
**With wait mode:**
1. Poll `ci_information` → get old CIPE → **ignore it, don't return**
2. Keep waiting internally (stale data stays in subagent)
3. New CIPE appears → switch to normal mode
4. Return to main agent with only the NEW, relevant CIPE data
## Polling Loop
### Subagent State Management
Maintain internal accumulated state across polls:
```
accumulated_state = {}
```
### Call `ci_information` MCP Tool
**Wait Mode (expecting new CI Attempt):**
```
ci_information({
branch: "<branch_name>",
select: "cipeUrl,commitSha,cipeStatus"
})
```
Only fetch minimal fields needed to detect CI Attempt change. Do NOT fetch heavy fields - stale data wastes context.
**Normal Mode (processing CI Attempt):**
```
ci_information({
branch: "<branch_name>",
select: "cipeStatus,cipeUrl,branch,commitSha,selfHealingStatus,verificationStatus,userAction,failedTaskIds,verifiedTaskIds,selfHealingEnabled,failureClassification,couldAutoApplyTasks,shortLink,confidence,confidenceReasoning"
})
```
Merge response into `accumulated_state` after each poll.
### Analyze Response
**If in Wait Mode** (expecting new CIPE):
1. Check if CIPE is new (see Two-Phase Operation above)
2. If old CIPE → **ignore status**, output wait message, poll again
3. If new CIPE → switch to normal mode, continue below
**If in Normal Mode**:
Based on the response, decide whether to **keep polling** or **return to main agent**.
### Keep Polling When
Continue polling (with backoff) if ANY of these conditions are true:
| Condition | Reason |
| --------------------------------------- | ---------------------------------------- |
| `cipeStatus == 'IN_PROGRESS'` | CI still running |
| `cipeStatus == 'NOT_STARTED'` | CI hasn't started yet |
| `selfHealingStatus == 'IN_PROGRESS'` | Self-healing agent working |
| `selfHealingStatus == 'NOT_STARTED'` | Self-healing not started yet |
| `failureClassification == 'FLAKY_TASK'` | Auto-rerun in progress |
| `userAction == 'APPLIED_AUTOMATICALLY'` | New CI Attempt spawning after auto-apply |
When `couldAutoApplyTasks == true`:
- `verificationStatus` = `NOT_STARTED`, `IN_PROGRESS` → keep polling (verification still in progress)
- `verificationStatus` = `COMPLETED` → return `fix_auto_applying` (auto-apply will happen, main agent spawns wait mode subagent)
- `verificationStatus` = `FAILED`, `NOT_EXECUTABLE` → return `fix_available` (auto-apply won't happen, needs manual action)
### Exponential Backoff
Between polls, wait with exponential backoff:
| Poll Attempt | Wait Time |
| ------------ | ----------------- |
| 1st | 60 seconds |
| 2nd | 90 seconds |
| 3rd+ | 120 seconds (cap) |
Reset to 60 seconds when state changes significantly.
**IMPORTANT:** Run sleep in foreground (NOT as background command). Background sleep causes "What should Claude do?" prompts when completed.
```bash
# Example backoff - run in FOREGROUND
sleep 60 # First wait
sleep 90 # Second wait
sleep 120 # Third and subsequent waits (capped)
```
### Fetch Heavy Fields on Actionable State
Before returning to main agent, fetch heavy fields if the status requires them:
| Status | Heavy Fields Needed |
| ------------------- | ------------------------------------------------------------------------------ |
| `ci_success` | None |
| `fix_auto_applying` | None |
| `fix_available` | `taskOutputSummary,suggestedFix,suggestedFixReasoning,suggestedFixDescription` |
| `fix_failed` | `taskOutputSummary` |
| `no_fix` | `taskOutputSummary` |
| `environment_issue` | None |
| `no_new_cipe` | None |
| `polling_timeout` | None |
| `cipe_canceled` | None |
| `cipe_timed_out` | None |
```
# Example: fetching heavy fields for fix_available
ci_information({
branch: "<branch_name>",
select: "taskOutputSummary,suggestedFix,suggestedFixReasoning,suggestedFixDescription"
})
```
Merge response into `accumulated_state`, then return merged state to main agent.
**Pagination:** Heavy string fields return first page only. If `hasMore` indicated, include in return format so main agent knows more content available.
### Return to Main Agent When
Return immediately with structured state if ANY of these conditions are true:
| Status | Condition |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ci_success` | `cipeStatus == 'SUCCEEDED'` |
| `fix_auto_applying` | `selfHealingStatus == 'COMPLETED'` AND `couldAutoApplyTasks == true` AND `verificationStatus == 'COMPLETED'` |
| `fix_available` | `selfHealingStatus == 'COMPLETED'` AND `suggestedFix != null` AND (`couldAutoApplyTasks != true` OR `verificationStatus` in (`FAILED`, `NOT_EXECUTABLE`)) |
| `fix_failed` | `selfHealingStatus == 'FAILED'` |
| `environment_issue` | `failureClassification == 'ENVIRONMENT_STATE'` |
| `no_fix` | `cipeStatus == 'FAILED'` AND (`selfHealingEnabled == false` OR `selfHealingStatus == 'NOT_EXECUTABLE'`) |
| `no_new_cipe` | `expectedCommitSha` or `previousCipeUrl` provided, but no new CI Attempt detected after 30 min |
| `polling_timeout` | Subagent has been polling for > configured timeout (default 60 min) |
| `cipe_canceled` | `cipeStatus == 'CANCELED'` |
| `cipe_timed_out` | `cipeStatus == 'TIMED_OUT'` |
## Subagent Timeout
Track elapsed time. If you have been polling for more than **60 minutes** (configurable via main agent), return with `status: polling_timeout`.
## Return Format
When returning to the main agent, provide a structured response with accumulated state:
```
## CI Monitor Result
**Status:** <status>
**Iterations:** <count>
**Elapsed:** <minutes>m <seconds>s
### CI Attempt Details
- **Status:** <cipeStatus>
- **URL:** <cipeUrl>
- **Branch:** <branch>
- **Commit:** <commitSha>
- **Failed Tasks:** <failedTaskIds>
- **Verified Tasks:** <verifiedTaskIds>
### Self-Healing Details
- **Enabled:** <selfHealingEnabled>
- **Status:** <selfHealingStatus>
- **Verification:** <verificationStatus>
- **User Action:** <userAction>
- **Classification:** <failureClassification>
- **Confidence:** <confidence>
- **Confidence Reasoning:** <confidenceReasoning>
### Fix Information (if available)
- **Short Link:** <shortLink>
- **Description:** <suggestedFixDescription>
- **Reasoning:** <suggestedFixReasoning>
### Task Output Summary (first page)
<taskOutputSummary>
[MORE_CONTENT_AVAILABLE: taskOutputSummary, pageToken: 1]
### Suggested Fix (first page)
<suggestedFix>
[MORE_CONTENT_AVAILABLE: suggestedFix, pageToken: 1]
```
### Pagination Indicators
When a heavy field has more content available, append indicator:
```
[MORE_CONTENT_AVAILABLE: <fieldName>, pageToken: <nextPage>]
```
Main agent can fetch additional pages if needed using:
```
ci_information({ select: "<fieldName>", pageToken: <nextPage> })
```
Fields that may have pagination:
- `taskOutputSummary` (reverse pagination - page 0 = most recent)
- `suggestedFix` (forward pagination - page 0 = start)
- `suggestedFixReasoning`
### Return Format for `no_new_cipe`
When returning with `status: no_new_cipe`, include additional context:
```
## CI Monitor Result
**Status:** no_new_cipe
**Iterations:** <count>
**Elapsed:** <minutes>m <seconds>s
### Expected CI Attempt Not Found
- **Expected Commit SHA:** <expectedCommitSha>
- **Previous CI Attempt URL:** <previousCipeUrl>
- **Last Seen CI Attempt URL:** <cipeUrl>
- **Last Seen Commit SHA:** <commitSha>
- **New CI Attempt Timeout:** 30 minutes (exceeded)
### Likely Cause
CI workflow failed before Nx tasks could run (e.g., install step, checkout, auth).
Check your CI provider logs for the commit <expectedCommitSha>.
### Last Known CI Attempt State
- **Status:** <cipeStatus>
- **Branch:** <branch>
```
## Status Reporting (Verbosity-Controlled)
Output is controlled by the `verbosity` parameter from the main agent:
| Level | What to Output |
| --------- | ----------------------------------------------------------------- |
| `minimal` | No intermediate output. Only return final result when actionable. |
| `medium` | Output only on significant state changes (not every poll). |
| `verbose` | Output detailed phase information after every poll. |
### Minimal Verbosity
No output during polling. Poll silently and return when done.
### Medium Verbosity (Default)
Output **only when state changes significantly** to save context tokens:
- `cipeStatus` changes (e.g., IN_PROGRESS → FAILED)
- `selfHealingStatus` changes (e.g., IN_PROGRESS → COMPLETED)
- New CI Attempt detected (in wait mode)
Format: single line, no decorators:
```
[CI Monitor] CI: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 4m
```
### Verbose Verbosity
Output detailed phase box after every poll:
```
[CI Monitor] ─────────────────────────────────────────────────────
[CI Monitor] Iteration <N> | Elapsed: <X>m <Y>s
[CI Monitor]
[CI Monitor] CI Status: <cipeStatus>
[CI Monitor] Self-Healing: <selfHealingStatus>
[CI Monitor] Verification: <verificationStatus>
[CI Monitor] Classification: <failureClassification>
[CI Monitor]
[CI Monitor] → <human-readable phase description>
[CI Monitor] ─────────────────────────────────────────────────────
```
### Phase Descriptions (for verbose output)
| Status Combo | Description |
| ----------------------------------------------------------------------------------------- | ------------------------------------------- |
| `cipeStatus: IN_PROGRESS` | "CI running..." |
| `cipeStatus: NOT_STARTED` | "Waiting for CI to start..." |
| `cipeStatus: FAILED` + `selfHealingStatus: NOT_STARTED` | "CI failed. Self-healing starting..." |
| `cipeStatus: FAILED` + `selfHealingStatus: IN_PROGRESS` | "CI failed. Self-healing generating fix..." |
| `cipeStatus: FAILED` + `selfHealingStatus: COMPLETED` + `verificationStatus: IN_PROGRESS` | "Fix generated! Verification running..." |
| `cipeStatus: FAILED` + `selfHealingStatus: COMPLETED` + `verificationStatus: COMPLETED` | "Fix ready! Verified successfully." |
| `cipeStatus: FAILED` + `selfHealingStatus: COMPLETED` + `verificationStatus: FAILED` | "Fix generated but verification failed." |
| `cipeStatus: FAILED` + `selfHealingStatus: FAILED` | "Self-healing could not generate a fix." |
| `cipeStatus: SUCCEEDED` | "CI passed!" |
## Important Notes
- You do NOT make apply/reject decisions - that's the main agent's job
- You do NOT perform git operations
- You only poll and report state
- Respect the `verbosity` parameter for output (default: medium)
- If `ci_information` returns an error, wait and retry (count as failed poll)
- Track consecutive failures - if 5 consecutive failures, return with `status: error`
- When expecting new CI Attempt, track the 30-minute new-CI-Attempt timeout separately from the main polling timeout
+437
View File
@@ -0,0 +1,437 @@
---
description: Monitor Nx Cloud CI pipeline and handle self-healing fixes automatically. Checks for Nx Cloud connection before starting.
argument-hint: '[instructions] [--max-cycles N] [--timeout MINUTES] [--verbosity minimal|medium|verbose] [--branch BRANCH] [--fresh] [--auto-fix-workflow] [--new-cipe-timeout MINUTES]'
---
# CI Monitor Command
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
## Context
- **Current Branch:** !`git branch --show-current`
- **Current Commit:** !`git rev-parse --short HEAD`
- **Remote Status:** !`git status -sb | head -1`
## User Instructions
$ARGUMENTS
**Important:** If user provides specific instructions, respect them over default behaviors described below.
## Configuration Defaults
| Setting | Default | Description |
| ------------------------- | ------------- | ------------------------------------------------------------------- |
| `--max-cycles` | 10 | Maximum CIPE cycles before timeout |
| `--timeout` | 120 | Maximum duration in minutes |
| `--verbosity` | medium | Output level: minimal, medium, verbose |
| `--branch` | (auto-detect) | Branch to monitor |
| `--subagent-timeout` | 60 | Subagent polling timeout in minutes |
| `--fresh` | false | Ignore previous context, start fresh |
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
Parse any overrides from `$ARGUMENTS` and merge with defaults.
## Nx Cloud Connection Check
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
### Step 0: Verify Nx Cloud Connection
1. **Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
2. **If `nx.json` missing OR neither property exists** → exit with:
```
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
```
3. **If connected** → continue to main loop
## Session Context Behavior
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
- **For a completely clean slate:** Exit Claude Code and restart `claude`
## Default Behaviors by Status
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
| Status | Default Behavior |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ci_success` | Exit with success. Log "CI passed successfully!" |
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
| `polling_timeout` | Subagent polling timeout reached. Exit with timeout. |
| `cipe_canceled` | CIPE was canceled. Exit with canceled status. |
| `cipe_timed_out` | CIPE timed out. Exit with timeout status. |
| `error` | Increment `no_progress_count`. If >= 3 → exit with circuit breaker. Otherwise wait 60s and loop. |
### Fix Available Decision Logic
When subagent returns `fix_available`, main agent compares `failedTaskIds` vs `verifiedTaskIds`:
#### Step 1: Categorize Tasks
1. **Verified tasks** = tasks in both `failedTaskIds` AND `verifiedTaskIds`
2. **Unverified tasks** = tasks in `failedTaskIds` but NOT in `verifiedTaskIds`
3. **E2E tasks** = unverified tasks where target contains "e2e" (task format: `<project>:<target>` or `<project>:<target>:<config>`)
4. **Verifiable tasks** = unverified tasks that are NOT e2e
#### Step 2: Determine Path
| Condition | Path |
| --------------------------------------- | ---------------------------------------- |
| No unverified tasks (all verified) | Apply via MCP |
| Unverified tasks exist, but ALL are e2e | Apply via MCP (treat as verified enough) |
| Verifiable tasks exist | Local verification flow |
#### Step 3a: Apply via MCP (fully/e2e-only verified)
- Call `update_self_healing_fix({ shortLink, action: "APPLY" })`
- Record `last_cipe_url`, spawn subagent in wait mode
#### Step 3b: Local Verification Flow
When verifiable (non-e2e) unverified tasks exist:
1. **Detect package manager:**
- `pnpm-lock.yaml` exists → `pnpm nx`
- `yarn.lock` exists → `yarn nx`
- Otherwise → `npx nx`
2. **Run verifiable tasks in parallel:**
- Spawn `general` subagents to run each task concurrently
- Each subagent runs: `<pm> nx run <taskId>`
- Collect pass/fail results from all subagents
3. **Evaluate results:**
| Result | Action |
| ------------------------- | ---------------------------- |
| ALL verifiable tasks pass | Apply via MCP |
| ANY verifiable task fails | Apply-locally + enhance flow |
4. **Apply-locally + enhance flow:**
- Run `nx apply-locally <shortLink>`
- Enhance the code to fix failing tasks
- Run failing tasks again to verify fix
- If still failing → increment `local_verify_count`, loop back to enhance
- If passing → commit and push, record `expected_commit_sha`, spawn subagent in wait mode
5. **Track attempts** (wraps step 4):
- Increment `local_verify_count` after each enhance cycle
- If `local_verify_count >= local_verify_attempts` (default: 3):
- Get code in commit-able state
- Commit and push with message indicating local verification failed
- Report to user:
```
[ci-monitor] Local verification failed after <N> attempts. Pushed to CI for final validation. Failed: <taskIds>
```
- Record `expected_commit_sha`, spawn subagent in wait mode (let CI be final judge)
#### Commit Message Format
```bash
git commit -m "fix(<projects>): <brief description>
Failed tasks: <taskId1>, <taskId2>
Local verification: passed|enhanced|failed-pushing-to-ci"
```
### Unverified Fix Flow (No Verification Attempted)
When `verificationStatus` is `FAILED`, `NOT_EXECUTABLE`, or fix has `couldAutoApplyTasks != true` with no verification:
- Analyze fix content (`suggestedFix`, `suggestedFixReasoning`, `taskOutputSummary`)
- If fix looks correct → apply via MCP
- If fix needs enhancement → use Apply Locally + Enhance Flow above
- If fix is wrong → reject via MCP, fix from scratch, commit, push
### Auto-Apply Eligibility
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
### Apply vs Reject vs Apply Locally
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
### Apply Locally + Enhance Flow
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
2. Make additional changes as needed
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Reject + Fix From Scratch Flow
When the fix is completely wrong:
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
2. Fix the issue from scratch locally
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Environment Issue Handling
When `failureClassification == 'ENVIRONMENT_STATE'`:
1. Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`
2. New CIPE spawns automatically (no local git operations needed)
3. Loop to poll for new CIPE with `previousCipeUrl` set
### No-New-CIPE Handling
When `status == 'no_new_cipe'`:
This means the expected CIPE was never created - CI likely failed before Nx tasks could run.
1. **Report to user:**
```
[ci-monitor] No CI attempt for <sha> after 10 min. Check CI provider for pre-Nx failures (install, checkout, auth). Last CI attempt: <previousCipeUrl>
```
2. **If user configured auto-fix attempts** (e.g., `--auto-fix-workflow`):
- Detect package manager: check for `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`
- Run install to update lockfile:
```bash
pnpm install # or npm install / yarn install
```
- If lockfile changed:
```bash
git add pnpm-lock.yaml # or appropriate lockfile
git commit -m "chore: update lockfile"
git push origin $(git branch --show-current)
```
- Record new commit SHA, loop to poll with `expectedCommitSha`
3. **Otherwise:** Exit with `no_new_cipe` status, providing guidance for user to investigate
## Exit Conditions
Exit the monitoring loop when ANY of these conditions are met:
| Condition | Exit Type |
| ------------------------------------------- | ---------------- |
| CI passes (`cipeStatus == 'SUCCEEDED'`) | Success |
| Max CIPE cycles reached | Timeout |
| Max duration reached | Timeout |
| 3 consecutive no-progress iterations | Circuit breaker |
| No fix available and local fix not possible | Failure |
| No new CIPE and auto-fix not configured | Pre-CIPE failure |
| User cancels | Cancelled |
## Main Loop
### Step 1: Initialize Tracking
```
cycle_count = 0
start_time = now()
no_progress_count = 0
local_verify_count = 0
last_state = null
last_cipe_url = null
expected_commit_sha = null
```
### Step 2: Spawn Subagent
Spawn the `ci-watcher` subagent to poll CI status:
**Fresh start (first spawn, no expected CIPE):**
```
Task(
agent: "ci-watcher",
prompt: "Monitor CI for branch '<branch>'.
Subagent timeout: <subagent-timeout> minutes.
New-CIPE timeout: <new-cipe-timeout> minutes.
Verbosity: <verbosity>."
)
```
**After action that triggers new CIPE (wait mode):**
```
Task(
agent: "ci-watcher",
prompt: "Monitor CI for branch '<branch>'.
Subagent timeout: <subagent-timeout> minutes.
New-CIPE timeout: <new-cipe-timeout> minutes.
Verbosity: <verbosity>.
WAIT MODE: A new CIPE should spawn. Ignore old CIPE until new one appears.
Expected commit SHA: <expected_commit_sha>
Previous CIPE URL: <last_cipe_url>"
)
```
### Step 3: Handle Subagent Response
When subagent returns:
1. Check the returned status
2. Look up default behavior in the table above
3. Check if user instructions override the default
4. Execute the appropriate action
5. **If action expects new CIPE**, update tracking (see Step 3a)
6. If action results in looping, go to Step 2
### Step 3a: Track State for New-CIPE Detection
After actions that should trigger a new CIPE, record state before looping:
| Action | What to Track | Subagent Mode |
| ----------------------------- | --------------------------------------------- | ------------- |
| Fix auto-applying | `last_cipe_url = current cipeUrl` | Wait mode |
| Apply via MCP | `last_cipe_url = current cipeUrl` | Wait mode |
| Apply locally + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Reject + fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Fix failed + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| No fix + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Environment rerun | `last_cipe_url = current cipeUrl` | Wait mode |
| No-new-CIPE + auto-fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
- Subagent will **completely ignore** the old/stale CIPE
- Subagent will only wait for new CIPE to appear
- Subagent will NOT return to main agent with stale CIPE data
- Once new CIPE detected, subagent switches to normal polling
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
### Step 4: Progress Tracking
After each action:
- If state changed significantly → reset `no_progress_count = 0`
- If state unchanged → `no_progress_count++`
- On new CI attempt detected → reset `local_verify_count = 0`
## Status Reporting
Based on verbosity level:
| Level | What to Report |
| --------- | -------------------------------------------------------------------------- |
| `minimal` | Only final result (success/failure/timeout) |
| `medium` | State changes + periodic updates ("Cycle N \| Elapsed: Xm \| Status: ...") |
| `verbose` | All of medium + full subagent responses, git outputs, MCP responses |
## User Instruction Examples
Users can override default behaviors:
| Instruction | Effect |
| ------------------------------------------------ | --------------------------------------------- |
| "never auto-apply" | Always prompt before applying any fix |
| "always ask before git push" | Prompt before each push |
| "reject any fix for e2e tasks" | Auto-reject if `failedTaskIds` contains e2e |
| "apply all fixes regardless of verification" | Skip verification check, apply everything |
| "if confidence < 70, reject" | Check confidence field before applying |
| "run 'nx affected -t typecheck' before applying" | Add local verification step |
| "auto-fix workflow failures" | Attempt lockfile updates on pre-CIPE failures |
| "wait 45 min for new CIPE" | Override new-CIPE timeout (default: 10 min) |
## Error Handling
| Error | Action |
| ------------------------ | ------------------------------------------------------------------------------------- |
| Git rebase conflict | Report to user, exit |
| `nx apply-locally` fails | Report to user, attempt manual patch or exit |
| MCP tool error | Retry once, if fails report to user |
| Subagent spawn failure | Retry once, if fails exit with error |
| No new CIPE detected | If `--auto-fix-workflow`, try lockfile update; otherwise report to user with guidance |
| Lockfile auto-fix fails | Report to user, exit with guidance to check CI logs |
## Example Session
### Example 1: Normal Flow with Self-Healing (medium verbosity)
```
[ci-monitor] Starting CI monitor for branch 'feature/add-auth'
[ci-monitor] Config: max-cycles=5, timeout=120m, verbosity=medium
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] CI attempt: IN_PROGRESS | Self-Healing: NOT_STARTED | Elapsed: 1m
[CI Monitor] CI attempt: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 3m
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 5m
[ci-monitor] Fix available! Verification: COMPLETED
[ci-monitor] Applying fix via MCP...
[ci-monitor] Fix applied in CI. Waiting for new CI attempt...
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] New CI attempt detected!
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 8m
[ci-monitor] CI passed successfully!
[ci-monitor] Summary:
- Total cycles: 2
- Total time: 12m 34s
- Fixes applied: 1
- Result: SUCCESS
```
### Example 2: Pre-CI Failure (medium verbosity)
```
[ci-monitor] Starting CI monitor for branch 'feature/add-products'
[ci-monitor] Config: max-cycles=5, timeout=120m, auto-fix-workflow=true
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 2m
[ci-monitor] Applying fix locally, enhancing, and pushing...
[ci-monitor] Committed: abc1234
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] Waiting for new CI attempt... (expected SHA: abc1234)
[CI Monitor] ⚠️ CI attempt timeout (10 min). Returning no_new_cipe.
[ci-monitor] Status: no_new_cipe
[ci-monitor] --auto-fix-workflow enabled. Attempting lockfile update...
[ci-monitor] Lockfile updated. Committed: def5678
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] New CI attempt detected!
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 18m
[ci-monitor] CI passed successfully!
[ci-monitor] Summary:
- Total cycles: 3
- Total time: 22m 15s
- Fixes applied: 1 (self-healing) + 1 (lockfile)
- Result: SUCCESS
```
+437
View File
@@ -0,0 +1,437 @@
---
name: ci-monitor
description: Monitor Nx Cloud CI pipeline and handle self-healing fixes automatically. Checks for Nx Cloud connection before starting.
---
# CI Monitor Command
You are the orchestrator for monitoring Nx Cloud CI pipeline executions and handling self-healing fixes. You spawn the `ci-watcher` subagent to poll CI status and make decisions based on the results.
## Context
- **Current Branch:** !`git branch --show-current`
- **Current Commit:** !`git rev-parse --short HEAD`
- **Remote Status:** !`git status -sb | head -1`
## User Instructions
$ARGUMENTS
**Important:** If user provides specific instructions, respect them over default behaviors described below.
## Configuration Defaults
| Setting | Default | Description |
| ------------------------- | ------------- | ------------------------------------------------------------------- |
| `--max-cycles` | 10 | Maximum CIPE cycles before timeout |
| `--timeout` | 120 | Maximum duration in minutes |
| `--verbosity` | medium | Output level: minimal, medium, verbose |
| `--branch` | (auto-detect) | Branch to monitor |
| `--subagent-timeout` | 60 | Subagent polling timeout in minutes |
| `--fresh` | false | Ignore previous context, start fresh |
| `--auto-fix-workflow` | false | Attempt common fixes for pre-CIPE failures (e.g., lockfile updates) |
| `--new-cipe-timeout` | 10 | Minutes to wait for new CIPE after action |
| `--local-verify-attempts` | 3 | Max local verification + enhance cycles before pushing to CI |
Parse any overrides from `$ARGUMENTS` and merge with defaults.
## Nx Cloud Connection Check
**CRITICAL**: Before starting the monitoring loop, verify the workspace is connected to Nx Cloud.
### Step 0: Verify Nx Cloud Connection
1. **Check `nx.json`** at workspace root for `nxCloudId` or `nxCloudAccessToken`
2. **If `nx.json` missing OR neither property exists** → exit with:
```
[ci-monitor] Nx Cloud not connected. Unlock 70% faster CI and auto-fix broken PRs with https://nx.dev/nx-cloud
```
3. **If connected** → continue to main loop
## Session Context Behavior
**Important:** Within a Claude Code session, conversation context persists. If you Ctrl+C to interrupt the monitor and re-run `/ci-monitor`, Claude remembers the previous state and may continue from where it left off.
- **To continue monitoring:** Just re-run `/ci-monitor` (context is preserved)
- **To start fresh:** Use `/ci-monitor --fresh` to ignore previous context
- **For a completely clean slate:** Exit Claude Code and restart `claude`
## Default Behaviors by Status
The subagent returns with one of the following statuses. This table defines the **default behavior** for each status. User instructions can override any of these.
| Status | Default Behavior |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ci_success` | Exit with success. Log "CI passed successfully!" |
| `fix_auto_applying` | Fix will be auto-applied by self-healing. Do NOT call MCP. Record `last_cipe_url`, spawn new subagent in wait mode to poll for new CIPE. |
| `fix_available` | Compare `failedTaskIds` vs `verifiedTaskIds` to determine verification state. See **Fix Available Decision Logic** section below. |
| `fix_failed` | Self-healing failed to generate fix. Attempt local fix based on `taskOutputSummary`. If successful → commit, push, loop. If not → exit with failure. |
| `environment_issue` | Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`. New CIPE spawns automatically. Loop to poll for new CIPE. |
| `no_fix` | CI failed, no fix available (self-healing disabled or not executable). Attempt local fix if possible. Otherwise exit with failure. |
| `no_new_cipe` | Expected CIPE never spawned (CI workflow likely failed before Nx tasks). Report to user, attempt common fixes if configured, or exit with guidance. |
| `polling_timeout` | Subagent polling timeout reached. Exit with timeout. |
| `cipe_canceled` | CIPE was canceled. Exit with canceled status. |
| `cipe_timed_out` | CIPE timed out. Exit with timeout status. |
| `error` | Increment `no_progress_count`. If >= 3 → exit with circuit breaker. Otherwise wait 60s and loop. |
### Fix Available Decision Logic
When subagent returns `fix_available`, main agent compares `failedTaskIds` vs `verifiedTaskIds`:
#### Step 1: Categorize Tasks
1. **Verified tasks** = tasks in both `failedTaskIds` AND `verifiedTaskIds`
2. **Unverified tasks** = tasks in `failedTaskIds` but NOT in `verifiedTaskIds`
3. **E2E tasks** = unverified tasks where target contains "e2e" (task format: `<project>:<target>` or `<project>:<target>:<config>`)
4. **Verifiable tasks** = unverified tasks that are NOT e2e
#### Step 2: Determine Path
| Condition | Path |
| --------------------------------------- | ---------------------------------------- |
| No unverified tasks (all verified) | Apply via MCP |
| Unverified tasks exist, but ALL are e2e | Apply via MCP (treat as verified enough) |
| Verifiable tasks exist | Local verification flow |
#### Step 3a: Apply via MCP (fully/e2e-only verified)
- Call `update_self_healing_fix({ shortLink, action: "APPLY" })`
- Record `last_cipe_url`, spawn subagent in wait mode
#### Step 3b: Local Verification Flow
When verifiable (non-e2e) unverified tasks exist:
1. **Detect package manager:**
- `pnpm-lock.yaml` exists → `pnpm nx`
- `yarn.lock` exists → `yarn nx`
- Otherwise → `npx nx`
2. **Run verifiable tasks in parallel:**
- Spawn `general` subagents to run each task concurrently
- Each subagent runs: `<pm> nx run <taskId>`
- Collect pass/fail results from all subagents
3. **Evaluate results:**
| Result | Action |
| ------------------------- | ---------------------------- |
| ALL verifiable tasks pass | Apply via MCP |
| ANY verifiable task fails | Apply-locally + enhance flow |
4. **Apply-locally + enhance flow:**
- Run `nx apply-locally <shortLink>`
- Enhance the code to fix failing tasks
- Run failing tasks again to verify fix
- If still failing → increment `local_verify_count`, loop back to enhance
- If passing → commit and push, record `expected_commit_sha`, spawn subagent in wait mode
5. **Track attempts** (wraps step 4):
- Increment `local_verify_count` after each enhance cycle
- If `local_verify_count >= local_verify_attempts` (default: 3):
- Get code in commit-able state
- Commit and push with message indicating local verification failed
- Report to user:
```
[ci-monitor] Local verification failed after <N> attempts. Pushed to CI for final validation. Failed: <taskIds>
```
- Record `expected_commit_sha`, spawn subagent in wait mode (let CI be final judge)
#### Commit Message Format
```bash
git commit -m "fix(<projects>): <brief description>
Failed tasks: <taskId1>, <taskId2>
Local verification: passed|enhanced|failed-pushing-to-ci"
```
### Unverified Fix Flow (No Verification Attempted)
When `verificationStatus` is `FAILED`, `NOT_EXECUTABLE`, or fix has `couldAutoApplyTasks != true` with no verification:
- Analyze fix content (`suggestedFix`, `suggestedFixReasoning`, `taskOutputSummary`)
- If fix looks correct → apply via MCP
- If fix needs enhancement → use Apply Locally + Enhance Flow above
- If fix is wrong → reject via MCP, fix from scratch, commit, push
### Auto-Apply Eligibility
The `couldAutoApplyTasks` field indicates whether the fix is eligible for automatic application:
- **`true`**: Fix is eligible for auto-apply. Subagent keeps polling while verification is in progress. Returns `fix_auto_applying` when verified, or `fix_available` if verification fails.
- **`false`** or **`null`**: Fix requires manual action (apply via MCP, apply locally, or reject)
**Key point**: When subagent returns `fix_auto_applying`, do NOT call MCP to apply - self-healing handles it. Just spawn a new subagent in wait mode.
### Apply vs Reject vs Apply Locally
- **Apply via MCP**: Calls `update_self_healing_fix({ shortLink, action: "APPLY" })`. Self-healing agent applies the fix in CI and a new CIPE spawns automatically. No local git operations needed.
- **Apply Locally**: Runs `nx apply-locally <shortLink>`. Applies the patch to your local working directory and sets state to `APPLIED_LOCALLY`. Use this when you want to enhance the fix before pushing.
- **Reject via MCP**: Calls `update_self_healing_fix({ shortLink, action: "REJECT" })`. Marks fix as rejected. Use only when the fix is completely wrong and you'll fix from scratch.
### Apply Locally + Enhance Flow
When the fix needs enhancement (use `nx apply-locally`, NOT reject):
1. Apply the patch locally: `nx apply-locally <shortLink>` (this also updates state to `APPLIED_LOCALLY`)
2. Make additional changes as needed
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Reject + Fix From Scratch Flow
When the fix is completely wrong:
1. Call MCP to reject: `update_self_healing_fix({ shortLink, action: "REJECT" })`
2. Fix the issue from scratch locally
3. Commit and push:
```bash
git add -A
git commit -m "fix: resolve <failedTaskIds>"
git push origin $(git branch --show-current)
```
4. Loop to poll for new CIPE
### Environment Issue Handling
When `failureClassification == 'ENVIRONMENT_STATE'`:
1. Call MCP to request rerun: `update_self_healing_fix({ shortLink, action: "RERUN_ENVIRONMENT_STATE" })`
2. New CIPE spawns automatically (no local git operations needed)
3. Loop to poll for new CIPE with `previousCipeUrl` set
### No-New-CIPE Handling
When `status == 'no_new_cipe'`:
This means the expected CIPE was never created - CI likely failed before Nx tasks could run.
1. **Report to user:**
```
[ci-monitor] No CI attempt for <sha> after 10 min. Check CI provider for pre-Nx failures (install, checkout, auth). Last CI attempt: <previousCipeUrl>
```
2. **If user configured auto-fix attempts** (e.g., `--auto-fix-workflow`):
- Detect package manager: check for `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`
- Run install to update lockfile:
```bash
pnpm install # or npm install / yarn install
```
- If lockfile changed:
```bash
git add pnpm-lock.yaml # or appropriate lockfile
git commit -m "chore: update lockfile"
git push origin $(git branch --show-current)
```
- Record new commit SHA, loop to poll with `expectedCommitSha`
3. **Otherwise:** Exit with `no_new_cipe` status, providing guidance for user to investigate
## Exit Conditions
Exit the monitoring loop when ANY of these conditions are met:
| Condition | Exit Type |
| ------------------------------------------- | ---------------- |
| CI passes (`cipeStatus == 'SUCCEEDED'`) | Success |
| Max CIPE cycles reached | Timeout |
| Max duration reached | Timeout |
| 3 consecutive no-progress iterations | Circuit breaker |
| No fix available and local fix not possible | Failure |
| No new CIPE and auto-fix not configured | Pre-CIPE failure |
| User cancels | Cancelled |
## Main Loop
### Step 1: Initialize Tracking
```
cycle_count = 0
start_time = now()
no_progress_count = 0
local_verify_count = 0
last_state = null
last_cipe_url = null
expected_commit_sha = null
```
### Step 2: Spawn Subagent
Spawn the `ci-watcher` subagent to poll CI status:
**Fresh start (first spawn, no expected CIPE):**
```
Task(
agent: "ci-watcher",
prompt: "Monitor CI for branch '<branch>'.
Subagent timeout: <subagent-timeout> minutes.
New-CIPE timeout: <new-cipe-timeout> minutes.
Verbosity: <verbosity>."
)
```
**After action that triggers new CIPE (wait mode):**
```
Task(
agent: "ci-watcher",
prompt: "Monitor CI for branch '<branch>'.
Subagent timeout: <subagent-timeout> minutes.
New-CIPE timeout: <new-cipe-timeout> minutes.
Verbosity: <verbosity>.
WAIT MODE: A new CIPE should spawn. Ignore old CIPE until new one appears.
Expected commit SHA: <expected_commit_sha>
Previous CIPE URL: <last_cipe_url>"
)
```
### Step 3: Handle Subagent Response
When subagent returns:
1. Check the returned status
2. Look up default behavior in the table above
3. Check if user instructions override the default
4. Execute the appropriate action
5. **If action expects new CIPE**, update tracking (see Step 3a)
6. If action results in looping, go to Step 2
### Step 3a: Track State for New-CIPE Detection
After actions that should trigger a new CIPE, record state before looping:
| Action | What to Track | Subagent Mode |
| ----------------------------- | --------------------------------------------- | ------------- |
| Fix auto-applying | `last_cipe_url = current cipeUrl` | Wait mode |
| Apply via MCP | `last_cipe_url = current cipeUrl` | Wait mode |
| Apply locally + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Reject + fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Fix failed + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| No fix + local fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
| Environment rerun | `last_cipe_url = current cipeUrl` | Wait mode |
| No-new-CIPE + auto-fix + push | `expected_commit_sha = $(git rev-parse HEAD)` | Wait mode |
**CRITICAL**: When passing `expectedCommitSha` or `last_cipe_url` to the subagent, it enters **wait mode**:
- Subagent will **completely ignore** the old/stale CIPE
- Subagent will only wait for new CIPE to appear
- Subagent will NOT return to main agent with stale CIPE data
- Once new CIPE detected, subagent switches to normal polling
**Why wait mode matters for context preservation**: Stale CIPE data can be very large (task output summaries, suggested fix patches, reasoning). If subagent returns this to main agent, it pollutes main agent's context with useless data since we already processed that CIPE. Wait mode keeps stale data in the subagent, never sending it to main agent.
### Step 4: Progress Tracking
After each action:
- If state changed significantly → reset `no_progress_count = 0`
- If state unchanged → `no_progress_count++`
- On new CI attempt detected → reset `local_verify_count = 0`
## Status Reporting
Based on verbosity level:
| Level | What to Report |
| --------- | -------------------------------------------------------------------------- |
| `minimal` | Only final result (success/failure/timeout) |
| `medium` | State changes + periodic updates ("Cycle N \| Elapsed: Xm \| Status: ...") |
| `verbose` | All of medium + full subagent responses, git outputs, MCP responses |
## User Instruction Examples
Users can override default behaviors:
| Instruction | Effect |
| ------------------------------------------------ | --------------------------------------------- |
| "never auto-apply" | Always prompt before applying any fix |
| "always ask before git push" | Prompt before each push |
| "reject any fix for e2e tasks" | Auto-reject if `failedTaskIds` contains e2e |
| "apply all fixes regardless of verification" | Skip verification check, apply everything |
| "if confidence < 70, reject" | Check confidence field before applying |
| "run 'nx affected -t typecheck' before applying" | Add local verification step |
| "auto-fix workflow failures" | Attempt lockfile updates on pre-CIPE failures |
| "wait 45 min for new CIPE" | Override new-CIPE timeout (default: 10 min) |
## Error Handling
| Error | Action |
| ------------------------ | ------------------------------------------------------------------------------------- |
| Git rebase conflict | Report to user, exit |
| `nx apply-locally` fails | Report to user, attempt manual patch or exit |
| MCP tool error | Retry once, if fails report to user |
| Subagent spawn failure | Retry once, if fails exit with error |
| No new CIPE detected | If `--auto-fix-workflow`, try lockfile update; otherwise report to user with guidance |
| Lockfile auto-fix fails | Report to user, exit with guidance to check CI logs |
## Example Session
### Example 1: Normal Flow with Self-Healing (medium verbosity)
```
[ci-monitor] Starting CI monitor for branch 'feature/add-auth'
[ci-monitor] Config: max-cycles=5, timeout=120m, verbosity=medium
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] CI attempt: IN_PROGRESS | Self-Healing: NOT_STARTED | Elapsed: 1m
[CI Monitor] CI attempt: FAILED | Self-Healing: IN_PROGRESS | Elapsed: 3m
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 5m
[ci-monitor] Fix available! Verification: COMPLETED
[ci-monitor] Applying fix via MCP...
[ci-monitor] Fix applied in CI. Waiting for new CI attempt...
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] New CI attempt detected!
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 8m
[ci-monitor] CI passed successfully!
[ci-monitor] Summary:
- Total cycles: 2
- Total time: 12m 34s
- Fixes applied: 1
- Result: SUCCESS
```
### Example 2: Pre-CI Failure (medium verbosity)
```
[ci-monitor] Starting CI monitor for branch 'feature/add-products'
[ci-monitor] Config: max-cycles=5, timeout=120m, auto-fix-workflow=true
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] CI attempt: FAILED | Self-Healing: COMPLETED | Elapsed: 2m
[ci-monitor] Applying fix locally, enhancing, and pushing...
[ci-monitor] Committed: abc1234
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] Waiting for new CI attempt... (expected SHA: abc1234)
[CI Monitor] ⚠️ CI attempt timeout (10 min). Returning no_new_cipe.
[ci-monitor] Status: no_new_cipe
[ci-monitor] --auto-fix-workflow enabled. Attempting lockfile update...
[ci-monitor] Lockfile updated. Committed: def5678
[ci-monitor] Spawning subagent to poll CI status...
[CI Monitor] New CI attempt detected!
[CI Monitor] CI attempt: SUCCEEDED | Elapsed: 18m
[ci-monitor] CI passed successfully!
[ci-monitor] Summary:
- Total cycles: 3
- Total time: 22m 15s
- Fixes applied: 1 (self-healing) + 1 (lockfile)
- Result: SUCCESS
```
+228
View File
@@ -0,0 +1,228 @@
---
name: nx-generate
description: Generate code using nx generators. USE WHEN scaffolding code or transforming existing code - for example creating libraries or applications, or anything else that is boilerplate code or automates repetitive tasks. ALWAYS use this first when generating code with Nx instead of calling MCP tools or running nx generate immediately.
---
# Run Nx Generator
Nx generators are powerful tools that scaffold projects, make automated code migrations or automate repetitive tasks in a monorepo. They ensure consistency across the codebase and reduce boilerplate work.
This skill applies when the user wants to:
- Create new projects like libraries or applications
- Scaffold features or boilerplate code
- Run workspace-specific or custom generators
- Do anything else that an nx generator exists for
## Generator Discovery Flow
### Step 1: List Available Generators
Use the Nx CLI to discover available generators:
- List all generators for a plugin: `npx nx list @nx/react`
- View available plugins: `npx nx list`
This includes:
- Plugin generators (e.g., `@nx/react:library`, `@nx/js:library`)
- Local workspace generators (defined in the repo's own plugins)
### Step 2: Match Generator to User Request
Based on the user's request, identify which generator(s) could fulfill their needs. Consider:
- What artifact type they want to create (library, application, etc.)
- Which framework or technology stack is relevant
- Whether they mentioned specific generator names
**IMPORTANT**: When both a local workspace generator and an external plugin generator could satisfy the request, **always prefer the local workspace generator**. Local generators are customized for the specific repo's patterns and conventions.
It's possible that the user request is something that no Nx generator exists for whatsoever. In this case, you can stop using this skill and try to help the user another way. HOWEVER, the burden of proof for this is high. Before aborting, carefully consider each and every generator that's available. Look into details for any that could be related in any way before making this decision.
## Pre-Execution Checklist
Before running any generator, complete these steps:
### 1. Fetch Generator Schema
Use the `--help` flag to understand all available options:
```bash
npx nx g @nx/react:library --help
```
Pay attention to:
- Required options that must be provided
- Optional options that may be relevant to the user's request
- Default values that might need to be overridden
### 2. Read Generator Source Code
Understanding what the generator actually does helps you:
- Know what files will be created/modified
- Understand any side effects (updating configs, installing deps, etc.)
- Identify options that might not be obvious from the schema
To find generator source code:
- For plugin generators: Use `node -e "console.log(require.resolve('@nx/<plugin>/generators.json'));"` to find the generators.json, then locate the source from there
- If that fails, read directly from `node_modules/<plugin>/generators.json`
- For local generators: They are typically in `tools/generators/` or a local plugin directory. You can search the repo for the generator name to find it.
### 2.5 Reevaluate if the generator is right
Once you have built up an understanding of what the selected generator does, reconsider: Is this the right generator to service the user request?
If not, it's okay to go back to the Generator Discovery Flow and select a different generator before proceeding. If you do, make sure to go through the entire pre-execution checklist once more.
### 3. Understand Repo Context
Before generating, examine the target area of the codebase:
- Look at similar existing artifacts (other libraries, applications, etc.)
- Identify patterns and conventions used in the repo
- Note naming conventions, file structures, and configuration patterns
- Try to match these patterns when configuring the generator
For example, if similar libraries are using a specific test runner, build tool or linter, try to match that if possible.
If projects or other artifacts are organized with a specific naming convention, try to match it.
### 4. Validate Required Options
Ensure all required options have values:
- Map the user's request to generator options
- Infer values from context where possible
- Ask the user for any critical missing information
## Execution
Keep in mind that you might have to prefix things with npx/pnpx/yarn if the user doesn't have nx installed globally.
Many generators will behave differently based on where they are executed. For example, first-party nx library generators use the cwd to determine the directory that the library should be placed in. This is highly important.
### Consider Dry-Run (Optional)
Running with `--dry-run` first is strongly encouraged but not mandatory. Use your judgment:
- For complex generators or unfamiliar territory: do a dry-run first
- For simple, well-understood generators: may proceed directly
- Dry-run shows file names and created/deleted/modified markers, but not content
- There are cases where a generator does not support dry-run (for example if it had to install an npm package) - in that case --dry-run might fail. Don't be discouraged but simply move on to running the generator for real and iterating from there.
### Running the Generator
Execute the generator with:
```bash
nx generate <generator-name> <options> --no-interactive
```
**CRITICAL**: Always include `--no-interactive` to prevent prompts that would hang the execution.
Example:
```bash
nx generate @nx/react:library --name=my-utils --no-interactive
```
### Handling Generator Failures
If the generator fails:
1. **Diagnose the error** - Read the error message carefully
2. **Identify the cause** - Missing options, invalid values, conflicts, etc.
3. **Attempt automatic fix** - Adjust options or resolve conflicts
4. **Retry** - Run the generator again with corrected options
Common failure reasons:
- Missing required options
- Invalid option values
- Conflicting with existing files
- Missing dependencies
- Generator doesn't support certain flag combinations
## Post-Generation
### 1. Modify Generated Code (If Needed)
Generators provide a starting point, but the output may need adjustment to match the user's specific requirements:
- Add or modify functionality as requested
- Adjust imports, exports, or configurations
- Integrate with existing code patterns in the repo
### 2. Format Code
Run formatting on all generated/modified files:
```bash
nx format --fix
```
Languages other than javascript/typescript might need other formatting invocations too.
### 3. Run Verification
Verify that the generated code works correctly. What this looks like will vary depending on the type of generator and the targets available.
If the generator created a new project, run its targets directly
Use your best judgement to determine what needs to be verified.
Example:
```bash
nx lint <new-project>
nx test <new-project>
nx build <new-project>
```
### 4. Handle Verification Failures
When verification fails:
**If scope is manageable** (a few lint errors, minor type issues):
- Fix the issues
- Re-run verification to confirm
**If issues are extensive** (many errors, complex problems):
- Attempt simple, obvious fixes first
- If still failing, escalate to the user with:
- Description of what was generated
- What verification is failing
- What you've attempted to fix
- Remaining issues that need user input
## Error Handling
### Generator Failures
- Check the error message for specific causes
- Verify all required options are provided
- Check for conflicts with existing files
- Ensure the generator name and options are correct
### Missing Options
- Consult the generator schema for required fields
- Infer values from context when reasonable
- Ask the user for values that cannot be inferred
## Key Principles
1. **Local generators first** - Always prefer workspace/local generators over external plugin generators when both could work
2. **Understand before running** - Read both the schema AND the source code to fully understand what will happen
3. **No prompts** - Always use `--no-interactive` to prevent hanging
4. **Generators are starting points** - Modify the output as needed to fully satisfy the user's requirements
5. **Verify changes work** - Don't just generate; ensure the code builds, lints, and tests pass
6. **Be proactive about fixes** - Don't just report errors; attempt to resolve them automatically when possible
7. **Match repo patterns** - Study existing similar code in the repo and match its conventions
+9
View File
@@ -0,0 +1,9 @@
---
name: nx-plugins
description: Find and add Nx plugins. USE WHEN user wants to discover available plugins, install a new plugin, or add support for a specific framework or technology to the workspace.
---
## Finding and Installing new plugins
- List plugins: `pnpm nx list`
- Install plugins `pnpm nx add <plugin>`. Example: `pnpm nx add @nx/react`.
+58
View File
@@ -0,0 +1,58 @@
---
name: nx-run-tasks
description: Helps with running tasks in an Nx workspace. USE WHEN the user wants to execute build, test, lint, serve, or run any other tasks defined in the workspace.
---
You can run tasks with Nx in the following way.
Keep in mind that you might have to prefix things with npx/pnpx/yarn if the user doesn't have nx installed globally. Look at the package.json or lockfile to determine which package manager is in use.
For more details on any command, run it with `--help` (e.g. `nx run-many --help`, `nx affected --help`).
## Understand which tasks can be run
You can check those via `nx show project <projectname> --json`, for example `nx show project myapp --json`. It contains a `targets` section which has information about targets that can be run. You can also just look at the `package.json` scripts or `project.json` targets, but you might miss out on inferred tasks by Nx plugins.
## Run a single task
```
nx run <project>:<task>
```
where `project` is the project name defined in `package.json` or `project.json` (if present).
## Run multiple tasks
```
nx run-many -t build test lint typecheck
```
You can pass a `-p` flag to filter to specific projects, otherwise it runs on all projects. You can also use `--exclude` to exclude projects, and `--parallel` to control the number of parallel processes (default is 3).
Examples:
- `nx run-many -t test -p proj1 proj2` — test specific projects
- `nx run-many -t test --projects=*-app --exclude=excluded-app` — test projects matching a pattern
- `nx run-many -t test --projects=tag:api-*` — test projects by tag
## Run tasks for affected projects
Use `nx affected` to only run tasks on projects that have been changed and projects that depend on changed projects. This is especially useful in CI and for large workspaces.
```
nx affected -t build test lint
```
By default it compares against the base branch. You can customize this:
- `nx affected -t test --base=main --head=HEAD` — compare against a specific base and head
- `nx affected -t test --files=libs/mylib/src/index.ts` — specify changed files directly
## Useful flags
These flags work with `run`, `run-many`, and `affected`:
- `--skipNxCache` — rerun tasks even when results are cached
- `--verbose` — print additional information such as stack traces
- `--nxBail` — stop execution after the first failed task
- `--configuration=<name>` — use a specific configuration (e.g. `production`)
+186
View File
@@ -0,0 +1,186 @@
---
name: nx-workspace
description: "Explore and understand Nx workspaces. USE WHEN answering any questions about the nx workspace, the projects in it or tasks to run. EXAMPLES: 'What projects are in this workspace?', 'How is project X configured?', 'What targets can I run?', 'What's affected by my changes?', 'Which projects depend on library Y?', or any questions about Nx workspace structure, project configuration, or available tasks."
---
# Nx Workspace Exploration
This skill provides read-only exploration of Nx workspaces. Use it to understand workspace structure, project configuration, available targets, and dependencies.
Keep in mind that you might have to prefix commands with `npx`/`pnpx`/`yarn` if nx isn't installed globally. Check the lockfile to determine the package manager in use.
## Listing Projects
Use `nx show projects` to list projects in the workspace.
```bash
# List all projects
nx show projects
# Filter by pattern (glob)
nx show projects --projects "apps/*"
nx show projects --projects "shared-*"
# Filter by project type
nx show projects --type app
nx show projects --type lib
nx show projects --type e2e
# Filter by target (projects that have a specific target)
nx show projects --withTarget build
nx show projects --withTarget e2e
# Find affected projects (changed since base branch)
nx show projects --affected
nx show projects --affected --base=main
nx show projects --affected --type app
# Combine filters
nx show projects --type lib --withTarget test
nx show projects --affected --exclude="*-e2e"
# Output as JSON
nx show projects --json
```
## Project Configuration
Use `nx show project <name> --json` to get the full resolved configuration for a project.
**Important**: Do NOT read `project.json` directly - it only contains partial configuration. The `nx show project` command returns the full resolved config including inferred targets from plugins.
You can read the full project schema at `node_modules/nx/schemas/project-schema.json` to understand nx project configuration options.
```bash
# Get full project configuration
nx show project my-app --json
# Extract specific parts from the JSON
nx show project my-app --json | jq '.targets'
nx show project my-app --json | jq '.targets.build'
nx show project my-app --json | jq '.targets | keys'
# Check project metadata
nx show project my-app --json | jq '{name, root, sourceRoot, projectType, tags}'
```
## Target Information
Targets define what tasks can be run on a project.
```bash
# List all targets for a project
nx show project my-app --json | jq '.targets | keys'
# Get full target configuration
nx show project my-app --json | jq '.targets.build'
# Check target executor/command
nx show project my-app --json | jq '.targets.build.executor'
nx show project my-app --json | jq '.targets.build.command'
# View target options
nx show project my-app --json | jq '.targets.build.options'
# Check target inputs/outputs (for caching)
nx show project my-app --json | jq '.targets.build.inputs'
nx show project my-app --json | jq '.targets.build.outputs'
# Find projects with a specific target
nx show projects --withTarget serve
nx show projects --withTarget e2e
```
## Workspace Configuration
Read `nx.json` directly for workspace-level configuration.
You can read the full project schema at `node_modules/nx/schemas/nx-schema.json` to understand nx project configuration options.
```bash
# Read the full nx.json
cat nx.json
# Or use jq for specific sections
cat nx.json | jq '.targetDefaults'
cat nx.json | jq '.namedInputs'
cat nx.json | jq '.plugins'
cat nx.json | jq '.generators'
```
Key nx.json sections:
- `targetDefaults` - Default configuration applied to all targets of a given name
- `namedInputs` - Reusable input definitions for caching
- `plugins` - Nx plugins and their configuration
- ...and much more, read the schema or nx.json for details
## Affected Projects
Find projects affected by changes in the current branch.
```bash
# Affected since base branch (auto-detected)
nx show projects --affected
# Affected with explicit base
nx show projects --affected --base=main
nx show projects --affected --base=origin/main
# Affected between two commits
nx show projects --affected --base=abc123 --head=def456
# Affected apps only
nx show projects --affected --type app
# Affected excluding e2e projects
nx show projects --affected --exclude="*-e2e"
# Affected by uncommitted changes
nx show projects --affected --uncommitted
# Affected by untracked files
nx show projects --affected --untracked
```
## Common Exploration Patterns
### "What's in this workspace?"
```bash
nx show projects
nx show projects --type app
nx show projects --type lib
```
### "How do I build/test/lint project X?"
```bash
nx show project X --json | jq '.targets | keys'
nx show project X --json | jq '.targets.build'
```
### "What depends on library Y?"
```bash
# Find projects that may depend on Y by searching for imports
# (Nx doesn't have a direct "dependents" command via CLI)
grep -r "from '@myorg/Y'" --include="*.ts" --include="*.tsx" apps/ libs/
```
### "What configuration options are available?"
```bash
cat node_modules/nx/schemas/nx-schema.json | jq '.properties | keys'
cat node_modules/nx/schemas/project-schema.json | jq '.properties | keys'
```
### "Why is project X affected?"
```bash
# Check what files changed
git diff --name-only main
# See which project owns those files
nx show project X --json | jq '.root'
```
+5
View File
@@ -54,3 +54,8 @@ _solution
# this file uses TS import attributes which the current prettier version does not support
tools/documentation/create-embeddings/src/main.mts
.nx/self-healing
# Inlined from `@yarnpkg/parser` keep as is
packages/nx/src/utils/yarn-syml/syml-grammar.js
+22 -11
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:
@@ -202,22 +212,23 @@ Fixes #ISSUE_NUMBER
<!-- nx configuration start-->
<!-- Leave the start & end comments to automatically receive updates. -->
# General Guidelines for working with Nx
## General Guidelines for working with Nx
- For navigating/exploring the workspace, invoke the `nx-workspace` skill first - it has patterns for querying projects, targets, and dependencies
- When running tasks (for example build, lint, test, e2e, etc.), always prefer running the task through `nx` (i.e. `nx run`, `nx run-many`, `nx affected`) instead of using the underlying tooling directly
- Prefix nx commands with the workspace's package manager (e.g., `pnpm nx build`, `npm exec nx test`) - avoids using globally installed CLI
- You have access to the Nx MCP server and its tools, use them to help the user
- When answering questions about the repository, use the `nx_workspace` tool first to gain an understanding of the workspace architecture where applicable.
- When working in individual projects, use the `nx_project_details` mcp tool to analyze and understand the specific project structure and dependencies
- For questions around nx configuration, best practices or if you're unsure, use the `nx_docs` tool to get relevant, up-to-date docs. Always use this instead of assuming things about nx configuration
- If the user needs help with an Nx configuration or project graph error, use the `nx_workspace` tool to get any errors
- For Nx plugin best practices, check `node_modules/@nx/<plugin>/PLUGIN.md`. Not all plugins have this file - proceed without it if unavailable.
- NEVER guess CLI flags - always check nx_docs or `--help` first when unsure
# CI Error Guidelines
## Scaffolding & Generators
If the user wants help with fixing an error in their CI pipeline, use the following flow:
- For scaffolding tasks (creating apps, libs, project structure, setup), ALWAYS invoke the `nx-generate` skill FIRST before exploring or calling MCP tools
- Retrieve the list of current CI Pipeline Executions (CIPEs) using the `nx_cloud_cipe_details` tool
- If there are any errors, use the `nx_cloud_fix_cipe_failure` tool to retrieve the logs for a specific task
- Use the task logs to see what's wrong and help the user fix their problem. Use the appropriate tools if necessary
- Make sure that the problem is fixed by running the task that you passed into the `nx_cloud_fix_cipe_failure` tool
## When to use nx_docs
- USE for: advanced config options, unfamiliar flags, migration guides, plugin configuration, edge cases
- DON'T USE for: basic generator syntax (`nx g @nx/react:app`), standard commands, things you already know
- The `nx-generate` skill handles generator discovery internally - don't call nx_docs just to look up generator syntax
<!-- nx configuration end-->
+14 -11
View File
@@ -21,6 +21,8 @@ When working on Nx documentation, all documentation content lives in the `astro-
- Development workflow and commands
- Sidebar management
**MANDATORY**: After editing any file in `astro-docs/src/content/`, run the `nx-docs-style-check` skill. No exceptions.
### Quick Reference
- Documentation content: `astro-docs/src/content/docs/`
@@ -202,22 +204,23 @@ Fixes #ISSUE_NUMBER
<!-- nx configuration start-->
<!-- Leave the start & end comments to automatically receive updates. -->
# General Guidelines for working with Nx
## General Guidelines for working with Nx
- For navigating/exploring the workspace, invoke the `nx-workspace` skill first - it has patterns for querying projects, targets, and dependencies
- When running tasks (for example build, lint, test, e2e, etc.), always prefer running the task through `nx` (i.e. `nx run`, `nx run-many`, `nx affected`) instead of using the underlying tooling directly
- Prefix nx commands with the workspace's package manager (e.g., `pnpm nx build`, `npm exec nx test`) - avoids using globally installed CLI
- You have access to the Nx MCP server and its tools, use them to help the user
- When answering questions about the repository, use the `nx_workspace` tool first to gain an understanding of the workspace architecture where applicable.
- When working in individual projects, use the `nx_project_details` mcp tool to analyze and understand the specific project structure and dependencies
- For questions around nx configuration, best practices or if you're unsure, use the `nx_docs` tool to get relevant, up-to-date docs. Always use this instead of assuming things about nx configuration
- If the user needs help with an Nx configuration or project graph error, use the `nx_workspace` tool to get any errors
- For Nx plugin best practices, check `node_modules/@nx/<plugin>/PLUGIN.md`. Not all plugins have this file - proceed without it if unavailable.
- NEVER guess CLI flags - always check nx_docs or `--help` first when unsure
# CI Error Guidelines
## Scaffolding & Generators
If the user wants help with fixing an error in their CI pipeline, use the following flow:
- For scaffolding tasks (creating apps, libs, project structure, setup), ALWAYS invoke the `nx-generate` skill FIRST before exploring or calling MCP tools
- Retrieve the list of current CI Pipeline Executions (CIPEs) using the `nx_cloud_cipe_details` tool
- If there are any errors, use the `nx_cloud_fix_cipe_failure` tool to retrieve the logs for a specific task
- Use the task logs to see what's wrong and help the user fix their problem. Use the appropriate tools if necessary
- Make sure that the problem is fixed by running the task that you passed into the `nx_cloud_fix_cipe_failure` tool
## When to use nx_docs
- USE for: advanced config options, unfamiliar flags, migration guides, plugin configuration, edge cases
- DON'T USE for: basic generator syntax (`nx g @nx/react:app`), standard commands, things you already know
- The `nx-generate` skill handles generator discovery internally - don't call nx_docs just to look up generator syntax
<!-- nx configuration end-->
+1 -177
View File
@@ -1,178 +1,2 @@
# Any file not covered by a rule below, will default to Jason + Victor and a few select others.
* @FrozenPandaz @vsavkin
/packages/**/* @FrozenPandaz @vsavkin @AgentEnder @jaysoo @JamesHenry
/e2e/**/* @FrozenPandaz @vsavkin @AgentEnder @jaysoo @JamesHenry
/scripts/**/* @FrozenPandaz @vsavkin @AgentEnder @jaysoo @JamesHenry
/tools/**/* @FrozenPandaz @vsavkin @AgentEnder @jaysoo @JamesHenry
package.json @nrwl/nx-core-reviewers
pnpm-lock.yaml @nrwl/nx-core-reviewers
rust-toolchain.toml @nrwl/nx-native-reviewers
# Docs Site + Graph
/astro-docs @nrwl/nx-docs-reviewers
/docs @nrwl/nx-docs-reviewers
/graph/** @philipjfulcher @FrozenPandaz @bcabanes @MaxKless @Coly010 @jaysoo @nartc
/images @nrwl/nx-docs-reviewers
/nx-dev/** @nrwl/nx-docs-reviewers
# Plugin Verticals
## Angular
/packages/angular/** @nrwl/nx-angular-reviewers
/packages/angular-rspack/** @nrwl/nx-angular-reviewers
/packages/angular-rspack-compiler/** @nrwl/nx-angular-reviewers
/examples/angular-rspack/** @nrwl/nx-angular-reviewers
/e2e/angular/** @nrwl/nx-angular-reviewers
/packages/angular/plugins/component-testing.ts @nrwl/nx-angular-reviewers @nrwl/nx-testing-tools-reviewers
/packages/angular/src/generators/cypress-component-configuration/** @nrwl/nx-angular-reviewers @nrwl/nx-testing-tools-reviewers
/packages/angular/src/generators/component-test/** @nrwl/nx-angular-reviewers @nrwl/nx-testing-tools-reviewers
## React
/packages/react/** @nrwl/nx-react-reviewers
/e2e/react/** @nrwl/nx-react-reviewers
/packages/next/** @nrwl/nx-react-reviewers
/e2e/next/** @nrwl/nx-react-reviewers
/packages/react/plugins/component-testing/** @nrwl/nx-react-reviewers @nrwl/nx-testing-tools-reviewers
/packages/react/src/generators/cypress-component-configuration/** @nrwl/nx-react-reviewers @nrwl/nx-testing-tools-reviewers
/packages/react/src/generators/component-test/** @nrwl/nx-react-reviewers @nrwl/nx-testing-tools-reviewers
# React Native
/packages/detox/** @nrwl/nx-react-reviewers
/e2e/detox/** @nrwl/nx-react-reviewers
/packages/expo/** @nrwl/nx-react-reviewers
/e2e/expo/** @nrwl/nx-react-reviewers
/packages/react-native/** @nrwl/nx-react-reviewers
/e2e/react-native/** @nrwl/nx-react-reviewers
## remix
/packages/remix/** @nrwl/nx-react-reviewers @Coly010
/e2e/remix/** @nrwl/nx-react-reviewers @Coly010
# Vue
/packages/vue/** @nrwl/nx-vue-reviewers
/e2e/vue/** @nrwl/nx-vue-reviewers
/packages/nuxt/** @nrwl/nx-vue-reviewers
/e2e/nuxt/** @nrwl/nx-vue-reviewers
## Node
/packages/node/** @nrwl/nx-node-reviewers
/packages/express/** @nrwl/nx-node-reviewers
/packages/nest/** @nrwl/nx-node-reviewers
/e2e/node/** @nrwl/nx-node-reviewers
## JS
/packages/js/** @nrwl/nx-js-reviewers
/e2e/js/** @nrwl/nx-js-reviewers
/packages/web/** @nrwl/nx-js-reviewers
/e2e/web/** @nrwl/nx-js-reviewers
/packages/webpack/** @nrwl/nx-js-reviewers
/e2e/webpack/** @nrwl/nx-js-reviewers
/packages/rspack/** @nrwl/nx-js-reviewers
/e2e/rspack/** @nrwl/nx-js-reviewers
/packages/rsbuild/** @nrwl/nx-js-reviewers
/packages/esbuild/** @nrwl/nx-js-reviewers
/e2e/esbuild/** @nrwl/nx-js-reviewers
/packages/rollup/** @nrwl/nx-js-reviewers
/e2e/rollup/** @nrwl/nx-js-reviewers
/packages/vite/** @nrwl/nx-js-reviewers
/e2e/vite/** @nrwl/nx-js-reviewers
/packages/vitest/** @nrwl/nx-js-reviewers
## Module Federation
/packages/module-federation/** @nrwl/nx-js-reviewers
## Tools
/packages/cypress/** @nrwl/nx-testing-tools-reviewers
/e2e/cypress/** @nrwl/nx-testing-tools-reviewers
/packages/jest/** @nrwl/nx-testing-tools-reviewers
/e2e/jest/** @nrwl/nx-testing-tools-reviewers
/packages/playwright/** @nrwl/nx-testing-tools-reviewers
/e2e/playwright/** @nrwl/nx-testing-tools-reviewers
# Linter
/packages/eslint-plugin/** @nrwl/nx-linter-reviewers
/packages/eslint/** @nrwl/nx-linter-reviewers
/e2e/eslint/** @nrwl/nx-linter-reviewers
.eslint* @nrwl/nx-linter-reviewers
# Storybook
/packages/storybook/** @nrwl/nx-storybook-reviewers
/e2e/storybook/** @nrwl/nx-storybook-reviewers
# Docker
/packages/docker/** @nrwl/nx-core-reviewers @Coly010 @jaysoo
## Devkit
/packages/devkit/** @nrwl/nx-devkit-reviewers
/packages/devkit/index.ts @FrozenPandaz @vsavkin
/packages/devkit/public-api.ts @FrozenPandaz @vsavkin
# Gradle
/packages/gradle/** @FrozenPandaz @MaxKless @lourw
/e2e/gradle/** @FrozenPandaz @MaxKless @lourw
/build.gradle.kts @FrozenPandaz @MaxKless @lourw
/settings.gradle.kts @FrozenPandaz @MaxKless @lourw
# Maven
/packages/maven/** @FrozenPandaz @MaxKless @lourw
/e2e/maven/** @FrozenPandaz @MaxKless @lourw
/pom.xml @FrozenPandaz @MaxKless @lourw
# Nx-Plugin
/packages/plugin/** @nrwl/nx-devkit-reviewers
/e2e/plugin/** @nrwl/nx-devkit-reviewers
/packages/create-nx-plugin/** @nrwl/nx-devkit-reviewers
## Core
/packages/nx/** @nrwl/nx-core-reviewers
/packages/nx/src/adapter @nrwl/nx-core-reviewers @leosvelperez
/packages/nx/src/native @nrwl/nx-core-reviewers @nrwl/nx-native-reviewers
/packages/nx/src/plugins/js/lock-file @nrwl/nx-core-reviewers @meeroslav
/packages/nx/src/command-line/init/implementation/angular/** @nrwl/nx-angular-reviewers @nrwl/nx-core-reviewers
/e2e/nx-init/src/nx-init-angular.test.ts @nrwl/nx-angular-reviewers
/packages/nx/src/command-line/init/implementation/react/** @nrwl/nx-react-reviewers
/e2e/nx-init/src/nx-init-react.test.ts @nrwl/nx-react-reviewers
/e2e/nx-init/src/files/cra/** @nrwl/nx-react-reviewers
/e2e/nx*/** @nrwl/nx-core-reviewers
/packages/workspace/** @nrwl/nx-core-reviewers
/e2e/workspace-create/** @nrwl/nx-core-reviewers
/packages/create-nx-workspace/** @nrwl/nx-core-reviewers
/packages/nx/src/command-line/release/** @nrwl/nx-core-reviewers @Coly010
/packages/nx/src/plugins/js/** @nrwl/nx-core-reviewers @nrwl/nx-js-reviewers
/e2e/release/** @nrwl/nx-core-reviewers @Coly010
# .NET
/packages/dotnet/** @FrozenPandaz @AgentEnder
/e2e/dotnet/** @FrozenPandaz @AgentEnder
# Misc
/e2e/lerna-smoke-tests/** @vsavkin @JamesHenry
/e2e/utils/** @meeroslav @nrwl/nx-testing-tools-reviewers @vsavkin
/CONTRIBUTING.md @FrozenPandaz
/CODE_OF_CONDUCT.md @FrozenPandaz
/CODEOWNERS @FrozenPandaz @AgentEnder
/packages/nx/src/nx-cloud/utilities/url-shorten.ts @MaxKless
# Scripts
/scripts/documentation @nrwl/nx-docs-reviewers
/scripts/angular-support-upgrades @nrwl/nx-angular-reviewers
# CI
/.nx/workflows/** @nrwl/nx-pipelines-reviewers
mise.toml @nrwl/nx-pipelines-reviewers @FrozenPandaz
/.github/** @nrwl/nx-pipelines-reviewers
/.husky/** @nrwl/nx-pipelines-reviewers
/packages/workspace/src/generators/ci-workflow/** @nrwl/nx-pipelines-reviewers
# AI Agent Integration
CLAUDE.md @FrozenPandaz @Coly010
.claude/** @FrozenPandaz @Coly010
.mcp.json @FrozenPandaz @Coly010
AGENTS.md @FrozenPandaz @Coly010
.gemini @FrozenPandaz @Coly010
# Global Files
project.json @FrozenPandaz @vsavkin
jest.config.ts @nrwl/nx-testing-tools-reviewers @FrozenPandaz
jest.preset.js @nrwl/nx-testing-tools-reviewers @FrozenPandaz
* @nrwl/nx-cli-reviewers
+2 -11
View File
@@ -2,19 +2,10 @@
We would love for you to contribute to Nx! Read this document to see how to do it.
## How to Get Started Video
Watch this 5-minute video:
<a href="https://www.youtube.com/watch?v=8LCA_4qxc08" target="_blank" rel="noreferrer">
<p style="text-align: center;"><img src="./images/how-to-contribute.png" width="600" alt="Nx - How to contribute"></p>
</a>
## Got a Question?
We are trying to keep GitHub issues for bug reports and feature requests. Using the `nrwl` tag
on [Stack Overflow](https://stackoverflow.com/questions/tagged/nrwl) is a much better place to ask general questions
about how to use Nx.
We are trying to keep GitHub issues for bug reports and feature requests.
You can join our [Discord](https://go.nx.dev/community) for general questions and seeking help from others.
## Found an Issue?
Generated
+2013 -1537
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<UseSharedCompilation>false</UseSharedCompilation>
</PropertyGroup>
</Project>
+1 -1
View File
@@ -1,6 +1,6 @@
(The MIT License)
Copyright (c) 2017-2025 Narwhal Technologies Inc.
Copyright (c) 2017-2026 Narwhal Technologies Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
+47 -76
View File
@@ -1,76 +1,52 @@
<p style="text-align: center;">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./images/nx-dark.svg">
<img alt="Nx - Smart Repos · Fast Builds" src="./images/nx-light.svg" width="100%">
</picture>
</p>
<div align="center">
<div style="text-align: center;">
<p>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="images/nx-logo-light.svg">
<img src="images/nx-logo.svg" alt="Nx Logo" width="140">
</picture>
</p>
[![CircleCI](https://circleci.com/gh/nrwl/nx.svg?style=svg)](https://circleci.com/gh/nrwl/nx)
[![License](https://img.shields.io/npm/l/nx.svg?style=flat-square)]()
[![NPM Version](https://badge.fury.io/js/nx.svg)](https://www.npmjs.com/package/nx)
[![Semantic Release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic-release-e10079.svg?style=flat-square)]()
[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
[![Join the chat at https://gitter.im/nrwl-nx/community](https://badges.gitter.im/nrwl-nx/community.svg)](https://gitter.im/nrwl-nx/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Join the Official Nx Discord Server](https://img.shields.io/discord/1143497901675401286?label=discord)](https://go.nx.dev/community)
<h1 align="center">Smart Monorepos · Fast Builds</h1>
<p>
<a href="https://www.npmjs.com/package/nx"><img src="https://img.shields.io/npm/v/nx.svg?style=for-the-badge" alt="NPM Version"></a>
<a href="https://github.com/nrwl/nx"><img src="https://img.shields.io/github/stars/nrwl/nx?style=for-the-badge&logo=github" alt="GitHub Stars"></a>
<a href=""><img src="https://img.shields.io/npm/l/nx.svg?style=for-the-badge" alt="License"></a>
<a href="https://go.nx.dev/community"><img src="https://img.shields.io/discord/1143497901675401286?label=discord&style=for-the-badge" alt="Discord"></a>
<a href="https://x.com/nxdevtools"><img src="https://img.shields.io/badge/@nxdevtools-555?style=for-the-badge&logo=x" alt="X (Twitter)"></a>
</p>
<br />
[**Docs**](https://nx.dev/docs) &nbsp;&bull;&nbsp; [**Changelog**](https://nx.dev/changelog) &nbsp;&bull;&nbsp; [**Blog**](https://nx.dev/blog) &nbsp;&bull;&nbsp; [**Courses**](https://nx.dev/courses) &nbsp;&bull;&nbsp; [**YouTube**](https://youtube.com/@nxdevtools)
<br />
</div>
<!-- delete me -->
Nx is a monorepo solution for TypeScript and polyglot codebases. Built with Rust for performance, extensible via TypeScript. Caches what didn't change, runs only what's affected, and comes with an integrated CI solution. Start simple, scale as you grow.
<hr>
## Quick Start
# Smart Repos · Fast Builds
Visit the [Nx quickstart docs](https://nx.dev/docs/quickstart) to get started.
Get to green PRs in half the time. Nx optimizes your builds, scales your CI, and fixes failed PRs. Built for developers and AI agents.
## Why Nx?
Create a new Nx workspace with
- **Incremental by design -** Run `npx nx init` in any npm/pnpm/yarn workspace. Nx picks up your existing `package.json` scripts, caches their outputs, and runs only what's
affected. No changes to your setup required.
- **AI-native tooling -** The Nx CLI is optimized for autonomous AI agents so they get the context they need and can operate just like a human. [Learn more &raquo;](https://github.com/nrwl/nx-ai-agents-config)
- **Polyglot plugin system -** Optional plugins auto-discover tasks, configure cache inputs/outputs, and scaffold code based on your actual tooling. Works with Vite, Webpack, Jest, Vitest, ESLint, Gradle, Maven, .NET, Go, and [more](https://nx.dev/technologies).
- **Integrated CI solution -** [Connect Nx to your CI provider](https://nx.dev/ci/intro/ci-with-nx) (GitHub Actions, GitLab, Azure, etc.) to enable remote caching, task distribution across machines, affected-only runs, and automatic e2e test splitting. [Learn more &raquo;](https://nx.dev/ci/intro/ci-with-nx)
- **Self-healing CI -** An AI agent on your CI pipeline that detects failures, analyzes root cause, proposes a fix, and verifies it automatically. Local agents connect to CI via MCP to autonomously detect and fix failures. [Learn more &raquo;](https://nx.dev/ci/features/self-healing)
```shell
npx create-nx-workspace
```
## Who uses Nx?
...or run
```
npx nx init
```
to add Nx to your existing workspace to get faster task scheduling, caching and more. More [in the docs](https://nx.dev/getting-started/intro).
## Learn about CI with Nx Cloud
[Nx Cloud](https://nx.dev/nx-cloud) connects directly to your existing CI setup, helping you scale your monorepos on CI by leveraging [remote caching](https://nx.dev/ci/features/remote-cache?utm_source=nxrepo&utm_medium=readme&utm_campaign=nxrepo), [task distribution across multiple machines](https://nx.dev/ci/features/distribute-task-execution?utm_source=nxrepo&utm_medium=readme&utm_campaign=nxrepo), [automated e2e test splitting](https://nx.dev/ci/features/split-e2e-tasks?utm_source=nxrepo&utm_medium=readme&utm_campaign=nxrepo) and [automated task flakiness detection](https://nx.dev/ci/features/flaky-tasks?utm_source=nxrepo&utm_medium=readme&utm_campaign=nxrepo)
Connect your existing Nx workspace with
```
npx nx connect
```
Learn more in the [Nx CI docs &raquo;](https://nx.dev/ci/getting-started/intro?utm_source=nxrepo&utm_medium=readme&utm_campaign=nxrepo)
## Useful links
- [Our docs](https://nx.dev/docs)
- [Our blog](https://nx.dev/blog)
- [Our community discord, live stream,...](https://nx.dev/community)
- [Our YouTube channel](https://www.youtube.com/@NxDevtools)
- [Our Twitter/X](https://x.com/nxdevtools)
<p style="text-align: center;"><a href="https://www.youtube.com/@nxdevtools/videos" target="_blank" rel="noreferrer"><img src="./images/nx-courses-and-videos.svg"
width="100%" alt="Nx - Smart Repos · Fast Builds"></a></p>
From startups to Fortune 500 companies. [See our Nx success stories &raquo;](https://nx.dev/customers)
## Want to help?
If you want to file a bug or submit a PR, read up on
our [guidelines for contributing](https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md) and watch this video that will
help you get started.
<a href="https://www.youtube.com/watch?v=8LCA_4qxc08" target="_blank" rel="noreferrer">
<p style="text-align: center;"><img src="./images/how-to-contribute.png" width="600" alt="Nx - How to contribute video"></p>
</a>
If you want to file a bug or submit a PR, read up on our [guidelines for contributing](https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md).
## Core Team
@@ -84,22 +60,17 @@ help you get started.
| ![James Henry](https://avatars.githubusercontent.com/u/900523?s=160&v=4) | ![Jon Cammisuli](https://avatars2.githubusercontent.com/u/4332460?s=160) | ![Max Kless](https://avatars.githubusercontent.com/u/34165455?s=160) | ![Juri Strumpflohner](https://avatars1.githubusercontent.com/u/542458?s=160) |
| [JamesHenry](https://github.com/JamesHenry) | [cammisuli](https://github.com/cammisuli) | [MaxKless](https://github.com/MaxKless) | [juristr](https://github.com/juristr) |
| Philip Fulcher | Caleb Ukle | Colum Ferry | Steven Nance |
| ------------------------------------------------------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| ![Philip Fulcher](https://avatars1.githubusercontent.com/u/1536471?s=160) | ![Caleb Ukle](https://avatars.githubusercontent.com/u/23272162?s=160) | ![Colum Ferry](https://avatars.githubusercontent.com/u/12140467?s=160) | ![Steven Nance](https://avatars.githubusercontent.com/u/1036428?s=160) |
| [philipjfulcher](https://github.com/philipjfulcher) | [barbados-clemens](https://github.com/barbados-clemens) | [Coly010](https://github.com/Coly010) | [llwt](https://github.com/llwt) |
| Caleb Ukle | Steven Nance | Miroslav Jonaš | Leosvel Pérez Espinosa |
| --------------------------------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| ![Caleb Ukle](https://avatars.githubusercontent.com/u/23272162?s=160) | ![Steven Nance](https://avatars.githubusercontent.com/u/1036428?s=160) | ![Miroslav Jonaš](https://avatars.githubusercontent.com/u/881612?s=160) | ![Leosvel Pérez Espinosa](https://avatars.githubusercontent.com/u/12051310?s=160) |
| [barbados-clemens](https://github.com/barbados-clemens) | [llwt](https://github.com/llwt) | [meeroslav](https://github.com/meeroslav) | [leosvelperez](https://github.com/leosvelperez) |
| Miroslav Jonaš | Leosvel Pérez Espinosa | Zachary DeRose | Craigory Coppola |
| ----------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
| ![Miroslav Jonaš](https://avatars.githubusercontent.com/u/881612?s=160) | ![Leosvel Pérez Espinosa](https://avatars.githubusercontent.com/u/12051310?s=160) | ![Zachary DeRose](https://avatars.githubusercontent.com/u/3788405?s=160) | ![Craigory Coppola](https://avatars.githubusercontent.com/u/6933928?s=160) |
| [meeroslav](https://github.com/meeroslav) | [leosvelperez](https://github.com/leosvelperez) | [ZackDeRose](https://github.com/ZackDeRose) | [AgentEnder](https://github.com/AgentEnder) |
| Zachary DeRose | Craigory Coppola | Chau Tran | Nicole Oliver |
| ------------------------------------------------------------------------ | -------------------------------------------------------------------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| ![Zachary DeRose](https://avatars.githubusercontent.com/u/3788405?s=160) | ![Craigory Coppola](https://avatars.githubusercontent.com/u/6933928?s=160) | ![Chau Tran](https://avatars.githubusercontent.com/u/25516557?s=160) | ![Nicole Oliver](https://avatars.githubusercontent.com/u/4440385?s=160) |
| [ZackDeRose](https://github.com/ZackDeRose) | [AgentEnder](https://github.com/AgentEnder) | [nartc](https://github.com/nartc) | [nixallover](https://github.com/nixallover) |
| Chau Tran | Nicole Oliver | Rares Matei | Altan Stalker |
| -------------------------------------------------------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| ![Chau Tran](https://avatars.githubusercontent.com/u/25516557?s=160) | ![Nicole Oliver](https://avatars.githubusercontent.com/u/4440385?s=160) | ![Rares Matei](https://avatars.githubusercontent.com/u/5975076?s=160) | ![Altan Stalker](https://avatars.githubusercontent.com/u/6324206?s=160) |
| [nartc](https://github.com/nartc) | [nixallover](https://github.com/nixallover) | [rarmatei](https://github.com/rarmatei) | [StalkAltan](https://github.com/StalkAltan) |
| Josh VanAllen | Austin Fahsl | Louie Weng |
| ----------------------------------------------------------------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------- |
| ![Josh VanAllen](https://avatars.githubusercontent.com/u/5290334?s=160) | ![Austin Fahsl](https://avatars.githubusercontent.com/u/6913035?s=160) | ![Louie Weng](https://avatars.githubusercontent.com/u/56288712?s=160) |
| [joshvanallen](https://github.com/joshvanallen) | [fahslaj](https://github.com/fahslaj) | [lourw](https://github.com/lourw) |
| Rares Matei | Altan Stalker | Josh VanAllen | Louie Weng |
| --------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------- |
| ![Rares Matei](https://avatars.githubusercontent.com/u/5975076?s=160) | ![Altan Stalker](https://avatars.githubusercontent.com/u/6324206?s=160) | ![Josh VanAllen](https://avatars.githubusercontent.com/u/5290334?s=160) | ![Louie Weng](https://avatars.githubusercontent.com/u/56288712?s=160) |
| [rarmatei](https://github.com/rarmatei) | [StalkAltan](https://github.com/StalkAltan) | [joshvanallen](https://github.com/joshvanallen) | [lourw](https://github.com/lourw) |
+12
View File
@@ -13,3 +13,15 @@ Instead, please report them to the Security Team at security@nrwl.io.
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message.
Nx follows the principle of Coordinated Vulnerability Disclosure.
## What Should Be Reported
The security email is for **demonstrable, verified vulnerabilities within the Nx codebase itself**.
**Please do not use the security email for:**
- Reports about outdated dependencies (e.g., "package X has a newer version available")
- Reports about dependencies with known CVEs that do not directly affect Nx functionality
- General vulnerability scanner output
If you have a concern about an outdated dependency that you believe impacts Nx users, please open a [GitHub issue](https://github.com/nrwl/nx/issues/new/choose) instead.
-6
View File
@@ -1,6 +0,0 @@
node_modules/
dist/
.astro/
.netlify/
test-output/
playwright-report/
-28
View File
@@ -1,28 +0,0 @@
{
"extends": ["plugin:playwright/recommended", "../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["**/*.spec.ts", "**/*.test.ts", "**/*.spec.js", "**/*.test.js"],
"rules": {
"playwright/no-standalone-expect": "off"
}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
},
{
"files": ["e2e/**/*.{ts,js,tsx,jsx}"],
"rules": {}
}
]
}
+169
View File
@@ -0,0 +1,169 @@
StylesPath = .vale/styles
MinAlertLevel = suggestion
# Treat Markdoc (.mdoc) files as markdown
[formats]
mdoc = md
# Ignore Markdoc tag syntax and @-scoped package names to avoid false positives
TokenIgnores = (\{%.*?%\}), (@\w+/[\w-]+)
[src/content/docs/**/*.{mdoc,mdx,md}]
BasedOnStyles = Nx
# Disable heading check for config option reference pages (headings are camelCase property names)
[src/content/docs/technologies/angular/angular-rsbuild/introduction.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/angular/angular-rspack/create-config.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/build-tools/webpack/Guides/webpack-plugins.mdoc]
Nx.Headings = NO
[src/content/docs/reference/Deprecated/affected-graph.mdoc]
Nx.Headings = NO
[src/content/docs/reference/Deprecated/print-affected.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/react/next/Guides/next-config-setup.mdoc]
Nx.Headings = NO
# Disable heading check for release notes (timestamps as headings)
[src/content/docs/reference/Nx Cloud/release-notes.mdoc]
Nx.Headings = NO
# Disable heading check for nx-cloud-cli (CLI flags as headings)
[src/content/docs/reference/nx-cloud-cli.mdoc]
Nx.Headings = NO
# Disable heading check for nx-console-settings (config option headings)
[src/content/docs/reference/nx-console-settings.mdoc]
Nx.Headings = NO
# Disable heading check for pages with camelCase API property headings
[src/content/docs/reference/nx-json.mdoc]
Nx.Headings = NO
[src/content/docs/reference/project-configuration.mdoc]
Nx.Headings = NO
[src/content/docs/reference/Deprecated/legacy-cache.mdoc]
Nx.Headings = NO
[src/content/docs/extending-nx/local-executors.mdoc]
Nx.Headings = NO
[src/content/docs/guides/Nx Release/programmatic-api.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/node/Guides/wait-for-tasks.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/test-tools/vitest/Guides/testing-without-building-dependencies.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/angular/Guides/nx-and-angular.mdoc]
Nx.Headings = NO
# Disable heading check for merge-atomized-outputs (warning message as heading)
[src/content/docs/technologies/test-tools/playwright/Guides/merge-atomized-outputs.mdoc]
Nx.Headings = NO
# Disable heading check for plugin introduction pages (@nx/ package name headings)
[src/content/docs/technologies/build-tools/docker/introduction.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/build-tools/rspack/introduction.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/build-tools/webpack/introduction.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/dotnet/introduction.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/eslint/introduction.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/java/gradle/introduction.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/java/maven/introduction.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/react/expo/introduction.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/react/react-native/introduction.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/test-tools/cypress/introduction.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/test-tools/detox/introduction.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/test-tools/playwright/introduction.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/test-tools/storybook/introduction.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/vue/nuxt/introduction.mdoc]
Nx.Headings = NO
# Disable heading check for remaining pages with structural false positives
# (code identifiers, slashes, quotes, parenthetical words in headings)
[src/content/docs/extending-nx/create-install-package.mdoc]
Nx.Headings = NO
[src/content/docs/extending-nx/create-preset.mdoc]
Nx.Headings = NO
[src/content/docs/getting-started/editor-setup.mdoc]
Nx.Headings = NO
[src/content/docs/guides/Adopting Nx/from-turborepo.mdoc]
Nx.Headings = NO
[src/content/docs/guides/Tasks & Caching/reduce-repetitive-configuration.mdoc]
Nx.Headings = NO
[src/content/docs/guides/Tasks & Caching/workspace-watching.mdoc]
Nx.Headings = NO
[src/content/docs/reference/Deprecated/custom-tasks-runner.mdoc]
Nx.Headings = NO
[src/content/docs/reference/Deprecated/rescope.mdoc]
Nx.Headings = NO
[src/content/docs/reference/Nx Cloud/credits-pricing.mdoc]
Nx.Headings = NO
[src/content/docs/reference/nx-mcp.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/eslint/Guides/custom-workspace-rules.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/module-federation/Guides/nx-module-federation-plugin.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/module-federation/introduction.mdoc]
Nx.Headings = NO
[src/content/docs/technologies/react/Guides/react-router.mdoc]
Nx.Headings = NO
[src/content/docs/troubleshooting/unknown-local-cache.mdoc]
Nx.Headings = NO
# Verbatim legal text - skip Nx voice rules
[src/content/docs/reference/powerpack-license.mdoc]
BasedOnStyles =
Nx.Headings = NO
Nx.ProductPossessives = NO
Nx.SerialComma = NO
@@ -0,0 +1,41 @@
extends: existence
message: "Avoid AI-sounding phrase '%s'. Rewrite to be direct."
level: error
ignorecase: true
tokens:
- "It's important to note that"
- "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"
- "Whether you're a beginner or an experienced developer"
- "In today's fast-paced development environment"
- 'Unlock the power of'
- 'Harness the power of'
- 'Take your workspace to the next level'
- 'Streamline your workflow'
- 'This comprehensive guide will'
- 'Without further ado'
- 'In conclusion'
- 'To summarize'
- "As we've seen"
- 'Needless to say'
- 'As a matter of fact'
- 'Generally speaking'
- 'It is worth mentioning'
- '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'
+138
View File
@@ -0,0 +1,138 @@
extends: capitalization
message: "Use sentence case for headings. '%s' should be '%s'."
level: error
scope: heading
match: $sentence
indicators:
- ':'
exceptions:
- Nx
- Nx Cloud
- Nx Console
- Nx Agents
- Nx Replay
- AI
- CI
- CD
- API
- APIs
- URL
- URLs
- CLI
- PR
- PRs
- IDE
- TypeScript
- JavaScript
- Angular
- React
- Vue
- Nuxt
- Vite
- Webpack
- Rspack
- Rollup
- ESLint
- Prettier
- GitHub
- GitHub Actions
- GitLab
- BitBucket
- BitBucket Cloud
- Azure DevOps
- Azure
- Docker
- Dockerfile
- Kubernetes
- Gradle
- Maven
- Node.js
- Deno
- Bun
- pnpm
- npm
- Yarn
- Next.js
- Remix
- Astro
- Storybook
- Jest
- Vitest
- Cypress
- Playwright
- Express
- Fastify
- Nest.js
- NestJS
- Expo
- React Native
- Module Federation
- TanStack
- TanStack Router
- IntelliJ
- VS Code
- VSCode
- WebStorm
- Turborepo
- Lerna
- Bazel
- JSON
- YAML
- TOML
- CSS
- HTML
- SSR
- SSG
- MFE
- DTE
- SWC
- Rsbuild
- Rolldown
# Abbreviations and acronyms
- AI
- UI
- DX
- ID
- FAQ
- SAML
- DPE
- WSL
- EJS
- AST
- TTG
- PATs
- IDEs
- MCP
- HTTP
- HTTPS
- INI
- VCS
- LTS
- DTS
- SVG
- SVGs
- SVGR
- EAS
- iOS
- AWS
- S3
- E2E
- TL;DR
- NuGet
- MongoDB
- OpenShift
- Vercel
- Netlify
# Proper nouns
- RxJS
- JetBrains
- Neovim
- PnP
- Self-Healing
- AMD64
- ARM64
# Filenames and env vars
- SELF_HEALING.md
- CLAUDE.md
- NODE_AUTH_TOKEN
- NX_REJECT_UNKNOWN_LOCAL_CACHE
@@ -0,0 +1,32 @@
extends: existence
message: "Avoid marketing language '%s'. Be specific about what the feature does instead."
level: suggestion
ignorecase: true
tokens:
- 'effortless'
- 'effortlessly'
- 'seamless'
- 'seamlessly'
- 'powerful'
- 'robust'
- 'comprehensive'
- 'leverage'
- 'utilize'
- 'facilitate'
- 'aforementioned'
- 'best-in-class'
- 'world-class'
- 'next-level'
- 'supercharge'
- 'delve'
- 'underscore'
- 'underscores'
- 'foster'
- 'empower'
- 'meticulous'
- 'meticulously'
- 'crucial'
- 'pivotal'
- 'paramount'
- 'intricate'
- 'multifaceted'
@@ -0,0 +1,25 @@
extends: existence
message: "Prefer active voice. '%s' could be rewritten."
level: suggestion
ignorecase: true
tokens:
- 'is cached by'
- 'is built by'
- 'is run by'
- 'is executed by'
- 'is generated by'
- 'is created by'
- 'is managed by'
- 'is handled by'
- 'is provided by'
- 'is configured by'
- 'is determined by'
- 'is computed by'
- 'is resolved by'
- 'is stored by'
- 'are cached by'
- 'are built by'
- 'are run by'
- 'are executed by'
- 'are generated by'
- 'are created by'
@@ -0,0 +1,9 @@
extends: substitution
message: "Use '%s' instead of '%s'. Product names must be capitalized."
level: error
ignorecase: false
swap:
'(?:nx cloud|NX Cloud|Nx cloud|NX cloud)': Nx Cloud
'(?:nx console|NX Console|Nx console|NX console)': Nx Console
'(?:nx agents|NX Agents|Nx agents|NX agents)': Nx Agents
'(?:nx replay|NX Replay|Nx replay|NX replay)': Nx Replay
@@ -0,0 +1,6 @@
extends: existence
message: "Don't use possessives on product names. Use 'the Nx configuration' instead of 'Nx's configuration'."
level: error
ignorecase: false
tokens:
- "Nx's"
@@ -0,0 +1,26 @@
extends: existence
message: "Don't write about the document itself. Get right to the content. Remove '%s'."
level: error
ignorecase: true
tokens:
- 'This page explains'
- 'This page describes'
- 'This page covers'
- 'This page shows'
- 'This document covers'
- 'This document explains'
- 'This document describes'
- 'In this guide'
- 'In this tutorial'
- 'In this section'
- 'This guide will'
- 'This tutorial will'
- 'This section will'
- "we'll walk through"
- 'we will walk through'
- "we'll explore"
- 'we will explore'
- "we'll cover"
- 'we will cover'
- "we'll look at"
- 'we will look at'
@@ -0,0 +1,14 @@
extends: existence
message: "Rewrite to lead with the reader's action instead of '%s'. For example, 'You can ...' or 'Run ...'."
level: warning
ignorecase: true
tokens:
- 'This allows you to'
- 'This enables you to'
- 'This lets you'
- 'This provides you with'
- 'This gives you the ability to'
- 'Nx allows you to'
- 'Nx enables you to'
- 'Nx provides the ability to'
- 'Nx provides you with'
@@ -0,0 +1,7 @@
extends: existence
message: "Use the Oxford (serial) comma before 'and' or 'or' in a list of three or more items."
level: suggestion
scope: sentence
tokens:
- '\w+,\s\w+\sand\s'
- '\w+,\s\w+\sor\s'
@@ -0,0 +1,9 @@
extends: substitution
message: "Use '%s' instead of '%s'."
level: warning
ignorecase: true
swap:
schematic: generator
schematics: generators
memoized: cached
stored results: cached
+11
View File
@@ -0,0 +1,11 @@
extends: existence
message: "Avoid '%s'. If something were truly simple, you wouldn't need to document it."
level: suggestion
ignorecase: true
tokens:
- 'easily'
- 'simply'
- 'straightforward'
- 'obviously'
- 'of course'
- 'trivial'
+86
View File
@@ -22,6 +22,48 @@ This documentation site leverages Astro's static site generation capabilities wi
- Dynamic API documentation generation from Nx packages and CLI commands
- Community plugin registry
## Information Architecture Principles
When creating or reorganizing documentation, follow these 5 principles to determine where content belongs.
### 1. Progressive Disclosure (The "Journey" Rule)
- **Concept:** Don't overwhelm the user. Reveal complexity only as they advance in their journey.
- **The Test:** _Is this for the First 30 Minutes (Getting Started), the First 30 Days (Features), or Forever (Reference)?_
### 2. Category Homogeneity (The "Scan" Rule)
- **Concept:** Items in a list must be of the same "type" (noun, verb, or concept) to reduce cognitive load.
- **The Test:** _Does this list mix Concepts (Mental Model), Tasks (Update Nx), and Products (React)? If yes, split it._
### 3. Type-Based Navigation (The "Intent" Rule)
- **Concept:** Separate **Learning** (Narrative/Guides) from **Looking Up** (Reference/API).
- **The Test:** _Is the user here to learn a workflow (Guide) or look up a flag syntax (Reference)?_
### 4. The Pen & Paper Test (The "Theory" Rule)
- **Concept:** Distinguish Architecture from Features to keep "Core Concepts" pure.
- **The Test:** _Can I explain this using only a pen and paper?_
- **Yes:** It goes in **How Nx Works** (Architecture).
- **No (I need a terminal):** It goes in **Platform Features** (Feature).
### 5. Universal vs. Specific (The "Placement" Rule)
- **Concept:** Distinguish Platform features from Ecosystem tools to prevent "Features" from becoming a junk drawer.
- **The Test:** _Does this feature apply to EVERY user (e.g., Caching, Agents)?_
- **Yes:** **Platform Features**.
- **No (Only React users):** **Technologies**.
### Sidebar Structure
The sidebar has 4 top-level sections that follow the user journey:
1. **Getting Started** - Essential setup, tutorials, and core concepts (How Nx Works, Platform Features)
2. **Technologies** - Framework and tool-specific guides (React, Angular, Node, build tools, test tools)
3. **Knowledge Base** - Recipes, troubleshooting, and topic-specific guides
4. **Reference** - Exhaustive facts, no narrative (CLI commands, configuration, API docs)
## Project Structure
```
@@ -280,3 +322,47 @@ The Framer page should render JSON inside a `<pre>` tag:
- Users can dismiss the banner (stored in localStorage)
- If `enabled` is `false` or `activeUntil` has passed, the banner won't show
- If `BANNER_URL` is not set, an empty collection is generated
## Versioned Docs
When a new major Nx version is released (or about to be released), create a versioned snapshot of the docs site so the previous version remains accessible at `{major}.nx.dev` (e.g. `22.nx.dev`).
### Creating a Version Snapshot
```bash
node ./scripts/create-versioned-docs.mts 22
```
This will:
1. Fetch tags from origin, find the latest stable release for that major (e.g. `22.6.4`)
2. Checkout that tag, install deps, and build the docs site
3. Create an orphan git branch `22` containing only the pre-built static site plus minimal scaffolding (root `package.json`, `nx.json`, `pnpm-lock.yaml`, `netlify.toml`, and a no-op `nx-dev` project) so Netlify's configured build command succeeds instantly
4. Return to your original branch
If no stable tags exist for that major version, it builds from the current branch.
For Nx 21+, the script builds `astro-docs` (Astro/Starlight). For legacy Nx 1820, it builds `nx-dev` (Next.js with static export) — this path will be removed once those versions are no longer maintained.
#### Flags
- `--force` — overwrite an existing local/remote `{major}` branch
- `--redirect-to-prod` — skip the build and produce a branch that 301s every path to `https://nx.dev/docs`. Used to retire an old versioned subdomain (e.g. `16.nx.dev`) without maintaining its docs
```bash
# Retire an old versioned site
node ./scripts/create-versioned-docs.mts 16 --redirect-to-prod
```
### Pushing the Branch
```bash
git push -f origin 22
```
### Deployment Setup
Versioned sites are served via Netlify branch deploys of the main `nx-dev` Netlify site, with custom domains managed in Squarespace.
- **Netlify** — each `{major}` branch is deployed as a [branch deploy](https://docs.netlify.com/site-deploys/overview/#branch-deploy-controls) of the `nx-dev` site. The branch's root `netlify.toml` overrides the UI build settings so Netlify serves the pre-built static files (no rebuild, no `@netlify/plugin-nextjs`). Add the branch to the site's branch deploy allowlist, then add `{major}.nx.dev` as a domain alias pointing at the branch deploy
- **Squarespace** — DNS for `nx.dev` is managed in Squarespace. Add a CNAME for `{major}` pointing at the Netlify branch deploy hostname
+472
View File
@@ -0,0 +1,472 @@
# Nx Documentation Style Guide
These rules apply to all content under `astro-docs/src/content/docs/`.
[Vale](#vale-configuration) enforces the mechanical ones automatically.
## Information architecture
When creating or reorganizing documentation, follow these five principles to determine where content belongs.
### 1. Progressive disclosure (the "journey" rule)
Don't overwhelm the user. Reveal complexity only as they advance in their journey.
**The test:** Is this for the first 30 minutes (Getting Started), the first 30 days (Features), or forever (Reference)?
### 2. Category homogeneity (the "scan" rule)
Items in a list must be of the same "type" (noun, verb, or concept) to reduce cognitive load.
**The test:** Does this list mix concepts (mental model), tasks (update Nx), and products (React)? If yes, split it.
### 3. Type-based navigation (the "intent" rule)
Separate learning (narrative/guides) from looking up (reference/API).
**The test:** Is the user here to learn a workflow (guide) or look up a flag syntax (reference)?
### 4. The pen and paper test (the "theory" rule)
Distinguish architecture from features to keep "core concepts" pure.
**The test:** Can I explain this using only a pen and paper?
- Yes: It goes in **How Nx Works** (architecture).
- No (I need a terminal): It goes in **Platform Features** (feature).
### 5. Universal vs. specific (the "placement" rule)
Distinguish platform features from ecosystem tools to prevent "Features" from becoming a junk drawer.
**The test:** Does this feature apply to every user (e.g., caching, Nx Agents)?
- Yes: **Platform Features**.
- No (only React users): **Technologies**.
### Sidebar structure
The sidebar has four top-level sections that follow the user journey:
1. **Getting Started** - Essential setup, tutorials, and core concepts (How Nx Works, Platform Features)
2. **Technologies** - Framework and tool-specific guides (React, Angular, Node, build tools, test tools)
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.
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.
- **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.
### Voice do's and don'ts
| Do | Don't |
| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
| "You can speed up builds by enabling remote caching." | "Nx allows you to speed up builds." |
| "Run `nx build` to build your project." | "In order to build your project, you can run the `nx build` command." |
| "This works best with fewer than 50 projects." | "This feature can easily scale to any number of projects." |
| "Nx reads your `vite.config.ts` and infers build targets automatically." | "Nx provides a robust and comprehensive mechanism for inferring build targets." |
| "If the cache is stale, delete `.nx/cache` and retry." | "Should you encounter issues with caching, you may want to consider clearing your cache directory." |
### Anti-AI language
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:**
- "It's important to note that..."
- "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..."
- "Take your workspace to the next level"
- "Streamline your workflow" (as a generic claim without specifics)
- "This comprehensive guide will..."
- "Without further ado..."
- "In conclusion..." / "To summarize..." / "As we've seen..."
- "Game-changer" / "Cutting-edge" / "Groundbreaking"
- "Seamless" / "Seamlessly" (unless describing an actual integration)
**Avoid hedging words:**
- "Essentially" / "Basically" / "Effectively"
- "Generally speaking"
- "It is worth mentioning"
- "Arguably"
- "Needless to say"
- "As a matter of fact"
**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.
- 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
Don't write about the document itself.
Do:
- "Nx uses a project graph to determine task dependencies."
Don't:
- "This page explains how Nx uses a project graph."
- "In this guide, we'll walk through..."
- "This document covers..."
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 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."
### Customer perspective
Focus on what the reader can do, not what Nx does.
Do:
- "Use `nx affected` to run tasks only for projects impacted by your changes."
Don't:
- "Nx allows you to run affected tasks."
- "Nx provides the ability to run tasks selectively."
Words like "allow" and "enable" are signals you're writing from the product's perspective instead of the reader's.
## Language
Write in US English.
### Active voice
Use active voice in most cases.
Do: "Nx caches the build output."
Don't: "The build output is cached by Nx."
Exception: When "Nx" as the subject sounds awkward, passive voice is fine. "The output is stored in `.nx/cache`" is better than "Nx stores the output in `.nx/cache`" if Nx isn't the focus of the sentence.
### Contractions
Use contractions. They make the text feel natural.
- "You'll need to configure..." not "You will need to configure..."
- "It doesn't support..." not "It does not support..."
Don't contract for emphasis in warnings or error descriptions:
- "**Do not** delete the `nx.json` file."
- "Requests to localhost **are not** allowed."
Don't contract proper nouns: "the Vite plugin is..." not "Vite's a plugin..."
### Capitalization
Use sentence case for headings. Capitalize proper nouns only.
- `# Use remote caching to speed up CI`
- `## Configure the Vite plugin`
Feature names are lowercase unless they are a proper product name:
| Correct | Incorrect |
| -------------- | -------------- |
| remote caching | Remote Caching |
| project graph | Project Graph |
| Nx Cloud | nx cloud |
| Nx Console | nx console |
| Nx Agents | nx agents |
| Nx Replay | nx replay |
### Acronyms
Spell out acronyms on first use per page. Don't spell out widely-known ones: CI, CD, API, URL, CLI, PR, IDE.
Don't make acronyms plural with apostrophes. Use `APIs`, not `API's`.
### Numbers
Spell out zero through nine. Use numerals for 10 and above. Always use numerals with units: "5 minutes", "3 projects."
### Possessives
Don't use possessives on product names. "the Docker CLI", not "Docker's CLI." "the Nx configuration", not "Nx's configuration."
## Text
### Headings
- Don't skip heading levels (e.g., `##` to `####`).
- Don't use code in headings unless it's essential (like a CLI command).
- Don't use bold text in headings.
- Keep headings short and scannable. Lead with keywords.
### Line length
- Wrap lines at approximately 100 characters for readability in diffs.
- Start each new sentence on a new line.
- Exception: Don't break links across lines.
### Punctuation
- Use serial (Oxford) commas: "React, Angular, and Vue."
- Use one space between sentences.
- Don't use semicolons. Use two sentences instead.
- Don't use em dashes or en dashes. Use commas or separate sentences.
### Placeholder text
Use `<` and `>` for values the reader must replace:
```shell
nx run <project-name>:build
```
If the placeholder is inline, wrap it in a single backtick: `<your-project>`.
### Bold
Use bold for:
- UI elements: "Select **Add Connection**."
- Navigation paths: "Go to **Settings** > **Workspace**."
Don't use bold for emphasis or keywords. If you need emphasis, rewrite the sentence to be clearer.
### Inline code
Use inline code (single backticks) for:
- Commands and CLI arguments: `nx build`, `--parallel`
- File names and paths: `nx.json`, `.nx/cache`
- Configuration keys: `targetDefaults`, `namedInputs`
- Short outputs and values: `true`, `false`, `success`
### Code blocks
Use triple backticks with a language identifier:
````markdown
```json
{
"targetDefaults": {
"build": {
"cache": true
}
}
}
```
````
- Always specify a syntax language. Use `plaintext` if nothing else fits.
- Add a blank line before and after code blocks.
- For long config files, show only the relevant section and use comments to indicate omitted parts:
```json
{
// ... other config
"targetDefaults": {
"build": {
"cache": true
}
}
}
```
## Links
Links help readers find related information, but too many links make text hard to read.
### General rules
- Don't duplicate links. If you link to a page once, don't link to it again on the same page.
- Don't use links in headings.
- Avoid more than 15 links to other pages on any single page.
- Avoid multiple links in a single paragraph when possible.
### Link text
Use descriptive text, not "here" or "this page."
Do:
- "For more information, see [remote caching](/features/cache)."
- "To configure task pipelines, see [task pipeline configuration](/concepts/task-pipeline-configuration)."
Don't:
- "For more information, see [this page](/features/cache)."
- "Click [here](/features/cache) to learn more."
- "For more information, see the [Remote Caching](/features/cache) documentation."
Standard patterns:
- `For more information, see [link text](url).`
- `To <do this thing>, see [link text](url).`
### External links
Minimize external links. They break over time and are hard to maintain. When you must link externally, prefer official documentation (e.g., Vite docs, Webpack docs) over blog posts or third-party guides.
## Lists
- Use ordered lists for sequences of steps.
- Use unordered lists when order doesn't matter.
- Use dashes (`-`) for unordered lists.
- Start ordered list items with `1.` (Markdown auto-increments).
- Make list items parallel in structure.
- Add a colon after the introductory phrase.
- Don't use list items to complete an introductory sentence.
Do:
```markdown
You can clear the cache in the following ways:
- Delete the `.nx/cache` directory manually.
- Run `nx reset` to clear all cached results.
```
Don't:
```markdown
You can clear the cache by:
- Deleting the `.nx/cache` directory manually.
- Running `nx reset`.
```
## Tables
Use tables for structured data that benefits from a matrix layout. For simple lists of items with descriptions, use a regular list instead.
- Don't leave cells empty. Use "N/A" or "None."
- Use sentence case for headers.
- Keep the header and delimiter rows the same length.
## Nx-specific terminology
Use these terms consistently. When writing about Nx concepts, use the exact term from this list.
| Term | Usage notes |
| -------------- | ------------------------------------------------------------------------------------------ |
| workspace | The root directory managed by Nx. Not "repo" or "monorepo" when referring to Nx's context. |
| project | An app or library within the workspace. |
| target | A task that can be run for a project (e.g., `build`, `test`, `lint`). |
| executor | The implementation behind a target. Not "builder." |
| generator | Code scaffolding tool. Not "schematic." |
| plugin | An Nx plugin that provides executors, generators, or graph inference. |
| task | A specific invocation of a target for a project (e.g., `myapp:build`). |
| project graph | The dependency graph between projects. |
| affected | Projects impacted by a code change. |
| cache / cached | Not "memoized" or "stored results." |
| remote caching | Sharing cached results across machines. Specific product: "Nx Replay." |
| Nx Cloud | The hosted CI/CD product. Always capitalized. |
| Nx Console | The IDE extension. Always capitalized. |
| Nx Agents | Distributed task execution product. Always capitalized. |
| Nx Replay | Remote caching product. Always capitalized. |
| `nx.json` | Always in code style. |
| `project.json` | Always in code style. |
## Vale configuration
[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.
### Running Vale
```shell
# Via Nx target (recommended)
nx vale astro-docs
# Directly (from astro-docs/ directory)
vale src/content/docs/
```
### Installing Vale
Vale is managed via [mise](https://mise.jdx.dev/). Run `mise install` from the repo root to install it.
You can also install directly via `brew install vale` (macOS) or `apt-get install vale` (Linux).
### 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, restatement closers |
| 3 - Voice | `suggestion` | Trust-undermining words, marketing language, passive voice, serial commas |
### Adding new rules
Create a new `.yml` file in `.vale/styles/Nx/`.
Vale supports several [extension points](https://vale.sh/docs/styles): `existence`, `substitution`, `occurrence`, `repetition`, `consistency`, `conditional`, `capitalization`, and `metric`.
Set `level` to match the tier: `error` for mechanical rules, `warning` for structural, `suggestion` for voice/judgment.
+45 -27
View File
@@ -5,15 +5,19 @@ import netlify from '@astrojs/netlify';
import react from '@astrojs/react';
import markdoc from '@astrojs/markdoc';
import tailwindcss from '@tailwindcss/vite';
import sitemap from '@astrojs/sitemap';
import { sidebar } from './sidebar.mts';
import rehypeTableOptionLinks from './src/plugins/utils/rehype-table-option-links.ts';
import { resolveNxDevUrl } from './src/utils/resolve-nx-dev-url.ts';
// Always resolve NX_DEV_URL so downstream consumers (Footer, Header) pick it up.
// For deploy previews this overrides any site-level env var to point to the matching preview.
process.env.NX_DEV_URL = resolveNxDevUrl();
const BASE = '/docs';
// This is exposed as window.__CONFIG
const PUBLIC_CONFIG = {
cookiebotDisabled: process.env.COOKIEBOT_DISABLED === 'true',
cookiebotId: process.env.COOKIEBOT_ID ?? null,
gaMeasurementId: 'UA-88380372-10',
gtmMeasurementId: 'GTM-KW8423B6',
isProd: process.env.NODE_ENV === 'production',
};
@@ -33,7 +37,36 @@ export default defineConfig({
},
},
},
markdown: {
rehypePlugins: [rehypeTableOptionLinks],
},
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':
'/docs/reference/deprecated/self-hosted-cache-packages',
'/reference/remote-cache-plugins/s3-cache/overview':
'/docs/reference/deprecated/self-hosted-cache-packages',
'/reference/remote-cache-plugins/gcs-cache':
'/docs/reference/deprecated/self-hosted-cache-packages',
'/reference/remote-cache-plugins/gcs-cache/overview':
'/docs/reference/deprecated/self-hosted-cache-packages',
'/reference/remote-cache-plugins/azure-cache':
'/docs/reference/deprecated/self-hosted-cache-packages',
'/reference/remote-cache-plugins/azure-cache/overview':
'/docs/reference/deprecated/self-hosted-cache-packages',
'/reference/remote-cache-plugins/shared-fs-cache':
'/docs/reference/deprecated/self-hosted-cache-packages',
'/reference/remote-cache-plugins/shared-fs-cache/overview':
'/docs/reference/deprecated/self-hosted-cache-packages',
'/reference/remote-cache-plugins/shared-fs-cache/generators':
'/docs/reference/deprecated/self-hosted-cache-packages',
},
// This adapter doesn't support local previews, so only load it on Netlify.
adapter: process.env['NETLIFY'] ? netlify() : undefined,
integrations: [
@@ -51,27 +84,12 @@ export default defineConfig({
replacesTitle: true,
},
disable404Route: true,
lastUpdated: true,
head: [
{
tag: 'script',
content: `window.__CONFIG = ${JSON.stringify(PUBLIC_CONFIG)};`,
},
...(process.env.COOKIEBOT_ID &&
process.env.COOKIEBOT_DISABLED !== 'true'
? [
{
/** @type {"script"} */
tag: 'script',
attrs: {
id: 'Cookiebot',
src: 'https://consent.cookiebot.com/uc.js',
'data-cbid': process.env.COOKIEBOT_ID,
'data-blockingmode': 'auto',
type: 'text/javascript',
},
},
]
: []),
{
tag: 'script',
attrs: {
@@ -87,17 +105,14 @@ export default defineConfig({
// since the sidebar doesn't auto generate w/ dynamic routes from src/pages/reference
// only the src/content/docs/reference files
'./src/plugins/sidebar-reference-updater.middleware.ts',
'./src/plugins/sidebar-icons.middleware.ts',
'./src/plugins/og.middleware.ts',
'./src/plugins/github-stars.middleware.ts',
'./src/plugins/raw-content.middleware.ts',
'./src/plugins/canonical.middleware.ts',
'./src/plugins/schema.middleware.ts',
],
markdown: {
// this breaks the renderMarkdown function in the plugin loader due to starlight path normalization
// as to _why_ it has to normalize a path?
// idk just working around the issue for now but we'll want to have linked headers so will need to fix
headingLinks: false,
headingLinks: true,
},
social: [
{ icon: 'github', label: 'GitHub', href: 'https://github.com/nrwl/nx' },
@@ -136,15 +151,15 @@ export default defineConfig({
// frequency of the term relative to document length
// versus weighted term count.
// default is 1.0
termFrequency: 0.75,
termFrequency: 0.65,
// pageLength changes the way ranking compares page lengths with the average page lengths on your site.
// default 0.75
pageLength: 0.5,
pageLength: 0.3,
// termSaturation controls how quickly a term “saturates” on a page.
// Once a term has appeared on a page many times,
// further appearances have a reduced impact on the page rank.
// default: 1.4
// termSaturation: 1.4,
termSaturation: 1.2,
// termSimilarity changes the ranking based on
// similarity of terms to the search query.
// Currently this only takes the length of the term into account.
@@ -154,5 +169,8 @@ export default defineConfig({
},
}),
react(),
sitemap({
lastmod: new Date(),
}),
],
});
@@ -0,0 +1,158 @@
import { test, expect } from '@playwright/test';
test.describe('CLI sub-command formatting', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/docs/reference/nx-commands');
await expect(
page.getByRole('heading', { name: 'Nx Commands' })
).toBeVisible();
});
test('parent commands render as h2 and sub-commands as h3', async ({
page,
}) => {
const mainContent = page.getByTestId('main-pane');
// "nx show" should be an h2 (top-level parent command)
const showHeading = mainContent.getByRole('heading', {
name: 'nx show',
level: 2,
exact: true,
});
await expect(showHeading).toBeVisible();
// "nx show projects" should be an h3 (sub-command nested under parent)
const showProjectsHeading = mainContent.getByRole('heading', {
name: 'nx show projects',
level: 3,
exact: true,
});
await expect(showProjectsHeading).toBeVisible();
// "nx show project" should also be an h3
const showProjectHeading = mainContent.getByRole('heading', {
name: 'nx show project',
level: 3,
exact: true,
});
await expect(showProjectHeading).toBeVisible();
});
test('sub-command usage blocks show the full command name', async ({
page,
}) => {
const mainContent = page.getByTestId('main-pane');
// Find the "nx show projects" section and verify its usage block
// The usage code block should contain "nx show projects", not "nx projects"
const showProjectsHeading = mainContent.getByRole('heading', {
name: 'nx show projects',
level: 3,
exact: true,
});
await expect(showProjectsHeading).toBeVisible();
// Get the section between "nx show projects" heading and the next heading.
// We look for a code block containing the correct usage pattern.
const codeBlocks = mainContent.locator('pre code');
const allCodeTexts = await codeBlocks.allTextContents();
// There should be a usage block with "nx show projects" (full sub-command name)
expect(allCodeTexts.some((text) => text.includes('nx show projects'))).toBe(
true
);
// There should NOT be a usage block with just "nx projects" (missing parent)
expect(
allCodeTexts.some(
(text) => text.match(/^nx projects/) || text.match(/\nnx projects/)
)
).toBe(false);
});
test('nested sub-commands beyond two levels are documented', async ({
page,
}) => {
const mainContent = page.getByTestId('main-pane');
// "nx show target inputs" is a third-level command — it should render as h3
// alongside other sub-commands and include its usage block.
const showTargetInputsHeading = mainContent.getByRole('heading', {
name: 'nx show target inputs',
level: 3,
exact: true,
});
await expect(showTargetInputsHeading).toBeVisible();
const showTargetOutputsHeading = mainContent.getByRole('heading', {
name: 'nx show target outputs',
level: 3,
exact: true,
});
await expect(showTargetOutputsHeading).toBeVisible();
const codeBlocks = mainContent.locator('pre code');
const allCodeTexts = await codeBlocks.allTextContents();
expect(
allCodeTexts.some((text) => text.includes('nx show target inputs'))
).toBe(true);
});
test('release sub-commands use correct heading and usage format', async ({
page,
}) => {
const mainContent = page.getByTestId('main-pane');
// "nx release" should be h2
const releaseHeading = mainContent.getByRole('heading', {
name: 'nx release',
level: 2,
exact: true,
});
await expect(releaseHeading).toBeVisible();
// "nx release version" should be h3
const releaseVersionHeading = mainContent.getByRole('heading', {
name: 'nx release version',
level: 3,
exact: true,
});
await expect(releaseVersionHeading).toBeVisible();
// Verify usage block includes the full command
const codeBlocks = mainContent.locator('pre code');
const allCodeTexts = await codeBlocks.allTextContents();
expect(
allCodeTexts.some((text) => text.includes('nx release version'))
).toBe(true);
});
test('options and examples are h4 headings excluded from the TOC', async ({
page,
}) => {
const mainContent = page.getByTestId('main-pane');
// Options/Examples should be h4 headings — linkable but below the TOC threshold (h2-h3)
const sharedOptionsHeading = mainContent.getByRole('heading', {
name: 'Shared Options',
level: 4,
});
await expect(sharedOptionsHeading.first()).toBeVisible();
const optionsHeading = mainContent.getByRole('heading', {
name: 'Options',
level: 4,
});
await expect(optionsHeading.first()).toBeVisible();
// They should NOT appear as h2 or h3 (which would put them in the TOC)
await expect(
mainContent.getByRole('heading', { name: 'Options', level: 2 })
).toHaveCount(0);
await expect(
mainContent.getByRole('heading', { name: 'Options', level: 3 })
).toHaveCount(0);
});
});
+1 -1
View File
@@ -11,7 +11,7 @@ test('links in descriptions of properties should correctly link to the same page
await page
.getByTestId('main-pane')
.getByRole('link', { name: 'nxCloudAccessToken' })
.getByRole('link', { name: 'nxCloudAccessToken', exact: true })
.click();
await expect(
+24
View File
@@ -0,0 +1,24 @@
import { baseConfig } from '../eslint.config.mjs';
import playwright from 'eslint-plugin-playwright';
export default [
...baseConfig,
playwright.configs['flat/recommended'],
{
files: ['**/*.spec.ts', '**/*.test.ts', '**/*.spec.js', '**/*.test.js'],
rules: {
'playwright/no-standalone-expect': 'off',
},
},
{
ignores: [
'node_modules/',
'dist/',
'.astro/',
'.netlify/',
'test-output/',
'playwright-report/',
'src/content/banner.json',
],
},
];
+74
View File
@@ -4,9 +4,15 @@ import {
Markdoc,
} from '@astrojs/markdoc/config';
import starlightMarkdoc from '@astrojs/starlight-markdoc';
import { transformOptionsTable } from './src/utils/markdoc-table-option-links';
export default defineMarkdocConfig({
extends: [starlightMarkdoc()],
nodes: {
table: {
transform: transformOptionsTable,
},
},
tags: {
call_to_action: {
render: component('./src/components/markdoc/CallToAction.astro'),
@@ -238,6 +244,15 @@ export default defineMarkdocConfig({
},
},
},
sidebar_group_cards: {
render: component('./src/components/markdoc/SidebarGroupCards.astro'),
attributes: {
group: {
type: 'String',
required: true,
},
},
},
metrics: {
render: component('./src/components/markdoc/Metrics.astro'),
attributes: {
@@ -402,6 +417,65 @@ export default defineMarkdocConfig({
},
},
},
llm_copy_prompt: {
render: component('./src/components/markdoc/LlmCopyPrompt.astro'),
attributes: {
title: { type: 'String', required: true },
},
children: ['paragraph', 'tag', 'list'],
transform(node, config) {
const attributes = node.transformAttributes(config);
function extractText(n, listContext) {
if (typeof n === 'string') return n;
if (n.type === 'text' || n.type === 'softbreak')
return n.attributes?.content ?? '\n';
if (n.type === 'code')
return '`' + (n.attributes?.content ?? '') + '`';
if (n.type === 'link') {
const inner = (n.children || [])
.map((c) => extractText(c))
.join('');
const href = n.attributes?.href;
return href ? `${inner} (${href})` : inner;
}
if (n.type === 'inline')
return (n.children || []).map((c) => extractText(c)).join('');
if (n.type === 'paragraph')
return (
(n.children || []).map((c) => extractText(c)).join('') + '\n'
);
if (n.type === 'list') {
const ordered = n.attributes?.ordered === true;
return (
(n.children || [])
.map((c, i) => extractText(c, { ordered, index: i + 1 }))
.join('\n') + '\n'
);
}
if (n.type === 'item') {
const prefix =
listContext?.ordered === true ? `${listContext.index}. ` : '- ';
return (
prefix + (n.children || []).map((c) => extractText(c)).join('')
);
}
if (n.children) return n.children.map((c) => extractText(c)).join('');
return '';
}
const promptText = node.children
.map((c) => extractText(c))
.join('\n')
.trim();
return new Markdoc.Tag(this.render, { ...attributes, promptText }, []);
},
},
llm_only: {
attributes: {},
children: ['paragraph', 'tag', 'list'],
transform() {
return null;
},
},
youtube: {
render: component('./src/components/markdoc/Youtube.astro'),
attributes: {

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