git-watchtower 1.14.2 → 1.14.4

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.
@@ -654,7 +654,7 @@ async function loadAsyncActionData(branch, currentData) {
654
654
  try {
655
655
  const host = new URL(env.webUrlBase).hostname;
656
656
  webUrl = buildBranchUrl(env.webUrlBase, host, branch.name);
657
- } catch (e) { /* ignore */ }
657
+ } catch (e) { /* invalid webUrlBase — leave webUrl null, modal hides the link */ }
658
658
  }
659
659
 
660
660
  // Fetch session URL (local git, fast but async)
@@ -1963,7 +1963,7 @@ async function pollGitChanges() {
1963
1963
  }
1964
1964
 
1965
1965
  // Background ahead/behind fetch for visible branches
1966
- fetchAheadBehindForBranches(pollFilteredBranches).catch(() => {});
1966
+ fetchAheadBehindForBranches(pollFilteredBranches).catch(() => { /* transient git/network error — next poll will retry */ });
1967
1967
 
1968
1968
  // AUTO-PULL: If current branch has remote updates, pull automatically (if enabled)
1969
1969
  const autoPullBranchName = store.get('currentBranch');
@@ -2207,7 +2207,12 @@ function createStaticServer() {
2207
2207
  let realStaticDir;
2208
2208
  try {
2209
2209
  realStaticDir = fs.realpathSync(resolvedStaticDir);
2210
- } catch {
2210
+ } catch (e) {
2211
+ // STATIC_DIR comes from our own package layout, so a realpath failure
2212
+ // means the install is broken (missing dir, permissions, etc.) — worth
2213
+ // diagnosing. Fall back to the unresolved path so the request still
2214
+ // gets its 403 rather than crashing.
2215
+ telemetry.captureError(e);
2211
2216
  realStaticDir = resolvedStaticDir;
2212
2217
  }
2213
2218
  if (!resolvedPath.startsWith(realStaticDir + path.sep) && resolvedPath !== realStaticDir) {
@@ -2447,7 +2452,7 @@ function setupKeyboardInput() {
2447
2452
  store.setState({ actionData: fullData, actionLoading: false });
2448
2453
  render();
2449
2454
  }
2450
- }).catch(() => {});
2455
+ }).catch(() => { /* PR was created; modal refresh is a nice-to-have, user can reopen */ });
2451
2456
  } catch (e) {
2452
2457
  const msg = (e && e.stderr) || (e && e.message) || String(e);
2453
2458
  addLog(`Failed to create ${prLabel}: ${msg.split('\n')[0]}`, 'error');
@@ -2930,7 +2935,7 @@ function setupKeyboardInput() {
2930
2935
  } else {
2931
2936
  startWebDashboard(true).then(() => {
2932
2937
  showFlash(`Web dashboard on :${WEB_PORT}`);
2933
- }).catch(() => {});
2938
+ }).catch(() => { /* startWebDashboard surfaces its own errors via addLog/showErrorToast */ });
2934
2939
  }
2935
2940
  break;
2936
2941
  }
@@ -3190,7 +3195,7 @@ async function startWebDashboard(openBrowser) {
3190
3195
  // Resolve and cache the repo web URL for link building in the web UI
3191
3196
  getRemoteWebUrl(null).then((url) => {
3192
3197
  if (url) webDashboard.setRepoWebUrl(url);
3193
- }).catch(() => {});
3198
+ }).catch(() => { /* no remote or unreachable — web UI falls back to branch names without links */ });
3194
3199
 
3195
3200
  // Atomically try to claim the coordinator role. If another live instance
3196
3201
  // already owns the lock, connect as a worker instead. This prevents a
@@ -3317,10 +3322,10 @@ async function startWebDashboard(openBrowser) {
3317
3322
  webStateInterval = null;
3318
3323
  }
3319
3324
  if (webDashboard) {
3320
- try { webDashboard.stop(); } catch (_) { /* ignore */ }
3325
+ try { webDashboard.stop(); } catch (_) { /* web server may not have bound yet — nothing to stop */ }
3321
3326
  }
3322
3327
  if (coordinator) {
3323
- try { coordinator.stop(); } catch (_) { /* ignore */ }
3328
+ try { coordinator.stop(); } catch (_) { /* coordinator may not have started its IPC server */ }
3324
3329
  }
3325
3330
  removeLock();
3326
3331
  removeSocket();
