Skip to content

feat(core): support filtered array-shape targetDefaults with projects and source#35340

Draft
AgentEnder wants to merge 11 commits intonextfrom
claude/target-defaults-filtering-oQfo2
Draft

feat(core): support filtered array-shape targetDefaults with projects and source#35340
AgentEnder wants to merge 11 commits intonextfrom
claude/target-defaults-filtering-oQfo2

Conversation

@AgentEnder
Copy link
Copy Markdown
Member

@AgentEnder AgentEnder commented Apr 18, 2026

Adds a new array-shaped targetDefaults in nx.json that supports filtering a default's applicability by project set (projects) and/or the plugin that originated the target (source). This lets polyglot workspaces give different defaults to e.g. @nx/vite:test and @nx/jest:test, or scope defaults to a subset of projects via tag: / glob / negation patterns.

Matcher uses a specificity ladder so the most specific entry wins: target+projects+source > target+projects > target+source > target alone. Exact target matches beat glob matches within a tier; ties go to the later array index.

Legacy record-shape targetDefaults is migrated automatically by the new 23-0-0-convert-target-defaults-to-array migration. Devkit helpers keep tolerating both shapes so newer devkit versions working against older nx installs continue to function.

Includes:

  • New TargetDefaultEntry / TargetDefaultsRecord types
  • findBestTargetDefault matcher + wiring through createTargetDefaultsResults using source-map attribution from the target's executor/command key (the authoring plugin, not the last writer)
  • Array-only JSON schema for nx.json
  • upsertTargetDefault devkit helper with auto-upgrade from record->array when filters are requested
  • Migration + tests
  • Updated direct writers in init, workspace, react, angular, cypress, eslint, playwright, and workspace generators
  • Docs update for nx.json reference

Current Behavior

Expected Behavior

Related Issue(s)

Fixes #

… and source

Adds a new array-shaped `targetDefaults` in nx.json that supports filtering
a default's applicability by project set (`projects`) and/or the plugin
that originated the target (`source`). This lets polyglot workspaces give
different defaults to e.g. `@nx/vite:test` and `@nx/jest:test`, or scope
defaults to a subset of projects via `tag:` / glob / negation patterns.

Matcher uses a specificity ladder so the most specific entry wins:
target+projects+source > target+projects > target+source > target alone.
Exact target matches beat glob matches within a tier; ties go to the later
array index.

Legacy record-shape `targetDefaults` is migrated automatically by the new
`23-0-0-convert-target-defaults-to-array` migration. Devkit helpers keep
tolerating both shapes so newer devkit versions working against older
`nx` installs continue to function.

Includes:
- New `TargetDefaultEntry` / `TargetDefaultsRecord` types
- `findBestTargetDefault` matcher + wiring through `createTargetDefaultsResults`
  using source-map attribution from the target's `executor`/`command` key
  (the authoring plugin, not the last writer)
- Array-only JSON schema for nx.json
- `upsertTargetDefault` devkit helper with auto-upgrade from record->array
  when filters are requested
- Migration + tests
- Updated direct writers in init, workspace, react, angular, cypress,
  eslint, playwright, and workspace generators
- Docs update for nx.json reference
@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud bot commented Apr 18, 2026

View your CI Pipeline Execution ↗ for commit 2ac153a

Command Status Duration Result
nx affected --targets=lint,test,build,e2e,e2e-c... ✅ Succeeded 16m 20s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 2s View ↗
nx-cloud record -- pnpm nx-cloud conformance:check ✅ Succeeded 19s View ↗
nx build workspace-plugin ✅ Succeeded <1s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded 19s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 15s View ↗

☁️ Nx Cloud last updated this comment at 2026-04-20 19:09:30 UTC

nx-cloud[bot]

This comment was marked as outdated.

nx-cloud bot and others added 2 commits April 18, 2026 14:13
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
When a targetDefaults entry sets `executor`, it now matches two ways:

- Filter match: `target.executor === defaults.executor` bumps specificity
  from tier 1 (target-only) to tier 2 (target + executor).
- Injection match: if the target has neither an executor nor a command,
  the entry still matches at tier 1 and the executor gets injected via
  the normal merge — no specificity bump.
- Mismatch: target has a different executor (or a command but no
  executor) drops the entry.

Specificity is now a whole-number 1-5 scale:

1. target (or target + executor injection)
2. target + executor match
3. target + source
4. target + projects
5. target + projects + source

`findBestTargetDefault` takes the target's current command as an extra
arg so the matcher can distinguish "bare target" from "command target"
cases. The rest of the wiring (`readAndPrepareTargetDefaults`,
`buildSyntheticTargetForRoot`) threads both executor and command through.
nx-cloud[bot]

This comment was marked as outdated.

nx core still reads the legacy record-shape `targetDefaults` via
`normalizeTargetDefaults`, but now emits a one-time warning that points
users at `nx repair` (which re-runs the migration that converts the
record to the new array shape). The warning only fires on first
encounter per process so routine command runs aren't spammed.

Also routes `getRunnerOptions` in `run-command.ts` through
`normalizeTargetDefaults` so the warning is centralized and the record
vs. array branch lives in one place.
@AgentEnder AgentEnder changed the title feat(core): support filtered array-shape targetDefaults with projects… feat(core): support filtered array-shape targetDefaults with projects and source Apr 18, 2026
…h deprecated

