nx 23.0.0 → 23.0.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 (59) hide show
  1. package/README.md +1 -2
  2. package/dist/bin/init-local.js +1 -1
  3. package/dist/release/changelog-renderer/index.d.ts +1 -0
  4. package/dist/release/changelog-renderer/index.js +27 -12
  5. package/dist/src/command-line/init/implementation/dot-nx/add-nx-scripts.d.ts +1 -0
  6. package/dist/src/command-line/init/implementation/dot-nx/add-nx-scripts.js +6 -1
  7. package/dist/src/command-line/init/init-v2.js +1 -1
  8. package/dist/src/command-line/migrate/agentic/prompts/system-prompt.js +3 -2
  9. package/dist/src/command-line/migrate/migrate.d.ts +2 -0
  10. package/dist/src/command-line/migrate/migrate.js +34 -7
  11. package/dist/src/command-line/migrate/resolve-package-version.js +3 -25
  12. package/dist/src/command-line/migrate/update-filters.js +2 -1
  13. package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.d.ts +1 -1
  14. package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +37 -20
  15. package/dist/src/command-line/release/version/resolve-current-version.js +4 -1
  16. package/dist/src/core/graph/main.js +1 -1
  17. package/dist/src/daemon/server/project-graph-incremental-recomputation.js +30 -11
  18. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  19. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  20. package/dist/src/plugins/js/lock-file/bun-parser.js +61 -38
  21. package/dist/src/plugins/js/lock-file/utils/pnpm-normalizer.js +1 -3
  22. package/dist/src/plugins/js/package-json/create-package-json.js +22 -1
  23. package/dist/src/plugins/js/project-graph/affected/tsconfig-json-changes.js +1 -1
  24. package/dist/src/plugins/js/project-graph/build-dependencies/target-project-locator.d.ts +7 -0
  25. package/dist/src/plugins/js/project-graph/build-dependencies/target-project-locator.js +26 -1
  26. package/dist/src/plugins/js/utils/register.d.ts +31 -10
  27. package/dist/src/plugins/js/utils/register.js +87 -18
  28. package/dist/src/plugins/package-json/create-nodes.js +5 -3
  29. package/dist/src/project-graph/operators.d.ts +0 -1
  30. package/dist/src/project-graph/operators.js +0 -44
  31. package/dist/src/project-graph/project-graph-builder.js +26 -10
  32. package/dist/src/project-graph/utils/project-configuration/target-defaults.js +23 -6
  33. package/dist/src/project-graph/utils/project-configuration-utils.js +28 -28
  34. package/dist/src/tasks-runner/batch/run-batch.js +3 -0
  35. package/dist/src/tasks-runner/cache.js +6 -0
  36. package/dist/src/tasks-runner/pseudo-terminal.d.ts +1 -0
  37. package/dist/src/tasks-runner/pseudo-terminal.js +18 -4
  38. package/dist/src/tasks-runner/run-command.js +24 -8
  39. package/dist/src/tasks-runner/utils.d.ts +1 -0
  40. package/dist/src/tasks-runner/utils.js +4 -0
  41. package/dist/src/utils/ab-testing.d.ts +9 -0
  42. package/dist/src/utils/ab-testing.js +18 -4
  43. package/dist/src/utils/catalog/index.d.ts +7 -0
  44. package/dist/src/utils/catalog/index.js +31 -0
  45. package/dist/src/utils/catalog/manager-utils.d.ts +9 -0
  46. package/dist/src/utils/catalog/manager-utils.js +215 -0
  47. package/dist/src/utils/catalog/pnpm-manager.js +4 -102
  48. package/dist/src/utils/catalog/yarn-manager.js +4 -102
  49. package/dist/src/utils/is-sandbox.js +2 -0
  50. package/dist/src/utils/min-release-age/behavior/pnpm.d.ts +6 -5
  51. package/dist/src/utils/min-release-age/behavior/pnpm.js +87 -339
  52. package/dist/src/utils/package-json.d.ts +1 -0
  53. package/dist/src/utils/package-manager.d.ts +6 -3
  54. package/dist/src/utils/package-manager.js +21 -7
  55. package/dist/src/utils/spinner.d.ts +13 -1
  56. package/dist/src/utils/spinner.js +20 -4
  57. package/dist/src/utils/terminal-link.d.ts +16 -0
  58. package/dist/src/utils/terminal-link.js +87 -0
  59. package/package.json +19 -15
