nx 22.3.3 → 22.4.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/package.json +11 -11
- package/plugins/package-json.d.ts.map +1 -1
- package/plugins/package-json.js +2 -1
- package/schemas/nx-schema.json +14 -0
- package/src/command-line/graph/graph.d.ts.map +1 -1
- package/src/command-line/graph/graph.js +90 -57
- package/src/command-line/release/config/config.d.ts.map +1 -1
- package/src/command-line/release/config/config.js +2 -0
- package/src/command-line/release/config/conventional-commits.d.ts.map +1 -1
- package/src/command-line/release/config/conventional-commits.js +1 -0
- package/src/command-line/release/utils/release-graph.d.ts +1 -0
- package/src/command-line/release/utils/release-graph.d.ts.map +1 -1
- package/src/command-line/release/utils/release-graph.js +10 -0
- package/src/command-line/release/utils/semver.d.ts +3 -1
- package/src/command-line/release/utils/semver.d.ts.map +1 -1
- package/src/command-line/release/utils/semver.js +39 -4
- package/src/command-line/release/utils/test/test-utils.d.ts +1 -0
- package/src/command-line/release/utils/test/test-utils.d.ts.map +1 -1
- package/src/command-line/release/utils/test/test-utils.js +1 -0
- package/src/command-line/release/version/version-actions.d.ts.map +1 -1
- package/src/command-line/release/version/version-actions.js +3 -1
- package/src/command-line/watch/watch.d.ts.map +1 -1
- package/src/command-line/watch/watch.js +22 -6
- package/src/config/nx-json.d.ts +28 -0
- package/src/config/nx-json.d.ts.map +1 -1
- package/src/config/workspace-json-project-json.d.ts +2 -0
- package/src/config/workspace-json-project-json.d.ts.map +1 -1
- package/src/core/graph/main.js +1 -1
- package/src/core/graph/styles.js +1 -1
- package/src/daemon/cache.d.ts.map +1 -1
- package/src/daemon/cache.js +9 -3
- package/src/daemon/client/client.d.ts +21 -4
- package/src/daemon/client/client.d.ts.map +1 -1
- package/src/daemon/client/client.js +412 -82
- package/src/daemon/client/daemon-socket-messenger.d.ts +5 -2
- package/src/daemon/client/daemon-socket-messenger.d.ts.map +1 -1
- package/src/daemon/client/daemon-socket-messenger.js +28 -6
- package/src/daemon/logger.d.ts +26 -0
- package/src/daemon/logger.d.ts.map +1 -0
- package/src/daemon/logger.js +65 -0
- package/src/daemon/server/file-watching/file-change-events.js +1 -1
- package/src/daemon/server/handle-hash-tasks.d.ts +1 -0
- package/src/daemon/server/handle-hash-tasks.d.ts.map +1 -1
- package/src/daemon/server/handle-hash-tasks.js +1 -1
- package/src/daemon/server/handle-process-in-background.js +1 -1
- package/src/daemon/server/handle-request-project-graph.js +1 -1
- package/src/daemon/server/nx-console-operations.js +1 -1
- package/src/daemon/server/project-graph-incremental-recomputation.d.ts +1 -1
- package/src/daemon/server/project-graph-incremental-recomputation.d.ts.map +1 -1
- package/src/daemon/server/project-graph-incremental-recomputation.js +7 -7
- package/src/daemon/server/project-graph-listener-sockets.d.ts +1 -1
- package/src/daemon/server/project-graph-listener-sockets.d.ts.map +1 -1
- package/src/daemon/server/project-graph-listener-sockets.js +2 -2
- package/src/daemon/server/server.d.ts.map +1 -1
- package/src/daemon/server/server.js +34 -18
- package/src/daemon/server/shutdown-utils.d.ts +1 -0
- package/src/daemon/server/shutdown-utils.d.ts.map +1 -1
- package/src/daemon/server/shutdown-utils.js +52 -1
- package/src/daemon/server/sync-generators.js +1 -1
- package/src/hasher/native-task-hasher-impl.d.ts +2 -2
- package/src/hasher/native-task-hasher-impl.d.ts.map +1 -1
- package/src/hasher/native-task-hasher-impl.js +4 -4
- package/src/hasher/task-hasher.d.ts +6 -6
- package/src/hasher/task-hasher.d.ts.map +1 -1
- package/src/hasher/task-hasher.js +8 -7
- package/src/native/index.d.ts +5 -1
- package/src/native/nx.wasi.cjs +13 -12
- package/src/native/nx.wasm32-wasi.wasm +0 -0
- package/src/plugins/js/project-graph/build-dependencies/target-project-locator.d.ts.map +1 -1
- package/src/plugins/js/project-graph/build-dependencies/target-project-locator.js +5 -0
- package/src/plugins/package-json/create-nodes.d.ts +9 -1
- package/src/plugins/package-json/create-nodes.d.ts.map +1 -1
- package/src/plugins/package-json/create-nodes.js +38 -10
- package/src/project-graph/nx-deps-cache.js +1 -1
- package/src/daemon/server/logger.d.ts +0 -19
- package/src/daemon/server/logger.d.ts.map +0 -1
- package/src/daemon/server/logger.js +0 -38
|
@@ -16,6 +16,7 @@ const nx_json_1 = require("../../config/nx-json");
|
|
|
16
16
|
const configuration_1 = require("../../config/configuration");
|
|
17
17
|
const promised_based_queue_1 = require("../../utils/promised-based-queue");
|
|
18
18
|
const daemon_socket_messenger_1 = require("./daemon-socket-messenger");
|
|
19
|
+
const logger_1 = require("../logger");
|
|
19
20
|
const cache_1 = require("../cache");
|
|
20
21
|
const is_nx_version_mismatch_1 = require("../is-nx-version-mismatch");
|
|
21
22
|
const error_types_1 = require("../../project-graph/error-types");
|
|
@@ -49,6 +50,10 @@ var DaemonStatus;
|
|
|
49
50
|
DaemonStatus[DaemonStatus["DISCONNECTED"] = 1] = "DISCONNECTED";
|
|
50
51
|
DaemonStatus[DaemonStatus["CONNECTED"] = 2] = "CONNECTED";
|
|
51
52
|
})(DaemonStatus || (DaemonStatus = {}));
|
|
53
|
+
const WAIT_FOR_SERVER_CONFIG = {
|
|
54
|
+
delayMs: 10,
|
|
55
|
+
maxAttempts: 6000, // 6000 * 10ms = 60 seconds
|
|
56
|
+
};
|
|
52
57
|
class DaemonClient {
|
|
53
58
|
constructor() {
|
|
54
59
|
this._daemonStatus = DaemonStatus.DISCONNECTED;
|
|
@@ -56,6 +61,11 @@ class DaemonClient {
|
|
|
56
61
|
this._daemonReady = null;
|
|
57
62
|
this._out = null;
|
|
58
63
|
this._err = null;
|
|
64
|
+
this.fileWatcherReconnecting = false;
|
|
65
|
+
this.fileWatcherCallbacks = new Map();
|
|
66
|
+
this.fileWatcherConfigs = new Map();
|
|
67
|
+
this.projectGraphListenerReconnecting = false;
|
|
68
|
+
this.projectGraphListenerCallbacks = new Map();
|
|
59
69
|
try {
|
|
60
70
|
this.nxJson = (0, configuration_1.readNxJson)();
|
|
61
71
|
}
|
|
@@ -117,6 +127,11 @@ class DaemonClient {
|
|
|
117
127
|
this._err?.close();
|
|
118
128
|
this._out = null;
|
|
119
129
|
this._err = null;
|
|
130
|
+
// Clean up file watcher and project graph listener connections
|
|
131
|
+
this.fileWatcherMessenger?.close();
|
|
132
|
+
this.fileWatcherMessenger = undefined;
|
|
133
|
+
this.projectGraphListenerMessenger?.close();
|
|
134
|
+
this.projectGraphListenerMessenger = undefined;
|
|
120
135
|
this._daemonStatus = DaemonStatus.DISCONNECTED;
|
|
121
136
|
this._waitForDaemonReady = new Promise((resolve) => (this._daemonReady = resolve));
|
|
122
137
|
}
|
|
@@ -129,6 +144,11 @@ class DaemonClient {
|
|
|
129
144
|
throw daemonProcessException('Unable to connect to daemon: no socket path available');
|
|
130
145
|
}
|
|
131
146
|
}
|
|
147
|
+
parseMessage(message) {
|
|
148
|
+
return (0, consume_messages_from_socket_1.isJsonMessage)(message)
|
|
149
|
+
? JSON.parse(message)
|
|
150
|
+
: (0, node_v8_1.deserialize)(Buffer.from(message, 'binary'));
|
|
151
|
+
}
|
|
132
152
|
async requestShutdown() {
|
|
133
153
|
return this.sendToDaemonViaQueue({ type: 'REQUEST_SHUTDOWN' });
|
|
134
154
|
}
|
|
@@ -161,7 +181,7 @@ class DaemonClient {
|
|
|
161
181
|
async getAllFileData() {
|
|
162
182
|
return await this.sendToDaemonViaQueue({ type: 'REQUEST_FILE_DATA' });
|
|
163
183
|
}
|
|
164
|
-
hashTasks(runnerOptions, tasks, taskGraph, env) {
|
|
184
|
+
hashTasks(runnerOptions, tasks, taskGraph, env, cwd) {
|
|
165
185
|
return this.sendToDaemonViaQueue({
|
|
166
186
|
type: 'HASH_TASKS',
|
|
167
187
|
runnerOptions,
|
|
@@ -170,6 +190,7 @@ class DaemonClient {
|
|
|
170
190
|
: env,
|
|
171
191
|
tasks,
|
|
172
192
|
taskGraph,
|
|
193
|
+
cwd,
|
|
173
194
|
});
|
|
174
195
|
}
|
|
175
196
|
async registerFileWatcher(config, callback) {
|
|
@@ -184,55 +205,310 @@ class DaemonClient {
|
|
|
184
205
|
throw e;
|
|
185
206
|
}
|
|
186
207
|
}
|
|
187
|
-
|
|
208
|
+
// Generate unique ID for this callback
|
|
209
|
+
const callbackId = Math.random().toString(36).substring(2, 11);
|
|
210
|
+
// Store callback and config for reconnection
|
|
211
|
+
this.fileWatcherCallbacks.set(callbackId, callback);
|
|
212
|
+
this.fileWatcherConfigs.set(callbackId, config);
|
|
188
213
|
await this.queue.sendToQueue(async () => {
|
|
214
|
+
// If we already have a connection, just register the new config
|
|
215
|
+
if (this.fileWatcherMessenger) {
|
|
216
|
+
this.fileWatcherMessenger.sendMessage({
|
|
217
|
+
type: 'REGISTER_FILE_WATCHER',
|
|
218
|
+
config,
|
|
219
|
+
});
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
189
222
|
await this.startDaemonIfNecessary();
|
|
190
223
|
const socketPath = this.getSocketPath();
|
|
191
|
-
|
|
224
|
+
this.fileWatcherMessenger = new daemon_socket_messenger_1.DaemonSocketMessenger((0, net_1.connect)(socketPath)).listen((message) => {
|
|
192
225
|
try {
|
|
193
|
-
const parsedMessage =
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
226
|
+
const parsedMessage = this.parseMessage(message);
|
|
227
|
+
// Notify all callbacks
|
|
228
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
229
|
+
cb(null, parsedMessage);
|
|
230
|
+
}
|
|
197
231
|
}
|
|
198
232
|
catch (e) {
|
|
199
|
-
|
|
233
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
234
|
+
cb(e, null);
|
|
235
|
+
}
|
|
200
236
|
}
|
|
201
237
|
}, () => {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
238
|
+
// Connection closed - trigger reconnection
|
|
239
|
+
logger_1.clientLogger.log(`[FileWatcher] Socket closed, triggering reconnection`);
|
|
240
|
+
this.fileWatcherMessenger = undefined;
|
|
241
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
242
|
+
cb('reconnecting', null);
|
|
243
|
+
}
|
|
244
|
+
this.reconnectFileWatcher();
|
|
245
|
+
}, (err) => {
|
|
246
|
+
if (err instanceof daemon_socket_messenger_1.VersionMismatchError) {
|
|
247
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
248
|
+
cb('closed', null);
|
|
249
|
+
}
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
253
|
+
cb(err, null);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
this.fileWatcherMessenger.sendMessage({
|
|
257
|
+
type: 'REGISTER_FILE_WATCHER',
|
|
258
|
+
config,
|
|
259
|
+
});
|
|
205
260
|
});
|
|
261
|
+
// Return unregister function
|
|
206
262
|
return () => {
|
|
207
|
-
|
|
263
|
+
this.fileWatcherCallbacks.delete(callbackId);
|
|
264
|
+
this.fileWatcherConfigs.delete(callbackId);
|
|
265
|
+
// If no more callbacks, close the connection
|
|
266
|
+
if (this.fileWatcherCallbacks.size === 0) {
|
|
267
|
+
this.fileWatcherMessenger?.close();
|
|
268
|
+
this.fileWatcherMessenger = undefined;
|
|
269
|
+
}
|
|
208
270
|
};
|
|
209
271
|
}
|
|
272
|
+
async reconnectFileWatcher() {
|
|
273
|
+
// Guard against concurrent reconnection attempts
|
|
274
|
+
if (this.fileWatcherReconnecting) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (this.fileWatcherCallbacks.size === 0) {
|
|
278
|
+
return; // No callbacks to reconnect
|
|
279
|
+
}
|
|
280
|
+
this.fileWatcherReconnecting = true;
|
|
281
|
+
logger_1.clientLogger.log(`[FileWatcher] Starting reconnection for ${this.fileWatcherCallbacks.size} callbacks`);
|
|
282
|
+
// Wait for daemon server to be available before trying to reconnect
|
|
283
|
+
let serverAvailable;
|
|
284
|
+
try {
|
|
285
|
+
serverAvailable = await this.waitForServerToBeAvailable({
|
|
286
|
+
ignoreVersionMismatch: false,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
// Version mismatch - pass error to callbacks so they can handle it
|
|
291
|
+
logger_1.clientLogger.log(`[FileWatcher] Error during reconnection: ${err.message}`);
|
|
292
|
+
this.fileWatcherReconnecting = false;
|
|
293
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
294
|
+
cb(err, null);
|
|
295
|
+
}
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (!serverAvailable) {
|
|
299
|
+
// Failed to reconnect after all attempts - notify as closed
|
|
300
|
+
logger_1.clientLogger.log(`[FileWatcher] Failed to reconnect - server unavailable`);
|
|
301
|
+
this.fileWatcherReconnecting = false;
|
|
302
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
303
|
+
cb('closed', null);
|
|
304
|
+
}
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
// Try to reconnect
|
|
309
|
+
const socketPath = this.getSocketPath();
|
|
310
|
+
this.fileWatcherMessenger = new daemon_socket_messenger_1.DaemonSocketMessenger((0, net_1.connect)(socketPath)).listen((message) => {
|
|
311
|
+
try {
|
|
312
|
+
const parsedMessage = this.parseMessage(message);
|
|
313
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
314
|
+
cb(null, parsedMessage);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
catch (e) {
|
|
318
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
319
|
+
cb(e, null);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}, () => {
|
|
323
|
+
// Connection closed - trigger reconnection again
|
|
324
|
+
this.fileWatcherMessenger = undefined;
|
|
325
|
+
// Reset reconnection flag before triggering reconnection
|
|
326
|
+
this.fileWatcherReconnecting = false;
|
|
327
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
328
|
+
cb('reconnecting', null);
|
|
329
|
+
}
|
|
330
|
+
this.reconnectFileWatcher();
|
|
331
|
+
}, (err) => {
|
|
332
|
+
if (err instanceof daemon_socket_messenger_1.VersionMismatchError) {
|
|
333
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
334
|
+
cb('closed', null);
|
|
335
|
+
}
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
// Other errors during reconnection - let retry loop handle
|
|
339
|
+
});
|
|
340
|
+
// Re-register all stored configs
|
|
341
|
+
for (const cfg of this.fileWatcherConfigs.values()) {
|
|
342
|
+
this.fileWatcherMessenger.sendMessage({
|
|
343
|
+
type: 'REGISTER_FILE_WATCHER',
|
|
344
|
+
config: cfg,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
// Successfully reconnected - notify callbacks
|
|
348
|
+
logger_1.clientLogger.log(`[FileWatcher] Reconnected successfully`);
|
|
349
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
350
|
+
cb('reconnected', null);
|
|
351
|
+
}
|
|
352
|
+
this.fileWatcherReconnecting = false;
|
|
353
|
+
}
|
|
354
|
+
catch (e) {
|
|
355
|
+
// Failed to reconnect - notify as closed
|
|
356
|
+
logger_1.clientLogger.log(`[FileWatcher] Reconnection failed: ${e.message}`);
|
|
357
|
+
this.fileWatcherReconnecting = false;
|
|
358
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
359
|
+
cb('closed', null);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
210
363
|
async registerProjectGraphRecomputationListener(callback) {
|
|
211
|
-
|
|
364
|
+
// Generate unique ID for this callback
|
|
365
|
+
const callbackId = Math.random().toString(36).substring(2, 11);
|
|
366
|
+
// Store callback
|
|
367
|
+
this.projectGraphListenerCallbacks.set(callbackId, callback);
|
|
212
368
|
await this.queue.sendToQueue(async () => {
|
|
369
|
+
// If we already have a connection, just send the registration
|
|
370
|
+
if (this.projectGraphListenerMessenger) {
|
|
371
|
+
this.projectGraphListenerMessenger.sendMessage({
|
|
372
|
+
type: register_project_graph_listener_1.REGISTER_PROJECT_GRAPH_LISTENER,
|
|
373
|
+
});
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
213
376
|
await this.startDaemonIfNecessary();
|
|
214
377
|
const socketPath = this.getSocketPath();
|
|
215
|
-
|
|
378
|
+
this.projectGraphListenerMessenger = new daemon_socket_messenger_1.DaemonSocketMessenger((0, net_1.connect)(socketPath)).listen((message) => {
|
|
216
379
|
try {
|
|
217
|
-
const parsedMessage =
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
380
|
+
const parsedMessage = this.parseMessage(message);
|
|
381
|
+
// Notify all callbacks
|
|
382
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
383
|
+
cb(null, parsedMessage);
|
|
384
|
+
}
|
|
221
385
|
}
|
|
222
386
|
catch (e) {
|
|
223
|
-
|
|
387
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
388
|
+
cb(e, null);
|
|
389
|
+
}
|
|
224
390
|
}
|
|
225
391
|
}, () => {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
392
|
+
// Connection closed - trigger reconnection
|
|
393
|
+
logger_1.clientLogger.log(`[ProjectGraphListener] Socket closed, triggering reconnection`);
|
|
394
|
+
this.projectGraphListenerMessenger = undefined;
|
|
395
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
396
|
+
cb('reconnecting', null);
|
|
397
|
+
}
|
|
398
|
+
this.reconnectProjectGraphListener();
|
|
399
|
+
}, (err) => {
|
|
400
|
+
if (err instanceof daemon_socket_messenger_1.VersionMismatchError) {
|
|
401
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
402
|
+
cb('closed', null);
|
|
403
|
+
}
|
|
404
|
+
process.exit(1);
|
|
405
|
+
}
|
|
406
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
407
|
+
cb(err, null);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
this.projectGraphListenerMessenger.sendMessage({
|
|
229
411
|
type: register_project_graph_listener_1.REGISTER_PROJECT_GRAPH_LISTENER,
|
|
230
412
|
});
|
|
231
413
|
});
|
|
414
|
+
// Return unregister function
|
|
232
415
|
return () => {
|
|
233
|
-
|
|
416
|
+
this.projectGraphListenerCallbacks.delete(callbackId);
|
|
417
|
+
// If no more callbacks, close the connection
|
|
418
|
+
if (this.projectGraphListenerCallbacks.size === 0) {
|
|
419
|
+
this.projectGraphListenerMessenger?.close();
|
|
420
|
+
this.projectGraphListenerMessenger = undefined;
|
|
421
|
+
}
|
|
234
422
|
};
|
|
235
423
|
}
|
|
424
|
+
async reconnectProjectGraphListener() {
|
|
425
|
+
// Guard against concurrent reconnection attempts
|
|
426
|
+
if (this.projectGraphListenerReconnecting) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
if (this.projectGraphListenerCallbacks.size === 0) {
|
|
430
|
+
return; // No callbacks to reconnect
|
|
431
|
+
}
|
|
432
|
+
this.projectGraphListenerReconnecting = true;
|
|
433
|
+
logger_1.clientLogger.log(`[ProjectGraphListener] Starting reconnection for ${this.projectGraphListenerCallbacks.size} callbacks`);
|
|
434
|
+
// Wait for daemon server to be available before trying to reconnect
|
|
435
|
+
let serverAvailable;
|
|
436
|
+
try {
|
|
437
|
+
serverAvailable = await this.waitForServerToBeAvailable({
|
|
438
|
+
ignoreVersionMismatch: false,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
catch (err) {
|
|
442
|
+
// Version mismatch - pass error to callbacks so they can handle it
|
|
443
|
+
logger_1.clientLogger.log(`[ProjectGraphListener] Error during reconnection: ${err.message}`);
|
|
444
|
+
this.projectGraphListenerReconnecting = false;
|
|
445
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
446
|
+
cb(err, null);
|
|
447
|
+
}
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
if (!serverAvailable) {
|
|
451
|
+
// Failed to reconnect after all attempts - notify as closed
|
|
452
|
+
logger_1.clientLogger.log(`[ProjectGraphListener] Failed to reconnect - server unavailable`);
|
|
453
|
+
this.projectGraphListenerReconnecting = false;
|
|
454
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
455
|
+
cb('closed', null);
|
|
456
|
+
}
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
try {
|
|
460
|
+
const socketPath = this.getSocketPath();
|
|
461
|
+
// Try to reconnect
|
|
462
|
+
this.projectGraphListenerMessenger = new daemon_socket_messenger_1.DaemonSocketMessenger((0, net_1.connect)(socketPath)).listen((message) => {
|
|
463
|
+
try {
|
|
464
|
+
const parsedMessage = this.parseMessage(message);
|
|
465
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
466
|
+
cb(null, parsedMessage);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
catch (e) {
|
|
470
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
471
|
+
cb(e, null);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}, () => {
|
|
475
|
+
// Connection closed - trigger reconnection again
|
|
476
|
+
this.projectGraphListenerMessenger = undefined;
|
|
477
|
+
// Reset reconnection flag before triggering reconnection
|
|
478
|
+
this.projectGraphListenerReconnecting = false;
|
|
479
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
480
|
+
cb('reconnecting', null);
|
|
481
|
+
}
|
|
482
|
+
this.reconnectProjectGraphListener();
|
|
483
|
+
}, (err) => {
|
|
484
|
+
if (err instanceof daemon_socket_messenger_1.VersionMismatchError) {
|
|
485
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
486
|
+
cb('closed', null);
|
|
487
|
+
}
|
|
488
|
+
process.exit(1);
|
|
489
|
+
}
|
|
490
|
+
// Other errors during reconnection - let retry loop handle
|
|
491
|
+
});
|
|
492
|
+
// Re-register
|
|
493
|
+
this.projectGraphListenerMessenger.sendMessage({
|
|
494
|
+
type: register_project_graph_listener_1.REGISTER_PROJECT_GRAPH_LISTENER,
|
|
495
|
+
});
|
|
496
|
+
// Successfully reconnected - notify callbacks
|
|
497
|
+
logger_1.clientLogger.log(`[ProjectGraphListener] Reconnected successfully`);
|
|
498
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
499
|
+
cb('reconnected', null);
|
|
500
|
+
}
|
|
501
|
+
this.projectGraphListenerReconnecting = false;
|
|
502
|
+
}
|
|
503
|
+
catch (e) {
|
|
504
|
+
// Failed to reconnect - notify as closed
|
|
505
|
+
logger_1.clientLogger.log(`[ProjectGraphListener] Reconnection failed: ${e.message}`);
|
|
506
|
+
this.projectGraphListenerReconnecting = false;
|
|
507
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
508
|
+
cb('closed', null);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
236
512
|
processInBackground(requirePath, data) {
|
|
237
513
|
return this.sendToDaemonViaQueue({
|
|
238
514
|
type: 'PROCESS_IN_BACKGROUND',
|
|
@@ -389,7 +665,7 @@ class DaemonClient {
|
|
|
389
665
|
return this.sendToDaemonViaQueue(message);
|
|
390
666
|
}
|
|
391
667
|
async isServerAvailable() {
|
|
392
|
-
return new Promise((resolve) => {
|
|
668
|
+
return new Promise((resolve, reject) => {
|
|
393
669
|
try {
|
|
394
670
|
const socketPath = this.getSocketPath();
|
|
395
671
|
if (!socketPath) {
|
|
@@ -405,6 +681,9 @@ class DaemonClient {
|
|
|
405
681
|
});
|
|
406
682
|
}
|
|
407
683
|
catch (err) {
|
|
684
|
+
if (err instanceof daemon_socket_messenger_1.VersionMismatchError) {
|
|
685
|
+
reject(err); // Let version mismatch bubble up
|
|
686
|
+
}
|
|
408
687
|
resolve(false);
|
|
409
688
|
}
|
|
410
689
|
});
|
|
@@ -417,7 +696,20 @@ class DaemonClient {
|
|
|
417
696
|
if (this._daemonStatus == DaemonStatus.DISCONNECTED) {
|
|
418
697
|
this._daemonStatus = DaemonStatus.CONNECTING;
|
|
419
698
|
let daemonPid = null;
|
|
420
|
-
|
|
699
|
+
let serverAvailable;
|
|
700
|
+
try {
|
|
701
|
+
serverAvailable = await this.isServerAvailable();
|
|
702
|
+
}
|
|
703
|
+
catch (err) {
|
|
704
|
+
// Version mismatch - treat as server not available, start new one
|
|
705
|
+
if (err instanceof daemon_socket_messenger_1.VersionMismatchError) {
|
|
706
|
+
serverAvailable = false;
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
throw err;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
if (!serverAvailable) {
|
|
421
713
|
daemonPid = await this.startInBackground();
|
|
422
714
|
}
|
|
423
715
|
this.setUpConnection();
|
|
@@ -444,16 +736,9 @@ class DaemonClient {
|
|
|
444
736
|
this.reset();
|
|
445
737
|
}
|
|
446
738
|
else {
|
|
447
|
-
|
|
448
|
-
title: 'Daemon process terminated and closed the connection',
|
|
449
|
-
bodyLines: [
|
|
450
|
-
'Please rerun the command, which will restart the daemon.',
|
|
451
|
-
`If you get this error again, check for any errors in the daemon process logs found in: ${tmp_dir_1.DAEMON_OUTPUT_LOG_FILE}`,
|
|
452
|
-
],
|
|
453
|
-
});
|
|
739
|
+
// Connection closed while we had pending work - try to reconnect
|
|
454
740
|
this._daemonStatus = DaemonStatus.DISCONNECTED;
|
|
455
|
-
this.
|
|
456
|
-
process.exit(1);
|
|
741
|
+
this.handleConnectionError(daemonProcessException('Daemon process terminated and closed the connection'));
|
|
457
742
|
}
|
|
458
743
|
}, (err) => {
|
|
459
744
|
if (!err.message) {
|
|
@@ -472,9 +757,90 @@ class DaemonClient {
|
|
|
472
757
|
else {
|
|
473
758
|
error = daemonProcessException(err.toString());
|
|
474
759
|
}
|
|
475
|
-
|
|
760
|
+
this.currentReject(error);
|
|
476
761
|
});
|
|
477
762
|
}
|
|
763
|
+
async handleConnectionError(error) {
|
|
764
|
+
logger_1.clientLogger.log(`[Reconnect] Connection error detected: ${error.message}`);
|
|
765
|
+
// Create a new ready promise for new requests to wait on
|
|
766
|
+
this._waitForDaemonReady = new Promise((resolve) => (this._daemonReady = resolve));
|
|
767
|
+
// Set status to CONNECTING so new requests will wait for reconnection
|
|
768
|
+
this._daemonStatus = DaemonStatus.CONNECTING;
|
|
769
|
+
let serverAvailable;
|
|
770
|
+
try {
|
|
771
|
+
serverAvailable = await this.waitForServerToBeAvailable({
|
|
772
|
+
ignoreVersionMismatch: false,
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
catch (err) {
|
|
776
|
+
if (err instanceof daemon_socket_messenger_1.VersionMismatchError) {
|
|
777
|
+
// New daemon has different version - reject with error so caller can handle
|
|
778
|
+
if (this.currentReject) {
|
|
779
|
+
this.currentReject(err);
|
|
780
|
+
}
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
throw err;
|
|
784
|
+
}
|
|
785
|
+
if (serverAvailable) {
|
|
786
|
+
logger_1.clientLogger.log(`[Reconnect] Reconnection successful, re-establishing connection`);
|
|
787
|
+
// Server is back up, establish connection and signal ready
|
|
788
|
+
this.establishConnection();
|
|
789
|
+
// Resend the pending message if one exists
|
|
790
|
+
if (this.currentMessage && this.currentResolve && this.currentReject) {
|
|
791
|
+
// Decrement the queue counter that was incremented when the error occurred
|
|
792
|
+
this.queue.decrementQueueCounter();
|
|
793
|
+
// Retry the message through the normal queue
|
|
794
|
+
const msg = this.currentMessage;
|
|
795
|
+
const res = this.currentResolve;
|
|
796
|
+
const rej = this.currentReject;
|
|
797
|
+
this.sendToDaemonViaQueue(msg).then(res, rej);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
else {
|
|
801
|
+
// Failed to reconnect after all attempts, reject the pending request
|
|
802
|
+
if (this.currentReject) {
|
|
803
|
+
this.currentReject(error);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
establishConnection() {
|
|
808
|
+
this._daemonStatus = DaemonStatus.DISCONNECTED;
|
|
809
|
+
this.setUpConnection();
|
|
810
|
+
this._daemonStatus = DaemonStatus.CONNECTED;
|
|
811
|
+
this._daemonReady();
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Wait for daemon server to be available.
|
|
815
|
+
* Used for reconnection - throws VersionMismatchError if daemon version differs.
|
|
816
|
+
*/
|
|
817
|
+
async waitForServerToBeAvailable(options) {
|
|
818
|
+
let attempts = 0;
|
|
819
|
+
logger_1.clientLogger.log(`[Client] Waiting for server (max: ${WAIT_FOR_SERVER_CONFIG.maxAttempts} attempts, ${WAIT_FOR_SERVER_CONFIG.delayMs}ms interval)`);
|
|
820
|
+
while (attempts < WAIT_FOR_SERVER_CONFIG.maxAttempts) {
|
|
821
|
+
await new Promise((resolve) => setTimeout(resolve, WAIT_FOR_SERVER_CONFIG.delayMs));
|
|
822
|
+
attempts++;
|
|
823
|
+
try {
|
|
824
|
+
if (await this.isServerAvailable()) {
|
|
825
|
+
logger_1.clientLogger.log(`[Client] Server available after ${attempts} attempts`);
|
|
826
|
+
return true;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
catch (err) {
|
|
830
|
+
if (err instanceof daemon_socket_messenger_1.VersionMismatchError) {
|
|
831
|
+
if (!options.ignoreVersionMismatch) {
|
|
832
|
+
throw err;
|
|
833
|
+
}
|
|
834
|
+
// Keep waiting - old cache file may exist
|
|
835
|
+
}
|
|
836
|
+
else {
|
|
837
|
+
throw err;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
logger_1.clientLogger.log(`[Client] Server not available after ${WAIT_FOR_SERVER_CONFIG.maxAttempts} attempts`);
|
|
842
|
+
return false;
|
|
843
|
+
}
|
|
478
844
|
async sendMessageToDaemon(message, force) {
|
|
479
845
|
await this.startDaemonIfNecessary();
|
|
480
846
|
// An open promise isn't enough to keep the event loop
|
|
@@ -503,29 +869,6 @@ class DaemonClient {
|
|
|
503
869
|
// don't error, this is a secondary concern that should not break task execution
|
|
504
870
|
}
|
|
505
871
|
}
|
|
506
|
-
retryMessageAfterNewDaemonStarts() {
|
|
507
|
-
const [msg, res, rej] = [
|
|
508
|
-
this.currentMessage,
|
|
509
|
-
this.currentResolve,
|
|
510
|
-
this.currentReject,
|
|
511
|
-
];
|
|
512
|
-
// If we get to this point the daemon is about to close, and we don't
|
|
513
|
-
// want to halt on our daemon terminated unexpectedly condition,
|
|
514
|
-
// so we decrement the promise queue to make it look empty.
|
|
515
|
-
this.queue.decrementQueueCounter();
|
|
516
|
-
if (msg) {
|
|
517
|
-
setTimeout(() => {
|
|
518
|
-
// We wait a bit to allow the server to finish shutting down before
|
|
519
|
-
// retrying the message, which will start a new daemon. Part of
|
|
520
|
-
// the process of starting up the daemon clears this.currentMessage etc
|
|
521
|
-
// so we need to store them before waiting.
|
|
522
|
-
this.sendToDaemonViaQueue(msg).then(res, rej);
|
|
523
|
-
}, 50);
|
|
524
|
-
}
|
|
525
|
-
else {
|
|
526
|
-
throw new Error('Daemon client attempted to retry a message without a current message');
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
872
|
handleMessage(serializedResult) {
|
|
530
873
|
try {
|
|
531
874
|
perf_hooks_1.performance.mark('result-parse-start-' + this.currentMessage.type);
|
|
@@ -535,14 +878,7 @@ class DaemonClient {
|
|
|
535
878
|
perf_hooks_1.performance.mark('result-parse-end-' + this.currentMessage.type);
|
|
536
879
|
perf_hooks_1.performance.measure('deserialize daemon response - ' + this.currentMessage.type, 'result-parse-start-' + this.currentMessage.type, 'result-parse-end-' + this.currentMessage.type);
|
|
537
880
|
if (parsedResult.error) {
|
|
538
|
-
|
|
539
|
-
(parsedResult.error.message === 'NX_VERSION_CHANGED' ||
|
|
540
|
-
parsedResult.error.message === 'LOCK_FILES_CHANGED')) {
|
|
541
|
-
this.retryMessageAfterNewDaemonStarts();
|
|
542
|
-
}
|
|
543
|
-
else {
|
|
544
|
-
this.currentReject(parsedResult.error);
|
|
545
|
-
}
|
|
881
|
+
this.currentReject(parsedResult.error);
|
|
546
882
|
}
|
|
547
883
|
else {
|
|
548
884
|
perf_hooks_1.performance.measure(`${this.currentMessage.type} round trip`, 'sendMessageToDaemon-start', 'result-parse-end-' + this.currentMessage.type);
|
|
@@ -573,6 +909,7 @@ class DaemonClient {
|
|
|
573
909
|
}
|
|
574
910
|
this._out = await (0, promises_1.open)(tmp_dir_1.DAEMON_OUTPUT_LOG_FILE, 'a');
|
|
575
911
|
this._err = await (0, promises_1.open)(tmp_dir_1.DAEMON_OUTPUT_LOG_FILE, 'a');
|
|
912
|
+
logger_1.clientLogger.log(`[Client] Starting new daemon server in background`);
|
|
576
913
|
const backgroundProcess = (0, child_process_1.spawn)(process.execPath, [(0, path_1.join)(__dirname, `../server/start.js`)], {
|
|
577
914
|
cwd: workspace_root_1.workspaceRoot,
|
|
578
915
|
stdio: ['ignore', this._out.fd, this._err.fd],
|
|
@@ -588,23 +925,16 @@ class DaemonClient {
|
|
|
588
925
|
/**
|
|
589
926
|
* Ensure the server is actually available to connect to via IPC before resolving
|
|
590
927
|
*/
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
const id = setInterval(async () => {
|
|
594
|
-
if (await this.isServerAvailable()) {
|
|
595
|
-
clearInterval(id);
|
|
596
|
-
resolve(backgroundProcess.pid);
|
|
597
|
-
}
|
|
598
|
-
else if (attempts > 6000) {
|
|
599
|
-
// daemon fails to start, the process probably exited
|
|
600
|
-
// we print the logs and exit the client
|
|
601
|
-
reject(daemonProcessException('Failed to start or connect to the Nx Daemon process.'));
|
|
602
|
-
}
|
|
603
|
-
else {
|
|
604
|
-
attempts++;
|
|
605
|
-
}
|
|
606
|
-
}, 10);
|
|
928
|
+
const serverAvailable = await this.waitForServerToBeAvailable({
|
|
929
|
+
ignoreVersionMismatch: true,
|
|
607
930
|
});
|
|
931
|
+
if (serverAvailable) {
|
|
932
|
+
logger_1.clientLogger.log(`[Client] Daemon server started, pid=${backgroundProcess.pid}`);
|
|
933
|
+
return backgroundProcess.pid;
|
|
934
|
+
}
|
|
935
|
+
else {
|
|
936
|
+
throw daemonProcessException('Failed to start or connect to the Nx Daemon process.');
|
|
937
|
+
}
|
|
608
938
|
}
|
|
609
939
|
async stop() {
|
|
610
940
|
try {
|
|
@@ -3,11 +3,14 @@ export interface Message extends Record<string, any> {
|
|
|
3
3
|
type: string;
|
|
4
4
|
data?: any;
|
|
5
5
|
}
|
|
6
|
+
export declare class VersionMismatchError extends Error {
|
|
7
|
+
constructor();
|
|
8
|
+
}
|
|
6
9
|
export declare class DaemonSocketMessenger {
|
|
7
10
|
private socket;
|
|
8
11
|
constructor(socket: Socket);
|
|
9
|
-
sendMessage(messageToDaemon: Message, force?: 'v8' | 'json'):
|
|
10
|
-
listen(onData: (message: string) => void, onClose?: () => void, onError?: (err: Error) => void):
|
|
12
|
+
sendMessage(messageToDaemon: Message, force?: 'v8' | 'json'): void;
|
|
13
|
+
listen(onData: (message: string) => void, onClose?: () => void, onError?: (err: Error) => void): DaemonSocketMessenger;
|
|
11
14
|
close(): void;
|
|
12
15
|
}
|
|
13
16
|
//# sourceMappingURL=daemon-socket-messenger.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon-socket-messenger.d.ts","sourceRoot":"","sources":["../../../../../../packages/nx/src/daemon/client/daemon-socket-messenger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"daemon-socket-messenger.d.ts","sourceRoot":"","sources":["../../../../../../packages/nx/src/daemon/client/daemon-socket-messenger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAS7B,MAAM,WAAW,OAAQ,SAAQ,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ;AAED,qBAAa,oBAAqB,SAAQ,KAAK;;CAM9C;AAED,qBAAa,qBAAqB;IACpB,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM;IAElC,WAAW,CAAC,eAAe,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,IAAI,GAAG,MAAM;IAuB3D,MAAM,CACJ,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EACjC,OAAO,GAAE,MAAM,IAAe,EAC9B,OAAO,GAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAe,GACvC,qBAAqB;IAwBxB,KAAK;CAKN"}
|