@@ -3387,7 +3392,7 @@ function restartProcess() {
3387
3392
  // child can acquire it. The parent stays alive waiting on child.on('close'),
3388
3393
  // so without this the child sees the parent as an active owner and refuses.
3389
3394
  if (monitorLockFile) {
3390
- try { monitorLock.release(monitorLockFile); } catch (_) { /* ignore */ }
3395
+ try { monitorLock.release(monitorLockFile); } catch (_) { /* lock file may have already been unlinked */ }
3391
3396
  monitorLockFile = null;
3392
3397
  }
3393
3398
 
@@ -3433,23 +3438,23 @@ function cleanupResources() {
3433
3438
 
3434
3439
  // Restore terminal first so the user sees a clean prompt even if a
3435
3440
  // later step throws.
3436
- try { write(ansi.showCursor); } catch (_) { /* ignore */ }
3437
- try { write(ansi.restoreScreen); } catch (_) { /* ignore */ }
3438
- try { restoreTerminalTitle(); } catch (_) { /* ignore */ }
3439
- try { if (process.stdin.isTTY) process.stdin.setRawMode(false); } catch (_) { /* ignore */ }
3440
- try { process.stdin.pause(); } catch (_) { /* ignore */ }
3441
+ try { write(ansi.showCursor); } catch (_) { /* stdout may be closed during crash cleanup */ }
3442
+ try { write(ansi.restoreScreen); } catch (_) { /* stdout may be closed during crash cleanup */ }
3443
+ try { restoreTerminalTitle(); } catch (_) { /* stdout may be closed during crash cleanup */ }
3444
+ try { if (process.stdin.isTTY) process.stdin.setRawMode(false); } catch (_) { /* stdin may already be unraw or detached */ }
3445
+ try { process.stdin.pause(); } catch (_) { /* stdin may already be paused or destroyed */ }
3441
3446
 
3442
3447
  if (pollIntervalId) {
3443
- try { clearTimeout(pollIntervalId); } catch (_) { /* ignore */ }
3448
+ try { clearTimeout(pollIntervalId); } catch (_) { /* defensive — clearTimeout normally won't throw */ }
3444
3449
  pollIntervalId = null;
3445
3450
  }
3446
3451
 
3447
3452
  if (periodicUpdateCheck) {
3448
- try { periodicUpdateCheck.stop(); } catch (_) { /* ignore */ }
3453
+ try { periodicUpdateCheck.stop(); } catch (_) { /* interval handle may already be cleared */ }
3449
3454
  }
3450
3455
 
3451
3456
  if (fileWatcher) {
3452
- try { fileWatcher.close(); } catch (_) { /* ignore */ }
3457
+ try { fileWatcher.close(); } catch (_) { /* watcher may already be closed by OS or previous cleanup */ }
3453
3458
  fileWatcher = null;
3454
3459
  }
3455
3460
 
@@ -3457,24 +3462,24 @@ function cleanupResources() {
3457
3462
  if (SERVER_MODE === 'static') {
3458
3463
  try {
3459
3464
  clients.forEach((client) => {
3460
- try { client.end(); } catch (_) { /* ignore */ }
3465
+ try { client.end(); } catch (_) { /* SSE client socket already closed */ }
3461
3466
  });
3462
3467
  clients.clear();
3463
- } catch (_) { /* ignore */ }
3468
+ } catch (_) { /* clients set may have mutated mid-iteration during shutdown */ }
3464
3469
  }
3465
3470
 
3466
3471
  // User's dev-server process (command mode)
3467
3472
  if (SERVER_MODE === 'command') {
3468
- try { stopServerProcess(); } catch (_) { /* ignore */ }
3473
+ try { stopServerProcess(); } catch (_) { /* dev-server child may already be gone */ }
3469
3474
  }
3470
3475
 
3471
3476
  // Web dashboard + worker/coordinator (unlinks lock file + IPC socket)
3472
- try { stopWebDashboard(); } catch (_) { /* ignore */ }
3477
+ try { stopWebDashboard(); } catch (_) { /* web dashboard may never have been started */ }
3473
3478
 
3474
3479
  // Per-repo monitor lock — release last so the slot stays reserved for the
3475
3480
  // entire lifetime of this process, including any errors in the steps above.
3476
3481
  if (monitorLockFile) {
3477
- try { monitorLock.release(monitorLockFile); } catch (_) { /* ignore */ }
3482
+ try { monitorLock.release(monitorLockFile); } catch (_) { /* lock file may have been unlinked externally */ }
3478
3483
  monitorLockFile = null;
3479
3484
  }
3480
3485
  }
@@ -3523,9 +3528,9 @@ process.on('uncaughtException', async (err) => {
3523
3528
  // if telemetry shutdown hangs or throws.
3524
3529
  cleanupResources();
3525
3530
 
3526
- try { telemetry.captureError(err); } catch (_) { /* ignore */ }
3531
+ try { telemetry.captureError(err); } catch (_) { /* telemetry must never prevent crash cleanup */ }
3527
3532
  console.error('Uncaught exception:', err);
3528
- try { await telemetry.shutdown(); } catch (_) { /* ignore */ }
3533
+ try { await telemetry.shutdown(); } catch (_) { /* telemetry must never prevent crash cleanup */ }
3529
3534
  process.exit(1);
3530
3535
  });
3531
3536
 
@@ -3652,11 +3657,11 @@ async function start() {
3652
3657
 
3653
3658
  // Detect default branch for ahead/behind counts, then fetch initial data
3654
3659
  detectDefaultBranch().then(() => {
3655
- fetchAheadBehindForBranches(initBranches).catch(() => {});
3656
- }).catch(() => {});
3660
+ fetchAheadBehindForBranches(initBranches).catch(() => { /* ahead/behind is background-only — stale counts are better than a noisy startup */ });
3661
+ }).catch(() => { /* no default branch detectable (no remote refs yet) — ahead/behind stays hidden */ });
3657
3662
 
