nx 22.3.3 → 22.4.0-canary.20251219-137ab45
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/src/command-line/graph/graph.d.ts.map +1 -1
- package/src/command-line/graph/graph.js +90 -57
- 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 +5 -0
- package/src/config/nx-json.d.ts.map +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 +20 -3
- package/src/daemon/client/client.d.ts.map +1 -1
- package/src/daemon/client/client.js +410 -81
- 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-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/native/nx.wasm32-wasi.wasm +0 -0
- 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
|
}
|
|
@@ -184,55 +204,310 @@ class DaemonClient {
|
|
|
184
204
|
throw e;
|
|
185
205
|
}
|
|
186
206
|
}
|
|
187
|
-
|
|
207
|
+
// Generate unique ID for this callback
|
|
208
|
+
const callbackId = Math.random().toString(36).substring(2, 11);
|
|
209
|
+
// Store callback and config for reconnection
|
|
210
|
+
this.fileWatcherCallbacks.set(callbackId, callback);
|
|
211
|
+
this.fileWatcherConfigs.set(callbackId, config);
|
|
188
212
|
await this.queue.sendToQueue(async () => {
|
|
213
|
+
// If we already have a connection, just register the new config
|
|
214
|
+
if (this.fileWatcherMessenger) {
|
|
215
|
+
this.fileWatcherMessenger.sendMessage({
|
|
216
|
+
type: 'REGISTER_FILE_WATCHER',
|
|
217
|
+
config,
|
|
218
|
+
});
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
189
221
|
await this.startDaemonIfNecessary();
|
|
190
222
|
const socketPath = this.getSocketPath();
|
|
191
|
-
|
|
223
|
+
this.fileWatcherMessenger = new daemon_socket_messenger_1.DaemonSocketMessenger((0, net_1.connect)(socketPath)).listen((message) => {
|
|
192
224
|
try {
|
|
193
|
-
const parsedMessage =
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
225
|
+
const parsedMessage = this.parseMessage(message);
|
|
226
|
+
// Notify all callbacks
|
|
227
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
228
|
+
cb(null, parsedMessage);
|
|
229
|
+
}
|
|
197
230
|
}
|
|
198
231
|
catch (e) {
|
|
199
|
-
|
|
232
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
233
|
+
cb(e, null);
|
|
234
|
+
}
|
|
200
235
|
}
|
|
201
236
|
}, () => {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
237
|
+
// Connection closed - trigger reconnection
|
|
238
|
+
logger_1.clientLogger.log(`[FileWatcher] Socket closed, triggering reconnection`);
|
|
239
|
+
this.fileWatcherMessenger = undefined;
|
|
240
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
241
|
+
cb('reconnecting', null);
|
|
242
|
+
}
|
|
243
|
+
this.reconnectFileWatcher();
|
|
244
|
+
}, (err) => {
|
|
245
|
+
if (err instanceof daemon_socket_messenger_1.VersionMismatchError) {
|
|
246
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
247
|
+
cb('closed', null);
|
|
248
|
+
}
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
252
|
+
cb(err, null);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
this.fileWatcherMessenger.sendMessage({
|
|
256
|
+
type: 'REGISTER_FILE_WATCHER',
|
|
257
|
+
config,
|
|
258
|
+
});
|
|
205
259
|
});
|
|
260
|
+
// Return unregister function
|
|
206
261
|
return () => {
|
|
207
|
-
|
|
262
|
+
this.fileWatcherCallbacks.delete(callbackId);
|
|
263
|
+
this.fileWatcherConfigs.delete(callbackId);
|
|
264
|
+
// If no more callbacks, close the connection
|
|
265
|
+
if (this.fileWatcherCallbacks.size === 0) {
|
|
266
|
+
this.fileWatcherMessenger?.close();
|
|
267
|
+
this.fileWatcherMessenger = undefined;
|
|
268
|
+
}
|
|
208
269
|
};
|
|
209
270
|
}
|
|
271
|
+
async reconnectFileWatcher() {
|
|
272
|
+
// Guard against concurrent reconnection attempts
|
|
273
|
+
if (this.fileWatcherReconnecting) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (this.fileWatcherCallbacks.size === 0) {
|
|
277
|
+
return; // No callbacks to reconnect
|
|
278
|
+
}
|
|
279
|
+
this.fileWatcherReconnecting = true;
|
|
280
|
+
logger_1.clientLogger.log(`[FileWatcher] Starting reconnection for ${this.fileWatcherCallbacks.size} callbacks`);
|
|
281
|
+
// Wait for daemon server to be available before trying to reconnect
|
|
282
|
+
let serverAvailable;
|
|
283
|
+
try {
|
|
284
|
+
serverAvailable = await this.waitForServerToBeAvailable({
|
|
285
|
+
ignoreVersionMismatch: false,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
// Version mismatch - pass error to callbacks so they can handle it
|
|
290
|
+
logger_1.clientLogger.log(`[FileWatcher] Error during reconnection: ${err.message}`);
|
|
291
|
+
this.fileWatcherReconnecting = false;
|
|
292
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
293
|
+
cb(err, null);
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (!serverAvailable) {
|
|
298
|
+
// Failed to reconnect after all attempts - notify as closed
|
|
299
|
+
logger_1.clientLogger.log(`[FileWatcher] Failed to reconnect - server unavailable`);
|
|
300
|
+
this.fileWatcherReconnecting = false;
|
|
301
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
302
|
+
cb('closed', null);
|
|
303
|
+
}
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
try {
|
|
307
|
+
// Try to reconnect
|
|
308
|
+
const socketPath = this.getSocketPath();
|
|
309
|
+
this.fileWatcherMessenger = new daemon_socket_messenger_1.DaemonSocketMessenger((0, net_1.connect)(socketPath)).listen((message) => {
|
|
310
|
+
try {
|
|
311
|
+
const parsedMessage = this.parseMessage(message);
|
|
312
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
313
|
+
cb(null, parsedMessage);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
catch (e) {
|
|
317
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
318
|
+
cb(e, null);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}, () => {
|
|
322
|
+
// Connection closed - trigger reconnection again
|
|
323
|
+
this.fileWatcherMessenger = undefined;
|
|
324
|
+
// Reset reconnection flag before triggering reconnection
|
|
325
|
+
this.fileWatcherReconnecting = false;
|
|
326
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
327
|
+
cb('reconnecting', null);
|
|
328
|
+
}
|
|
329
|
+
this.reconnectFileWatcher();
|
|
330
|
+
}, (err) => {
|
|
331
|
+
if (err instanceof daemon_socket_messenger_1.VersionMismatchError) {
|
|
332
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
333
|
+
cb('closed', null);
|
|
334
|
+
}
|
|
335
|
+
process.exit(1);
|
|
336
|
+
}
|
|
337
|
+
// Other errors during reconnection - let retry loop handle
|
|
338
|
+
});
|
|
339
|
+
// Re-register all stored configs
|
|
340
|
+
for (const cfg of this.fileWatcherConfigs.values()) {
|
|
341
|
+
this.fileWatcherMessenger.sendMessage({
|
|
342
|
+
type: 'REGISTER_FILE_WATCHER',
|
|
343
|
+
config: cfg,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
// Successfully reconnected - notify callbacks
|
|
347
|
+
logger_1.clientLogger.log(`[FileWatcher] Reconnected successfully`);
|
|
348
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
349
|
+
cb('reconnected', null);
|
|
350
|
+
}
|
|
351
|
+
this.fileWatcherReconnecting = false;
|
|
352
|
+
}
|
|
353
|
+
catch (e) {
|
|
354
|
+
// Failed to reconnect - notify as closed
|
|
355
|
+
logger_1.clientLogger.log(`[FileWatcher] Reconnection failed: ${e.message}`);
|
|
356
|
+
this.fileWatcherReconnecting = false;
|
|
357
|
+
for (const cb of this.fileWatcherCallbacks.values()) {
|
|
358
|
+
cb('closed', null);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
210
362
|
async registerProjectGraphRecomputationListener(callback) {
|
|
211
|
-
|
|
363
|
+
// Generate unique ID for this callback
|
|
364
|
+
const callbackId = Math.random().toString(36).substring(2, 11);
|
|
365
|
+
// Store callback
|
|
366
|
+
this.projectGraphListenerCallbacks.set(callbackId, callback);
|
|
212
367
|
await this.queue.sendToQueue(async () => {
|
|
368
|
+
// If we already have a connection, just send the registration
|
|
369
|
+
if (this.projectGraphListenerMessenger) {
|
|
370
|
+
this.projectGraphListenerMessenger.sendMessage({
|
|
371
|
+
type: register_project_graph_listener_1.REGISTER_PROJECT_GRAPH_LISTENER,
|
|
372
|
+
});
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
213
375
|
await this.startDaemonIfNecessary();
|
|
214
376
|
const socketPath = this.getSocketPath();
|
|
215
|
-
|
|
377
|
+
this.projectGraphListenerMessenger = new daemon_socket_messenger_1.DaemonSocketMessenger((0, net_1.connect)(socketPath)).listen((message) => {
|
|
216
378
|
try {
|
|
217
|
-
const parsedMessage =
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
379
|
+
const parsedMessage = this.parseMessage(message);
|
|
380
|
+
// Notify all callbacks
|
|
381
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
382
|
+
cb(null, parsedMessage);
|
|
383
|
+
}
|
|
221
384
|
}
|
|
222
385
|
catch (e) {
|
|
223
|
-
|
|
386
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
387
|
+
cb(e, null);
|
|
388
|
+
}
|
|
224
389
|
}
|
|
225
390
|
}, () => {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
391
|
+
// Connection closed - trigger reconnection
|
|
392
|
+
logger_1.clientLogger.log(`[ProjectGraphListener] Socket closed, triggering reconnection`);
|
|
393
|
+
this.projectGraphListenerMessenger = undefined;
|
|
394
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
395
|
+
cb('reconnecting', null);
|
|
396
|
+
}
|
|
397
|
+
this.reconnectProjectGraphListener();
|
|
398
|
+
}, (err) => {
|
|
399
|
+
if (err instanceof daemon_socket_messenger_1.VersionMismatchError) {
|
|
400
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
401
|
+
cb('closed', null);
|
|
402
|
+
}
|
|
403
|
+
process.exit(1);
|
|
404
|
+
}
|
|
405
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
406
|
+
cb(err, null);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
this.projectGraphListenerMessenger.sendMessage({
|
|
229
410
|
type: register_project_graph_listener_1.REGISTER_PROJECT_GRAPH_LISTENER,
|
|
230
411
|
});
|
|
231
412
|
});
|
|
413
|
+
// Return unregister function
|
|
232
414
|
return () => {
|
|
233
|
-
|
|
415
|
+
this.projectGraphListenerCallbacks.delete(callbackId);
|
|
416
|
+
// If no more callbacks, close the connection
|
|
417
|
+
if (this.projectGraphListenerCallbacks.size === 0) {
|
|
418
|
+
this.projectGraphListenerMessenger?.close();
|
|
419
|
+
this.projectGraphListenerMessenger = undefined;
|
|
420
|
+
}
|
|
234
421
|
};
|
|
235
422
|
}
|
|
423
|
+
async reconnectProjectGraphListener() {
|
|
424
|
+
// Guard against concurrent reconnection attempts
|
|
425
|
+
if (this.projectGraphListenerReconnecting) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (this.projectGraphListenerCallbacks.size === 0) {
|
|
429
|
+
return; // No callbacks to reconnect
|
|
430
|
+
}
|
|
431
|
+
this.projectGraphListenerReconnecting = true;
|
|
432
|
+
logger_1.clientLogger.log(`[ProjectGraphListener] Starting reconnection for ${this.projectGraphListenerCallbacks.size} callbacks`);
|
|
433
|
+
// Wait for daemon server to be available before trying to reconnect
|
|
434
|
+
let serverAvailable;
|
|
435
|
+
try {
|
|
436
|
+
serverAvailable = await this.waitForServerToBeAvailable({
|
|
437
|
+
ignoreVersionMismatch: false,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
catch (err) {
|
|
441
|
+
// Version mismatch - pass error to callbacks so they can handle it
|
|
442
|
+
logger_1.clientLogger.log(`[ProjectGraphListener] Error during reconnection: ${err.message}`);
|
|
443
|
+
this.projectGraphListenerReconnecting = false;
|
|
444
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
445
|
+
cb(err, null);
|
|
446
|
+
}
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (!serverAvailable) {
|
|
450
|
+
// Failed to reconnect after all attempts - notify as closed
|
|
451
|
+
logger_1.clientLogger.log(`[ProjectGraphListener] Failed to reconnect - server unavailable`);
|
|
452
|
+
this.projectGraphListenerReconnecting = false;
|
|
453
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
454
|
+
cb('closed', null);
|
|
455
|
+
}
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
try {
|
|
459
|
+
const socketPath = this.getSocketPath();
|
|
460
|
+
// Try to reconnect
|
|
461
|
+
this.projectGraphListenerMessenger = new daemon_socket_messenger_1.DaemonSocketMessenger((0, net_1.connect)(socketPath)).listen((message) => {
|
|
462
|
+
try {
|
|
463
|
+
const parsedMessage = this.parseMessage(message);
|
|
464
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
465
|
+
cb(null, parsedMessage);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
catch (e) {
|
|
469
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
470
|
+
cb(e, null);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}, () => {
|
|
474
|
+
// Connection closed - trigger reconnection again
|
|
475
|
+
this.projectGraphListenerMessenger = undefined;
|
|
476
|
+
// Reset reconnection flag before triggering reconnection
|
|
477
|
+
this.projectGraphListenerReconnecting = false;
|
|
478
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
479
|
+
cb('reconnecting', null);
|
|
480
|
+
}
|
|
481
|
+
this.reconnectProjectGraphListener();
|
|
482
|
+
}, (err) => {
|
|
483
|
+
if (err instanceof daemon_socket_messenger_1.VersionMismatchError) {
|
|
484
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
485
|
+
cb('closed', null);
|
|
486
|
+
}
|
|
487
|
+
process.exit(1);
|
|
488
|
+
}
|
|
489
|
+
// Other errors during reconnection - let retry loop handle
|
|
490
|
+
});
|
|
491
|
+
// Re-register
|
|
492
|
+
this.projectGraphListenerMessenger.sendMessage({
|
|
493
|
+
type: register_project_graph_listener_1.REGISTER_PROJECT_GRAPH_LISTENER,
|
|
494
|
+
});
|
|
495
|
+
// Successfully reconnected - notify callbacks
|
|
496
|
+
logger_1.clientLogger.log(`[ProjectGraphListener] Reconnected successfully`);
|
|
497
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
498
|
+
cb('reconnected', null);
|
|
499
|
+
}
|
|
500
|
+
this.projectGraphListenerReconnecting = false;
|
|
501
|
+
}
|
|
502
|
+
catch (e) {
|
|
503
|
+
// Failed to reconnect - notify as closed
|
|
504
|
+
logger_1.clientLogger.log(`[ProjectGraphListener] Reconnection failed: ${e.message}`);
|
|
505
|
+
this.projectGraphListenerReconnecting = false;
|
|
506
|
+
for (const cb of this.projectGraphListenerCallbacks.values()) {
|
|
507
|
+
cb('closed', null);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
236
511
|
processInBackground(requirePath, data) {
|
|
237
512
|
return this.sendToDaemonViaQueue({
|
|
238
513
|
type: 'PROCESS_IN_BACKGROUND',
|
|
@@ -389,7 +664,7 @@ class DaemonClient {
|
|
|
389
664
|
return this.sendToDaemonViaQueue(message);
|
|
390
665
|
}
|
|
391
666
|
async isServerAvailable() {
|
|
392
|
-
return new Promise((resolve) => {
|
|
667
|
+
return new Promise((resolve, reject) => {
|
|
393
668
|
try {
|
|
394
669
|
const socketPath = this.getSocketPath();
|
|
395
670
|
if (!socketPath) {
|
|
@@ -405,6 +680,9 @@ class DaemonClient {
|
|
|
405
680
|
});
|
|
406
681
|
}
|
|
407
682
|
catch (err) {
|
|
683
|
+
if (err instanceof daemon_socket_messenger_1.VersionMismatchError) {
|
|
684
|
+
reject(err); // Let version mismatch bubble up
|
|
685
|
+
}
|
|
408
686
|
resolve(false);
|
|
409
687
|
}
|
|
410
688
|
});
|
|
@@ -417,7 +695,20 @@ class DaemonClient {
|
|
|
417
695
|
if (this._daemonStatus == DaemonStatus.DISCONNECTED) {
|
|
418
696
|
this._daemonStatus = DaemonStatus.CONNECTING;
|
|
419
697
|
let daemonPid = null;
|
|
420
|
-
|
|
698
|
+
let serverAvailable;
|
|
699
|
+
try {
|
|
700
|
+
serverAvailable = await this.isServerAvailable();
|
|
701
|
+
}
|
|
702
|
+
catch (err) {
|
|
703
|
+
// Version mismatch - treat as server not available, start new one
|
|
704
|
+
if (err instanceof daemon_socket_messenger_1.VersionMismatchError) {
|
|
705
|
+
serverAvailable = false;
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
throw err;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (!serverAvailable) {
|
|
421
712
|
daemonPid = await this.startInBackground();
|
|
422
713
|
}
|
|
423
714
|
this.setUpConnection();
|
|
@@ -444,16 +735,9 @@ class DaemonClient {
|
|
|
444
735
|
this.reset();
|
|
445
736
|
}
|
|
446
737
|
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
|
-
});
|
|
738
|
+
// Connection closed while we had pending work - try to reconnect
|
|
454
739
|
this._daemonStatus = DaemonStatus.DISCONNECTED;
|
|
455
|
-
this.
|
|
456
|
-
process.exit(1);
|
|
740
|
+
this.handleConnectionError(daemonProcessException('Daemon process terminated and closed the connection'));
|
|
457
741
|
}
|
|
458
742
|
}, (err) => {
|
|
459
743
|
if (!err.message) {
|
|
@@ -472,9 +756,90 @@ class DaemonClient {
|
|
|
472
756
|
else {
|
|
473
757
|
error = daemonProcessException(err.toString());
|
|
474
758
|
}
|
|
475
|
-
|
|
759
|
+
this.currentReject(error);
|
|
476
760
|
});
|
|
477
761
|
}
|
|
762
|
+
async handleConnectionError(error) {
|
|
763
|
+
logger_1.clientLogger.log(`[Reconnect] Connection error detected: ${error.message}`);
|
|
764
|
+
// Create a new ready promise for new requests to wait on
|
|
765
|
+
this._waitForDaemonReady = new Promise((resolve) => (this._daemonReady = resolve));
|
|
766
|
+
// Set status to CONNECTING so new requests will wait for reconnection
|
|
767
|
+
this._daemonStatus = DaemonStatus.CONNECTING;
|
|
768
|
+
let serverAvailable;
|
|
769
|
+
try {
|
|
770
|
+
serverAvailable = await this.waitForServerToBeAvailable({
|
|
771
|
+
ignoreVersionMismatch: false,
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
catch (err) {
|
|
775
|
+
if (err instanceof daemon_socket_messenger_1.VersionMismatchError) {
|
|
776
|
+
// New daemon has different version - reject with error so caller can handle
|
|
777
|
+
if (this.currentReject) {
|
|
778
|
+
this.currentReject(err);
|
|
779
|
+
}
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
throw err;
|
|
783
|
+
}
|
|
784
|
+
if (serverAvailable) {
|
|
785
|
+
logger_1.clientLogger.log(`[Reconnect] Reconnection successful, re-establishing connection`);
|
|
786
|
+
// Server is back up, establish connection and signal ready
|
|
787
|
+
this.establishConnection();
|
|
788
|
+
// Resend the pending message if one exists
|
|
789
|
+
if (this.currentMessage && this.currentResolve && this.currentReject) {
|
|
790
|
+
// Decrement the queue counter that was incremented when the error occurred
|
|
791
|
+
this.queue.decrementQueueCounter();
|
|
792
|
+
// Retry the message through the normal queue
|
|
793
|
+
const msg = this.currentMessage;
|
|
794
|
+
const res = this.currentResolve;
|
|
795
|
+
const rej = this.currentReject;
|
|
796
|
+
this.sendToDaemonViaQueue(msg).then(res, rej);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
else {
|
|
800
|
+
// Failed to reconnect after all attempts, reject the pending request
|
|
801
|
+
if (this.currentReject) {
|
|
802
|
+
this.currentReject(error);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
establishConnection() {
|
|
807
|
+
this._daemonStatus = DaemonStatus.DISCONNECTED;
|
|
808
|
+
this.setUpConnection();
|
|
809
|
+
this._daemonStatus = DaemonStatus.CONNECTED;
|
|
810
|
+
this._daemonReady();
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Wait for daemon server to be available.
|
|
814
|
+
* Used for reconnection - throws VersionMismatchError if daemon version differs.
|
|
815
|
+
*/
|
|
816
|
+
async waitForServerToBeAvailable(options) {
|
|
817
|
+
let attempts = 0;
|
|
818
|
+
logger_1.clientLogger.log(`[Client] Waiting for server (max: ${WAIT_FOR_SERVER_CONFIG.maxAttempts} attempts, ${WAIT_FOR_SERVER_CONFIG.delayMs}ms interval)`);
|
|
819
|
+
while (attempts < WAIT_FOR_SERVER_CONFIG.maxAttempts) {
|
|
820
|
+
await new Promise((resolve) => setTimeout(resolve, WAIT_FOR_SERVER_CONFIG.delayMs));
|
|
821
|
+
attempts++;
|
|
822
|
+
try {
|
|
823
|
+
if (await this.isServerAvailable()) {
|
|
824
|
+
logger_1.clientLogger.log(`[Client] Server available after ${attempts} attempts`);
|
|
825
|
+
return true;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
catch (err) {
|
|
829
|
+
if (err instanceof daemon_socket_messenger_1.VersionMismatchError) {
|
|
830
|
+
if (!options.ignoreVersionMismatch) {
|
|
831
|
+
throw err;
|
|
832
|
+
}
|
|
833
|
+
// Keep waiting - old cache file may exist
|
|
834
|
+
}
|
|
835
|
+
else {
|
|
836
|
+
throw err;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
logger_1.clientLogger.log(`[Client] Server not available after ${WAIT_FOR_SERVER_CONFIG.maxAttempts} attempts`);
|
|
841
|
+
return false;
|
|
842
|
+
}
|
|
478
843
|
async sendMessageToDaemon(message, force) {
|
|
479
844
|
await this.startDaemonIfNecessary();
|
|
480
845
|
// An open promise isn't enough to keep the event loop
|
|
@@ -503,29 +868,6 @@ class DaemonClient {
|
|
|
503
868
|
// don't error, this is a secondary concern that should not break task execution
|
|
504
869
|
}
|
|
505
870
|
}
|
|
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
871
|
handleMessage(serializedResult) {
|
|
530
872
|
try {
|
|
531
873
|
perf_hooks_1.performance.mark('result-parse-start-' + this.currentMessage.type);
|
|
@@ -535,14 +877,7 @@ class DaemonClient {
|
|
|
535
877
|
perf_hooks_1.performance.mark('result-parse-end-' + this.currentMessage.type);
|
|
536
878
|
perf_hooks_1.performance.measure('deserialize daemon response - ' + this.currentMessage.type, 'result-parse-start-' + this.currentMessage.type, 'result-parse-end-' + this.currentMessage.type);
|
|
537
879
|
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
|
-
}
|
|
880
|
+
this.currentReject(parsedResult.error);
|
|
546
881
|
}
|
|
547
882
|
else {
|
|
548
883
|
perf_hooks_1.performance.measure(`${this.currentMessage.type} round trip`, 'sendMessageToDaemon-start', 'result-parse-end-' + this.currentMessage.type);
|
|
@@ -573,6 +908,7 @@ class DaemonClient {
|
|
|
573
908
|
}
|
|
574
909
|
this._out = await (0, promises_1.open)(tmp_dir_1.DAEMON_OUTPUT_LOG_FILE, 'a');
|
|
575
910
|
this._err = await (0, promises_1.open)(tmp_dir_1.DAEMON_OUTPUT_LOG_FILE, 'a');
|
|
911
|
+
logger_1.clientLogger.log(`[Client] Starting new daemon server in background`);
|
|
576
912
|
const backgroundProcess = (0, child_process_1.spawn)(process.execPath, [(0, path_1.join)(__dirname, `../server/start.js`)], {
|
|
577
913
|
cwd: workspace_root_1.workspaceRoot,
|
|
578
914
|
stdio: ['ignore', this._out.fd, this._err.fd],
|
|
@@ -588,23 +924,16 @@ class DaemonClient {
|
|
|
588
924
|
/**
|
|
589
925
|
* Ensure the server is actually available to connect to via IPC before resolving
|
|
590
926
|
*/
|
|
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);
|
|
927
|
+
const serverAvailable = await this.waitForServerToBeAvailable({
|
|
928
|
+
ignoreVersionMismatch: true,
|
|
607
929
|
});
|
|
930
|
+
if (serverAvailable) {
|
|
931
|
+
logger_1.clientLogger.log(`[Client] Daemon server started, pid=${backgroundProcess.pid}`);
|
|
932
|
+
return backgroundProcess.pid;
|
|
933
|
+
}
|
|
934
|
+
else {
|
|
935
|
+
throw daemonProcessException('Failed to start or connect to the Nx Daemon process.');
|
|
936
|
+
}
|
|
608
937
|
}
|
|
609
938
|
async stop() {
|
|
610
939
|
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"}
|