Wraps `targetDefaults` in a `oneOf` so editors accept both the new array
form and the legacy record form. The record branch is marked
`deprecated: true` so IDEs/JSON schema tooling surfaces a deprecation
hint. The description nudges users toward `nx repair` to convert.
nx-cloud[bot]

This comment was marked as outdated.

nx-cloud bot and others added 3 commits April 19, 2026 03:08
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
…the union

Now that `TargetDefaults` is `TargetDefaultEntry[] | TargetDefaultsRecord`,
direct dot/bracket access like `nxJson.targetDefaults['build']` no longer
typechecks, and a handful of generator implementations were still writing
record-shape mutations directly. This patch sweeps the remaining call
sites:

Generator lib code now routes writes through `upsertTargetDefault`
(preserving `??=`-style "only if unset" semantics by reading the
existing entry first):

- `packages/eslint/src/generators/init/init.ts`
- `packages/next/src/generators/custom-server/custom-server.ts`
- `packages/vitest/src/generators/configuration/configuration.ts`

Generator-output specs now resolve the entry from either shape via a
small local helper or `Array.isArray` narrow:

- eslint init/workspace-rules-project specs
- cypress init spec
- react application spec
- angular setup-ssr spec
- vite vitest generator spec

Historical migration specs (which run against pre-array-shape nx.json
fixtures) get a narrowed `LegacyNxJson` alias so the existing record
accesses keep typechecking unchanged. This applies to:

- cypress update-21-0-0 (remove-tsconfig-and-copy-files-options-from-cypress-executor)
- angular update-17-1-0 (browser-target-to-build-target,
  replace-nguniversal-builders), update-17-2-0
  (rename-webpack-dev-server), update-20-2-0
  (remove-tailwind-config-from-ng-packagr-executors)
- js update-22-0-0 (remove-external-options-from-js-executors)
- nx update-16-0-0 (update-depends-on-to-tokens)

Vite/vitest migration specs that read post-migration record-shape state
use a small `td(json)` cast helper for the same purpose.
…ining e2e tests

Mirrors the dual-shape pattern from the upstream `run.test.ts` fix.
Several e2e tests still mutated `nxJson.targetDefaults` as a record
(`json.targetDefaults.X = ...` or `??=`) which silently fails when nx.json
is in the new array shape — the assignment writes a non-numeric property
to the array and the matcher never sees it, so the test's expected
default never applies.

Updated:
- e2e/nx/src/nxw.test.ts (echo target)
- e2e/js/src/js-generators.ts (build target dependsOn merge)
- e2e/angular/src/projects-buildable-libs.test.ts (webpack-browser /
  browser-esbuild executor defaults)

Each site now branches on `Array.isArray(targetDefaults)` and pushes/
finds-by-tuple in the array shape, otherwise falls back to the legacy
record write.
Copy link
Copy Markdown
Contributor

@nx-cloud nx-cloud bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

At least one additional CI pipeline execution has run since the conclusion below was written and it may no longer be applicable.

Nx Cloud is proposing a fix for your failed CI:

We address two code_change failures introduced by this PR. The incorrect as (string | unknown)[] cast in vitest/configuration.ts is removed — existing?.dependsOn is already typed as (TargetDependencyConfig | string)[] | undefined from Partial<TargetConfiguration>, so no cast is needed, and the spread now satisfies the expected type. Additionally, prettier --write is applied to nx-json.mdoc to resolve the format check failure caused by the documentation changes added in this PR.

Tip

We verified this fix by re-running vitest:build-base, astro-docs:format.

Warning

The suggested diff is too large to display here, but you can view it on Nx Cloud ↗


Apply fix via Nx Cloud  Reject fix via Nx Cloud


Or Apply changes locally with:

npx nx-cloud apply-locally Hu9r-m6jm

Apply fix locally with your editor ↗   View interactive diff ↗



🎓 Learn more about Self-Healing CI on nx.dev

claude and others added 3 commits April 19, 2026 05:41
…nfig generator

`existing?.dependsOn` is already typed as
`(TargetDependencyConfig | string)[] | undefined` via
`Partial<TargetConfiguration>`, so the `as (string | unknown)[]` cast
was an incorrect widening that the spread no longer needed. Remove it.

Also runs prettier on `astro-docs/src/content/docs/reference/nx-json.mdoc`
to satisfy the format check.
Generators (jest init, jest configuration) now route writes through
upsertTargetDefault so they respect whichever shape nx.json is in, and
the two legacy jest migrations (update-21-0-0, update-21-3-0) branch on
Array.isArray to mutate both the record and the array shape.

Specs for all four files are rewritten against the array shape: seed
targetDefaults as entries, assert on the entry list directly.
The deprecation warning for record-shape `targetDefaults` in nx.json
went through `output.warn`, which writes to stdout. That corrupted
structured stdout — `nx show project --json` emitted the ` NX  nx.json
uses the legacy record-shape…` header before the JSON payload, so
JSON.parse on the result failed with `Unexpected token 'N'`, breaking
the spread E2E suite.

Write the warning directly to `process.stderr` so it stays visible in
a TTY but never contaminates `--json` / machine-readable stdout. Add a
unit assertion that the warning never hits stdout so a future
regression fails at unit-test speed rather than only in e2e.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants