nx 23.2.0-beta.2 → 23.2.0-beta.4

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 (58) hide show
  1. package/dist/bin/nx.js +1 -0
  2. package/dist/src/analytics/analytics.js +9 -7
  3. package/dist/src/command-line/graph/graph.d.ts +1 -0
  4. package/dist/src/command-line/graph/graph.js +15 -6
  5. package/dist/src/command-line/migrate/migrate.js +16 -1
  6. package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +1 -1
  7. package/dist/src/command-line/release/utils/remote-release-clients/github.js +1 -1
  8. package/dist/src/command-line/release/utils/remote-release-clients/gitlab.js +1 -1
  9. package/dist/src/config/nx-json.d.ts +4 -2
  10. package/dist/src/core/graph/main.js +1 -1
  11. package/dist/src/native/index.d.ts +2 -2
  12. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  13. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  14. package/dist/src/plugins/js/lock-file/pnpm-parser.js +4 -2
  15. package/dist/src/plugins/js/project-graph/build-dependencies/target-project-locator.js +12 -4
  16. package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.d.ts +1 -0
  17. package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.js +16 -2
  18. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.d.ts +5 -8
  19. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +11 -14
  20. package/dist/src/project-graph/utils/project-configuration/source-maps.d.ts +12 -5
  21. package/dist/src/project-graph/utils/project-configuration/source-maps.js +21 -7
  22. package/dist/src/project-graph/utils/project-configuration/target-defaults.d.ts +1 -1
  23. package/dist/src/project-graph/utils/project-configuration/target-defaults.js +26 -27
  24. package/dist/src/project-graph/utils/project-configuration/target-merging.js +50 -8
  25. package/dist/src/project-graph/utils/project-configuration/target-normalization.js +16 -3
  26. package/dist/src/project-graph/utils/project-configuration/utils.d.ts +15 -0
  27. package/dist/src/project-graph/utils/project-configuration/utils.js +42 -6
  28. package/dist/src/project-graph/utils/project-configuration-utils.d.ts +15 -12
  29. package/dist/src/project-graph/utils/project-configuration-utils.js +79 -99
  30. package/dist/src/tasks-runner/task-env.d.ts +9 -0
  31. package/dist/src/tasks-runner/task-env.js +26 -2
  32. package/dist/src/tasks-runner/task-orchestrator.js +2 -6
  33. package/dist/src/utils/analytics-prompt.d.ts +0 -6
  34. package/dist/src/utils/analytics-prompt.js +0 -51
  35. package/dist/src/utils/catalog/bun-manager-utils.d.ts +8 -0
  36. package/dist/src/utils/catalog/bun-manager-utils.js +174 -0
  37. package/dist/src/utils/catalog/bun-manager.d.ts +28 -0
  38. package/dist/src/utils/catalog/bun-manager.js +130 -0
  39. package/dist/src/utils/catalog/index.d.ts +1 -1
  40. package/dist/src/utils/catalog/index.js +1 -1
  41. package/dist/src/utils/catalog/manager-factory.js +3 -0
  42. package/dist/src/utils/catalog/manager-utils.d.ts +8 -1
  43. package/dist/src/utils/catalog/manager-utils.js +9 -4
  44. package/dist/src/utils/catalog/manager.d.ts +13 -1
  45. package/dist/src/utils/catalog/manager.js +30 -0
  46. package/dist/src/utils/catalog/pnpm-manager.d.ts +2 -1
  47. package/dist/src/utils/catalog/pnpm-manager.js +3 -0
  48. package/dist/src/utils/catalog/types.d.ts +4 -0
  49. package/dist/src/utils/catalog/yarn-manager.d.ts +6 -2
  50. package/dist/src/utils/catalog/yarn-manager.js +23 -32
  51. package/dist/src/utils/git-utils.d.ts +15 -0
  52. package/dist/src/utils/git-utils.js +64 -6
  53. package/dist/src/utils/package-json.js +11 -7
  54. package/dist/src/utils/package-manager.d.ts +3 -1
  55. package/dist/src/utils/package-manager.js +22 -2
  56. package/dist/src/utils/workspace-id.d.ts +20 -0
  57. package/dist/src/utils/workspace-id.js +54 -0
  58. package/package.json +22 -17
