nx 23.1.0 → 23.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/dist/bin/nx.js +2 -1
  2. package/dist/release/changelog-renderer/index.js +4 -5
  3. package/dist/release/index.d.ts +1 -1
  4. package/dist/src/ai/configure-ai-agents-disclaimer.d.ts +5 -0
  5. package/dist/src/ai/configure-ai-agents-disclaimer.js +31 -0
  6. package/dist/src/analytics/analytics.d.ts +6 -0
  7. package/dist/src/analytics/analytics.js +16 -8
  8. package/dist/src/analytics/index.d.ts +1 -1
  9. package/dist/src/analytics/index.js +2 -1
  10. package/dist/src/command-line/add/add.js +13 -1
  11. package/dist/src/command-line/affected/affected.js +1 -0
  12. package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +2 -2
  13. package/dist/src/command-line/graph/graph.d.ts +1 -0
  14. package/dist/src/command-line/graph/graph.js +15 -6
  15. package/dist/src/command-line/import/import.js +2 -3
  16. package/dist/src/command-line/init/ai-agent-prompts.d.ts +1 -1
  17. package/dist/src/command-line/init/ai-agent-prompts.js +7 -6
  18. package/dist/src/command-line/init/command-object.js +10 -2
  19. package/dist/src/command-line/init/configure-plugins.js +3 -2
  20. package/dist/src/command-line/init/implementation/add-nx-to-monorepo.js +4 -2
  21. package/dist/src/command-line/init/implementation/add-nx-to-nest.js +4 -3
  22. package/dist/src/command-line/init/implementation/add-nx-to-npm-repo.js +4 -3
  23. package/dist/src/command-line/init/implementation/add-nx-to-turborepo.js +4 -3
  24. package/dist/src/command-line/init/implementation/angular/index.js +4 -2
  25. package/dist/src/command-line/init/implementation/angular/integrated-workspace.js +2 -5
  26. package/dist/src/command-line/init/implementation/angular/legacy-angular-versions.js +3 -0
  27. package/dist/src/command-line/init/implementation/dot-nx/add-nx-scripts.js +4 -1
  28. package/dist/src/command-line/init/implementation/utils.d.ts +3 -3
  29. package/dist/src/command-line/init/implementation/utils.js +22 -3
  30. package/dist/src/command-line/init/init-v2.js +4 -5
  31. package/dist/src/command-line/migrate/execute-migration.d.ts +72 -0
  32. package/dist/src/command-line/migrate/execute-migration.js +389 -0
  33. package/dist/src/command-line/migrate/migrate-ui-api.js +17 -12
  34. package/dist/src/command-line/migrate/migrate.d.ts +5 -45
  35. package/dist/src/command-line/migrate/migrate.js +122 -402
  36. package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +1 -1
  37. package/dist/src/command-line/nx-cloud/connect/view-logs.js +2 -0
  38. package/dist/src/command-line/release/changelog.js +5 -0
  39. package/dist/src/command-line/release/index.d.ts +1 -1
  40. package/dist/src/command-line/release/utils/git.js +7 -3
  41. package/dist/src/command-line/release/utils/release-graph.d.ts +19 -2
  42. package/dist/src/command-line/release/utils/release-graph.js +71 -19
  43. package/dist/src/command-line/release/utils/remote-release-clients/github.js +7 -1
  44. package/dist/src/command-line/release/utils/remote-release-clients/gitlab.js +1 -1
  45. package/dist/src/command-line/release/utils/shared.d.ts +2 -2
  46. package/dist/src/command-line/release/version/release-group-processor.d.ts +24 -0
  47. package/dist/src/command-line/release/version/release-group-processor.js +80 -6
  48. package/dist/src/command-line/release/version/version-actions.d.ts +27 -2
  49. package/dist/src/command-line/release/version/version-actions.js +9 -1
  50. package/dist/src/command-line/release/version.js +9 -5
  51. package/dist/src/command-line/reset/reset.js +6 -1
  52. package/dist/src/command-line/run/run-one.js +1 -0
  53. package/dist/src/command-line/run-many/run-many.js +1 -0
  54. package/dist/src/command-line/show/show-target/info.js +11 -2
  55. package/dist/src/command-line/show/show-target/inputs.js +50 -153
  56. package/dist/src/command-line/show/show-target/outputs.js +48 -173
  57. package/dist/src/command-line/show/show-target/utils.d.ts +28 -0
  58. package/dist/src/command-line/show/show-target/utils.js +83 -3
  59. package/dist/src/command-line/watch/watch.js +13 -0
  60. package/dist/src/command-line/yargs-utils/shared-options.d.ts +1 -1
  61. package/dist/src/commands-runner/create-command-graph.js +5 -0
  62. package/dist/src/config/nx-json.d.ts +5 -3
  63. package/dist/src/core/graph/main.js +1 -1
  64. package/dist/src/core/graph/styles.js +1 -1
  65. package/dist/src/daemon/client/client.d.ts +52 -3
  66. package/dist/src/daemon/client/client.js +177 -55
  67. package/dist/src/daemon/client/daemon-environment.d.ts +17 -0
  68. package/dist/src/daemon/client/daemon-environment.js +110 -2
  69. package/dist/src/daemon/client/daemon-socket-messenger.js +4 -0
  70. package/dist/src/daemon/message-types/daemon-message.d.ts +32 -0
  71. package/dist/src/daemon/message-types/daemon-message.js +25 -0
  72. package/dist/src/daemon/server/file-watching/file-watcher-sockets.d.ts +5 -0
  73. package/dist/src/daemon/server/file-watching/file-watcher-sockets.js +16 -0
  74. package/dist/src/daemon/server/handle-tasks-execution-hooks.d.ts +1 -1
  75. package/dist/src/daemon/server/latest-nx.js +5 -2
  76. package/dist/src/daemon/server/server.js +31 -4
  77. package/dist/src/daemon/server/shutdown-utils.d.ts +5 -0
  78. package/dist/src/daemon/server/shutdown-utils.js +11 -4
  79. package/dist/src/daemon/socket-utils.d.ts +1 -0
  80. package/dist/src/daemon/socket-utils.js +24 -3
  81. package/dist/src/daemon/tmp-dir.d.ts +43 -3
  82. package/dist/src/daemon/tmp-dir.js +437 -15
  83. package/dist/src/devkit-internals.d.ts +96 -3
  84. package/dist/src/devkit-internals.js +200 -4
  85. package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.d.ts +5 -0
  86. package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.js +9 -2
  87. package/dist/src/generators/tree.d.ts +4 -2
  88. package/dist/src/generators/utils/nx-json.js +13 -3
  89. package/dist/src/hasher/check-task-files.d.ts +86 -0
  90. package/dist/src/hasher/check-task-files.js +378 -0
  91. package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.d.ts +12 -0
  92. package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.js +250 -0
  93. package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.md +50 -0
  94. package/dist/src/native/index.d.ts +31 -2
  95. package/dist/src/native/index.js +71 -28
  96. package/dist/src/native/native-bindings.js +3 -0
  97. package/dist/src/native/native-file-cache-location.d.ts +17 -1
  98. package/dist/src/native/native-file-cache-location.js +86 -12
  99. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  100. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  101. package/dist/src/nx-cloud/utilities/get-cloud-options.js +5 -2
  102. package/dist/src/nx-cloud/utilities/onboarding.js +5 -2
  103. package/dist/src/plugins/js/lock-file/bun-parser.js +43 -10
  104. package/dist/src/plugins/js/lock-file/npm-parser.js +71 -27
  105. package/dist/src/plugins/js/lock-file/pnpm-parser.js +110 -17
  106. package/dist/src/plugins/js/lock-file/project-graph-pruning.js +15 -9
  107. package/dist/src/plugins/js/lock-file/utils/pnpm-normalizer.js +3 -0
  108. package/dist/src/plugins/js/lock-file/yarn-parser.js +32 -5
  109. package/dist/src/plugins/js/project-graph/affected/npm-packages.js +51 -20
  110. package/dist/src/plugins/js/project-graph/build-dependencies/target-project-locator.js +14 -4
  111. package/dist/src/plugins/js/utils/register.d.ts +18 -0
  112. package/dist/src/plugins/js/utils/register.js +81 -0
  113. package/dist/src/plugins/js/utils/typescript.d.ts +9 -0
  114. package/dist/src/plugins/js/utils/typescript.js +15 -0
  115. package/dist/src/project-graph/affected/affected-project-graph-models.d.ts +1 -1
  116. package/dist/src/project-graph/affected/affected-project-graph.d.ts +1 -1
  117. package/dist/src/project-graph/affected/affected-project-graph.js +2 -2
  118. package/dist/src/project-graph/affected/locators/project-glob-changes.js +5 -2
  119. package/dist/src/project-graph/build-project-graph.js +7 -1
  120. package/dist/src/project-graph/error-types.d.ts +12 -1
  121. package/dist/src/project-graph/error-types.js +68 -3
  122. package/dist/src/project-graph/file-utils.js +6 -1
  123. package/dist/src/project-graph/plugins/isolation/isolated-plugin.d.ts +1 -0
  124. package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +22 -1
  125. package/dist/src/project-graph/plugins/isolation/messaging.d.ts +16 -3
  126. package/dist/src/project-graph/plugins/isolation/messaging.js +3 -0
  127. package/dist/src/project-graph/plugins/isolation/plugin-worker.js +58 -8
  128. package/dist/src/project-graph/plugins/loaded-nx-plugin.js +1 -0
  129. package/dist/src/project-graph/plugins/transpiler.js +4 -0
  130. package/dist/src/project-graph/project-graph.js +23 -0
  131. package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.d.ts +10 -12
  132. package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.js +93 -63
  133. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.d.ts +5 -8
  134. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +16 -15
  135. package/dist/src/project-graph/utils/project-configuration/source-maps.d.ts +12 -5
  136. package/dist/src/project-graph/utils/project-configuration/source-maps.js +21 -7
  137. package/dist/src/project-graph/utils/project-configuration/target-defaults.d.ts +1 -1
  138. package/dist/src/project-graph/utils/project-configuration/target-defaults.js +26 -27
  139. package/dist/src/project-graph/utils/project-configuration/target-merging.js +50 -8
  140. package/dist/src/project-graph/utils/project-configuration/target-normalization.js +149 -6
  141. package/dist/src/project-graph/utils/project-configuration/utils.d.ts +15 -0
  142. package/dist/src/project-graph/utils/project-configuration/utils.js +42 -6
  143. package/dist/src/project-graph/utils/project-configuration-utils.d.ts +15 -12
  144. package/dist/src/project-graph/utils/project-configuration-utils.js +79 -101
  145. package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +11 -4
  146. package/dist/src/tasks-runner/life-cycles/performance-analysis.js +15 -6
  147. package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +5 -4
  148. package/dist/src/tasks-runner/life-cycles/performance-report.d.ts +18 -8
  149. package/dist/src/tasks-runner/life-cycles/performance-report.js +66 -29
  150. package/dist/src/tasks-runner/run-command.js +4 -2
  151. package/dist/src/tasks-runner/task-env.d.ts +9 -0
  152. package/dist/src/tasks-runner/task-env.js +31 -4
  153. package/dist/src/tasks-runner/task-orchestrator.d.ts +4 -1
  154. package/dist/src/tasks-runner/task-orchestrator.js +23 -10
  155. package/dist/src/tasks-runner/utils.js +2 -6
  156. package/dist/src/utils/acknowledge-build-scripts.d.ts +13 -0
  157. package/dist/src/utils/acknowledge-build-scripts.js +102 -0
  158. package/dist/src/utils/analytics-prompt.d.ts +0 -6
  159. package/dist/src/utils/analytics-prompt.js +0 -51
  160. package/dist/src/utils/assert-workspace-validity.js +7 -2
  161. package/dist/src/utils/catalog/manager-utils.d.ts +1 -2
  162. package/dist/src/utils/catalog/manager-utils.js +22 -5
  163. package/dist/src/utils/catalog/pnpm-manager.d.ts +1 -0
  164. package/dist/src/utils/catalog/pnpm-manager.js +3 -15
  165. package/dist/src/utils/catalog/yarn-manager.d.ts +1 -0
  166. package/dist/src/utils/catalog/yarn-manager.js +3 -15
  167. package/dist/src/utils/command-line-utils.js +30 -20
  168. package/dist/src/utils/fileutils.d.ts +5 -0
  169. package/dist/src/utils/fileutils.js +7 -2
  170. package/dist/src/utils/git-revision.d.ts +12 -0
  171. package/dist/src/utils/git-revision.js +25 -0
  172. package/dist/src/utils/git-utils.d.ts +16 -3
  173. package/dist/src/utils/git-utils.js +146 -52
  174. package/dist/src/utils/git-worktrees.d.ts +30 -0
  175. package/dist/src/utils/git-worktrees.js +212 -0
  176. package/dist/src/utils/ignore.d.ts +128 -1
  177. package/dist/src/utils/ignore.js +182 -12
  178. package/dist/src/utils/long-running-target.d.ts +10 -0
  179. package/dist/src/utils/long-running-target.js +19 -0
  180. package/dist/src/utils/min-release-age/behavior/bun.js +7 -11
  181. package/dist/src/utils/min-release-age/behavior/npm.js +5 -2
  182. package/dist/src/utils/min-release-age/behavior/pnpm.js +9 -7
  183. package/dist/src/utils/min-release-age/packument.js +1 -1
  184. package/dist/src/utils/nx-tmp-dir.d.ts +18 -0
  185. package/dist/src/utils/nx-tmp-dir.js +40 -0
  186. package/dist/src/utils/owned-private-dir.d.ts +132 -0
  187. package/dist/src/utils/owned-private-dir.js +323 -0
  188. package/dist/src/utils/package-json.js +11 -7
  189. package/dist/src/utils/package-manager-config/bunfig.d.ts +14 -0
  190. package/dist/src/utils/package-manager-config/bunfig.js +49 -0
  191. package/dist/src/utils/package-manager-config/npmrc.d.ts +23 -0
  192. package/dist/src/utils/package-manager-config/npmrc.js +124 -0
  193. package/dist/src/utils/package-manager-config/pnpm-config.d.ts +17 -0
  194. package/dist/src/utils/package-manager-config/pnpm-config.js +60 -0
  195. package/dist/src/utils/package-manager.d.ts +23 -2
  196. package/dist/src/utils/package-manager.js +264 -43
  197. package/dist/src/utils/provenance.js +18 -16
  198. package/dist/src/utils/registry-config/bun.d.ts +2 -0
  199. package/dist/src/utils/registry-config/bun.js +257 -0
  200. package/dist/src/utils/registry-config/index.d.ts +13 -0
  201. package/dist/src/utils/registry-config/index.js +102 -0
  202. package/dist/src/utils/registry-config/pnpm.d.ts +2 -0
  203. package/dist/src/utils/registry-config/pnpm.js +1551 -0
  204. package/dist/src/utils/registry-config/utils.d.ts +195 -0
  205. package/dist/src/utils/registry-config/utils.js +496 -0
  206. package/dist/src/utils/registry-config/yarn-berry.d.ts +2 -0
  207. package/dist/src/utils/registry-config/yarn-berry.js +612 -0
  208. package/dist/src/utils/registry-config/yarn-classic.d.ts +2 -0
  209. package/dist/src/utils/registry-config/yarn-classic.js +1001 -0
  210. package/dist/src/utils/safe-spawn.d.ts +24 -0
  211. package/dist/src/utils/safe-spawn.js +104 -0
  212. package/dist/src/utils/shell-quoting.d.ts +15 -0
  213. package/dist/src/utils/shell-quoting.js +35 -0
  214. package/dist/src/utils/wait-for-socket-connection.d.ts +11 -0
  215. package/dist/src/utils/wait-for-socket-connection.js +14 -8
  216. package/dist/src/utils/workspace-id.d.ts +20 -0
  217. package/dist/src/utils/workspace-id.js +54 -0
  218. package/migrations.json +13 -1
  219. package/package.json +22 -17
  220. package/dist/src/migrations/update-17-3-0/nx-release-path.d.ts +0 -3
  221. package/dist/src/migrations/update-17-3-0/nx-release-path.js +0 -47
  222. package/dist/src/utils/min-release-age/npmrc.d.ts +0 -15
  223. package/dist/src/utils/min-release-age/npmrc.js +0 -45
