git-watchtower 1.11.10 → 1.12.1
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 +20 -6
- package/package.json +1 -1
- package/src/server/process.js +29 -4
- package/src/ui/ansi.js +72 -55
package/bin/git-watchtower.js
CHANGED
|
@@ -389,6 +389,20 @@ let AUTO_PULL = true;
|
|
|
389
389
|
const MAX_LOG_ENTRIES = 10;
|
|
390
390
|
const MAX_SERVER_LOG_LINES = 500;
|
|
391
391
|
|
|
392
|
+
// Timing constants (ms)
|
|
393
|
+
/** Grace period before SIGKILLing a process after SIGTERM. */
|
|
394
|
+
const FORCE_KILL_GRACE_MS = 3000;
|
|
395
|
+
/** Additional grace period added to a command's timeout before SIGKILL. */
|
|
396
|
+
const SIGKILL_GRACE_AFTER_TIMEOUT_MS = 5000;
|
|
397
|
+
/** Delay between stopping and restarting the dev server. */
|
|
398
|
+
const SERVER_RESTART_DELAY_MS = 500;
|
|
399
|
+
/** How long a transient flash message stays on screen. */
|
|
400
|
+
const FLASH_MESSAGE_DURATION_MS = 3000;
|
|
401
|
+
/** Debounce window for file watcher events before notifying clients. */
|
|
402
|
+
const FILE_WATCHER_DEBOUNCE_MS = 100;
|
|
403
|
+
/** Max time to wait for the static HTTP server to close on shutdown. */
|
|
404
|
+
const SERVER_CLOSE_TIMEOUT_MS = 2000;
|
|
405
|
+
|
|
392
406
|
// Telemetry session tracking
|
|
393
407
|
let branchSwitchCount = 0;
|
|
394
408
|
let sessionStartTime = null;
|
|
@@ -742,7 +756,7 @@ function stopServerProcess() {
|
|
|
742
756
|
} catch (e) {
|
|
743
757
|
// Process group may already be dead
|
|
744
758
|
}
|
|
745
|
-
},
|
|
759
|
+
}, FORCE_KILL_GRACE_MS);
|
|
746
760
|
|
|
747
761
|
// Clear the force-kill timer if the process exits cleanly
|
|
748
762
|
proc.once('close', () => {
|
|
@@ -760,7 +774,7 @@ function restartServerProcess() {
|
|
|
760
774
|
setTimeout(() => {
|
|
761
775
|
startServerProcess();
|
|
762
776
|
render();
|
|
763
|
-
},
|
|
777
|
+
}, SERVER_RESTART_DELAY_MS);
|
|
764
778
|
}
|
|
765
779
|
|
|
766
780
|
// Network and polling state
|
|
@@ -857,7 +871,7 @@ function execCli(cmd, args = [], options = {}) {
|
|
|
857
871
|
if (timeout > 0) {
|
|
858
872
|
const killTimer = setTimeout(() => {
|
|
859
873
|
try { child.kill('SIGKILL'); } catch (e) { /* already dead */ }
|
|
860
|
-
}, timeout +
|
|
874
|
+
}, timeout + SIGKILL_GRACE_AFTER_TIMEOUT_MS);
|
|
861
875
|
child.on('close', () => clearTimeout(killTimer));
|
|
862
876
|
}
|
|
863
877
|
});
|
|
@@ -1336,7 +1350,7 @@ function showFlash(message) {
|
|
|
1336
1350
|
flashTimeout = setTimeout(() => {
|
|
1337
1351
|
store.setState({ flashMessage: null });
|
|
1338
1352
|
render();
|
|
1339
|
-
},
|
|
1353
|
+
}, FLASH_MESSAGE_DURATION_MS);
|
|
1340
1354
|
}
|
|
1341
1355
|
|
|
1342
1356
|
function hideFlash() {
|
|
@@ -2194,7 +2208,7 @@ function setupFileWatcher() {
|
|
|
2194
2208
|
addLog(`File changed: ${filename}`, 'info');
|
|
2195
2209
|
notifyClients();
|
|
2196
2210
|
render();
|
|
2197
|
-
},
|
|
2211
|
+
}, FILE_WATCHER_DEBOUNCE_MS);
|
|
2198
2212
|
});
|
|
2199
2213
|
|
|
2200
2214
|
fileWatcher.on('error', (err) => {
|
|
@@ -3250,7 +3264,7 @@ async function shutdown() {
|
|
|
3250
3264
|
clients.clear();
|
|
3251
3265
|
|
|
3252
3266
|
const serverClosePromise = new Promise(resolve => server.close(resolve));
|
|
3253
|
-
const timeoutPromise = new Promise(resolve => setTimeout(resolve,
|
|
3267
|
+
const timeoutPromise = new Promise(resolve => setTimeout(resolve, SERVER_CLOSE_TIMEOUT_MS));
|
|
3254
3268
|
await Promise.race([serverClosePromise, timeoutPromise]);
|
|
3255
3269
|
}
|
|
3256
3270
|
|
package/package.json
CHANGED
package/src/server/process.js
CHANGED
|
@@ -31,8 +31,17 @@ const KILL_GRACE_PERIOD = 3000;
|
|
|
31
31
|
const RESTART_DELAY = 500;
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* Parse a command string into command and arguments
|
|
35
|
-
* Handles quoted strings
|
|
34
|
+
* Parse a command string into command and arguments.
|
|
35
|
+
* Handles quoted strings, backslash escapes (e.g. `\"`, `\\`, `\ `),
|
|
36
|
+
* and empty quoted arguments (`""`).
|
|
37
|
+
*
|
|
38
|
+
* Rules (POSIX-ish):
|
|
39
|
+
* - Inside single quotes, characters are literal — backslashes do NOT escape.
|
|
40
|
+
* - Inside double quotes or outside any quotes, a backslash causes the next
|
|
41
|
+
* character to be treated literally (so `\"` yields `"`, `\\` yields `\`,
|
|
42
|
+
* and `\ ` yields a literal space that doesn't split the argument).
|
|
43
|
+
* - A trailing backslash with no following character is left literal.
|
|
44
|
+
*
|
|
36
45
|
* @param {string} commandString - Command string to parse
|
|
37
46
|
* @returns {{command: string, args: string[]}}
|
|
38
47
|
*/
|
|
@@ -41,27 +50,43 @@ function parseCommand(commandString) {
|
|
|
41
50
|
let current = '';
|
|
42
51
|
let inQuotes = false;
|
|
43
52
|
let quoteChar = '';
|
|
53
|
+
// Tracks whether we've started accumulating an argument — distinguishes
|
|
54
|
+
// `""` (empty argument) from whitespace between arguments.
|
|
55
|
+
let hasCurrent = false;
|
|
44
56
|
|
|
45
57
|
for (let i = 0; i < commandString.length; i++) {
|
|
46
58
|
const char = commandString[i];
|
|
47
59
|
|
|
60
|
+
// Backslash escapes: unless we're inside single quotes, a backslash
|
|
61
|
+
// causes the next character to be treated literally. A trailing
|
|
62
|
+
// backslash (no following character) falls through and is kept literal.
|
|
63
|
+
if (char === '\\' && quoteChar !== "'" && i + 1 < commandString.length) {
|
|
64
|
+
current += commandString[i + 1];
|
|
65
|
+
hasCurrent = true;
|
|
66
|
+
i++;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
48
70
|
if ((char === '"' || char === "'") && !inQuotes) {
|
|
49
71
|
inQuotes = true;
|
|
50
72
|
quoteChar = char;
|
|
73
|
+
hasCurrent = true;
|
|
51
74
|
} else if (char === quoteChar && inQuotes) {
|
|
52
75
|
inQuotes = false;
|
|
53
76
|
quoteChar = '';
|
|
54
77
|
} else if (char === ' ' && !inQuotes) {
|
|
55
|
-
if (
|
|
78
|
+
if (hasCurrent) {
|
|
56
79
|
args.push(current);
|
|
57
80
|
current = '';
|
|
81
|
+
hasCurrent = false;
|
|
58
82
|
}
|
|
59
83
|
} else {
|
|
60
84
|
current += char;
|
|
85
|
+
hasCurrent = true;
|
|
61
86
|
}
|
|
62
87
|
}
|
|
63
88
|
|
|
64
|
-
if (
|
|
89
|
+
if (hasCurrent) {
|
|
65
90
|
args.push(current);
|
|
66
91
|
}
|
|
67
92
|
|
package/src/ui/ansi.js
CHANGED
|
@@ -7,6 +7,22 @@
|
|
|
7
7
|
const ESC = '\x1b';
|
|
8
8
|
const CSI = `${ESC}[`;
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Whether color output is enabled.
|
|
12
|
+
* Honors the NO_COLOR convention (https://no-color.org/) and TERM=dumb.
|
|
13
|
+
* Structural codes (cursor movement, screen control) are still emitted
|
|
14
|
+
* so the TUI layout remains functional — only color/style codes are stripped.
|
|
15
|
+
*/
|
|
16
|
+
const colorsEnabled = !(
|
|
17
|
+
(process.env.NO_COLOR && process.env.NO_COLOR !== '') ||
|
|
18
|
+
process.env.TERM === 'dumb'
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
/** Empty string for disabled color codes, or the given code when enabled. */
|
|
22
|
+
const c = (code) => (colorsEnabled ? code : '');
|
|
23
|
+
/** Empty-returning function for disabled color functions. */
|
|
24
|
+
const cFn = (fn) => (colorsEnabled ? fn : () => '');
|
|
25
|
+
|
|
10
26
|
/**
|
|
11
27
|
* ANSI escape codes for terminal control and styling
|
|
12
28
|
*/
|
|
@@ -44,81 +60,81 @@ const ansi = {
|
|
|
44
60
|
restoreCursor: `${CSI}u`,
|
|
45
61
|
|
|
46
62
|
// Text styles
|
|
47
|
-
reset: `${CSI}0m
|
|
48
|
-
bold: `${CSI}1m
|
|
49
|
-
dim: `${CSI}2m
|
|
50
|
-
italic: `${CSI}3m
|
|
51
|
-
underline: `${CSI}4m
|
|
52
|
-
blink: `${CSI}5m
|
|
53
|
-
inverse: `${CSI}7m
|
|
54
|
-
hidden: `${CSI}8m
|
|
55
|
-
strikethrough: `${CSI}9m
|
|
63
|
+
reset: c(`${CSI}0m`),
|
|
64
|
+
bold: c(`${CSI}1m`),
|
|
65
|
+
dim: c(`${CSI}2m`),
|
|
66
|
+
italic: c(`${CSI}3m`),
|
|
67
|
+
underline: c(`${CSI}4m`),
|
|
68
|
+
blink: c(`${CSI}5m`),
|
|
69
|
+
inverse: c(`${CSI}7m`),
|
|
70
|
+
hidden: c(`${CSI}8m`),
|
|
71
|
+
strikethrough: c(`${CSI}9m`),
|
|
56
72
|
|
|
57
73
|
// Reset specific styles
|
|
58
|
-
resetBold: `${CSI}22m
|
|
59
|
-
resetDim: `${CSI}22m
|
|
60
|
-
resetItalic: `${CSI}23m
|
|
61
|
-
resetUnderline: `${CSI}24m
|
|
62
|
-
resetBlink: `${CSI}25m
|
|
63
|
-
resetInverse: `${CSI}27m
|
|
64
|
-
resetHidden: `${CSI}28m
|
|
65
|
-
resetStrikethrough: `${CSI}29m
|
|
74
|
+
resetBold: c(`${CSI}22m`),
|
|
75
|
+
resetDim: c(`${CSI}22m`),
|
|
76
|
+
resetItalic: c(`${CSI}23m`),
|
|
77
|
+
resetUnderline: c(`${CSI}24m`),
|
|
78
|
+
resetBlink: c(`${CSI}25m`),
|
|
79
|
+
resetInverse: c(`${CSI}27m`),
|
|
80
|
+
resetHidden: c(`${CSI}28m`),
|
|
81
|
+
resetStrikethrough: c(`${CSI}29m`),
|
|
66
82
|
|
|
67
83
|
// Foreground colors (standard)
|
|
68
|
-
black: `${CSI}30m
|
|
69
|
-
red: `${CSI}31m
|
|
70
|
-
green: `${CSI}32m
|
|
71
|
-
yellow: `${CSI}33m
|
|
72
|
-
blue: `${CSI}34m
|
|
73
|
-
magenta: `${CSI}35m
|
|
74
|
-
cyan: `${CSI}36m
|
|
75
|
-
white: `${CSI}37m
|
|
76
|
-
default: `${CSI}39m
|
|
84
|
+
black: c(`${CSI}30m`),
|
|
85
|
+
red: c(`${CSI}31m`),
|
|
86
|
+
green: c(`${CSI}32m`),
|
|
87
|
+
yellow: c(`${CSI}33m`),
|
|
88
|
+
blue: c(`${CSI}34m`),
|
|
89
|
+
magenta: c(`${CSI}35m`),
|
|
90
|
+
cyan: c(`${CSI}36m`),
|
|
91
|
+
white: c(`${CSI}37m`),
|
|
92
|
+
default: c(`${CSI}39m`),
|
|
77
93
|
|
|
78
94
|
// Foreground colors (bright)
|
|
79
|
-
gray: `${CSI}90m
|
|
80
|
-
brightRed: `${CSI}91m
|
|
81
|
-
brightGreen: `${CSI}92m
|
|
82
|
-
brightYellow: `${CSI}93m
|
|
83
|
-
brightBlue: `${CSI}94m
|
|
84
|
-
brightMagenta: `${CSI}95m
|
|
85
|
-
brightCyan: `${CSI}96m
|
|
86
|
-
brightWhite: `${CSI}97m
|
|
95
|
+
gray: c(`${CSI}90m`),
|
|
96
|
+
brightRed: c(`${CSI}91m`),
|
|
97
|
+
brightGreen: c(`${CSI}92m`),
|
|
98
|
+
brightYellow: c(`${CSI}93m`),
|
|
99
|
+
brightBlue: c(`${CSI}94m`),
|
|
100
|
+
brightMagenta: c(`${CSI}95m`),
|
|
101
|
+
brightCyan: c(`${CSI}96m`),
|
|
102
|
+
brightWhite: c(`${CSI}97m`),
|
|
87
103
|
|
|
88
104
|
// Background colors (standard)
|
|
89
|
-
bgBlack: `${CSI}40m
|
|
90
|
-
bgRed: `${CSI}41m
|
|
91
|
-
bgGreen: `${CSI}42m
|
|
92
|
-
bgYellow: `${CSI}43m
|
|
93
|
-
bgBlue: `${CSI}44m
|
|
94
|
-
bgMagenta: `${CSI}45m
|
|
95
|
-
bgCyan: `${CSI}46m
|
|
96
|
-
bgWhite: `${CSI}47m
|
|
97
|
-
bgDefault: `${CSI}49m
|
|
105
|
+
bgBlack: c(`${CSI}40m`),
|
|
106
|
+
bgRed: c(`${CSI}41m`),
|
|
107
|
+
bgGreen: c(`${CSI}42m`),
|
|
108
|
+
bgYellow: c(`${CSI}43m`),
|
|
109
|
+
bgBlue: c(`${CSI}44m`),
|
|
110
|
+
bgMagenta: c(`${CSI}45m`),
|
|
111
|
+
bgCyan: c(`${CSI}46m`),
|
|
112
|
+
bgWhite: c(`${CSI}47m`),
|
|
113
|
+
bgDefault: c(`${CSI}49m`),
|
|
98
114
|
|
|
99
115
|
// Background colors (bright)
|
|
100
|
-
bgGray: `${CSI}100m
|
|
101
|
-
bgBrightRed: `${CSI}101m
|
|
102
|
-
bgBrightGreen: `${CSI}102m
|
|
103
|
-
bgBrightYellow: `${CSI}103m
|
|
104
|
-
bgBrightBlue: `${CSI}104m
|
|
105
|
-
bgBrightMagenta: `${CSI}105m
|
|
106
|
-
bgBrightCyan: `${CSI}106m
|
|
107
|
-
bgBrightWhite: `${CSI}107m
|
|
116
|
+
bgGray: c(`${CSI}100m`),
|
|
117
|
+
bgBrightRed: c(`${CSI}101m`),
|
|
118
|
+
bgBrightGreen: c(`${CSI}102m`),
|
|
119
|
+
bgBrightYellow: c(`${CSI}103m`),
|
|
120
|
+
bgBrightBlue: c(`${CSI}104m`),
|
|
121
|
+
bgBrightMagenta: c(`${CSI}105m`),
|
|
122
|
+
bgBrightCyan: c(`${CSI}106m`),
|
|
123
|
+
bgBrightWhite: c(`${CSI}107m`),
|
|
108
124
|
|
|
109
125
|
/**
|
|
110
126
|
* Set foreground color using 256-color palette
|
|
111
127
|
* @param {number} n - Color number (0-255)
|
|
112
128
|
* @returns {string}
|
|
113
129
|
*/
|
|
114
|
-
fg256: (n) => `${CSI}38;5;${n}m
|
|
130
|
+
fg256: cFn((n) => `${CSI}38;5;${n}m`),
|
|
115
131
|
|
|
116
132
|
/**
|
|
117
133
|
* Set background color using 256-color palette
|
|
118
134
|
* @param {number} n - Color number (0-255)
|
|
119
135
|
* @returns {string}
|
|
120
136
|
*/
|
|
121
|
-
bg256: (n) => `${CSI}48;5;${n}m
|
|
137
|
+
bg256: cFn((n) => `${CSI}48;5;${n}m`),
|
|
122
138
|
|
|
123
139
|
/**
|
|
124
140
|
* Set foreground color using RGB
|
|
@@ -127,7 +143,7 @@ const ansi = {
|
|
|
127
143
|
* @param {number} b - Blue (0-255)
|
|
128
144
|
* @returns {string}
|
|
129
145
|
*/
|
|
130
|
-
fgRgb: (r, g, b) => `${CSI}38;2;${r};${g};${b}m
|
|
146
|
+
fgRgb: cFn((r, g, b) => `${CSI}38;2;${r};${g};${b}m`),
|
|
131
147
|
|
|
132
148
|
/**
|
|
133
149
|
* Set background color using RGB
|
|
@@ -136,7 +152,7 @@ const ansi = {
|
|
|
136
152
|
* @param {number} b - Blue (0-255)
|
|
137
153
|
* @returns {string}
|
|
138
154
|
*/
|
|
139
|
-
bgRgb: (r, g, b) => `${CSI}48;2;${r};${g};${b}m
|
|
155
|
+
bgRgb: cFn((r, g, b) => `${CSI}48;2;${r};${g};${b}m`),
|
|
140
156
|
};
|
|
141
157
|
|
|
142
158
|
/**
|
|
@@ -492,6 +508,7 @@ module.exports = {
|
|
|
492
508
|
wordWrap,
|
|
493
509
|
horizontalLine,
|
|
494
510
|
style,
|
|
511
|
+
colorsEnabled,
|
|
495
512
|
// Export constants for direct use
|
|
496
513
|
ESC,
|
|
497
514
|
CSI,
|