Skip to content

Tags: nrwl/nx

Tags

23.2.0-beta.7

Toggle 23.2.0-beta.7's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
feat(linter): add the @nx/oxlint plugin (#36491)

## Current Behavior

`@nx/oxlint` resides in the nx-labs repository, shipping on its own
cadence and unavailable through generators. No Nx generator offers an
option to request an Oxlint project.

Additionally, the `linter` option has never reflected workspace choices.
Every generator carried static JSON-schema defaults, so `nx g
@nx/react:lib` in an Oxlint-adopting workspace still produced ESLint
projects. Workspaces created with `--linter=none` had ESLint inferred on
first generator run without explicit flags. Plugin workspace creation
ignored the choice entirely—`@nx/plugin:preset` hardcoded ESLint.

## Expected Behavior

**`@nx/oxlint` graduates into `packages/oxlint`, shipping
experimental.** Additions include an inference plugin detecting Oxlint
from config files, a configuration generator, and a bridge enabling Nx's
`enforce-module-boundaries` rule under Oxlint's JS-plugin API.

**Project generators now follow workspace linter preferences.** The new
`detectLinters` function reads the workspace and returns every linter it
has, most-preferred first, so `[0]` is the one a generator should
follow:

| Workspace State | Result |
|---|---|
| Has `oxlint` | Oxlint |
| Has `eslint` | ESLint |
| Has neither | None |

Previously, detection only distinguished between Oxlint and ESLint,
collapsing "uses ESLint" and "uses nothing" into one category.

Verification involved 17 generators tested against all three workspace
configurations without `--linter` flags—51/51 matched workspace
preferences. An additional 25-generator sweep confirmed detection never
overrides explicit requests.

### Breaking-ish Changes

- **`detectLinters` replaces four hand-rolled `isEslintInstalled`
helpers.** Those probed `require('eslint')`, which resolves from the
generator's own scope; `eslint` is a peer dependency of several
first-party plugins, so they returned true in workspaces that do not use
it. `detectLinters` reads the tree and is exported through
`@nx/js/internal`.

- **12 generators declare `linter` as optional** on input `schema.d.ts`
where previously required, though required on `NormalizedSchema`. This
is strictly more permissive; missing resolution now becomes a compile
error rather than silent failure.

- **Non-interactive `nx g <framework>:app|lib` in linter-free workspaces
now generates no linter,** where previously ESLint was generated.
User-visible on paths unrelated to Oxlint; warrants a release note.

- **Generators no longer ask which linter to use when the workspace
already has one.** Following the workspace is not a question; the prompt
is reserved for a workspace with no linter, where there is a real
choice. Pass `--linter` to override. Cost: opting a single project out
interactively now needs `--linter=none`.

- **The linter prompt moved from schemas into every generator.**
Previously, JSON `x-prompt` always highlighted the first enum value
regardless of workspace setup. Generators now resolve through
`normalizeLinterOption`.

- **`@nx/angular:host` resolves the linter once for host and remotes.**
Angular's `normalizeOptions` returns a new object, so resolved values
never reached callers—each delegation resolved independently, allowing
divergent answers.

- **`create-nx-plugin` now prompts for Nx Cloud in an interactive
terminal.** Declaring `--interactive` (below) also un-skips
`determineNxCloud`, which read the same flag. The prompt was never a
decision to omit—it was dead because the flag did not exist—and this
brings `create-nx-plugin` in line with `create-nx-workspace`.

- **`@nx/web` no longer scaffolds a Jest `src/test-setup.ts`.** Its
`web-components` setup file only ever held the
`document-register-element` polyfill, which was removed from the
template long ago; the value has since produced an empty file wired into
`setupFilesAfterEnv`. The file, its `setupFilesAfterEnv` entry and its
`tsconfig.spec.json` include are all gated on the same value and go
together.

- **`@nx/oxlint` declares `engines: { node: "^20.19.0 || >=22.12.0"
}`.** It's the only ESM package under `packages/`, requiring synchronous
`require` compatibility.

### Workspace Creation

Plugin workspace creation now respects `--linter`. The option threads
through `@nx/plugin:preset`, with resolution occurring once—child
generators prompt independently.

`create-nx-plugin` gained `--interactive` (default true), which it never
declared. Without it, prompts reading this flag saw `undefined` and
skipped themselves.

A detected `none` no longer pins into `nx.json`. Recording `eslint` or
`oxlint` preserves real choices, but freezing `none` prevents workspaces
from adopting linting later.

**The linter question now precedes the test-runner questions.** It
previously trailed `unitTestRunner` and `e2eTestRunner`, sitting beside
the formatter—a workspace-level question deliberately asked last. Every
stack now asks it after the appearance questions (bundler, style, SSR)
and before the test runners, so linter, unit-test runner and e2e runner
read as one block. The react stack's preset branch was split to make
this possible: the bundler resolves first, since
`determineUnitTestRunner` still keys off `preferVitest: bundler ===
'vite'`. The Next.js and React Native/Expo arms were identical and
merged rather than duplicated.

Each stack resolves its own linter inside its `determine*Options`
function, alongside every other per-stack option (`appName`,
`framework`, `unitTestRunner`, `e2eTestRunner`, `formatter`). Resolution
is deliberately not hoisted above the preset switch: `apps`, `ts` and
`npm` reach no generator that takes a linter, so a hoisted call would
ask a question and discard the answer on the most travelled path in the
CLI.

`--preset=web-components` received its own `web` stack. It was the only
`Preset` member mapped to the `unknown` catch-all, which skips option
resolution—harmless until this PR replaced `@nx/web:application`'s
internal `|| 'eslint'` with detection, at which point it scaffolded
unlinted. All 24 `Preset` members are now explicitly cased.

`--preset=ts-standalone` previously asked twice—parent and child each
resolved independently. The preset now forwards `--linter` so the child
short-circuits.

`--no-workspaces` now asks about linters. Previously, only workspaced
stacks determined linters; this layout independence is now respected.

At creation time, ESLint remains the default when prompts cannot run
(CI, `--no-interactive`)—`determineLinterOptions` keeps `initial: 0`
deliberately while Oxlint remains experimental.

### Generated Code Passes Lint

Generating a project and immediately linting it surfaced three failures,
all measured against oxlint 1.75 rather than reasoned about.

- **The React routing scaffold used `<div role="navigation">`**, which
`jsx-a11y/prefer-tag-over-role` reports; it is now a `<nav>`, which
carries that role implicitly. `create-nx-workspace` defaults React
workspaces to routing, so every generated app hit this. Nothing selected
the wrapper by role — no stylesheet, no attribute selector, no test
query — and `addRoute` locates `Route` and `Link` by tag name.
- **The `@nx/next` welcome page lacked the `prefer-tag-over-role`
suppression** that the React and Remix welcome templates already carry.
`@nx/next` enables the `jsx-a11y` plugin and the page embeds five inline
SVGs with `role="img"`, which has no tag to swap for. Both routers
inject the same content, so both wrappers carry it. Sweeping every
package, `jsx-a11y` is enabled only for react, next and remix — expo and
react-native get `react`/`react-perf`, vue and nuxt get `vue`, and the
`role=` attributes in angular and web live in template literals rather
than JSX.
- **The empty Jest setup file** described above, reported by
`unicorn/no-empty-file`.

## Documentation

New Oxlint pages cover setup, inferred tasks, config format, task
naming, type-aware linting, and module-boundaries bridging. Four
knowledge-base pages show `create-nx-workspace` terminal transcripts
with the linter prompt in the position it actually occupies—before the
test-runner questions. The prompt is absent from `--preset=apps`
transcripts, where it cannot fire.

## Related Issue(s)

Relates to NXC-4312

Fixes NXC-4774 — Oxlint a11y rules give contradictory guidance on
`role="navigation"` vs `<nav>`. Note the reported *second, circular*
violation did not reproduce: `<nav>` and even `<nav role="navigation">`
are both clean, and oxlint has no `no-redundant-roles` rule.

Fixes NXC-4776 — Oxlint `unicorn/no-empty-file` flags the Jest
test-setup file in the generated web app.

Known follow-up, deliberately not addressed here: NXC-4784 — the
inference plugin decides on lint targets by extension count alone, so a
project whose files are all ignored still gets a target, and that target
fails.

<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/Graduate-nx-oxlint-from-labs-into-packages-oxlint-NXC-4312-33c21af4)
<!-- polygraph-session-end -->