3658
3663
  // Load sparklines and action cache in background
3659
- refreshAllSparklines().catch(() => {});
3664
+ refreshAllSparklines().catch(() => { /* sparkline cache stays empty — activity column just renders blank */ });
3660
3665
  initActionCache().then(() => {
3661
3666
  // Once env is known, kick off initial PR status fetch
3662
3667
  fetchAllPrStatuses().then(map => {
@@ -3665,8 +3670,8 @@ async function start() {
3665
3670
  lastPrStatusFetch = Date.now();
3666
3671
  render();
3667
3672
  }
3668
- }).catch(() => {});
3669
- }).catch(() => {});
3673
+ }).catch(() => { /* gh/glab unreachable — inline PR indicators stay hidden, poller will retry */ });
3674
+ }).catch(() => { /* cliEnv detection failed — PR actions fall back to web links where possible */ });
3670
3675
 
3671
3676
  // Start server based on mode
3672
3677
  const startBranchName = store.get('currentBranch');
@@ -3737,7 +3742,7 @@ async function start() {
3737
3742
  addLog(`New version available: ${latestVersion} \u2192 npm i -g git-watchtower`, 'update');
3738
3743
  render();
3739
3744
  }
3740
- }).catch(() => {});
3745
+ }).catch(() => { /* npm registry unreachable — periodic check will try again in 4h */ });
3741
3746
 
3742
3747
  // Re-check for updates periodically (every 4 hours) while running.
3743
3748
  // Assigned to module scope so the top-level exit handler can stop it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-watchtower",
3
- "version": "1.14.2",
3
+ "version": "1.14.4",
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/git/remote.js CHANGED
@@ -77,7 +77,7 @@ function detectPlatform(webUrl) {
77
77
  if (host === 'gitlab.com' || parts.includes('gitlab')) return 'gitlab';
78
78
  if (host === 'bitbucket.org' || parts.includes('bitbucket')) return 'bitbucket';
79
79
  if (host === 'dev.azure.com' || host.endsWith('.visualstudio.com')) return 'azure';
80
- } catch (e) { /* ignore */ }
80
+ } catch (e) { /* webUrl isn't a valid URL — fall through to the self-hosted default */ }
81
81
  return 'github'; // default assumption for self-hosted
82
82
  }
83
83
 
@@ -20,6 +20,7 @@ const fs = require('fs');
20
20
  const path = require('path');
21
21
  const os = require('os');
22
22
  const crypto = require('crypto');
23
+ const telemetry = require('../telemetry');
23
24
 
24
25
  /**
25
26
  * Directory for watchtower runtime files
@@ -161,14 +162,14 @@ function finalizeLock(pid, port, socketPath) {
161
162
  * Remove the lock file.
162
163
  */
