git-watchtower 1.14.12 → 1.14.14
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 +22 -0
- package/package.json +1 -1
- package/src/casino/index.js +47 -3
- package/src/utils/pipe-error.js +43 -0
package/bin/git-watchtower.js
CHANGED
|
@@ -103,6 +103,7 @@ const store = new Store();
|
|
|
103
103
|
const { WebDashboardServer } = require('../src/server/web');
|
|
104
104
|
const { Coordinator, Worker, generateProjectId, getActiveCoordinator, tryAcquireLock, finalizeLock, removeLock, removeSocket, isProcessAlive } = require('../src/server/coordinator');
|
|
105
105
|
const monitorLock = require('../src/utils/monitor-lock');
|
|
106
|
+
const { createPipeErrorHandler } = require('../src/utils/pipe-error');
|
|
106
107
|
|
|
107
108
|
const PROJECT_ROOT = process.cwd();
|
|
108
109
|
|
|
@@ -3602,6 +3603,27 @@ process.on('SIGTERM', shutdown);
|
|
|
3602
3603
|
process.on('exit', () => {
|
|
3603
3604
|
cleanupResources();
|
|
3604
3605
|
});
|
|
3606
|
+
|
|
3607
|
+
// Defense-in-depth against a stdio pipe closing mid-run. The #13 TTY guard
|
|
3608
|
+
// stops `git-watchtower | head` at startup, but a TTY can still disappear
|
|
3609
|
+
// later (SSH drops, terminal window closes, pty tears down). Without this,
|
|
3610
|
+
// the next write() emits an async EPIPE which Node promotes to
|
|
3611
|
+
// uncaughtException — producing a crash report and telemetry noise for
|
|
3612
|
+
// what is a benign pipe-closed condition.
|
|
3613
|
+
const stdioPipeErrorHandler = createPipeErrorHandler({
|
|
3614
|
+
onEpipe: () => {
|
|
3615
|
+
isShuttingDown = true;
|
|
3616
|
+
try { cleanupResources(); } catch (_) { /* best-effort during pipe-close */ }
|
|
3617
|
+
process.exit(0);
|
|
3618
|
+
},
|
|
3619
|
+
onOther: (err) => {
|
|
3620
|
+
// Any other stdio error is unexpected — re-raise so the existing
|
|
3621
|
+
// uncaughtException handler can capture telemetry and restore terminal.
|
|
3622
|
+
setImmediate(() => { throw err; });
|
|
3623
|
+
},
|
|
3624
|
+
});
|
|
3625
|
+
process.stdout.on('error', stdioPipeErrorHandler);
|
|
3626
|
+
process.stderr.on('error', stdioPipeErrorHandler);
|
|
3605
3627
|
process.on('uncaughtException', async (err) => {
|
|
3606
3628
|
isShuttingDown = true;
|
|
3607
3629
|
|
package/package.json
CHANGED
package/src/casino/index.js
CHANGED
|
@@ -41,6 +41,7 @@ let slotResultFlashFrame = 0; // Flash animation frame
|
|
|
41
41
|
let slotResultInterval = null; // Interval for result display/flash
|
|
42
42
|
let slotResultRenderCallback = null; // Callback for re-rendering
|
|
43
43
|
let slotResultLabel = null; // "NOTHING", "WIN", "BIG WIN", "JACKPOT" etc
|
|
44
|
+
let slotResultClearTimeout = null; // No-win 2s auto-clear timer handle
|
|
44
45
|
|
|
45
46
|
// Win animation state
|
|
46
47
|
let winAnimationFrame = 0;
|
|
@@ -98,10 +99,42 @@ function enable() {
|
|
|
98
99
|
function disable() {
|
|
99
100
|
casinoEnabled = false;
|
|
100
101
|
stopMarquee();
|
|
101
|
-
stopSlotReels()
|
|
102
|
+
// resetSlotState — not stopSlotReels — because stopSlotReels() runs the
|
|
103
|
+
// end-of-poll result animation (labels a "NOTHING" panel and schedules a
|
|
104
|
+
// 2s auto-clear). Calling that during disable paints a brand-new casino
|
|
105
|
+
// effect on the way out, which the user just asked to stop.
|
|
106
|
+
resetSlotState();
|
|
102
107
|
stopWinAnimation();
|
|
103
108
|
}
|
|
104
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Clear all slot reel state and timers without scheduling any new effects.
|
|
112
|
+
* Safe to call from disable() — unlike stopSlotReels(), it does not kick
|
|
113
|
+
* off a result display or a delayed clear timer.
|
|
114
|
+
* @private
|
|
115
|
+
*/
|
|
116
|
+
function resetSlotState() {
|
|
117
|
+
if (slotReelInterval) {
|
|
118
|
+
clearInterval(slotReelInterval);
|
|
119
|
+
slotReelInterval = null;
|
|
120
|
+
}
|
|
121
|
+
if (slotResultInterval) {
|
|
122
|
+
clearInterval(slotResultInterval);
|
|
123
|
+
slotResultInterval = null;
|
|
124
|
+
}
|
|
125
|
+
if (slotResultClearTimeout) {
|
|
126
|
+
clearTimeout(slotResultClearTimeout);
|
|
127
|
+
slotResultClearTimeout = null;
|
|
128
|
+
}
|
|
129
|
+
isSpinning = false;
|
|
130
|
+
slotReelFrame = 0;
|
|
131
|
+
slotResult = null;
|
|
132
|
+
slotResultIsWin = false;
|
|
133
|
+
slotResultFlashFrame = 0;
|
|
134
|
+
slotResultLabel = null;
|
|
135
|
+
slotResultRenderCallback = null;
|
|
136
|
+
}
|
|
137
|
+
|
|
105
138
|
/**
|
|
106
139
|
* Toggle casino mode
|
|
107
140
|
* @returns {boolean} New state
|
|
@@ -312,8 +345,11 @@ function stopSlotReels(hadUpdates = false, renderCallback = null, winLevel = nul
|
|
|
312
345
|
slotResult.push(SLOT_SYMBOLS[idx]);
|
|
313
346
|
}
|
|
314
347
|
|
|
315
|
-
// Display for 2 seconds then fade
|
|
316
|
-
|
|
348
|
+
// Display for 2 seconds then fade. Store the handle so disable() /
|
|
349
|
+
// resetSlotState() can cancel it — otherwise a delayed clear fires
|
|
350
|
+
// mid-way through the next enabled session and nulls live state.
|
|
351
|
+
slotResultClearTimeout = setTimeout(() => {
|
|
352
|
+
slotResultClearTimeout = null;
|
|
317
353
|
slotResult = null;
|
|
318
354
|
slotResultLabel = null;
|
|
319
355
|
if (slotResultRenderCallback) slotResultRenderCallback();
|
|
@@ -326,6 +362,7 @@ function stopSlotReels(hadUpdates = false, renderCallback = null, winLevel = nul
|
|
|
326
362
|
* @returns {Object|null}
|
|
327
363
|
*/
|
|
328
364
|
function getSlotResultLabel() {
|
|
365
|
+
if (!casinoEnabled) return null;
|
|
329
366
|
return slotResultLabel;
|
|
330
367
|
}
|
|
331
368
|
|
|
@@ -334,6 +371,7 @@ function getSlotResultLabel() {
|
|
|
334
371
|
* @returns {boolean}
|
|
335
372
|
*/
|
|
336
373
|
function isSlotSpinning() {
|
|
374
|
+
if (!casinoEnabled) return false;
|
|
337
375
|
return isSpinning;
|
|
338
376
|
}
|
|
339
377
|
|
|
@@ -342,6 +380,7 @@ function isSlotSpinning() {
|
|
|
342
380
|
* @returns {boolean}
|
|
343
381
|
*/
|
|
344
382
|
function hasSlotResult() {
|
|
383
|
+
if (!casinoEnabled) return false;
|
|
345
384
|
return slotResult !== null;
|
|
346
385
|
}
|
|
347
386
|
|
|
@@ -350,6 +389,7 @@ function hasSlotResult() {
|
|
|
350
389
|
* @returns {boolean}
|
|
351
390
|
*/
|
|
352
391
|
function isSlotsActive() {
|
|
392
|
+
if (!casinoEnabled) return false;
|
|
353
393
|
return isSpinning || slotResult !== null;
|
|
354
394
|
}
|
|
355
395
|
|
|
@@ -358,6 +398,10 @@ function isSlotsActive() {
|
|
|
358
398
|
* @returns {string}
|
|
359
399
|
*/
|
|
360
400
|
function getSlotReelDisplay() {
|
|
401
|
+
// Defense-in-depth: even if stale state survived a disable/enable cycle,
|
|
402
|
+
// don't paint casino UI when the mode is off.
|
|
403
|
+
if (!casinoEnabled) return '';
|
|
404
|
+
|
|
361
405
|
// Show result if we have one
|
|
362
406
|
if (slotResult) {
|
|
363
407
|
const reels = [];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stdio pipe error handling.
|
|
3
|
+
*
|
|
4
|
+
* The TUI writes ANSI frames to stdout continuously. If the downstream
|
|
5
|
+
* consumer goes away — SSH drops, the terminal window closes, or the
|
|
6
|
+
* user intentionally short-circuits via `| head` on a non-guarded
|
|
7
|
+
* invocation — the next write() emits an async 'error' event with
|
|
8
|
+
* code EPIPE on the stream. Without a handler, Node promotes that to
|
|
9
|
+
* uncaughtException, which logs a crash report, generates telemetry
|
|
10
|
+
* noise, and clutters the user's prompt on exit.
|
|
11
|
+
*
|
|
12
|
+
* A normal pipe-closed condition is not a crash; the correct response
|
|
13
|
+
* is to clean up and exit quietly with status 0.
|
|
14
|
+
*
|
|
15
|
+
* @module utils/pipe-error
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
'use strict';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Build an 'error' handler for process.stdout / process.stderr.
|
|
22
|
+
*
|
|
23
|
+
* @param {Object} callbacks
|
|
24
|
+
* @param {() => void} callbacks.onEpipe - Invoked when the pipe is closed
|
|
25
|
+
* from the other end. Typically runs cleanupResources() and exits 0.
|
|
26
|
+
* @param {(err: Error) => void} callbacks.onOther - Invoked for any other
|
|
27
|
+
* stream error. Typically re-raises via setImmediate so the existing
|
|
28
|
+
* uncaughtException handler can capture telemetry.
|
|
29
|
+
* @returns {(err: Error & { code?: string }) => void}
|
|
30
|
+
*/
|
|
31
|
+
function createPipeErrorHandler({ onEpipe, onOther }) {
|
|
32
|
+
return function pipeErrorHandler(err) {
|
|
33
|
+
if (err && err.code === 'EPIPE') {
|
|
34
|
+
onEpipe();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
onOther(err);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
createPipeErrorHandler,
|
|
43
|
+
};
|