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
@@ -13,10 +13,10 @@ const delayed_spinner_1 = require("../../utils/delayed-spinner");
13
13
  const plugin_progress_text_1 = require("../../utils/plugin-progress-text");
14
14
  const progress_topics_1 = require("../../utils/progress-topics");
15
15
  const error_types_1 = require("../error-types");
16
- const source_maps_1 = require("./project-configuration/source-maps");
17
16
  const target_defaults_1 = require("./project-configuration/target-defaults");
18
17
  var target_merging_1 = require("./project-configuration/target-merging");
19
18
  Object.defineProperty(exports, "mergeTargetConfigurations", { enumerable: true, get: function () { return target_merging_1.mergeTargetConfigurations; } });
19
+ const target_merging_2 = require("./project-configuration/target-merging");
20
20
  var target_defaults_2 = require("./project-configuration/target-defaults");
21
21
  Object.defineProperty(exports, "readTargetDefaultsForTarget", { enumerable: true, get: function () { return target_defaults_2.readTargetDefaultsForTarget; } });
22
22
  /**
@@ -123,14 +123,13 @@ async function createProjectConfigurationsWithPlugins(root = workspace_root_1.wo
123
123
  * Runs a single plugin batch through two passes:
124
124
  *
125
125
  * 1. Every project node in every plugin result is handed to `mergeFn`,
126
- * which decides where it lands (the manager's rootMap, an
127
- * intermediate rootMap, etc.). Any failure is collected into
128
- * `errors`; processing keeps going. External nodes are accumulated
129
- * onto the shared `externalNodes` record.
126
+ * which merges it into the manager's rootMap. Any failure is
127
+ * collected into `errors`; processing keeps going. External nodes
128
+ * are accumulated onto the shared `externalNodes` record.
130
129
  * 2. After every project in the batch has been merged, name-reference
131
- * sentinels for the batch are registered against `nameRefRootMap` —
132
- * the rootMap the batch was merged into — so sentinels point at the
133
- * target objects that actually received the merges.
130
+ * sentinels for the batch are registered against the manager's
131
+ * rootMap, so sentinels point at the target objects that actually
132
+ * received the merges.
134
133
  *
135
134
  * The two passes can't be collapsed: a sentinel registered too early
136
135
  * would point at the pre-merge object, and a later project in the same
@@ -138,7 +137,11 @@ async function createProjectConfigurationsWithPlugins(root = workspace_root_1.wo
138
137
  * the registration into a second pass also lets forward references
139
138
  * inside the same batch resolve eagerly.
140
139
  */
