nx 23.0.0-beta.23 → 23.0.0-beta.24

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 (34) hide show
  1. package/dist/src/command-line/examples.js +1 -1
  2. package/dist/src/command-line/migrate/agentic/definitions.js +24 -1
  3. package/dist/src/command-line/migrate/agentic/handoff.d.ts +6 -0
  4. package/dist/src/command-line/migrate/agentic/handoff.js +8 -1
  5. package/dist/src/command-line/migrate/agentic/prompts/generic-validation.js +1 -1
  6. package/dist/src/command-line/migrate/agentic/prompts/hybrid-prompt-migration.js +1 -1
  7. package/dist/src/command-line/migrate/agentic/prompts/prompt-migration.js +1 -1
  8. package/dist/src/command-line/migrate/agentic/prompts/system-prompt.js +18 -17
  9. package/dist/src/command-line/migrate/agentic/select.d.ts +2 -0
  10. package/dist/src/command-line/migrate/agentic/select.js +4 -2
  11. package/dist/src/command-line/migrate/command-object.js +1 -1
  12. package/dist/src/command-line/migrate/migrate-ui-api.d.ts +12 -0
  13. package/dist/src/command-line/migrate/migrate-ui-api.js +74 -5
  14. package/dist/src/command-line/migrate/migrate.d.ts +2 -0
  15. package/dist/src/command-line/migrate/migrate.js +35 -10
  16. package/dist/src/command-line/migrate/multi-major.d.ts +1 -0
  17. package/dist/src/command-line/migrate/multi-major.js +4 -4
  18. package/dist/src/command-line/migrate/safe-prompt.d.ts +5 -0
  19. package/dist/src/command-line/migrate/safe-prompt.js +9 -0
  20. package/dist/src/core/graph/main.js +1 -1
  21. package/dist/src/core/graph/styles.css +1 -1
  22. package/dist/src/daemon/server/project-graph-incremental-recomputation.js +27 -19
  23. package/dist/src/generators/utils/project-configuration.d.ts +10 -1
  24. package/dist/src/generators/utils/project-configuration.js +0 -8
  25. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  26. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  27. package/dist/src/plugins/js/utils/register.js +6 -0
  28. package/dist/src/project-graph/plugins/get-plugins.js +63 -19
  29. package/dist/src/project-graph/plugins/resolve-plugin.d.ts +7 -0
  30. package/dist/src/project-graph/plugins/resolve-plugin.js +15 -0
  31. package/dist/src/project-graph/utils/retrieve-workspace-files.d.ts +6 -0
  32. package/dist/src/project-graph/utils/retrieve-workspace-files.js +9 -0
  33. package/package.json +11 -11
  34. package/schemas/migrations-schema.json +186 -0
@@ -273,7 +273,7 @@ exports.examples = {
273
273
  },
274
274
  {
275
275
  command: 'migrate latest --interactive',
276
- description: 'Collect package updates and migrations in interactive mode. In this mode, the user will be prompted whether to apply any optional package update and migration',
276
+ description: 'Collect package updates and migrations in interactive mode. In this mode, the user will be prompted whether to apply any optional package update and migration. Only supported when migrating to Nx versions lower than v23',
277
277
  },
