git-watchtower 1.10.19 → 1.11.0

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.
@@ -2531,7 +2531,7 @@ function setupKeyboardInput() {
2531
2531
  if (key === '\r' || key === '\n') {
2532
2532
  const selectedIdx = store.get('updateModalSelectedIndex') || 0;
2533
2533
  if (selectedIdx === 0) {
2534
- // Update now — run npm i -g git-watchtower
2534
+ // Update & restart — run npm i -g git-watchtower, then re-exec
2535
2535
  store.setState({ updateInProgress: true });
2536
2536
  render();
2537
2537
  const { spawn } = require('child_process');
@@ -2544,8 +2544,8 @@ function setupKeyboardInput() {
2544
2544
  store.setState({ updateInProgress: false, updateModalVisible: false, updateModalSelectedIndex: 0 });
2545
2545
  if (code === 0) {
2546
2546
  store.setState({ updateAvailable: null });
2547
- addLog('Successfully updated git-watchtower! Restart to use new version.', 'update');
2548
- showFlash('Updated! Restart to use new version.');
2547
+ addLog('Successfully updated git-watchtower! Restarting...', 'update');
2548
+ restartProcess();
2549
2549
  } else {
2550
2550
  addLog(`Update failed (exit code ${code}). Run manually: npm i -g git-watchtower`, 'error');
2551
2551
  showFlash('Update failed. Try manually: npm i -g git-watchtower');
@@ -2987,6 +2987,36 @@ async function handleWebAction(action, payload) {
2987
2987
  }
2988
2988
  }
2989
2989
  break;
2990
+ case 'checkUpdate':
2991
+ if (payload && payload.install) {
2992
+ store.setState({ updateInProgress: true });
2993
+ render();
2994
+ const { spawn: spawnUpdate } = require('child_process');
2995
+ const updateChild = spawnUpdate('npm', ['i', '-g', 'git-watchtower'], {
2996
+ stdio: 'ignore',
2997
+ detached: false,
2998
+ });
2999
+ updateChild.on('close', (code) => {
3000
+ store.setState({ updateInProgress: false });
3001
+ if (code === 0) {
3002
+ store.setState({ updateAvailable: null });
3003
+ sendResult(true, 'Updated! Restarting...');
3004
+ addLog('Successfully updated git-watchtower! Restarting...', 'update');
3005
+ restartProcess();
3006
+ } else {
3007
+ sendResult(false, `Update failed (exit code ${code})`);
3008
+ addLog(`Update failed (exit code ${code}). Run manually: npm i -g git-watchtower`, 'error');
3009
+ render();
3010
+ }
3011
+ });
3012
+ updateChild.on('error', (err2) => {
3013
+ store.setState({ updateInProgress: false });
3014
+ sendResult(false, err2.message);
3015
+ addLog(`Update failed: ${err2.message}`, 'error');
3016
+ render();
3017
+ });
3018
+ }
3019
+ break;
2990
3020
  }
2991
3021
  } catch (err) {
2992
3022
  addLog(`Web action error: ${err.message}`, 'error');
@@ -3121,6 +3151,47 @@ function stopWebDashboard() {
3121
3151
  return wasPort;
3122
3152
  }
3123
3153
 
3154
+ // ============================================================================
3155
+ // Restart after update
3156
+ // ============================================================================
3157
+
3158
+ /**
3159
+ * Restart the process after a successful update by re-execing with the same
3160
+ * arguments. Cleans up terminal state and spawns a replacement process,
3161
+ * then exits the current one.
3162
+ */
3163
+ function restartProcess() {
3164
+ // Restore terminal state
3165
+ write(ansi.showCursor);
3166
+ write(ansi.restoreScreen);
3167
+ restoreTerminalTitle();
3168
+ if (process.stdin.isTTY) process.stdin.setRawMode(false);
3169
+
3170
+ // Stop server, watcher, polling
3171
+ if (fileWatcher) fileWatcher.close();
3172
+ if (pollIntervalId) clearTimeout(pollIntervalId);
3173
+ if (SERVER_MODE === 'command') stopServerProcess();
3174
+ else if (SERVER_MODE === 'static') {
3175
+ clients.forEach(client => client.end());
3176
+ clients.clear();
3177
+ }
3178
+ stopWebDashboard();
3179
+
3180
+ console.log('\n♻ Restarting git-watchtower...\n');
3181
+
3182
+ const { spawn: spawnChild } = require('child_process');
3183
+ const child = spawnChild(process.argv[0], process.argv.slice(1), {
3184
+ stdio: 'inherit',
3185
+ detached: false,
3186
+ });
3187
+ child.on('error', () => {
3188
+ console.error('Failed to restart. Please run git-watchtower manually.');
3189
+ process.exit(1);
3190
+ });
3191
+ // Forward the child's exit code when it finishes
3192
+ child.on('close', (code) => process.exit(code || 0));
3193
+ }
3194
+
3124
3195
  // ============================================================================
3125
3196
  // Shutdown
3126
3197
  // ============================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-watchtower",
3
- "version": "1.10.19",
3
+ "version": "1.11.0",
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": {
@@ -5,6 +5,7 @@
5
5
 
6
6
  const { spawn } = require('child_process');
7
7
  const { ServerError } = require('../utils/errors');
8
+ const { Mutex } = require('../utils/async');
8
9
 
9
10
  /**
10
11
  * @typedef {Object} ServerProcessState
@@ -90,6 +91,7 @@ class ProcessManager {
90
91
  this.crashed = false;
91
92
  this.logs = [];
92
93
  this.command = '';
94
+ this._restartMutex = new Mutex();
93
95
  }
94
96
 
95
97
  /**
@@ -283,13 +285,15 @@ class ProcessManager {
283
285
  * @returns {Promise<{success: boolean, error?: Error, pid?: number}>}
284
286
  */
285
287
  async restart() {
286
- const command = this.command;
287
- this.stop();
288
+ return this._restartMutex.withLock(async () => {
289
+ const command = this.command;
290
+ this.stop();
288
291
 
289
- // Wait before restarting
290
- await new Promise((resolve) => setTimeout(resolve, RESTART_DELAY));
292
+ // Wait before restarting
293
+ await new Promise((resolve) => setTimeout(resolve, RESTART_DELAY));
291
294
 
292
- return this.start(command);
295
+ return this.start(command);
296
+ });
293
297
  }
294
298
 
295
299
  /**
@@ -968,7 +968,7 @@ ${pureFnBlock}
968
968
  } else {
969
969
  html += '<div class="confirm-actions">';
970
970
  html += '<button class="confirm-btn" id="update-dismiss">Dismiss</button>';
971
- html += '<button class="confirm-btn primary" id="update-install">Update Now</button>';
971
+ html += '<button class="confirm-btn primary" id="update-install">Update &amp; Restart</button>';
972
972
  html += '</div>';
973
973
  }
974
974
  document.getElementById('update-content').innerHTML = html;
@@ -1485,7 +1485,7 @@ function renderUpdateModal(state, write) {
1485
1485
  const updateCmd = 'npm i -g git-watchtower';
1486
1486
 
1487
1487
  const options = [
1488
- 'Update now',
1488
+ 'Update & restart',
1489
1489
  'Show update command',
1490
1490
  ];
1491
1491
  const selectedIdx = state.updateModalSelectedIndex || 0;