nx 23.2.0-beta.4 → 23.2.0-beta.6

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 (120) hide show
  1. package/dist/bin/nx.js +1 -0
  2. package/dist/src/command-line/affected/affected.js +1 -0
  3. package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +2 -2
  4. package/dist/src/command-line/init/init-v2.js +2 -2
  5. package/dist/src/command-line/migrate/agentic/definitions.js +2 -2
  6. package/dist/src/command-line/migrate/agentic/handoff-gitignore.d.ts +15 -19
  7. package/dist/src/command-line/migrate/agentic/handoff-gitignore.js +19 -31
  8. package/dist/src/command-line/migrate/agentic/handoff.d.ts +4 -6
  9. package/dist/src/command-line/migrate/agentic/handoff.js +9 -8
  10. package/dist/src/command-line/migrate/agentic/print-dropped-agent-context.d.ts +7 -12
  11. package/dist/src/command-line/migrate/agentic/print-dropped-agent-context.js +7 -0
  12. package/dist/src/command-line/migrate/agentic/run-step.d.ts +10 -0
  13. package/dist/src/command-line/migrate/agentic/select.d.ts +10 -2
  14. package/dist/src/command-line/migrate/agentic/select.js +10 -2
  15. package/dist/src/command-line/migrate/agentic/types.d.ts +17 -3
  16. package/dist/src/command-line/migrate/agentic/types.js +26 -0
  17. package/dist/src/command-line/migrate/command-object.d.ts +3 -0
  18. package/dist/src/command-line/migrate/command-object.js +6 -1
  19. package/dist/src/command-line/migrate/execute-migration.d.ts +14 -9
  20. package/dist/src/command-line/migrate/execute-migration.js +82 -18
  21. package/dist/src/command-line/migrate/migrate-analytics.d.ts +7 -0
  22. package/dist/src/command-line/migrate/migrate-analytics.js +14 -0
  23. package/dist/src/command-line/migrate/migrate-commits.d.ts +32 -8
  24. package/dist/src/command-line/migrate/migrate-commits.js +77 -9
  25. package/dist/src/command-line/migrate/migrate-config.d.ts +11 -12
  26. package/dist/src/command-line/migrate/migrate-config.js +14 -14
  27. package/dist/src/command-line/migrate/migrate-output.d.ts +45 -7
  28. package/dist/src/command-line/migrate/migrate-output.js +52 -8
  29. package/dist/src/command-line/migrate/migrate-ui-api.d.ts +2 -1
  30. package/dist/src/command-line/migrate/migrate-ui-api.js +16 -7
  31. package/dist/src/command-line/migrate/migrate.d.ts +17 -68
  32. package/dist/src/command-line/migrate/migrate.js +411 -229
  33. package/dist/src/command-line/migrate/migration-shape.d.ts +8 -0
  34. package/dist/src/command-line/migrate/run/index.d.ts +2 -0
  35. package/dist/src/command-line/migrate/run/index.js +5 -0
  36. package/dist/src/command-line/migrate/run/util.d.ts +1 -0
  37. package/dist/src/command-line/migrate/run/util.js +16 -0
  38. package/dist/src/command-line/migrate/run/worker.d.ts +15 -0
  39. package/dist/src/command-line/migrate/run/worker.js +419 -0
  40. package/dist/src/command-line/migrate/run-migration-process.js +6 -5
  41. package/dist/src/command-line/migrate/sort-migrations.d.ts +16 -0
  42. package/dist/src/command-line/migrate/sort-migrations.js +32 -0
  43. package/dist/src/command-line/migrate/version-skew-guard.d.ts +58 -0
  44. package/dist/src/command-line/migrate/version-skew-guard.js +155 -0
  45. package/dist/src/command-line/nx-cloud/start-nx-agents/command-object.d.ts +2 -0
  46. package/dist/src/command-line/nx-cloud/start-nx-agents/command-object.js +16 -0
  47. package/dist/src/command-line/nx-cloud/start-nx-agents/start-nx-agents.d.ts +4 -0
  48. package/dist/src/command-line/nx-cloud/start-nx-agents/start-nx-agents.js +13 -0
  49. package/dist/src/command-line/nx-commands.js +26 -24
  50. package/dist/src/command-line/release/changelog.js +5 -0
  51. package/dist/src/command-line/release/utils/git.js +1 -1
  52. package/dist/src/command-line/release/utils/release-graph.d.ts +1 -0
  53. package/dist/src/command-line/release/utils/release-graph.js +15 -3
  54. package/dist/src/command-line/release/version/version-actions.d.ts +8 -0
  55. package/dist/src/command-line/release/version/version-actions.js +8 -0
  56. package/dist/src/command-line/release/version.js +9 -5
  57. package/dist/src/command-line/reset/reset.js +6 -1
  58. package/dist/src/command-line/run/run-one.js +1 -0
  59. package/dist/src/command-line/run-many/run-many.js +1 -0
  60. package/dist/src/config/misc-interfaces.d.ts +19 -7
  61. package/dist/src/core/graph/main.js +1 -1
  62. package/dist/src/core/graph/styles.js +1 -1
  63. package/dist/src/daemon/client/client.d.ts +45 -1
  64. package/dist/src/daemon/client/client.js +128 -32
  65. package/dist/src/daemon/client/daemon-socket-messenger.js +4 -0
  66. package/dist/src/daemon/message-types/daemon-message.d.ts +32 -0
  67. package/dist/src/daemon/message-types/daemon-message.js +25 -0
  68. package/dist/src/daemon/server/latest-nx.js +5 -2
  69. package/dist/src/daemon/server/server.js +22 -0
  70. package/dist/src/daemon/server/shutdown-utils.d.ts +5 -0
  71. package/dist/src/daemon/server/shutdown-utils.js +11 -4
  72. package/dist/src/daemon/socket-utils.d.ts +1 -0
  73. package/dist/src/daemon/socket-utils.js +24 -3
  74. package/dist/src/daemon/tmp-dir.d.ts +43 -3
  75. package/dist/src/daemon/tmp-dir.js +437 -15
  76. package/dist/src/devkit-internals.d.ts +92 -3
  77. package/dist/src/devkit-internals.js +189 -5
  78. package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.d.ts +5 -0
  79. package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.js +9 -2
  80. package/dist/src/generators/tree.d.ts +4 -2
  81. package/dist/src/native/index.d.ts +10 -0
  82. package/dist/src/native/index.js +71 -28
  83. package/dist/src/native/native-bindings.js +1 -0
  84. package/dist/src/native/native-file-cache-location.d.ts +17 -1
  85. package/dist/src/native/native-file-cache-location.js +86 -12
  86. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  87. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  88. package/dist/src/nx-cloud/utilities/get-cloud-options.js +5 -2
  89. package/dist/src/nx-cloud/utilities/onboarding.js +5 -2
  90. package/dist/src/nx-cloud/utilities/url-shorten.d.ts +1 -4
  91. package/dist/src/nx-cloud/utilities/url-shorten.js +9 -8
  92. package/dist/src/plugins/js/lock-file/project-graph-pruning.js +14 -8
  93. package/dist/src/plugins/js/project-graph/build-dependencies/target-project-locator.js +2 -0
  94. package/dist/src/project-graph/affected/affected-project-graph-models.d.ts +1 -1
  95. package/dist/src/project-graph/affected/affected-project-graph.d.ts +1 -1
  96. package/dist/src/project-graph/affected/affected-project-graph.js +2 -2
  97. package/dist/src/project-graph/affected/locators/project-glob-changes.js +5 -2
  98. package/dist/src/project-graph/plugins/isolation/isolated-plugin.d.ts +1 -0
  99. package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +18 -1
  100. package/dist/src/project-graph/plugins/isolation/messaging.d.ts +16 -3
  101. package/dist/src/project-graph/plugins/isolation/messaging.js +3 -0
  102. package/dist/src/project-graph/plugins/isolation/plugin-worker.js +58 -8
  103. package/dist/src/project-graph/project-graph.js +20 -0
  104. package/dist/src/tasks-runner/life-cycles/performance-report.d.ts +12 -0
  105. package/dist/src/tasks-runner/life-cycles/performance-report.js +62 -3
  106. package/dist/src/tasks-runner/run-command.js +4 -0
  107. package/dist/src/utils/assert-workspace-validity.js +7 -2
  108. package/dist/src/utils/child-process.d.ts +31 -1
  109. package/dist/src/utils/child-process.js +103 -0
  110. package/dist/src/utils/ignore.d.ts +128 -1
  111. package/dist/src/utils/ignore.js +182 -12
  112. package/dist/src/utils/nx-tmp-dir.d.ts +18 -0
  113. package/dist/src/utils/nx-tmp-dir.js +40 -0
  114. package/dist/src/utils/owned-private-dir.d.ts +132 -0
  115. package/dist/src/utils/owned-private-dir.js +323 -0
  116. package/dist/src/utils/shell-quoting.d.ts +15 -0
  117. package/dist/src/utils/shell-quoting.js +35 -0
  118. package/dist/src/utils/wait-for-socket-connection.d.ts +11 -0
  119. package/dist/src/utils/wait-for-socket-connection.js +14 -8
  120. package/package.json +12 -12