163
164
  function removeLock() {
164
- try { fs.unlinkSync(LOCK_FILE); } catch (e) { /* ignore */ }
165
+ try { fs.unlinkSync(LOCK_FILE); } catch (e) { /* lock file may not exist */ }
165
166
  }
166
167
 
167
168
  /**
168
169
  * Remove stale socket file.
169
170
  */
170
171
  function removeSocket() {
171
- try { fs.unlinkSync(SOCKET_PATH); } catch (e) { /* ignore */ }
172
+ try { fs.unlinkSync(SOCKET_PATH); } catch (e) { /* socket file may not exist */ }
172
173
  }
173
174
 
174
175
  /**
@@ -255,7 +256,7 @@ class Coordinator {
255
256
  stop() {
256
257
  // Close all worker sockets
257
258
  for (const socket of this.workerSockets.values()) {
258
- try { socket.destroy(); } catch (e) { /* ignore */ }
259
+ try { socket.destroy(); } catch (e) { /* socket may already be destroyed */ }
259
260
  }
260
261
  this.workerSockets.clear();
261
262
  this.projects.clear();
@@ -359,7 +360,12 @@ class Coordinator {
359
360
  try {
360
361
  const msg = JSON.parse(line);
361
362
  this._handleWorkerMessage(socket, msg, (id) => { workerId = id; }, () => workerId);
362
- } catch (e) { /* ignore bad JSON */ }
363
+ } catch (e) {
364
+ // Both sides of this socket are our own code, so a JSON-parse
365
+ // failure indicates a protocol/version bug worth diagnosing.
366
+ telemetry.captureError(e);
367
+ /* skip malformed frame and keep reading */
368
+ }
363
369
  }
364
370
  }
365
371
  });
@@ -445,7 +451,7 @@ class Coordinator {
445
451
  _sendMessage(socket, msg) {
446
452
  try {
447
453
  socket.write(JSON.stringify(msg) + '\n');
448
- } catch (e) { /* ignore write errors on dead sockets */ }
454
+ } catch (e) { /* peer socket closed between iteration and write peer will reconnect if it recovers */ }
449
455
  }
450
456
 
451
457
  /**
@@ -517,7 +523,12 @@ class Worker {
517
523
  try {
518
524
  const msg = JSON.parse(line);
519
525
  this._handleMessage(msg);
520
- } catch (e) { /* ignore */ }
526
+ } catch (e) {
527
+ // Both sides of this socket are our own code, so a JSON-parse
528
+ // failure indicates a protocol/version bug worth diagnosing.
529
+ telemetry.captureError(e);
530
+ /* skip malformed frame and keep reading */
531
+ }
521
532
  }
522
533
  }
523
534
  });
@@ -574,7 +585,7 @@ class Worker {
574
585
  if (this.socket && this._connected) {
575
586
  try {
576
587
  this.socket.write(JSON.stringify(msg) + '\n');
577
- } catch (e) { /* ignore */ }
588
+ } catch (e) { /* coordinator socket closed between isConnected() check and write */ }
578
589
  }
579
590
  }
580
591
 
@@ -70,7 +70,7 @@ function getDashboardJs() {
70
70
  function savePrefs(updates) {
71
71
  const prefs = loadPrefs();
72
72
  Object.keys(updates).forEach((k) => { prefs[k] = updates[k]; });
73
- try { localStorage.setItem(PREFS_KEY, JSON.stringify(prefs)); } catch (e) { /* ignore */ }
73
+ try { localStorage.setItem(PREFS_KEY, JSON.stringify(prefs)); } catch (e) { /* localStorage quota exceeded or disabled (private mode) — prefs are best-effort */ }
74
74
  return prefs;
75
75
  }
76
76
  const prefs = loadPrefs();
@@ -122,7 +122,7 @@ function getDashboardJs() {
122
122
  try {
123
123
  const n = new Notification(title, { body, tag: tag || 'git-watchtower', icon: '', silent: false });
124
124
  setTimeout(() => n.close(), 8000);
125
- } catch (e) { /* ignore */ }
125
+ } catch (e) { /* Notification constructor can throw on some browsers (e.g. permission revoked mid-session) */ }
126
126
  }
127
127
 
128
128
  function diffBranchesForNotifications(oldBranches, newBranches) {
@@ -232,14 +232,14 @@ function getDashboardJs() {
232
232
  }
233
233
  renderTabs();
234
234
  render();
235
- } catch (err) { /* ignore parse errors */ }
235
+ } catch (err) { /* malformed SSE state frame — skip this push, next one will re-render */ }
236
236
  });
