git-watchtower 1.14.1 → 1.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-watchtower",
3
- "version": "1.14.1",
3
+ "version": "1.14.2",
4
4
  "description": "Terminal-based Git branch monitor with activity sparklines and optional dev server with live reload",
5
5
  "main": "bin/git-watchtower.js",
6
6
  "bin": {
@@ -265,38 +265,13 @@ class ProcessManager {
265
265
  return false;
266
266
  }
267
267
 
268
- // Capture reference before nulling — needed for deferred SIGKILL
268
+ // Capture reference before nulling — needed for deferred force-kill
269
269
  const proc = this.process;
270
270
 
271
- // Try graceful shutdown first
272
271
  if (process.platform === 'win32') {
273
- try {
274
- spawn('taskkill', ['/pid', proc.pid.toString(), '/f', '/t']);
275
- } catch (e) {
276
- // Ignore taskkill errors
277
- }
272
+ this._stopWindows(proc);
278
273
  } else {
279
- try {
280
- // Kill the entire process group (negative PID) so that
281
- // grandchildren (e.g. npm -> node -> vite) are also terminated.
282
- process.kill(-proc.pid, 'SIGTERM');
283
-
284
- // Force kill after grace period
285
- const forceKillTimeout = setTimeout(() => {
286
- try {
287
- process.kill(-proc.pid, 'SIGKILL');
288
- } catch (e) {
289
- // Process group may already be dead
290
- }
291
- }, KILL_GRACE_PERIOD);
292
-
293
- // Clear timeout if process exits cleanly
294
- proc.once('close', () => {
295
- clearTimeout(forceKillTimeout);
296
- });
297
- } catch (e) {
298
- // Process group may already be dead
299
- }
274
+ this._stopUnix(proc);
300
275
  }
301
276
 
302
277
  this.process = null;
@@ -305,6 +280,77 @@ class ProcessManager {
305
280
  return true;
306
281
  }
307
282
 
283
+ /**
284
+ * Unix stop: SIGTERM the process group, then SIGKILL after a grace period.
285
+ * The grace timer is unref'd so it doesn't keep the event loop alive when
286
+ * the main process wants to exit.
287
+ * @param {import('child_process').ChildProcess} proc
288
+ * @private
289
+ */
290
+ _stopUnix(proc) {
291
+ // If the process has already exited, there's nothing to signal.
292
+ if (proc.exitCode !== null || proc.signalCode !== null) return;
293
+
294
+ try {
295
+ process.kill(-proc.pid, 'SIGTERM');
296
+ } catch (e) {
297
+ // Process group may already be dead
298
+ return;
299
+ }
300
+
301
+ const forceKillTimeout = setTimeout(() => {
302
+ // Re-check: process may have exited during the grace period.
303
+ if (proc.exitCode !== null || proc.signalCode !== null) return;
304
+ try {
305
+ process.kill(-proc.pid, 'SIGKILL');
306
+ } catch (e) {
307
+ // Process group may already be dead
308
+ }
309
+ }, KILL_GRACE_PERIOD);
310
+
311
+ // Don't let this timer keep the event loop alive on shutdown.
312
+ forceKillTimeout.unref();
313
+
314
+ // Clear early if the process exits before the grace period.
315
+ proc.once('close', () => {
316
+ clearTimeout(forceKillTimeout);
317
+ });
318
+ }
319
+
320
+ /**
321
+ * Windows stop: taskkill /t (tree kill). If the process doesn't exit
322
+ * within the grace period, retry with /f (force).
323
+ * @param {import('child_process').ChildProcess} proc
324
+ * @private
325
+ */
326
+ _stopWindows(proc) {
327
+ if (proc.exitCode !== null || proc.signalCode !== null) return;
328
+
329
+ try {
330
+ spawn('taskkill', ['/pid', proc.pid.toString(), '/t']);
331
+ } catch (e) {
332
+ // Ignore spawn errors (PID already gone, etc.)
333
+ return;
334
+ }
335
+
336
+ // Fallback: force-kill if the process is still alive after the
337
+ // grace period. This mirrors the Unix SIGTERM → SIGKILL pattern.
338
+ const forceKillTimeout = setTimeout(() => {
339
+ if (proc.exitCode !== null || proc.signalCode !== null) return;
340
+ try {
341
+ spawn('taskkill', ['/pid', proc.pid.toString(), '/f', '/t']);
342
+ } catch (e) {
343
+ // Ignore — process may already be dead
344
+ }
345
+ }, KILL_GRACE_PERIOD);
346
+
347
+ forceKillTimeout.unref();
348
+
349
+ proc.once('close', () => {
350
+ clearTimeout(forceKillTimeout);
351
+ });
352
+ }
353
+
308
354
  /**
309
355
  * Restart the server process
310
356
  * @returns {Promise<{success: boolean, error?: Error, pid?: number}>}