278
278
  {
279
279
  command: 'migrate latest --from=nx@14.5.0 --exclude-applied-migrations',
@@ -4,6 +4,7 @@ exports.AGENT_DEFINITIONS = exports.opencodeDefinition = exports.codexDefinition
4
4
  exports.getAgentDefinition = getAgentDefinition;
5
5
  const os_1 = require("os");
6
6
  const path_1 = require("path");
7
+ const handoff_1 = require("./handoff");
7
8
  // --- Claude Code ---------------------------------------------------------
8
9
  function claudeCodeWellKnownPaths() {
9
10
  if (process.platform === 'win32') {
@@ -12,9 +13,25 @@ function claudeCodeWellKnownPaths() {
12
13
  }
13
14
  return [(0, path_1.join)((0, os_1.homedir)(), '.claude', 'local', 'claude')];
14
15
  }
16
+ // Pre-authorizes the handoff write: Claude Code's default permission mode
17
+ // asks before file writes it has no allow rule for, so without this each step
18
+ // ends with an approval prompt for nx's own handoff scratch. Prefix-less
19
+ // patterns resolve against the session cwd (pinned to the workspace root
20
+ // below); `Edit` covers corrections to an already-written handoff.
21
+ const CLAUDE_CODE_HANDOFF_ALLOWED_TOOLS = `Write(${handoff_1.MIGRATE_RUNS_RELATIVE_DIR}/**),Edit(${handoff_1.MIGRATE_RUNS_RELATIVE_DIR}/**)`;
15
22
  function claudeCodeBuildInteractive(ctx) {
16
23
  return {
17
- args: ['--system-prompt', ctx.systemContext, ctx.userPrompt],
24
+ // `--allowedTools` is variadic (space/comma separated): a positional
25
+ // placed right after its value gets swallowed as another rule. The rules
26
+ // must stay in one comma-joined element with a non-variadic flag
27
+ // (`--system-prompt`) between them and the user prompt.
28
+ args: [
29
+ '--allowedTools',
30
+ CLAUDE_CODE_HANDOFF_ALLOWED_TOOLS,
31
+ '--system-prompt',
32
+ ctx.systemContext,
33
+ ctx.userPrompt,
34
+ ],
18
35
  cwd: ctx.workspaceRoot,
19
36
  };
20
37
  }
@@ -29,6 +46,9 @@ exports.claudeCodeDefinition = {
29
46
  function codexWellKnownPaths() {
30
47
  return [];
31
48
  }
49
+ // No handoff permission flag: codex's default sandbox already allows writes
50
+ // inside the cwd tree without prompting, and a user-hardened read-only config
51
+ // is a deliberate choice we don't override.
32
52
  function codexBuildInteractive(ctx) {
33
53
  return {
34
54
  args: ['-c', `developer_instructions=${ctx.systemContext}`, ctx.userPrompt],
@@ -62,6 +82,9 @@ function opencodeWellKnownPaths() {
62
82
  candidates.push((0, path_1.join)(home, '.opencode', 'bin', 'opencode'));
63
83
  return candidates;
64
84
  }
85
+ // No handoff permission config: opencode's `edit` permission defaults to
86
+ // allow, and injecting one would clobber (not merge with) a user's own
87
+ // permission patterns.
65
88
  function opencodeBuildInteractive(ctx) {
66
89
  const config = {
67
90
  agent: {
@@ -1,4 +1,10 @@
1
1
  import { HandoffFile } from './types';
2
+ /**
3
+ * Workspace-relative directory holding all migrate-run scratch (handoff
4
+ * files). Shared with the agent permission rules in `definitions.ts` so the
5
+ * pre-authorized write scope can't drift from the actual layout.
6
+ */
7
+ export declare const MIGRATE_RUNS_RELATIVE_DIR = ".nx/migrate-runs";
2
8
  /** Returns the run directory for a given workspace + run id (target version). */
3
9
  export declare function runDirPath(workspaceRoot: string, runId: string): string;
4
10
  /**
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MIGRATE_RUNS_RELATIVE_DIR = void 0;
3
4
  exports.runDirPath = runDirPath;
4
5
  exports.mkdirSafely = mkdirSafely;
5
6
  exports.initRunDir = initRunDir;
@@ -9,9 +10,15 @@ exports.readHandoff = readHandoff;
9
10
  exports.waitForValidHandoff = waitForValidHandoff;
10
11
  const fs_1 = require("fs");
11
12
  const path_1 = require("path");
13
+ /**
14
+ * Workspace-relative directory holding all migrate-run scratch (handoff
15
+ * files). Shared with the agent permission rules in `definitions.ts` so the
16
+ * pre-authorized write scope can't drift from the actual layout.
17
+ */
18
+ exports.MIGRATE_RUNS_RELATIVE_DIR = '.nx/migrate-runs';
12
19
  /** Returns the run directory for a given workspace + run id (target version). */
13
20
  function runDirPath(workspaceRoot, runId) {
14
- return (0, path_1.join)(workspaceRoot, '.nx', 'migrate-runs', runId);
21
+ return (0, path_1.join)(workspaceRoot, exports.MIGRATE_RUNS_RELATIVE_DIR, runId);
15
22
  }
16
23
  /**
17
24
  * `mkdir -p` with a contextual error wrapper. Without this, the raw
@@ -42,7 +42,7 @@ function buildGenericValidationUserPrompt(ctx) {
42
42
  const firstStep = ctx.impl.hasDiffContext
43
43
  ? `1. Inspect this migration's changes. ${(0, shared_rendering_1.renderGitInspectInstruction)()} Resolve each affected path to its owning Nx project via \`nx show project <name>\` (or by reading the project's \`project.json\` / \`package.json\`) to discover which targets each project actually defines — do not assume \`typecheck\` / \`test\` / \`lint\` exist. If no typecheck-equivalent exists, \`build\` is an acceptable substitute.`
44
44
  : `1. Resolve each path in <files_changed> to its owning Nx project. Use \`nx show project <name>\` (or read the project's \`project.json\` / \`package.json\`) to discover which targets each project actually defines — do not assume \`typecheck\` / \`test\` / \`lint\` exist. If no typecheck-equivalent exists, \`build\` is an acceptable substitute.`;
45
- lines.push(``, `<validation_instructions>`, firstStep, `2. Pick the smallest relevant subset of available targets to verify the change. Prefer \`nx affected -t <target>\` (or \`nx run <project>:<target>\` for a single project). When many small projects are affected, you may use \`nx run-many -t <target> -p <project1>,<project2>\` with the project list derived from the changed files. Unscoped \`nx run-many\` (no \`-p\`) is forbidden.`, `3. If a verification surfaces an issue the migration should have produced cleanly (e.g. a missing import, a type annotation the generator's template missed), you may apply a minor in-scope fix. The boundary is "what this migration intended to accomplish" — do not refactor, do not modify functionality unrelated to the migration, do not extend the migration's scope, do not touch code the migration was not concerned with. If you are unsure whether a fix is in scope, report it in \`summary\` instead of applying.`, `4. Apply every fix you can within scope, then write your handoff. On \`status: "success"\`, summarize what you verified and any fixes you applied. On \`status: "failed"\`, enumerate the unresolved findings in \`summary\` so the user can address them; no commit will be created from a failed run, so the generator's changes and your partial fixes will sit uncommitted in the working tree for the user to review.`, `</validation_instructions>`, ``, `Once you finish, write your handoff JSON to:`, ...(0, shared_rendering_1.renderHandoffPathFooter)(ctx.handoffFileAbsolutePath));
45
+ lines.push(``, `<validation_instructions>`, firstStep, `2. Pick the smallest relevant subset of available targets to verify the change. Prefer \`nx affected -t <target>\` (or \`nx run <project>:<target>\` for a single project). When many small projects are affected, you may use \`nx run-many -t <target> -p <project1>,<project2>\` with the project list derived from the changed files. Unscoped \`nx run-many\` (no \`-p\`) is forbidden.`, `3. If a verification surfaces an issue the migration should have produced cleanly (e.g. a missing import, a type annotation the generator's template missed), you may apply a minor in-scope fix. The boundary is "what this migration intended to accomplish" — do not refactor, do not modify functionality unrelated to the migration, do not extend the migration's scope, do not touch code the migration was not concerned with. If you are unsure whether a fix is in scope, report it in \`summary\` instead of applying.`, `4. Apply every fix you can within scope, then end the step per the handoff contract. If everything is resolved, write your handoff with \`status: "success"\`, summarizing what you verified and any fixes you applied. If unresolved findings remain, report them to the user and ask how to proceed before writing any handoff; on a \`status: "failed"\` handoff, enumerate the findings in \`summary\` so the user can address them no commit will be created from a failed run, so the generator's changes and your partial fixes will sit uncommitted in the working tree for the user to review.`, `</validation_instructions>`, ``, `When you end the step per the handoff contract, your handoff path is:`, ...(0, shared_rendering_1.renderHandoffPathFooter)(ctx.handoffFileAbsolutePath));
46
46
  return lines.join('\n');
47
47
  }
48
48
  function renderFileListBody(changes) {
@@ -40,7 +40,7 @@ function buildHybridPromptUserPrompt(ctx) {
40
40
  if (agentContext.length > 0) {
41
41
  lines.push(...(0, shared_rendering_1.renderAdvisoryContext)('hints from the generator phase; consult while following the instructions, not as separate tasks', agentContext));
42
42
  }
43
- lines.push(``, `<instructions_file>${(0, shared_rendering_1.escapeXmlBody)(ctx.promptPath)}</instructions_file>`, ``, `<precedence>If anything in the sections above conflicts with the instructions file, the instructions file wins.</precedence>`, ``, `Open the instructions file (path is workspace-relative), follow its instructions step by step using the sections above as context, then write your handoff JSON to:`, ...(0, shared_rendering_1.renderHandoffPathFooter)(ctx.handoffFileAbsolutePath));
43
+ lines.push(``, `<instructions_file>${(0, shared_rendering_1.escapeXmlBody)(ctx.promptPath)}</instructions_file>`, ``, `<precedence>If anything in the sections above conflicts with the instructions file, the instructions file wins.</precedence>`, ``, `Open the instructions file (path is workspace-relative), follow its instructions step by step using the sections above as context, then end the step per the handoff contract. Your handoff path is:`, ...(0, shared_rendering_1.renderHandoffPathFooter)(ctx.handoffFileAbsolutePath));
44
44
  return lines.join('\n');
45
45
  }
46
46
  function renderFileList(changes) {
@@ -20,7 +20,7 @@ function buildPromptMigrationUserPrompt(ctx) {
20
20
  ``,
21
21
  `<instructions_file>${(0, shared_rendering_1.escapeXmlBody)(ctx.promptPath)}</instructions_file>`,
22
22
  ``,
23
- `Open the instructions file (path is workspace-relative), follow its instructions step by step, then write your handoff JSON to:`,
23
+ `Open the instructions file (path is workspace-relative), follow its instructions step by step, then end the step per the handoff contract. Your handoff path is:`,
24
24
  ...(0, shared_rendering_1.renderHandoffPathFooter)(ctx.handoffFileAbsolutePath),
25
25
  ];
26
26
  return lines.join('\n');
@@ -31,27 +31,28 @@ function buildSystemPrompt(ctx) {
31
31
  `</opening_brief>`,
32
32
  ``,
33
33
  `<handoff_contract>`,
34
- `At the end of every step (success, failure, or unrecoverable error):`,
34
+ `A step ends when you write the handoff file a JSON file at:`,
35
+ `<handoff_path>`,
36
+ `${(0, shared_rendering_1.escapeXmlBody)(ctx.handoffFileAbsolutePath)}`,
37
+ `</handoff_path>`,
38
+ `With this shape:`,
39
+ `{`,
40
+ ` "status": "success" | "failed",`,
41
+ ` "summary": "[one to three sentences: what was done, or why it failed]"`,
42
+ `}`,
43
+ `\`nx migrate\` is watching for this file; once it appears nx closes this session automatically and continues with the next step. Do not attempt further work after the handoff is written.`,
35
44
  ``,
36
- `1. Summarize what you did or why you couldn't in one or two sentences. Then note that writing the handoff file next will close this session and \`nx migrate\` will continue with the next step. Offer the user a chance to ask follow-up questions or redirect before you write — if they have none, proceed with the write.`,
37
- `2. Write a JSON file at:`,
38
- ` <handoff_path>`,
39
- ` ${(0, shared_rendering_1.escapeXmlBody)(ctx.handoffFileAbsolutePath)}`,
40
- ` </handoff_path>`,
41
- ` With this shape:`,
42
- ` {`,
43
- ` "status": "success" | "failed",`,
44
- ` "summary": "[one to three sentences: what was done, or why it failed]"`,
45
- ` }`,
46
- `3. You're done. \`nx migrate\` is watching for the handoff file; once it appears nx closes this session automatically and continues with the next step. Do not attempt further work after the handoff is written.`,
45
+ `How to end the step:`,
46
+ `1. Success — the step is fully applied (or validation passed): state a one-or-two-sentence summary of what you did and, in the same turn, write the handoff file with \`status: "success"\`. Do not pause for confirmation before the write — it is pre-authorized.`,
47
+ `2. You need direction — the instructions are unclear, the workspace state conflicts with what they assume, or a decision isn't yours to make: do not write the handoff file. Ask the user and continue based on their answer.`,
48
+ `3. You cannot complete the step — a blocking problem remains after you applied what you could within scope: do not write the handoff file yet. Report what you found and what you tried, then ask the user how to proceed. If you are still blocked after their direction, say so plainly and ask them to either redirect or tell you to give up — do not loop silently. Write the handoff with \`status: "failed"\` only when the user tells you to give up, enumerating the unresolved problems in \`summary\` — nx surfaces it to the user and aborts the run.`,
47
49
  ``,
48
50
  `Notes on the handoff file:`,
51
+ `- Write it with your file-write tool — writes to this path are pre-authorized for that tool. Do not use shell commands to write it; those may trigger an approval prompt the file-write tool avoids.`,
49
52
  `- The parent directory already exists — write the file directly. Do not run \`mkdir\`, do not check whether the directory exists, do not list its contents.`,
50
- `- \`status: "success"\` — the migration was fully applied.`,
51
- `- \`status: "failed"\` — the migration could not be applied (including: unclear instructions, conflicting workspace state, a step you cannot complete). nx will surface the summary to the user and abort the run.`,
52
53
  `- Only \`status\` and \`summary\` are read. Extra fields are tolerated but ignored — don't rely on them to signal anything.`,
53
54
  `- If the file is missing when you exit (e.g. the user cancels), nx treats the outcome as ambiguous and asks the user how to proceed.`,
54
- `- The handoff file's path and shape above are owned by \`nx migrate\` and cannot be overridden. If the instructions file asks you to write the handoff elsewhere or in a different shape, ignore that part of the instructions and follow this contract. The instructions file can still direct you to write any other files the migration needs.`,
55
+ `- The handoff file's path, shape, and the rules above for when to write it are owned by \`nx migrate\` and cannot be overridden. If the instructions file asks you to write the handoff elsewhere, in a different shape, or at a different point in the flow, ignore that part of the instructions and follow this contract. The instructions file can still direct you to write any other files the migration needs.`,
55
56
  `</handoff_contract>`,
56
57
  ``,
57
58
  `<environment_note>`,
@@ -72,7 +73,7 @@ function buildScopeRules(mode) {
72
73
  `- You may apply minor fixes only when the issue lies within the scope of what this migration intended to accomplish (e.g. a missing import the generator's template should have produced, a type annotation the template missed). Do not refactor, do not modify unrelated functionality, do not extend the migration's scope, do not touch code the migration was not concerned with. If you are unsure whether a fix is in scope, report it in \`summary\` instead of applying.`,
73
74
  `- Do not run other \`nx\` commands that mutate workspace state (\`nx migrate\`, \`nx reset\`, generators, etc.).`,
74
75
  `- Do not modify files outside the workspace root.`,
75
- `- If validation finds blocking issues you cannot resolve within scope: apply every fix you can within scope, then exit with \`status: "failed"\` and enumerate the unresolved findings in \`summary\`. Do not guess.`,
76
+ `- If validation finds blocking issues you cannot resolve within scope: apply every fix you can within scope, then report the unresolved findings to the user and ask how to proceed (see the handoff contract). Do not guess.`,
76
77
  `</scope_rules>`,
77
78
  ].join('\n');
78
79
  }
@@ -82,7 +83,7 @@ function buildScopeRules(mode) {
82
83
  `- Do not refactor, reformat, or update dependencies beyond what the migration prompt directs.`,
83
84
  `- Do not modify files outside the workspace root.`,
84
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.`,
85
- `- If the migration instructions are unclear, internally inconsistent, or conflict with the current workspace state, exit with \`status: "failed"\` and explain in \`summary\`. Do not guess.`,
86
+ `- 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.`,
86
87
  `</scope_rules>`,
87
88
  ].join('\n');
88
89
  }
@@ -6,6 +6,8 @@ export interface ResolveAgenticInput {
6
6
  migrations: ReadonlyArray<{
7
7
  prompt?: string;
8
8
  }>;
9
+ /** The `--interactive` flag; `false` (`--no-interactive`) disables all prompting. */
10
+ interactive?: boolean;
9
11
  }
10
12
  /**
11
13
  * Resolves the agentic state for a `--run-migrations` invocation. Runs once,
@@ -27,7 +27,9 @@ async function resolveAgentic(input) {
27
27
  });
28
28
  return { kind: 'inside-agent' };
29
29
  }
30
- const isInteractive = !!process.stdin.isTTY && !!process.stdout.isTTY;
30
+ const isInteractive = !!process.stdin.isTTY &&
31
+ !!process.stdout.isTTY &&
32
+ input.interactive !== false;
31
33
  // Skip detection for the one case where the result is unused: explicit
32
34
  // `--agentic=false`. For every other path (explicit enable, explicit id, or
33
35
  // undefined-with-prompt) we either need the detection result to pick/verify
@@ -95,7 +97,7 @@ function requireInteractiveOrAbort(isInteractive) {
95
97
  }
96
98
  function warnAgenticInteractiveOnly() {
97
99
  output_1.output.warn({
98
- title: 'Skipping the agentic flow: it is interactive-only in this release and this is a non-interactive terminal.',
100
+ title: 'Skipping the agentic flow: it is interactive-only in this release and this run is non-interactive.',
99
101
  bodyLines: [
100
102
  'Continuing the migration without the agentic flow. Re-run in an interactive terminal to use it.',
101
103
  ],
@@ -76,7 +76,7 @@ function withMigrationOptions(yargs) {
76
76
  default: exports.DEFAULT_MIGRATION_COMMIT_PREFIX,
77
77
  })
78
78
  .option('interactive', {
79
- describe: 'Enable prompts to confirm whether to collect optional package updates and migrations.',
79
+ describe: "Enable prompts to confirm whether to collect optional package updates and migrations. Not supported when migrating to Nx v23 or later: use '--mode' to choose which packages to migrate.",
80
80
  type: 'boolean',
81
81
  })
82
82
  .option('excludeAppliedMigrations', {
@@ -1,5 +1,7 @@
1
1
  import type { MigrationDetailsWithId } from '../../config/misc-interfaces';
2
2
  import type { FileChange } from '../../generators/tree';
3
+ import { isHybridMigration, isPromptOnlyMigration } from './migrate';
4
+ export { isPromptOnlyMigration, isHybridMigration };
3
5
  export type MigrationsJsonMetadata = {
4
6
  completedMigrations?: Record<string, SuccessfulMigration | FailedMigration | SkippedMigration | StoppedMigration>;
5
7
  runningMigrations?: string[];
@@ -16,6 +18,7 @@ export type SuccessfulMigration = {
16
18
  changedFiles: Omit<FileChange, 'content'>[];
17
19
  ref: string;
18
20
  nextSteps?: string[];
21
+ acknowledgedPrompt?: boolean;
19
22
  };
20
23
  export type FailedMigration = {
21
24
  type: 'failed';
@@ -72,5 +75,14 @@ export declare function addStoppedMigration(id: string, error: string): (migrati
72
75
  };
73
76
  export declare function readMigrationsJsonMetadata(workspacePath: string): MigrationsJsonMetadata;
74
77
  export declare function undoMigration(workspacePath: string, id: string): (migrationsJsonMetadata: MigrationsJsonMetadata) => MigrationsJsonMetadata;
78
+ /**
79
+ * Records that the user has confirmed completion of a prompt-bearing
80
+ * migration's AI prompt phase. Dispatches by shape so callers (the webview
81
+ * event handler in Nx Console) don't need to know which is which:
82
+ * - prompt-only: records success directly (no spawn, no process lifecycle).
83
+ * - hybrid: persists the `acknowledgedPrompt` flag on the existing
84
+ * successful record from the generator phase.
85
+ */
86
+ export declare function acknowledgeMigrationPrompt(workspacePath: string, migration: MigrationDetailsWithId): void;
75
87
  export declare function killMigrationProcess(migrationId: string, workspacePath?: string): boolean;
76
88
  export declare function stopMigration(migrationId: string): (migrationsJsonMetadata: MigrationsJsonMetadata) => MigrationsJsonMetadata;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isHybridMigration = exports.isPromptOnlyMigration = void 0;
3
4
  exports.recordInitialMigrationMetadata = recordInitialMigrationMetadata;
4
5
  exports.finishMigrationProcess = finishMigrationProcess;
5
6
  exports.runSingleMigration = runSingleMigration;
@@ -12,12 +13,15 @@ exports.addSkippedMigration = addSkippedMigration;
12
13
  exports.addStoppedMigration = addStoppedMigration;
13
14
  exports.readMigrationsJsonMetadata = readMigrationsJsonMetadata;
14
15
  exports.undoMigration = undoMigration;
16
+ exports.acknowledgeMigrationPrompt = acknowledgeMigrationPrompt;
15
17
  exports.killMigrationProcess = killMigrationProcess;
16
18
  exports.stopMigration = stopMigration;
17
19
  const child_process_1 = require("child_process");
18
20
  const fs_1 = require("fs");
19
21
  const path_1 = require("path");
20
22
  const migrate_1 = require("./migrate");
23
+ Object.defineProperty(exports, "isHybridMigration", { enumerable: true, get: function () { return migrate_1.isHybridMigration; } });
24
+ Object.defineProperty(exports, "isPromptOnlyMigration", { enumerable: true, get: function () { return migrate_1.isPromptOnlyMigration; } });
21
25
  let currentMigrationProcess = null;
22
26
  let currentMigrationId = null;
23
27
  let migrationCancelled = false;
@@ -79,6 +83,14 @@ async function runSingleMigration(workspacePath, migration, configuration) {
79
83
  currentMigrationId = migration.id;
80
84
  migrationCancelled = false;
81
85
  modifyMigrationsJsonMetadata(workspacePath, addRunningMigration(migration.id));
86
+ // Prompt-only migrations have no deterministic implementation to spawn.
87
+ // The state-machine's auto-run hits this branch; the manual Mark-as-
88
+ // Completed path calls `recordPromptOnlySuccess` directly so it stays out
89
+ // of the process-tracking lifecycle.
90
+ if ((0, migrate_1.isPromptOnlyMigration)(migration)) {
91
+ recordPromptOnlySuccess(workspacePath, migration);
92
+ return;
93
+ }
82
94
  const gitRefBefore = (0, child_process_1.execSync)('git rev-parse HEAD', {
83
95
  cwd: workspacePath,
84
96
  encoding: 'utf-8',
@@ -192,6 +204,13 @@ async function runSingleMigration(workspacePath, migration, configuration) {
192
204
  }
193
205
  }
194
206
  async function getImplementationPath(workspacePath, migration) {
207
+ // Prompt-only migrations have no implementation — the "source" the user
208
+ // wants to see is the prompt file itself. Resolving via the regular
209
+ // implementation lookup would throw because both `implementation` and
210
+ // `factory` are unset.
211
+ if ((0, migrate_1.isPromptOnlyMigration)(migration)) {
212
+ return (0, path_1.join)(workspacePath, migration.prompt);
213
+ }
195
214
  const { collection, collectionPath } = (0, migrate_1.readMigrationCollection)(migration.package, workspacePath);
196
215
  const { path } = (0, migrate_1.getImplementationPath)(collection, collectionPath, migration.name);
197
216
  return path;
@@ -208,6 +227,11 @@ function addSuccessfulMigration(id, fileChanges, ref, nextSteps) {
208
227
  if (!copied.completedMigrations) {
209
228
  copied.completedMigrations = {};
210
229
  }
230
+ // Carry forward a previously-set acknowledgedPrompt so any caller that
231
+ // re-records a successful entry for the same id (no current trigger; this
232
+ // is defensive against future paths) cannot silently drop the user's ack.
233
+ const existing = copied.completedMigrations[id];
234
+ const acknowledgedPrompt = existing?.type === 'successful' && existing.acknowledgedPrompt;
211
235
  copied.completedMigrations = {
212
236
  ...copied.completedMigrations,
213
237
  [id]: {
@@ -216,6 +240,7 @@ function addSuccessfulMigration(id, fileChanges, ref, nextSteps) {
216
240
  changedFiles: fileChanges,
217
241
  ref,
218
242
  nextSteps,
243
+ ...(acknowledgedPrompt && { acknowledgedPrompt: true }),
219
244
  },
220
245
  };
221
246
  return copied;
@@ -312,17 +337,61 @@ function undoMigration(workspacePath, id) {
312
337
  const existing = migrationsJsonMetadata.completedMigrations[id];
313
338
  if (existing.type !== 'successful')
314
339
  throw new Error(`undoMigration called on unsuccessful migration: ${id}`);
315
- (0, child_process_1.execSync)(`git reset --hard ${existing.ref}^`, {
316
- cwd: workspacePath,
317
- encoding: 'utf-8',
318
- windowsHide: true,
319
- });
340
+ // No-changes successful entries (prompt-only short-circuit, generators
341
+ // that ran but produced no diff) have no migration commit to roll back;
342
+ // `existing.ref` is the unmodified HEAD at run time, so `ref^` would
343
+ // reset past unrelated history. Only flip the metadata to skipped.
344
+ if (existing.changedFiles.length > 0) {
345
+ (0, child_process_1.execSync)(`git reset --hard ${existing.ref}^`, {
346
+ cwd: workspacePath,
347
+ encoding: 'utf-8',
348
+ windowsHide: true,
349
+ });
350
+ }
320
351
  migrationsJsonMetadata.completedMigrations[id] = {
321
352
  type: 'skipped',
322
353
  };
323
354
  return migrationsJsonMetadata;
324
355
  };
325
356
  }
357
+ /**
358
+ * Records that the user has confirmed completion of a prompt-bearing
359
+ * migration's AI prompt phase. Dispatches by shape so callers (the webview
360
+ * event handler in Nx Console) don't need to know which is which:
361
+ * - prompt-only: records success directly (no spawn, no process lifecycle).
362
+ * - hybrid: persists the `acknowledgedPrompt` flag on the existing
363
+ * successful record from the generator phase.
364
+ */
365
+ function acknowledgeMigrationPrompt(workspacePath, migration) {
366
+ if ((0, migrate_1.isPromptOnlyMigration)(migration)) {
367
+ recordPromptOnlySuccess(workspacePath, migration);
368
+ return;
369
+ }
370
+ modifyMigrationsJsonMetadata(workspacePath, (metadata) => {
371
+ const existing = metadata.completedMigrations?.[migration.id];
372
+ if (!existing || existing.type !== 'successful') {
373
+ return metadata;
374
+ }
375
+ metadata.completedMigrations = {
376
+ ...metadata.completedMigrations,
377
+ [migration.id]: { ...existing, acknowledgedPrompt: true },
378
+ };
379
+ return metadata;
380
+ });
381
+ }
382
+ // Writes a successful record for a prompt-only migration without touching the
383
+ // process-lifecycle tracking that `runSingleMigration` manages — safe to call
384
+ // from `acknowledgeMigrationPrompt` while another migration may be running.
385
+ // The prompt-path reminder is rendered by the UI from `migration.prompt`, so
386
+ // no next step is recorded here.
387
+ function recordPromptOnlySuccess(workspacePath, migration) {
388
+ const ref = (0, child_process_1.execSync)('git rev-parse HEAD', {
389
+ cwd: workspacePath,
390
+ encoding: 'utf-8',
391
+ windowsHide: true,
392
+ }).trim();
393
+ modifyMigrationsJsonMetadata(workspacePath, addSuccessfulMigration(migration.id, [], ref, []));
394
+ }
326
395
  function killMigrationProcess(migrationId, workspacePath) {
327
396
  try {
328
397
  if (workspacePath) {
@@ -121,6 +121,7 @@ export declare function resolveMode(mode: MigrateMode | undefined, targetPackage
121
121
  hasExcludeAppliedMigrations: boolean;
122
122
  installedMajor?: number | null;
123
123
  isV23Plus?: boolean;
124
+ interactive?: boolean;
124
125
  }, configuredMode?: MigrateMode): Promise<MigrateMode>;
125
126
  type GenerateMigrations = {
126
127
  type: 'generateMigrations';
@@ -154,6 +155,7 @@ type RunMigrations = {
154
155
  ifExists: boolean;
155
156
  agentic: AgenticArg;
156
157
  validate?: boolean;
158
+ interactive?: boolean;
157
159
  };
158
160
  export declare function parseMigrationsOptions(options: {
159
161
  [k: string]: any;
@@ -542,6 +542,13 @@ async function resolveMode(mode, targetPackage, targetVersion, context = {
542
542
  installedMajor >= 22 &&
543
543
  context.isV23Plus === true;
544
544
  const thirdPartyAvailable = installedMajor !== null && installedMajor >= 23;
545
+ // `--interactive` x-prompts let users skip optional third-party updates; the
546
+ // modes supersede that choice, so it is disallowed behind the same v23+ gate.
547
+ if (context.interactive === true &&
548
+ context.isV23Plus === true &&
549
+ (0, version_utils_1.isNxEquivalentTarget)(targetPackage, targetVersion)) {
550
+ throw new Error(`Error: '--interactive' is not supported when migrating to Nx v23 or later. Use '--mode' to choose which packages to migrate: 'first-party' migrates only Nx and its plugins, 'third-party' migrates only the third-party dependencies referenced by Nx, 'all' migrates everything.`);
551
+ }
545
552
  if (mode) {
546
553
  if ((0, version_utils_1.isNxEquivalentTarget)(targetPackage, targetVersion)) {
547
554
  if (mode === 'first-party' && !firstPartyAvailable) {
@@ -590,7 +597,8 @@ async function resolveMode(mode, targetPackage, targetVersion, context = {
590
597
  }
591
598
  if (thirdPartyAvailable &&
592
599
  !context.hasFrom &&
593
- !context.hasExcludeAppliedMigrations) {
600
+ !context.hasExcludeAppliedMigrations &&
601
+ context.interactive !== true) {
594
602
  choices.push({
595
603
  name: 'third-party',
596
604
  message: 'Third-party only (deps managed by Nx)',
@@ -599,7 +607,7 @@ async function resolveMode(mode, targetPackage, targetVersion, context = {
599
607
  if (!choices.length) {
600
608
  return 'all';
601
609
  }
602
- if (!process.stdin.isTTY || (0, is_ci_1.isCI)()) {
610
+ if (!(0, safe_prompt_1.canPrompt)(context.interactive)) {
603
611
  return 'all';
604
612
  }
605
613
  choices.push({
@@ -682,6 +690,7 @@ async function parseMigrationsOptions(options) {
682
690
  ifExists: options.ifExists,
683
691
  agentic: options.agentic,
684
692
  validate: options.validate,
693
+ interactive: options.interactive,
685
694
  };
686
695
  }
687
696
  assertThirdPartyModeFlagCompatibility(options);
@@ -713,6 +722,7 @@ async function parseMigrationsOptions(options) {
713
722
  mode,
714
723
  from: options.from,
715
724
  excludeAppliedMigrations: options.excludeAppliedMigrations,
725
+ interactive: options.interactive,
716
726
  });
717
727
  assertThirdPartyTargetBounds({
718
728
  targetPackage,
@@ -743,6 +753,9 @@ function assertThirdPartyModeFlagCompatibility(options) {
743
753
  if (options.excludeAppliedMigrations === true) {
744
754
  throw new Error(`Error: '--mode=third-party' cannot be combined with '--exclude-applied-migrations'.`);
745
755
  }
756
+ if (options.interactive === true) {
757
+ throw new Error(`Error: '--mode=third-party' cannot be combined with '--interactive'.`);
758
+ }
746
759
  }
747
760
  // Resolves the target package/version up front (third-party → installed
748
761
  // canonical; otherwise resolves dist-tags so the v23 mode gate reads a concrete
@@ -806,6 +819,7 @@ async function resolveTargetAndMode(args) {
806
819
  hasExcludeAppliedMigrations: options.excludeAppliedMigrations === true,
807
820
  installedMajor,
808
821
  isV23Plus,
822
+ interactive: options.interactive,
809
823
  }, options.modeFromConfig);
810
824
  let installedNxVersion;
811
825
  // For third-party, anchor `targetPackage`/`targetVersion` to the installed
@@ -1097,13 +1111,23 @@ async function getPackageMigrationsUsingInstallImpl(packageName, packageVersion,
1097
1111
  await (0, provenance_1.ensurePackageHasProvenance)(packageName, packageVersion);
1098
1112
  }
1099
1113
  try {
1100
- await execAsync(`${pmc.add} ${packageName}@${packageVersion}`, {
1101
- cwd: dir,
1102
- env: {
1103
- ...process.env,
1104
- npm_config_legacy_peer_deps: 'true',
1105
- },
1106
- });
1114
+ const addCommand = `${pmc.add} ${packageName}@${packageVersion}`;
1115
+ try {
1116
+ await execAsync(addCommand, {
1117
+ cwd: dir,
1118
+ env: {
1119
+ ...process.env,
1120
+ npm_config_legacy_peer_deps: 'true',
1121
+ },
1122
+ });
1123
+ }
1124
+ catch (e) {
1125
+ // Only the install command failed; format it as a command failure so the
1126
+ // user sees the package manager's stderr. Errors from the later steps
1127
+ // (reading/validating migrations, resolving prompt files) are surfaced
1128
+ // as-is by the outer catch instead of being mislabeled as install failures.
1129
+ throw new Error(formatCommandFailure(addCommand, e));
1130
+ }
1107
1131
  const { migrations: migrationsFilePath, packageGroup, packageJson, } = readPackageMigrationConfig(packageName, dir);
1108
1132
  let migrations = undefined;
1109
1133
  let resolvedPromptFiles;
@@ -1122,7 +1146,7 @@ async function getPackageMigrationsUsingInstallImpl(packageName, packageVersion,
1122
1146
  catch (e) {
1123
1147
  throw new Error([
1124
1148
  `Failed to fetch migrations for ${packageName}@${packageVersion}`,
1125
- formatCommandFailure(`${pmc.add} ${packageName}@${packageVersion}`, e),
1149
+ e instanceof Error ? e.message : String(e),
1126
1150
  ].join('\n'));
1127
1151
  }
1128
1152
  finally {
@@ -2144,6 +2168,7 @@ async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits, c
2144
2168
  const agentic = await resolveAgentic({
2145
2169
  agentic: opts.agentic,
2146
2170
  migrations,
2171
+ interactive: opts.interactive,
2147
2172
  });
2148
2173
  const { effective: effectiveCreateCommits, agenticHasDiffContext, warning: createCommitsWarning, error: createCommitsError, } = resolveCreateCommits({
2149
2174
  createCommits: shouldCreateCommits,
@@ -24,6 +24,7 @@ export declare function maybePromptOrWarnMultiMajorMigration(args: {
24
24
  mode: MigrateMode;
25
25
  options: {
26
26
  multiMajorMode?: MultiMajorMode;
27
+ interactive?: boolean;
27
28
  };
28
29
  targetPackage: string;
29
30
  targetVersion: string;
@@ -4,7 +4,6 @@ exports.MULTI_MAJOR_MODE_FLAG = void 0;
4
4
  exports.maybePromptOrWarnMultiMajorMigration = maybePromptOrWarnMultiMajorMigration;
5
5
  const safe_prompt_1 = require("./safe-prompt");
6
6
  const semver_1 = require("semver");
7
- const is_ci_1 = require("../../utils/is-ci");
8
7
  const installed_nx_version_1 = require("../../utils/installed-nx-version");
9
8
  const output_1 = require("../../utils/output");
10
9
  const package_manager_1 = require("../../utils/package-manager");
@@ -137,9 +136,10 @@ async function maybePromptOrWarnMultiMajorMigration(args) {
137
136
  if ((0, semver_1.major)(targetVersion) - installedMajor < 2) {
138
137
  return { chosen: targetVersion };
139
138
  }
140
- const interactive = !!process.stdin.isTTY && !(0, is_ci_1.isCI)();
141
- // Non-TTY without gradual opt-in stays on the warn-only path; avoid the
142
- // registry round-trip used to look up incremental migration options.
139
+ const interactive = (0, safe_prompt_1.canPrompt)(options.interactive);
140
+ // Non-interactive (non-TTY, CI, or --no-interactive) without gradual opt-in
141
+ // stays on the warn-only path; avoid the registry round-trip used to look
142
+ // up incremental migration options.
143
143
  if (!interactive && multiMajorMode !== 'gradual') {
144
144
  warnMultiMajorMigration(targetPackage, installed, targetVersion);
145
145
  return { chosen: targetVersion };
@@ -1,4 +1,9 @@
1
1
  import { prompt as enquirerPrompt } from 'enquirer';
2
+ /**
3
+ * Whether `nx migrate` may show interactive prompts: requires a TTY on stdin,
4
+ * not running in CI, and the user not having passed `--no-interactive`.
5
+ */
6
+ export declare function canPrompt(interactive: boolean | undefined): boolean;
2
7
  /**
3
8
  * Drop-in replacement for enquirer's `prompt()` that hardens cancel
4
9
  * handling for every interactive prompt under `nx migrate`.
@@ -1,8 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.canPrompt = canPrompt;
3
4
  exports.migratePrompt = migratePrompt;
4
5
  const enquirer_1 = require("enquirer");
6
+ const is_ci_1 = require("../../utils/is-ci");
5
7
  const output_1 = require("../../utils/output");
8
+ /**
9
+ * Whether `nx migrate` may show interactive prompts: requires a TTY on stdin,
10
+ * not running in CI, and the user not having passed `--no-interactive`.
11
+ */
12
+ function canPrompt(interactive) {
13
+ return !!process.stdin.isTTY && !(0, is_ci_1.isCI)() && interactive !== false;
14
+ }
6
15
  /**
7
16
  * Drop-in replacement for enquirer's `prompt()` that hardens cancel
8
17
  * handling for every interactive prompt under `nx migrate`.