nx 24.0.0-beta.0 → 24.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/nx-cloud-internals.d.ts +1 -0
- package/dist/nx-cloud-internals.js +7 -1
- package/dist/src/command-line/graph/graph.js +2 -1
- package/dist/src/command-line/migrate/agentic/close-agent-session.d.ts +1 -1
- package/dist/src/command-line/migrate/agentic/close-agent-session.js +4 -3
- package/dist/src/command-line/migrate/agentic/handoff-gitignore.d.ts +19 -23
- package/dist/src/command-line/migrate/agentic/handoff-gitignore.js +16 -19
- package/dist/src/command-line/migrate/agentic/master/run-master-session.js +25 -13
- package/dist/src/command-line/migrate/agentic/master/spawn-master.js +82 -22
- package/dist/src/command-line/migrate/agentic/select.js +3 -3
- package/dist/src/command-line/migrate/agentic/types.js +1 -3
- package/dist/src/command-line/migrate/command-object.js +5 -9
- package/dist/src/command-line/migrate/execute-migration.d.ts +2 -2
- package/dist/src/command-line/migrate/execute-migration.js +18 -10
- package/dist/src/command-line/migrate/migrate-analytics.d.ts +6 -0
- package/dist/src/command-line/migrate/migrate-analytics.js +12 -0
- package/dist/src/command-line/migrate/migrate-commits.d.ts +4 -1
- package/dist/src/command-line/migrate/migrate-commits.js +10 -3
- package/dist/src/command-line/migrate/migrate-config.d.ts +6 -6
- package/dist/src/command-line/migrate/migrate-config.js +12 -6
- package/dist/src/command-line/migrate/migrate.d.ts +3 -30
- package/dist/src/command-line/migrate/migrate.js +206 -237
- package/dist/src/command-line/migrate/run/agent-output.d.ts +1 -0
- package/dist/src/command-line/migrate/run/agent-output.js +5 -0
- package/dist/src/command-line/migrate/run/broker.d.ts +14 -5
- package/dist/src/command-line/migrate/run/broker.js +68 -20
- package/dist/src/command-line/migrate/run/existing-run-report.d.ts +1 -1
- package/dist/src/command-line/migrate/run/existing-run-report.js +7 -5
- package/dist/src/command-line/migrate/run/index.d.ts +6 -5
- package/dist/src/command-line/migrate/run/index.js +12 -1
- package/dist/src/command-line/migrate/run/orchestrator.d.ts +21 -0
- package/dist/src/command-line/migrate/run/orchestrator.js +149 -32
- package/dist/src/command-line/migrate/run/run-state.d.ts +13 -0
- package/dist/src/command-line/migrate/run/run-state.js +21 -0
- package/dist/src/command-line/migrate/run/runbook.js +19 -0
- package/dist/src/command-line/migrate/run/state-machine.js +4 -1
- package/dist/src/command-line/migrate/run/util.d.ts +3 -0
- package/dist/src/command-line/migrate/run/util.js +7 -1
- package/dist/src/command-line/migrate/sort-migrations.d.ts +1 -7
- package/dist/src/command-line/migrate/sort-migrations.js +1 -12
- package/dist/src/command-line/show/show-target/info.js +1 -1
- package/dist/src/config/nx-json.d.ts +13 -4
- package/dist/src/config/task-graph.d.ts +1 -1
- package/dist/src/config/workspace-json-project-json.d.ts +72 -0
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/devkit-exports.d.ts +1 -1
- package/dist/src/hasher/check-task-files.js +4 -4
- package/dist/src/hasher/native-task-hasher-impl.js +3 -1
- package/dist/src/hasher/task-hasher.d.ts +2 -0
- package/dist/src/hasher/task-hasher.js +4 -1
- package/dist/src/native/index.d.ts +92 -14
- package/dist/src/native/native-bindings.js +3 -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/project-graph/utils/project-configuration/target-normalization.js +6 -1
- package/dist/src/project-graph/utils/retrieve-workspace-files.js +27 -6
- package/dist/src/tasks-runner/create-task-graph.d.ts +7 -16
- package/dist/src/tasks-runner/create-task-graph.js +43 -57
- package/dist/src/tasks-runner/default-tasks-runner.js +1 -1
- package/dist/src/tasks-runner/init-tasks-runner.js +13 -0
- package/dist/src/tasks-runner/life-cycle.d.ts +4 -1
- package/dist/src/tasks-runner/life-cycle.js +7 -0
- package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +5 -2
- package/dist/src/tasks-runner/life-cycles/performance-analysis.js +13 -7
- package/dist/src/tasks-runner/life-cycles/performance-life-cycle.d.ts +4 -3
- package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +9 -2
- package/dist/src/tasks-runner/readiness/probes.d.ts +9 -0
- package/dist/src/tasks-runner/readiness/probes.js +62 -0
- package/dist/src/tasks-runner/readiness/ready-when.d.ts +38 -0
- package/dist/src/tasks-runner/readiness/ready-when.js +190 -0
- package/dist/src/tasks-runner/run-command.js +7 -6
- package/dist/src/tasks-runner/running-tasks/batch-process.js +6 -0
- package/dist/src/tasks-runner/running-tasks/node-child-process.js +5 -4
- package/dist/src/tasks-runner/task-graph-utils.d.ts +9 -6
- package/dist/src/tasks-runner/task-graph-utils.js +17 -12
- package/dist/src/tasks-runner/task-orchestrator.d.ts +37 -10
- package/dist/src/tasks-runner/task-orchestrator.js +459 -79
- package/dist/src/tasks-runner/tasks-runner.d.ts +1 -1
- package/dist/src/tasks-runner/tasks-schedule.d.ts +18 -1
- package/dist/src/tasks-runner/tasks-schedule.js +64 -6
- package/dist/src/tasks-runner/utils.js +6 -22
- package/dist/src/utils/child-process.js +1 -0
- package/dist/src/utils/git-utils.d.ts +6 -1
- package/dist/src/utils/git-utils.js +26 -5
- package/dist/src/utils/plugins/core-plugins.js +4 -0
- package/dist/src/utils/spawn-without-terminal.d.ts +27 -0
- package/dist/src/utils/spawn-without-terminal.js +110 -0
- package/package.json +12 -11
- package/schemas/nx-schema.json +93 -4
- package/schemas/project-schema.json +84 -2
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
// a public API. The client probes these exports to tell what this nx supports,
|
|
4
4
|
// so keep them stable.
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.openUltracacheConfigurations = exports.importUltracacheConfigurations = void 0;
|
|
6
|
+
exports.TASK_GRAPH_FORMAT = exports.openUltracacheConfigurations = exports.importUltracacheConfigurations = void 0;
|
|
7
7
|
var store_1 = require("./src/ultracache/store");
|
|
8
8
|
Object.defineProperty(exports, "importUltracacheConfigurations", { enumerable: true, get: function () { return store_1.importUltracacheConfigurations; } });
|
|
9
9
|
Object.defineProperty(exports, "openUltracacheConfigurations", { enumerable: true, get: function () { return store_1.openUltracacheConfigurations; } });
|
|
10
|
+
// Nx Cloud reads it to pick the task-graph shape it hands this nx. Bump it when
|
|
11
|
+
// the shape changes in a way Cloud must handle.
|
|
12
|
+
// 1: `dependencies` and `continuousDependencies` edges are task ids (implied
|
|
13
|
+
// when the export is absent)
|
|
14
|
+
// 2: edges are objects (`{ id, waitFor?, inputs? }`)
|
|
15
|
+
exports.TASK_GRAPH_FORMAT = 2;
|
|
@@ -967,7 +967,8 @@ function expandInputs(inputs, project, allWorkspaceFiles, depGraphClientResponse
|
|
|
967
967
|
}
|
|
968
968
|
});
|
|
969
969
|
const workspaceRootsExpanded = getExpandedWorkspaceRoots(workspaceRootInputs, allWorkspaceFiles);
|
|
970
|
-
const
|
|
970
|
+
const projectRoots = depGraphClientResponse.projects.map((p) => p.data.root);
|
|
971
|
+
const filesExpanded = filesInputs.flatMap((globs) => (0, native_1.expandFilesInput)(workspace_root_1.workspaceRoot, globs, projectRoots));
|
|
971
972
|
const otherInputsExpanded = otherInputs.map((input) => {
|
|
972
973
|
if (input === 'TsConfig') {
|
|
973
974
|
return (0, node_path_1.relative)(workspace_root_1.workspaceRoot, (0, typescript_1.getRootTsConfigPath)());
|
|
@@ -18,4 +18,4 @@ export declare function waitForExit(child: ChildProcess): Promise<ExitInfo>;
|
|
|
18
18
|
* orphan the agent. `taskkill` failures are swallowed.
|
|
19
19
|
*/
|
|
20
20
|
export declare function closeAgentSession(child: ChildProcess, exitPromise: Promise<ExitInfo>, gracefulExitMs: number, forceKillWaitMs: number): Promise<void>;
|
|
21
|
-
export declare function raceWithTimeout(promise: Promise<unknown>, timeoutMs: number): Promise<
|
|
21
|
+
export declare function raceWithTimeout(promise: Promise<unknown>, timeoutMs: number): Promise<boolean>;
|
|
@@ -109,13 +109,14 @@ async function forceKillWindowsTree(child, exitPromise, forceKillWaitMs) {
|
|
|
109
109
|
}
|
|
110
110
|
await raceWithTimeout(exitPromise, forceKillWaitMs);
|
|
111
111
|
}
|
|
112
|
+
// True when `promise` resolves before the timeout.
|
|
112
113
|
async function raceWithTimeout(promise, timeoutMs) {
|
|
113
114
|
let timer;
|
|
114
115
|
try {
|
|
115
|
-
await Promise.race([
|
|
116
|
-
promise,
|
|
116
|
+
return await Promise.race([
|
|
117
|
+
promise.then(() => true),
|
|
117
118
|
new Promise((resolve) => {
|
|
118
|
-
timer = setTimeout(resolve, timeoutMs);
|
|
119
|
+
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
119
120
|
}),
|
|
120
121
|
]);
|
|
121
122
|
}
|
|
@@ -3,26 +3,23 @@
|
|
|
3
3
|
* `.nx/migrate-runs/<run-id>/`: handoff and prompt files in both cases, plus
|
|
4
4
|
* the durable run state and its plan snapshots for the orchestrator. The v23
|
|
5
5
|
* migration `23-0-0-add-migrate-runs-to-git-ignore` adds `.nx/migrate-runs` to
|
|
6
|
-
* `.gitignore
|
|
7
|
-
* commits would absorb
|
|
6
|
+
* `.gitignore`, but the scratch exists before that migration runs, so earlier
|
|
7
|
+
* commits would absorb it into the user-visible diff. Its two callers use this
|
|
8
|
+
* function to add the entry before that can happen:
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
+
* 1. Orchestrator init calls it with `applyWhenPlanned` and no standalone
|
|
11
|
+
* commit, before it creates the run dir. Its checkpoint commit carries the
|
|
12
|
+
* edit when commits are on and the checkpoint succeeds. A planned v23
|
|
13
|
+
* migration then changes nothing in its own step.
|
|
10
14
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* is the entire queue.
|
|
15
|
+
* 2. A single-migration worker run with an agent defers to the requested
|
|
16
|
+
* migration when it is the v23 one. Otherwise, below v23, it applies the
|
|
17
|
+
* entry and commits it as a standalone preflight commit (or leaves it in
|
|
18
|
+
* the working tree under `--no-create-commits`).
|
|
16
19
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* and commit it as a standalone preflight commit (or leave it in the
|
|
21
|
-
* working tree under `--no-create-commits`).
|
|
22
|
-
*
|
|
23
|
-
* Not in the queue AND target >= v23 means the user is already past v23. They
|
|
24
|
-
* had the entry historically; if it is gone, that is a conscious removal we
|
|
25
|
-
* respect.
|
|
20
|
+
* When the v23 migration is not planned and nx is at v23 or later, the user is
|
|
21
|
+
* already past v23. They had the entry historically; if it is gone, that is a
|
|
22
|
+
* conscious removal we respect.
|
|
26
23
|
*/
|
|
27
24
|
export declare function applyAgenticHandoffGitignoreFallback({ migrations, installedNxVersion, effectiveCreateCommits, commitPrefix, root, applyWhenPlanned, commitStandalone, }: {
|
|
28
25
|
migrations: ReadonlyArray<{
|
|
@@ -41,12 +38,11 @@ export declare function applyAgenticHandoffGitignoreFallback({ migrations, insta
|
|
|
41
38
|
commitPrefix: string;
|
|
42
39
|
root: string;
|
|
43
40
|
/**
|
|
44
|
-
* Apply the entry even when the
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* v23 cutoff does not apply.
|
|
41
|
+
* Apply the entry even when the v23 migration is in the plan. The
|
|
42
|
+
* orchestrator creates its run dir at init, before any migration runs, so
|
|
43
|
+
* it can't defer to that migration. A planned migration also means the
|
|
44
|
+
* missing entry is not a conscious removal, so the v23 cutoff does not
|
|
45
|
+
* apply.
|
|
50
46
|
*/
|
|
51
47
|
applyWhenPlanned?: boolean;
|
|
52
48
|
/**
|
|
@@ -14,32 +14,29 @@ const types_1 = require("./types");
|
|
|
14
14
|
* `.nx/migrate-runs/<run-id>/`: handoff and prompt files in both cases, plus
|
|
15
15
|
* the durable run state and its plan snapshots for the orchestrator. The v23
|
|
16
16
|
* migration `23-0-0-add-migrate-runs-to-git-ignore` adds `.nx/migrate-runs` to
|
|
17
|
-
* `.gitignore
|
|
18
|
-
* commits would absorb
|
|
17
|
+
* `.gitignore`, but the scratch exists before that migration runs, so earlier
|
|
18
|
+
* commits would absorb it into the user-visible diff. Its two callers use this
|
|
19
|
+
* function to add the entry before that can happen:
|
|
19
20
|
*
|
|
20
|
-
*
|
|
21
|
+
* 1. Orchestrator init calls it with `applyWhenPlanned` and no standalone
|
|
22
|
+
* commit, before it creates the run dir. Its checkpoint commit carries the
|
|
23
|
+
* edit when commits are on and the checkpoint succeeds. A planned v23
|
|
24
|
+
* migration then changes nothing in its own step.
|
|
21
25
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* is the entire queue.
|
|
26
|
+
* 2. A single-migration worker run with an agent defers to the requested
|
|
27
|
+
* migration when it is the v23 one. Otherwise, below v23, it applies the
|
|
28
|
+
* entry and commits it as a standalone preflight commit (or leaves it in
|
|
29
|
+
* the working tree under `--no-create-commits`).
|
|
27
30
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* and commit it as a standalone preflight commit (or leave it in the
|
|
32
|
-
* working tree under `--no-create-commits`).
|
|
33
|
-
*
|
|
34
|
-
* Not in the queue AND target >= v23 means the user is already past v23. They
|
|
35
|
-
* had the entry historically; if it is gone, that is a conscious removal we
|
|
36
|
-
* respect.
|
|
31
|
+
* When the v23 migration is not planned and nx is at v23 or later, the user is
|
|
32
|
+
* already past v23. They had the entry historically; if it is gone, that is a
|
|
33
|
+
* conscious removal we respect.
|
|
37
34
|
*/
|
|
38
35
|
async function applyAgenticHandoffGitignoreFallback({ migrations, installedNxVersion, effectiveCreateCommits, commitPrefix, root, applyWhenPlanned = false, commitStandalone = true, }) {
|
|
39
36
|
if (migrations.some(types_1.isHandoffGitignoreMigration)) {
|
|
40
37
|
if (!applyWhenPlanned) {
|
|
41
|
-
// The
|
|
42
|
-
//
|
|
38
|
+
// The single-migration worker run is that migration, and it adds the
|
|
39
|
+
// entry itself.
|
|
43
40
|
return;
|
|
44
41
|
}
|
|
45
42
|
}
|
|
@@ -21,20 +21,29 @@ async function runMasterSession(input) {
|
|
|
21
21
|
createCommits: init.createCommits,
|
|
22
22
|
skipInstall: init.skipInstall,
|
|
23
23
|
};
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
// A resume refuses when it cannot restore the run's missing runbook; a
|
|
25
|
+
// start refuses when the default-branch commit confirmation is declined.
|
|
26
|
+
let refusal = 'declined_commits';
|
|
27
|
+
const resume = (runId) => {
|
|
28
|
+
refusal = 'runbook_missing';
|
|
29
|
+
return (0, run_1.runOrchestratorResume)({
|
|
30
|
+
root: init.root,
|
|
31
|
+
runId,
|
|
32
|
+
policy,
|
|
33
|
+
emitAgentInstructions: false,
|
|
34
|
+
});
|
|
35
|
+
};
|
|
30
36
|
// With `replaceRunId`, starts fresh over that run only; the consent covers
|
|
31
37
|
// the run the user saw, and any other active run is reported instead.
|
|
32
|
-
const start = (replaceRunId) =>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
const start = (replaceRunId) => {
|
|
39
|
+
refusal = 'declined_commits';
|
|
40
|
+
return (0, run_1.runOrchestratorInit)({
|
|
41
|
+
...init,
|
|
42
|
+
emitAgentInstructions: false,
|
|
43
|
+
onExistingRun: replaceRunId === undefined ? 'report' : 'start-fresh',
|
|
44
|
+
replaceRunId,
|
|
45
|
+
});
|
|
46
|
+
};
|
|
38
47
|
let ready = requestedRunId !== undefined && !startFresh
|
|
39
48
|
? resume(requestedRunId)
|
|
40
49
|
: await start(startFresh ? requestedRunId : undefined);
|
|
@@ -44,6 +53,7 @@ async function runMasterSession(input) {
|
|
|
44
53
|
output_1.output.log({
|
|
45
54
|
title: `Leaving migrate run ${ready.runId} as it is. ${continueHint(init.root, agent.id, ready.runId, ready.facts.policy)}`,
|
|
46
55
|
});
|
|
56
|
+
(0, migrate_analytics_1.reportMigrateRunStopped)('aborted');
|
|
47
57
|
return;
|
|
48
58
|
}
|
|
49
59
|
if (decision === 'continue') {
|
|
@@ -56,9 +66,11 @@ async function runMasterSession(input) {
|
|
|
56
66
|
}
|
|
57
67
|
if (ready.kind === 'existing-run') {
|
|
58
68
|
output_1.output.warn((0, run_1.renderExistingRunReport)(ready.facts, (0, run_1.renderExistingRunCommands)(init.root, ready.facts, init.migrationsPath, agent.id)));
|
|
69
|
+
(0, migrate_analytics_1.reportMigrateRunStopped)('existing_run');
|
|
59
70
|
return 1;
|
|
60
71
|
}
|
|
61
72
|
if (ready.kind === 'refused') {
|
|
73
|
+
(0, migrate_analytics_1.reportMigrateRunStopped)(refusal);
|
|
62
74
|
return;
|
|
63
75
|
}
|
|
64
76
|
const { runId, runRoot, runbookPath, reconcileCommand } = ready;
|
|
@@ -145,7 +157,7 @@ async function runMasterSession(input) {
|
|
|
145
157
|
}
|
|
146
158
|
}
|
|
147
159
|
function continueHint(root, agentId, runId, policy) {
|
|
148
|
-
return `Run ${(0, run_1.renderContinueCommand)(root, runId, policy, agentId)}
|
|
160
|
+
return `Run ${(0, run_1.renderContinueCommand)(root, runId, policy, agentId)} to continue it.`;
|
|
149
161
|
}
|
|
150
162
|
async function decideExistingRun(found, root) {
|
|
151
163
|
output_1.output.log((0, run_1.renderExistingRunReport)(found.facts));
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.spawnMasterSession = spawnMasterSession;
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
|
+
const events_1 = require("events");
|
|
5
6
|
const fs_1 = require("fs");
|
|
6
7
|
const path_1 = require("path");
|
|
8
|
+
const exit_codes_1 = require("../../../../utils/exit-codes");
|
|
7
9
|
const logger_1 = require("../../../../utils/logger");
|
|
8
10
|
const output_1 = require("../../../../utils/output");
|
|
11
|
+
const spawn_without_terminal_1 = require("../../../../utils/spawn-without-terminal");
|
|
9
12
|
const migrate_output_1 = require("../../migrate-output");
|
|
10
13
|
const run_1 = require("../../run");
|
|
11
14
|
const close_agent_session_1 = require("../close-agent-session");
|
|
@@ -13,16 +16,21 @@ const handoff_1 = require("../handoff");
|
|
|
13
16
|
const terminal_repair_1 = require("../terminal-repair");
|
|
14
17
|
const windows_cmd_1 = require("../windows-cmd");
|
|
15
18
|
const invocations_1 = require("./invocations");
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
|
|
19
|
+
// How long a second Ctrl+C waits for the killed operation to settle before nx
|
|
20
|
+
// stops waiting on it: only a process that left its group can hold it open.
|
|
21
|
+
const OPERATION_KILL_WAIT_MS = 2_000;
|
|
22
|
+
// SIGINTs this close together are one Ctrl+C: `npx` and the package managers
|
|
23
|
+
// pass on to nx the SIGINT the terminal already sent it.
|
|
24
|
+
const SAME_PRESS_MS = 500;
|
|
25
|
+
// The wrapper's local re-exec sets the first two for its own hop; inherited,
|
|
26
|
+
// they would change install or routing behavior for every `nx migrate` the
|
|
27
|
+
// agent runs. CI exports the last two for its own `nx affected` runs;
|
|
28
|
+
// inherited, they would replace the base the run names and drop uncommitted
|
|
29
|
+
// files from the pass's selection. The workspace's env files can still set
|
|
30
|
+
// them: every nx command loads those.
|
|
22
31
|
const STRIPPED_ENV_VARS = [
|
|
23
32
|
'NX_MIGRATE_SKIP_INSTALL',
|
|
24
33
|
'NX_MIGRATE_USE_LOCAL',
|
|
25
|
-
'NX_MIGRATE_ORCHESTRATOR',
|
|
26
34
|
'NX_BASE',
|
|
27
35
|
'NX_HEAD',
|
|
28
36
|
];
|
|
@@ -80,9 +88,26 @@ async function spawnMasterSession(input) {
|
|
|
80
88
|
broker?.close();
|
|
81
89
|
return { kind: 'spawn-failed', error: toError(error) };
|
|
82
90
|
}
|
|
83
|
-
// Ctrl+C belongs to the agent from the moment it exists.
|
|
84
|
-
|
|
85
|
-
|
|
91
|
+
// Ctrl+C belongs to the agent from the moment it exists. The operations the
|
|
92
|
+
// broker runs have no terminal, so nx passes on what would have reached them
|
|
93
|
+
// through it, and ends them when it goes away itself.
|
|
94
|
+
const ctrlC = new events_1.EventEmitter();
|
|
95
|
+
let lastPressAt = -Infinity;
|
|
96
|
+
const forwardSigint = () => {
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
if (now - lastPressAt < SAME_PRESS_MS)
|
|
99
|
+
return;
|
|
100
|
+
lastPressAt = now;
|
|
101
|
+
(0, spawn_without_terminal_1.signalCommandsWithoutTerminal)('SIGINT');
|
|
102
|
+
ctrlC.emit('press');
|
|
103
|
+
};
|
|
104
|
+
const exitOnSignal = (signal) => {
|
|
105
|
+
(0, spawn_without_terminal_1.signalCommandsWithoutTerminal)(signal);
|
|
106
|
+
process.exit((0, exit_codes_1.signalToCode)(signal));
|
|
107
|
+
};
|
|
108
|
+
process.on('SIGINT', forwardSigint);
|
|
109
|
+
process.on('SIGHUP', exitOnSignal);
|
|
110
|
+
process.on('SIGTERM', exitOnSignal);
|
|
86
111
|
const sentinelWatch = new AbortController();
|
|
87
112
|
let brokerFailure;
|
|
88
113
|
// Settles when the poll aborts and the request in flight is answered, or
|
|
@@ -133,17 +158,33 @@ async function spawnMasterSession(input) {
|
|
|
133
158
|
finally {
|
|
134
159
|
sentinelWatch.abort();
|
|
135
160
|
// With the agent gone, the terminal is restored before the wait: an
|
|
136
|
-
// agent that left it raw would keep a Ctrl+C from reaching
|
|
137
|
-
// in flight as a signal.
|
|
161
|
+
// agent that left it raw would keep a Ctrl+C from reaching nx as a signal.
|
|
138
162
|
const exited = child.exitCode !== null || child.signalCode !== null;
|
|
163
|
+
let stoppedWaiting = false;
|
|
139
164
|
if (started && exited) {
|
|
140
165
|
restoreTerminal();
|
|
141
|
-
warnOperationInFlight(broker);
|
|
166
|
+
const inFlight = warnOperationInFlight(broker);
|
|
167
|
+
if (inFlight && (await interruptedTwice(brokerDone, ctrlC))) {
|
|
168
|
+
(0, spawn_without_terminal_1.signalCommandsWithoutTerminal)('SIGKILL');
|
|
169
|
+
if (!(await (0, close_agent_session_1.raceWithTimeout)(brokerDone, OPERATION_KILL_WAIT_MS))) {
|
|
170
|
+
stoppedWaiting = true;
|
|
171
|
+
output_1.output.warn({
|
|
172
|
+
title: `Stopped waiting for ${(0, run_1.treeOperationLabel)(inFlight.request, inFlight.stepName)}.`,
|
|
173
|
+
bodyLines: [
|
|
174
|
+
'A process it started may still be running. Resume the run once it has exited.',
|
|
175
|
+
],
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
142
179
|
}
|
|
143
|
-
// The request in flight settles before the lock is released
|
|
144
|
-
|
|
180
|
+
// The request in flight settles before the lock is released, unless nx
|
|
181
|
+
// stopped waiting on it above.
|
|
182
|
+
if (!stoppedWaiting)
|
|
183
|
+
await brokerDone;
|
|
145
184
|
broker.close();
|
|
146
|
-
process.removeListener('SIGINT',
|
|
185
|
+
process.removeListener('SIGINT', forwardSigint);
|
|
186
|
+
process.removeListener('SIGHUP', exitOnSignal);
|
|
187
|
+
process.removeListener('SIGTERM', exitOnSignal);
|
|
147
188
|
if (started && !exited)
|
|
148
189
|
restoreTerminal();
|
|
149
190
|
}
|
|
@@ -155,15 +196,34 @@ function restoreTerminal() {
|
|
|
155
196
|
(0, terminal_repair_1.restoreTermiosAfterAgent)();
|
|
156
197
|
(0, migrate_output_1.resetSgrAfterAgent)();
|
|
157
198
|
}
|
|
158
|
-
// With the agent gone
|
|
159
|
-
// nothing else tells the user one is still running.
|
|
199
|
+
// With the agent gone nothing else tells the user an operation still runs.
|
|
160
200
|
function warnOperationInFlight(broker) {
|
|
161
|
-
const
|
|
162
|
-
if (!
|
|
163
|
-
return;
|
|
201
|
+
const inFlight = broker.requestInFlight;
|
|
202
|
+
if (!inFlight)
|
|
203
|
+
return null;
|
|
164
204
|
output_1.output.warn({
|
|
165
|
-
title: `Still running ${(0, run_1.treeOperationLabel)(request)} for this migrate run. Press Ctrl+C to end it
|
|
205
|
+
title: `Still running ${(0, run_1.treeOperationLabel)(inFlight.request, inFlight.stepName)} for this migrate run (${(0, run_1.formatElapsed)(Date.now() - inFlight.startedAt)} so far).${(0, run_1.commitsStep)(inFlight.request.kind) ? ` ${run_1.COMMIT_INSTALLS_FIRST}` : ''} Press Ctrl+C to end it, and again to stop everything it started. The run can be resumed afterwards.`,
|
|
206
|
+
});
|
|
207
|
+
return inFlight;
|
|
208
|
+
}
|
|
209
|
+
// The first Ctrl+C reaches the operation as usual; the second ends whatever
|
|
210
|
+
// outlived it.
|
|
211
|
+
async function interruptedTwice(done, ctrlC) {
|
|
212
|
+
let countPress;
|
|
213
|
+
const secondPress = new Promise((resolve) => {
|
|
214
|
+
let presses = 0;
|
|
215
|
+
countPress = () => {
|
|
216
|
+
if (++presses === 2)
|
|
217
|
+
resolve(true);
|
|
218
|
+
};
|
|
166
219
|
});
|
|
220
|
+
ctrlC.on('press', countPress);
|
|
221
|
+
try {
|
|
222
|
+
return await Promise.race([done.then(() => false), secondPress]);
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
ctrlC.removeListener('press', countPress);
|
|
226
|
+
}
|
|
167
227
|
}
|
|
168
228
|
async function serviceBrokerUntilAborted(broker, intervalMs, signal) {
|
|
169
229
|
while (!signal.aborted) {
|
|
@@ -97,7 +97,7 @@ function requireInteractiveOrAbort(isInteractive) {
|
|
|
97
97
|
if (isInteractive)
|
|
98
98
|
return;
|
|
99
99
|
output_1.output.error({
|
|
100
|
-
title: 'The agentic flow
|
|
100
|
+
title: 'The agentic flow needs an interactive terminal.',
|
|
101
101
|
bodyLines: [
|
|
102
102
|
'Re-run in an interactive terminal, or pass `--agentic=false` to skip the agentic flow.',
|
|
103
103
|
],
|
|
@@ -106,9 +106,9 @@ function requireInteractiveOrAbort(isInteractive) {
|
|
|
106
106
|
}
|
|
107
107
|
function warnAgenticInteractiveOnly() {
|
|
108
108
|
output_1.output.warn({
|
|
109
|
-
title: 'Skipping the agentic flow:
|
|
109
|
+
title: 'Skipping the agentic flow: the agent session needs an interactive terminal, and this run is non-interactive.',
|
|
110
110
|
bodyLines: [
|
|
111
|
-
'Continuing
|
|
111
|
+
'Continuing without the agentic flow. Generators still run, prompt-based changes are left for you to apply, and no AI validation runs. Re-run in an interactive terminal to use the agentic flow.',
|
|
112
112
|
],
|
|
113
113
|
});
|
|
114
114
|
}
|
|
@@ -28,9 +28,7 @@ exports.PROMPTS_DIR_NAME = 'prompts';
|
|
|
28
28
|
* `.gitignore`. Hard-coded because the agentic preflight is a deliberate
|
|
29
29
|
* one-off coupling: this exact migration owns the entry that keeps
|
|
30
30
|
* `.nx/migrate-runs/<run-id>/...` scratch out of per-migration commits. If
|
|
31
|
-
* the migration is ever renamed, this entry must move with it.
|
|
32
|
-
* rather than in `handoff-gitignore.ts` so `sortMigrations`' hoist check
|
|
33
|
-
* doesn't load that module's migration-execution machinery.
|
|
31
|
+
* the migration is ever renamed, this entry must move with it.
|
|
34
32
|
*/
|
|
35
33
|
const HANDOFF_GITIGNORE_MIGRATION_PACKAGE = 'nx';
|
|
36
34
|
const HANDOFF_GITIGNORE_MIGRATION_NAME = '23-0-0-add-migrate-runs-to-git-ignore';
|
|
@@ -59,10 +59,8 @@ function withMigrationOptions(yargs) {
|
|
|
59
59
|
type: 'string',
|
|
60
60
|
})
|
|
61
61
|
.option('runId', {
|
|
62
|
-
|
|
63
|
-
describe: 'The orchestrated migrate run to record a single migration into, to continue with --run-migrations, or to reconcile when given alone.',
|
|
62
|
+
describe: "The id of a migrate run, as nx prints it. With --run-migrations: continue that run, or replace it with --start-fresh. The AI agent driving a run also passes it with --run-migration to record a migration into the run, and alone to record a step's outcome and get the next step.",
|
|
64
63
|
type: 'string',
|
|
65
|
-
hidden: true,
|
|
66
64
|
})
|
|
67
65
|
.option('stepAction', {
|
|
68
66
|
describe: 'How an orchestrated reconcile resolves its single failed or died step.',
|
|
@@ -71,9 +69,8 @@ function withMigrationOptions(yargs) {
|
|
|
71
69
|
hidden: true,
|
|
72
70
|
})
|
|
73
71
|
.option('startFresh', {
|
|
74
|
-
describe: 'With --run-migrations and --run-id=<id
|
|
72
|
+
describe: 'With --run-migrations and --run-id=<id>: delete the record of the active migrate run the id names, then run the whole plan again, as a new run when orchestrated. Migrations the deleted run applied stay applied.',
|
|
75
73
|
type: 'boolean',
|
|
76
|
-
hidden: true,
|
|
77
74
|
})
|
|
78
75
|
.option('ifExists', {
|
|
79
76
|
describe: `Run migrations only if the migrations file exists, if not continues successfully.`,
|
|
@@ -123,17 +120,16 @@ function withMigrationOptions(yargs) {
|
|
|
123
120
|
choices: exports.MULTI_MAJOR_MODES,
|
|
124
121
|
})
|
|
125
122
|
.option('agentic', {
|
|
126
|
-
describe: '
|
|
123
|
+
describe: 'Use an AI agent to apply prompt-based migrations and review the results. With --run-migrations, one agent session drives the whole run. Pass `--agentic=<agent>` to pin a specific agent (claude-code, codex, or opencode). Pass `--agentic=false` or `--no-agentic` to disable it, including when nx migrate runs inside an AI agent.',
|
|
127
124
|
coerce: cli_args_1.coerceAgenticArg,
|
|
128
125
|
})
|
|
129
126
|
.option('validate', {
|
|
130
|
-
describe: '
|
|
127
|
+
describe: 'Have the AI agent driving the run review the changes of generator-only migrations (those with no `prompt:` field). On by default; pass `--no-validate` to opt out. A continued run keeps the setting it started with. Has no effect when no agent drives the run, or with --run-migration inside an AI agent.',
|
|
131
128
|
type: 'boolean',
|
|
132
129
|
})
|
|
133
130
|
.option('finalValidation', {
|
|
134
|
-
describe: 'With --run-migrations
|
|
131
|
+
describe: 'With --run-migrations, when an AI agent drives the run: once every migration has run, the agent checks that the projects the run touched still lint, build, and pass their unit tests. On by default; pass --no-final-validation to opt out. A continued run keeps the setting it started with.',
|
|
135
132
|
type: 'boolean',
|
|
136
|
-
hidden: true,
|
|
137
133
|
})
|
|
138
134
|
.check(({ createCommits, commitPrefix, from, excludeAppliedMigrations, include, agentic, }) => {
|
|
139
135
|
// Only an explicit `--no-create-commits` is decidable here, before the
|
|
@@ -9,8 +9,8 @@ interface PackageMigrationConfig extends NxMigrationsConfiguration {
|
|
|
9
9
|
export declare function readPackageMigrationConfig(packageName: string, dir: string): PackageMigrationConfig;
|
|
10
10
|
/**
|
|
11
11
|
* With a `sink`, stdin is ignored and stdout and stderr are collected for a
|
|
12
|
-
* caller sharing the terminal
|
|
13
|
-
*
|
|
12
|
+
* caller sharing the terminal, and the install runs without the terminal (see
|
|
13
|
+
* `spawnWithoutTerminal`). Without a sink, the install owns the terminal.
|
|
14
14
|
*/
|
|
15
15
|
export declare function runInstall(nxWorkspaceRoot?: string, phase?: MigrationInstallPhase, rerunCommand?: string, sink?: MigrateOutputSink): Promise<void>;
|
|
16
16
|
export type MigrationInstallPhase = 'pre-migration' | 'post-migration';
|
|
@@ -33,6 +33,7 @@ const package_manager_1 = require("../../utils/package-manager");
|
|
|
33
33
|
const fs_1 = require("fs");
|
|
34
34
|
const installation_directory_1 = require("../../utils/installation-directory");
|
|
35
35
|
const shell_quoting_1 = require("../../utils/shell-quoting");
|
|
36
|
+
const spawn_without_terminal_1 = require("../../utils/spawn-without-terminal");
|
|
36
37
|
const project_graph_1 = require("../../project-graph/project-graph");
|
|
37
38
|
const version_utils_1 = require("./version-utils");
|
|
38
39
|
const deferred_output_1 = require("./deferred-output");
|
|
@@ -64,8 +65,8 @@ function readPackageMigrationConfig(packageName, dir) {
|
|
|
64
65
|
}
|
|
65
66
|
/**
|
|
66
67
|
* With a `sink`, stdin is ignored and stdout and stderr are collected for a
|
|
67
|
-
* caller sharing the terminal
|
|
68
|
-
*
|
|
68
|
+
* caller sharing the terminal, and the install runs without the terminal (see
|
|
69
|
+
* `spawnWithoutTerminal`). Without a sink, the install owns the terminal.
|
|
69
70
|
*/
|
|
70
71
|
function runInstall(nxWorkspaceRoot, phase = 'pre-migration', rerunCommand, sink) {
|
|
71
72
|
const cwd = nxWorkspaceRoot ?? process.cwd();
|
|
@@ -81,14 +82,21 @@ function runInstall(nxWorkspaceRoot, phase = 'pre-migration', rerunCommand, sink
|
|
|
81
82
|
// Without a sink it is mirrored live and other package managers inherit
|
|
82
83
|
// stderr directly, since their output is not inspected.
|
|
83
84
|
const shouldCaptureStderr = packageManager === 'npm';
|
|
84
|
-
const child =
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
const child = sink
|
|
86
|
+
? (0, spawn_without_terminal_1.spawnWithoutTerminal)(installCommand, {
|
|
87
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
88
|
+
cwd,
|
|
89
|
+
})
|
|
90
|
+
: (0, child_process_1.spawn)(installCommand, {
|
|
91
|
+
shell: true,
|
|
92
|
+
stdio: [
|
|
93
|
+
'inherit',
|
|
94
|
+
'inherit',
|
|
95
|
+
shouldCaptureStderr ? 'pipe' : 'inherit',
|
|
96
|
+
],
|
|
97
|
+
windowsHide: true,
|
|
98
|
+
cwd,
|
|
99
|
+
});
|
|
92
100
|
// Decoded per stream: a chunk boundary can split a multi-byte character,
|
|
93
101
|
// and a sequence still incomplete at the end is what `end()` returns.
|
|
94
102
|
const stdoutText = new string_decoder_1.StringDecoder('utf8');
|
|
@@ -18,6 +18,7 @@ export type MigratePromptChoices = {
|
|
|
18
18
|
export type MigratePromptName = keyof MigratePromptChoices;
|
|
19
19
|
export type MigrateGenerateErrorCode = 'resolve_version' | 'fetch_migrations' | 'package_updates';
|
|
20
20
|
export type MigrateRunErrorCode = 'npm_install' | 'migration_exec' | 'agentic' | 'other';
|
|
21
|
+
export type MigrateRunStopReason = 'existing_run' | 'declined_commits' | 'aborted' | 'runbook_missing';
|
|
21
22
|
export declare function setMigrateInclude(include: MigrateInclude): void;
|
|
22
23
|
export declare function setMigrateIncludeSource(source: MigrateIncludeSource): void;
|
|
23
24
|
export declare function classifyMigrateFetchFallback(error: unknown): MigrateFetchFallbackReason;
|
|
@@ -60,6 +61,11 @@ export declare function reportMigrateRunComplete(opts: {
|
|
|
60
61
|
migrationCount: number;
|
|
61
62
|
appliedCount: number;
|
|
62
63
|
}): void;
|
|
64
|
+
/**
|
|
65
|
+
* Closes a `migrate_run_start` whose invocation stopped before running any
|
|
66
|
+
* migration.
|
|
67
|
+
*/
|
|
68
|
+
export declare function reportMigrateRunStopped(reason: MigrateRunStopReason): void;
|
|
63
69
|
export declare function reportMigrateRunError(opts: {
|
|
64
70
|
code: MigrateRunErrorCode;
|
|
65
71
|
migrationPackage?: string;
|
|
@@ -10,6 +10,7 @@ exports.reportMigrateGenerateError = reportMigrateGenerateError;
|
|
|
10
10
|
exports.reportMigrateRunStart = reportMigrateRunStart;
|
|
11
11
|
exports.hasMigrateRunStarted = hasMigrateRunStarted;
|
|
12
12
|
exports.reportMigrateRunComplete = reportMigrateRunComplete;
|
|
13
|
+
exports.reportMigrateRunStopped = reportMigrateRunStopped;
|
|
13
14
|
exports.reportMigrateRunError = reportMigrateRunError;
|
|
14
15
|
exports.reportMigrateOrchestratorInit = reportMigrateOrchestratorInit;
|
|
15
16
|
exports.reportMigrateOrchestratorDispense = reportMigrateOrchestratorDispense;
|
|
@@ -165,6 +166,17 @@ function reportMigrateRunComplete(opts) {
|
|
|
165
166
|
});
|
|
166
167
|
});
|
|
167
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Closes a `migrate_run_start` whose invocation stopped before running any
|
|
171
|
+
* migration.
|
|
172
|
+
*/
|
|
173
|
+
function reportMigrateRunStopped(reason) {
|
|
174
|
+
safeReport(() => {
|
|
175
|
+
if (!analytics_1.customDimensions)
|
|
176
|
+
return;
|
|
177
|
+
(0, analytics_1.reportEvent)(`migrate_run_stopped_${reason}`);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
168
180
|
function reportMigrateRunError(opts) {
|
|
169
181
|
safeReport(() => {
|
|
170
182
|
if (!analytics_1.customDimensions || runErrorRecorded)
|
|
@@ -31,13 +31,16 @@ export type CommitResult = {
|
|
|
31
31
|
* The default `failureGuidance` describes the classic loop's absorb-and-recap
|
|
32
32
|
* behavior; a caller with no later commit or recap to absorb the diff (the
|
|
33
33
|
* standalone single-migration worker) passes its own.
|
|
34
|
+
*
|
|
35
|
+
* A `sink` collects the output for a caller sharing the terminal with an
|
|
36
|
+
* agent, and the commit then runs without the terminal.
|
|
34
37
|
*/
|
|
35
38
|
export declare function commitMigrationIfRequested(root: string, migration: {
|
|
36
39
|
name: string;
|
|
37
40
|
}, shouldCreateCommits: boolean, commitPrefix: string, installDepsIfChanged: () => Promise<void>, pendingMigrations?: ReadonlyArray<{
|
|
38
41
|
package: string;
|
|
39
42
|
name: string;
|
|
40
|
-
}>, failureGuidance?: string,
|
|
43
|
+
}>, failureGuidance?: string, sink?: MigrateOutputSink): Promise<CommitResult>;
|
|
41
44
|
/**
|
|
42
45
|
* Commits any pre-existing working-tree state into a dedicated "checkpoint"
|
|
43
46
|
* commit before the first migration runs. Without this, the first migration's
|