nx 23.2.0-beta.7 → 23.2.0-beta.8
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/bin/init-local.js +3 -5
- package/dist/release/changelog-renderer/index.js +4 -5
- package/dist/src/adapter/ngcli-adapter.js +60 -1
- package/dist/src/command-line/completion/command-object.js +7 -12
- package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +17 -23
- package/dist/src/command-line/generate/generate.js +35 -35
- package/dist/src/command-line/import/import.js +18 -40
- package/dist/src/command-line/init/ai-agent-prompts.js +5 -11
- package/dist/src/command-line/init/command-object.js +4 -2
- package/dist/src/command-line/init/implementation/add-nx-to-monorepo.js +12 -32
- package/dist/src/command-line/init/implementation/add-nx-to-nest.js +8 -21
- package/dist/src/command-line/init/implementation/add-nx-to-npm-repo.js +8 -21
- package/dist/src/command-line/init/implementation/angular/index.js +6 -15
- package/dist/src/command-line/init/init-v1.js +8 -18
- package/dist/src/command-line/init/init-v2.js +26 -56
- package/dist/src/command-line/migrate/agentic/definitions.js +35 -8
- package/dist/src/command-line/migrate/agentic/handoff-gitignore.d.ts +22 -3
- package/dist/src/command-line/migrate/agentic/handoff-gitignore.js +13 -10
- package/dist/src/command-line/migrate/agentic/handoff.d.ts +3 -1
- package/dist/src/command-line/migrate/agentic/handoff.js +4 -2
- package/dist/src/command-line/migrate/agentic/run-step.js +1 -0
- package/dist/src/command-line/migrate/agentic/runner.js +24 -24
- package/dist/src/command-line/migrate/agentic/select.js +20 -30
- package/dist/src/command-line/migrate/agentic/types.d.ts +19 -3
- package/dist/src/command-line/migrate/agentic/types.js +13 -4
- package/dist/src/command-line/migrate/command-object.d.ts +3 -0
- package/dist/src/command-line/migrate/command-object.js +13 -0
- package/dist/src/command-line/migrate/execute-migration.d.ts +14 -0
- package/dist/src/command-line/migrate/execute-migration.js +30 -9
- package/dist/src/command-line/migrate/migrate-analytics.d.ts +28 -0
- package/dist/src/command-line/migrate/migrate-analytics.js +51 -1
- package/dist/src/command-line/migrate/migrate-commits.d.ts +8 -2
- package/dist/src/command-line/migrate/migrate-commits.js +66 -19
- package/dist/src/command-line/migrate/migrate-config.d.ts +4 -2
- package/dist/src/command-line/migrate/migrate-config.js +12 -4
- package/dist/src/command-line/migrate/migrate.d.ts +9 -2
- package/dist/src/command-line/migrate/migrate.js +179 -56
- package/dist/src/command-line/migrate/multi-major.js +7 -10
- package/dist/src/command-line/migrate/resolve-package-version.js +4 -8
- package/dist/src/command-line/migrate/run/agent-output.d.ts +20 -0
- package/dist/src/command-line/migrate/run/agent-output.js +65 -0
- package/dist/src/command-line/migrate/run/index.d.ts +7 -0
- package/dist/src/command-line/migrate/run/index.js +19 -1
- package/dist/src/command-line/migrate/run/orchestrator.d.ts +20 -0
- package/dist/src/command-line/migrate/run/orchestrator.js +1200 -0
- package/dist/src/command-line/migrate/run/run-id.d.ts +16 -0
- package/dist/src/command-line/migrate/run/run-id.js +56 -0
- package/dist/src/command-line/migrate/run/run-state.d.ts +157 -0
- package/dist/src/command-line/migrate/run/run-state.js +438 -0
- package/dist/src/command-line/migrate/run/state-lock.d.ts +25 -0
- package/dist/src/command-line/migrate/run/state-lock.js +76 -0
- package/dist/src/command-line/migrate/run/state-machine.d.ts +84 -0
- package/dist/src/command-line/migrate/run/state-machine.js +315 -0
- package/dist/src/command-line/migrate/run/util.d.ts +58 -0
- package/dist/src/command-line/migrate/run/util.js +139 -7
- package/dist/src/command-line/migrate/run/worker.d.ts +1 -0
- package/dist/src/command-line/migrate/run/worker.js +308 -45
- package/dist/src/command-line/migrate/safe-prompt.d.ts +14 -26
- package/dist/src/command-line/migrate/safe-prompt.js +32 -43
- package/dist/src/command-line/migrate/step-actions.d.ts +5 -0
- package/dist/src/command-line/migrate/step-actions.js +12 -0
- package/dist/src/command-line/migrate/text.d.ts +18 -0
- package/dist/src/command-line/migrate/text.js +26 -0
- package/dist/src/command-line/migrate/version-skew-guard.d.ts +6 -4
- package/dist/src/command-line/migrate/version-skew-guard.js +34 -14
- package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +16 -20
- package/dist/src/command-line/release/changelog.js +6 -17
- package/dist/src/command-line/release/plan.js +15 -28
- package/dist/src/command-line/release/release.js +6 -17
- package/dist/src/command-line/release/utils/remote-release-clients/github.js +16 -22
- package/dist/src/command-line/release/utils/remote-release-clients/gitlab.js +13 -22
- package/dist/src/command-line/release/utils/resolve-semver-specifier.js +19 -41
- package/dist/src/command-line/release/version/resolve-current-version.js +7 -10
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/devkit-internals.d.ts +2 -0
- package/dist/src/devkit-internals.js +12 -4
- package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.d.ts +12 -0
- package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.js +250 -0
- package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.md +50 -0
- 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/lock-file/bun-parser.js +43 -10
- package/dist/src/project-graph/utils/project-configuration/target-normalization.js +128 -2
- package/dist/src/tasks-runner/run-command.js +17 -32
- package/dist/src/tasks-runner/task-env.js +5 -2
- package/dist/src/tasks-runner/utils.js +2 -6
- package/dist/src/utils/analytics-prompt.js +4 -7
- package/dist/src/utils/exit-codes.d.ts +9 -0
- package/dist/src/utils/exit-codes.js +19 -0
- package/dist/src/utils/fileutils.d.ts +5 -0
- package/dist/src/utils/fileutils.js +7 -2
- package/dist/src/utils/git-utils.d.ts +33 -2
- package/dist/src/utils/git-utils.js +156 -8
- package/dist/src/utils/long-running-target.d.ts +10 -0
- package/dist/src/utils/long-running-target.js +19 -0
- package/dist/src/utils/min-release-age/behavior/bun.js +7 -11
- package/dist/src/utils/min-release-age/behavior/npm.js +5 -2
- package/dist/src/utils/min-release-age/packument.js +1 -1
- package/dist/src/utils/nx-console-prompt.js +3 -6
- package/dist/src/utils/package-manager-config/bunfig.d.ts +14 -0
- package/dist/src/utils/package-manager-config/bunfig.js +49 -0
- package/dist/src/utils/package-manager-config/npmrc.d.ts +23 -0
- package/dist/src/utils/package-manager-config/npmrc.js +124 -0
- package/dist/src/utils/package-manager-config/pnpm-config.d.ts +17 -0
- package/dist/src/utils/package-manager-config/pnpm-config.js +60 -0
- package/dist/src/utils/package-manager.d.ts +21 -2
- package/dist/src/utils/package-manager.js +244 -47
- package/dist/src/utils/params.d.ts +7 -1
- package/dist/src/utils/params.js +64 -6
- package/dist/src/utils/prompt-helpers.d.ts +53 -0
- package/dist/src/utils/prompt-helpers.js +111 -0
- package/dist/src/utils/provenance.js +6 -14
- package/dist/src/utils/registry-config/bun.d.ts +2 -0
- package/dist/src/utils/registry-config/bun.js +257 -0
- package/dist/src/utils/registry-config/index.d.ts +13 -0
- package/dist/src/utils/registry-config/index.js +102 -0
- package/dist/src/utils/registry-config/pnpm.d.ts +2 -0
- package/dist/src/utils/registry-config/pnpm.js +1551 -0
- package/dist/src/utils/registry-config/utils.d.ts +195 -0
- package/dist/src/utils/registry-config/utils.js +496 -0
- package/dist/src/utils/registry-config/yarn-berry.d.ts +2 -0
- package/dist/src/utils/registry-config/yarn-berry.js +612 -0
- package/dist/src/utils/registry-config/yarn-classic.d.ts +2 -0
- package/dist/src/utils/registry-config/yarn-classic.js +1001 -0
- package/dist/src/utils/safe-spawn.d.ts +24 -0
- package/dist/src/utils/safe-spawn.js +104 -0
- package/migrations.json +6 -0
- package/package.json +17 -13
- package/dist/src/utils/min-release-age/npmrc.d.ts +0 -15
- package/dist/src/utils/min-release-age/npmrc.js +0 -45
|
@@ -1,16 +1,148 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// Internal to run/: deliberately not re-exported from ./index.
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.nowIso = nowIso;
|
|
5
|
+
exports.depsHash = depsHash;
|
|
6
|
+
exports.installDepsChangedSinceDispense = installDepsChangedSinceDispense;
|
|
7
|
+
exports.recordInstallLanded = recordInstallLanded;
|
|
8
|
+
exports.isPidAlive = isPidAlive;
|
|
9
|
+
exports.summarizeError = summarizeError;
|
|
10
|
+
exports.warnCommitFailed = warnCommitFailed;
|
|
4
11
|
exports.pmExecPrefix = pmExecPrefix;
|
|
12
|
+
exports.pmInstallCommand = pmInstallCommand;
|
|
13
|
+
const crypto_1 = require("crypto");
|
|
5
14
|
const package_manager_1 = require("../../../utils/package-manager");
|
|
6
|
-
const
|
|
15
|
+
const execute_migration_1 = require("../execute-migration");
|
|
16
|
+
const state_lock_1 = require("./state-lock");
|
|
17
|
+
const agent_output_1 = require("./agent-output");
|
|
18
|
+
function nowIso() {
|
|
19
|
+
return new Date().toISOString();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Fingerprints the workspace dependencies so a step can persist what they
|
|
23
|
+
* looked like and a later actor can still tell whether the migration changed
|
|
24
|
+
* them. Hashed rather than stored verbatim to keep run.json small; the value
|
|
25
|
+
* is only ever compared for equality.
|
|
26
|
+
*
|
|
27
|
+
* `null` means the probe itself failed. Callers must not read that as
|
|
28
|
+
* "unchanged": the value is persisted and compared across processes, so a
|
|
29
|
+
* failed read on either end would otherwise silently skip an install that was
|
|
30
|
+
* needed.
|
|
31
|
+
*/
|
|
32
|
+
function depsHash(root) {
|
|
33
|
+
const deps = (0, execute_migration_1.readPackageJsonDeps)(root);
|
|
34
|
+
return deps === null ? null : (0, crypto_1.createHash)('sha256').update(deps).digest('hex');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Installs when the workspace dependencies differ from the step's recorded
|
|
38
|
+
* baseline, so a prompt applied by another actor, or a retry that only has the
|
|
39
|
+
* commit left to do, still installs what the changes need. Comparing against
|
|
40
|
+
* the persisted baseline is what makes that possible: by the time either runs,
|
|
41
|
+
* the edits are already on disk, so a snapshot taken here would see them as
|
|
42
|
+
* the starting point and never detect a change.
|
|
43
|
+
*
|
|
44
|
+
* The baseline is re-pointed at the current dependencies once the install
|
|
45
|
+
* lands, so the next actor to compare does not install the same change again.
|
|
46
|
+
*
|
|
47
|
+
* Every probe failure installs, on either end of the comparison. A step whose
|
|
48
|
+
* dispense-time probe failed has no baseline, which says the dependencies it
|
|
49
|
+
* started from are unknown rather than unchanged, and skipping there would
|
|
50
|
+
* strand the change with nothing left to detect it.
|
|
51
|
+
*/
|
|
52
|
+
async function installDepsChangedSinceDispense(root, dir, step, skipInstall, rerunCommand) {
|
|
53
|
+
const current = depsHash(root);
|
|
54
|
+
if (current !== null && current === step.depsHashAtDispense)
|
|
55
|
+
return;
|
|
56
|
+
if (skipInstall) {
|
|
57
|
+
(0, execute_migration_1.logSkippedPostMigrationInstall)(root);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
await (0, execute_migration_1.runInstall)(root, 'post-migration', rerunCommand);
|
|
61
|
+
recordInstallLanded(root, dir, step.id);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Records what an install that just landed means for the run: the installing
|
|
65
|
+
* step's dependency baseline moves to what is now on disk, and every step's
|
|
66
|
+
* install-failure mark clears.
|
|
67
|
+
*
|
|
68
|
+
* The baseline has to move, or the next actor to compare (the prompt fold, a
|
|
69
|
+
* retry, the step's own commit) reads the step's own edits as an unapplied
|
|
70
|
+
* change and pays a second full install. The marks clear because the package
|
|
71
|
+
* manager installs the whole workspace package.json, so this install also
|
|
72
|
+
* covers the dependency edits of every earlier step that failed to install its
|
|
73
|
+
* own.
|
|
74
|
+
*
|
|
75
|
+
* A failed probe leaves the baseline alone: an install that runs twice costs
|
|
76
|
+
* time, one that never runs leaves the workspace inconsistent with its
|
|
77
|
+
* package.json.
|
|
78
|
+
*
|
|
79
|
+
* Never throws. It runs inside callers that treat a throw as "the install
|
|
80
|
+
* failed", and the install has already succeeded by then. A run state this
|
|
81
|
+
* cannot read or write is a real problem, but it is reported by the next
|
|
82
|
+
* mandatory write rather than misattributed here, and the only cost of losing
|
|
83
|
+
* this one is a redundant install later.
|
|
84
|
+
*/
|
|
85
|
+
function recordInstallLanded(root, dir, stepId) {
|
|
86
|
+
const hash = depsHash(root);
|
|
87
|
+
try {
|
|
88
|
+
(0, state_lock_1.updateRunState)(dir, (fresh) => {
|
|
89
|
+
const baselineMoves = fresh.steps.some((s) => s.id === stepId && hash !== null && s.depsHashAtDispense !== hash);
|
|
90
|
+
if (!baselineMoves && !fresh.steps.some((s) => s.installFailed)) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
...fresh,
|
|
95
|
+
steps: fresh.steps.map(({ installFailed, ...s }) => s.id === stepId && baselineMoves
|
|
96
|
+
? { ...s, depsHashAtDispense: hash }
|
|
97
|
+
: s),
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// See above: the install already succeeded, so a state failure here must
|
|
103
|
+
// not be reported as an install failure.
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// ESRCH means the process is gone; EPERM means it exists but isn't ours (alive).
|
|
107
|
+
function isPidAlive(pid) {
|
|
108
|
+
try {
|
|
109
|
+
process.kill(pid, 0);
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
return e.code === 'EPERM';
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// The agent reads this to decide retry-vs-skip, so keep it to the error's
|
|
117
|
+
// first line and bound the length rather than dumping a multi-line stack.
|
|
118
|
+
function summarizeError(e) {
|
|
119
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
120
|
+
const firstLine = message.split('\n')[0].trim();
|
|
121
|
+
return firstLine.length > 200 ? `${firstLine.slice(0, 197)}...` : firstLine;
|
|
122
|
+
}
|
|
123
|
+
// The commit helper reports its own failures via the result status and logs
|
|
124
|
+
// the details itself; `cause` is set only when the attempt threw instead
|
|
125
|
+
// (the pre-commit dependency install), which nothing else has logged.
|
|
126
|
+
function warnCommitFailed(name, cause) {
|
|
127
|
+
const causeText = cause === undefined ? '' : ` (${summarizeError(cause)})`;
|
|
128
|
+
(0, agent_output_1.warnToAgent)({
|
|
129
|
+
title: `The commit for ${name} could not be created${causeText}; its changes remain in the working tree for a later commit to absorb.`,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const cachedPmCommands = new Map();
|
|
7
133
|
// getPackageManagerCommand can shell out to detect a version, so cache the
|
|
8
134
|
// result per root.
|
|
9
|
-
function
|
|
10
|
-
let
|
|
11
|
-
if (
|
|
12
|
-
|
|
13
|
-
|
|
135
|
+
function pmCommands(root) {
|
|
136
|
+
let commands = cachedPmCommands.get(root);
|
|
137
|
+
if (commands === undefined) {
|
|
138
|
+
commands = (0, package_manager_1.getPackageManagerCommand)((0, package_manager_1.detectPackageManager)(root), root);
|
|
139
|
+
cachedPmCommands.set(root, commands);
|
|
14
140
|
}
|
|
15
|
-
return
|
|
141
|
+
return commands;
|
|
142
|
+
}
|
|
143
|
+
function pmExecPrefix(root) {
|
|
144
|
+
return pmCommands(root).exec;
|
|
145
|
+
}
|
|
146
|
+
function pmInstallCommand(root) {
|
|
147
|
+
return pmCommands(root).install;
|
|
16
148
|
}
|
|
@@ -2,6 +2,7 @@ import { type AgenticArg } from '../agentic/select';
|
|
|
2
2
|
export interface RunSingleMigrationWorkerInput {
|
|
3
3
|
root: string;
|
|
4
4
|
runMigration: string;
|
|
5
|
+
runId?: string;
|
|
5
6
|
/** The raw `--agentic` value; resolved here against the environment. */
|
|
6
7
|
agentic: AgenticArg;
|
|
7
8
|
validate: boolean | undefined;
|
|
@@ -3,16 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.runSingleMigrationWorker = runSingleMigrationWorker;
|
|
4
4
|
const fs_1 = require("fs");
|
|
5
5
|
const path_1 = require("path");
|
|
6
|
-
const configuration_1 = require("../../../config/configuration");
|
|
7
6
|
const git_utils_1 = require("../../../utils/git-utils");
|
|
8
|
-
const command_line_utils_1 = require("../../../utils/command-line-utils");
|
|
9
7
|
const fileutils_1 = require("../../../utils/fileutils");
|
|
10
8
|
const installation_directory_1 = require("../../../utils/installation-directory");
|
|
11
|
-
const logger_1 = require("../../../utils/logger");
|
|
12
|
-
const output_1 = require("../../../utils/output");
|
|
13
9
|
const package_json_1 = require("../../../utils/package-json");
|
|
10
|
+
const inception_1 = require("../agentic/inception");
|
|
14
11
|
const print_dropped_agent_context_1 = require("../agentic/print-dropped-agent-context");
|
|
15
12
|
const select_1 = require("../agentic/select");
|
|
13
|
+
const types_1 = require("../agentic/types");
|
|
16
14
|
const command_object_1 = require("../command-object");
|
|
17
15
|
const execute_migration_1 = require("../execute-migration");
|
|
18
16
|
const migrate_analytics_1 = require("../migrate-analytics");
|
|
@@ -20,10 +18,22 @@ const migrate_commits_1 = require("../migrate-commits");
|
|
|
20
18
|
const migrate_output_1 = require("../migrate-output");
|
|
21
19
|
const migration_shape_1 = require("../migration-shape");
|
|
22
20
|
const safe_prompt_1 = require("../safe-prompt");
|
|
21
|
+
const run_state_1 = require("./run-state");
|
|
22
|
+
const run_id_1 = require("./run-id");
|
|
23
|
+
const state_machine_1 = require("./state-machine");
|
|
24
|
+
const state_lock_1 = require("./state-lock");
|
|
23
25
|
const util_1 = require("./util");
|
|
26
|
+
const text_1 = require("../text");
|
|
27
|
+
const agent_output_1 = require("./agent-output");
|
|
24
28
|
async function runSingleMigrationWorker(input) {
|
|
25
|
-
const { root, runMigration, commitPrefix, interactive, skipInstall, isVerbose, } = input;
|
|
26
|
-
|
|
29
|
+
const { root, runMigration, runId, commitPrefix, interactive, skipInstall, isVerbose, } = input;
|
|
30
|
+
// The worker is a second CLI entry point: the run id reaches runDir() (where
|
|
31
|
+
// join resolves '..'), so validate it up front exactly as the orchestrator
|
|
32
|
+
// does before it trusts a run id.
|
|
33
|
+
if (runId !== undefined && !run_id_1.RUN_ID_SAFE.test(runId)) {
|
|
34
|
+
throw new Error(`Invalid run id '${runId}'.`);
|
|
35
|
+
}
|
|
36
|
+
const { migrations, source } = readMigrationsSource(root, runId);
|
|
27
37
|
const migration = resolveMigration(migrations, runMigration, source);
|
|
28
38
|
(0, migrate_analytics_1.reportMigrateSingleMigrationInvocation)({
|
|
29
39
|
migrationType: (0, migration_shape_1.isPromptOnlyMigration)(migration)
|
|
@@ -31,7 +41,16 @@ async function runSingleMigrationWorker(input) {
|
|
|
31
41
|
: (0, migration_shape_1.isHybridMigration)(migration)
|
|
32
42
|
? 'hybrid'
|
|
33
43
|
: 'generator',
|
|
44
|
+
orchestrated: !!runId,
|
|
34
45
|
});
|
|
46
|
+
if (runId) {
|
|
47
|
+
// A recorded run takes its commit config from run.json and is driven by
|
|
48
|
+
// the outer agent, so the standalone resolution below doesn't apply. That
|
|
49
|
+
// includes the default-branch confirmation: the run decided once, at init,
|
|
50
|
+
// whether to commit, and asking again would re-prompt on every step.
|
|
51
|
+
await runRecorded(root, runId, migration, skipInstall, isVerbose);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
35
54
|
let agentic;
|
|
36
55
|
try {
|
|
37
56
|
agentic = await (0, select_1.resolveAgentic)({
|
|
@@ -46,7 +65,7 @@ async function runSingleMigrationWorker(input) {
|
|
|
46
65
|
}
|
|
47
66
|
const resolved = (0, migrate_commits_1.resolveCreateCommits)({
|
|
48
67
|
createCommits: input.createCommits,
|
|
49
|
-
|
|
68
|
+
mode: agentic.kind,
|
|
50
69
|
isGitRepo: (0, git_utils_1.isGitRepository)(root),
|
|
51
70
|
commitPrefixIsCustom: commitPrefix !== command_object_1.DEFAULT_MIGRATION_COMMIT_PREFIX,
|
|
52
71
|
});
|
|
@@ -54,27 +73,13 @@ async function runSingleMigrationWorker(input) {
|
|
|
54
73
|
throw new Error(resolved.error);
|
|
55
74
|
}
|
|
56
75
|
if (resolved.warning) {
|
|
57
|
-
|
|
76
|
+
(0, agent_output_1.warnToAgent)({ title: resolved.warning });
|
|
58
77
|
}
|
|
59
78
|
const createCommits = resolved.effective;
|
|
60
|
-
if (createCommits &&
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const defaultBranch = (0, command_line_utils_1.getBaseRef)((0, configuration_1.readNxJson)(root)).replace(/^origin\//, '');
|
|
65
|
-
const proceed = await (0, migrate_commits_1.confirmCommitsOnDefaultBranch)({
|
|
66
|
-
currentBranch,
|
|
67
|
-
defaultBranch,
|
|
68
|
-
});
|
|
69
|
-
if (!proceed) {
|
|
70
|
-
output_1.output.log({
|
|
71
|
-
title: `Skipped running the migration to avoid committing to the default branch '${currentBranch}'.`,
|
|
72
|
-
bodyLines: [
|
|
73
|
-
'Switch to a different branch and re-run, or re-run and confirm to proceed.',
|
|
74
|
-
],
|
|
75
|
-
});
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
79
|
+
if (createCommits &&
|
|
80
|
+
(0, safe_prompt_1.canPrompt)(interactive) &&
|
|
81
|
+
!(await (0, migrate_commits_1.confirmMigrationCommitsOnDefaultBranch)(root, 'running the migration'))) {
|
|
82
|
+
return;
|
|
78
83
|
}
|
|
79
84
|
await runStandalone(root, migration, {
|
|
80
85
|
agentic,
|
|
@@ -89,7 +94,33 @@ async function runSingleMigrationWorker(input) {
|
|
|
89
94
|
isVerbose,
|
|
90
95
|
});
|
|
91
96
|
}
|
|
92
|
-
function readMigrationsSource(root) {
|
|
97
|
+
function readMigrationsSource(root, runId) {
|
|
98
|
+
if (runId) {
|
|
99
|
+
const dir = (0, run_state_1.runDir)(root, runId);
|
|
100
|
+
// A missing run.json would surface a raw ENOENT from readRunState's
|
|
101
|
+
// readFileSync; report it the way the orchestrator does instead, down to
|
|
102
|
+
// carrying no remediation: starting a run is a separate, gated entry point,
|
|
103
|
+
// and `--run-migrations` would run the whole plan in process instead.
|
|
104
|
+
if (!(0, run_state_1.hasRunState)(dir)) {
|
|
105
|
+
throw new Error(`No migrate run '${runId}' was found under ${types_1.MIGRATE_RUNS_RELATIVE_DIR}.`);
|
|
106
|
+
}
|
|
107
|
+
// Version refusal (NewerRunStateFormatError) propagates.
|
|
108
|
+
const state = (0, run_state_1.readRunState)(dir);
|
|
109
|
+
const round = (0, state_machine_1.latestRound)(state);
|
|
110
|
+
if (!round) {
|
|
111
|
+
throw new Error(`The migrate run '${runId}' has no recorded plan, so there is no migration to run.`);
|
|
112
|
+
}
|
|
113
|
+
// Safe to join and to name in the error below only because the read
|
|
114
|
+
// refuses any planSnapshot that is not a bare `plan-<round>.json`.
|
|
115
|
+
const planPath = (0, path_1.join)(dir, round.planSnapshot);
|
|
116
|
+
if (!(0, fs_1.existsSync)(planPath)) {
|
|
117
|
+
throw new Error(`The plan snapshot '${round.planSnapshot}' for migrate run '${runId}' doesn't exist, can't run the migration.`);
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
migrations: readPlanMigrations(planPath),
|
|
121
|
+
source: round.planSnapshot,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
93
124
|
const migrationsPath = (0, path_1.join)(root, 'migrations.json');
|
|
94
125
|
if (!(0, fs_1.existsSync)(migrationsPath)) {
|
|
95
126
|
throw new Error(`File 'migrations.json' doesn't exist, can't run the migration. Run \`${(0, util_1.pmExecPrefix)(root)} nx migrate\` to generate it first.`);
|
|
@@ -121,6 +152,32 @@ function resolveMigration(migrations, id, source) {
|
|
|
121
152
|
}
|
|
122
153
|
async function runStandalone(root, migration, opts) {
|
|
123
154
|
const { agentic, createCommits, commitPrefix, skipInstall, isVerbose } = opts;
|
|
155
|
+
// Standalone never writes run state. Warn (don't block) when an orchestrated
|
|
156
|
+
// run is active so the user knows this execution won't be recorded into it.
|
|
157
|
+
// A newer-nx run dir must not hard-block this stateless path (the fail-closed
|
|
158
|
+
// refusal in run-state.ts targets run-starting callers); tolerate it and skip
|
|
159
|
+
// the warning. A failed scan can't block it either, but gets a warning of its
|
|
160
|
+
// own: an active run may exist that this execution silently won't record into.
|
|
161
|
+
let active;
|
|
162
|
+
try {
|
|
163
|
+
active = (0, run_state_1.findActiveRun)(root).active;
|
|
164
|
+
}
|
|
165
|
+
catch (e) {
|
|
166
|
+
if (!(e instanceof run_state_1.NewerRunStateFormatError)) {
|
|
167
|
+
(0, agent_output_1.warnToAgent)({
|
|
168
|
+
title: `Could not check for an active migrate run: ${e instanceof Error ? e.message : e}`,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
active = null;
|
|
172
|
+
}
|
|
173
|
+
if (active) {
|
|
174
|
+
(0, agent_output_1.warnToAgent)({
|
|
175
|
+
title: `This migration won't be recorded into the active migrate run '${active.runId}'.`,
|
|
176
|
+
bodyLines: [
|
|
177
|
+
`Pass --run-id=${active.runId} to record it into that run instead.`,
|
|
178
|
+
],
|
|
179
|
+
});
|
|
180
|
+
}
|
|
124
181
|
if ((0, migration_shape_1.isPromptOnlyMigration)(migration)) {
|
|
125
182
|
if (agentic.kind !== 'enabled') {
|
|
126
183
|
// No agent to apply the prompt, so nothing runs: no checkpoint, no
|
|
@@ -266,8 +323,220 @@ async function runStandalone(root, migration, opts) {
|
|
|
266
323
|
}, resolvedCollection);
|
|
267
324
|
}
|
|
268
325
|
}
|
|
269
|
-
|
|
270
|
-
|
|
326
|
+
async function runRecorded(root, runId, migration, skipInstall, isVerbose) {
|
|
327
|
+
const dir = (0, run_state_1.runDir)(root, runId);
|
|
328
|
+
// Version refusal (NewerRunStateFormatError) propagates.
|
|
329
|
+
let state = (0, run_state_1.readRunState)(dir);
|
|
330
|
+
// A recorded run never resolves the agentic flow (the outer agent drives
|
|
331
|
+
// it); prompts are emitted for that agent or printed for a user hand-running
|
|
332
|
+
// the dispensed command.
|
|
333
|
+
const agenticKind = (0, inception_1.isInsideAgent)()
|
|
334
|
+
? 'inside-agent'
|
|
335
|
+
: 'disabled';
|
|
336
|
+
const migrationId = `${migration.package}:${migration.name}`;
|
|
337
|
+
// The plan was read from the latest round's snapshot, so only that round's
|
|
338
|
+
// step may match; a same-id step from an older round must not.
|
|
339
|
+
const latest = (0, state_machine_1.latestRound)(state);
|
|
340
|
+
const step = state.steps.find((s) => s.migrationId === migrationId && s.roundIndex === latest?.index);
|
|
341
|
+
if (!step) {
|
|
342
|
+
throw new Error(`The migrate run '${runId}' has no step for migration '${migrationId}'.`);
|
|
343
|
+
}
|
|
344
|
+
// Validated against the fresh disk state: a second worker racing the same
|
|
345
|
+
// dispensed step reads 'running' here and aborts before the engine runs.
|
|
346
|
+
state = transition(dir, {
|
|
347
|
+
type: 'start',
|
|
348
|
+
stepId: step.id,
|
|
349
|
+
pid: process.pid,
|
|
350
|
+
startedAt: (0, util_1.nowIso)(),
|
|
351
|
+
});
|
|
352
|
+
// A prior attempt's generator half already ran, so this attempt must not
|
|
353
|
+
// reapply it against a tree that already holds its changes: a hybrid
|
|
354
|
+
// re-emits only its prompt, and a plain generator step has nothing left to
|
|
355
|
+
// do but the install and commit its previous attempt failed on.
|
|
356
|
+
// Read from the state the start transition returned: a delayed invocation
|
|
357
|
+
// may have claimed a later attempt whose flag its entry snapshot predates.
|
|
358
|
+
const startedStep = state.steps.find((s) => s.id === step.id);
|
|
359
|
+
const generatorAlreadyCompleted = startedStep.generatorCompleted === true;
|
|
360
|
+
// The run records its own install policy because dispensed worker commands
|
|
361
|
+
// are re-invoked by the loop and never carry the user's flags; an explicit
|
|
362
|
+
// --skip-install on this invocation still applies on top of it.
|
|
363
|
+
const effectiveSkipInstall = state.skipInstall === true || skipInstall;
|
|
364
|
+
let outcome;
|
|
365
|
+
try {
|
|
366
|
+
if ((0, migration_shape_1.isPromptOnlyMigration)(migration) ||
|
|
367
|
+
(generatorAlreadyCompleted && (0, migration_shape_1.isHybridMigration)(migration))) {
|
|
368
|
+
emitOrPrintPrompt(root, migration, agenticKind);
|
|
369
|
+
}
|
|
370
|
+
else if (generatorAlreadyCompleted) {
|
|
371
|
+
state = await finishCompletedGenerator(dir, root, state, startedStep, migration, effectiveSkipInstall, runId);
|
|
372
|
+
outcome = buildOutcome([], [], 'The generator ran in an earlier attempt; this attempt completed its install and commit.', root);
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
const installer = new execute_migration_1.ChangedDepInstaller(root, effectiveSkipInstall, `${(0, execute_migration_1.formatSingleMigrationRerunCommand)(migrationId)} --run-id=${runId}`);
|
|
376
|
+
// Read once; the run and the hybrid documentation resolution share it.
|
|
377
|
+
const resolvedCollection = (0, execute_migration_1.readMigrationCollection)(migration.package, root);
|
|
378
|
+
const { changes, nextSteps, agentContext, logs, madeChanges } = await (0, execute_migration_1.runNxOrAngularMigration)(root, migration, isVerbose, (0, migration_shape_1.isHybridMigration)(migration), resolvedCollection);
|
|
379
|
+
// Recorded before the commit is attempted: from here on the changes are
|
|
380
|
+
// in the tree, so a failed install or commit must leave a retry with
|
|
381
|
+
// only those left to do rather than running the generator again.
|
|
382
|
+
state = transition(dir, {
|
|
383
|
+
type: 'markGeneratorCompleted',
|
|
384
|
+
stepId: step.id,
|
|
385
|
+
});
|
|
386
|
+
if (!(0, migration_shape_1.isHybridMigration)(migration)) {
|
|
387
|
+
forwardDroppedAgentContext(migration, agentContext, agenticKind);
|
|
388
|
+
}
|
|
389
|
+
const install = () => recordingInstallFailure(dir, step.id, () => installer.installDepsIfChanged());
|
|
390
|
+
// Commits follow the run config, not CLI flags, and only when the
|
|
391
|
+
// generator changed something: a no-op step must not create a commit (nor
|
|
392
|
+
// a ledger entry) that absorbs prior pending diffs under its name.
|
|
393
|
+
if (state.createCommits && madeChanges) {
|
|
394
|
+
state = await commitStepChanges(dir, root, state, step, migration, install);
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
await install();
|
|
398
|
+
}
|
|
399
|
+
if (installer.skippedInstall) {
|
|
400
|
+
(0, execute_migration_1.logSkippedPostMigrationInstall)(root);
|
|
401
|
+
}
|
|
402
|
+
else if (installer.installed) {
|
|
403
|
+
(0, util_1.recordInstallLanded)(root, dir, step.id);
|
|
404
|
+
}
|
|
405
|
+
printNextSteps(migration, nextSteps);
|
|
406
|
+
if ((0, migration_shape_1.isHybridMigration)(migration)) {
|
|
407
|
+
emitOrPrintPrompt(root, migration, agenticKind, {
|
|
408
|
+
logs,
|
|
409
|
+
changes,
|
|
410
|
+
agentContext,
|
|
411
|
+
}, resolvedCollection);
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
outcome = buildOutcome(changes, nextSteps, migration.description, root);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
catch (e) {
|
|
419
|
+
// The failed-step dispense surfaces this so the agent can decide
|
|
420
|
+
// retry-vs-skip; carry the error's first line, not a full stack.
|
|
421
|
+
transition(dir, {
|
|
422
|
+
type: 'fail',
|
|
423
|
+
stepId: step.id,
|
|
424
|
+
finishedAt: (0, util_1.nowIso)(),
|
|
425
|
+
outcome: { summary: (0, util_1.summarizeError)(e) },
|
|
426
|
+
});
|
|
427
|
+
throw e;
|
|
428
|
+
}
|
|
429
|
+
// A prompt half (prompt-only, or the prompt phase of a hybrid) is applied by
|
|
430
|
+
// a separate actor, so the step parks in awaiting-prompt-outcome and this
|
|
431
|
+
// process exits successfully.
|
|
432
|
+
if ((0, migration_shape_1.isPromptOnlyMigration)(migration) || (0, migration_shape_1.isHybridMigration)(migration)) {
|
|
433
|
+
transition(dir, {
|
|
434
|
+
type: 'awaitPromptOutcome',
|
|
435
|
+
stepId: step.id,
|
|
436
|
+
finishedAt: (0, util_1.nowIso)(),
|
|
437
|
+
});
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
transition(dir, {
|
|
441
|
+
type: 'succeed',
|
|
442
|
+
stepId: step.id,
|
|
443
|
+
finishedAt: (0, util_1.nowIso)(),
|
|
444
|
+
...(outcome ? { outcome } : {}),
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
// Applies a step event to the freshest on-disk state under the lock, writes it,
|
|
448
|
+
// and returns it. Reading fresh is what makes a racing worker's 'start' see the
|
|
449
|
+
// step already 'running' and abort. An illegal transition (e.g. a step that was
|
|
450
|
+
// never dispensed) fails with the state machine's own reason.
|
|
451
|
+
function transition(dir, event) {
|
|
452
|
+
return (0, state_lock_1.updateRunState)(dir, (fresh) => {
|
|
453
|
+
const result = (0, state_machine_1.applyStepEvent)(fresh, event);
|
|
454
|
+
if (result.kind === 'error') {
|
|
455
|
+
throw new Error(`Cannot record this migration into the run: ${result.reason}`);
|
|
456
|
+
}
|
|
457
|
+
return result.state;
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
// Records a dependency install failure on the step before letting it fail the
|
|
461
|
+
// attempt. The 'failed' status says this attempt did not finish, not that the
|
|
462
|
+
// workspace's dependencies are missing, and the two part ways as soon as the
|
|
463
|
+
// agent skips the step or a later step's commit absorbs its diff: either one
|
|
464
|
+
// completes the run with node_modules stale and nothing left to warn about.
|
|
465
|
+
async function recordingInstallFailure(dir, stepId, install) {
|
|
466
|
+
try {
|
|
467
|
+
return await install();
|
|
468
|
+
}
|
|
469
|
+
catch (e) {
|
|
470
|
+
(0, state_lock_1.updateRunState)(dir, (fresh) => (0, state_machine_1.markInstallFailed)(fresh, stepId));
|
|
471
|
+
throw e;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
// Finishes a step whose generator ran in an earlier attempt that then failed
|
|
475
|
+
// on the install or the commit. The generator's changes are already in the
|
|
476
|
+
// tree, so only those two are left, and the install compares against the
|
|
477
|
+
// step's persisted baseline rather than against what that generator wrote.
|
|
478
|
+
async function finishCompletedGenerator(dir, root, state, step, migration, skipInstall, runId) {
|
|
479
|
+
const migrationId = `${migration.package}:${migration.name}`;
|
|
480
|
+
const installDeps = () => recordingInstallFailure(dir, step.id, () => (0, util_1.installDepsChangedSinceDispense)(root, dir, step, skipInstall, `${(0, execute_migration_1.formatSingleMigrationRerunCommand)(migrationId)} --run-id=${runId}`));
|
|
481
|
+
if (!state.createCommits) {
|
|
482
|
+
await installDeps();
|
|
483
|
+
return state;
|
|
484
|
+
}
|
|
485
|
+
return commitStepChanges(dir, root, state, step, migration, installDeps);
|
|
486
|
+
}
|
|
487
|
+
// Installs what the step changed, commits it, and records the result in the
|
|
488
|
+
// ledger, shared by a step's first attempt and by one that only has the commit
|
|
489
|
+
// left to do. The absorbed step ids are computed before the commit so the
|
|
490
|
+
// ledger entry and the commit body name the same ones.
|
|
491
|
+
async function commitStepChanges(dir, root, state, step, migration, installDeps) {
|
|
492
|
+
const absorbedStepIds = (0, state_machine_1.uncoveredFailedStepIds)(state).filter((id) => id !== step.id);
|
|
493
|
+
let result;
|
|
494
|
+
try {
|
|
495
|
+
result = await (0, migrate_commits_1.commitMigrationIfRequested)(root, migration, true, state.commitPrefix, installDeps, (0, state_machine_1.stepsToPendingMigrations)(state, absorbedStepIds));
|
|
496
|
+
}
|
|
497
|
+
catch (commitError) {
|
|
498
|
+
// A post-migration install failure leaves the diff uncommitted; record the
|
|
499
|
+
// debt so only a landed entry can cover it.
|
|
500
|
+
appendCommit(dir, { kind: 'failed', stepIds: [step.id] });
|
|
501
|
+
throw commitError;
|
|
502
|
+
}
|
|
503
|
+
if (result.status === 'failed') {
|
|
504
|
+
(0, util_1.warnCommitFailed)(migration.name);
|
|
505
|
+
}
|
|
506
|
+
const entry = (0, state_machine_1.commitResultToLedgerEntry)(result, step.id, absorbedStepIds);
|
|
507
|
+
return entry ? appendCommit(dir, entry) : state;
|
|
508
|
+
}
|
|
509
|
+
// Appends a ledger entry to the freshest on-disk state under the lock. The git
|
|
510
|
+
// commit itself already ran outside the lock; only this pure append is locked.
|
|
511
|
+
function appendCommit(dir, entry) {
|
|
512
|
+
return (0, state_lock_1.updateRunState)(dir, (fresh) => ({
|
|
513
|
+
...fresh,
|
|
514
|
+
commits: [...fresh.commits, entry],
|
|
515
|
+
}));
|
|
516
|
+
}
|
|
517
|
+
function buildOutcome(changes, nextSteps, description, root) {
|
|
518
|
+
const outcome = {};
|
|
519
|
+
if (changes.length > 0) {
|
|
520
|
+
outcome.fileChanges = changes.map((c) => c.path);
|
|
521
|
+
}
|
|
522
|
+
// Non-git repos have no HEAD; omit rather than record a placeholder.
|
|
523
|
+
const gitRefAfter = (0, git_utils_1.getLatestCommitSha)(root);
|
|
524
|
+
if (gitRefAfter) {
|
|
525
|
+
outcome.gitRefAfter = gitRefAfter;
|
|
526
|
+
}
|
|
527
|
+
if (nextSteps.length > 0) {
|
|
528
|
+
outcome.nextSteps = nextSteps;
|
|
529
|
+
}
|
|
530
|
+
if (description) {
|
|
531
|
+
outcome.summary = description;
|
|
532
|
+
}
|
|
533
|
+
return outcome;
|
|
534
|
+
}
|
|
535
|
+
// Only `run-step` is actually deferred by these requires, and it is the one
|
|
536
|
+
// worth deferring: it pulls in the prompt builders, which nothing but an
|
|
537
|
+
// enabled agentic flow needs. The other two are loaded either way, since the
|
|
538
|
+
// `run/` barrel every caller comes through re-exports `orchestrator.ts`, which
|
|
539
|
+
// imports both statically.
|
|
271
540
|
async function prepareAgenticRun(root, migration, agentic, effectiveCreateCommits, commitPrefix) {
|
|
272
541
|
const { applyAgenticHandoffGitignoreFallback } = require('../agentic/handoff-gitignore');
|
|
273
542
|
const { packageJson: nxPackageJson } = (0, package_json_1.readModulePackageJson)('nx', (0, installation_directory_1.getNxRequirePaths)(root));
|
|
@@ -324,9 +593,9 @@ function forwardDroppedAgentContext(migration, agentContext, agenticKind) {
|
|
|
324
593
|
function printNextSteps(migration, nextSteps) {
|
|
325
594
|
if (nextSteps.length === 0)
|
|
326
595
|
return;
|
|
327
|
-
|
|
596
|
+
(0, agent_output_1.logToAgent)({
|
|
328
597
|
title: `Next steps for ${migration.package}: ${migration.name}`,
|
|
329
|
-
bodyLines: nextSteps.map((line) => `- ${line}`),
|
|
598
|
+
bodyLines: nextSteps.map((line) => `- ${(0, text_1.singleLine)(line)}`),
|
|
330
599
|
});
|
|
331
600
|
}
|
|
332
601
|
function emitOrPrintPrompt(root, migration, agenticKind, impl, resolvedCollection) {
|
|
@@ -353,18 +622,10 @@ function emitPromptForOuterAgent(migrationId, promptPath, documentationPath, imp
|
|
|
353
622
|
: {}),
|
|
354
623
|
};
|
|
355
624
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
`The following prompt-based migration was not applied automatically. Apply it to this workspace, then continue.`,
|
|
361
|
-
``,
|
|
362
|
-
`<nx_migrate_prompt migration="${(0, print_dropped_agent_context_1.escapeXmlAttr)(migrationId)}">`,
|
|
363
|
-
json,
|
|
364
|
-
`</nx_migrate_prompt>`,
|
|
365
|
-
].join('\n');
|
|
366
|
-
// Bare newline pair frames the block so adjacent stdout doesn't run into it.
|
|
367
|
-
process.stdout.write(`\n${block}\n\n`);
|
|
625
|
+
(0, agent_output_1.logToAgent)({
|
|
626
|
+
title: `The following prompt-based migration was not applied automatically. Apply it to this workspace, then continue.`,
|
|
627
|
+
});
|
|
628
|
+
(0, agent_output_1.emitPromptBlock)(migrationId, payload);
|
|
368
629
|
}
|
|
369
630
|
function printPromptForUser(root, migration, promptPath, documentationPath) {
|
|
370
631
|
const bodyLines = [];
|
|
@@ -394,7 +655,7 @@ function printPromptForUser(root, migration, promptPath, documentationPath) {
|
|
|
394
655
|
}
|
|
395
656
|
bodyLines.push('', 'Review the instructions above and apply them manually.');
|
|
396
657
|
}
|
|
397
|
-
|
|
658
|
+
(0, agent_output_1.logToAgent)({
|
|
398
659
|
title: `Prompt-based migration ${migration.package}: ${migration.name} must be applied manually`,
|
|
399
660
|
bodyLines,
|
|
400
661
|
});
|
|
@@ -413,7 +674,9 @@ function resolveDocumentationPath(root, migration, resolvedCollection) {
|
|
|
413
674
|
// An unreadable collection is reported through the warning below.
|
|
414
675
|
}
|
|
415
676
|
if (!documentationPath) {
|
|
416
|
-
|
|
677
|
+
(0, agent_output_1.warnToAgent)({
|
|
678
|
+
title: `Could not resolve the "documentation" file "${migration.documentation}" declared for migration "${migration.package}: ${migration.name}". It will be skipped.`,
|
|
679
|
+
});
|
|
417
680
|
}
|
|
418
681
|
return documentationPath;
|
|
419
682
|
}
|
|
@@ -1,33 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Choice } from '../../utils/prompt-helpers';
|
|
2
2
|
/**
|
|
3
3
|
* Whether `nx migrate` may show interactive prompts: requires a TTY on stdin,
|
|
4
4
|
* not running in CI, and the user not having passed `--no-interactive`.
|
|
5
5
|
*/
|
|
6
6
|
export declare function canPrompt(interactive: boolean | undefined): boolean;
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
* handling for every interactive prompt under `nx migrate`.
|
|
10
|
-
*
|
|
11
|
-
* Why: enquirer's built-in cancel path (`Prompt.cancel` → `close` →
|
|
12
|
-
* `keypress.js` `off` → `readline.pause`) races on `Interface.pause()`
|
|
13
|
-
* when triggered more than once in quick succession (mashed Ctrl+C),
|
|
14
|
-
* throwing `ERR_USE_AFTER_CLOSE` from an unhandled async chain that
|
|
15
|
-
* `await prompt(...)` cannot catch.
|
|
16
|
-
*
|
|
17
|
-
* In raw mode, Ctrl+C does NOT generate a SIGINT signal (the kernel's
|
|
18
|
-
* `ISIG` flag is cleared while enquirer's keypress listener is active);
|
|
19
|
-
* instead the 0x03 byte flows through `combos.js` and dispatches the
|
|
20
|
-
* `cancel` action. So a `process.on('SIGINT', ...)` handler is the wrong
|
|
21
|
-
* mechanism here — it never fires.
|
|
22
|
-
*
|
|
23
|
-
* The right hook is enquirer's per-prompt `options.cancel` override. When
|
|
24
|
-
* present, `Prompt.keypress` (lib/prompt.js:42) calls our handler INSTEAD
|
|
25
|
-
* of `this.cancel`, so enquirer's broken cleanup never runs. Both Ctrl+C
|
|
26
|
-
* (byte 0x03) and Esc map to the `cancel` action, so this single override
|
|
27
|
-
* covers both paths.
|
|
28
|
-
*
|
|
29
|
-
* The handler emits a single-line notice via `output.warn` and exits with
|
|
30
|
-
* POSIX status 130 (128 + SIGINT). Synchronous exit is correct here: the
|
|
31
|
-
* user explicitly asked to abort, there's no recovery state to preserve.
|
|
8
|
+
* Yes/no question for `nx migrate`, aborting the run if the user cancels.
|
|
32
9
|
*/
|
|
33
|
-
export declare function
|
|
10
|
+
export declare function migrateConfirm(options: {
|
|
11
|
+
message: string;
|
|
12
|
+
initial?: boolean;
|
|
13
|
+
}): Promise<boolean>;
|
|
14
|
+
/**
|
|
15
|
+
* Single-choice question for `nx migrate`, aborting the run if the user cancels.
|
|
16
|
+
*/
|
|
17
|
+
export declare function migrateChoice<T extends string>(options: {
|
|
18
|
+
message: string;
|
|
19
|
+
choices: Choice<T>[];
|
|
20
|
+
initial?: T;
|
|
21
|
+
}): Promise<T>;
|