@@ -21,7 +21,7 @@ const utils_1 = require("./utils");
21
21
  * fly. That's all the matcher needs; the full target merge happens
22
22
  * downstream in the real merge.
23
23
  */
24
- function createTargetDefaultsResults(specifiedPluginRootMap, defaultPluginRootMap, nxJsonConfiguration, specifiedSourceMaps, defaultSourceMaps) {
24
+ function createTargetDefaultsResults(specifiedPluginRootMap, defaultPluginRootMap, nxJsonConfiguration, specifiedSourceMaps) {
25
25
  const targetDefaultsConfig = nxJsonConfiguration.targetDefaults;
26
26
  if (!targetDefaultsConfig) {
27
27
  return [];
@@ -65,7 +65,7 @@ function createTargetDefaultsResults(specifiedPluginRootMap, defaultPluginRootMa
65
65
  if (!effective)
66
66
  continue;
67
67
  const sourcePlugin = needsSourcePlugin
68
- ? resolveSourcePlugin(root, targetName, specifiedSourceMaps, defaultSourceMaps)
68
+ ? resolveSourcePlugin(root, targetName, defaultTargets[targetName], specifiedSourceMaps)
69
69
  : undefined;
70
70
  const syntheticTargets = buildSyntheticTargetsForRoot(targetName, root, effective, targetDefaults, projectName, projectNode, sourcePlugin);
71
71
  for (const { key, index, target } of syntheticTargets) {
@@ -372,34 +372,33 @@ function normalizeTargetDefaultValue(value) {
372
372
  // A bare config object is functionally a one-element array.
373
373
  return [value ?? {}];
374
374
  }
375
- function resolveSourcePlugin(root, targetName, specifiedSourceMaps, defaultSourceMaps) {
376
- // Default-plugin attribution overrides specified-plugin attribution in the
377
- // merge, so check it first. The executor/command keys carry the plugin that
378
- // *created* the target, which is what `filter.plugin` ("targets originated by
379
- // X") means. The top-level `targets.<name>` node key is deliberately NOT used
380
- // as a fallback: it tracks the last writer, so a later plugin augmenting the
381
- // target would mis-attribute it. The trade-off is that a target with neither
382
- // an executor nor a command (a rare, non-runnable shape) resolves to no
383
- // source plugin and won't match a `filter.plugin` default — accepted, since
384
- // every runnable target carries one of these keys.
385
- const executorKey = `${(0, source_maps_1.targetSourceMapKey)(targetName)}.executor`;
386
- const commandKey = `${(0, source_maps_1.targetSourceMapKey)(targetName)}.command`;
387
- const candidates = [
388
- pluginFromSourceMap(defaultSourceMaps, root, executorKey),
389
- pluginFromSourceMap(defaultSourceMaps, root, commandKey),
390
- pluginFromSourceMap(specifiedSourceMaps, root, executorKey),
391
- pluginFromSourceMap(specifiedSourceMaps, root, commandKey),
392
- ];
393
- for (const candidate of candidates) {
394
- if (candidate && candidate !== source_maps_1.TARGET_DEFAULTS_PLUGIN_NAME)
395
- return candidate;
375
+ function resolveSourcePlugin(root, targetName, defaultTarget, specifiedSourceMaps) {
376
+ // `filter.plugin` ("targets originated by X") can only name a plugin from
377
+ // nx.json's `plugins` — the specified set. When the default layer authors
378
+ // the target's identity (its merged config carries an executor or command,
379
+ // which the merge lets win), the originator is a default plugin and the
380
+ // target has no matchable source plugin; no source maps are needed to see
381
+ // that. Otherwise the specified layer's executor/command attribution names
382
+ // the originator. The top-level `targets.<name>` node key is deliberately
383
+ // NOT used as a fallback: it tracks ownership, so a later plugin augmenting
384
+ // the target would mis-attribute it. The trade-off is that a target with
385
+ // neither an executor nor a command (a rare, non-runnable shape) resolves to
386
+ // no source plugin and won't match a `filter.plugin` default — accepted,
387
+ // since every runnable target carries one of these keys.
388
+ if (defaultTarget &&
389
+ (defaultTarget.executor !== undefined ||
390
+ defaultTarget.command !== undefined)) {
391
+ return undefined;
392
+ }
393
+ const sourceMap = specifiedSourceMaps?.[root];
394
+ for (const identityKey of ['executor', 'command']) {
395
+ const plugin = sourceMap?.[`${(0, source_maps_1.targetSourceMapKey)(targetName)}.${identityKey}`]?.[1];
396
+ if (plugin && plugin !== source_maps_1.TARGET_DEFAULTS_PLUGIN_NAME) {
397
+ return plugin;
398
+ }
396
399
  }
397
400
  return undefined;
398
401
  }
399
- function pluginFromSourceMap(maps, root, key) {
400
- const entry = maps?.[root]?.[key];
401
- return entry?.[1];
402
- }
403
402
  // Builds a name → MatcherProjectNode view for `findMatchingProjects` to
404
403
  // consult, across both layered rootMaps. Tags are unioned across layers. A
405
404
  // full `mergeProjectConfigurationIntoRootMap` would be overkill — the matcher
@@ -170,6 +170,11 @@ function mergeConfigurations(newConfigurations, baseConfigurations, projectConfi
170
170
  // Before '...': base wins for shared names. Keep base's source-map
171
171
  // entries when it owns the config.
172
172
  if (baseHasConfig) {
173
+ // Base wins, so the incoming config is dropped without reaching
174
+ // `mergeConfigurationValue`. Validate its nested spread here so an
175
+ // integer-like-key ambiguity throws regardless of which side owns
176
+ // the config name (mirrors the base-independent target-level check).
177
+ (0, utils_1.assertNoIntegerLikeSpreadKey)(newConfigurations?.[configName], configIdentifier ? `Object at "${configIdentifier}"` : 'Object');
173
178
  mergedConfigurations[configName] = baseConfigurations[configName];
174
179
  }
175
180
  else {
@@ -282,8 +287,11 @@ function mergeTargetConfigurations(target, baseTarget, projectConfigSourceMap, s
282
287
  ? new Set(targetKeys.slice(0, spreadPosInTarget))
283
288
  : new Set();
284
289
  // Integer-like keys get hoisted to targetKeys[0], making their position
285
- // relative to '...' unrecoverable.
286
- if (hasSpread &&
290
+ // relative to '...' unrecoverable. This is a property of the authored
291
+ // config, not of the base, so it throws regardless of compatibility —
292
+ // which also keeps the error identical between the target-defaults staging
293
+ // merge and the real merge, whose bases differ.
294
+ if (spreadPosInTarget >= 0 &&
287
295
  targetKeys[0] &&
288
296
  utils_1.INTEGER_LIKE_KEY_PATTERN.test(targetKeys[0])) {
289
297
  throw new utils_1.IntegerLikeSpreadKeyError(targetKeys[0], targetIdentifier ? `Target at "${targetIdentifier}"` : 'Target');
@@ -304,6 +312,13 @@ function mergeTargetConfigurations(target, baseTarget, projectConfigSourceMap, s
304
312
  return;
305
313
  if (hasSpread && keysBeforeSpread.has(key)) {
306
314
  // Before '...': base wins; fall through to target only if base lacks it.
315
+ // When base wins, the incoming `target[key]` is dropped without ever
316
+ // reaching `getMergeValueResult`, so validate its nested spread here —
317
+ // the integer-like-key ambiguity is a property of the authored value,
318
+ // not of which side owns the key, and must throw either way.
319
+ (0, utils_1.assertNoIntegerLikeSpreadKey)(target[key], projectConfigSourceMap
320
+ ? `Object at "${targetIdentifier}.${key}"`
321
+ : 'Object');
307
322
  result[key] =
308
323
  key in mergeBase
309
324
  ? mergeBase[key]
@@ -381,13 +396,20 @@ function mergeTargetConfigurations(target, baseTarget, projectConfigSourceMap, s
381
396
  processKey(key);
382
397
  }
383
398
  }
384
- // Update source map once after loop. Real plugins win last, but a target
385
- // default — which only stamps fields onto an existing target and never
386
- // authors its existence — must not steal the node key from the plugin that
387
- // introduced the target. An incompatible replace clears these keys above, so
388
- // a replacing real plugin still re-owns the node.
399
+ // Update the node key once after the loop. Ownership follows identity: this
400
+ // merge claims `targets.<name>` only when it changed the target's identity
401
+ // (a new/different executor or command, or an incompatible replace — whose
402
+ // key purge above empties the slot anyway). A plugin that only layers fields
403
+ // onto an existing target leaves the node with its creator; weak
404
+ // target-defaults stamps are always reclaimable.
389
405
  if (projectConfigSourceMap) {
390
- (0, source_maps_1.recordTargetIdentitySourceMapInfo)(projectConfigSourceMap, targetIdentifier, sourceInformation);
406
+ const identityChanged = !isCompatible ||
407
+ (target.executor !== undefined &&
408
+ target.executor !== baseTarget?.executor) ||
409
+ (target.command !== undefined &&
410
+ target.command !== baseTarget?.command) ||
411
+ suppliesNewOptionsIdentity(baseTarget, target);
412
+ (0, source_maps_1.recordTargetIdentitySourceMapInfo)(projectConfigSourceMap, targetIdentifier, sourceInformation, identityChanged);
391
413
  }
392
414
  // merge options if there are any
393
415
  // if the targets aren't compatible, we simply discard the old options during the merge
@@ -443,6 +465,26 @@ function isCompatibleTarget(a, b) {
443
465
  }
444
466
  return true;
445
467
  }
468
+ /**
469
+ * Run-commands and run-script targets carry their runnable identity in
470
+ * `options` (see {@link isCompatibleTarget}). A layer that supplies that
471
+ * identity where the base had none changed what the target runs — the same
472
+ * identity change as setting an executor on a target that had none.
473
+ */
474
+ function suppliesNewOptionsIdentity(baseTarget, target) {
475
+ const executor = target.executor ?? baseTarget?.executor;
476
+ if (executor === 'nx:run-commands') {
477
+ const baseCommand = baseTarget?.options?.command ??
478
+ baseTarget?.options?.commands?.join(' && ');
479
+ const targetCommand = target.options?.command ?? target.options?.commands?.join(' && ');
480
+ return !!targetCommand && targetCommand !== baseCommand;
481
+ }
482
+ if (executor === 'nx:run-script') {
483
+ const targetScript = target.options?.script;
484
+ return !!targetScript && targetScript !== baseTarget?.options?.script;
485
+ }
486
+ return false;
487
+ }
446
488
  function resolveNxTokensInOptions(object, project, key) {
447
489
  const result = Array.isArray(object) ? [...object] : { ...object };
448
490
  for (let [opt, value] of Object.entries(object ?? {})) {
@@ -5,10 +5,13 @@ exports.normalizeTarget = normalizeTarget;
5
5
  exports.validateAndNormalizeProjectRootMap = validateAndNormalizeProjectRootMap;
6
6
  const executor_utils_1 = require("../../../command-line/run/executor-utils");
7
7
  const fileutils_1 = require("../../../utils/fileutils");
8
+ const long_running_target_1 = require("../../../utils/long-running-target");
9
+ const output_1 = require("../../../utils/output");
8
10
  const to_project_name_1 = require("../../../config/to-project-name");
9
11
  const error_types_1 = require("../../error-types");
10
12
  const target_merging_1 = require("./target-merging");
11
13
  const node_fs_1 = require("node:fs");
14
+ const git_worktrees_1 = require("../../../utils/git-worktrees");
12
15
  const path_1 = require("path");
13
16
  function validateProject(project,
14
17
  // name -> project
@@ -65,15 +68,137 @@ function normalizeTarget(target, project, workspaceRoot, projectsMap, errorMsgKe
65
68
  }
66
69
  return target;
67
70
  }
71
+ // TODO(v24): remove the legacy target-name cache fallback and its warning.
72
+ // Removal needs a second mechanism for plugin-inferred targets, which the
73
+ // accompanying migration cannot reach.
74
+ /**
75
+ * Whether `target` is cacheable only by way of the legacy name-based fallback:
76
+ * the exact target-name key of `targetDefaults` declares `cache: true`, but an
77
+ * executor key won target-default resolution instead, so the merged target never
78
+ * received it.
79
+ *
80
+ * A `true` result means the user's `cache: true` silently lost, so this doubles
81
+ * as the condition for warning them that the name key is being shadowed.
82
+ */
83
+ function isLegacyCachedTarget(targetName, targetDefaults, target) {
84
+ // Resolution already decided `cache`, so the name key isn't shadowed.
85
+ if (target.cache !== undefined) {
86
+ return false;
87
+ }
88
+ if (isLongRunningTarget(targetName, target)) {
89
+ return false;
90
+ }
91
+ // Restricted to shadowing, which is narrower than what pre-23 restored: that
92
+ // derivation matched on target name alone, so a name key dropped as
93
+ // incompatible (its entry declared a foreign executor) was cacheable too.
94
+ // Restoring that as well would mean writing `cache` with no key to name in
95
+ // the warning, and no migration able to retire it. Deliberately not covered.
96
+ if (!findShadowingTargetDefaultKey(targetDefaults, target)) {
97
+ return false;
98
+ }
99
+ return declaresCacheTrue(targetDefaults?.[targetName]);
100
+ }
101
+ /**
102
+ * Whether the name key declares `cache: true` on an entry that always applies.
103
+ *
104
+ * Filters are deliberately not evaluated. They cannot express a pre-23 config
105
+ * (the filtered array shape postdates the behavior being restored), and a
106
+ * filtered entry declaring `cache` may or may not apply to this project — so
107
+ * rather than guess, a filtered `cache` declares the value unknowable and
108
+ * nothing is restored. Among unfiltered entries the last wins, matching the
109
+ * in-key merge order.
110
+ */
111
+ function declaresCacheTrue(value) {
112
+ if (!value) {
113
+ return false;
114
+ }
115
+ const entries = Array.isArray(value) ? value : [value];
116
+ let declared;
117
+ for (const entry of entries) {
118
+ // `nx.json` is hand-edited; a null or scalar entry would throw here.
119
+ if (!entry || typeof entry !== 'object')
120
+ continue;
121
+ if (entry.cache === undefined)
122
+ continue;
123
+ if (entry.filter)
124
+ return false;
125
+ declared = entry.cache;
126
+ }
127
+ return declared === true;
128
+ }
129
+ /**
130
+ * The normalization-time half of the pre-23 `longRunningTask` guard, which kept
131
+ * `cacheableOperations` from ever making these cacheable. Its remaining clause
132
+ * — `task.overrides['watch']` — is a runtime invocation override with no target
133
+ * equivalent, so it has no counterpart here.
134
+ */
135
+ function isLongRunningTarget(targetName, target) {
136
+ return !!target.continuous || (0, long_running_target_1.isLongRunningTargetName)(targetName);
137
+ }
138
+ /**
139
+ * The `targetDefaults` key that beat the target-name key for `target`. Only an
140
+ * executor key can: key precedence puts the exact target name ahead of every
141
+ * glob, so nothing else outranks it. Undefined when the name key lost for
142
+ * another reason (e.g. its entry declared a foreign executor and was dropped as
143
+ * incompatible) — see {@link isLegacyCachedTarget} for why that case is left
144
+ * alone even though pre-23 restored it.
145
+ *
146
+ * `hasOwnProperty` rather than a lookup: an executor named `__proto__` resolves
147
+ * through the prototype chain to a truthy object, which would report a key the
148
+ * user never wrote.
149
+ */
150
+ function findShadowingTargetDefaultKey(targetDefaults, target) {
151
+ return target.executor &&
152
+ targetDefaults &&
153
+ Object.prototype.hasOwnProperty.call(targetDefaults, target.executor)
154
+ ? target.executor
155
+ : undefined;
156
+ }
157
+ /**
158
+ * Emits a single grouped warning for every (shadowing key, target-name key)
159
+ * pair that relied on the deprecated fallback. Grouping matters because the
160
+ * same pair recurs in every affected project — a per-target warning would
161
+ * print hundreds of identical lines in a large workspace.
162
+ */
163
+ function warnAboutLegacyCachedTargets(legacyCacheReads) {
164
+ if (legacyCacheReads.size === 0) {
165
+ return;
166
+ }
167
+ const bodyLines = [];
168
+ for (const [shadowingKey, targetKeys] of legacyCacheReads) {
169
+ for (const targetKey of targetKeys) {
170
+ bodyLines.push(` - "${shadowingKey}" does not set "cache", so it was read from "${targetKey}"`);
171
+ }
172
+ }
173
+ bodyLines.push('', 'An executor key applies to every target that resolves through it, so exclude any continuous target before setting "cache" on one — a target that is both cacheable and continuous is rejected.', 'Target defaults resolve to a single key rather than merging, so an executor key hides the target name key entirely.', 'Set "cache" on the executor key to keep these targets cacheable — reading it from the target name key is deprecated and will be removed in Nx 24.');
174
+ output_1.output.warn({
175
+ title: 'Some targets are only cacheable through a deprecated fallback.',
176
+ bodyLines,
177
+ });
178
+ }
68
179
  function normalizeTargets(project, sourceMaps, nxJsonConfiguration, workspaceRoot,
69
180
  /**
70
181
  * Project configurations keyed by project name
71
182
  */
72
- projects) {
183
+ projects,
184
+ /**
185
+ * Shadowing `targetDefaults` key -> target name keys its `cache` was read
186
+ * from. Accumulated across projects so the deprecation warns once per pair.
187
+ */
188
+ legacyCacheReads) {
73
189
  const targetErrorMessage = [];
74
190
  for (const targetName in project.targets) {
75
191
  project.targets[targetName] = normalizeTarget(project.targets[targetName], project, workspaceRoot, projects, [project.root, targetName].join(':'));
76
192
  const target = project.targets[targetName];
193
+ const targetDefaults = nxJsonConfiguration.targetDefaults;
194
+ if (isLegacyCachedTarget(targetName, targetDefaults, target)) {
195
+ target.cache = true;
196
+ // Always defined: `isLegacyCachedTarget` returns false without it.
197
+ const shadowingKey = findShadowingTargetDefaultKey(targetDefaults, target);
198
+ const targetKeys = legacyCacheReads.get(shadowingKey) ?? new Set();
199
+ targetKeys.add(targetName);
200
+ legacyCacheReads.set(shadowingKey, targetKeys);
201
+ }
77
202
  if (
78
203
  // If the target has no executor or command, it doesn't do anything
79
204
  !target.executor &&
@@ -106,6 +231,7 @@ function validateAndNormalizeProjectRootMap(workspaceRoot, projectRootMap, nxJso
106
231
  const conflicts = new Map();
107
232
  const projectRootsWithNoName = [];
108
233
  const validityErrors = [];
234
+ const legacyCacheReads = new Map();
109
235
  for (const root in projectRootMap) {
110
236
  const project = projectRootMap[root];
111
237
  // We're setting `// targets` as a comment `targets` is empty due to Project Crystal.
@@ -115,9 +241,22 @@ function validateAndNormalizeProjectRootMap(workspaceRoot, projectRootMap, nxJso
115
241
  // We initially did this in the project.json plugin, but
116
242
  // that resulted in project.json files without names causing
117
243
  // the resulting project to change names from earlier plugins...
118
- if (!project.name &&
119
- (0, node_fs_1.existsSync)((0, path_1.join)(workspaceRoot, project.root, 'project.json'))) {
120
- project.name = (0, to_project_name_1.toProjectName)((0, path_1.join)(root, 'project.json'));
244
+ if (!project.name) {
245
+ const projectJsonPath = (0, path_1.join)(workspaceRoot, project.root, 'project.json');
246
+ if ((0, node_fs_1.existsSync)(projectJsonPath)) {
247
+ // The project.json plugin may not have run (e.g. when a single
248
+ // plugin is run in isolation via `addPlugin` from a generator), so
249
+ // prefer the name declared in project.json before deriving one from
250
+ // the directory name.
251
+ let nameFromProjectJson;
252
+ try {
253
+ nameFromProjectJson =
254
+ (0, fileutils_1.readJsonFile)(projectJsonPath).name;
255
+ }
256
+ catch { }
257
+ project.name =
258
+ nameFromProjectJson ?? (0, to_project_name_1.toProjectName)((0, path_1.join)(root, 'project.json'));
259
+ }
121
260
  }
122
261
  try {
123
262
  validateProject(project, projects);
@@ -142,7 +281,7 @@ function validateAndNormalizeProjectRootMap(workspaceRoot, projectRootMap, nxJso
142
281
  for (const root in projectRootMap) {
143
282
  const project = projectRootMap[root];
144
283
  try {
145
- normalizeTargets(project, sourceMaps, nxJsonConfiguration, workspaceRoot, projects);
284
+ normalizeTargets(project, sourceMaps, nxJsonConfiguration, workspaceRoot, projects, legacyCacheReads);
146
285
  }
147
286
  catch (e) {
148
287
  if (e instanceof error_types_1.WorkspaceValidityError) {
@@ -153,9 +292,13 @@ function validateAndNormalizeProjectRootMap(workspaceRoot, projectRootMap, nxJso
153
292
  }
154
293
  }
155
294
  }
295
+ warnAboutLegacyCachedTargets(legacyCacheReads);
156
296
  const errors = [];
157
297
  if (conflicts.size > 0) {
158
- errors.push(new error_types_1.MultipleProjectsWithSameNameError(conflicts, projects));
298
+ // Only on the way to throwing, so a workspace without duplicates never
299
+ // pays for reading git's worktree registry.
300
+ const worktreeAdvice = (0, git_worktrees_1.analyzeWorktreeConflicts)(workspaceRoot, conflicts);
301
+ errors.push(new error_types_1.MultipleProjectsWithSameNameError(conflicts, projects, worktreeAdvice ?? undefined));
159
302
  }
160
303
  if (projectRootsWithNoName.length > 0) {
161
304
  errors.push(new error_types_1.ProjectsWithNoNameError(projectRootsWithNoName, projects));
@@ -8,6 +8,21 @@ export declare const INTEGER_LIKE_KEY_PATTERN: RegExp;
8
8
  export declare class IntegerLikeSpreadKeyError extends Error {
9
9
  constructor(key: string, context: string);
10
10
  }
11
+ /**
12
+ * Throws `IntegerLikeSpreadKeyError` when `value` is a `'...'` spread object
13
+ * whose enumeration hoists an integer-like key ahead of the spread, making its
14
+ * authored position ambiguous.
15
+ *
16
+ * The ambiguity is a property of the authored value alone, not of any merge
17
+ * base. `mergeObjectWithSpread` runs this the moment it merges such a value —
18
+ * but a merge layer that lets the base win a key drops the incoming value
19
+ * without merging it, so the check must also be run eagerly at those
20
+ * base-owns-key shortcuts. Otherwise the error would surface or vanish
21
+ * depending on which side owns the key (e.g. it fires in the target-defaults
22
+ * staging merge but not the real merge), which is exactly the divergence that
23
+ * makes discarding staging errors unsafe.
24
+ */
25
+ export declare function assertNoIntegerLikeSpreadKey(value: unknown, errorContext: string): void;
11
26
  type SourceMapContext = {
12
27
  sourceMap: Record<string, SourceInformation>;
13
28
  key: string;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.IntegerLikeSpreadKeyError = exports.INTEGER_LIKE_KEY_PATTERN = exports.NX_SPREAD_TOKEN = void 0;
4
4
  exports.uniqueKeysInObjects = uniqueKeysInObjects;
5
+ exports.assertNoIntegerLikeSpreadKey = assertNoIntegerLikeSpreadKey;
5
6
  exports.getMergeValueResult = getMergeValueResult;
6
7
  const source_maps_1 = require("./source-maps");
7
8
  exports.NX_SPREAD_TOKEN = '...';
@@ -30,6 +31,29 @@ class IntegerLikeSpreadKeyError extends Error {
30
31
  }
31
32
  }
32
33
  exports.IntegerLikeSpreadKeyError = IntegerLikeSpreadKeyError;
34
+ /**
35
+ * Throws `IntegerLikeSpreadKeyError` when `value` is a `'...'` spread object
36
+ * whose enumeration hoists an integer-like key ahead of the spread, making its
37
+ * authored position ambiguous.
38
+ *
39
+ * The ambiguity is a property of the authored value alone, not of any merge
40
+ * base. `mergeObjectWithSpread` runs this the moment it merges such a value —
41
+ * but a merge layer that lets the base win a key drops the incoming value
42
+ * without merging it, so the check must also be run eagerly at those
43
+ * base-owns-key shortcuts. Otherwise the error would surface or vanish
44
+ * depending on which side owns the key (e.g. it fires in the target-defaults
45
+ * staging merge but not the real merge), which is exactly the divergence that
46
+ * makes discarding staging errors unsafe.
47
+ */
48
+ function assertNoIntegerLikeSpreadKey(value, errorContext) {
49
+ if (!isObject(value) || value[exports.NX_SPREAD_TOKEN] !== true) {
50
+ return;
51
+ }
52
+ const keys = Object.keys(value);
53
+ if (keys[0] && exports.INTEGER_LIKE_KEY_PATTERN.test(keys[0])) {
54
+ throw new IntegerLikeSpreadKeyError(keys[0], errorContext);
55
+ }
56
+ }
33
57
  /**
34
58
  * `"..."` in `newValue` (as an array element or a key set to `true`)
35
59
  * expands the base at that position; otherwise `newValue` replaces
@@ -52,11 +76,25 @@ function getMergeValueResult(baseValue, newValue, sourceMapContext, deferSpreads
52
76
  // plugin already set, purely as a merge guard — must not steal provenance
53
77
  // from the layer that introduced the value. A genuinely new value (no base,
54
78
  // or a different one) still attributes to the new layer.
55
- if (!isUnchangedPrimitive(newValue, baseValue)) {
79
+ //
80
+ // The exception cuts the other way too: when the *existing* attribution is a
81
+ // target-defaults stamp (TD merges before the plugin whose value it
82
+ // predicted, so its stamp lands first), a real plugin re-stating that value
83
+ // is the genuine author and reclaims the key.
84
+ if (!isUnchangedPrimitive(newValue, baseValue) ||
85
+ isReclaimableTargetDefaultsStamp(sourceMapContext)) {
56
86
  writeTopLevelSourceMap(sourceMapContext);
57
87
  }
58
88
  return newValue;
59
89
  }
90
+ // Whether the key's current attribution is a weak target-defaults stamp that
91
+ // the (non-target-defaults) writer should reclaim despite the value being
92
+ // unchanged.
93
+ function isReclaimableTargetDefaultsStamp(ctx) {
94
+ return (!!ctx &&
95
+ ctx.sourceMap[ctx.key]?.[1] === source_maps_1.TARGET_DEFAULTS_PLUGIN_NAME &&
96
+ ctx.sourceInformation[1] !== source_maps_1.TARGET_DEFAULTS_PLUGIN_NAME);
97
+ }
60
98
  // Whether `newValue` leaves a defined primitive base untouched. Objects fall
61
99
  // through (always re-attributed) to preserve the existing replace semantics —
62
100
  // the regression this guards is scalar executor/command re-stamping.
@@ -120,12 +158,10 @@ function mergeObjectWithSpread(baseValue, newValue, sourceMapContext, deferSprea
120
158
  const errorContext = sourceMapContext?.key
121
159
  ? `Object at "${sourceMapContext.key}"`
122
160
  : 'Object';
161
+ // Integer-like keys are hoisted to the front of enumeration, so one
162
+ // alongside `'...'` makes its authored position ambiguous.
163
+ assertNoIntegerLikeSpreadKey(newValue, errorContext);
123
164
  const newKeys = Object.keys(newValue);
124
- // Integer-like keys are hoisted to the front of enumeration, so if one
125
- // exists alongside `'...'` it must be newKeys[0].
126
- if (newKeys[0] && exports.INTEGER_LIKE_KEY_PATTERN.test(newKeys[0])) {
127
- throw new IntegerLikeSpreadKeyError(newKeys[0], errorContext);
128
- }
129
165
  // Base per-key sources captured lazily — only for shared keys the new
130
166
  // object overwrites before `'...'`, since writing their new source
131
167
  // clobbers the base entry the spread will need to restore.
@@ -60,19 +60,22 @@ export type MergeError = AggregateCreateNodesError | MergeNodesError | ProjectsW
60
60
  /**
61
61
  * Merges create nodes results into a single rootMap.
62
62
  *
63
- * Specified plugin results are merged once into the manager. Default
64
- * plugin results are first staged into an intermediate rootMap (with
65
- * `'...'` spreads deferred) so that synthesis can read each layer's
66
- * contribution without re-running the merge. The synthetic result from
67
- * `createTargetDefaultsResults` is then merged into the manager, and
68
- * the staged intermediate is replayed on top — that replay is where
69
- * deferred spreads expand against the final (specified + synth) base.
63
+ * Every layer merges into the manager through the same source-map-aware
64
+ * merge, in precedence order:
70
65
  *
71
- * Synthesis itself doesn't materialize a second rootMap. Per
72
- * (root, target) it does an on-the-fly merge of the two layered
73
- * contributions to learn the eventual executor/command, then matches
74
- * defaults against that merged shape. This keeps specified-plugin
75
- * merge work to a single pass.
66
+ * specified plugins → synthetic target defaults → default plugins
67
+ *
68
+ * so field-level provenance is decided by the merge itself for all three
69
+ * layers — whichever layer a field's final value came from owns its
70
+ * attribution, and `'...'` spreads in default-plugin configs resolve against
71
+ * the accumulated specified + target-defaults base.
72
+ *
73
+ * Target-default synthesis needs the *merged* shape of the default layer
74
+ * (to predict each target's eventual executor/command) before that layer
75
+ * merges into the manager. To get it, default results are first staged into
76
+ * a throwaway intermediate rootMap with unresolvable `'...'` spreads
77
+ * deferred. The staging output feeds only `createTargetDefaultsResults`; the
78
+ * default plugins then merge into the manager from their original results.
76
79
  */
77
80
  export declare function mergeCreateNodesResults(specifiedResults: CreateNodesResultEntry[][], defaultResults: CreateNodesResultEntry[][], nxJsonConfiguration: NxJsonConfiguration, workspaceRoot: string, errors: MergeError[]): {
78
81
  projectRootMap: Record<string, ProjectConfiguration>;