git-watchtower 2.3.15 → 2.3.17
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.
- package/bin/git-watchtower.js +8 -4
- package/package.json +1 -1
- package/src/git/remote.js +5 -36
- package/src/server/static.js +40 -0
- package/src/server/web-ui/js.js +20 -12
- package/src/server/web-ui/pure.js +80 -1
package/bin/git-watchtower.js
CHANGED
|
@@ -878,7 +878,7 @@ const { parseDiffStats, stash: gitStash, stashPop: gitStashPop } = require('../s
|
|
|
878
878
|
|
|
879
879
|
// Server process command parsing and static server utilities
|
|
880
880
|
const { parseCommand } = require('../src/server/process');
|
|
881
|
-
const { getMimeType, injectLiveReload, resolveStaticPath } = require('../src/server/static');
|
|
881
|
+
const { getMimeType, injectLiveReload, resolveStaticPath, broadcastReload } = require('../src/server/static');
|
|
882
882
|
|
|
883
883
|
// State (non-store globals)
|
|
884
884
|
let previousBranchStates = new Map(); // branch name -> commit hash
|
|
@@ -2299,9 +2299,13 @@ function restartPolling() {
|
|
|
2299
2299
|
|
|
2300
2300
|
function notifyClients() {
|
|
2301
2301
|
if (NO_SERVER) return; // No clients in no-server mode
|
|
2302
|
-
clients.
|
|
2303
|
-
|
|
2304
|
-
|
|
2302
|
+
if (clients.size === 0) return;
|
|
2303
|
+
const { delivered, dropped } = broadcastReload(clients);
|
|
2304
|
+
if (delivered > 0) {
|
|
2305
|
+
addLog(`Reloading ${delivered} browser(s)`, 'info');
|
|
2306
|
+
}
|
|
2307
|
+
if (dropped > 0) {
|
|
2308
|
+
addLog(`Dropped ${dropped} dead live-reload client(s)`, 'warning');
|
|
2305
2309
|
}
|
|
2306
2310
|
}
|
|
2307
2311
|
|
package/package.json
CHANGED
package/src/git/remote.js
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
* @module git/remote
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
// Branch-URL building lives in the pure module so it can be inlined into
|
|
7
|
+
// the web dashboard bundle. We re-export it here so existing Node callers
|
|
8
|
+
// keep working without an extra hop.
|
|
9
|
+
const { buildBranchUrl } = require('../server/web-ui/pure');
|
|
10
|
+
|
|
6
11
|
/**
|
|
7
12
|
* Parse a git remote URL into { host, path } components.
|
|
8
13
|
* Supports SSH (git@host:path), HTTPS, and ssh:// protocol formats.
|
|
@@ -27,42 +32,6 @@ function parseRemoteUrl(remoteUrl) {
|
|
|
27
32
|
return null;
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
/**
|
|
31
|
-
* Build a branch URL for the appropriate git hosting service.
|
|
32
|
-
* Each service has its own URL format for viewing branches.
|
|
33
|
-
* @param {string} baseUrl - Repository base URL (e.g., https://github.com/user/repo)
|
|
34
|
-
* @param {string} host - Hostname of the git hosting service
|
|
35
|
-
* @param {string} branchName - Name of the branch
|
|
36
|
-
* @returns {string}
|
|
37
|
-
*/
|
|
38
|
-
function buildBranchUrl(baseUrl, host, branchName) {
|
|
39
|
-
const branch = encodeURIComponent(branchName);
|
|
40
|
-
|
|
41
|
-
// Azure DevOps: dev.azure.com/org/project/_git/repo or org.visualstudio.com
|
|
42
|
-
if (host === 'dev.azure.com' || host.endsWith('.visualstudio.com')) {
|
|
43
|
-
return `${baseUrl}?version=GB${branch}`;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Bitbucket Cloud
|
|
47
|
-
if (host === 'bitbucket.org') {
|
|
48
|
-
return `${baseUrl}/src/${branch}`;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// AWS CodeCommit
|
|
52
|
-
if (host.match(/codecommit\..+\.amazonaws\.com/)) {
|
|
53
|
-
return `${baseUrl}/browse/refs/heads/${branch}`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// SourceHut
|
|
57
|
-
if (host === 'git.sr.ht') {
|
|
58
|
-
return `${baseUrl}/tree/${branch}`;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// GitHub, GitLab, Codeberg, Gitea, Forgejo, Gogs, and self-hosted instances
|
|
62
|
-
// All use /tree/<branch>
|
|
63
|
-
return `${baseUrl}/tree/${branch}`;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
35
|
/**
|
|
67
36
|
* Detect the git hosting platform from a web URL.
|
|
68
37
|
* @param {string|null} webUrl - Web URL of the repository
|
package/src/server/static.js
CHANGED
|
@@ -125,10 +125,50 @@ function resolveStaticPath(candidate, realStaticDir) {
|
|
|
125
125
|
return { status: 'ok', path: realPath };
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Broadcast an SSE frame to every live-reload client, isolating per-client
|
|
130
|
+
* write failures so one dead socket can't abort the whole iteration.
|
|
131
|
+
*
|
|
132
|
+
* Previously this was a one-liner in bin/git-watchtower.js:
|
|
133
|
+
* clients.forEach(c => c.write('data: reload\n\n'));
|
|
134
|
+
* If any client's underlying socket had been destroyed (browser tab
|
|
135
|
+
* closed, network reset, proxy hangup) without 'close' firing yet, the
|
|
136
|
+
* synchronous write threw `ERR_STREAM_DESTROYED` and aborted the
|
|
137
|
+
* forEach mid-iteration — every later client in the Set never received
|
|
138
|
+
* the reload event. Wrapping each write in try/catch and removing the
|
|
139
|
+
* failed client from the Set keeps the broadcast atomic-per-client and
|
|
140
|
+
* also prunes the dead entry so the next call doesn't trip on it again.
|
|
141
|
+
*
|
|
142
|
+
* @param {Set<{write: function, end?: function}>} clients - SSE response objects
|
|
143
|
+
* @param {string} [frame='data: reload\n\n'] - Pre-formatted SSE frame
|
|
144
|
+
* @returns {{delivered: number, dropped: number}}
|
|
145
|
+
*/
|
|
146
|
+
function broadcastReload(clients, frame) {
|
|
147
|
+
const message = frame || 'data: reload\n\n';
|
|
148
|
+
let delivered = 0;
|
|
149
|
+
let dropped = 0;
|
|
150
|
+
// Iterate a snapshot — Set.delete during forEach is safe in V8, but
|
|
151
|
+
// copying makes the contract explicit and survives any future
|
|
152
|
+
// iterator-protocol changes.
|
|
153
|
+
for (const client of Array.from(clients)) {
|
|
154
|
+
try {
|
|
155
|
+
client.write(message);
|
|
156
|
+
delivered++;
|
|
157
|
+
} catch (e) {
|
|
158
|
+
// Dead socket. Drop it and keep going so subsequent clients still
|
|
159
|
+
// see the broadcast.
|
|
160
|
+
clients.delete(client);
|
|
161
|
+
dropped++;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { delivered, dropped };
|
|
165
|
+
}
|
|
166
|
+
|
|
128
167
|
module.exports = {
|
|
129
168
|
MIME_TYPES,
|
|
130
169
|
getMimeType,
|
|
131
170
|
LIVE_RELOAD_SCRIPT,
|
|
132
171
|
injectLiveReload,
|
|
133
172
|
resolveStaticPath,
|
|
173
|
+
broadcastReload,
|
|
134
174
|
};
|
package/src/server/web-ui/js.js
CHANGED
|
@@ -198,26 +198,34 @@ function getDashboardJs() {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
// ── URL Building Helpers ──────────────────────────────────────
|
|
201
|
+
// state.repoWebUrl is the server-built BASE url (no /tree/...). Extract
|
|
202
|
+
// the host once per call and let the buildXxxUrl helpers — inlined from
|
|
203
|
+
// pure.js — branch on platform. The previous getBranchUrl/getCommitUrl/
|
|
204
|
+
// getPrUrl hardcoded GitHub paths and produced broken links for
|
|
205
|
+
// Bitbucket (/src/), Azure DevOps (?version=GB), AWS CodeCommit
|
|
206
|
+
// (/browse/refs/heads/), self-hosted GitLab, etc.
|
|
201
207
|
function getRepoUrl() {
|
|
202
|
-
return (state && state.repoWebUrl) ? state.repoWebUrl
|
|
208
|
+
return (state && state.repoWebUrl) ? state.repoWebUrl : null;
|
|
209
|
+
}
|
|
210
|
+
function getRepoHost() {
|
|
211
|
+
var base = getRepoUrl();
|
|
212
|
+
if (!base) return null;
|
|
213
|
+
try { return new URL(base).hostname; } catch (e) { return null; }
|
|
203
214
|
}
|
|
204
215
|
function getBranchUrl(branchName) {
|
|
205
|
-
|
|
216
|
+
var base = getRepoUrl();
|
|
206
217
|
if (!base) return null;
|
|
207
|
-
return base
|
|
218
|
+
return buildBranchUrl(base, getRepoHost(), branchName);
|
|
208
219
|
}
|
|
209
220
|
function getCommitUrl(hash) {
|
|
210
|
-
|
|
211
|
-
if (!base
|
|
212
|
-
return base
|
|
221
|
+
var base = getRepoUrl();
|
|
222
|
+
if (!base) return null;
|
|
223
|
+
return buildCommitUrl(base, getRepoHost(), hash);
|
|
213
224
|
}
|
|
214
225
|
function getPrUrl(prNumber) {
|
|
215
|
-
|
|
216
|
-
if (!base
|
|
217
|
-
|
|
218
|
-
return base + '/-/merge_requests/' + prNumber;
|
|
219
|
-
}
|
|
220
|
-
return base + '/pull/' + prNumber;
|
|
226
|
+
var base = getRepoUrl();
|
|
227
|
+
if (!base) return null;
|
|
228
|
+
return buildPrUrl(base, getRepoHost(), prNumber);
|
|
221
229
|
}
|
|
222
230
|
|
|
223
231
|
// ── SSE Connection ─────────────────────────────────────────────
|
|
@@ -134,4 +134,83 @@ function getDisplayBranches(branches, options) {
|
|
|
134
134
|
return result;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Build a branch URL for the appropriate git hosting service.
|
|
139
|
+
* Pure / browser-safe — duplicated logic lives in src/git/remote.js for
|
|
140
|
+
* Node-side callers, but THIS function is the canonical implementation
|
|
141
|
+
* that the web dashboard inlines into its bundle.
|
|
142
|
+
*
|
|
143
|
+
* @param {string} baseUrl - Repository base URL (e.g., https://github.com/user/repo)
|
|
144
|
+
* @param {string} host - Hostname of the git hosting service
|
|
145
|
+
* @param {string} branchName - Name of the branch
|
|
146
|
+
* @returns {string}
|
|
147
|
+
*/
|
|
148
|
+
function buildBranchUrl(baseUrl, host, branchName) {
|
|
149
|
+
var branch = encodeURIComponent(branchName);
|
|
150
|
+
var h = String(host || '').toLowerCase();
|
|
151
|
+
|
|
152
|
+
// Azure DevOps: dev.azure.com/org/project/_git/repo or org.visualstudio.com
|
|
153
|
+
if (h === 'dev.azure.com' || /\.visualstudio\.com$/.test(h)) {
|
|
154
|
+
return baseUrl + '?version=GB' + branch;
|
|
155
|
+
}
|
|
156
|
+
// Bitbucket Cloud
|
|
157
|
+
if (h === 'bitbucket.org') return baseUrl + '/src/' + branch;
|
|
158
|
+
// AWS CodeCommit
|
|
159
|
+
if (/codecommit\..+\.amazonaws\.com/.test(h)) {
|
|
160
|
+
return baseUrl + '/browse/refs/heads/' + branch;
|
|
161
|
+
}
|
|
162
|
+
// GitHub, GitLab, SourceHut, Codeberg, Gitea, Forgejo, Gogs, self-hosted
|
|
163
|
+
return baseUrl + '/tree/' + branch;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Build a commit URL for the appropriate git hosting service.
|
|
168
|
+
* @param {string} baseUrl - Repository base URL
|
|
169
|
+
* @param {string} host - Hostname
|
|
170
|
+
* @param {string} sha - Commit SHA (full or short)
|
|
171
|
+
* @returns {string|null}
|
|
172
|
+
*/
|
|
173
|
+
function buildCommitUrl(baseUrl, host, sha) {
|
|
174
|
+
if (!sha) return null;
|
|
175
|
+
var h = String(host || '').toLowerCase();
|
|
176
|
+
// Bitbucket uses /commits/ (plural)
|
|
177
|
+
if (h === 'bitbucket.org') return baseUrl + '/commits/' + sha;
|
|
178
|
+
// GitHub, GitLab, Gitea, Forgejo, Codeberg, Azure (best-effort), self-hosted
|
|
179
|
+
return baseUrl + '/commit/' + sha;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Build a pull/merge-request URL for the appropriate git hosting service.
|
|
184
|
+
* @param {string} baseUrl - Repository base URL
|
|
185
|
+
* @param {string} host - Hostname
|
|
186
|
+
* @param {string|number} prNumber - PR / MR number
|
|
187
|
+
* @returns {string|null}
|
|
188
|
+
*/
|
|
189
|
+
function buildPrUrl(baseUrl, host, prNumber) {
|
|
190
|
+
if (!prNumber) return null;
|
|
191
|
+
var h = String(host || '').toLowerCase();
|
|
192
|
+
if (h === 'bitbucket.org') return baseUrl + '/pull-requests/' + prNumber;
|
|
193
|
+
// GitLab.com or any host containing "gitlab" (self-hosted gitlab.example.com)
|
|
194
|
+
if (h === 'gitlab.com' || h.split('.').indexOf('gitlab') !== -1) {
|
|
195
|
+
return baseUrl + '/-/merge_requests/' + prNumber;
|
|
196
|
+
}
|
|
197
|
+
if (h === 'dev.azure.com' || /\.visualstudio\.com$/.test(h)) {
|
|
198
|
+
return baseUrl + '/pullrequest/' + prNumber;
|
|
199
|
+
}
|
|
200
|
+
if (/codecommit\..+\.amazonaws\.com/.test(h)) {
|
|
201
|
+
return baseUrl + '/pull-requests/' + prNumber;
|
|
202
|
+
}
|
|
203
|
+
// GitHub, Gitea, Codeberg, Forgejo, Gogs, self-hosted
|
|
204
|
+
return baseUrl + '/pull/' + prNumber;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
module.exports = {
|
|
208
|
+
escHtml,
|
|
209
|
+
timeAgo,
|
|
210
|
+
renderSparklineBars,
|
|
211
|
+
fmtCompact,
|
|
212
|
+
getDisplayBranches,
|
|
213
|
+
buildBranchUrl,
|
|
214
|
+
buildCommitUrl,
|
|
215
|
+
buildPrUrl,
|
|
216
|
+
};
|