237
237
 
238
238
  evtSource.addEventListener('flash', (e) => {
239
239
  try {
240
240
  const data = JSON.parse(e.data);
241
241
  showFlash(data.text, data.type);
242
- } catch (err) { /* ignore */ }
242
+ } catch (err) { /* malformed flash payload — not worth surfacing, skip */ }
243
243
  });
244
244
 
245
245
  evtSource.addEventListener('actionResult', (e) => {
@@ -251,7 +251,7 @@ function getDashboardJs() {
251
251
  } else {
252
252
  showToast(data.message, data.success ? 'success' : 'error');
253
253
  }
254
- } catch (err) { /* ignore */ }
254
+ } catch (err) { /* malformed actionResult payload — skip (the action already ran server-side) */ }
255
255
  });
256
256
 
257
257
  evtSource.onerror = () => {
@@ -434,7 +434,7 @@ function getDashboardJs() {
434
434
  state.serverMode = pState.serverMode || 'none';
435
435
  state.repoWebUrl = pState.repoWebUrl || null;
436
436
  render();
437
- } catch (err) { /* ignore */ }
437
+ } catch (err) { /* malformed per-project state response — keep current view until the next poll */ }
438
438
  }
439
439
  };
440
440
  xhr.send();
@@ -804,8 +804,8 @@ ${pureFnBlock}
804
804
  const btn = e.target.closest('.action-item');
805
805
  if (!btn) return;
806
806
  const key = btn.getAttribute('data-action-key');
807
- const data = {};
808
- try { data = JSON.parse(btn.getAttribute('data-action-data') || '{}'); } catch (err) { /* ignore */ }
807
+ let data = {};
808
+ try { data = JSON.parse(btn.getAttribute('data-action-data') || '{}'); } catch (err) { /* malformed data-action-data — fall through with empty object */ }
809
809
 
810
810
  hideBranchActions();
811
811
 
package/src/server/web.js CHANGED
@@ -272,7 +272,7 @@ class WebDashboardServer {
272
272
 
273
273
  // Close all SSE connections
274
274
  for (const client of this.clients) {
275
- try { client.end(); } catch (e) { /* ignore */ }
275
+ try { client.end(); } catch (e) { /* SSE client may already be disconnected */ }
276
276
  }
277
277
  this.clients.clear();
278
278
 
@@ -293,7 +293,7 @@ class WebDashboardServer {
293
293
  try {
294
294
  client.write('event: flash\n');
295
295
  client.write('data: ' + data + '\n\n');
296
- } catch (e) { /* ignore dead clients */ }
296
+ } catch (e) { /* SSE client disconnected — will be pruned when its response closes */ }
297
297
  }
298
298
  }
299
299
 
@@ -307,7 +307,7 @@ class WebDashboardServer {
307
307
  try {
308
308
  client.write('event: preview\n');
309
309
  client.write('data: ' + json + '\n\n');
310
- } catch (e) { /* ignore dead clients */ }
310
+ } catch (e) { /* SSE client disconnected — will be pruned when its response closes */ }
311
311
  }
312
312
  }
313
313
 
@@ -321,7 +321,7 @@ class WebDashboardServer {
321
321
  try {
322
322
  client.write('event: actionResult\n');
323
323
  client.write('data: ' + json + '\n\n');
324
- } catch (e) { /* ignore dead clients */ }
324
+ } catch (e) { /* SSE client disconnected — will be pruned when its response closes */ }
325
325
  }
326
326
  }
327
327
 
@@ -74,7 +74,7 @@ function readLock(file) {
74
74
  }
75
75
 
76
76
  function removeLock(file) {
77
- try { fs.unlinkSync(file); } catch (e) { /* ignore */ }
77
+ try { fs.unlinkSync(file); } catch (e) { /* lock file may not exist (already removed or never created) */ }
78
78
  }
79
79
 
80
80
  /**
@@ -100,7 +100,7 @@ function startPeriodicUpdateCheck(onUpdateFound, interval = UPDATE_CHECK_INTERVA
100
100
  .then((latestVersion) => {
101
101
  if (latestVersion) onUpdateFound(latestVersion);
102
102
  })
103
- .catch(() => {});
103
+ .catch(() => { /* npm registry unreachable — next scheduled tick will try again */ });
104
104
  }, interval);
105
105
 
106
106
  return { stop: () => clearInterval(timerId) };