---------

Co-authored-by: Juri <[email protected]>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <[email protected]>

23.2.0-beta.6

Toggle 23.2.0-beta.6's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(core): re-spawn nx migrate without a shell to preserve argv exact…

…ly (#36215)

## Current Behavior

`nx migrate --run-migrations` re-spawns itself as `nx _migrate` by
joining the raw forwarded argv (`process.argv.slice(3).join(' ')`) into
a shell command string, in both re-spawn paths (the temp-install CLI and
the `NX_MIGRATE_USE_LOCAL` local path). Any forwarded argument
containing shell metacharacters is re-split or crashes the shell:

```
nx migrate --run-migrations --create-commits --commit-prefix="chore(repo): [nx migration] "
/bin/sh: 1: Syntax error: "(" unexpected
```

The same argument list works when invoking `nx _migrate` directly, which
is the only workaround today. This bit 3 of 5 repos in a coordinated
migration run where a scoped commit prefix (required by commitlint)
contains parentheses and spaces.

Quoting alone does not fix Windows, where every route from a package
manager to nx runs through cmd.exe: `%VAR%` expands inside double
quotes, and a bare `^` is eaten as the escape character. The e2e case
covering `--commit-prefix` was skipped on Windows for exactly that
reason.

## Expected Behavior

The re-spawn no longer builds a command string. `runNxArgvSync` locates
the nx to run and spawns `process.execPath` with an argv array, so
nothing parses the arguments in between and each one reaches the child
byte for byte. Both re-spawn routes go through it: the workspace's own
nx under `NX_MIGRATE_USE_LOCAL`, and on the default route the CLI that
`nx migrate` installs into a temp dir. The e2e case covering that prefix
drops its `isNotWindows` guard, though the Windows CI matrix is
commented out today, so nothing exercises it on that platform yet.

`getNxBin` decides what to spawn for the workspace routes, reading
rather than resolving: it ascends to the nearest `node_modules/nx` and
takes the entry point its `bin` field names, which is the file a package
manager links into `node_modules/.bin`. The ascent is npx-shaped, so a
workspace with no nx of its own can be handed an ancestor's that pnpm or
yarn would decline to run. Reading the manifest is what keeps the temp
installation working too: nx moved its entry point from `bin/nx.js` to
`dist/bin/nx.js` in 22.7.0, so a fixed layout can only ever match one
side of that. That installation is read from its own directory alone,
since it declares nx itself and an nx above the temp directory is one
nothing asked for. A resolver is the wrong tool here, as the comment on
`readLocalNxVersion` already noted. `require.resolve` answers from
`NODE_PATH` once its explicit paths miss, and `nxCliPath` points
`NODE_PATH` at the temp installation before spawning it; it also answers
through Node's package self-reference, which returns the running nx
whatever `paths` it is given. Either one lets the temp installation hand
off to itself, which under `--run-migrations` re-enters the same
hand-off and respawns without end.

`getNxBin` returns null for a Yarn PnP workspace and for a
`.nx/installation` workspace, where the `./nx` wrapper has to run so it
can re-sync the installation against `nx.json`'s `installation.version`.
The caller then falls back to whatever `getRunNxBaseCommand` names, the
package manager or that wrapper, with each argument quoted.

On that fallback path, arguments containing `^` are now quoted on
Windows as well. The character is deliberately not added to
`SHELL_META_CHARS`, which is shared with
`serializeOverridesIntoCommandLine`, where quoting every `^1.2.3`
version range would change unrelated task output.

> [!NOTE]
> On Windows the fallback path does not carry two kinds of argument. One
containing a double quote is refused: the quoting rests on an unbroken
double-quoted run, and an embedded quote ends it, since cmd.exe
recognizes no backslash escape. Carrying one would mean caret-escaping
every metacharacter instead, doubled again for a `.cmd` shim, which is
not worth taking on for a path with no Windows coverage to check it
against. A `%VAR%` is still expanded, since cmd.exe expands it inside
double quotes too, and suppressing that needs the `%%cd:~,%` trick,
which only works when the caller owns the whole `cmd.exe /e:ON /c "..."`
line, and `execSync` does not. Both are confined to a Yarn PnP or
`.nx/installation` workspace on Windows, since every other workspace
takes the argv path.

Unit tests cover the argv spawn and its exit-code contract, `getNxBin`'s
lookup and each of its null cases, the shell fallback taken when no nx
resolves, the refusal of a Windows argument carrying a double quote, and
the quoting on both platforms including the exact commit prefix above. A
regression test pins the temp hand-off against reaching for an nx above
its own directory. The POSIX quoting was also checked by round-tripping
the helper's output through a real `/bin/sh`.

## Related Issue(s)

No existing issue — discovered during the multi-repo migration to
23.1.0-beta.6.

<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/nxc-4662-6d5e3016)
<!-- polygraph-session-end -->

