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.
Files changed (39) hide show
  1. package/package.json +11 -11
  2. package/src/command-line/graph/graph.d.ts.map +1 -1
  3. package/src/command-line/graph/graph.js +90 -57
  4. package/src/command-line/watch/watch.d.ts.map +1 -1
  5. package/src/command-line/watch/watch.js +22 -6
  6. package/src/config/nx-json.d.ts +5 -0
  7. package/src/config/nx-json.d.ts.map +1 -1
  8. package/src/daemon/cache.d.ts.map +1 -1
  9. package/src/daemon/cache.js +9 -3
  10. package/src/daemon/client/client.d.ts +20 -3
  11. package/src/daemon/client/client.d.ts.map +1 -1
  12. package/src/daemon/client/client.js +410 -81
  13. package/src/daemon/client/daemon-socket-messenger.d.ts +5 -2
  14. package/src/daemon/client/daemon-socket-messenger.d.ts.map +1 -1
  15. package/src/daemon/client/daemon-socket-messenger.js +28 -6
  16. package/src/daemon/logger.d.ts +26 -0
  17. package/src/daemon/logger.d.ts.map +1 -0
  18. package/src/daemon/logger.js +65 -0
  19. package/src/daemon/server/file-watching/file-change-events.js +1 -1
  20. package/src/daemon/server/handle-process-in-background.js +1 -1
  21. package/src/daemon/server/handle-request-project-graph.js +1 -1
  22. package/src/daemon/server/nx-console-operations.js +1 -1
  23. package/src/daemon/server/project-graph-incremental-recomputation.d.ts +1 -1
  24. package/src/daemon/server/project-graph-incremental-recomputation.d.ts.map +1 -1
  25. package/src/daemon/server/project-graph-incremental-recomputation.js +7 -7
  26. package/src/daemon/server/project-graph-listener-sockets.d.ts +1 -1
  27. package/src/daemon/server/project-graph-listener-sockets.d.ts.map +1 -1
  28. package/src/daemon/server/project-graph-listener-sockets.js +2 -2
  29. package/src/daemon/server/server.d.ts.map +1 -1
  30. package/src/daemon/server/server.js +34 -18
  31. package/src/daemon/server/shutdown-utils.d.ts +1 -0
  32. package/src/daemon/server/shutdown-utils.d.ts.map +1 -1
  33. package/src/daemon/server/shutdown-utils.js +52 -1
  34. package/src/daemon/server/sync-generators.js +1 -1
  35. package/src/native/nx.wasm32-wasi.wasm +0 -0
  36. package/src/project-graph/nx-deps-cache.js +1 -1
  37. package/src/daemon/server/logger.d.ts +0 -19
  38. package/src/daemon/server/logger.d.ts.map +0 -1
  39. 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
- let messenger;
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
- messenger = new daemon_socket_messenger_1.DaemonSocketMessenger((0, net_1.connect)(socketPath)).listen((message) => {
223
+ this.fileWatcherMessenger = new daemon_socket_messenger_1.DaemonSocketMessenger((0, net_1.connect)(socketPath)).listen((message) => {
192
224
  try {
193
- const parsedMessage = (0, consume_messages_from_socket_1.isJsonMessage)(message)
194
- ? JSON.parse(message)
195
- : (0, node_v8_1.deserialize)(Buffer.from(message, 'binary'));
196
- callback(null, parsedMessage);
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
- callback(e, null);
232
+ for (const cb of this.fileWatcherCallbacks.values()) {
233
+ cb(e, null);
234
+ }
200
235
  }
201
236
  }, () => {
202
- callback('closed', null);
203
- }, (err) => callback(err, null));
204
- return messenger.sendMessage({ type: 'REGISTER_FILE_WATCHER', config });
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
- messenger?.close();
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
- let messenger;
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
- messenger = new daemon_socket_messenger_1.DaemonSocketMessenger((0, net_1.connect)(socketPath)).listen((message) => {
377
+ this.projectGraphListenerMessenger = new daemon_socket_messenger_1.DaemonSocketMessenger((0, net_1.connect)(socketPath)).listen((message) => {
216
378
  try {
217
- const parsedMessage = (0, consume_messages_from_socket_1.isJsonMessage)(message)
218
- ? JSON.parse(message)
219
- : (0, node_v8_1.deserialize)(Buffer.from(message, 'binary'));
220
- callback(null, parsedMessage);
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
- callback(e, null);
386
+ for (const cb of this.projectGraphListenerCallbacks.values()) {
387
+ cb(e, null);
388
+ }
224
389
  }
225
390
  }, () => {
226
- callback('closed', null);
227
- }, (err) => callback(err, null));
228
- return messenger.sendMessage({
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
- messenger?.close();
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
- if (!(await this.isServerAvailable())) {
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
- output_1.output.error({
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.currentReject?.(daemonProcessException('Daemon process terminated and closed the connection'));
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
- return this.currentReject(error);
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
- if ('message' in parsedResult.error &&
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
- let attempts = 0;
592
- return new Promise((resolve, reject) => {
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'): Promise<void>;
10
- listen(onData: (message: string) => void, onClose?: () => void, onError?: (err: Error) => void): this;
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;AAQ7B,MAAM,WAAW,OAAQ,SAAQ,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ;AAED,qBAAa,qBAAqB;IACpB,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM;IAE5B,WAAW,CAAC,eAAe,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,IAAI,GAAG,MAAM;IAkBjE,MAAM,CACJ,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EACjC,OAAO,GAAE,MAAM,IAAe,EAC9B,OAAO,GAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAkB;IAe7C,KAAK;CAGN"}
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"}