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.
- package/dist/src/command-line/examples.js +1 -1
- package/dist/src/command-line/migrate/agentic/definitions.js +24 -1
- package/dist/src/command-line/migrate/agentic/handoff.d.ts +6 -0
- package/dist/src/command-line/migrate/agentic/handoff.js +8 -1
- package/dist/src/command-line/migrate/agentic/prompts/generic-validation.js +1 -1
- package/dist/src/command-line/migrate/agentic/prompts/hybrid-prompt-migration.js +1 -1
- package/dist/src/command-line/migrate/agentic/prompts/prompt-migration.js +1 -1
- package/dist/src/command-line/migrate/agentic/prompts/system-prompt.js +18 -17
- package/dist/src/command-line/migrate/agentic/select.d.ts +2 -0
- package/dist/src/command-line/migrate/agentic/select.js +4 -2
- package/dist/src/command-line/migrate/command-object.js +1 -1
- package/dist/src/command-line/migrate/migrate-ui-api.d.ts +12 -0
- package/dist/src/command-line/migrate/migrate-ui-api.js +74 -5
- package/dist/src/command-line/migrate/migrate.d.ts +2 -0
- package/dist/src/command-line/migrate/migrate.js +35 -10
- package/dist/src/command-line/migrate/multi-major.d.ts +1 -0
- package/dist/src/command-line/migrate/multi-major.js +4 -4
- package/dist/src/command-line/migrate/safe-prompt.d.ts +5 -0
- package/dist/src/command-line/migrate/safe-prompt.js +9 -0
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/core/graph/styles.css +1 -1
- package/dist/src/daemon/server/project-graph-incremental-recomputation.js +27 -19
- package/dist/src/generators/utils/project-configuration.d.ts +10 -1
- package/dist/src/generators/utils/project-configuration.js +0 -8
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/plugins/js/utils/register.js +6 -0
- package/dist/src/project-graph/plugins/get-plugins.js +63 -19
- package/dist/src/project-graph/plugins/resolve-plugin.d.ts +7 -0
- package/dist/src/project-graph/plugins/resolve-plugin.js +15 -0
- package/dist/src/project-graph/utils/retrieve-workspace-files.d.ts +6 -0
- package/dist/src/project-graph/utils/retrieve-workspace-files.js +9 -0
- package/package.json +11 -11
- 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
|
-
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
-
`
|
|
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
|
-
`
|
|
37
|
-
`
|
|
38
|
-
`
|
|
39
|
-
`
|
|
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
|
|
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
|
|
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,
|
|
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 &&
|
|
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
|
|
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:
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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 (!
|
|
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
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
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
|
-
|
|
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,
|
|
@@ -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 =
|
|
141
|
-
// Non-TTY without gradual opt-in
|
|
142
|
-
// registry round-trip used to look
|
|
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`.
|