@@ -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,108 +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 via
229
- // spread merges, introducing name-ref strings that weren't visible in any
230
- // single plugin result. Re-walking the final merged targets sentinelizes
231
- // them so the final substitution sweep resolves them too.
232
- nodesManager.registerNameRefs(intermediateDefaultRootMap);
233
- // Overlay default-plugin attribution onto the main source maps using
234
- // "only fill missing" semantics. Any key already present in
235
- // configurationSourceMaps was written by a specified plugin or by
236
- // target defaults, and that attribution is strictly more correct:
237
- // - For fields the default plugin never shadowed, the existing entry
238
- // already matches what the default plugin would overlay.
239
- // - For fields where a default plugin placed `...` after other keys,
240
- // those keys yielded to the base during the single-layer apply
241
- // above. The stale default-plugin entry in
242
- // `defaultConfigurationSourceMaps` must NOT clobber the base
243
- // attribution that the specified plugin / TD already recorded.
244
- const mainRootMap = nodesManager.getRootMap();
245
- for (const root in defaultConfigurationSourceMaps) {
246
- const existing = (configurationSourceMaps[root] ??= {});
247
- const incoming = defaultConfigurationSourceMaps[root];
248
- // A default plugin's targets are synthesized into the main rootmap by
249
- // target defaults *before* the default layer is applied, so target
250
- // defaults wrote the main source map's entry for the target node and its
251
- // identity fields first — even though it never authored them (it only
252
- // stamps them as a merge guard and cannot bring a target into existence).
253
- // The identity fields are the executor/command plus, for run-commands, the
254
- // `options.command`/`options.commands` the synthetic copies from the winner
255
- // to stay compatible (#36067). For those keys, the real default plugin's
256
- // attribution must override the target-defaults stamp.
257
- const identityKeys = new Set();
258
- for (const targetName in mainRootMap[root]?.targets ?? {}) {
259
- const base = (0, source_maps_1.targetSourceMapKey)(targetName);
260
- identityKeys.add(base);
261
- identityKeys.add(`${base}.executor`);
262
- identityKeys.add(`${base}.command`);
263
- identityKeys.add(`${base}.options.command`);
264
- identityKeys.add(`${base}.options.commands`);
265
- }
266
- for (const key in incoming) {
267
- if (existing[key] === undefined) {
268
- existing[key] = incoming[key];
269
- }
270
- else if (identityKeys.has(key) &&
271
- existing[key][1] === source_maps_1.TARGET_DEFAULTS_PLUGIN_NAME) {
272
- existing[key] = incoming[key];
273
- }
274
- }
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);
275
255
  }
276
256
  const projectRootMap = nodesManager.getRootMap();