@@ -4,13 +4,16 @@ exports.getNgCompatLayer = exports.MigrationImplementationMissingError = exports
4
4
  exports.readPackageMigrationConfig = readPackageMigrationConfig;
5
5
  exports.runInstall = runInstall;
6
6
  exports.isNpmPeerDepsError = isNpmPeerDepsError;
7
+ exports.formatSingleMigrationRerunCommand = formatSingleMigrationRerunCommand;
7
8
  exports.logNpmPeerDepsError = logNpmPeerDepsError;
9
+ exports.logSkippedPostMigrationInstall = logSkippedPostMigrationInstall;
8
10
  exports.runNxOrAngularMigration = runNxOrAngularMigration;
9
11
  exports.getStringifiedPackageJsonDeps = getStringifiedPackageJsonDeps;
10
12
  exports.runNxMigration = runNxMigration;
11
13
  exports.parseMigrationReturn = parseMigrationReturn;
12
14
  exports.filterStrings = filterStrings;
13
15
  exports.readMigrationCollection = readMigrationCollection;
16
+ exports.resolveDocumentationFileToWorkspacePath = resolveDocumentationFileToWorkspacePath;
14
17
  exports.getImplementationPath = getImplementationPath;
15
18
  exports.isAngularMigration = isAngularMigration;
16
19
  const tslib_1 = require("tslib");
@@ -27,6 +30,7 @@ const package_manager_1 = require("../../utils/package-manager");
27
30
  const output_1 = require("../../utils/output");
28
31
  const fs_1 = require("fs");
29
32
  const installation_directory_1 = require("../../utils/installation-directory");
33
+ const shell_quoting_1 = require("../../utils/shell-quoting");
30
34
  const project_graph_1 = require("../../project-graph/project-graph");
31
35
  const version_utils_1 = require("./version-utils");
32
36
  function readPackageMigrationConfig(packageName, dir) {
@@ -55,7 +59,7 @@ function readPackageMigrationConfig(packageName, dir) {
55
59
  };
56
60
  }
57
61
  }
