git-watchtower 1.9.5 → 1.9.7

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.
@@ -1691,11 +1691,17 @@ async function pollGitChanges() {
1691
1691
  for (const knownName of knownBranchNames) {
1692
1692
  if (!fetchedBranchNames.has(knownName)) {
1693
1693
  // This branch was deleted from remote
1694
+ // Check if already present in allBranches (avoid duplicates)
1695
+ const alreadyInList = allBranches.some(b => b.name === knownName);
1696
+ if (alreadyInList) continue;
1697
+
1694
1698
  const existingInList = currentBranches.find(b => b.name === knownName);
1695
- if (existingInList && !existingInList.isDeleted) {
1696
- existingInList.isDeleted = true;
1697
- existingInList.deletedAt = now;
1698
- addLog(`Branch deleted: ${knownName}`, 'warning');
1699
+ if (existingInList) {
1700
+ if (!existingInList.isDeleted) {
1701
+ existingInList.isDeleted = true;
1702
+ existingInList.deletedAt = now;
1703
+ addLog(`Branch deleted: ${knownName}`, 'warning');
1704
+ }
1699
1705
  // Keep it in the list temporarily
1700
1706
  allBranches.push(existingInList);
1701
1707
  }
@@ -2839,13 +2845,14 @@ async function shutdown() {
2839
2845
 
2840
2846
  process.on('SIGINT', shutdown);
2841
2847
  process.on('SIGTERM', shutdown);
2842
- process.on('uncaughtException', (err) => {
2848
+ process.on('uncaughtException', async (err) => {
2843
2849
  telemetry.captureError(err);
2844
2850
  write(ansi.showCursor);
2845
2851
  write(ansi.restoreScreen);
2846
2852
  restoreTerminalTitle();
2847
2853
  if (process.stdin.isTTY) process.stdin.setRawMode(false);
2848
2854
  console.error('Uncaught exception:', err);
2855
+ await telemetry.shutdown();
2849
2856
  process.exit(1);
2850
2857
  });
2851
2858
 
@@ -2867,7 +2874,8 @@ async function start() {
2867
2874
  const config = await ensureConfig(cliArgs);
2868
2875
  applyConfig(config);
2869
2876
 
2870
- // Telemetry: opt-in prompt (first run only) and initialization
2877
+ // Telemetry: set version early so consent events include $lib_version
2878
+ telemetry.setVersion(PACKAGE_VERSION);
2871
2879
  await telemetry.promptIfNeeded(promptYesNo);
2872
2880
  telemetry.init({ version: PACKAGE_VERSION });
2873
2881
  sessionStartTime = Date.now();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-watchtower",
3
- "version": "1.9.5",
3
+ "version": "1.9.7",
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": {
@@ -8,13 +8,13 @@
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node bin/git-watchtower.js",
11
- "test": "node --test tests/unit/**/*.test.js tests/integration/**/*.test.js",
12
- "test:unit": "node --test tests/unit/**/*.test.js",
13
- "test:integration": "node --test tests/integration/**/*.test.js",
14
- "test:watch": "node --test --watch tests/unit/**/*.test.js",
15
- "test:coverage": "c8 --reporter=text --reporter=html --reporter=lcov node --test tests/unit/**/*.test.js tests/integration/**/*.test.js",
16
- "test:coverage:text": "c8 --reporter=text node --test tests/unit/**/*.test.js tests/integration/**/*.test.js",
17
- "test:coverage:html": "c8 --reporter=html node --test tests/unit/**/*.test.js tests/integration/**/*.test.js && echo 'Coverage report: coverage/index.html'",
11
+ "test": "node --require ./tests/setup.js --test tests/unit/**/*.test.js tests/integration/**/*.test.js",
12
+ "test:unit": "node --require ./tests/setup.js --test tests/unit/**/*.test.js",
13
+ "test:integration": "node --require ./tests/setup.js --test tests/integration/**/*.test.js",
14
+ "test:watch": "node --require ./tests/setup.js --test --watch tests/unit/**/*.test.js",
15
+ "test:coverage": "c8 --reporter=text --reporter=html --reporter=lcov node --require ./tests/setup.js --test tests/unit/**/*.test.js tests/integration/**/*.test.js",
16
+ "test:coverage:text": "c8 --reporter=text node --require ./tests/setup.js --test tests/unit/**/*.test.js tests/integration/**/*.test.js",
17
+ "test:coverage:html": "c8 --reporter=html node --require ./tests/setup.js --test tests/unit/**/*.test.js tests/integration/**/*.test.js && echo 'Coverage report: coverage/index.html'",
18
18
  "typecheck": "tsc --noEmit"
19
19
  },
20
20
  "devDependencies": {
@@ -22,40 +22,46 @@ const FLUSH_INTERVAL = 30000; // 30 seconds
22
22
  const FLUSH_AT = 10; // flush when 10 events accumulated
23
23
 
24
24
  /**
25
- * Send a batch of events to PostHog via HTTPS POST (fire-and-forget)
25
+ * Send a batch of events to PostHog via HTTPS POST.
26
+ * Returns a promise that resolves when the request completes (or fails).
27
+ * Callers that don't need to wait can ignore the return value.
26
28
  * @param {Array<Record<string, any>>} events
29
+ * @returns {Promise<void>}
27
30
  */
28
31
  function sendBatch(events) {
29
- if (events.length === 0) return;
30
-
31
- const payload = JSON.stringify({ api_key: POSTHOG_API_KEY, batch: events });
32
-
33
- const req = https.request({
34
- hostname: POSTHOG_HOST,
35
- port: 443,
36
- path: '/batch',
37
- method: 'POST',
38
- headers: {
39
- 'Content-Type': 'application/json',
40
- 'Content-Length': Buffer.byteLength(payload),
41
- },
42
- timeout: 5000,
43
- });
32
+ if (events.length === 0) return Promise.resolve();
33
+
34
+ return new Promise((resolve) => {
35
+ const payload = JSON.stringify({ api_key: POSTHOG_API_KEY, batch: events });
36
+
37
+ const req = https.request({
38
+ hostname: POSTHOG_HOST,
39
+ port: 443,
40
+ path: '/batch',
41
+ method: 'POST',
42
+ headers: {
43
+ 'Content-Type': 'application/json',
44
+ 'Content-Length': Buffer.byteLength(payload),
45
+ },
46
+ timeout: 5000,
47
+ });
44
48
 
45
- // Fire-and-forget: ignore all errors and responses
46
- req.on('error', () => {});
47
- req.on('timeout', () => req.destroy());
48
- req.end(payload);
49
+ req.on('error', () => resolve());
50
+ req.on('timeout', () => { req.destroy(); resolve(); });
51
+ req.on('response', () => resolve());
52
+ req.end(payload);
53
+ });
49
54
  }
50
55
 
51
56
  /**
52
- * Flush pending events
57
+ * Flush pending events.
58
+ * @returns {Promise<void>} Resolves when the batch has been sent (or fails).
53
59
  */
54
60
  function flush() {
55
- if (eventQueue.length === 0) return;
61
+ if (eventQueue.length === 0) return Promise.resolve();
56
62
  const batch = eventQueue;
57
63
  eventQueue = [];
58
- sendBatch(batch);
64
+ return sendBatch(batch);
59
65
  }
60
66
 
61
67
  /**
@@ -81,6 +87,15 @@ function queueEvent(event, properties, overrideDistinctId) {
81
87
  }
82
88
  }
83
89
 
90
+ /**
91
+ * Set the app version so that even pre-init events include $lib_version.
92
+ * Call this before promptIfNeeded() so consent events carry the version.
93
+ * @param {string} version
94
+ */
95
+ function setVersion(version) {
96
+ appVersion = version;
97
+ }
98
+
84
99
  /**
85
100
  * Initialize the analytics client if telemetry is enabled
86
101
  * @param {{ version: string }} options
@@ -190,7 +205,7 @@ function captureAlways(event, userDistinctId, properties = {}) {
190
205
  }
191
206
 
192
207
  /**
193
- * Flush pending events and shutdown
208
+ * Flush pending events and shutdown.
194
209
  * Call this before process exit to ensure events are sent.
195
210
  * @returns {Promise<void>}
196
211
  */
@@ -203,7 +218,7 @@ async function shutdown() {
203
218
  if (!enabled) return;
204
219
 
205
220
  try {
206
- flush();
221
+ await flush();
207
222
  } catch {
208
223
  // Best-effort flush
209
224
  } finally {
@@ -220,6 +235,7 @@ function isEnabled() {
220
235
  }
221
236
 
222
237
  module.exports = {
238
+ setVersion,
223
239
  init,
224
240
  capture,
225
241
  captureError,
@@ -83,6 +83,7 @@ async function promptIfNeeded(promptYesNo) {
83
83
 
84
84
  module.exports = {
85
85
  // Analytics
86
+ setVersion: analytics.setVersion,
86
87
  init: analytics.init,
87
88
  capture: analytics.capture,
88
89
  captureError: analytics.captureError,