git-watchtower 1.12.5 → 1.12.6
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/bin/git-watchtower.js +76 -31
- package/package.json +1 -1
package/bin/git-watchtower.js
CHANGED
|
@@ -3355,39 +3355,80 @@ function restartProcess() {
|
|
|
3355
3355
|
// ============================================================================
|
|
3356
3356
|
|
|
3357
3357
|
let isShuttingDown = false;
|
|
3358
|
+
let _resourcesCleaned = false;
|
|
3358
3359
|
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3360
|
+
/**
|
|
3361
|
+
* Idempotent, best-effort cleanup of every long-lived resource we own:
|
|
3362
|
+
* terminal state, timers, file watcher, live-reload SSE clients, the
|
|
3363
|
+
* user's dev-server child process, and the web-dashboard / coordinator
|
|
3364
|
+
* (which unlinks the lock file and IPC socket). Safe to call multiple
|
|
3365
|
+
* times and from any exit path (shutdown, uncaughtException, 'exit').
|
|
3366
|
+
*
|
|
3367
|
+
* Every step is wrapped in try/catch so a failure in one resource does
|
|
3368
|
+
* not prevent the rest from being cleaned up. Stays synchronous so it
|
|
3369
|
+
* can run inside an 'exit' handler where async callbacks won't execute.
|
|
3370
|
+
*/
|
|
3371
|
+
function cleanupResources() {
|
|
3372
|
+
if (_resourcesCleaned) return;
|
|
3373
|
+
_resourcesCleaned = true;
|
|
3374
|
+
|
|
3375
|
+
// Restore terminal first so the user sees a clean prompt even if a
|
|
3376
|
+
// later step throws.
|
|
3377
|
+
try { write(ansi.showCursor); } catch (_) { /* ignore */ }
|
|
3378
|
+
try { write(ansi.restoreScreen); } catch (_) { /* ignore */ }
|
|
3379
|
+
try { restoreTerminalTitle(); } catch (_) { /* ignore */ }
|
|
3380
|
+
try { if (process.stdin.isTTY) process.stdin.setRawMode(false); } catch (_) { /* ignore */ }
|
|
3381
|
+
try { process.stdin.pause(); } catch (_) { /* ignore */ }
|
|
3362
3382
|
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3383
|
+
if (pollIntervalId) {
|
|
3384
|
+
try { clearTimeout(pollIntervalId); } catch (_) { /* ignore */ }
|
|
3385
|
+
pollIntervalId = null;
|
|
3386
|
+
}
|
|
3367
3387
|
|
|
3368
|
-
if (
|
|
3369
|
-
|
|
3388
|
+
if (periodicUpdateCheck) {
|
|
3389
|
+
try { periodicUpdateCheck.stop(); } catch (_) { /* ignore */ }
|
|
3370
3390
|
}
|
|
3371
|
-
process.stdin.pause();
|
|
3372
3391
|
|
|
3373
|
-
if (fileWatcher)
|
|
3374
|
-
|
|
3392
|
+
if (fileWatcher) {
|
|
3393
|
+
try { fileWatcher.close(); } catch (_) { /* ignore */ }
|
|
3394
|
+
fileWatcher = null;
|
|
3395
|
+
}
|
|
3375
3396
|
|
|
3376
|
-
//
|
|
3397
|
+
// Live-reload SSE clients (static mode)
|
|
3398
|
+
if (SERVER_MODE === 'static') {
|
|
3399
|
+
try {
|
|
3400
|
+
clients.forEach((client) => {
|
|
3401
|
+
try { client.end(); } catch (_) { /* ignore */ }
|
|
3402
|
+
});
|
|
3403
|
+
clients.clear();
|
|
3404
|
+
} catch (_) { /* ignore */ }
|
|
3405
|
+
}
|
|
3406
|
+
|
|
3407
|
+
// User's dev-server process (command mode)
|
|
3377
3408
|
if (SERVER_MODE === 'command') {
|
|
3378
|
-
stopServerProcess();
|
|
3379
|
-
}
|
|
3380
|
-
|
|
3381
|
-
|
|
3409
|
+
try { stopServerProcess(); } catch (_) { /* ignore */ }
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
// Web dashboard + worker/coordinator (unlinks lock file + IPC socket)
|
|
3413
|
+
try { stopWebDashboard(); } catch (_) { /* ignore */ }
|
|
3414
|
+
}
|
|
3415
|
+
|
|
3416
|
+
async function shutdown() {
|
|
3417
|
+
if (isShuttingDown) return;
|
|
3418
|
+
isShuttingDown = true;
|
|
3419
|
+
|
|
3420
|
+
cleanupResources();
|
|
3382
3421
|
|
|
3383
|
-
|
|
3384
|
-
|
|
3422
|
+
// For the static HTTP server, give in-flight connections a brief
|
|
3423
|
+
// grace period to drain. cleanupResources ended the SSE clients; this
|
|
3424
|
+
// races server.close against SERVER_CLOSE_TIMEOUT_MS so we never hang
|
|
3425
|
+
// forever on a stuck browser.
|
|
3426
|
+
if (SERVER_MODE === 'static' && server) {
|
|
3427
|
+
const serverClosePromise = new Promise((resolve) => server.close(resolve));
|
|
3428
|
+
const timeoutPromise = new Promise((resolve) => setTimeout(resolve, SERVER_CLOSE_TIMEOUT_MS));
|
|
3385
3429
|
await Promise.race([serverClosePromise, timeoutPromise]);
|
|
3386
3430
|
}
|
|
3387
3431
|
|
|
3388
|
-
// Stop web dashboard and coordinator
|
|
3389
|
-
stopWebDashboard();
|
|
3390
|
-
|
|
3391
3432
|
// Flush telemetry
|
|
3392
3433
|
telemetry.capture('session_ended', {
|
|
3393
3434
|
duration_seconds: sessionStartTime ? Math.round((Date.now() - sessionStartTime) / 1000) : 0,
|
|
@@ -3402,19 +3443,23 @@ async function shutdown() {
|
|
|
3402
3443
|
|
|
3403
3444
|
process.on('SIGINT', shutdown);
|
|
3404
3445
|
process.on('SIGTERM', shutdown);
|
|
3405
|
-
//
|
|
3406
|
-
//
|
|
3446
|
+
// Belt-and-suspenders: if we exit via a path that didn't call
|
|
3447
|
+
// cleanupResources (e.g. a hard crash in startup before handlers were
|
|
3448
|
+
// registered), still do synchronous best-effort cleanup.
|
|
3407
3449
|
process.on('exit', () => {
|
|
3408
|
-
|
|
3450
|
+
cleanupResources();
|
|
3409
3451
|
});
|
|
3410
3452
|
process.on('uncaughtException', async (err) => {
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
if
|
|
3453
|
+
isShuttingDown = true;
|
|
3454
|
+
|
|
3455
|
+
// Synchronous teardown first — so the coordinator socket, lock file,
|
|
3456
|
+
// dev-server child process group, and SSE clients are released even
|
|
3457
|
+
// if telemetry shutdown hangs or throws.
|
|
3458
|
+
cleanupResources();
|
|
3459
|
+
|
|
3460
|
+
try { telemetry.captureError(err); } catch (_) { /* ignore */ }
|
|
3416
3461
|
console.error('Uncaught exception:', err);
|
|
3417
|
-
await telemetry.shutdown();
|
|
3462
|
+
try { await telemetry.shutdown(); } catch (_) { /* ignore */ }
|
|
3418
3463
|
process.exit(1);
|
|
3419
3464
|
});
|
|
3420
3465
|
|
package/package.json
CHANGED