git-watchtower 1.12.1 → 1.12.2

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.
@@ -419,6 +419,10 @@ let worker = null;
419
419
  let projectId = null;
420
420
  let webStateInterval = null;
421
421
 
422
+ // Periodic update check controller — hoisted to module scope so the exit
423
+ // handler can clean it up regardless of where in start() we are.
424
+ let periodicUpdateCheck = null;
425
+
422
426
  function applyConfig(config) {
423
427
  // Server settings
424
428
  SERVER_MODE = config.server?.mode || 'static';
@@ -481,6 +485,17 @@ function openInBrowser(url) {
481
485
  });
482
486
  }
483
487
 
488
+ /**
489
+ * Build a localhost URL for the given port.
490
+ * Centralizes the `http://localhost:${port}` pattern so it's easy to adjust
491
+ * (e.g. switch protocol or host) in one place.
492
+ * @param {number} port
493
+ * @returns {string}
494
+ */
495
+ function localhostUrl(port) {
496
+ return `http://localhost:${port}`;
497
+ }
498
+
484
499
  // parseRemoteUrl, buildBranchUrl, detectPlatform, buildWebUrl, extractSessionUrl
485
500
  // imported from src/git/remote.js
486
501
 
@@ -2123,7 +2138,7 @@ function createStaticServer() {
2123
2138
  return;
2124
2139
  }
2125
2140
 
2126
- const url = new URL(req.url, `http://localhost:${PORT}`);
2141
+ const url = new URL(req.url, localhostUrl(PORT));
2127
2142
  let pathname = url.pathname;
2128
2143
  const logPath = pathname; // Keep original for logging
2129
2144
 
@@ -2773,7 +2788,7 @@ function setupKeyboardInput() {
2773
2788
 
2774
2789
  case 'o': // Open live server in browser
2775
2790
  if (!NO_SERVER) {
2776
- const serverUrl = `http://localhost:${PORT}`;
2791
+ const serverUrl = localhostUrl(PORT);
2777
2792
  addLog(`Opening ${serverUrl} in browser...`, 'info');
2778
2793
  openInBrowser(serverUrl);
2779
2794
  render();
@@ -3016,7 +3031,7 @@ async function handleWebAction(action, payload) {
3016
3031
  break;
3017
3032
  case 'openBrowser':
3018
3033
  if (!NO_SERVER) {
3019
- openInBrowser(`http://localhost:${PORT}`);
3034
+ openInBrowser(localhostUrl(PORT));
3020
3035
  sendResult(true, 'Opened in browser');
3021
3036
  }
3022
3037
  break;
@@ -3103,7 +3118,7 @@ async function startWebDashboard(openBrowser) {
3103
3118
  });
3104
3119
  worker.onCommand = (action, payload) => handleWebAction(action, payload);
3105
3120
  await worker.connect();
3106
- addLog(`Joined web dashboard at http://localhost:${existing.port} (tab)`, 'success');
3121
+ addLog(`Joined web dashboard at ${localhostUrl(existing.port)} (tab)`, 'success');
3107
3122
 
3108
3123
  // Push state periodically
3109
3124
  webStateInterval = setInterval(() => {
@@ -3154,8 +3169,8 @@ async function startWebDashboard(openBrowser) {
3154
3169
  WEB_PORT = port;
3155
3170
  writeLock(process.pid, port, coordinator.socketPath);
3156
3171
 
3157
- addLog(`Web dashboard: http://localhost:${port}`, 'success');
3158
- if (openBrowser) openInBrowser(`http://localhost:${port}`);
3172
+ addLog(`Web dashboard: ${localhostUrl(port)}`, 'success');
3173
+ if (openBrowser) openInBrowser(localhostUrl(port));
3159
3174
  render();
3160
3175
  } catch (err) {
3161
3176
  addLog(`Web dashboard failed: ${err.message}`, 'error');
@@ -3285,6 +3300,11 @@ async function shutdown() {
3285
3300
 
3286
3301
  process.on('SIGINT', shutdown);
3287
3302
  process.on('SIGTERM', shutdown);
3303
+ // Clean up long-lived timers on exit, regardless of which code path got us
3304
+ // here (normal exit, uncaught exception, early failure in start()).
3305
+ process.on('exit', () => {
3306
+ if (periodicUpdateCheck) periodicUpdateCheck.stop();
3307
+ });
3288
3308
  process.on('uncaughtException', async (err) => {
3289
3309
  telemetry.captureError(err);
3290
3310
  write(ansi.showCursor);
@@ -3413,11 +3433,11 @@ async function start() {
3413
3433
  // Static mode
3414
3434
  server = createStaticServer();
3415
3435
  server.listen(PORT, '127.0.0.1', () => {
3416
- addLog(`Server started on http://localhost:${PORT}`, 'success');
3436
+ addLog(`Server started on ${localhostUrl(PORT)}`, 'success');
3417
3437
  addLog(`Serving ${STATIC_DIR.replace(PROJECT_ROOT, '.')}`, 'info');
3418
3438
  addLog(`Current branch: ${store.get('currentBranch')}`, 'info');
3419
3439
  // Add server log entries for static server
3420
- addServerLog(`Static server started on http://localhost:${PORT}`);
3440
+ addServerLog(`Static server started on ${localhostUrl(PORT)}`);
3421
3441
  addServerLog(`Serving files from: ${STATIC_DIR.replace(PROJECT_ROOT, '.')}`);
3422
3442
  addServerLog(`Live reload enabled - waiting for browser connections...`);
3423
3443
  render();
@@ -3468,8 +3488,9 @@ async function start() {
3468
3488
  }
3469
3489
  }).catch(() => {});
3470
3490
 
3471
- // Re-check for updates periodically (every 4 hours) while running
3472
- const periodicCheck = startPeriodicUpdateCheck((latestVersion) => {
3491
+ // Re-check for updates periodically (every 4 hours) while running.
3492
+ // Assigned to module scope so the top-level exit handler can stop it.
3493
+ periodicUpdateCheck = startPeriodicUpdateCheck((latestVersion) => {
3473
3494
  const alreadyKnown = store.get('updateAvailable');
3474
3495
  store.setState({ updateAvailable: latestVersion });
3475
3496
  if (!alreadyKnown) {
@@ -3479,9 +3500,6 @@ async function start() {
3479
3500
  }
3480
3501
  render();
3481
3502
  });
3482
-
3483
- // Clean up periodic check on exit
3484
- process.on('exit', () => periodicCheck.stop());
3485
3503
  }
3486
3504
 
3487
3505
  start().catch(err => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-watchtower",
3
- "version": "1.12.1",
3
+ "version": "1.12.2",
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": {
package/src/cli/args.js CHANGED
@@ -175,7 +175,15 @@ function parseArgs(argv, options = {}) {
175
175
  * @returns {object} Merged config
176
176
  */
177
177
  function applyCliArgsToConfig(config, cliArgs) {
178
- const merged = JSON.parse(JSON.stringify(config)); // deep clone
178
+ // Shallow clone with nested object spreading — the config is at most two
179
+ // levels deep and all values are primitives, so this is equivalent to a deep
180
+ // clone but avoids the JSON round-trip (which silently drops `undefined`,
181
+ // functions, and throws on circular refs).
182
+ const merged = {
183
+ ...config,
184
+ server: { ...config.server },
185
+ web: { ...config.web },
186
+ };
179
187
 
180
188
  // Server settings
181
189
  if (cliArgs.mode !== null) {