git-watchtower 1.10.3 → 1.10.5

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.
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Pure utility functions shared between Node (server) and the web dashboard.
3
+ *
4
+ * These are extracted so they can be unit-tested in Node while still being
5
+ * inlined into the browser bundle by the assembly step in js.js.
6
+ *
7
+ * Every function here MUST be self-contained (no closures over external
8
+ * state) so it can be serialised with Function.prototype.toString() and
9
+ * embedded in a <script> tag.
10
+ *
11
+ * @module server/web-ui/pure
12
+ */
13
+
14
+ /**
15
+ * Escape a string for safe insertion into HTML.
16
+ * @param {string} s
17
+ * @returns {string}
18
+ */
19
+ function escHtml(s) {
20
+ if (!s) return '';
21
+ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
22
+ }
23
+
24
+ /**
25
+ * Format a date string as a relative time (e.g. "5m ago", "2d ago").
26
+ * @param {string} dateStr - ISO date string or any value accepted by `new Date()`
27
+ * @returns {string}
28
+ */
29
+ function timeAgo(dateStr) {
30
+ if (!dateStr) return '';
31
+ var ts = new Date(dateStr).getTime();
32
+ if (isNaN(ts)) return '';
33
+ var diff = Date.now() - ts;
34
+ if (diff < 0) return 'now';
35
+ var s = Math.floor(diff / 1000);
36
+ if (s < 60) return s + 's ago';
37
+ var m = Math.floor(s / 60);
38
+ if (m < 60) return m + 'm ago';
39
+ var h = Math.floor(m / 60);
40
+ if (h < 24) return h + 'h ago';
41
+ var d = Math.floor(h / 24);
42
+ return d + 'd ago';
43
+ }
44
+
45
+ /**
46
+ * Render a sparkline Unicode string as HTML bar elements.
47
+ * @param {string} sparkStr - String of Unicode block characters (U+2581–U+2588)
48
+ * @returns {string} HTML string
49
+ */
50
+ function renderSparklineBars(sparkStr) {
51
+ if (!sparkStr) return '';
52
+ var chars = '\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588';
53
+ var html = '<div class="sparkline-bar">';
54
+ for (var i = 0; i < sparkStr.length; i++) {
55
+ var ch = sparkStr[i];
56
+ var idx = chars.indexOf(ch);
57
+ if (idx < 0) {
58
+ html += '<div class="spark-bar" style="height:1px"></div>';
59
+ } else {
60
+ var pct = Math.round(((idx + 1) / 8) * 100);
61
+ html += '<div class="spark-bar" style="height:' + pct + '%"></div>';
62
+ }
63
+ }
64
+ html += '</div>';
65
+ return html;
66
+ }
67
+
68
+ /**
69
+ * Format a number in compact notation (e.g. 1500 → "1.5k").
70
+ * @param {number} n
71
+ * @returns {string}
72
+ */
73
+ function fmtCompact(n) {
74
+ if (n < 1000) return String(n);
75
+ if (n < 10000) return (n / 1000).toFixed(1) + 'k';
76
+ if (n < 1000000) return Math.round(n / 1000) + 'k';
77
+ return (n / 1000000).toFixed(1) + 'm';
78
+ }
79
+
80
+ /**
81
+ * Filter and sort branches for display.
82
+ * Pure function — all dependencies passed as arguments.
83
+ * @param {Array} branches - Full branch list from state
84
+ * @param {Object} options
85
+ * @param {string} [options.searchQuery=''] - Filter string
86
+ * @param {string[]} [options.pinnedBranches=[]] - Branch names pinned to top
87
+ * @param {string} [options.sortOrder='default'] - 'default' | 'alpha' | 'recent'
88
+ * @returns {Array} Filtered and sorted branch list
89
+ */
90
+ function getDisplayBranches(branches, options) {
91
+ if (!branches) return [];
92
+ var searchQuery = (options && options.searchQuery) || '';
93
+ var pinnedBranches = (options && options.pinnedBranches) || [];
94
+ var sortOrder = (options && options.sortOrder) || 'default';
95
+
96
+ var result = branches.slice();
97
+
98
+ if (searchQuery) {
99
+ var q = searchQuery.toLowerCase();
100
+ result = result.filter(function(b) {
101
+ return b.name.toLowerCase().indexOf(q) !== -1;
102
+ });
103
+ }
104
+
105
+ // Build pin lookup once
106
+ var pinSet = {};
107
+ for (var i = 0; i < pinnedBranches.length; i++) pinSet[pinnedBranches[i]] = true;
108
+
109
+ if (sortOrder === 'alpha') {
110
+ result.sort(function(a, b) {
111
+ var aPin = pinSet[a.name] ? 1 : 0;
112
+ var bPin = pinSet[b.name] ? 1 : 0;
113
+ if (aPin !== bPin) return bPin - aPin;
114
+ return a.name.localeCompare(b.name);
115
+ });
116
+ } else if (sortOrder === 'recent') {
117
+ result.sort(function(a, b) {
118
+ var aPin = pinSet[a.name] ? 1 : 0;
119
+ var bPin = pinSet[b.name] ? 1 : 0;
120
+ if (aPin !== bPin) return bPin - aPin;
121
+ var aDate = a.date ? new Date(a.date).getTime() : 0;
122
+ var bDate = b.date ? new Date(b.date).getTime() : 0;
123
+ return bDate - aDate;
124
+ });
125
+ } else if (pinnedBranches.length > 0) {
126
+ // Default sort: only move pinned branches to top
127
+ result.sort(function(a, b) {
128
+ var aPin = pinSet[a.name] ? 1 : 0;
129
+ var bPin = pinSet[b.name] ? 1 : 0;
130
+ return bPin - aPin;
131
+ });
132
+ }
133
+
134
+ return result;
135
+ }
136
+
137
+ module.exports = { escHtml, timeAgo, renderSparklineBars, fmtCompact, getDisplayBranches };
package/src/server/web.js CHANGED
@@ -429,7 +429,7 @@ class WebDashboardServer {
429
429
 
430
430
  // Keepalive heartbeat to prevent proxy/LB timeouts
431
431
  const keepalive = setInterval(() => {
432
- try { res.write(': keepalive\\n\\n'); } catch (e) { clearInterval(keepalive); }
432
+ try { res.write(': keepalive\n\n'); } catch (e) { clearInterval(keepalive); }
433
433
  }, SSE_KEEPALIVE_INTERVAL);
434
434
 
435
435
  req.on('close', () => {