git-watchtower 1.14.11 → 1.14.13
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 +36 -0
- package/package.json +1 -1
- 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
|
|
|
@@ -3644,6 +3666,20 @@ process.on('unhandledRejection', async (reason) => {
|
|
|
3644
3666
|
// ============================================================================
|
|
3645
3667
|
|
|
3646
3668
|
async function start() {
|
|
3669
|
+
// git-watchtower is a full-screen TUI. If stdout isn't a TTY (piped to a
|
|
3670
|
+
// file, `tee`, `head`, or captured by a non-interactive CI runner) every
|
|
3671
|
+
// frame writes hideCursor/clearScreen/moveTo/color escapes into the pipe,
|
|
3672
|
+
// producing an unreadable log and wasting CPU on a render loop no human
|
|
3673
|
+
// will see. Refuse to start and point the user at a sensible alternative.
|
|
3674
|
+
if (!process.stdout.isTTY) {
|
|
3675
|
+
console.error('git-watchtower: stdout is not a TTY.');
|
|
3676
|
+
console.error('');
|
|
3677
|
+
console.error(' This is an interactive terminal UI and cannot render when stdout is');
|
|
3678
|
+
console.error(' piped or redirected. If you only need a CI-friendly status check,');
|
|
3679
|
+
console.error(' use `git fetch` + `git log` directly.');
|
|
3680
|
+
process.exit(1);
|
|
3681
|
+
}
|
|
3682
|
+
|
|
3647
3683
|
// Check if git is available
|
|
3648
3684
|
const gitAvailable = await checkGitAvailable();
|
|
3649
3685
|
if (!gitAvailable) {
|
package/package.json
CHANGED
|
@@ -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
|
+
};
|