feat(core): support filtered array-shape targetDefaults with projects and source#35340
feat(core): support filtered array-shape targetDefaults with projects and source#35340AgentEnder wants to merge 11 commits intonextfrom
Conversation
… 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
|
View your CI Pipeline Execution ↗ for commit 2ac153a
☁️ Nx Cloud last updated this comment at |
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 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.
…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.
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.
There was a problem hiding this comment.
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 ↗
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
…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.
Adds a new array-shaped
targetDefaultsin 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:testand@nx/jest:test, or scope defaults to a subset of projects viatag:/ 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
targetDefaultsis migrated automatically by the new23-0-0-convert-target-defaults-to-arraymigration. Devkit helpers keep tolerating both shapes so newer devkit versions working against oldernxinstalls continue to function.Includes:
TargetDefaultEntry/TargetDefaultsRecordtypesfindBestTargetDefaultmatcher + wiring throughcreateTargetDefaultsResultsusing source-map attribution from the target'sexecutor/commandkey (the authoring plugin, not the last writer)upsertTargetDefaultdevkit helper with auto-upgrade from record->array when filters are requestedCurrent Behavior
Expected Behavior
Related Issue(s)
Fixes #