58
- function runInstall(nxWorkspaceRoot, phase = 'pre-migration') {
62
+ function runInstall(nxWorkspaceRoot, phase = 'pre-migration', rerunCommand) {
59
63
  const cwd = nxWorkspaceRoot ?? process.cwd();
60
64
  const packageManager = (0, package_manager_1.detectPackageManager)(cwd);
61
65
  const pmCommands = (0, package_manager_1.getPackageManagerCommand)(packageManager, cwd);
@@ -92,7 +96,7 @@ function runInstall(nxWorkspaceRoot, phase = 'pre-migration') {
92
96
  // (CLI migrate, `nx repair`, single-migration runner, etc.) surfaces
93
97
  // it consistently. Top-level callers catch `NpmPeerDepsInstallError`
94
98
  // and return a non-zero exit code without re-logging.
95
- logNpmPeerDepsError(phase);
99
+ logNpmPeerDepsError(phase, rerunCommand);
96
100
  reject(new NpmPeerDepsInstallError());
97
101
  return;
98
102
  }
@@ -124,7 +128,20 @@ function isNpmPeerDepsError(stderr) {
124
128
  lowerStderr.includes('could not resolve dependency') ||
125
129
  lowerStderr.includes('conflicting peer dependency'));
126
130
  }
127
- function logNpmPeerDepsError(phase) {
131
+ // The single-migration rerun command lands in copyable guidance (see
132
+ // `logNpmPeerDepsError` below), so quote ids a shell would split or expand.
133
+ // Single quotes stay literal in POSIX shells and PowerShell alike; double
134
+ // quotes would leave $-expansion active in both. The embedded-quote escape is
135
+ // the POSIX '\'' sequence, the one character PowerShell disagrees on (it wants
136
+ // ''). cmd.exe is knowingly not covered: it does not group on single quotes,
137
+ // and no quoting suppresses its %VAR% expansion.
138
+ function formatSingleMigrationRerunCommand(migrationId) {
139
+ const id = (0, shell_quoting_1.needsShellQuoting)(migrationId)
140
+ ? `'${migrationId.replace(/'/g, String.raw `'\''`)}'`
141
+ : migrationId;
142
+ return `nx migrate --run-migration=${id}`;
143
+ }
144
+ function logNpmPeerDepsError(phase, rerunCommand = 'nx migrate --run-migrations') {
128
145
  const peerDepsResolutionSteps = [
129
146
  'Recommended approaches (in order of preference):',
130
147
  '',
@@ -138,7 +155,7 @@ function logNpmPeerDepsError(phase) {
138
155
  ];
139
156
  const manualInstallHint = [
140
157
  'If you installed the dependencies manually, pass "--skip-install" to avoid re-installing them:',
141
- ' nx migrate --run-migrations --skip-install',
158
+ ` ${rerunCommand} --skip-install`,
142
159
  ];
143
160
  if (phase === 'pre-migration') {
144
161
  output_1.output.error({
@@ -146,8 +163,8 @@ function logNpmPeerDepsError(phase) {
146
163
  bodyLines: [
147
164
  ...peerDepsResolutionSteps,
148
165
  '',
149
- 'Once the conflicts are resolved, re-run the migrations:',
150
- ' nx migrate --run-migrations',
166
+ 'Once the conflicts are resolved, re-run the migration command:',
167
+ ` ${rerunCommand}`,
151
168
  '',
152
169
  ...manualInstallHint,
153
170
  ],
@@ -160,18 +177,27 @@ function logNpmPeerDepsError(phase) {
160
177
  ...peerDepsResolutionSteps,
161
178
  '',
162
179
  'Once the conflicts are resolved, run "npm install" to install the updated dependencies.',
163
- 'If the migration was interrupted before completing, re-run the remaining migrations:',
164
- ' nx migrate --run-migrations',
180
+ 'If the migration run was interrupted before completing, re-run it:',
181
+ ` ${rerunCommand}`,
165
182
  '',
166
183
  ...manualInstallHint,
167
184
  ],
168
185
  });
169
186
  }
170
187
  }
188
+ function logSkippedPostMigrationInstall(root) {
189
+ const packageManager = (0, package_manager_1.detectPackageManager)(root);
190
+ const installCommand = (0, package_manager_1.getPackageManagerCommand)(packageManager, root).install;
191
+ output_1.output.warn({
192
+ title: 'Migrations updated your dependencies, but the install was skipped',
193
+ bodyLines: [`Run "${installCommand}" to install the updated dependencies.`],
194
+ });
195
+ }
171
196
  class ChangedDepInstaller {
172
- constructor(root, shouldSkipInstall = false) {
197
+ constructor(root, shouldSkipInstall = false, rerunCommand) {
173
198
  this.root = root;
174
199
  this.shouldSkipInstall = shouldSkipInstall;
200
+ this.rerunCommand = rerunCommand;
175
201
  this._skippedInstall = false;
176
202
  this.initialDeps = getStringifiedPackageJsonDeps(root);
177
203
  }
@@ -185,7 +211,7 @@ class ChangedDepInstaller {
185
211
  this._skippedInstall = true;
186
212
  }
187
213
  else {
188
- await runInstall(this.root, 'post-migration');
214
+ await runInstall(this.root, 'post-migration', this.rerunCommand);
189
215
  }
190
216
  }
191
217
  this.initialDeps = currentDeps;
@@ -197,6 +223,8 @@ async function runNxOrAngularMigration(root, migration, isVerbose, captureGenera
197
223
  let changes = [];
198
224
  let nextSteps = [];
199
225
  let agentContext = [];
226
+ // Angular schematics have no return channel, so they can never waive it.
227
+ let skipAgentic = false;
200
228
  let logs = '';
201
229
  // Angular's `ngResult.changes` is synthesized from the schematic's
202
230
  // DryRunEvent stream so Nx and Angular paths can share commit/validation
@@ -204,7 +232,8 @@ async function runNxOrAngularMigration(root, migration, isVerbose, captureGenera
204
232
  let madeChanges = false;
205
233
  logger_1.logger.info(pc.dim('→ Running generator…'));
206
234
  if (!isAngularMigration(collection, migration.name)) {
207
- ({ nextSteps, changes, agentContext, logs } = await runNxMigration(root, collectionPath, collection, migration.name, migration.version, captureGeneratorOutput));
235
+ ({ nextSteps, changes, agentContext, skipAgentic, logs } =
236
+ await runNxMigration(root, collectionPath, collection, migration.name, migration.version, captureGeneratorOutput));
208
237
  madeChanges = changes.length > 0;
209
238
  logger_1.logger.info(`Ran ${migration.name} from ${migration.package}`);
210
239
  if (migration.description) {
@@ -213,7 +242,14 @@ async function runNxOrAngularMigration(root, migration, isVerbose, captureGenera
213
242
  logger_1.logger.info('');
214
243
  if (!madeChanges) {
215
244
  logger_1.logger.info(`No changes were made\n`);
216
- return { changes, nextSteps, agentContext, logs, madeChanges };
245
+ return {
246
+ changes,
247
+ nextSteps,
248
+ agentContext,
249
+ skipAgentic,
250
+ logs,
251
+ madeChanges,
252
+ };
217
253
  }
218
254
  logger_1.logger.info('Changes:');
219
255
  (0, tree_1.printChanges)(changes, ' ');
@@ -233,13 +269,20 @@ async function runNxOrAngularMigration(root, migration, isVerbose, captureGenera
233
269
  logger_1.logger.info('');
234
270
  if (!madeChanges) {
235
271
  logger_1.logger.info(`No changes were made\n`);
236
- return { changes, nextSteps, agentContext, logs, madeChanges };
272
+ return {
273
+ changes,
274
+ nextSteps,
275
+ agentContext,
276
+ skipAgentic,
277
+ logs,
278
+ madeChanges,
279
+ };
237
280
  }
238
281
  logger_1.logger.info('Changes:');
239
282
  ngResult.loggingQueue.forEach((log) => logger_1.logger.info(' ' + log));
240
283
  logger_1.logger.info('');
241
284
  }
242
- return { changes, nextSteps, agentContext, logs, madeChanges };
285
+ return { changes, nextSteps, agentContext, skipAgentic, logs, madeChanges };
243
286
  }
244
287
  function getStringifiedPackageJsonDeps(root) {
245
288
  try {
@@ -265,25 +308,31 @@ async function runNxMigration(root, collectionPath, collection, name, migrationV
265
308
  else {
266
309
  result = await fn(host, {});
267
310
  }
268
- const { nextSteps, agentContext } = parseMigrationReturn(result);
311
+ const { nextSteps, agentContext, skipAgentic } = parseMigrationReturn(result);
269
312
  host.lock();
270
313
  const changes = host.listChanges();
271
314
  (0, tree_1.flushChanges)(root, changes);
272
- return { changes, nextSteps, agentContext, logs };
315
+ return { changes, nextSteps, agentContext, skipAgentic, logs };
273
316
  }
274
317
  function parseMigrationReturn(value) {
275
318
  if (Array.isArray(value)) {
276
- return { nextSteps: filterStrings(value), agentContext: [] };
319
+ return {
320
+ nextSteps: filterStrings(value),
321
+ agentContext: [],
322
+ skipAgentic: false,
323
+ };
277
324
  }
278
325
  if (value && typeof value === 'object') {
279
326
  const obj = value;
280
327
  return {
281
328
  nextSteps: filterStrings(obj.nextSteps),
282
329
  agentContext: filterStrings(obj.agentContext),
330
+ // Strict, so a truthy non-boolean can't opt a migration out of its AI step.
331
+ skipAgentic: obj.skipAgentic === true,
283
332
  };
284
333
  }
285
334
  // Catches `void`, mistakenly-returned generator callbacks, malformed values.
286
- return { nextSteps: [], agentContext: [] };
335
+ return { nextSteps: [], agentContext: [], skipAgentic: false };
287
336
  }
288
337
  // Bucket-level tolerance: a single non-string entry shouldn't discard the
289
338
  // whole `nextSteps` / `agentContext` array. Migration authors occasionally
@@ -303,6 +352,21 @@ function readMigrationCollection(packageName, root) {
303
352
  collectionPath,
304
353
  };
305
354
  }
355
+ // Workspace-relative because the agent runs with cwd at the workspace root;
356
+ // absolute only for layouts that resolve outside it (hoisted/symlinked).
357
+ function resolveDocumentationFileToWorkspacePath(root, migrationsDir, documentation) {
358
+ let documentationFile;
359
+ try {
360
+ documentationFile = require.resolve(documentation, {
361
+ paths: [migrationsDir],
362
+ });
363
+ }
364
+ catch {
365
+ return undefined;
366
+ }
367
+ const relativePath = (0, path_1.relative)(root, documentationFile);
368
+ return relativePath.startsWith('..') ? documentationFile : relativePath;
369
+ }
306
370
  function getImplementationPath(collection, collectionPath, name, migrationVersion) {
307
371
  const g = collection.generators?.[name] || collection.schematics?.[name];
308
372
  if (!g) {
@@ -67,5 +67,12 @@ export declare function reportMigrateRunError(opts: {
67
67
  migrationCount?: number;
68
68
  error?: unknown;
69
69
  }): void;
70
+ /**
71
+ * Counts invocations, not completions: emitted as soon as the migration id
72
+ * resolves, while the worker can still stop before running anything.
73
+ */
74
+ export declare function reportMigrateSingleMigrationInvocation(opts: {
75
+ migrationType: 'generator' | 'prompt' | 'hybrid';
76
+ }): void;
70
77
  export declare function computeMajorsCrossed(installed: string | null | undefined, target: string | null | undefined): number | undefined;
71
78
  export declare function safeReport(emit: () => void): void;
@@ -11,6 +11,7 @@ exports.reportMigrateRunStart = reportMigrateRunStart;
11
11
  exports.hasMigrateRunStarted = hasMigrateRunStarted;
12
12
  exports.reportMigrateRunComplete = reportMigrateRunComplete;
13
13
  exports.reportMigrateRunError = reportMigrateRunError;
14
+ exports.reportMigrateSingleMigrationInvocation = reportMigrateSingleMigrationInvocation;
14
15
  exports.computeMajorsCrossed = computeMajorsCrossed;
15
16
  exports.safeReport = safeReport;
16
17
  const semver_1 = require("semver");
@@ -180,6 +181,19 @@ function reportMigrateRunError(opts) {
180
181
  });
181
182
  });
182
183
  }
184
+ /**
185
+ * Counts invocations, not completions: emitted as soon as the migration id
186
+ * resolves, while the worker can still stop before running anything.
187
+ */
188
+ function reportMigrateSingleMigrationInvocation(opts) {
189
+ safeReport(() => {
190
+ if (!analytics_1.customDimensions)
191
+ return;
192
+ (0, analytics_1.reportEvent)('migrate_single_migration_invocation', {
193
+ [analytics_1.customDimensions.promptChoice]: opts.migrationType,
194
+ });
195
+ });
196
+ }
183
197
  // `_migrate` runs either from a temp install of the latest CLI or from the
184
198
  // workspace-local installation; same signal as the run-phase re-dispatch
185
199
  // check in migrate.ts.
@@ -1,3 +1,4 @@
1
+ import type { ResolvedAgentic } from './agentic/types';
1
2
  /**
2
3
  * Discriminated result for `commitMigrationIfRequested`. Distinguishes the
3
4
  * shapes the executor needs to react to:
@@ -23,21 +24,20 @@ export type CommitResult = {
23
24
  status: 'disabled';
24
25
  };
25
26
  /**
26
- * Creates a per-migration commit when `shouldCreateCommits` is true.
27
+ * `pendingMigrations` are listed in the commit body so a `git log -p` reader
28
+ * can see which earlier migrations' diffs this commit absorbed (their own
29
+ * commits failed and `git add -A` picked their working-tree state up too).
27
30
  *
28
- * When `pendingMigrations` is non-empty, the commit message body lists
29
- * those entries so a reader of `git log -p` can see which prior migrations'
30
- * diffs were absorbed into this commit (because their own commits failed and
31
- * `git add -A` here captured their working-tree state too). Each entry is
32
- * rendered as `<package>: <name>` for unambiguous attribution across
33
- * packages.
31
+ * The default `failureGuidance` describes the classic loop's absorb-and-recap
32
+ * behavior; a caller with no later commit or recap to absorb the diff (the
33
+ * standalone single-migration worker) passes its own.
34
34
  */
35
35
  export declare function commitMigrationIfRequested(root: string, migration: {
36
36
  name: string;
37
37
  }, shouldCreateCommits: boolean, commitPrefix: string, installDepsIfChanged: () => Promise<void>, pendingMigrations?: ReadonlyArray<{
38
38
  package: string;
39
39
  name: string;
40
- }>): Promise<CommitResult>;
40
+ }>, failureGuidance?: string): Promise<CommitResult>;
41
41
  /**
42
42
  * Commits any pre-existing working-tree state into a dedicated "checkpoint"
43
43
  * commit before the first migration runs. Without this, the first migration's
@@ -48,3 +48,27 @@ export declare function commitMigrationIfRequested(root: string, migration: {
48
48
  * the working tree is already clean.
49
49
  */
50
50
  export declare function commitCheckpointBeforeMigrations(root: string, commitPrefix: string): void;
51
+ /**
52
+ * `agenticHasDiffContext` gates the agent prompt: without per-migration commits
53
+ * to isolate a migration's diff, the prompt embeds a file list instead of
54
+ * pointing at git.
55
+ */
56
+ export declare function resolveCreateCommits(args: {
57
+ createCommits: boolean | undefined;
58
+ agenticKind: ResolvedAgentic['kind'];
59
+ isGitRepo: boolean;
60
+ commitPrefixIsCustom?: boolean;
61
+ }): {
62
+ effective: boolean;
63
+ agenticHasDiffContext: boolean;
64
+ warning?: string;
65
+ error?: string;
66
+ };
67
+ /**
68
+ * Callers gate this on commits being effective and on prompting being
69
+ * possible, so non-interactive runs (CI, `--no-interactive`) never reach here.
70
+ */
71
+ export declare function confirmCommitsOnDefaultBranch(args: {
72
+ currentBranch: string | null;
73
+ defaultBranch: string | null;
74
+ }): Promise<boolean>;
@@ -2,22 +2,24 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.commitMigrationIfRequested = commitMigrationIfRequested;
4
4
  exports.commitCheckpointBeforeMigrations = commitCheckpointBeforeMigrations;
5
+ exports.resolveCreateCommits = resolveCreateCommits;
6
+ exports.confirmCommitsOnDefaultBranch = confirmCommitsOnDefaultBranch;
5
7
  const tslib_1 = require("tslib");
6
8
  const pc = tslib_1.__importStar(require("picocolors"));
7
9
  const git_utils_1 = require("../../utils/git-utils");
8
10
  const logger_1 = require("../../utils/logger");
9
11
  const output_1 = require("../../utils/output");
12
+ const safe_prompt_1 = require("./safe-prompt");
10
13
  /**
11
- * Creates a per-migration commit when `shouldCreateCommits` is true.
14
+ * `pendingMigrations` are listed in the commit body so a `git log -p` reader
15
+ * can see which earlier migrations' diffs this commit absorbed (their own
16
+ * commits failed and `git add -A` picked their working-tree state up too).
12
17
  *
13
- * When `pendingMigrations` is non-empty, the commit message body lists
14
- * those entries so a reader of `git log -p` can see which prior migrations'
15
- * diffs were absorbed into this commit (because their own commits failed and
16
- * `git add -A` here captured their working-tree state too). Each entry is
17
- * rendered as `<package>: <name>` for unambiguous attribution across
18
- * packages.
18
+ * The default `failureGuidance` describes the classic loop's absorb-and-recap
19
+ * behavior; a caller with no later commit or recap to absorb the diff (the
20
+ * standalone single-migration worker) passes its own.
19
21
  */
20
- async function commitMigrationIfRequested(root, migration, shouldCreateCommits, commitPrefix, installDepsIfChanged, pendingMigrations = []) {
22
+ async function commitMigrationIfRequested(root, migration, shouldCreateCommits, commitPrefix, installDepsIfChanged, pendingMigrations = [], failureGuidance = 'The next successful commit will absorb it and reference this migration in its body; if no later commit lands, the end-of-run output will list this migration so you can commit or revert manually.') {
21
23
  if (!shouldCreateCommits)
22
24
  return { status: 'disabled' };
23
25
  await installDepsIfChanged();
@@ -39,7 +41,7 @@ async function commitMigrationIfRequested(root, migration, shouldCreateCommits,
39
41
  }
40
42
  catch (err) {
41
43
  const reason = err instanceof Error ? err.message : String(err);
42
- logger_1.logger.info(pc.red(`Could not create a commit for ${migration.name}:\n${reason}\nThe migration's diff remains in the working tree; inspect with \`git status\` / \`git diff\` to review. The next successful commit will absorb it and reference this migration in its body; if no later commit lands, the end-of-run output will list this migration so you can commit or revert manually.`));
44
+ logger_1.logger.info(pc.red(`Could not create a commit for ${migration.name}:\n${reason}\nThe migration's diff remains in the working tree; inspect with \`git status\` / \`git diff\` to review. ${failureGuidance}`));
43
45
  return { status: 'failed', reason };
44
46
  }
45
47
  }
@@ -100,3 +102,69 @@ function commitCheckpointBeforeMigrations(root, commitPrefix) {
100
102
  });
101
103
  }
102
104
  }
105
+ /**
106
+ * `agenticHasDiffContext` gates the agent prompt: without per-migration commits
107
+ * to isolate a migration's diff, the prompt embeds a file list instead of
108
+ * pointing at git.
109
+ */
110
+ function resolveCreateCommits(args) {
111
+ const { createCommits, agenticKind, isGitRepo, commitPrefixIsCustom } = args;
112
+ if (createCommits === true && !isGitRepo) {
113
+ return {
114
+ effective: false,
115
+ agenticHasDiffContext: false,
116
+ error: '`--create-commits` requires a git repository. Run `git init` first, or omit the flag.',
117
+ };
118
+ }
119
+ if (agenticKind === 'enabled') {
120
+ if (createCommits === false) {
121
+ return {
122
+ effective: false,
123
+ agenticHasDiffContext: false,
124
+ warning: "--no-create-commits was passed alongside --agentic. Without per-migration commits, the agent can't isolate the current migration's changes from earlier migrations in this run. Drop --no-create-commits for accurate per-migration review." +
125
+ (commitPrefixIsCustom
126
+ ? ' Note: the custom --commit-prefix value will have no effect because commits are disabled.'
127
+ : ''),
128
+ };
129
+ }
130
+ // Not an error like the explicit `--create-commits` branch above: the
131
+ // agentic default was never asked for, so degrade instead.
132
+ if (!isGitRepo) {
133
+ return {
134
+ effective: false,
135
+ agenticHasDiffContext: false,
136
+ warning: '`--agentic` enables per-migration commits by default, but the workspace is not a git repository. Continuing without commits, so the agent will not receive per-file diff context. Run `git init` to enable.' +
137
+ (commitPrefixIsCustom
138
+ ? ' The custom --commit-prefix value will have no effect.'
139
+ : ''),
140
+ };
141
+ }
142
+ return { effective: true, agenticHasDiffContext: true };
143
+ }
144
+ return {
145
+ effective: createCommits === true,
146
+ agenticHasDiffContext: false,
147
+ warning: commitPrefixIsCustom && createCommits !== true
148
+ ? 'A custom migrate commit prefix is configured, but commits are not enabled for this run, so it has no effect. Set `migrate.createCommits` to `true` (or pass `--create-commits`) to create a commit per migration.'
149
+ : undefined,
150
+ };
151
+ }
152
+ /**
153
+ * Callers gate this on commits being effective and on prompting being
154
+ * possible, so non-interactive runs (CI, `--no-interactive`) never reach here.
155
+ */
156
+ async function confirmCommitsOnDefaultBranch(args) {
157
+ const { currentBranch, defaultBranch } = args;
158
+ if (!currentBranch || !defaultBranch || currentBranch !== defaultBranch) {
159
+ return true;
160
+ }
161
+ const { proceed } = await (0, safe_prompt_1.migratePrompt)([
162
+ {
163
+ name: 'proceed',
164
+ type: 'confirm',
165
+ message: `You're on the default branch '${currentBranch}'. nx migrate will create a commit for each migration on this branch. Continue?`,
166
+ initial: false,
167
+ },
168
+ ]);
169
+ return proceed;
170
+ }
@@ -1,20 +1,19 @@
1
1
  import type { NxMigrateConfiguration } from '../../config/nx-json';
2
2
  import { type MigrateArgs } from './command-object';
3
3
  /**
4
- * Overlays `nx.json` `migrate` defaults onto the raw `nx migrate` CLI args so a
5
- * CLI flag always wins, then `nx.json`, then the built-in default. Returns a new
6
- * args object; the input is not mutated.
4
+ * Overlays `nx.json` `migrate` defaults onto the raw CLI args: a CLI flag wins,
5
+ * then `nx.json`, then the built-in default. Returns a new args object; the
6
+ * input is not mutated.
7
7
  *
8
- * Phase-aware: generate-only options (`include`, `multiMajorMode`) are applied only
9
- * when not running migrations; run-only options (`createCommits`,
10
- * `commitPrefix`, `agentic`, `validate`) only when running migrations. This
11
- * mirrors where each option is consumed and avoids tripping the "cannot be
12
- * combined with --run-migrations" guards in `parseMigrationsOptions`.
8
+ * Phase-aware so each option is only filled where it is consumed, and so a
9
+ * config value never trips the mutually-exclusive-flag guards in
10
+ * `parseMigrationsOptions`: `include` and `multiMajorMode` in the generate
11
+ * phase only, `agentic` / `validate` / `createCommits` / `commitPrefix` when
12
+ * running the whole migrations file or a single migration.
13
13
  *
14
- * `include` is carried as `includeFromConfig` rather than `include` so it is never
15
- * mistaken for an explicit `--include`: `resolveInclude` applies it only when the
16
- * resolved target supports optional updates, leaving targets that don't opt in
17
- * unaffected.
14
+ * `include` is carried as `includeFromConfig` so it is never mistaken for an
15
+ * explicit `--include`: `resolveInclude` applies it only when the resolved
16
+ * target supports optional updates.
18
17
  */
19
18
  export declare function applyNxJsonMigrateDefaults(args: MigrateArgs, migrateConfig: NxMigrateConfiguration | undefined, env?: NodeJS.ProcessEnv): MigrateArgs;
20
19
  /**
@@ -6,20 +6,19 @@ const cli_args_1 = require("./agentic/cli-args");
6
6
  const command_object_1 = require("./command-object");
7
7
  const MULTI_MAJOR_MODE_ENV = 'NX_MULTI_MAJOR_MODE';
8
8
  /**
9
- * Overlays `nx.json` `migrate` defaults onto the raw `nx migrate` CLI args so a
10
- * CLI flag always wins, then `nx.json`, then the built-in default. Returns a new
11
- * args object; the input is not mutated.
9
+ * Overlays `nx.json` `migrate` defaults onto the raw CLI args: a CLI flag wins,
10
+ * then `nx.json`, then the built-in default. Returns a new args object; the
11
+ * input is not mutated.
12
12
  *
13
- * Phase-aware: generate-only options (`include`, `multiMajorMode`) are applied only
14
- * when not running migrations; run-only options (`createCommits`,
15
- * `commitPrefix`, `agentic`, `validate`) only when running migrations. This
16
- * mirrors where each option is consumed and avoids tripping the "cannot be
17
- * combined with --run-migrations" guards in `parseMigrationsOptions`.
13
+ * Phase-aware so each option is only filled where it is consumed, and so a
14
+ * config value never trips the mutually-exclusive-flag guards in
15
+ * `parseMigrationsOptions`: `include` and `multiMajorMode` in the generate
16
+ * phase only, `agentic` / `validate` / `createCommits` / `commitPrefix` when
17
+ * running the whole migrations file or a single migration.
18
18
  *
19
- * `include` is carried as `includeFromConfig` rather than `include` so it is never
20
- * mistaken for an explicit `--include`: `resolveInclude` applies it only when the
21
- * resolved target supports optional updates, leaving targets that don't opt in
22
- * unaffected.
19
+ * `include` is carried as `includeFromConfig` so it is never mistaken for an
20
+ * explicit `--include`: `resolveInclude` applies it only when the resolved
21
+ * target supports optional updates.
23
22
  */
24
23
  function applyNxJsonMigrateDefaults(args, migrateConfig, env = process.env) {
25
24
  if (!migrateConfig) {
@@ -29,7 +28,8 @@ function applyNxJsonMigrateDefaults(args, migrateConfig, env = process.env) {
29
28
  // `--run-migrations` with no value is normalized to '' by yargs, so a defined
30
29
  // (even empty-string) value means we're in the run-migrations phase.
31
30
  const isRunMigrations = merged.runMigrations !== undefined;
32
- if (isRunMigrations) {
31
+ const isSingleMigration = merged.runMigration !== undefined;
32
+ if (isRunMigrations || isSingleMigration) {
33
33
  if (merged.createCommits === undefined &&
34
34
  migrateConfig.createCommits !== undefined) {
35
35
  assertType(migrateConfig.createCommits, 'boolean', 'createCommits');
@@ -53,7 +53,7 @@ function applyNxJsonMigrateDefaults(args, migrateConfig, env = process.env) {
53
53
  merged.validate = migrateConfig.validate;
54
54
  }
55
55
  }
56
- else {
56
+ if (!isRunMigrations && !isSingleMigration) {
57
57
  if (merged.include === undefined && migrateConfig.include !== undefined) {
58
58
  assertOneOf(migrateConfig.include, command_object_1.MIGRATE_INCLUDE_VALUES, 'include');
59
59
  merged.includeFromConfig = migrateConfig.include;
@@ -1,11 +1,10 @@
1
1
  /**
2
- * Presentation layer for `nx migrate --run-migrations`. Pure helpers — every
3
- * function maps (state) → (terminal output or string lines). Shared visual
4
- * vocabulary across the migrate run:
5
- * `→` start · `✓` success · `✗` failure · `↷` skipped · `ℹ` info · `─` boundary
2
+ * Pure presentation helpers for the migrate run phase (`--run-migrations` and
3
+ * the single-migration worker). Shared glyph vocabulary:
4
+ * `→` start, `✓` success, `✗` failure, `↷` skipped, `ℹ` info, `─` boundary
6
5
  *
7
6
  * Inputs are typed structurally (e.g. `{ name: string }[]`) so this module
8
- * stays decoupled from `ExecutableMigration` and the executor in migrate.ts.
7
+ * stays decoupled from the migration executor in migrate.ts.
9
8
  */
10
9
  /**
11
10
  * Some agent TUIs (codex, opencode) don't fully reset their cursor / SGR state
@@ -24,6 +23,22 @@ export declare function logMigrationBoundary(index: number, total: number, pkg:
24
23
  * ✓ <label>[ (<sha>)]: <summary>
25
24
  */
26
25
  export declare function logAgenticSuccessOutcome(label: string, sha: string | null, summary: string): void;
26
+ /**
27
+ * Logs the skip line for a migration that waived its AI step through
28
+ * `skipAgentic`, plus a verbose note for any `agentContext` the waiver
29
+ * dropped. A hybrid waives its paired prompt; a generator-only migration
30
+ * waives the validation pass, so callers must reach here only once they know
31
+ * one was on the table. Under `inside-agent` only a hybrid can, so the
32
+ * hand-off dropped alongside is always a prompt's; a waived generator-only
33
+ * migration keeps its own. The note is author-facing, hence `--verbose`.
34
+ */
35
+ export declare function logWaivedAgenticStep(migration: {
36
+ package: string;
37
+ name: string;
38
+ prompt?: string;
39
+ implementation?: string;
40
+ factory?: string;
41
+ }, agentContext: string[]): void;
27
42
  /**
28
43
  * Per-migration outcome record consumed by the failure recap. One entry is
29
44
  * appended per iteration that returned without throwing; the failing migration
@@ -80,6 +95,13 @@ export type CommitState = {
80
95
  * means the migration threw before completing — the executor's catch block
81
96
  * records it so the recap can list it under retained-state alongside any
82
97
  * other migrations whose commits never landed.
98
+ *
99
+ * `waivedAgenticStep` is set when the migration returned `skipAgentic: true`
100
+ * and something was actually waived: a hybrid's prompt, which is owed in every
101
+ * agentic mode, or a generator-only migration's validation step, only when it
102
+ * would have run. Recorded here rather than counted in the executor so the
103
+ * success tally and the failure recap derive the same number from the same
104
+ * records instead of threading a counter through both.
83
105
  */
84
106
  export type MigrationOutcome = {
85
107
  migration: {
@@ -89,6 +111,7 @@ export type MigrationOutcome = {
89
111
  status: 'completed';
90
112
  kind: MigrationOutcomeKind;
91
113
  commit: CommitState;
114
+ waivedAgenticStep?: boolean;
92
115
  } | {
93
116
  migration: {
94
117
  package: string;
@@ -106,6 +129,18 @@ export type MigrationOutcome = {
106
129
  * counted because the absorbing commit's record already contributes one.
107
130
  */
108
131
  export declare function countLandedCommits(outcomes: ReadonlyArray<MigrationOutcome>): number;
132
+ /**
133
+ * Counts the migrations that waived the AI step they would otherwise have
134
+ * run. Shared by the success tally and the failure recap so the two can't
135
+ * report different numbers.
136
+ */
137
+ export declare function countWaivedAgenticSteps(outcomes: ReadonlyArray<MigrationOutcome>): number;
138
+ /**
139
+ * The recap phrase for waived AI steps. "not needed" rather than
140
+ * "skipped"/"deferred", which both recaps reserve for work the user still
141
+ * owes.
142
+ */
143
+ export declare function formatWaivedAgenticSteps(count: number): string;
109
144
  /**
110
145
  * Migrations whose own commit attempt failed and whose diff was never
111
146
  * absorbed by a later commit. Surfaces what the user has to commit or
@@ -144,17 +179,20 @@ export declare function logFailureRecap(opts: {
144
179
  * emitting a misleading `0 prompt migrations skipped.` line.
145
180
  *
146
181
  * Rule (kept coherent across every scenario):
147
- * - When at least one migration was applied: `<N> migrations applied, <K> commits created[, <D> prompt migrations <skipped|deferred>]`.
182
+ * - When at least one migration was applied: `<N> migrations applied, <K> commits created[, <D> prompt migrations <skipped|deferred>][, <W> AI steps not needed]`.
148
183
  * The `<K> commits created` part stays even at 0 — it tells the reader work
149
184
  * was applied but not committed (the J4/J8 information made explicit).
150
185
  * - When zero migrations were applied but some prompt halves were
151
- * skipped/deferred: `<D> prompt migrations <skipped|deferred>` only.
186
+ * skipped/deferred: `<D> prompt migrations <skipped|deferred>` only. Waiving
187
+ * never adds to `skippedPromptsCount`, so `appliedCount` is non-zero whenever
188
+ * `waivedAgenticStepsCount` is, and this branch can't be reached with one.
152
189
  * - When zero of either: no body line.
153
190
  */
154
191
  export declare function buildTallyBodyLine(opts: {
155
192
  appliedCount: number;
156
193
  committedShasCount: number;
157
194
  skippedPromptsCount: number;
195
+ waivedAgenticStepsCount: number;
158
196
  insideAgent: boolean;
159
197
  }): string | null;
160
198
  /**