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.
Files changed (77) hide show
  1. package/package.json +11 -11
  2. package/plugins/package-json.d.ts.map +1 -1
  3. package/plugins/package-json.js +2 -1
  4. package/schemas/nx-schema.json +14 -0
  5. package/src/command-line/graph/graph.d.ts.map +1 -1
  6. package/src/command-line/graph/graph.js +90 -57
  7. package/src/command-line/release/config/config.d.ts.map +1 -1
  8. package/src/command-line/release/config/config.js +2 -0
  9. package/src/command-line/release/config/conventional-commits.d.ts.map +1 -1
  10. package/src/command-line/release/config/conventional-commits.js +1 -0
  11. package/src/command-line/release/utils/release-graph.d.ts +1 -0
  12. package/src/command-line/release/utils/release-graph.d.ts.map +1 -1
  13. package/src/command-line/release/utils/release-graph.js +10 -0
  14. package/src/command-line/release/utils/semver.d.ts +3 -1
  15. package/src/command-line/release/utils/semver.d.ts.map +1 -1
  16. package/src/command-line/release/utils/semver.js +39 -4
  17. package/src/command-line/release/utils/test/test-utils.d.ts +1 -0
  18. package/src/command-line/release/utils/test/test-utils.d.ts.map +1 -1
  19. package/src/command-line/release/utils/test/test-utils.js +1 -0
  20. package/src/command-line/release/version/version-actions.d.ts.map +1 -1
  21. package/src/command-line/release/version/version-actions.js +3 -1
  22. package/src/command-line/watch/watch.d.ts.map +1 -1
  23. package/src/command-line/watch/watch.js +22 -6
  24. package/src/config/nx-json.d.ts +28 -0
  25. package/src/config/nx-json.d.ts.map +1 -1
  26. package/src/config/workspace-json-project-json.d.ts +2 -0
  27. package/src/config/workspace-json-project-json.d.ts.map +1 -1
  28. package/src/core/graph/main.js +1 -1
  29. package/src/core/graph/styles.js +1 -1
  30. package/src/daemon/cache.d.ts.map +1 -1
  31. package/src/daemon/cache.js +9 -3
  32. package/src/daemon/client/client.d.ts +21 -4
  33. package/src/daemon/client/client.d.ts.map +1 -1
  34. package/src/daemon/client/client.js +412 -82
  35. package/src/daemon/client/daemon-socket-messenger.d.ts +5 -2
  36. package/src/daemon/client/daemon-socket-messenger.d.ts.map +1 -1
  37. package/src/daemon/client/daemon-socket-messenger.js +28 -6
  38. package/src/daemon/logger.d.ts +26 -0
  39. package/src/daemon/logger.d.ts.map +1 -0
  40. package/src/daemon/logger.js +65 -0
  41. package/src/daemon/server/file-watching/file-change-events.js +1 -1
  42. package/src/daemon/server/handle-hash-tasks.d.ts +1 -0
  43. package/src/daemon/server/handle-hash-tasks.d.ts.map +1 -1
  44. package/src/daemon/server/handle-hash-tasks.js +1 -1
  45. package/src/daemon/server/handle-process-in-background.js +1 -1
  46. package/src/daemon/server/handle-request-project-graph.js +1 -1
  47. package/src/daemon/server/nx-console-operations.js +1 -1
  48. package/src/daemon/server/project-graph-incremental-recomputation.d.ts +1 -1
  49. package/src/daemon/server/project-graph-incremental-recomputation.d.ts.map +1 -1
  50. package/src/daemon/server/project-graph-incremental-recomputation.js +7 -7
  51. package/src/daemon/server/project-graph-listener-sockets.d.ts +1 -1
  52. package/src/daemon/server/project-graph-listener-sockets.d.ts.map +1 -1
  53. package/src/daemon/server/project-graph-listener-sockets.js +2 -2
  54. package/src/daemon/server/server.d.ts.map +1 -1
  55. package/src/daemon/server/server.js +34 -18
  56. package/src/daemon/server/shutdown-utils.d.ts +1 -0
  57. package/src/daemon/server/shutdown-utils.d.ts.map +1 -1
  58. package/src/daemon/server/shutdown-utils.js +52 -1
  59. package/src/daemon/server/sync-generators.js +1 -1
  60. package/src/hasher/native-task-hasher-impl.d.ts +2 -2
  61. package/src/hasher/native-task-hasher-impl.d.ts.map +1 -1
  62. package/src/hasher/native-task-hasher-impl.js +4 -4
  63. package/src/hasher/task-hasher.d.ts +6 -6
  64. package/src/hasher/task-hasher.d.ts.map +1 -1
  65. package/src/hasher/task-hasher.js +8 -7
  66. package/src/native/index.d.ts +5 -1
  67. package/src/native/nx.wasi.cjs +13 -12
  68. package/src/native/nx.wasm32-wasi.wasm +0 -0
  69. package/src/plugins/js/project-graph/build-dependencies/target-project-locator.d.ts.map +1 -1
  70. package/src/plugins/js/project-graph/build-dependencies/target-project-locator.js +5 -0
  71. package/src/plugins/package-json/create-nodes.d.ts +9 -1
  72. package/src/plugins/package-json/create-nodes.d.ts.map +1 -1
  73. package/src/plugins/package-json/create-nodes.js +38 -10
  74. package/src/project-graph/nx-deps-cache.js +1 -1
  75. package/src/daemon/server/logger.d.ts +0 -19
  76. package/src/daemon/server/logger.d.ts.map +0 -1
  77. 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
- let messenger;
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
- messenger = new daemon_socket_messenger_1.DaemonSocketMessenger((0, net_1.connect)(socketPath)).listen((message) => {
224
+ this.fileWatcherMessenger = new daemon_socket_messenger_1.DaemonSocketMessenger((0, net_1.connect)(socketPath)).listen((message) => {
192
225
  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);
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
- callback(e, null);
233
+ for (const cb of this.fileWatcherCallbacks.values()) {
234
+ cb(e, null);
235
+ }
200
236
  }
201
237
  }, () => {
202
- callback('closed', null);
203
- }, (err) => callback(err, null));
204
- return messenger.sendMessage({ type: 'REGISTER_FILE_WATCHER', config });
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
- messenger?.close();
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
- let messenger;
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
- messenger = new daemon_socket_messenger_1.DaemonSocketMessenger((0, net_1.connect)(socketPath)).listen((message) => {
378
+ this.projectGraphListenerMessenger = new daemon_socket_messenger_1.DaemonSocketMessenger((0, net_1.connect)(socketPath)).listen((message) => {
216
379
  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);
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
- callback(e, null);
387
+ for (const cb of this.projectGraphListenerCallbacks.values()) {
388
+ cb(e, null);
389
+ }
224
390
  }
225
391
  }, () => {
226
- callback('closed', null);
227
- }, (err) => callback(err, null));
228
- return messenger.sendMessage({
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
- messenger?.close();
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
- if (!(await this.isServerAvailable())) {
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
- 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
- });
739
+ // Connection closed while we had pending work - try to reconnect
454
740
  this._daemonStatus = DaemonStatus.DISCONNECTED;
455
- this.currentReject?.(daemonProcessException('Daemon process terminated and closed the connection'));
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
- return this.currentReject(error);
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
- 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
- }
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
- 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);
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'): 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"}