@teambit/watcher 1.0.831 → 1.0.833
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/dist/index.d.ts +2 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -1
- package/dist/watcher-daemon.d.ts +156 -0
- package/dist/watcher-daemon.js +498 -0
- package/dist/watcher-daemon.js.map +1 -0
- package/dist/watcher.d.ts +40 -0
- package/dist/watcher.js +233 -0
- package/dist/watcher.js.map +1 -1
- package/package.json +8 -8
- /package/dist/{preview-1764692648851.js → preview-1764708308174.js} +0 -0
|
@@ -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;
|