@teambit/watcher 1.0.830 → 1.0.832

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.
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_net","data","_interopRequireDefault","require","_fsExtra","_path","e","__esModule","default","_defineProperty","r","t","_toPropertyKey","Object","defineProperty","value","enumerable","configurable","writable","i","_toPrimitive","Symbol","toPrimitive","call","TypeError","String","Number","SOCKET_FILENAME","LOCK_FILENAME","HEARTBEAT_INTERVAL_MS","CONNECTION_TIMEOUT_MS","WatcherDaemon","constructor","scopePath","logger","Set","socketPath","path","join","lockPath","isRunning","fs","pathExists","lockContent","readFile","pid","JSON","parse","parseErr","debug","message","remove","process","kill","canConnect","tryConnect","Promise","resolve","socket","net","createConnection","timeout","setTimeout","destroy","on","clearTimeout","start","outputFile","stringify","startTime","Date","now","server","createServer","handleConnection","reject","Error","err","error","listen","startHeartbeat","clients","add","size","sendToClient","type","delete","broadcast","client","write","broadcastEvents","events","broadcastError","isDropError","heartbeatInterval","setInterval","timestamp","clientCount","stop","isShuttingDown","clearInterval","clear","close","exports","WatcherClient","connect","isConnected","handleData","disconnectHandler","buffer","toString","lines","split","pop","line","trim","handleMessage","eventsHandler","errorHandler","readyHandler","onEvents","handler","onError","onReady","onDisconnect","connected","disconnect","getOrCreateWatcherConnection","retryCount","MAX_RETRIES","isDaemon","jitter","Math","random","daemon"],"sources":["watcher-daemon.ts"],"sourcesContent":["import net from 'net';\nimport fs from 'fs-extra';\nimport path from 'path';\nimport type { Event } from '@parcel/watcher';\nimport type { Logger } from '@teambit/logger';\n\nconst SOCKET_FILENAME = 'watcher.sock';\nconst LOCK_FILENAME = 'watcher.lock';\nconst HEARTBEAT_INTERVAL_MS = 5000;\nconst CONNECTION_TIMEOUT_MS = 3000;\n\nexport type WatcherEvent = {\n type: 'events';\n events: Event[];\n};\n\nexport type WatcherError = {\n type: 'error';\n message: string;\n isDropError?: boolean;\n};\n\nexport type WatcherHeartbeat = {\n type: 'heartbeat';\n timestamp: number;\n};\n\nexport type WatcherReady = {\n type: 'ready';\n};\n\nexport type WatcherMessage = WatcherEvent | WatcherError | WatcherHeartbeat | WatcherReady;\n\n/**\n * WatcherDaemon is the server-side of the shared watcher infrastructure.\n * It runs a Unix domain socket server and broadcasts file system events to all connected clients.\n *\n * Only ONE daemon can run per workspace. The daemon is responsible for:\n * 1. Subscribing to Parcel Watcher for file system events\n * 2. Broadcasting events to all connected clients\n * 3. Sending heartbeats to clients so they know the daemon is alive\n * 4. Cleaning up resources on shutdown\n */\nexport class WatcherDaemon {\n private server: net.Server | null = null;\n private clients: Set<net.Socket> = new Set();\n private heartbeatInterval: NodeJS.Timeout | null = null;\n private isShuttingDown = false;\n\n constructor(\n private scopePath: string,\n private logger: Logger\n ) {}\n\n get socketPath(): string {\n return path.join(this.scopePath, SOCKET_FILENAME);\n }\n\n get lockPath(): string {\n return path.join(this.scopePath, LOCK_FILENAME);\n }\n\n /**\n * Check if a daemon is already running for this workspace\n */\n static async isRunning(scopePath: string, logger?: Logger): Promise<boolean> {\n const lockPath = path.join(scopePath, LOCK_FILENAME);\n const socketPath = path.join(scopePath, SOCKET_FILENAME);\n\n // Check if lock file exists\n if (!(await fs.pathExists(lockPath))) {\n return false;\n }\n\n // Check if the PID in lock file is still alive\n try {\n const lockContent = await fs.readFile(lockPath, 'utf8');\n let pid: number;\n try {\n ({ pid } = JSON.parse(lockContent));\n } catch (parseErr: any) {\n // Malformed JSON in lock file - treat as stale and clean up\n logger?.debug(`Malformed lock file, cleaning up: ${parseErr.message}`);\n await fs.remove(lockPath);\n await fs.remove(socketPath);\n return false;\n }\n\n // Check if process is running\n try {\n process.kill(pid, 0); // Signal 0 doesn't kill, just checks if process exists\n } catch {\n // Process doesn't exist, clean up stale lock\n await fs.remove(lockPath);\n await fs.remove(socketPath);\n return false;\n }\n\n // Process exists, try to connect to verify it's actually the daemon\n const canConnect = await WatcherDaemon.tryConnect(socketPath);\n if (!canConnect) {\n // Lock file exists but socket doesn't respond - stale lock\n await fs.remove(lockPath);\n await fs.remove(socketPath);\n return false;\n }\n\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Try to connect to an existing daemon socket\n */\n private static tryConnect(socketPath: string): Promise<boolean> {\n return new Promise((resolve) => {\n const socket = net.createConnection(socketPath);\n const timeout = setTimeout(() => {\n socket.destroy();\n resolve(false);\n }, CONNECTION_TIMEOUT_MS);\n\n socket.on('connect', () => {\n clearTimeout(timeout);\n socket.destroy();\n resolve(true);\n });\n\n socket.on('error', () => {\n clearTimeout(timeout);\n socket.destroy();\n resolve(false);\n });\n });\n }\n\n /**\n * Start the daemon server\n */\n async start(): Promise<void> {\n // Remove any stale socket file\n await fs.remove(this.socketPath);\n\n // Create lock file with our PID\n await fs.outputFile(\n this.lockPath,\n JSON.stringify({\n pid: process.pid,\n startTime: Date.now(),\n })\n );\n\n // Create Unix domain socket server\n this.server = net.createServer((socket) => this.handleConnection(socket));\n\n return new Promise((resolve, reject) => {\n if (!this.server) {\n reject(new Error('Server not initialized'));\n return;\n }\n\n this.server.on('error', (err) => {\n this.logger.error(`Watcher daemon server error: ${err.message}`);\n reject(err);\n });\n\n this.server.listen(this.socketPath, () => {\n this.logger.debug(`Watcher daemon started on ${this.socketPath}`);\n this.startHeartbeat();\n resolve();\n });\n });\n }\n\n /**\n * Handle a new client connection\n */\n private handleConnection(socket: net.Socket): void {\n this.clients.add(socket);\n this.logger.debug(`Watcher daemon: client connected (${this.clients.size} total)`);\n\n // Send ready message to new client\n this.sendToClient(socket, { type: 'ready' });\n\n socket.on('close', () => {\n this.clients.delete(socket);\n this.logger.debug(`Watcher daemon: client disconnected (${this.clients.size} total)`);\n });\n\n socket.on('error', (err) => {\n this.logger.debug(`Watcher daemon: client error - ${err.message}`);\n this.clients.delete(socket);\n });\n }\n\n /**\n * Broadcast events to all connected clients\n */\n broadcast(message: WatcherMessage): void {\n const data = JSON.stringify(message) + '\\n';\n\n for (const client of this.clients) {\n try {\n client.write(data);\n } catch (err: any) {\n this.logger.debug(`Failed to send to client: ${err.message}`);\n this.clients.delete(client);\n }\n }\n }\n\n /**\n * Send message to a specific client\n */\n private sendToClient(socket: net.Socket, message: WatcherMessage): void {\n try {\n socket.write(JSON.stringify(message) + '\\n');\n } catch (err: any) {\n this.logger.debug(`Failed to send to client: ${err.message}`);\n }\n }\n\n /**\n * Broadcast file system events from Parcel watcher\n */\n broadcastEvents(events: Event[]): void {\n this.broadcast({ type: 'events', events });\n }\n\n /**\n * Broadcast an error to all clients\n */\n broadcastError(message: string, isDropError = false): void {\n this.broadcast({ type: 'error', message, isDropError });\n }\n\n /**\n * Start sending heartbeats to clients\n */\n private startHeartbeat(): void {\n this.heartbeatInterval = setInterval(() => {\n this.broadcast({ type: 'heartbeat', timestamp: Date.now() });\n }, HEARTBEAT_INTERVAL_MS);\n }\n\n /**\n * Get the number of connected clients\n */\n get clientCount(): number {\n return this.clients.size;\n }\n\n /**\n * Stop the daemon and cleanup\n */\n async stop(): Promise<void> {\n if (this.isShuttingDown) {\n return;\n }\n this.isShuttingDown = true;\n\n this.logger.debug('Watcher daemon stopping...');\n\n if (this.heartbeatInterval) {\n clearInterval(this.heartbeatInterval);\n this.heartbeatInterval = null;\n }\n\n // Close all client connections\n for (const client of this.clients) {\n client.destroy();\n }\n this.clients.clear();\n\n // Close server\n if (this.server) {\n await new Promise<void>((resolve) => {\n this.server!.close(() => resolve());\n });\n this.server = null;\n }\n\n // Remove lock and socket files\n await fs.remove(this.lockPath);\n await fs.remove(this.socketPath);\n\n this.logger.debug('Watcher daemon stopped');\n }\n}\n\n/**\n * WatcherClient connects to an existing WatcherDaemon to receive file system events.\n *\n * Usage:\n * ```typescript\n * const client = new WatcherClient(scopePath, logger);\n * await client.connect();\n * client.onEvents((events) => { ... });\n * client.onError((err) => { ... });\n * ```\n */\nexport class WatcherClient {\n private socket: net.Socket | null = null;\n private eventsHandler: ((events: Event[]) => void) | null = null;\n private errorHandler: ((error: WatcherError) => void) | null = null;\n private readyHandler: (() => void) | null = null;\n private disconnectHandler: (() => void) | null = null;\n private buffer = '';\n private isConnected = false;\n\n constructor(\n private scopePath: string,\n private logger: Logger\n ) {}\n\n get socketPath(): string {\n return path.join(this.scopePath, SOCKET_FILENAME);\n }\n\n /**\n * Connect to the daemon\n */\n connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n this.socket = net.createConnection(this.socketPath);\n\n const timeout = setTimeout(() => {\n this.socket?.destroy();\n reject(new Error('Connection timeout'));\n }, CONNECTION_TIMEOUT_MS);\n\n this.socket.on('connect', () => {\n clearTimeout(timeout);\n this.isConnected = true;\n this.logger.debug('Watcher client connected to daemon');\n resolve();\n });\n\n this.socket.on('data', (data) => {\n this.handleData(data);\n });\n\n this.socket.on('close', () => {\n this.isConnected = false;\n this.logger.debug('Watcher client disconnected from daemon');\n this.disconnectHandler?.();\n });\n\n this.socket.on('error', (err) => {\n clearTimeout(timeout);\n this.socket?.destroy();\n this.isConnected = false;\n this.logger.debug(`Watcher client error: ${err.message}`);\n reject(err);\n });\n });\n }\n\n /**\n * Handle incoming data from the daemon\n */\n private handleData(data: Buffer): void {\n this.buffer += data.toString('utf8');\n\n // Process complete messages (newline-delimited JSON)\n const lines = this.buffer.split('\\n');\n this.buffer = lines.pop() || ''; // Keep incomplete line in buffer\n\n for (const line of lines) {\n if (!line.trim()) continue;\n\n try {\n const message: WatcherMessage = JSON.parse(line);\n this.handleMessage(message);\n } catch (err: any) {\n this.logger.debug(`Failed to parse message: ${err.message}`);\n }\n }\n }\n\n /**\n * Handle a parsed message from the daemon\n */\n private handleMessage(message: WatcherMessage): void {\n switch (message.type) {\n case 'events':\n this.eventsHandler?.(message.events);\n break;\n case 'error':\n this.errorHandler?.(message);\n break;\n case 'ready':\n this.readyHandler?.();\n break;\n case 'heartbeat':\n // Heartbeats serve to keep the TCP connection alive and allow the OS to detect\n // a dead daemon faster. The client doesn't need to actively monitor them since\n // the socket 'close' event will fire when the daemon dies.\n break;\n }\n }\n\n /**\n * Register handler for file system events\n */\n onEvents(handler: (events: Event[]) => void): void {\n this.eventsHandler = handler;\n }\n\n /**\n * Register handler for errors\n */\n onError(handler: (error: WatcherError) => void): void {\n this.errorHandler = handler;\n }\n\n /**\n * Register handler for ready signal\n */\n onReady(handler: () => void): void {\n this.readyHandler = handler;\n }\n\n /**\n * Register handler for disconnection\n */\n onDisconnect(handler: () => void): void {\n this.disconnectHandler = handler;\n }\n\n /**\n * Check if connected to daemon\n */\n get connected(): boolean {\n return this.isConnected;\n }\n\n /**\n * Disconnect from the daemon\n */\n disconnect(): void {\n if (this.socket) {\n this.socket.destroy();\n this.socket = null;\n }\n this.isConnected = false;\n }\n}\n\n/**\n * Get or create a watcher connection for the given workspace.\n * Returns either a daemon (if we're the first) or a client (if daemon exists).\n *\n * Uses retry with random jitter to handle race conditions when multiple processes\n * try to become the daemon simultaneously.\n */\nexport async function getOrCreateWatcherConnection(\n scopePath: string,\n logger: Logger,\n retryCount = 0\n): Promise<{ isDaemon: boolean; daemon?: WatcherDaemon; client?: WatcherClient }> {\n const MAX_RETRIES = 3;\n\n // Check if daemon is already running\n const isRunning = await WatcherDaemon.isRunning(scopePath, logger);\n\n if (isRunning) {\n // Connect as client\n const client = new WatcherClient(scopePath, logger);\n try {\n await client.connect();\n return { isDaemon: false, client };\n } catch (err: any) {\n // Connection failed - daemon might have just died, retry\n if (retryCount < MAX_RETRIES) {\n // Add random jitter (100-500ms) to reduce thundering herd\n const jitter = 100 + Math.random() * 400;\n await new Promise((resolve) => setTimeout(resolve, jitter));\n return getOrCreateWatcherConnection(scopePath, logger, retryCount + 1);\n }\n throw err;\n }\n }\n\n // Try to become the daemon\n const daemon = new WatcherDaemon(scopePath, logger);\n try {\n await daemon.start();\n return { isDaemon: true, daemon };\n } catch (err: any) {\n // Failed to start daemon - another process might have beaten us, retry as client\n if (retryCount < MAX_RETRIES) {\n // Add random jitter to reduce thundering herd\n const jitter = 100 + Math.random() * 400;\n await new Promise((resolve) => setTimeout(resolve, jitter));\n return getOrCreateWatcherConnection(scopePath, logger, retryCount + 1);\n }\n throw err;\n }\n}\n"],"mappings":"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","ignoreList":[]}
package/dist/watcher.d.ts CHANGED
@@ -57,11 +57,51 @@ export declare class Watcher {
57
57
  private dropErrorDebounceTimer;
58
58
  private dropErrorCount;
59
59
  private isRecoveringFromSnapshot;
60
+ private watcherDaemon;
61
+ private watcherClient;
62
+ private isDaemon;
63
+ private parcelSubscription;
64
+ private signalCleanupHandler;
60
65
  constructor(workspace: Workspace, pubsub: PubsubMain, watcherMain: WatcherMain, options: WatchOptions, msgs?: EventMessages | undefined);
61
66
  get consumer(): Consumer;
62
67
  private getParcelIgnorePatterns;
63
68
  watch(): Promise<void>;
64
69
  private watchParcel;
70
+ /**
71
+ * Start Parcel watcher as the daemon - broadcast events to all clients
72
+ */
73
+ private startParcelWatcherAsDaemon;
74
+ /**
75
+ * Handle Parcel watcher events when running as daemon
76
+ */
77
+ private onParcelWatchAsDaemon;
78
+ /**
79
+ * Start as a client receiving events from the daemon
80
+ */
81
+ private startAsClient;
82
+ /**
83
+ * Handle disconnection from the daemon - try to become the new daemon or reconnect
84
+ */
85
+ private handleDaemonDisconnection;
86
+ /**
87
+ * Remove any existing signal handlers to prevent accumulation.
88
+ * This is important when transitioning between client and daemon modes.
89
+ */
90
+ private removeSignalHandlers;
91
+ /**
92
+ * Setup graceful shutdown handlers for daemon mode.
93
+ * When SIGINT/SIGTERM is received, we need to:
94
+ * 1. Stop the daemon (cleanup socket, notify clients)
95
+ * 2. Call process.exit() to actually terminate
96
+ *
97
+ * Important: Once you add a handler for SIGINT, Node.js no longer exits automatically.
98
+ * You must call process.exit() explicitly.
99
+ */
100
+ private setupDaemonShutdown;
101
+ /**
102
+ * Setup graceful shutdown handlers for client mode.
103
+ */
104
+ private setupClientShutdown;
65
105
  private watchChokidar;
66
106
  /**
67
107
  * *** DEBOUNCING ***
package/dist/watcher.js CHANGED
@@ -102,6 +102,13 @@ function _harmonyModules() {
102
102
  };
103
103
  return data;
104
104
  }
105
+ function _watcherDaemon() {
106
+ const data = require("./watcher-daemon");
107
+ _watcherDaemon = function () {
108
+ return data;
109
+ };
110
+ return data;
111
+ }
105
112
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
106
113
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
107
114
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
@@ -136,6 +143,14 @@ class Watcher {
136
143
  _defineProperty(this, "dropErrorDebounceTimer", null);
137
144
  _defineProperty(this, "dropErrorCount", 0);
138
145
  _defineProperty(this, "isRecoveringFromSnapshot", false);
146
+ // Shared watcher daemon/client
147
+ _defineProperty(this, "watcherDaemon", null);
148
+ _defineProperty(this, "watcherClient", null);
149
+ _defineProperty(this, "isDaemon", false);
150
+ // Parcel watcher subscription for cleanup
151
+ _defineProperty(this, "parcelSubscription", null);
152
+ // Signal handlers for cleanup (to avoid accumulation)
153
+ _defineProperty(this, "signalCleanupHandler", null);
139
154
  this.ipcEventsDir = this.watcherMain.ipcEvents.eventsDir;
140
155
  this.verbose = this.options.verbose || false;
141
156
  this.logger = this.watcherMain.logger;
@@ -160,6 +175,40 @@ class Watcher {
160
175
  }
161
176
  async watchParcel() {
162
177
  this.msgs?.onStart(this.workspace);
178
+
179
+ // Use shared watcher daemon pattern to avoid FSEvents limit on macOS
180
+ // FSEvents has a system-wide limit on concurrent watchers, which causes
181
+ // "Error starting FSEvents stream" when multiple bit commands run watchers.
182
+ // This is only an issue on macOS - other platforms don't have this limitation.
183
+ const isMacOS = process.platform === 'darwin';
184
+ const isSharedDisabled = process.env.BIT_WATCHER_NO_SHARED === 'true' || process.env.BIT_WATCHER_NO_SHARED === '1';
185
+ const useSharedWatcher = isMacOS && !isSharedDisabled;
186
+ if (useSharedWatcher) {
187
+ try {
188
+ const connection = await (0, _watcherDaemon().getOrCreateWatcherConnection)(this.workspace.scope.path, this.logger);
189
+ if (connection.isDaemon && connection.daemon) {
190
+ // We're the daemon - run the actual Parcel watcher
191
+ this.isDaemon = true;
192
+ this.watcherDaemon = connection.daemon;
193
+ this.logger.debug('Started as watcher daemon');
194
+ await this.startParcelWatcherAsDaemon();
195
+ } else if (connection.client) {
196
+ // We're a client - receive events from the daemon
197
+ this.isDaemon = false;
198
+ this.watcherClient = connection.client;
199
+ this.logger.debug('Connected to existing watcher daemon');
200
+ await this.startAsClient();
201
+ }
202
+ this.msgs?.onReady(this.workspace, this.rootDirs, this.verbose);
203
+ this.logger.clearStatusLine();
204
+ return;
205
+ } catch (err) {
206
+ // If shared watcher setup fails, fall back to direct Parcel watcher
207
+ this.logger.debug(`Shared watcher setup failed, falling back to direct watcher: ${err.message}`);
208
+ }
209
+ }
210
+
211
+ // Original direct Parcel watcher logic (fallback)
163
212
  try {
164
213
  await _watcher().default.subscribe(this.workspace.path, this.onParcelWatch.bind(this), {
165
214
  ignore: this.getParcelIgnorePatterns()
@@ -180,10 +229,194 @@ Similarly, if you're using VSCode with the Bit extension, you can enable "Compil
180
229
  If the issue persists, please refer to the Watchman troubleshooting guide:
181
230
  https://facebook.github.io/watchman/docs/troubleshooting#fseventstreamstart-register_with_server-error-f2d_register_rpc--null--21`);
182
231
  }
232
+ throw err;
183
233
  }
184
234
  this.msgs?.onReady(this.workspace, this.rootDirs, this.verbose);
185
235
  this.logger.clearStatusLine();
186
236
  }
237
+
238
+ /**
239
+ * Start Parcel watcher as the daemon - broadcast events to all clients
240
+ */
241
+ async startParcelWatcherAsDaemon() {
242
+ try {
243
+ // Clean up existing subscription if any (e.g., when transitioning from client to daemon)
244
+ if (this.parcelSubscription) {
245
+ await this.parcelSubscription.unsubscribe();
246
+ this.parcelSubscription = null;
247
+ }
248
+ this.parcelSubscription = await _watcher().default.subscribe(this.workspace.path, this.onParcelWatchAsDaemon.bind(this), {
249
+ ignore: this.getParcelIgnorePatterns()
250
+ });
251
+
252
+ // Write initial snapshot for FSEvents buffer overflow recovery
253
+ await this.writeSnapshotIfNeeded();
254
+ this.logger.debug('Initial watcher snapshot created (daemon mode)');
255
+
256
+ // Setup graceful shutdown
257
+ this.setupDaemonShutdown();
258
+ } catch (err) {
259
+ // Clean up daemon on failure
260
+ await this.watcherDaemon?.stop();
261
+ throw err;
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Handle Parcel watcher events when running as daemon
267
+ */
268
+ async onParcelWatchAsDaemon(err, allEvents) {
269
+ const events = allEvents.filter(event => !this.shouldIgnoreFromLocalScopeParcel(event.path));
270
+
271
+ // Broadcast events to all clients
272
+ if (events.length > 0) {
273
+ this.watcherDaemon?.broadcastEvents(events);
274
+ }
275
+
276
+ // Also broadcast errors
277
+ if (err) {
278
+ const isDropError = err.message.includes('Events were dropped');
279
+ this.watcherDaemon?.broadcastError(err.message, isDropError);
280
+ }
281
+
282
+ // Process events locally (the daemon is also a watcher)
283
+ await this.onParcelWatch(err, allEvents);
284
+ }
285
+
286
+ /**
287
+ * Start as a client receiving events from the daemon
288
+ */
289
+ async startAsClient() {
290
+ if (!this.watcherClient) {
291
+ throw new Error('Watcher client not initialized');
292
+ }
293
+
294
+ // Handle events from the daemon
295
+ this.watcherClient.onEvents(async events => {
296
+ const filteredEvents = events.filter(event => !this.shouldIgnoreFromLocalScopeParcel(event.path));
297
+ if (filteredEvents.length > 0) {
298
+ const startTime = Date.now();
299
+ await this.processEvents(filteredEvents, startTime);
300
+ }
301
+ });
302
+
303
+ // Handle errors from the daemon
304
+ this.watcherClient.onError(async error => {
305
+ if (error.isDropError) {
306
+ // The daemon will handle recovery, but we should be aware
307
+ this.logger.debug('Daemon reported FSEvents buffer overflow');
308
+ } else {
309
+ this.msgs?.onError(new Error(error.message));
310
+ }
311
+ });
312
+
313
+ // Handle disconnection from the daemon
314
+ this.watcherClient.onDisconnect(async () => {
315
+ this.logger.debug('Disconnected from watcher daemon');
316
+
317
+ // Try to become the new daemon or reconnect
318
+ await this.handleDaemonDisconnection();
319
+ });
320
+
321
+ // Setup graceful shutdown
322
+ this.setupClientShutdown();
323
+ }
324
+
325
+ /**
326
+ * Handle disconnection from the daemon - try to become the new daemon or reconnect
327
+ */
328
+ async handleDaemonDisconnection() {
329
+ // Wait a bit for any other client to potentially become the daemon
330
+ await this.sleep(500);
331
+ try {
332
+ const connection = await (0, _watcherDaemon().getOrCreateWatcherConnection)(this.workspace.scope.path, this.logger);
333
+ if (connection.isDaemon && connection.daemon) {
334
+ // We became the new daemon
335
+ this.isDaemon = true;
336
+ this.watcherDaemon = connection.daemon;
337
+ this.watcherClient = null;
338
+ this.logger.console(_chalk().default.yellow('Previous watcher daemon disconnected. This process is now the watcher daemon.'));
339
+ await this.startParcelWatcherAsDaemon();
340
+ } else if (connection.client) {
341
+ // Another process became the daemon, connect to it
342
+ this.watcherClient = connection.client;
343
+ this.logger.debug('Reconnected to new watcher daemon');
344
+ await this.startAsClient();
345
+ }
346
+ } catch (err) {
347
+ this.logger.error(`Failed to reconnect after daemon disconnection: ${err.message}`);
348
+ this.msgs?.onError(err);
349
+ }
350
+ }
351
+
352
+ /**
353
+ * Remove any existing signal handlers to prevent accumulation.
354
+ * This is important when transitioning between client and daemon modes.
355
+ */
356
+ removeSignalHandlers() {
357
+ if (this.signalCleanupHandler) {
358
+ process.off('SIGINT', this.signalCleanupHandler);
359
+ process.off('SIGTERM', this.signalCleanupHandler);
360
+ this.signalCleanupHandler = null;
361
+ }
362
+ }
363
+
364
+ /**
365
+ * Setup graceful shutdown handlers for daemon mode.
366
+ * When SIGINT/SIGTERM is received, we need to:
367
+ * 1. Stop the daemon (cleanup socket, notify clients)
368
+ * 2. Call process.exit() to actually terminate
369
+ *
370
+ * Important: Once you add a handler for SIGINT, Node.js no longer exits automatically.
371
+ * You must call process.exit() explicitly.
372
+ */
373
+ setupDaemonShutdown() {
374
+ // Remove old handlers to prevent accumulation when transitioning modes
375
+ this.removeSignalHandlers();
376
+ let isShuttingDown = false;
377
+ const cleanup = () => {
378
+ if (isShuttingDown) return;
379
+ isShuttingDown = true;
380
+ this.logger.debug('Daemon shutting down...');
381
+ // Unsubscribe from Parcel watcher
382
+ this.parcelSubscription?.unsubscribe().catch(err => {
383
+ this.logger.debug(`Error unsubscribing from Parcel watcher: ${err.message}`);
384
+ });
385
+ // Stop is async but we need to exit - start the cleanup and exit
386
+ // The socket will be cleaned up by the OS when the process exits
387
+ this.watcherDaemon?.stop().catch(err => {
388
+ this.logger.error(`Error stopping daemon: ${err.message}`);
389
+ }).finally(() => {
390
+ process.exit(0);
391
+ });
392
+
393
+ // Fallback: if stop() hangs, force exit after 1 second
394
+ setTimeout(() => {
395
+ process.exit(0);
396
+ }, 1000).unref();
397
+ };
398
+ this.signalCleanupHandler = cleanup;
399
+ process.on('SIGINT', cleanup);
400
+ process.on('SIGTERM', cleanup);
401
+ }
402
+
403
+ /**
404
+ * Setup graceful shutdown handlers for client mode.
405
+ */
406
+ setupClientShutdown() {
407
+ // Remove old handlers to prevent accumulation when transitioning modes
408
+ this.removeSignalHandlers();
409
+ let isShuttingDown = false;
410
+ const cleanup = () => {
411
+ if (isShuttingDown) return;
412
+ isShuttingDown = true;
413
+ this.watcherClient?.disconnect();
414
+ process.exit(0);
415
+ };
416
+ this.signalCleanupHandler = cleanup;
417
+ process.on('SIGINT', cleanup);
418
+ process.on('SIGTERM', cleanup);
419
+ }
187
420
  async watchChokidar() {
188
421
  await this.createChokidarWatcher();
189
422
  const watcher = this.chokidarWatcher;