277
257
  try {
@@ -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;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getForceColorForChild = getForceColorForChild;
3
4
  exports.getEnvVariablesForBatchProcess = getEnvVariablesForBatchProcess;
4
5
  exports.getTaskSpecificEnv = getTaskSpecificEnv;
5
6
  exports.getEnvVariablesForTask = getEnvVariablesForTask;
@@ -11,13 +12,33 @@ const dotenv_expand_1 = require("dotenv-expand");
11
12
  const node_path_1 = require("node:path");
12
13
  const workspace_root_1 = require("../utils/workspace-root");
13
14
  const task_env_paths_1 = require("./task-env-paths");
15
+ /**
16
+ * Resolves the FORCE_COLOR value for forked child processes.
17
+ *
18
+ * When the user sets FORCE_COLOR=0, bin/nx.ts deletes it from process.env
19
+ * (workaround for picocolors treating "0" as truthy) and saves the original
20
+ * value in NX_ORIGINAL_FORCE_COLOR. Without this check, the undefined
21
+ * FORCE_COLOR would default to 'true', re-enabling colors in all children.
22
+ */
23
+ function getForceColorForChild() {
24
+ if (process.env.FORCE_COLOR !== undefined) {
25
+ return process.env.FORCE_COLOR;
26
+ }
27
+ if (process.env.NX_ORIGINAL_FORCE_COLOR === '0') {
28
+ return '0';
29
+ }
30
+ return 'true';
31
+ }
14
32
  function getEnvVariablesForBatchProcess(skipNxCache, captureStderr) {
15
- return {
33
+ const res = {
16
34
  // User Process Env Variables override Dotenv Variables
17
35
  ...process.env,
18
36
  // Nx Env Variables overrides everything
19
- ...getNxEnvVariablesForForkedProcess(process.env.FORCE_COLOR === undefined ? 'true' : process.env.FORCE_COLOR, skipNxCache, captureStderr),
37
+ ...getNxEnvVariablesForForkedProcess(getForceColorForChild(), skipNxCache, captureStderr),
20
38
  };
39
+ // NX_ORIGINAL_FORCE_COLOR is an internal signal and should not leak into child processes
40
+ delete res.NX_ORIGINAL_FORCE_COLOR;
41
+ return res;
21
42
  }
22
43
  // The orchestrator now calls this eagerly during the coordinator pre-hash
23
44
  // in addition to processTask (and again in hashBatchTasks), so the same
@@ -58,6 +79,9 @@ function getEnvVariablesForTask(task, taskSpecificEnv, forceColor, skipNxCache,
58
79
  }
59
80
  // we don't reset NX_BASE or NX_HEAD because those are set by the user and should be preserved
60
81
  delete res.NX_SET_CLI;
82
+ // NX_ORIGINAL_FORCE_COLOR is an internal signal used by getForceColorForChild()
83
+ // and should not leak into child processes
84
+ delete res.NX_ORIGINAL_FORCE_COLOR;
61
85
  return res;
62
86
  }
63
87
  function getNxEnvVariablesForForkedProcess(forceColor, skipNxCache, captureStderr, outputPath, streamOutput) {
@@ -731,9 +731,7 @@ class TaskOrchestrator {
731
731
  ? false
732
732
  : (0, utils_1.shouldStreamOutput)(task, this.initiatingProject);
733
733
  const env = pipeOutput
734
- ? (0, task_env_1.getEnvVariablesForTask)(task, taskSpecificEnv, process.env.FORCE_COLOR === undefined
735
- ? 'true'
736
- : process.env.FORCE_COLOR, this.options.skipNxCache, this.options.captureStderr, null, null)
734
+ ? (0, task_env_1.getEnvVariablesForTask)(task, taskSpecificEnv, (0, task_env_1.getForceColorForChild)(), this.options.skipNxCache, this.options.captureStderr, null, null)
737
735
  : (0, task_env_1.getEnvVariablesForTask)(task, taskSpecificEnv, undefined, this.options.skipNxCache, this.options.captureStderr, temporaryOutputPath, streamOutput);
738
736
  let resolveDiscreteExit;
739
737
  const discreteExitHandled = new Promise((r) => (resolveDiscreteExit = r));
@@ -944,9 +942,7 @@ class TaskOrchestrator {
944
942
  ? false
945
943
  : (0, utils_1.shouldStreamOutput)(task, this.initiatingProject);
946
944
  let env = pipeOutput
947
- ? (0, task_env_1.getEnvVariablesForTask)(task, taskSpecificEnv, process.env.FORCE_COLOR === undefined
948
- ? 'true'
949
- : process.env.FORCE_COLOR, this.options.skipNxCache, this.options.captureStderr, null, null)
945
+ ? (0, task_env_1.getEnvVariablesForTask)(task, taskSpecificEnv, (0, task_env_1.getForceColorForChild)(), this.options.skipNxCache, this.options.captureStderr, null, null)
950
946
  : (0, task_env_1.getEnvVariablesForTask)(task, taskSpecificEnv, undefined, this.options.skipNxCache, this.options.captureStderr, temporaryOutputPath, streamOutput);
951
947
  this.detectTaskInvocationLoop(task);
952
948
  const childProcess = await this.runTask(task, streamOutput, env, temporaryOutputPath, pipeOutput);
@@ -7,9 +7,3 @@
7
7
  */
8
8
  export declare function ensureAnalyticsPreferenceSet(root?: string, interactive?: boolean): Promise<'yes' | 'no' | 'unset'>;
9
9
  export declare function promptForAnalyticsPreference(): Promise<boolean>;
10
- /**
11
- * Generates a deterministic workspace ID.
12
- * Priority: nxCloudId > git remote URL (hashed).
13
- * Returns null if neither is available (no telemetry).
14
- */
15
- export declare function generateWorkspaceId(cwd?: string): string | null;
@@ -2,9 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ensureAnalyticsPreferenceSet = ensureAnalyticsPreferenceSet;
4
4
  exports.promptForAnalyticsPreference = promptForAnalyticsPreference;
5
- exports.generateWorkspaceId = generateWorkspaceId;
6
- const crypto_1 = require("crypto");
7
- const child_process_1 = require("child_process");
8
5
  const fs_1 = require("fs");
9
6
  const enquirer_1 = require("enquirer");
10
7
  const path_1 = require("path");
@@ -85,51 +82,3 @@ async function saveAnalyticsPreference(root, enabled) {
85
82
  // Silently fail - don't block user's command
86
83
  }
87
84
  }
88
- /**
89
- * Generates a deterministic workspace ID.
90
- * Priority: nxCloudId > git remote URL (hashed).
91
- * Returns null if neither is available (no telemetry).
92
- */
93
- function generateWorkspaceId(cwd) {
94
- const root = cwd ?? workspace_root_1.workspaceRoot;
95
- // Use nxCloudId if available — most stable identifier
96
- const nxJson = (0, nx_json_1.readNxJson)(root);
97
- const nxCloudId = nxJson?.nxCloudId ?? nxJson?.nxCloudAccessToken;
98
- if (nxCloudId) {
99
- return nxCloudId;
100
- }
101
- // Fall back to git remote URL hash
102
- try {
103
- const remoteUrl = (0, child_process_1.execSync)('git remote get-url origin', {
104
- stdio: 'pipe',
105
- cwd: root,
106
- windowsHide: true,
107
- })
108
- .toString()
109
- .trim();
110
- if (remoteUrl) {
111
- return (0, crypto_1.createHash)('sha256').update(remoteUrl).digest('hex').slice(0, 32);
112
- }
113
- }
114
- catch {
115
- // No git remote available
116
- }
117
- // Fall back to first commit SHA — already a hash
118
- try {
119
- const firstCommit = (0, child_process_1.execSync)('git rev-list --max-parents=0 HEAD', {
120
- stdio: 'pipe',
121
- cwd: root,
122
- windowsHide: true,
123
- })
124
- .toString()
125
- .trim()
126
- .split('\n')[0];
127
- if (firstCommit) {
128
- return firstCommit;
129
- }
130
- }
131
- catch {
132
- // Not a git repo
133
- }
134
- return null;
135
- }
@@ -0,0 +1,8 @@
1
+ import type { Tree } from '../../generators/tree';
2
+ import type { CatalogDefinitions } from './types';
3
+ export declare function readBunCatalogDefinitions(filename: string, treeOrRoot: Tree | string, cache: Map<string, CatalogDefinitions | null>): CatalogDefinitions | null;
4
+ export declare function updateBunCatalogVersionsInFile(filename: string, treeOrRoot: Tree | string, updates: Array<{
5
+ packageName: string;
6
+ version: string;
7
+ catalogName?: string;
8
+ }>): void;
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readBunCatalogDefinitions = readBunCatalogDefinitions;
4
+ exports.updateBunCatalogVersionsInFile = updateBunCatalogVersionsInFile;
5
+ const jsonc_parser_1 = require("jsonc-parser");
6
+ const node_fs_1 = require("node:fs");
7
+ const node_path_1 = require("node:path");
8
+ const fileutils_1 = require("../fileutils");
9
+ const json_1 = require("../json");
10
+ const output_1 = require("../output");
11
+ // Extracts catalog definitions from a parsed bun package.json, normalizing the
12
+ // top-level and `workspaces`-nested locations into a single CatalogDefinitions.
13
+ // Bun treats the locations as all-or-nothing: when either catalog field exists
14
+ // under `workspaces`, the top-level fields are ignored entirely rather than
15
+ // merged.
16
+ function normalizeBunCatalogDefinitions(packageJson) {
17
+ const nested = packageJson.workspaces && !Array.isArray(packageJson.workspaces)
18
+ ? packageJson.workspaces
19
+ : undefined;
20
+ const source = nested?.catalog !== undefined || nested?.catalogs !== undefined
21
+ ? nested
22
+ : packageJson;
23
+ const { catalog, catalogs } = source;
24
+ if (!catalog && !catalogs) {
25
+ return null;
26
+ }
27
+ return { catalog, catalogs };
28
+ }
29
+ function readBunCatalogConfigFromFs(filename, fullPath) {
30
+ try {
31
+ return normalizeBunCatalogDefinitions((0, fileutils_1.readJsonFile)(fullPath));
32
+ }
33
+ catch (error) {
34
+ output_1.output.warn({
35
+ title: `Unable to parse ${filename}`,
36
+ bodyLines: [error.toString()],
37
+ });
38
+ return null;
39
+ }
40
+ }
41
+ function readBunCatalogConfigFromTree(filename, tree) {
42
+ const content = tree.read(filename, 'utf-8');
43
+ try {
44
+ return normalizeBunCatalogDefinitions((0, json_1.parseJson)(content));
45
+ }
46
+ catch (error) {
47
+ output_1.output.warn({
48
+ title: `Unable to parse ${filename}`,
49
+ bodyLines: [error.toString()],
50
+ });
51
+ return null;
52
+ }
53
+ }
54
+ // Mirror of readCatalogDefinitions for bun's package.json-based catalogs: the
55
+ // fs (string-root) branch is cached per pass, the Tree branch stays live.
56
+ function readBunCatalogDefinitions(filename, treeOrRoot, cache) {
57
+ if (typeof treeOrRoot === 'string') {
58
+ if (cache.has(treeOrRoot)) {
59
+ return cache.get(treeOrRoot);
60
+ }
61
+ const configPath = (0, node_path_1.join)(treeOrRoot, filename);
62
+ const defs = (0, node_fs_1.existsSync)(configPath)
63
+ ? readBunCatalogConfigFromFs(filename, configPath)
64
+ : null;
65
+ cache.set(treeOrRoot, defs);
66
+ return defs;
67
+ }
68
+ if (!treeOrRoot.exists(filename)) {
69
+ return null;
70
+ }
71
+ return readBunCatalogConfigFromTree(filename, treeOrRoot);
72
+ }
73
+ function isRecord(value) {
74
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
75
+ }
76
+ // Resolves the jsonc path a catalog update should target. Follows the same
77
+ // all-or-nothing routing as the reader: updates land in the `workspaces`
78
+ // location when it holds a catalog field, otherwise at the top level.
79
+ function resolveBunCatalogTargetPath(packageJson, packageName, catalogName) {
80
+ const nested = packageJson.workspaces && !Array.isArray(packageJson.workspaces)
81
+ ? packageJson.workspaces
82
+ : undefined;
83
+ const nestedIsActive = nested?.catalog !== undefined || nested?.catalogs !== undefined;
84
+ const prefix = nestedIsActive ? ['workspaces'] : [];
85
+ return catalogName
86
+ ? [...prefix, 'catalogs', catalogName, packageName]
87
+ : [...prefix, 'catalog', packageName];
88
+ }
89
+ // jsonc-parser's `modify` creates missing intermediate objects but throws on
90
+ // null (or other non-object) ones — the JSON counterpart of pnpm's empty
91
+ // `catalog:` placeholder. Walk the target path and, at the first non-object
92
+ // step, replace that node wholesale with the remaining path nested as fresh
93
+ // objects.
94
+ function planBunCatalogEdit(packageJson, targetPath, version) {
95
+ let node = packageJson;
96
+ for (let i = 0; i < targetPath.length - 1; i++) {
97
+ if (!isRecord(node)) {
98
+ break;
99
+ }
100
+ const next = node[targetPath[i]];
101
+ if (next !== undefined && !isRecord(next)) {
102
+ const value = targetPath
103
+ .slice(i + 1)
104
+ .reduceRight((acc, key) => ({ [key]: acc }), version);
105
+ return { path: targetPath.slice(0, i + 1), value };
106
+ }
107
+ node = next;
108
+ }
109
+ return { path: targetPath, value: version };
110
+ }
111
+ function updateBunCatalogVersionsInFile(filename, treeOrRoot, updates) {
112
+ let checkExists;
113
+ let readContent;
114
+ let writeContent;
115
+ if (typeof treeOrRoot === 'string') {
116
+ const configPath = (0, node_path_1.join)(treeOrRoot, filename);
117
+ checkExists = () => (0, node_fs_1.existsSync)(configPath);
118
+ readContent = () => (0, node_fs_1.readFileSync)(configPath, 'utf-8');
119
+ writeContent = (content) => (0, node_fs_1.writeFileSync)(configPath, content, 'utf-8');
120
+ }
121
+ else {
122
+ checkExists = () => treeOrRoot.exists(filename);
123
+ readContent = () => treeOrRoot.read(filename, 'utf-8');
124
+ writeContent = (content) => treeOrRoot.write(filename, content);
125
+ }
126
+ if (!checkExists()) {
127
+ output_1.output.warn({
128
+ title: `No ${filename} found`,
129
+ bodyLines: [
130
+ `Cannot update catalog versions without a ${filename} file.`,
131
+ `Create a ${filename} file to use catalogs.`,
132
+ ],
133
+ });
134
+ return;
135
+ }
136
+ try {
137
+ let content = readContent();
138
+ // parseJson surfaces a genuine syntax error here rather than letting a
139
+ // broken file be silently rewritten.
140
+ let packageJson = (0, json_1.parseJson)(content);
141
+ let hasChanges = false;
142
+ for (const update of updates) {
143
+ const { packageName, version, catalogName } = update;
144
+ const targetPath = resolveBunCatalogTargetPath(packageJson, packageName, catalogName);
145
+ // `modify` emits an edit even for an identical value, so check first to
146
+ // keep an already-matching file untouched.
147
+ const currentValue = targetPath.reduce((node, key) => (isRecord(node) ? node[key] : undefined), packageJson);
148
+ if (currentValue === version) {
149
+ continue;
150
+ }
151
+ const { path, value } = planBunCatalogEdit(packageJson, targetPath, version);
152
+ const edits = (0, jsonc_parser_1.modify)(content, path, value, {
153
+ formattingOptions: { insertSpaces: true, tabSize: 2 },
154
+ });
155
+ if (edits.length > 0) {
156
+ content = (0, jsonc_parser_1.applyEdits)(content, edits);
157
+ // Re-parse so the next update routes against the just-applied edit
158
+ // (e.g. a null `catalog` placeholder replaced with a fresh map).
159
+ packageJson = (0, json_1.parseJson)(content);
160
+ hasChanges = true;
161
+ }
162
+ }
163
+ if (hasChanges) {
164
+ writeContent(content);
165
+ }
166
+ }
167
+ catch (error) {
168
+ output_1.output.error({
169
+ title: 'Failed to update catalog versions',
170
+ bodyLines: [error instanceof Error ? error.message : String(error)],
171
+ });
172
+ throw error;
173
+ }
174
+ }
@@ -0,0 +1,28 @@
1
+ import type { Tree } from '../../generators/tree';
2
+ import { type CatalogManager } from './manager';
3
+ import type { CatalogDefinitions, CatalogReference, CatalogReferenceMatch } from './types';
4
+ /**
5
+ * Bun-specific catalog manager implementation.
6
+ *
7
+ * Bun declares catalogs in the root package.json `catalog`/`catalogs` fields,
8
+ * either at the top level or nested under the object form of `workspaces`.
9
+ * Unlike pnpm, the name "default" is not special: `catalog:` resolves only
10
+ * against `catalog`, and `catalog:default` against `catalogs.default`.
11
+ */
12
+ export declare class BunCatalogManager implements CatalogManager {
13
+ readonly name = "bun";
14
+ readonly catalogProtocol = "catalog:";
15
+ private definitionsByRoot;
16
+ isCatalogReference(version: string): boolean;
17
+ parseCatalogReference(version: string): CatalogReference | null;
18
+ getCatalogDefinitionFilePaths(): string[];
19
+ getCatalogDefinitions(treeOrRoot: Tree | string): CatalogDefinitions | null;
20
+ resolveCatalogReference(treeOrRoot: Tree | string, packageName: string, version: string): string | null;
21
+ getCatalogReferencesForPackage(treeOrRoot: Tree | string, packageName: string): CatalogReferenceMatch[];
22
+ validateCatalogReference(treeOrRoot: Tree | string, packageName: string, version: string): void;
23
+ updateCatalogVersions(treeOrRoot: Tree | string, updates: Array<{
24
+ packageName: string;
25
+ version: string;
26
+ catalogName?: string;
27
+ }>): void;
28
+ }