nx 24.0.0-beta.0 → 24.0.0-beta.1
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/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/master/spawn-master.js +76 -15
- 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-commits.d.ts +4 -1
- package/dist/src/command-line/migrate/migrate-commits.js +10 -3
- package/dist/src/command-line/migrate/migrate.js +43 -6
- 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/index.d.ts +4 -3
- package/dist/src/command-line/migrate/run/index.js +8 -1
- package/dist/src/command-line/migrate/run/orchestrator.js +72 -13
- 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/show/show-target/info.js +1 -1
- package/dist/src/config/task-graph.d.ts +1 -1
- package/dist/src/config/workspace-json-project-json.d.ts +60 -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/native/index.d.ts +60 -11
- 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/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/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 +5 -4
- 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 +27 -4
- package/dist/src/tasks-runner/task-orchestrator.js +374 -24
- 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/spawn-without-terminal.d.ts +27 -0
- package/dist/src/utils/spawn-without-terminal.js +110 -0
- package/package.json +11 -11
- package/schemas/nx-schema.json +65 -0
- package/schemas/project-schema.json +62 -0
|
@@ -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;
|
|
@@ -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
|
}
|
|
@@ -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,6 +16,12 @@ 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");
|
|
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;
|
|
16
25
|
// The wrapper's local re-exec sets the first two for its own hop and the user
|
|
17
26
|
// sets the third to reach this path; inherited, they would change install or
|
|
18
27
|
// routing behavior for every `nx migrate` the agent runs. CI exports the last
|
|
@@ -80,9 +89,26 @@ async function spawnMasterSession(input) {
|
|
|
80
89
|
broker?.close();
|
|
81
90
|
return { kind: 'spawn-failed', error: toError(error) };
|
|
82
91
|
}
|
|
83
|
-
// Ctrl+C belongs to the agent from the moment it exists.
|
|
84
|
-
|
|
85
|
-
|
|
92
|
+
// Ctrl+C belongs to the agent from the moment it exists. The operations the
|
|
93
|
+
// broker runs have no terminal, so nx passes on what would have reached them
|
|
94
|
+
// through it, and ends them when it goes away itself.
|
|
95
|
+
const ctrlC = new events_1.EventEmitter();
|
|
96
|
+
let lastPressAt = -Infinity;
|
|
97
|
+
const forwardSigint = () => {
|
|
98
|
+
const now = Date.now();
|
|
99
|
+
if (now - lastPressAt < SAME_PRESS_MS)
|
|
100
|
+
return;
|
|
101
|
+
lastPressAt = now;
|
|
102
|
+
(0, spawn_without_terminal_1.signalCommandsWithoutTerminal)('SIGINT');
|
|
103
|
+
ctrlC.emit('press');
|
|
104
|
+
};
|
|
105
|
+
const exitOnSignal = (signal) => {
|
|
106
|
+
(0, spawn_without_terminal_1.signalCommandsWithoutTerminal)(signal);
|
|
107
|
+
process.exit((0, exit_codes_1.signalToCode)(signal));
|
|
108
|
+
};
|
|
109
|
+
process.on('SIGINT', forwardSigint);
|
|
110
|
+
process.on('SIGHUP', exitOnSignal);
|
|
111
|
+
process.on('SIGTERM', exitOnSignal);
|
|
86
112
|
const sentinelWatch = new AbortController();
|
|
87
113
|
let brokerFailure;
|
|
88
114
|
// Settles when the poll aborts and the request in flight is answered, or
|
|
@@ -133,17 +159,33 @@ async function spawnMasterSession(input) {
|
|
|
133
159
|
finally {
|
|
134
160
|
sentinelWatch.abort();
|
|
135
161
|
// 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.
|
|
162
|
+
// agent that left it raw would keep a Ctrl+C from reaching nx as a signal.
|
|
138
163
|
const exited = child.exitCode !== null || child.signalCode !== null;
|
|
164
|
+
let stoppedWaiting = false;
|
|
139
165
|
if (started && exited) {
|
|
140
166
|
restoreTerminal();
|
|
141
|
-
warnOperationInFlight(broker);
|
|
167
|
+
const inFlight = warnOperationInFlight(broker);
|
|
168
|
+
if (inFlight && (await interruptedTwice(brokerDone, ctrlC))) {
|
|
169
|
+
(0, spawn_without_terminal_1.signalCommandsWithoutTerminal)('SIGKILL');
|
|
170
|
+
if (!(await (0, close_agent_session_1.raceWithTimeout)(brokerDone, OPERATION_KILL_WAIT_MS))) {
|
|
171
|
+
stoppedWaiting = true;
|
|
172
|
+
output_1.output.warn({
|
|
173
|
+
title: `Stopped waiting for ${(0, run_1.treeOperationLabel)(inFlight.request, inFlight.stepName)}.`,
|
|
174
|
+
bodyLines: [
|
|
175
|
+
'A process it started may still be running. Resume the run once it has exited.',
|
|
176
|
+
],
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
142
180
|
}
|
|
143
|
-
// The request in flight settles before the lock is released
|
|
144
|
-
|
|
181
|
+
// The request in flight settles before the lock is released, unless nx
|
|
182
|
+
// stopped waiting on it above.
|
|
183
|
+
if (!stoppedWaiting)
|
|
184
|
+
await brokerDone;
|
|
145
185
|
broker.close();
|
|
146
|
-
process.removeListener('SIGINT',
|
|
186
|
+
process.removeListener('SIGINT', forwardSigint);
|
|
187
|
+
process.removeListener('SIGHUP', exitOnSignal);
|
|
188
|
+
process.removeListener('SIGTERM', exitOnSignal);
|
|
147
189
|
if (started && !exited)
|
|
148
190
|
restoreTerminal();
|
|
149
191
|
}
|
|
@@ -155,15 +197,34 @@ function restoreTerminal() {
|
|
|
155
197
|
(0, terminal_repair_1.restoreTermiosAfterAgent)();
|
|
156
198
|
(0, migrate_output_1.resetSgrAfterAgent)();
|
|
157
199
|
}
|
|
158
|
-
// With the agent gone
|
|
159
|
-
// nothing else tells the user one is still running.
|
|
200
|
+
// With the agent gone nothing else tells the user an operation still runs.
|
|
160
201
|
function warnOperationInFlight(broker) {
|
|
161
|
-
const
|
|
162
|
-
if (!
|
|
163
|
-
return;
|
|
202
|
+
const inFlight = broker.requestInFlight;
|
|
203
|
+
if (!inFlight)
|
|
204
|
+
return null;
|
|
164
205
|
output_1.output.warn({
|
|
165
|
-
title: `Still running ${(0, run_1.treeOperationLabel)(request)} for this migrate run. Press Ctrl+C to end it
|
|
206
|
+
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.`,
|
|
207
|
+
});
|
|
208
|
+
return inFlight;
|
|
209
|
+
}
|
|
210
|
+
// The first Ctrl+C reaches the operation as usual; the second ends whatever
|
|
211
|
+
// outlived it.
|
|
212
|
+
async function interruptedTwice(done, ctrlC) {
|
|
213
|
+
let countPress;
|
|
214
|
+
const secondPress = new Promise((resolve) => {
|
|
215
|
+
let presses = 0;
|
|
216
|
+
countPress = () => {
|
|
217
|
+
if (++presses === 2)
|
|
218
|
+
resolve(true);
|
|
219
|
+
};
|
|
166
220
|
});
|
|
221
|
+
ctrlC.on('press', countPress);
|
|
222
|
+
try {
|
|
223
|
+
return await Promise.race([done.then(() => false), secondPress]);
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
ctrlC.removeListener('press', countPress);
|
|
227
|
+
}
|
|
167
228
|
}
|
|
168
229
|
async function serviceBrokerUntilAborted(broker, intervalMs, signal) {
|
|
169
230
|
while (!signal.aborted) {
|
|
@@ -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');
|
|
@@ -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
|
|
@@ -20,6 +20,9 @@ const deferred_output_1 = require("./deferred-output");
|
|
|
20
20
|
// ignore rule that normally hides it goes missing mid-run (a checkout, a
|
|
21
21
|
// .gitignore edit, or the migration's own changes).
|
|
22
22
|
const MIGRATE_COMMIT_EXCLUDES = [types_1.MIGRATE_RUNS_RELATIVE_DIR];
|
|
23
|
+
// Without it the agent continues as the absorb guidance says, and a cause that
|
|
24
|
+
// fails every commit leaves the whole run uncommitted.
|
|
25
|
+
const AGENT_SESSION_COMMIT_FAILURE = "Commits in this session cannot ask for input, such as a signing passphrase or a hook's prompt, so later commits may fail the same way until the cause is fixed. Before the next step, tell the user what failed and ask whether to fix the cause first or to continue as is. Either way nx tries every later commit, and the first one that lands includes these changes.";
|
|
23
26
|
/**
|
|
24
27
|
* `pendingMigrations` are listed in the commit body so a `git log -p` reader
|
|
25
28
|
* can see which earlier migrations' diffs this commit absorbed (their own
|
|
@@ -28,10 +31,14 @@ const MIGRATE_COMMIT_EXCLUDES = [types_1.MIGRATE_RUNS_RELATIVE_DIR];
|
|
|
28
31
|
* The default `failureGuidance` describes the classic loop's absorb-and-recap
|
|
29
32
|
* behavior; a caller with no later commit or recap to absorb the diff (the
|
|
30
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.
|
|
31
37
|
*/
|
|
32
|
-
async function commitMigrationIfRequested(root, migration, shouldCreateCommits, commitPrefix, installDepsIfChanged, pendingMigrations = [], failureGuidance = 'Any uncommitted changes will be included in the next successful commit, which will reference this migration; if they remain uncommitted, the end-of-run output will list this migration so you can commit or revert them manually.',
|
|
38
|
+
async function commitMigrationIfRequested(root, migration, shouldCreateCommits, commitPrefix, installDepsIfChanged, pendingMigrations = [], failureGuidance = 'Any uncommitted changes will be included in the next successful commit, which will reference this migration; if they remain uncommitted, the end-of-run output will list this migration so you can commit or revert them manually.', sink) {
|
|
33
39
|
if (!shouldCreateCommits)
|
|
34
40
|
return { status: 'disabled' };
|
|
41
|
+
const out = sink ?? deferred_output_1.terminalOutput;
|
|
35
42
|
await installDepsIfChanged();
|
|
36
43
|
// Generator may have only touched gitignored paths or the excluded scratch
|
|
37
44
|
// dir, or the prompt half made no change: log neutrally, not as an error.
|
|
@@ -42,7 +49,7 @@ async function commitMigrationIfRequested(root, migration, shouldCreateCommits,
|
|
|
42
49
|
}
|
|
43
50
|
const commitMessage = buildCommitMessage(`${commitPrefix}${migration.name}`, pendingMigrations);
|
|
44
51
|
try {
|
|
45
|
-
const sha = await (0, git_utils_1.tryCommitChangesAsync)(commitMessage, root, MIGRATE_COMMIT_EXCLUDES);
|
|
52
|
+
const sha = await (0, git_utils_1.tryCommitChangesAsync)(commitMessage, root, MIGRATE_COMMIT_EXCLUDES, { withoutTerminal: sink !== undefined });
|
|
46
53
|
if (sha)
|
|
47
54
|
return { status: 'committed', sha };
|
|
48
55
|
// null = commit landed but `git rev-parse HEAD` failed (see
|
|
@@ -52,7 +59,7 @@ async function commitMigrationIfRequested(root, migration, shouldCreateCommits,
|
|
|
52
59
|
}
|
|
53
60
|
catch (err) {
|
|
54
61
|
const reason = err instanceof Error ? err.message : String(err);
|
|
55
|
-
out.line('red', `The commit for ${migration.name} failed:\n${reason}\nCheck \`git status\` and \`git log\`; the commit may have landed despite this error. ${failureGuidance}`);
|
|
62
|
+
out.line('red', `The commit for ${migration.name} failed:\n${reason}\nCheck \`git status\` and \`git log\`; the commit may have landed despite this error. ${failureGuidance}${sink ? ` ${AGENT_SESSION_COMMIT_FAILURE}` : ''}`);
|
|
56
63
|
return { status: 'failed', reason };
|
|
57
64
|
}
|
|
58
65
|
}
|
|
@@ -37,6 +37,7 @@ const package_manager_1 = require("../../utils/package-manager");
|
|
|
37
37
|
const errors_1 = require("../../utils/min-release-age/errors");
|
|
38
38
|
const resolve_package_version_1 = require("./resolve-package-version");
|
|
39
39
|
const handle_errors_1 = require("../../utils/handle-errors");
|
|
40
|
+
const exit_codes_1 = require("../../utils/exit-codes");
|
|
40
41
|
const connect_to_nx_cloud_1 = require("../nx-cloud/connect/connect-to-nx-cloud");
|
|
41
42
|
const output_1 = require("../../utils/output");
|
|
42
43
|
const fs_1 = require("fs");
|
|
@@ -68,6 +69,7 @@ Object.defineProperty(exports, "normalizeVersion", { enumerable: true, get: func
|
|
|
68
69
|
const execute_migration_1 = require("./execute-migration");
|
|
69
70
|
const step_actions_1 = require("./step-actions");
|
|
70
71
|
const sort_migrations_1 = require("./sort-migrations");
|
|
72
|
+
const text_1 = require("./text");
|
|
71
73
|
const inception_1 = require("./agentic/inception");
|
|
72
74
|
const version_skew_guard_1 = require("./version-skew-guard");
|
|
73
75
|
const versions_1 = require("../../utils/versions");
|
|
@@ -88,20 +90,27 @@ function formatCommandFailure(command, error) {
|
|
|
88
90
|
.trim();
|
|
89
91
|
return [`Command failed: ${command}`, ...(details ? [details] : [])].join('\n');
|
|
90
92
|
}
|
|
93
|
+
// Ctrl+C is left to the child. Killed by it, this process would hand the
|
|
94
|
+
// terminal back to the shell while the child still runs.
|
|
91
95
|
function runOrReturnExitCode(run) {
|
|
96
|
+
const leaveSigintToChild = () => { };
|
|
97
|
+
process.on('SIGINT', leaveSigintToChild);
|
|
92
98
|
try {
|
|
93
99
|
run();
|
|
94
100
|
return 0;
|
|
95
101
|
}
|
|
96
102
|
catch (e) {
|
|
97
|
-
if (typeof e
|
|
98
|
-
e
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
if (typeof e !== 'object' || e === null)
|
|
104
|
+
throw e;
|
|
105
|
+
if ('signal' in e && e.signal === 'SIGINT')
|
|
106
|
+
(0, exit_codes_1.exitAsInterrupted)();
|
|
107
|
+
if ('status' in e && typeof e.status === 'number')
|
|
101
108
|
return e.status;
|
|
102
|
-
}
|
|
103
109
|
throw e;
|
|
104
110
|
}
|
|
111
|
+
finally {
|
|
112
|
+
process.removeListener('SIGINT', leaveSigintToChild);
|
|
113
|
+
}
|
|
105
114
|
}
|
|
106
115
|
function cleanSemver(version) {
|
|
107
116
|
return (0, semver_1.clean)(version) ?? (0, semver_1.coerce)(version);
|
|
@@ -2161,7 +2170,35 @@ async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits, c
|
|
|
2161
2170
|
activeRunToReplace(root, opts.runId);
|
|
2162
2171
|
}
|
|
2163
2172
|
if (!shouldSkipInstall && !process.env.NX_MIGRATE_SKIP_INSTALL) {
|
|
2164
|
-
|
|
2173
|
+
// The run must resume while the cause of a step's failed install persists.
|
|
2174
|
+
const failedInstallSteps = continued?.steps.filter((s) => s.installFailed === true) ?? [];
|
|
2175
|
+
if (failedInstallSteps.length > 0) {
|
|
2176
|
+
const { pmInstallCommand, stepLabel, TERMINAL_STEP_STATUSES } = require('./run');
|
|
2177
|
+
// A settled step keeps its mark, but no step action can retry it.
|
|
2178
|
+
const settled = failedInstallSteps.filter((s) => TERMINAL_STEP_STATUSES.has(s.status));
|
|
2179
|
+
const retryable = failedInstallSteps.filter((s) => !settled.includes(s));
|
|
2180
|
+
const notCompleted = (steps) => `The dependency install of ${steps
|
|
2181
|
+
.map(stepLabel)
|
|
2182
|
+
.join(', ')} did not complete earlier in this run.`;
|
|
2183
|
+
output_1.output.warn({
|
|
2184
|
+
title: 'Skipping the dependency install',
|
|
2185
|
+
bodyLines: [
|
|
2186
|
+
...(retryable.length > 0
|
|
2187
|
+
? [
|
|
2188
|
+
`${notCompleted(retryable)} Retrying that step installs again; any other choice leaves the install to you.`,
|
|
2189
|
+
]
|
|
2190
|
+
: []),
|
|
2191
|
+
...(settled.length > 0
|
|
2192
|
+
? [
|
|
2193
|
+
`${notCompleted(settled)} Run \`${pmInstallCommand(root)}\` once the cause of the failed install is fixed.`,
|
|
2194
|
+
]
|
|
2195
|
+
: []),
|
|
2196
|
+
].map(text_1.singleLine),
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
2199
|
+
else {
|
|
2200
|
+
await (0, execute_migration_1.runInstall)();
|
|
2201
|
+
}
|
|
2165
2202
|
}
|
|
2166
2203
|
if (!__dirname.startsWith(workspace_root_1.workspaceRoot)) {
|
|
2167
2204
|
// we are running from a temp installation with nx latest, switch to running
|
|
@@ -16,6 +16,7 @@ export interface AgentMessage {
|
|
|
16
16
|
export declare function safeLines(lines: readonly string[]): string[];
|
|
17
17
|
export declare function logToAgent(message: AgentMessage): void;
|
|
18
18
|
export declare function warnToAgent(message: AgentMessage): void;
|
|
19
|
+
export declare function logLineToAgent(line: string): void;
|
|
19
20
|
export declare function emitStepBlock(runId: string, stepId: string, action: string, payload: object): void;
|
|
20
21
|
export declare function emitPromptBlock(migrationId: string, payload: object): void;
|
|
21
22
|
export declare function emitStepPromptBlock(stepId: string, payload: object): void;
|
|
@@ -13,10 +13,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
13
13
|
exports.safeLines = safeLines;
|
|
14
14
|
exports.logToAgent = logToAgent;
|
|
15
15
|
exports.warnToAgent = warnToAgent;
|
|
16
|
+
exports.logLineToAgent = logLineToAgent;
|
|
16
17
|
exports.emitStepBlock = emitStepBlock;
|
|
17
18
|
exports.emitPromptBlock = emitPromptBlock;
|
|
18
19
|
exports.emitStepPromptBlock = emitStepPromptBlock;
|
|
19
20
|
exports.emitRunbookBlock = emitRunbookBlock;
|
|
21
|
+
const logger_1 = require("../../../utils/logger");
|
|
20
22
|
const output_1 = require("../../../utils/output");
|
|
21
23
|
const print_dropped_agent_context_1 = require("../agentic/print-dropped-agent-context");
|
|
22
24
|
const text_1 = require("../text");
|
|
@@ -40,6 +42,9 @@ function logToAgent(message) {
|
|
|
40
42
|
function warnToAgent(message) {
|
|
41
43
|
output_1.output.warn(safe(message));
|
|
42
44
|
}
|
|
45
|
+
function logLineToAgent(line) {
|
|
46
|
+
logger_1.logger.info((0, text_1.singleLine)(line));
|
|
47
|
+
}
|
|
43
48
|
/**
|
|
44
49
|
* Serializes a block payload. `<` becomes its JSON unicode escape so a raw one
|
|
45
50
|
* in a value cannot forge the closing tag, and the result stays valid JSON for
|
|
@@ -91,8 +91,8 @@ export declare function acquireTreeOperation(dir: string, request: TreeOperation
|
|
|
91
91
|
export declare function releaseTreeOperation(dir: string, owner: string, markedStepId?: string): void;
|
|
92
92
|
/** The reservation a live process other than `owner` holds, if any. */
|
|
93
93
|
export declare function liveTreeOperation(state: MigrateRunState, owner?: string): MigrateTreeOperation | undefined;
|
|
94
|
-
export declare function treeBusyMessage(held: MigrateTreeOperation): string;
|
|
95
|
-
export declare function treeOperationLabel(held: Pick<MigrateTreeOperation, 'kind' | 'stepId'
|
|
94
|
+
export declare function treeBusyMessage(held: MigrateTreeOperation, stepName?: string): string;
|
|
95
|
+
export declare function treeOperationLabel(held: Pick<MigrateTreeOperation, 'kind' | 'stepId'>, stepName?: string): string;
|
|
96
96
|
export declare function brokerDir(runDirPath: string): string;
|
|
97
97
|
/**
|
|
98
98
|
* Runs the step's install and commit where they can land: in this process
|
|
@@ -119,6 +119,15 @@ export declare function resetStepTree(dir: string, step: MigrateStep, resetInPro
|
|
|
119
119
|
* reservation it runs with, so the caller writes no transition of its own.
|
|
120
120
|
*/
|
|
121
121
|
export declare function giveUpStepTree(root: string, dir: string, step: MigrateStep, skipInstall: boolean, reconcileCommand: string, scope: TreeScope): Promise<GiveUpOutcome>;
|
|
122
|
+
export declare const COMMIT_INSTALLS_FIRST = "A commit first installs any dependency changes.";
|
|
123
|
+
export declare function commitsStep(kind: MigrateTreeOperation['kind']): boolean;
|
|
124
|
+
export declare function sessionOperationWayOut(operation: string): string;
|
|
125
|
+
export declare function formatElapsed(ms: number): string;
|
|
126
|
+
export interface InFlightOperation {
|
|
127
|
+
request: BrokerRequest;
|
|
128
|
+
startedAt: number;
|
|
129
|
+
stepName: string;
|
|
130
|
+
}
|
|
122
131
|
/**
|
|
123
132
|
* The parent side. Holds one exclusive lock for the session's lifetime so a
|
|
124
133
|
* waiting step can tell a slow parent from a dead one, answers each request
|
|
@@ -138,13 +147,13 @@ export declare class MigrateCommitBroker {
|
|
|
138
147
|
private inFlight;
|
|
139
148
|
private readonly lock;
|
|
140
149
|
constructor(root: string, dir: string, reconcileCommand: string, policy: MigrateRunPolicy);
|
|
141
|
-
/** The request whose operation this process is running right now. */
|
|
142
|
-
get requestInFlight():
|
|
150
|
+
/** The request whose operation this process is running right now, and since when. */
|
|
151
|
+
get requestInFlight(): InFlightOperation | null;
|
|
143
152
|
/** Answers this session's unanswered requests, one at a time. */
|
|
144
153
|
service(): Promise<void>;
|
|
145
154
|
private record;
|
|
146
155
|
private answer;
|
|
147
|
-
/** Releases the lock; call after the last `service` settled. */
|
|
156
|
+
/** Releases the lock; call after the last `service` settled, or once giving up on it. */
|
|
148
157
|
close(): void;
|
|
149
158
|
private notADirectory;
|
|
150
159
|
}
|