---------

Co-authored-by: Leosvel Pérez Espinosa <[email protected]>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>

23.2.0-beta.5

Toggle 23.2.0-beta.5's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
docs(misc): clean up intro and getting-started onboarding pages (#36595)

## Current Behavior

Intro page mentions a couple of deepdives on how to run tasks and if you
can use Nx in single repo (not monorepo). Both aren't important on this
page -> remove.

"Start a New Project" offers two paths: `create-nx-workspace` and Nx
Cloud browser onboarding. Templates read as four starters. Neither
onboarding page has an AI prompt or sample timings. Intro carries two
deep-dive callouts.

## Expected Behavior

Tightens intro page, removes Nx Console since it's covered in `Editor
setup` page.

"Start a New Project" is CNW only. The template section says the four
short names are shortcuts and points at the gallery. Both onboarding
pages get a copyable agent prompt and an example run block, borrowed
from the Nx Cloud get-started page. Intro deep-dive callouts folded into
one line each.

Both init and CNW pages now have `CI setup` and `AI setup` as next steps
since those are important now (no more `Editor setup`).

Previews:
-
https://deploy-preview-36595--nx-docs.netlify.app/docs/getting-started/intro
-
https://deploy-preview-36595--nx-docs.netlify.app/docs/getting-started/start-new-project
-
https://deploy-preview-36595--nx-docs.netlify.app/docs/getting-started/start-with-existing-project

## Related Issue(s)

DOC-579

<!-- polygraph-session-start -->
---
<p><picture><source media="(prefers-color-scheme: dark)"
srcset="https://static.ops.cloud.nx.app/polygraph/session-logo-v4-dark.svg"><img
src="/web-asset-proxy?u=aHR0cHM6Ly9naXRodWIuY29tL25yd2wvbngvPGEgaHJlZj0.d8c3bce3b651ca194f0f9899e764f8de2ebb7d73&v=20260725-2"https://static.ops.cloud.nx.app/polygraph/session-logo-v4-light.svg" rel="nofollow">https://static.ops.cloud.nx.app/polygraph/session-logo-v4-light.svg"
width="16" height="22" align="middle" alt="Polygraph"></picture> <a
href="/web-asset-proxy?u=aHR0cHM6Ly9naXRodWIuY29tL25yd2wvbngvPGEgaHJlZj0.d8c3bce3b651ca194f0f9899e764f8de2ebb7d73&v=20260725-2"https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/noble-osprey-dd3ebfa3">View" rel="nofollow">https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/noble-osprey-dd3ebfa3">View
session ↗</a></p>
<!-- polygraph-session-end -->

23.2.0-beta.4

Toggle 23.2.0-beta.4's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(core): keep real dependencies when omitting peers from npm temp i…

…nstalls (#36518)

## Current Behavior

`ensurePackage` installs on-demand plugins into a temp dir. Since #36295
that install passes `--omit=peer` for npm, so peers resolve from the
workspace instead of being duplicated into the temp dir.

npm flags a package as a peer if **anything** in the tree peer-depends
on it — a real `dependencies` edge does not clear the flag.
`--omit=peer` therefore also prunes packages that are genuine
dependencies of the package being installed.

`@nx/detox` hard-depends on `@nx/jest` and `@nx/eslint`; `@nx/web`
declares both as optional peers. So installing `@nx/detox` on npm
silently drops both:

```console
$ npm i -D @nx/[email protected] --omit=peer --ignore-scripts
$ ls node_modules/@nx
detox devkit js module-federation nx-darwin-arm64 react rollup vitest web workspace
# @nx/jest and @nx/eslint are missing
```

They are still written to `package-lock.json` with `"peer": true`, are
absent from `node_modules/.package-lock.json`, and the install exits 0
with no warning.

Generating a React Native app with Detox then fails. Observed on 22.7.x,
where `ensure-dependencies.ts` imports `@nx/jest/src/utils/versions`:

```
NX  Cannot find module '@nx/jest/src/utils/versions'

Require stack:
- <tmp>/node_modules/@nx/detox/src/generators/application/lib/ensure-dependencies.js
```

On master the same file imports `@nx/jest/internal` instead — a
different subpath of the same pruned package, so it fails the same way.

This is not Detox-specific: 14 first-party plugins hard-depend on
`@nx/jest` or `@nx/eslint`, and several deep-import `@nx/eslint/src/*`
at runtime. Any of them fetched on demand in an npm workspace can lose a
dependency it needs.

## Expected Behavior

npm uses `--legacy-peer-deps` instead. That ignores `peerDependencies` —
the intent of #36295 — without pruning real dependencies:

```console
$ npm i -D @nx/[email protected] --legacy-peer-deps --ignore-scripts
$ ls node_modules/@nx
detox devkit eslint jest js module-federation nx-darwin-arm64 react rollup vite vitest web workspace
```

bun does not over-prune (verified against the same tree), so bun keeps
`--omit=peer`. pnpm and yarn are unchanged.

## Related Issue(s)

N/A — regression from #36295, which has not been released yet.

## Notes for reviewers

**CI will not exercise this change.** Two independent reasons:

1. The macOS Detox e2e only runs when the diff touches `packages/detox`,
`packages/react-native`, `packages/expo`, or their e2e projects
(`scripts/check-react-native-changes.js`). #36295 touched only
`packages/nx`, so the gate skipped it — and it skips this PR too.
2. Even when that job does run, master's e2e uses a shared base
workspace that preinstalls the plugins
(`<e2e>/nx/proj-backup/npm/node_modules/@nx/` contains detox, jest,
eslint, react-native). So `ensurePackage` short-circuits on
`require('@nx/detox')` and the temp-install path never executes at all.

Verified locally instead. The end-to-end run was done on **22.7.x**,
which has no shared base workspace and so genuinely fetches `@nx/detox`
on demand — same branch, same e2e, only the flag differing:

| temp dir | `node_modules/@nx/` contents | result |
| --- | --- | --- |
| `--omit=peer` | detox devkit js module-federation nx-darwin-arm64
react rollup vitest web workspace | 4 tests failed |
| `--legacy-peer-deps` | detox devkit **eslint jest** js
module-federation nx-darwin-arm64 react rollup vite vitest web workspace
| 4 tests passed |

`ensurePackage` never calls `cleanup()`, so these temp dirs survive and
are the reliable signal — `Fetching ...` log lines are absent from
passing runs either way because `runCLI` swallows child stdout on
success.

Also run:

- `nx run e2e-detox:e2e-macos-local` on 22.7.x with this change — 2
suites / 4 tests pass
- `nx test nx --testPathPatterns=src/utils/package-json.spec.ts` —
passes
- `tsc -p packages/nx/tsconfig.lib.json --noEmit` — clean
- `nx prepush` — passes
- the two `npm i` runs above, against published 22.7.7

**Needs backporting to 22.7.x**, which carries the same flag via
`74311e713d` and is the active patch line. Neither line has released
`--omit=peer` yet (`[email protected]` still ships the old flag-less install
command), so there is no user impact today.

<!-- polygraph-session-start -->
---
<p><picture><source media="(prefers-color-scheme: dark)"
srcset="https://static.ops.cloud.nx.app/polygraph/session-logo-v4-dark.svg"><img
src="/web-asset-proxy?u=aHR0cHM6Ly9naXRodWIuY29tL25yd2wvbngvPGEgaHJlZj0.d8c3bce3b651ca194f0f9899e764f8de2ebb7d73&v=20260725-2"https://static.ops.cloud.nx.app/polygraph/session-logo-v4-light.svg" rel="nofollow">https://static.ops.cloud.nx.app/polygraph/session-logo-v4-light.svg"
width="16" height="22" align="middle" alt="Polygraph"></picture> <a
href="/web-asset-proxy?u=aHR0cHM6Ly9naXRodWIuY29tL25yd2wvbngvPGEgaHJlZj0.d8c3bce3b651ca194f0f9899e764f8de2ebb7d73&v=20260725-2"https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/Fix-npm-temp-install-pruning-real-dependencies-via---omitpeer-f7aff304">View" rel="nofollow">https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/Fix-npm-temp-install-pruning-real-dependencies-via---omitpeer-f7aff304">View
session ↗</a></p>
<!-- polygraph-session-end -->

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>

23.1.1

Toggle 23.1.1's commit message

Verified

This commit was signed with the committer’s verified signature.
FrozenPandaz Jason Jean
fix(core): keep real dependencies when omitting peers from npm temp i…

…nstalls (#36518)

## Current Behavior

`ensurePackage` installs on-demand plugins into a temp dir. Since #36295
that install passes `--omit=peer` for npm, so peers resolve from the
workspace instead of being duplicated into the temp dir.

npm flags a package as a peer if **anything** in the tree peer-depends
on it — a real `dependencies` edge does not clear the flag.
`--omit=peer` therefore also prunes packages that are genuine
dependencies of the package being installed.

`@nx/detox` hard-depends on `@nx/jest` and `@nx/eslint`; `@nx/web`
declares both as optional peers. So installing `@nx/detox` on npm
silently drops both:

```console
$ npm i -D @nx/[email protected] --omit=peer --ignore-scripts
$ ls node_modules/@nx
detox devkit js module-federation nx-darwin-arm64 react rollup vitest web workspace
# @nx/jest and @nx/eslint are missing
```

They are still written to `package-lock.json` with `"peer": true`, are
absent from `node_modules/.package-lock.json`, and the install exits 0
with no warning.

Generating a React Native app with Detox then fails. Observed on 22.7.x,
where `ensure-dependencies.ts` imports `@nx/jest/src/utils/versions`:

```
NX  Cannot find module '@nx/jest/src/utils/versions'

Require stack:
- <tmp>/node_modules/@nx/detox/src/generators/application/lib/ensure-dependencies.js
```

On master the same file imports `@nx/jest/internal` instead — a
different subpath of the same pruned package, so it fails the same way.

This is not Detox-specific: 14 first-party plugins hard-depend on
`@nx/jest` or `@nx/eslint`, and several deep-import `@nx/eslint/src/*`
at runtime. Any of them fetched on demand in an npm workspace can lose a
dependency it needs.

## Expected Behavior

npm uses `--legacy-peer-deps` instead. That ignores `peerDependencies` —
the intent of #36295 — without pruning real dependencies:

```console
$ npm i -D @nx/[email protected] --legacy-peer-deps --ignore-scripts
$ ls node_modules/@nx
detox devkit eslint jest js module-federation nx-darwin-arm64 react rollup vite vitest web workspace
```

bun does not over-prune (verified against the same tree), so bun keeps
`--omit=peer`. pnpm and yarn are unchanged.

## Related Issue(s)

N/A — regression from #36295, which has not been released yet.

## Notes for reviewers

**CI will not exercise this change.** Two independent reasons:

1. The macOS Detox e2e only runs when the diff touches `packages/detox`,
`packages/react-native`, `packages/expo`, or their e2e projects
(`scripts/check-react-native-changes.js`). #36295 touched only
`packages/nx`, so the gate skipped it — and it skips this PR too.
2. Even when that job does run, master's e2e uses a shared base
workspace that preinstalls the plugins
(`<e2e>/nx/proj-backup/npm/node_modules/@nx/` contains detox, jest,
eslint, react-native). So `ensurePackage` short-circuits on
`require('@nx/detox')` and the temp-install path never executes at all.

Verified locally instead. The end-to-end run was done on **22.7.x**,
which has no shared base workspace and so genuinely fetches `@nx/detox`
on demand — same branch, same e2e, only the flag differing:

| temp dir | `node_modules/@nx/` contents | result |
| --- | --- | --- |
| `--omit=peer` | detox devkit js module-federation nx-darwin-arm64
react rollup vitest web workspace | 4 tests failed |
| `--legacy-peer-deps` | detox devkit **eslint jest** js
module-federation nx-darwin-arm64 react rollup vite vitest web workspace
| 4 tests passed |

`ensurePackage` never calls `cleanup()`, so these temp dirs survive and
are the reliable signal — `Fetching ...` log lines are absent from
passing runs either way because `runCLI` swallows child stdout on
success.

Also run:

- `nx run e2e-detox:e2e-macos-local` on 22.7.x with this change — 2
suites / 4 tests pass
- `nx test nx --testPathPatterns=src/utils/package-json.spec.ts` —
passes
- `tsc -p packages/nx/tsconfig.lib.json --noEmit` — clean
- `nx prepush` — passes
- the two `npm i` runs above, against published 22.7.7

**Needs backporting to 22.7.x**, which carries the same flag via
`74311e713d` and is the active patch line. Neither line has released
`--omit=peer` yet (`[email protected]` still ships the old flag-less install
command), so there is no user impact today.

<!-- polygraph-session-start -->
---
<p><picture><source media="(prefers-color-scheme: dark)"
srcset="https://static.ops.cloud.nx.app/polygraph/session-logo-v4-dark.svg"><img
src="/web-asset-proxy?u=aHR0cHM6Ly9naXRodWIuY29tL25yd2wvbngvPGEgaHJlZj0.d8c3bce3b651ca194f0f9899e764f8de2ebb7d73&v=20260725-2"https://static.ops.cloud.nx.app/polygraph/session-logo-v4-light.svg" rel="nofollow">https://static.ops.cloud.nx.app/polygraph/session-logo-v4-light.svg"
width="16" height="22" align="middle" alt="Polygraph"></picture> <a
href="/web-asset-proxy?u=aHR0cHM6Ly9naXRodWIuY29tL25yd2wvbngvPGEgaHJlZj0.d8c3bce3b651ca194f0f9899e764f8de2ebb7d73&v=20260725-2"https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/Fix-npm-temp-install-pruning-real-dependencies-via---omitpeer-f7aff304">View" rel="nofollow">https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/Fix-npm-temp-install-pruning-real-dependencies-via---omitpeer-f7aff304">View
session ↗</a></p>
<!-- polygraph-session-end -->

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
(cherry picked from commit 1a1fbe9)

22.7.8

Toggle 22.7.8's commit message

Verified

This commit was signed with the committer’s verified signature.
FrozenPandaz Jason Jean
fix(core): bump pinned axios and brace-expansion past vulnerable vers…

…ions (#36507)

axios pinned at 1.16.1 and brace-expansion override at 5.0.6; both are
flagged by July 2026 advisories (axios < 1.18.0, brace-expansion <=
5.0.7).

axios 1.18.1 (nx, create-nx-workspace, root, plus a pnpm override so
transitive copies resolve patched too) and brace-expansion 5.0.8. No
source changes.

Fixes #36474, NXC-4739

<!-- polygraph-session-start -->
---
<p><picture><source media="(prefers-color-scheme: dark)"
srcset="https://static.ops.cloud.nx.app/polygraph/session-logo-v4-dark.svg"><img
src="/web-asset-proxy?u=aHR0cHM6Ly9naXRodWIuY29tL25yd2wvbngvPGEgaHJlZj0.d8c3bce3b651ca194f0f9899e764f8de2ebb7d73&v=20260725-2"https://static.ops.cloud.nx.app/polygraph/session-logo-v4-light.svg" rel="nofollow">https://static.ops.cloud.nx.app/polygraph/session-logo-v4-light.svg"
width="16" height="22" align="middle" alt="Polygraph"></picture> <a
href="/web-asset-proxy?u=aHR0cHM6Ly9naXRodWIuY29tL25yd2wvbngvPGEgaHJlZj0.d8c3bce3b651ca194f0f9899e764f8de2ebb7d73&v=20260725-2"https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nxc-4739-66bb9743">View" rel="nofollow">https://snapshot.app.trypolygraph.com/orgs/69cdc268b6aa527e4129c2b4/sessions/nxc-4739-66bb9743">View
session ↗</a></p>
<!-- polygraph-session-end -->

23.2.0-beta.3

Toggle 23.2.0-beta.3's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(js): resolve package and extension-less tsconfig extends read fro…

…m the tree (#36271)

## Current Behavior

Generators and migrations that parse a `tsconfig.json` from the devkit
`Tree` each build their own host that reads file contents from the
`Tree` but resolves file existence and paths through `ts.sys`. That host
cannot follow two `extends` forms:

- A package-provided base such as `@tsconfig/node20/tsconfig.json`.
TypeScript resolves it to an absolute path, which the `Tree` re-roots
under the workspace, so the base reads as nothing.
- An extension-less base such as `./tsconfig`. It resolves against the
current working directory, so it only works when the command runs from
the workspace root.

In both cases the base's options silently vanish from the merged result
(the failure surfaces only as a `TS5083`/`TS6053` the callers discard).
The `add-ignore-deprecations` TypeScript 6 migration can then miss a
deprecated option a config inherits from such a base, and generators can
read the wrong compiler options.

## Expected Behavior

`extends` resolves the way `tsc` resolves it, regardless of the
`extends` form or the working directory, when a config is parsed from
the `Tree`. A config that inherits a deprecated option through a package
or extension-less base is handled correctly by the TypeScript 6
migration, and generators read the fully-merged compiler options.

## Implementation Details

A single tree-faithful host, `createTreeParseConfigHost`, is extracted
into `@nx/js` and adopted at the five sites that each rebuilt one: the
angular and js tsconfig utilities, the js `setup-build` and rollup
`configuration` generators, and the `update-23-1-0`
`add-ignore-deprecations` TypeScript 6 migration. It maps absolute paths
under the `Tree` root back to tree-relative, falls back to `fs` for
paths that resolve outside the workspace (a pnpm store or a
`link:`/`file:` target), and answers existence from the `Tree`.
`realpath` and `getCurrentDirectory` are anchored to the `Tree` root, so
resolution is independent of the working directory: TypeScript resolves
a package-form base as a relative path and hands it to `realpath`, which
`ts.sys` would re-anchor to `process.cwd()`. The out-of-root `fs` branch
is gated on `isFile` to match `ts.sys`, so a directory is never read as
a config file.

The migration now warns about a config whose `extends` chain is
genuinely unresolvable instead of silently guessing at incomplete
options.

Two changes worth calling out: existence at the generator sites now
comes from the `Tree` rather than disk, so a base present on disk but
deleted in the `Tree` is reported unresolvable; and `readTsConfig`'s
optional `sys` parameter widens from `ts.System` to
`ts.ParseConfigHost`, a public `@nx/js` signature change that stays
source-compatible for existing callers.

Host unit tests and package-form and extension-less `extends` fixtures
are added to the migration spec.

## Related Issue(s)

Fixes NXC-4609

<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/nxc-4609-a2099980)
<!-- polygraph-session-end -->

23.2.0-beta.2

Toggle 23.2.0-beta.2's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(core): strip terminal query sequences when replaying task output (#…

…36432)

## Current Behavior

When a task's captured pty output is replayed (TUI summary, static
terminal output, cache replays), any terminal *query* escape sequences
the child emitted are written to the real terminal verbatim. The
terminal dutifully replies on stdin — but by then nx has restored cooked
mode and nothing is consuming replies, so the reply gets echoed into the
visible output as garbage next to the run summary, e.g.:

```
> nvim

^[[?62;22;52c
 NX   Successfully ran target edit for project @nx/nx-source (3m)
```

`ESC[?62;22;52c` is the terminal's Primary Device Attributes reply to
the `ESC[c` probe nvim sends at startup. The existing passthrough filter
only handles one such sequence (`ESC[6n`), fixing a single symptom
rather than the class.

## Expected Behavior

Replayed output is a recording — no process is waiting for the
terminal's answers anymore, so reply-eliciting sequences are stripped
before the replay is written. A new `stripTerminalQueries()` helper
removes:

- DA1/DA2/DA3 device attribute queries (`CSI c`, `CSI > c`, `CSI = c`) —
replies (`CSI ? … c`) are intentionally preserved
- DSR status/cursor reports (`CSI 5 n`, `CSI 6 n`, `CSI ? Ps n`)
- XTVERSION (`CSI > q`) and DECRQM mode queries (`CSI ? Ps $ p`)
- kitty keyboard protocol query (`CSI ? u`)
- XTWINOPS report requests (`CSI 14 t`, `CSI 18 t`, …) while preserving
non-reporting window ops
- OSC color/clipboard queries (`OSC 10;?`, `OSC 52;c;?`, …) while
preserving OSC sets like window titles
- XTGETTCAP / DECRQSS (`DCS + q … ST`, `DCS $ q … ST`)

The strip is applied in `output.logCommandOutput`, which every replay
path (tui-summary, static run-one/run-many, empty, invoke-runner life
cycles) funnels through. Live pty passthrough is untouched: while a task
runs, queries must reach the real terminal and the replies are consumed
in raw mode.

## Related Issue(s)

N/A — reported while testing #36322 locally; reproduced on stock nx
22.4.1, pre-existing and unrelated to that PR.

<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/Strip-terminal-query-sequences-from-replayed-task-output-895a849d)
<!-- polygraph-session-end -->

23.2.0-beta.1

Toggle 23.2.0-beta.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(core): render critical-path tasks as a nested list in the job sum…

…mary (#36394)

## Current Behavior

In the GitHub Actions job summary, the Nx Run Report's "Speed up or
split the longest tasks on the critical path" recommendation renders its
task list as terminal-style rows collapsed with `<br>`:

```
- Speed up or split the longest tasks on the critical path:<br>e2e-react-native:e2e-macos-local                 20m 2s<br>@nx/nx-source:populate-local-registry-storage    5m 31s
```

The rows are space-padded for terminal column alignment, but HTML
collapses runs of spaces, so the rendered summary shows ragged,
hard-to-read lines jammed into a single bullet.

## Expected Behavior

The Markdown renderer formats the task list as a nested list under the
recommendation's bullet:

```
- Speed up or split the longest tasks on the critical path:
  - `e2e-react-native:e2e-macos-local` — 20m 2s
  - `@nx/nx-source:populate-local-registry-storage` — 5m 31s
```

Structurally, the critical-path recommendation now carries its task rows
as data (`RecTaskRows`) instead of a pre-joined terminal string, and
each renderer formats them natively. The terminal report and the TUI
popup payload output are byte-for-byte unchanged (covered by the
existing tests, which pass unmodified); only the job-summary Markdown
changes.

## Related Issue(s)

N/A

<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/Speed-up-main-macos-CI-job-parallel-e2e--drop-dead-Homebrew-cache-7918829a)
<!-- polygraph-session-end -->

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>

23.2.0-beta.0

Toggle 23.2.0-beta.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
feat(core): add a full-width TUI status bar and vim-style pane search (

…#36263)

## Current Behavior

The TUI task list renders its own bottom rows (keyboard hints, Nx Cloud
message, filter display) inside its own column, so they are cramped in
split layouts and disappear entirely when the task list is hidden
(fullscreen pane). The run title and NX badge live in the task-list
table header, terminal panes draw their own keybinding hints on their
bottom borders, and there is no way to search a pane's output. Much of
the UI state (cloud message/link, filter text, perf-report flag) is
duplicated between `TuiState` and `TasksList`, kept in sync via
broadcast actions.

## Expected Behavior

**Full-width status bar** on the bottom row of the TUI:

- Left: minimal progress counts with a live overall run duration —
`63/174 (1m 23s)` — which double as the clickable Nx Cloud link when a
structured link exists.
- Middle: free-text cloud messages (they can carry errors), transient
pane feedback ("copied to clipboard"), or the compact confirmed-search
display.
- Right: context-aware keyboard hints (task-list vs focused-pane) with
progressive fitting — as many whole hint items as fit the space — and
the `NON-INTERACTIVE i to toggle` / `INTERACTIVE <ctrl>+z to toggle`
indicator pinned right-most, never dropped.
- The task-list filter (`/`) swaps the bar row vim-style while typing;
the bar is mouse-selectable (drag to highlight + copy) and always
visible, including fullscreen-pane mode.
- The ` NX ` badge (run-state colored) and the run title stay at the
top-left of the task list in a minimal form; both columns keep
bottom-aligned scrollbars.

**Vim-style pane search**: `/` in a non-interactive pane searches the
full scrollback (case-insensitive, wrap-aware) with incremental jumping
while typing; Enter confirms into `n`/`N` navigation with wrap-around;
Esc cancels/clears. Matches highlight reverse-video with the current
match on a warning-colored background, and the bar shows `/query 2/5
(n/N)` while a confirmed search is active.

**State consolidation (started)**: `TuiState` is now the single owner of
the cloud message/link, filter text, and perf-report flag — the
`TasksList` mirrors and the `UpdateCloudMessage`/`UpdateCloudLink`
actions are deleted, and filter persistence across TUI mode switches is
automatic. Remaining mirrors (task statuses/timings, focus, pinned
tasks) are named follow-ups.

## Related Issue(s)


[NXC-4610](https://linear.app/nxdev/issue/NXC-4610/tui-full-width-status-bar-and-vim-style-terminal-pane-search)

<!-- polygraph-session-start -->
---
[View session information
↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/TUI-Status-Bar-Development-11a216a4)
<!-- polygraph-session-end -->