141
- function mergeCreateNodesResultsFromSinglePlugin(pluginResults, mergeFn, nodesManager, nameRefRootMap, externalNodes, errors) {
140
+ function mergeCreateNodesResultsFromSinglePlugin(pluginResults, mergeFn, nodesManager, externalNodes, errors) {
141
+ mergeSinglePluginResults(pluginResults, mergeFn, externalNodes, errors);
142
+ registerNameRefsFromSinglePlugin(pluginResults, nodesManager, errors);
143
+ }
144
+ function mergeSinglePluginResults(pluginResults, mergeFn, externalNodes, errors) {
142
145
  for (const result of pluginResults) {
143
146
  const [pluginName, file, nodes, pluginIndex] = result;
144
147
  const { projects: projectNodes, externalNodes: pluginExternalNodes } = nodes;
@@ -156,11 +159,13 @@ function mergeCreateNodesResultsFromSinglePlugin(pluginResults, mergeFn, nodesMa
156
159
  }
157
160
  Object.assign(externalNodes, pluginExternalNodes);
158
161
  }
162
+ }
163
+ function registerNameRefsFromSinglePlugin(pluginResults, nodesManager, errors) {
159
164
  for (const result of pluginResults) {
160
165
  const [pluginName, file, nodes, pluginIndex] = result;
161
166
  const { projects: projectNodes } = nodes;
162
167
  try {
163
- nodesManager.registerNameRefs(projectNodes, nameRefRootMap);
168
+ nodesManager.registerNameRefs(projectNodes);
164
169
  }
165
170
  catch (error) {
166
171
  errors.push(new error_types_1.MergeNodesError({ file, pluginName, error, pluginIndex }));
@@ -170,110 +175,83 @@ function mergeCreateNodesResultsFromSinglePlugin(pluginResults, mergeFn, nodesMa
170
175
  /**
171
176
  * Merges create nodes results into a single rootMap.
172
177
  *
173
- * Specified plugin results are merged once into the manager. Default
174
- * plugin results are first staged into an intermediate rootMap (with
175
- * `'...'` spreads deferred) so that synthesis can read each layer's
176
- * contribution without re-running the merge. The synthetic result from
177
- * `createTargetDefaultsResults` is then merged into the manager, and
178
- * the staged intermediate is replayed on top — that replay is where
179
- * deferred spreads expand against the final (specified + synth) base.
178
+ * Every layer merges into the manager through the same source-map-aware
179
+ * merge, in precedence order:
180
+ *
181
+ * specified plugins → synthetic target defaults → default plugins
182
+ *
183
+ * so field-level provenance is decided by the merge itself for all three
184
+ * layers — whichever layer a field's final value came from owns its
185
+ * attribution, and `'...'` spreads in default-plugin configs resolve against
186
+ * the accumulated specified + target-defaults base.
180
187
  *
181
- * Synthesis itself doesn't materialize a second rootMap. Per
182
- * (root, target) it does an on-the-fly merge of the two layered
183
- * contributions to learn the eventual executor/command, then matches
184
- * defaults against that merged shape. This keeps specified-plugin
185
- * merge work to a single pass.
188
+ * Target-default synthesis needs the *merged* shape of the default layer
189
+ * (to predict each target's eventual executor/command) before that layer
190
+ * merges into the manager. To get it, default results are first staged into
191
+ * a throwaway intermediate rootMap with unresolvable `'...'` spreads
192
+ * deferred. The staging output feeds only `createTargetDefaultsResults`; the
193
+ * default plugins then merge into the manager from their original results.
186
194
  */
187
195
  function mergeCreateNodesResults(specifiedResults, defaultResults, nxJsonConfiguration, workspaceRoot, errors) {
188
196
  perf_hooks_1.performance.mark('createNodes:merge - start');
189
197
  const nodesManager = new project_nodes_manager_1.ProjectNodesManager();
190
198
  const externalNodes = {};
191
199
  const configurationSourceMaps = {};
192
- const intermediateDefaultRootMap = {};
193
- // Kept separate so the intermediate merge doesn't clobber
194
- // specified/TD attribution on fields the defaults don't touch.
195
- const defaultConfigurationSourceMaps = {};
196
200
  const mergeToManager = (project, sourceInfo) => nodesManager.mergeProjectNode(project, configurationSourceMaps, sourceInfo);
197
- const mergeToIntermediate = (project, sourceInfo) => {
198
- (0, project_nodes_manager_1.mergeProjectConfigurationIntoRootMap)(intermediateDefaultRootMap, project, defaultConfigurationSourceMaps, sourceInfo);
199
- };
200
201
  for (const pluginResults of specifiedResults) {
201
- mergeCreateNodesResultsFromSinglePlugin(pluginResults, mergeToManager, nodesManager, nodesManager.getRootMap(), externalNodes, errors);
202
- }
203
- for (const pluginResults of defaultResults) {
204
- mergeCreateNodesResultsFromSinglePlugin(pluginResults, mergeToIntermediate, nodesManager, intermediateDefaultRootMap, externalNodes, errors);
205
- }
206
- const targetDefaultsResults = (0, target_defaults_1.createTargetDefaultsResults)(nodesManager.getRootMap(), intermediateDefaultRootMap, nxJsonConfiguration, configurationSourceMaps, defaultConfigurationSourceMaps);
207
- if (targetDefaultsResults.length > 0) {
208
- mergeCreateNodesResultsFromSinglePlugin(targetDefaultsResults, mergeToManager, nodesManager, nodesManager.getRootMap(), externalNodes, errors);
202
+ mergeCreateNodesResultsFromSinglePlugin(pluginResults, mergeToManager, nodesManager, externalNodes, errors);
209
203
  }
210
- // Apply the intermediate default rootMap as a single layer. Preserved
211
- // spread sentinels resolve here against the real specified + TD base.
212
- // Source maps are intentionally not written — TD attribution for
213
- // fields that yield to the base (e.g. keys before `...`) stays intact.
214
- for (const root in intermediateDefaultRootMap) {
215
- const project = intermediateDefaultRootMap[root];
216
- try {
217
- nodesManager.mergeProjectNode(project, undefined, undefined);
204
+ // Without target defaults there is nothing to synthesize, and the staging
205
+ // pass exists only to feed synthesis — skip straight to the default-plugin
206
+ // merge.
207
+ if (Object.keys(nxJsonConfiguration.targetDefaults ?? {}).length > 0) {
208
+ // Throwaway staging area: the default layer's merged shape (unresolvable
209
+ // `'...'` spreads deferred), read only by target-default synthesis. The
210
+ // default plugins merge into the manager from their original results, not
211
+ // from this map. No source maps are kept for it — synthesis attributes
212
+ // targets without them (a default plugin can never be named by a
213
+ // `filter.plugin`); the real default merge writes the manager's.
214
+ const intermediateDefaultRootMap = {};
215
+ // The rootMap merge adopts input arrays/objects by reference and grows
216
+ // them in place (e.g. `mergeMetadata`), so staging works on deep clones —
217
+ // handing it the plugin results themselves would corrupt them before the
218
+ // real merge below reads them.
219
+ const mergeToIntermediate = (project, sourceInfo) => {
220
+ (0, project_nodes_manager_1.mergeProjectConfigurationIntoRootMap)(intermediateDefaultRootMap, (0, target_merging_2.deepClone)(project), undefined, sourceInfo, false, true);
221
+ };
222
+ // Stage the default layer for synthesis. Merge errors are discarded and
223
+ // external nodes land in a scratch object — the same results merge into
224
+ // the manager below, where both surface once with proper plugin context.
225
+ // The discard is safe because every merge throw is base-independent in
226
+ // both its condition and its reachability: each throw's condition reads
227
+ // only the plugin's own config, and the spread-ambiguity throws fire even
228
+ // when a key is dropped by a base-owns-key shortcut, because those
229
+ // shortcuts eagerly validate the dropped value (`assertNoIntegerLikeSpreadKey`).
230
+ // So a given config raises the same error in this pass and the real merge
231
+ // below despite their bases differing.
232
+ // Name references are NOT registered here: `applySubstitutions` sweeps only
233
+ // the manager's `rootMap`, so sentinels registered against this throwaway
234
+ // rootMap would never be visited and would never resolve.
235
+ const stagingErrors = [];
236
+ const stagingExternalNodes = {};
237
+ for (const pluginResults of defaultResults) {
238
+ mergeSinglePluginResults(pluginResults, mergeToIntermediate, stagingExternalNodes, stagingErrors);
218
239
  }
219
- catch (error) {
220
- errors.push(new error_types_1.MergeNodesError({
221
- file: 'nx.json',
222
- pluginName: 'nx/default-plugins',
223
- error,
224
- pluginIndex: undefined,
225
- }));
240
+ const targetDefaultsResults = (0, target_defaults_1.createTargetDefaultsResults)(nodesManager.getRootMap(), intermediateDefaultRootMap, nxJsonConfiguration, configurationSourceMaps);
241
+ if (targetDefaultsResults.length > 0) {
242
+ mergeCreateNodesResultsFromSinglePlugin(targetDefaultsResults, mergeToManager, nodesManager, externalNodes, errors);
226
243
  }
227
244
  }
228
- // The intermediate apply may have rebuilt dependsOn / inputs arrays
229
- // via spread merges, leaving sentinels inserted against the
230
- // intermediate rootMap pointing at now-orphaned arrays. Re-walking
231
- // the final merged targets rebinds each encountered sentinel's
232
- // `parent` to the current array (see
233
- // ProjectNameInNodePropsManager#processInputs / processDependsOn).
234
- nodesManager.registerNameRefs(intermediateDefaultRootMap);
235
- // Overlay default-plugin attribution onto the main source maps using
236
- // "only fill missing" semantics. Any key already present in
237
- // configurationSourceMaps was written by a specified plugin or by
238
- // target defaults, and that attribution is strictly more correct:
239
- // - For fields the default plugin never shadowed, the existing entry
240
- // already matches what the default plugin would overlay.
241
- // - For fields where a default plugin placed `...` after other keys,
242
- // those keys yielded to the base during the single-layer apply
243
- // above. The stale default-plugin entry in
244
- // `defaultConfigurationSourceMaps` must NOT clobber the base
245
- // attribution that the specified plugin / TD already recorded.
246
- const mainRootMap = nodesManager.getRootMap();
247
- for (const root in defaultConfigurationSourceMaps) {
248
- const existing = (configurationSourceMaps[root] ??= {});
249
- const incoming = defaultConfigurationSourceMaps[root];
250
- // A default plugin's targets are synthesized into the main rootmap by
251
- // target defaults *before* the default layer is applied, so target
252
- // defaults wrote the main source map's entry for the target node and its
253
- // identity fields first — even though it never authored them (it only
254
- // stamps them as a merge guard and cannot bring a target into existence).
255
- // The identity fields are the executor/command plus, for run-commands, the
256
- // `options.command`/`options.commands` the synthetic copies from the winner
257
- // to stay compatible (#36067). For those keys, the real default plugin's
258
- // attribution must override the target-defaults stamp.
259
- const identityKeys = new Set();
260
- for (const targetName in mainRootMap[root]?.targets ?? {}) {
261
- const base = (0, source_maps_1.targetSourceMapKey)(targetName);
262
- identityKeys.add(base);
263
- identityKeys.add(`${base}.executor`);
264
- identityKeys.add(`${base}.command`);
265
- identityKeys.add(`${base}.options.command`);
266
- identityKeys.add(`${base}.options.commands`);
267
- }
268
- for (const key in incoming) {
269
- if (existing[key] === undefined) {
270
- existing[key] = incoming[key];
271
- }
272
- else if (identityKeys.has(key) &&
273
- existing[key][1] === source_maps_1.TARGET_DEFAULTS_PLUGIN_NAME) {
274
- existing[key] = incoming[key];
275
- }
276
- }
245
+ // Merge the default plugins into the manager on top of the specified + TD
246
+ // base, from their original results. This is the same source-map-aware path
247
+ // the other layers take, so every field a default plugin wins — including
248
+ // fields it overrides on a specified/TD target — is attributed to it by the
249
+ // merge itself, `'...'` spreads resolve against the real base (keys a spread
250
+ // lets the base win keep their base attribution), and identity provenance
251
+ // follows the node-ownership rules in `recordTargetIdentitySourceMapInfo` /
252
+ // `getMergeValueResult`.
253
+ for (const pluginResults of defaultResults) {
254
+ mergeCreateNodesResultsFromSinglePlugin(pluginResults, mergeToManager, nodesManager, externalNodes, errors);
277
255
  }
278
256
  const projectRootMap = nodesManager.getRootMap();
279
257
  try {
@@ -29,15 +29,20 @@ export interface Timespan {
29
29
  /** `parallelism: false` tasks running during the slice (each one holds every slot). */
30
30
  nonParallel: number;
31
31
  }
32
+ /**
33
+ * A task and how long it ran (ms). The unit the report's task lists are built from —
34
+ * shared with the renderers so the producer and the formatters can't drift.
35
+ */
36
+ export interface TaskDurationRow {
37
+ id: string;
38
+ duration: number;
39
+ }
32
40
  export interface PerformanceSummary {
33
41
  runDuration: number;
34
42
  criticalPathDuration: number;
35
43
  criticalPathTaskCount: number;
36
44
  /** Longest critical-path tasks that ran (desc, capped at a few), cache hits excluded; empty when the path was fully cached. */
37
- criticalPathTop: Array<{
38
- id: string;
39
- duration: number;
40
- }>;
45
+ criticalPathTop: TaskDurationRow[];
41
46
  /** Ids of tasks that failed (slowest first), for the GitHub Actions summary's failed-tasks list. Continuous tasks and tasks without a complete window are excluded. */
42
47
  failedTasks: string[];
43
48
  /** runDuration − criticalPathDuration. */
@@ -60,6 +65,8 @@ export interface PerformanceSummary {
60
65
  cacheableCount: number;
61
66
  cacheSkipped: boolean;
62
67
  remoteCacheEnabled: boolean;
68
+ /** The workspace opted out of Nx Cloud (`neverConnectToCloud` / NX_NO_CLOUD) — never recommend it. */
69
+ cloudOptedOut: boolean;
63
70
  }
64
71
  /** Construction-time inputs for {@link PerformanceLifeCycle}. */
65
72
  export interface PerformanceLifeCycleOptions {
@@ -18,6 +18,8 @@ const CACHE_HIT_STATUSES = new Set([
18
18
  ]);
19
19
  /** Gap (ms) still counted as the same pre-dispatch hashing phase; a larger gap means older, unrelated hashing (e.g. a daemon's previous run). */
20
20
  const PRE_DISPATCH_HASH_GAP = 1000;
21
+ /** A critical-path task shorter than this fraction of the path is noise, not a speed-up target. */
22
+ const CRITICAL_PATH_TOP_MIN_FRACTION = 0.2;
21
23
  /**
22
24
  * Pure analysis over one finished run's collected timings — no lifecycle state and no
23
25
  * mutation. Built once per run from the {@link PerformanceLifeCycle}'s accumulated
@@ -171,15 +173,17 @@ class PerformanceAnalysis {
171
173
  * (their duration is just restore time); no-status tasks (synthetic test runs) are
172
174
  * kept.
173
175
  */
174
- computeCriticalPathTop(criticalPathTasks, durations) {
175
- return criticalPathTasks
176
+ computeCriticalPathTop(criticalPathTasks, durations, criticalPathDuration) {
177
+ return (criticalPathTasks
176
178
  .filter((id) => {
177
179
  const status = this.statuses.get(id);
178
180
  return !status || !CACHE_HIT_STATUSES.has(status);
179
181
  })
180
182
  .map((id) => ({ id, duration: durations.get(id) ?? 0 }))
183
+ // Only tasks that meaningfully shape the path are worth speeding up.
184
+ .filter((t) => t.duration >= CRITICAL_PATH_TOP_MIN_FRACTION * criticalPathDuration)
181
185
  .sort((a, b) => b.duration - a.duration)
182
- .slice(0, 3);
186
+ .slice(0, 3));
183
187
  }
184
188
  /** Cache outcome: tasks restored (`cacheHits`) and the total with a cache outcome (`cacheableCount` = hits + ran). No-status tasks count for neither. */
185
189
  computeCacheStats() {
@@ -244,12 +248,15 @@ class PerformanceAnalysis {
244
248
  cores,
245
249
  criticalPathDuration,
246
250
  });
247
- const criticalPathTop = this.computeCriticalPathTop(criticalPathTasks, durations);
251
+ const criticalPathTop = this.computeCriticalPathTop(criticalPathTasks, durations, criticalPathDuration);
248
252
  const { cacheHits, cacheableCount } = this.computeCacheStats();
249
253
  // `skipNxCache` already folds in NX_SKIP_NX_CACHE / NX_DISABLE_NX_CACHE
250
254
  // (normalized in command-line-utils) — don't re-read those env vars here.
251
255
  const cacheSkipped = this.options.skipNxCache === true;
252
256
  const remoteCacheEnabled = this.remoteCacheEnabled();
257
+ const cloudOptedOut = this.options.nxJson
258
+ ? !!(0, nx_cloud_utils_1.isNxCloudDisabled)(this.options.nxJson)
259
+ : false;
253
260
  // Coordinator-dominated: hashing/scheduling outweighs task work by >3x the
254
261
  // critical path, which keeps cold runs critical-path-bound.
255
262
  const coordinatorDominated = coordinatorOverhead >= performance_report_1.MEANINGFUL_OVERHEAD &&
@@ -271,14 +278,16 @@ class PerformanceAnalysis {
271
278
  parallel,
272
279
  cores,
273
280
  isCI,
274
- // Can only start distributing in CI when not already doing so.
275
- canDistribute: isCI && !distributing,
281
+ // Can only start distributing in CI when not already doing so — and never
282
+ // suggest Nx Agents to a workspace that opted out of Nx Cloud.
283
+ canDistribute: isCI && !distributing && !cloudOptedOut,
276
284
  distributing,
277
285
  coordinatorDominated,
278
286
  cacheHits,
279
287
  cacheableCount,
280
288
  cacheSkipped,
281
289
  remoteCacheEnabled,
290
+ cloudOptedOut,
282
291
  };
283
292
  }
284
293
  }
@@ -142,8 +142,9 @@ function flushPerformanceReport() {
142
142
  // restore_terminal cooks the terminal back post-TUI, so console.log's plain \n
143
143
  // renders fine; it also supplies the single trailing newline formatReport omits.
144
144
  console.log((0, performance_report_1.formatReport)(summary));
145
- // In GitHub Actions, also append the report (plus a per-task table) to the job
146
- // summary page. Independent of the console.log above so neither masks the other.
145
+ // In GitHub Actions, also append the report to the job summary page — the same stats
146
+ // as above, led by the run's outcome (a failed-tasks list, or a success line).
147
+ // Independent of the console.log above so neither masks the other.
147
148
  writePerformanceReportToGitHubActions(summary);
148
149
  }
149
150
  catch (e) {
@@ -157,8 +158,8 @@ function flushPerformanceReport() {
157
158
  /**
158
159
  * Append the performance report to the GitHub Actions job summary page when running in
159
160
  * Actions (`$GITHUB_STEP_SUMMARY` is set there and nowhere else). No-op otherwise. The
160
- * per-task table is computed lazily so non-CI runs don't pay for it. Best-effort: a
161
- * write failure must never affect the run.
161
+ * Markdown is rendered below the guard, so non-CI runs don't pay to format a report
162
+ * nothing reads. Best-effort: a write failure must never affect the run.
162
163
  *
163
164
  * Skipped for a nested run (one nx command invoked by another nx task's command), so only
164
165
  * the outermost run writes to the summary. Nx sets `NX_TASK_TARGET_PROJECT` on every task's
@@ -1,12 +1,16 @@
1
1
  import type { PerformanceSummaryPayload } from '../../native';
2
- import type { PerformanceSummary } from './performance-analysis';
2
+ import type { PerformanceSummary, TaskDurationRow } from './performance-analysis';
3
3
  /**
4
- * A recommendation built from structured parts so the link text comes from the
5
- * link definition (not a substring scanned out of the assembled report). A part
6
- * is either literal text or a {@link RecLink}; the renderers below project the
7
- * same parts to the terminal string, the payload string, and the popup links.
4
+ * A recommendation built from structured parts so the link text comes from the link
5
+ * definition (not a substring scanned out of the assembled report). A part is literal
6
+ * text, a {@link RecLink}, or a {@link RecTaskRows}, projected to each output string by
7
+ * {@link renderRecommendation}.
8
+ *
9
+ * String parts must be single-line — multi-line content needs its own structured part (as
10
+ * {@link RecTaskRows} is). TS can't enforce this: `string` is already a handled member, so
11
+ * a multi-line one compiles and then breaks the Markdown nested list.
8
12
  */
9
- type RecPart = string | RecLink;
13
+ type RecPart = string | RecLink | RecTaskRows;
10
14
  export type Recommendation = RecPart[];
11
15
  /**
12
16
  * A docs link inside a recommendation: a sentence is the visible text and the whole of
@@ -15,16 +19,22 @@ export type Recommendation = RecPart[];
15
19
  * re-links the phrase from {@link PerformanceSummaryPayload.links}.
16
20
  */
17
21
  interface RecLink {
18
- /** Visible label: the sentence that links. */
19
22
  visible: string;
20
- /** OSC 8 click target / appended URL: the utm-tagged URL. */
21
23
  href: string;
22
24
  }
25
+ /**
26
+ * The critical path's longest tasks as data, so each renderer formats them natively:
27
+ * the terminal and payload as space-aligned columns, Markdown as a nested list
28
+ * (HTML collapses space runs, so aligned columns don't survive rendering there).
29
+ */
30
+ type RecTaskRows = TaskDurationRow[];
23
31
  /**
24
32
  * The recommendation string the napi payload ships and the Rust popup matches against.
25
33
  * Links are URL-less (the popup re-links them from {@link PerformanceSummaryPayload.links}).
26
34
  */
27
35
  export declare function recommendationToPayloadString(rec: Recommendation): string;
36
+ /** Below this run duration (ms), the run is already fast — recommend nothing. */
37
+ export declare const MIN_RECOMMENDATION_RUN_DURATION = 30000;
28
38
  /** Below this (ms) overhead is noise, not worth a recommendation. */
29
39
  export declare const MEANINGFUL_OVERHEAD = 1000;
30
40
  /**
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MEANINGFUL_OVERHEAD = void 0;
3
+ exports.MEANINGFUL_OVERHEAD = exports.MIN_RECOMMENDATION_RUN_DURATION = void 0;
4
4
  exports.recommendationToPayloadString = recommendationToPayloadString;
5
5
  exports.buildRecommendations = buildRecommendations;
6
6
  exports.formatReport = formatReport;
@@ -25,15 +25,49 @@ const NX_DISTRIBUTE_CTA = 'Distribute across machines with Nx Agents';
25
25
  function phraseLink(phrase, taggedUrl) {
26
26
  return { visible: phrase, href: taggedUrl };
27
27
  }
28
+ // Discriminate positively — test for what each part *is*. A `!isRecTaskRows` catch-all
29
+ // would misclassify a future `RecPart` member as a link; TS can't catch that (it never
30
+ // checks a predicate body), so `recommendationLinks`' `.filter(isRecLink)` would ship
31
+ // `{text: undefined, href: undefined}` to the popup.
28
32
  function isRecLink(part) {
29
- return typeof part !== 'string';
33
+ return typeof part !== 'string' && 'href' in part;
34
+ }
35
+ function isRecTaskRows(part) {
36
+ return Array.isArray(part);
37
+ }
38
+ /**
39
+ * Project a recommendation to a string, formatting each non-text part with the caller's
40
+ * renderers. The three output targets (payload, terminal, Markdown) share this one dispatch.
41
+ * After the string and task-rows branches a part is a {@link RecLink}, so `render.link`
42
+ * takes it directly — and a new {@link RecPart} member that is neither would fail to satisfy
43
+ * that `RecLink` parameter, turning "forgot to handle it" into a compile error right here.
44
+ */
45
+ function renderRecommendation(rec, render) {
46
+ return rec
47
+ .map((part) => {
48
+ if (typeof part === 'string') {
49
+ return part;
50
+ }
51
+ if (isRecTaskRows(part)) {
52
+ return render.taskRows(part);
53
+ }
54
+ return render.link(part);
55
+ })
56
+ .join('');
30
57
  }
31
58
  /**
32
59
  * The recommendation string the napi payload ships and the Rust popup matches against.
33
60
  * Links are URL-less (the popup re-links them from {@link PerformanceSummaryPayload.links}).
34
61
  */
35
62
  function recommendationToPayloadString(rec) {
36
- return rec.map((part) => (!isRecLink(part) ? part : part.visible)).join('');
63
+ return renderRecommendation(rec, {
64
+ link: (link) => link.visible,
65
+ taskRows: taskRowsToText,
66
+ });
67
+ }
68
+ /** Task rows as the text block the terminal and payload embed: newline-led, space-aligned columns. */
69
+ function taskRowsToText(tasks) {
70
+ return ['', ...formatTopTaskRows(tasks)].join('\n');
37
71
  }
38
72
  /**
39
73
  * The recommendation as a terminal string. With OSC 8 the phrase becomes a hyperlink
@@ -42,16 +76,12 @@ function recommendationToPayloadString(rec) {
42
76
  * are off.
43
77
  */
44
78
  function recommendationToTerminalString(rec, hyperlinks) {
45
- return rec
46
- .map((part) => {
47
- if (!isRecLink(part)) {
48
- return part;
49
- }
50
- return hyperlinks
51
- ? (0, terminal_link_1.terminalLink)(part.visible, part.href)
52
- : `${part.visible} → ${part.href}`;
53
- })
54
- .join('');
79
+ return renderRecommendation(rec, {
80
+ link: (link) => hyperlinks
81
+ ? (0, terminal_link_1.terminalLink)(link.visible, link.href)
82
+ : `${link.visible} → ${link.href}`,
83
+ taskRows: taskRowsToText,
84
+ });
55
85
  }
56
86
  /** The popup links (phrase + href) for every link in a recommendation list, for OSC 8 re-linking. */
57
87
  function recommendationLinks(recommendations) {
@@ -59,6 +89,8 @@ function recommendationLinks(recommendations) {
59
89
  .filter(isRecLink)
60
90
  .map((part) => ({ text: part.visible, href: part.href })));
61
91
  }
92
+ /** Below this run duration (ms), the run is already fast — recommend nothing. */
93
+ exports.MIN_RECOMMENDATION_RUN_DURATION = 30_000;
62
94
  /** At/below this hit rate, recommend remote cache (if off); above it caching works. */
63
95
  const LOW_CACHE_HIT_RATE = 0.1;
64
96
  /** Below this (ms) overhead is noise, not worth a recommendation. */
@@ -75,7 +107,8 @@ function recoverableTime(s) {
75
107
  }
76
108
  /** Render the longest critical-path tasks as aligned columns: task (left), duration (right). */
77
109
  function formatTopTaskRows(tasks) {
78
- // The only caller returns early when empty, so `tasks` is non-empty here.
110
+ // Non-empty by construction: the only recommendation carrying task rows requires
111
+ // `criticalPathTop.length > 0` to apply, so no empty array reaches the widths below.
79
112
  const idWidth = Math.max(...tasks.map((t) => t.id.length));
80
113
  const durations = tasks.map((t) => (0, native_1.formatDuration)(t.duration));
81
114
  const durWidth = Math.max(...durations.map((d) => d.length));
@@ -136,8 +169,10 @@ const RECOMMENDATIONS = [
136
169
  },
137
170
  {
138
171
  // Barely-used cache with no remote: set up Nx Cloud. Whole-phrase link; the payload
139
- // string stays URL-less (the popup re-links the phrase).
172
+ // string stays URL-less (the popup re-links the phrase). Never pushed at a
173
+ // workspace that opted out of Nx Cloud.
140
174
  isApplicable: (c) => !c.cacheSkipped &&
175
+ !c.cloudOptedOut &&
141
176
  c.cacheableCount > 0 &&
142
177
  !c.remoteCacheEnabled &&
143
178
  c.cacheHits / c.cacheableCount <= LOW_CACHE_HIT_RATE,
@@ -168,10 +203,8 @@ const RECOMMENDATIONS = [
168
203
  // only multi-line rec). Nothing ran (fully cached) → it doesn't apply.
169
204
  isApplicable: (c) => criticalPathBound(c) && c.criticalPathTop.length > 0,
170
205
  build: (c) => [
171
- [
172
- `Speed up or split the longest tasks on the critical path:`,
173
- ...formatTopTaskRows(c.criticalPathTop),
174
- ].join('\n'),
206
+ `Speed up or split the longest tasks on the critical path:`,
207
+ c.criticalPathTop,
175
208
  ],
176
209
  },
177
210
  ];
@@ -181,6 +214,10 @@ const RECOMMENDATIONS = [
181
214
  * the GitHub-summary Markdown, and the TUI payload.
182
215
  */
183
216
  function buildRecommendations(s) {
217
+ // A fast run has nothing worth optimizing — stats only, no advice.
218
+ if (s.runDuration < exports.MIN_RECOMMENDATION_RUN_DURATION) {
219
+ return [];
220
+ }
184
221
  const c = {
185
222
  recoverableByParallel: s.recoverableByParallel,
186
223
  recoverableByMachines: s.recoverableByMachines,
@@ -194,6 +231,7 @@ function buildRecommendations(s) {
194
231
  cacheableCount: s.cacheableCount,
195
232
  cacheSkipped: s.cacheSkipped,
196
233
  remoteCacheEnabled: s.remoteCacheEnabled,
234
+ cloudOptedOut: s.cloudOptedOut,
197
235
  };
198
236
  return RECOMMENDATIONS.filter((r) => r.isApplicable(c)).map((r) => r.build(c));
199
237
  }
@@ -252,18 +290,17 @@ function formatReport(s) {
252
290
  }
253
291
  /**
254
292
  * A recommendation as Markdown: every link becomes `[phrase](href)` (no OSC 8, unlike the
255
- * terminal renderer) — the whole sentence reads as prose and is the link text. The
256
- * critical-path rec embeds newline-separated, space-aligned task rows; collapse them to
257
- * `<br>`-joined lines so they render inside the list item.
293
+ * terminal renderer) — the whole sentence reads as prose and is the link text. Task rows
294
+ * become a nested list under the recommendation's bullet (space-aligned columns don't
295
+ * survive HTML's whitespace collapsing).
258
296
  */
259
297
  function recommendationToMarkdownString(rec) {
260
- return rec
261
- .map((part) => !isRecLink(part) ? part : `[${part.visible}](${part.href})`)
262
- .join('')
263
- .split('\n')
264
- .map((line) => line.trim())
265
- .filter(Boolean)
266
- .join('<br>');
298
+ return renderRecommendation(rec, {
299
+ link: (link) => `[${link.visible}](${link.href})`,
300
+ taskRows: (rows) => rows
301
+ .map((t) => `\n - \`${t.id}\` — ${(0, native_1.formatDuration)(t.duration)}`)
302
+ .join(''),
303
+ });
267
304
  }
268
305
  /**
269
306
  * The performance report as GitHub-flavored Markdown for the Actions job summary
@@ -25,6 +25,7 @@ const update_manager_1 = require("../nx-cloud/update-manager");
25
25
  const logger_1 = require("../utils/logger");
26
26
  const nx_key_1 = require("../utils/nx-key");
27
27
  const output_1 = require("../utils/output");
28
+ const configure_ai_agents_disclaimer_1 = require("../ai/configure-ai-agents-disclaimer");
28
29
  const sync_generators_1 = require("../utils/sync-generators");
29
30
  const workspace_root_1 = require("../utils/workspace-root");
30
31
  const create_task_graph_1 = require("./create-task-graph");
@@ -410,9 +411,10 @@ async function printConfigureAiAgentsDisclaimer() {
410
411
  return;
411
412
  }
412
413
  const { outdatedAgents } = await client_1.daemonClient.getConfigureAiAgentsStatus();
413
- if (outdatedAgents.length > 0) {
414
- output_1.output.logRawLine(output_1.output.dim('Your AI agent configuration is outdated. Run "nx configure-ai-agents" to update.'));
414
+ if (!(0, configure_ai_agents_disclaimer_1.shouldPrintConfigureAiAgentsDisclaimer)(outdatedAgents, workspace_root_1.workspaceRoot)) {
415
+ return;
415
416
  }
417
+ output_1.output.logRawLine(output_1.output.dim('Your AI agent configuration is outdated. Run "nx configure-ai-agents" to update.'));
416
418
  }
417
419
  catch {
418
420
  // Silently ignore errors
@@ -1,5 +1,14 @@
1
1
  import { ProjectGraph } from '../config/project-graph';
2
2
  import { Task } from '../config/task-graph';
3
+ /**
4
+ * Resolves the FORCE_COLOR value for forked child processes.
5
+ *
6
+ * When the user sets FORCE_COLOR=0, bin/nx.ts deletes it from process.env
7
+ * (workaround for picocolors treating "0" as truthy) and saves the original
8
+ * value in NX_ORIGINAL_FORCE_COLOR. Without this check, the undefined
9
+ * FORCE_COLOR would default to 'true', re-enabling colors in all children.
10
+ */
11
+ export declare function getForceColorForChild(): string;
3
12
  export declare function getEnvVariablesForBatchProcess(skipNxCache: boolean, captureStderr: boolean): NodeJS.ProcessEnv;
4
13
  export declare function getTaskSpecificEnv(task: Task, graph: ProjectGraph): NodeJS.ProcessEnv;
5
14
  export declare function getEnvVariablesForTask(task: Task, taskSpecificEnv: NodeJS.ProcessEnv, forceColor: string, skipNxCache: boolean, captureStderr: boolean, outputPath: string, streamOutput: boolean): NodeJS.ProcessEnv;