package/README.md CHANGED
@@ -7,13 +7,12 @@
7
7
 
8
8
  <div style="text-align: center;">
9
9
 
10
- [![CircleCI](https://circleci.com/gh/nrwl/nx.svg?style=svg)](https://circleci.com/gh/nrwl/nx)
11
10
  [![License](https://img.shields.io/npm/l/@nx/workspace.svg?style=flat-square)]()
12
11
  [![NPM Version](https://badge.fury.io/js/nx.svg)](https://www.npmjs.com/package/nx)
13
12
  [![Semantic Release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=flat-square)]()
14
13
  [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
15
- [![Join the chat at https://gitter.im/nrwl-nx/community](https://badges.gitter.im/nrwl-nx/community.svg)](https://gitter.im/nrwl-nx/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
16
14
  [![Join us on the Official Nx Discord Server](https://img.shields.io/discord/1143497901675401286?label=discord)](https://go.nx.dev/community)
15
+ [![Nx Sandboxing](https://staging.nx.app/workspaces/62d013ea0852fe0a2df74438/sandbox-badge.svg)](https://nx.dev/docs/features/ci-features/sandboxing)
17
16
 
18
17
  </div>
19
18
 
@@ -162,7 +162,7 @@ function handleAngularCLIFallbacks(workspace) {
162
162
  console.log(`- fix migrations that "almost work"`);
163
163
  console.log(`- commit a partially migrated state`);
164
164
  console.log(`- change versions of packages to match organizational requirements`);
165
- console.log(`And, in general, it is lot more reliable for non-trivial workspaces. Read more at: https://nx.dev/getting-started/nx-and-angular#ng-update-and-nx-migrate`);
165
+ console.log(`And, in general, it is lot more reliable for non-trivial workspaces. Read more at: https://nx.dev/docs/technologies/angular/guides/nx-and-angular#ng-update-vs-nx-migrate`);
166
166
  console.log(`Run "nx migrate latest" to update to the latest version of Nx.`);
167
167
  console.log(`Running "ng update" can still be useful in some dev workflows, so we aren't planning to remove it.`);
168
168
  console.log(`If you need to use it, run "FORCE_NG_UPDATE=true ng update".`);
@@ -105,5 +105,6 @@ export default class DefaultChangelogRenderer {
105
105
  protected groupChangesByType(): Record<string, ChangelogChange[]>;
106
106
  protected groupChangesByScope(changes: ChangelogChange[]): Record<string, ChangelogChange[]>;
107
107
  protected extractBreakingChangeExplanation(message: string): string | null;
108
+ private isBreakingChangeBoundary;
108
109
  protected formatName(name?: string): string;
109
110
  }
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const semver_1 = require("semver");
4
+ // Stripped from breaking change explanations (e.g. bot/session markers).
5
+ const HtmlCommentRegex = /<!--[\s\S]*?-->/g;
4
6
  class DefaultChangelogRenderer {
5
7
  /**
6
8
  * A ChangelogRenderer class takes in the determined changes and other relevant metadata
@@ -342,20 +344,33 @@ class DefaultChangelogRenderer {
342
344
  return null;
343
345
  }
344
346
  const startOfBreakingChange = startIndex + breakingChangeIdentifier.length;
345
- // Extract all text after BREAKING CHANGE: until we hit a Co-authored-by section or git metadata
346
- let endOfBreakingChange = message.length;
347
- const coAuthoredBySection = message.indexOf('---------\n\nCo-authored-by:');
348
- if (coAuthoredBySection !== -1) {
349
- endOfBreakingChange = coAuthoredBySection;
350
- }
351
- else {
352
- // Look for the git metadata delimiter (a line with just ")
353
- const gitMetadataMarker = message.indexOf('"\n', startOfBreakingChange);
354
- if (gitMetadataMarker !== -1) {
355
- endOfBreakingChange = gitMetadataMarker;
347
+ // A squash-merged PR body can trail the note with template sections,
348
+ // trailers, and git metadata. Strip comments (they can appear anywhere),
349
+ // then keep lines until the first boundary.
350
+ const lines = message
351
+ .slice(startOfBreakingChange)
352
+ .replace(HtmlCommentRegex, '')
353
+ .split('\n');
354
+ // The rest of the "BREAKING CHANGE:" line is always kept; scan from the next.
355
+ const explanationLines = [lines[0]];
356
+ for (let i = 1; i < lines.length; i++) {
357
+ if (this.isBreakingChangeBoundary(lines[i])) {
358
+ break;
356
359
  }
360
+ explanationLines.push(lines[i]);
357
361
  }
358
- return message.substring(startOfBreakingChange, endOfBreakingChange).trim();
362
+ return explanationLines.join('\n').trim();
363
+ }
364
+ isBreakingChangeBoundary(line) {
365
+ const trimmed = line.trim();
366
+ return (
367
+ // Markdown heading (e.g. "## Related issues")
368
+ /^#{1,6}\s/.test(trimmed) ||
369
+ // Horizontal rule / separator
370
+ /^-{3,}$/.test(trimmed) ||
371
+ /^Co-authored-by:/i.test(trimmed) ||
372
+ // Git metadata delimiter following the body
373
+ trimmed === '"');
359
374
  }
360
375
  formatName(name = '') {
361
376
  return name
@@ -1,5 +1,6 @@
1
1
  import { Tree } from '../../../../generators/tree';
2
2
  export declare const nxWrapperPath: (p?: typeof import("path")) => string;
3
+ export declare function getDotNxWrapperVersionCommand(platform?: NodeJS.Platform): string;
3
4
  export declare function generateDotNxSetup(version?: string): void;
4
5
  export declare function normalizeVersionForNxJson(pkg: string, version: string): string;
5
6
  export declare function writeMinimalNxJson(host: Tree, version: string): void;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.nxWrapperPath = void 0;
4
+ exports.getDotNxWrapperVersionCommand = getDotNxWrapperVersionCommand;
4
5
  exports.generateDotNxSetup = generateDotNxSetup;
5
6
  exports.normalizeVersionForNxJson = normalizeVersionForNxJson;
6
7
  exports.writeMinimalNxJson = writeMinimalNxJson;
@@ -50,6 +51,10 @@ const SHELL_SCRIPT_CONTENTS = [
50
51
  // Executes the nx wrapper script
51
52
  `node ${path.posix.join('$path_to_root', (0, exports.nxWrapperPath)(path.posix))} "$@"`,
52
53
  ].join('\n');
54
+ // cmd.exe can't run the bash './nx' wrapper; use the generated nx.bat on Windows.
55
+ function getDotNxWrapperVersionCommand(platform = process.platform) {
56
+ return platform === 'win32' ? '.\\nx.bat --version' : './nx --version';
57
+ }
53
58
  function generateDotNxSetup(version) {
54
59
  const host = new tree_1.FsTree(process.cwd(), false, '.nx setup');
55
60
  writeMinimalNxJson(host, version);
@@ -66,7 +71,7 @@ function generateDotNxSetup(version) {
66
71
  // This is needed when using a global nx with dot-nx, otherwise running any nx command using global command will fail due to missing modules.
67
72
  // Pipe stderr so failures surface in telemetry instead of bare "Command failed: ./nx --version".
68
73
  try {
69
- (0, child_process_1.execSync)('./nx --version', {
74
+ (0, child_process_1.execSync)(getDotNxWrapperVersionCommand(), {
70
75
  stdio: ['ignore', 'ignore', 'pipe'],
71
76
  encoding: 'utf8',
72
77
  windowsHide: true,
@@ -348,7 +348,7 @@ async function runInit(options, baseMeta) {
348
348
  }
349
349
  else {
350
350
  nxCloudChoice = options.interactive
351
- ? await (0, connect_to_nx_cloud_1.connectExistingRepoToNxCloudPrompt)()
351
+ ? await (0, connect_to_nx_cloud_1.connectExistingRepoToNxCloudPrompt)('init', 'setupNxCloud', false)
352
352
  : 'skip';
353
353
  }
354
354
  if (nxCloudChoice === 'yes') {
@@ -80,9 +80,10 @@ function buildScopeRules(mode) {
80
80
  return [
81
81
  `<scope_rules>`,
82
82
  `- Apply only the changes the migration prompt asks for.`,
83
- `- Do not refactor, reformat, or update dependencies beyond what the migration prompt directs.`,
83
+ `- Do not refactor or update dependencies beyond what the migration prompt directs, and do not reformat files you did not change.`,
84
+ `- After applying your changes and before writing the handoff, format the files you created or modified so they match the workspace's style. If the workspace uses Prettier, run \`nx format:write\`, which formats your uncommitted changes; if it has no formatter configured, skip this.`,
84
85
  `- Do not modify files outside the workspace root.`,
85
- `- Do not run other \`nx\` commands that mutate workspace state (\`nx migrate\`, \`nx reset\`, \`nx run-many\`, generators, etc.). Read-only inspection (\`nx show\`, \`nx graph --file\`, reading files) is fine.`,
86
+ `- Do not run other \`nx\` commands that mutate workspace state (\`nx migrate\`, \`nx reset\`, \`nx run-many\`, generators, etc.), except \`nx format:write\` to format the files you changed. Read-only inspection (\`nx show\`, \`nx graph --file\`, reading files) is fine.`,
86
87
  `- If the migration instructions are unclear, internally inconsistent, or conflict with the current workspace state, ask the user for direction (see the handoff contract). Do not guess.`,
87
88
  `</scope_rules>`,
88
89
  ].join('\n');
@@ -85,6 +85,7 @@ export declare class Migrator {
85
85
  }>;
86
86
  private createMigrateJson;
87
87
  private buildPackageJsonUpdates;
88
+ private resolveVersionForCascade;
88
89
  private populatePackageJsonUpdatesAndGetPackagesToCheck;
89
90
  private getPackageJsonUpdatesFromMigrationConfig;
90
91
  /**
@@ -172,6 +173,7 @@ export declare function createFetcher(pmc: PackageManagerCommands): ((pkg: strin
172
173
  stats?: MigrateFetchStats;
173
174
  };
174
175
  export { filterDowngradedUpdates };
176
+ export declare function generateMigrationsJsonAndUpdatePackageJson(root: string, opts: GenerateMigrations, fetch?: MigratorOptions['fetch']): Promise<void>;
175
177
  /**
176
178
  * Detects npm peer-dependency resolution failures. Keyed on the `ERESOLVE`
177
179
  * error code, which npm consistently emits for this class of failure across
@@ -6,6 +6,7 @@ exports.resolveCanonicalNxPackage = resolveCanonicalNxPackage;
6
6
  exports.resolveInclude = resolveInclude;
7
7
  exports.parseMigrationsOptions = parseMigrationsOptions;
8
8
  exports.createFetcher = createFetcher;
9
+ exports.generateMigrationsJsonAndUpdatePackageJson = generateMigrationsJsonAndUpdatePackageJson;
9
10
  exports.isNpmPeerDepsError = isNpmPeerDepsError;
10
11
  exports.resolveAgenticRunId = resolveAgenticRunId;
11
12
  exports.formatSkippedPromptsNextStep = formatSkippedPromptsNextStep;
@@ -191,16 +192,37 @@ class Migrator {
191
192
  !this.areIncompatiblePackagesPresent(packageUpdate.incompatibleWith) &&
192
193
  (!this.interactive ||
193
194
  (await this.runPackageJsonUpdatesConfirmationPrompt(packageUpdate, packageUpdateKey, packageToCheck.package)))) {
194
- Object.entries(packageUpdate.packages).forEach(([name, update]) => {
195
+ const updateEntries = Object.entries(packageUpdate.packages);
196
+ // Validate all up front so invalid metadata fails fast, before any
197
+ // resolution does I/O.
198
+ for (const [name, update] of updateEntries) {
195
199
  this.validatePackageUpdateVersion(packageToCheck.package, name, update);
196
- filteredUpdates[name] = update;
197
- this.packageUpdates[name] = update;
198
- });
200
+ }
201
+ // Resolve serially: resolution can prompt (pnpm strict cooldown) and
202
+ // append to minimumReleaseAgeExclude, so a serial loop avoids
203
+ // overlapping prompts and keeps packageUpdates ordering stable.
204
+ for (const [name, update] of updateEntries) {
205
+ const resolvedUpdate = {
206
+ ...update,
207
+ version: await this.resolveVersionForCascade(name, update.version),
208
+ };
209
+ filteredUpdates[name] = resolvedUpdate;
210
+ this.packageUpdates[name] = resolvedUpdate;
211
+ }
199
212
  }
200
213
  }
201
214
  await Promise.all(Object.entries(filteredUpdates).map(([name, update]) => this.buildPackageJsonUpdates(name, update)));
202
215
  }
203
216
  }
217
+ async resolveVersionForCascade(packageName, version) {
218
+ // Already a fully-qualified semver (incl. prereleases) - nothing to resolve.
219
+ if ((0, semver_1.valid)(version)) {
220
+ return version;
221
+ }
222
+ // Otherwise resolve the spec (range/tag) through the min-release-age policy,
223
+ // which also honors any configured minimumReleaseAgeExclude entries.
224
+ return (0, resolve_package_version_1.resolvePackageVersionRespectingMinReleaseAge)(packageName, version);
225
+ }
204
226
  async populatePackageJsonUpdatesAndGetPackagesToCheck(targetPackage, target) {
205
227
  let targetVersion = target.version;
206
228
  if (this.to[targetPackage]) {
@@ -1347,6 +1369,7 @@ function readNxVersion(packageJson, root) {
1347
1369
  (0, package_json_1.getDependencyVersionFromPackageJson)('@nx/workspace', root, packageJson) ??
1348
1370
  (0, package_json_1.getDependencyVersionFromPackageJson)('@nrwl/workspace', root, packageJson));
1349
1371
  }
1372
+ // Exported for testing the optional-include orchestration seam (see NXC-4590).
1350
1373
  async function generateMigrationsJsonAndUpdatePackageJson(root, opts, fetch) {
1351
1374
  const pmc = (0, package_manager_1.getPackageManagerCommand)();
1352
1375
  let phase = 'fetch_migrations';
@@ -1413,10 +1436,15 @@ async function generateMigrationsJsonAndUpdatePackageJson(root, opts, fetch) {
1413
1436
  // the user already has. Drop those before writing; nx migrate is
1414
1437
  // forward-only, never a downgrade.
1415
1438
  phase = 'package_updates';
1416
- const writableUpdates = (0, update_filters_1.filterDowngradedUpdates)(packageUpdates, originalPackageJson, installedPackageVersions);
1439
+ // Resolve catalog: specifiers first so the filter compares real versions.
1440
+ const writableUpdates = (0, update_filters_1.filterDowngradedUpdates)(packageUpdates, (0, catalog_1.resolveCatalogSpecifiers)(originalPackageJson), installedPackageVersions);
1417
1441
  const wrotePackageJson = await updatePackageJson(root, writableUpdates);
1418
1442
  const wroteNxJsonInstallation = await updateInstallationDetails(root, writableUpdates);
1419
- const promptMigrationFiles = (0, prompt_files_1.writePromptMigrationFiles)(root, migrations, promptContents ?? {}, packageUpdates[walkedTargetPackage].version);
1443
+ // Under `--include=optional` the target's own entry is filtered out of
1444
+ // `packageUpdates` (it's a required package), so resolve the version
1445
+ // defensively. Also reused by the completion analytics below.
1446
+ const resolvedTargetVersion = packageUpdates[walkedTargetPackage]?.version ?? opts.targetVersion;
1447
+ const promptMigrationFiles = (0, prompt_files_1.writePromptMigrationFiles)(root, migrations, promptContents ?? {}, resolvedTargetVersion);
1420
1448
  if (migrations.length > 0) {
1421
1449
  await createMigrationsFile(root, [
1422
1450
  ...addSplitConfigurationMigrationIfAvailable(from, writableUpdates),
@@ -1428,7 +1456,6 @@ async function generateMigrationsJsonAndUpdatePackageJson(root, opts, fetch) {
1428
1456
  : include === 'optional'
1429
1457
  ? `- Processed optional dependency updates only (skipped required package updates).`
1430
1458
  : null;
1431
- const resolvedTargetVersion = packageUpdates[walkedTargetPackage]?.version ?? opts.targetVersion;
1432
1459
  // The param expressions below evaluate before the report function is
1433
1460
  // entered; `safeReport` keeps them inside the analytics boundary so a
1434
1461
  // param-building throw can't surface here and convert an already
@@ -137,9 +137,9 @@ async function resolveWithPolicy(packageName, version, policy, applySideEffects)
137
137
  if (applySideEffects && outcome.version !== outcome.unconstrained) {
138
138
  reportChangedOutcome(packageName, spec, outcome.version, outcome.unconstrained, policy);
139
139
  }
140
- if (outcome.immature && applySideEffects) {
141
- handleImmaturePick(packageName, outcome.version, policy);
142
- }
140
+ // An immature loose pick is returned as-is. nx does not write the
141
+ // minimumReleaseAgeExclude entry here: migrate does not replace the install,
142
+ // so the real `pnpm install` (>=11.1.3) auto-writes it itself.
143
143
  return outcome.version;
144
144
  }
145
145
  catch (e) {
@@ -171,28 +171,6 @@ function reportChangedOutcome(packageName, spec, picked, unconstrained, policy)
171
171
  title: `Resolved ${packageName}@${picked} instead of ${unconstrained}: ${policy.sourceDescription}.`,
172
172
  });
173
173
  }
174
- // pnpm v11 loose installs an immature version; >=11.1.3 also records it as an
175
- // exclude in pnpm-workspace.yaml. Mirror that so a later real install agrees.
176
- function handleImmaturePick(packageName, version, policy) {
177
- if (policy.behavior.packageManager !== 'pnpm' ||
178
- !policy.behavior.writesExcludes) {
179
- return;
180
- }
181
- const added = (0, pnpm_exclude_writer_1.appendMinimumReleaseAgeExcludes)(workspace_root_1.workspaceRoot, [
182
- `${packageName}@${version}`,
183
- ]);
184
- // Already present (e.g. a prior pick or a pre-existing entry): nothing written,
185
- // so do not claim we added it.
186
- if (added.length === 0) {
187
- return;
188
- }
189
- output_1.output.log({
190
- title: `Added ${packageName}@${version} to minimumReleaseAgeExclude in pnpm-workspace.yaml.`,
191
- bodyLines: [
192
- `It is within the ${policy.sourceDescription} window; this mirrors what pnpm would write at install time.`,
193
- ],
194
- });
195
- }
196
174
  async function handleViolation(packageName, error, policy) {
197
175
  const isPnpmStrict = policy.behavior.packageManager === 'pnpm' &&
198
176
  policy.behavior.strict &&
@@ -35,7 +35,8 @@ function filterDowngradedUpdates(packageUpdates, packageJson, getInstalledVersio
35
35
  if (!specifier) {
36
36
  continue;
37
37
  }
38
- const floor = (0, semver_1.minVersion)(specifier);
38
+ // Skip specifiers that aren't semver ranges (workspace:/npm:/git/file).
39
+ const floor = (0, semver_1.validRange)(specifier) ? (0, semver_1.minVersion)(specifier) : null;
39
40
  if (floor && (0, semver_1.lt)(floor.version, resolvedNorm)) {
40
41
  result[name] = update;
41
42
  }
@@ -9,5 +9,5 @@ export declare function connectToNxCloudCommand(options: {
9
9
  generateToken?: boolean;
10
10
  checkRemote?: boolean;
11
11
  }, command?: string): Promise<boolean>;
12
- export declare function connectExistingRepoToNxCloudPrompt(command?: string, key?: MessageKey): Promise<MessageOptionKey>;
12
+ export declare function connectExistingRepoToNxCloudPrompt(command?: string, key?: MessageKey, recordCompletion?: boolean): Promise<MessageOptionKey>;
13
13
  export declare function connectToNxCloudWithPrompt(command: string): Promise<void>;
@@ -180,27 +180,34 @@ async function runConnectToNxCloud(options, command) {
180
180
  function sleep(ms) {
181
181
  return new Promise((resolve) => setTimeout(resolve, ms));
182
182
  }
183
- async function connectExistingRepoToNxCloudPrompt(command = 'init', key = 'setupNxCloud') {
184
- const res = await nxCloudPrompt(key);
185
- await (0, ab_testing_1.recordStat)({
186
- command,
187
- nxVersion: versions_1.nxVersion,
188
- useCloud: res === 'yes',
189
- meta: {
190
- type: 'complete',
191
- setupCloudPrompt: ab_testing_1.messages.codeOfSelectedPromptMessage(key) || '',
192
- nxCloudArg: res,
193
- nodeVersion: process.versions.node,
194
- os: process.platform,
195
- packageManager: (0, package_manager_1.detectPackageManager)(),
196
- aiAgent: (0, native_1.isAiAgent)(),
197
- isCI: (0, is_ci_1.isCI)(),
198
- },
199
- });
183
+ async function connectExistingRepoToNxCloudPrompt(command = 'init', key = 'setupNxCloud', recordCompletion = true) {
184
+ const res = await nxCloudPrompt(key, utmContentForCommand(command));
185
+ // TODO: once legacy init-v1 (the NX_ADD_PLUGINS=false / useInferencePlugins:false path) is
186
+ // removed, drop this recordStat and the recordCompletion flag entirely - init-v2 records its
187
+ // own complete, and view-logs should record its own stat, so this shared helper won't record.
188
+ // init-v2 records its own init "complete" stat, so it opts out here to avoid double-counting.
189
+ // Other callers (e.g. view-logs, legacy init-v1) rely on this as their only completion event.
190
+ if (recordCompletion) {
191
+ await (0, ab_testing_1.recordStat)({
192
+ command,
193
+ nxVersion: versions_1.nxVersion,
194
+ useCloud: res === 'yes',
195
+ meta: {
196
+ type: 'complete',
197
+ setupCloudPrompt: ab_testing_1.messages.codeOfSelectedPromptMessage(key) || '',
198
+ nxCloudArg: res,
199
+ nodeVersion: process.versions.node,
200
+ os: process.platform,
201
+ packageManager: (0, package_manager_1.detectPackageManager)(),
202
+ aiAgent: (0, native_1.isAiAgent)(),
203
+ isCI: (0, is_ci_1.isCI)(),
204
+ },
205
+ });
206
+ }
200
207
  return res;
201
208
  }
202
209
  async function connectToNxCloudWithPrompt(command) {
203
- const setNxCloud = await nxCloudPrompt('setupNxCloud');
210
+ const setNxCloud = await nxCloudPrompt('setupNxCloud', utmContentForCommand(command));
204
211
  let useCloud = false;
205
212
  if (setNxCloud === 'yes') {
206
213
  useCloud = await connectToNxCloudCommand({ generateToken: false }, command);
@@ -229,7 +236,17 @@ async function connectToNxCloudWithPrompt(command) {
229
236
  },
230
237
  });
231
238
  }
232
- async function nxCloudPrompt(key) {
239
+ function utmContentForCommand(command) {
240
+ switch (command) {
241
+ case 'migrate':
242
+ return 'nx-migrate';
243
+ case 'view-logs':
244
+ return 'nx-connect';
245
+ default:
246
+ return 'nx-init';
247
+ }
248
+ }
249
+ async function nxCloudPrompt(key, utmContent) {
233
250
  const { message, choices, initial, footer, hint } = ab_testing_1.messages.getPrompt(key);
234
251
  const promptConfig = {
235
252
  name: 'NxCloud',
@@ -239,7 +256,7 @@ async function nxCloudPrompt(key) {
239
256
  initial,
240
257
  }; // meeroslav: types in enquirer are not up to date
241
258
  if (footer) {
242
- promptConfig.footer = () => pc.dim(footer);
259
+ promptConfig.footer = () => pc.dim(`${footer} ${(0, ab_testing_1.nxCloudHyperlink)(utmContent)}`);
243
260
  }
244
261
  if (hint) {
245
262
  promptConfig.hint = () => pc.dim(hint);
@@ -67,7 +67,10 @@ async function resolveCurrentVersionFromRegistry(tree, projectGraphNode, release
67
67
  return cached.currentVersion;
68
68
  }
69
69
  let registryTxt = '';
70
- const spinner = spinner_1.globalSpinner.start(`Resolving the current version for ${projectGraphNode.name} from the configured registry...`);
70
+ // Skip non-TTY logging the resolved version is reported through the
71
+ // project's batched `ProjectLogger` so the spinner step shouldn't emit an
72
+ // out-of-band line that would bypass the grouped output.
73
+ const spinner = spinner_1.globalSpinner.start(`Resolving the current version for ${projectGraphNode.name} from the configured registry...`, undefined, { skipNonTtyLogging: true });
71
74
  try {
72
75
  const res = await versionActions.readCurrentVersionFromRegistry(tree, finalConfigForProject.currentVersionResolverMetadata);
73
76
  if (!res) {