watchmen-cli 1.1.6 → 1.1.9
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/install.js +82 -6
- package/package.json +1 -1
package/install.js
CHANGED
|
@@ -5,10 +5,16 @@ const https = require("https");
|
|
|
5
5
|
const http = require("http");
|
|
6
6
|
const fs = require("fs");
|
|
7
7
|
const path = require("path");
|
|
8
|
+
const crypto = require("crypto");
|
|
8
9
|
const { execSync } = require("child_process");
|
|
9
10
|
|
|
10
11
|
const VERSION = require("./package.json").version;
|
|
12
|
+
// Versioned URL first (exact-pin, immune to CDN drift on the latest alias);
|
|
13
|
+
// fall back to /latest/ when the versioned path 404s (e.g. release mid-promote).
|
|
14
|
+
// Either way, the binary's reported version is asserted against VERSION below.
|
|
11
15
|
const DOWNLOAD_BASE = "https://releases.trywatchmen.cloud/download/community";
|
|
16
|
+
const VERSIONED_URL_TMPL = (slug) => `${DOWNLOAD_BASE}/${VERSION}/${slug}`;
|
|
17
|
+
const LATEST_URL_TMPL = (slug) => `${DOWNLOAD_BASE}/${slug}`;
|
|
12
18
|
|
|
13
19
|
// 60s connect timeout, 120s total — covers slow connections without hanging forever
|
|
14
20
|
const CONNECT_TIMEOUT_MS = 60_000;
|
|
@@ -146,12 +152,23 @@ async function main() {
|
|
|
146
152
|
}
|
|
147
153
|
}
|
|
148
154
|
|
|
149
|
-
const
|
|
155
|
+
const versionedUrl = VERSIONED_URL_TMPL(platformSlug);
|
|
156
|
+
const latestUrl = LATEST_URL_TMPL(platformSlug);
|
|
150
157
|
|
|
151
158
|
console.log(`\n WatchmenCLI v${VERSION} — ${platformKey}`);
|
|
152
|
-
console.log(` ${url}\n`);
|
|
153
159
|
|
|
154
|
-
|
|
160
|
+
let binary;
|
|
161
|
+
let sourceUrl;
|
|
162
|
+
try {
|
|
163
|
+
console.log(` ${versionedUrl}`);
|
|
164
|
+
binary = await download(versionedUrl);
|
|
165
|
+
sourceUrl = versionedUrl;
|
|
166
|
+
} catch (err) {
|
|
167
|
+
console.log(` (versioned URL unavailable: ${err.message.split("\n")[0]})`);
|
|
168
|
+
console.log(` falling back to: ${latestUrl}\n`);
|
|
169
|
+
binary = await download(latestUrl);
|
|
170
|
+
sourceUrl = latestUrl;
|
|
171
|
+
}
|
|
155
172
|
|
|
156
173
|
// Validate minimum binary size (real binaries are >5MB)
|
|
157
174
|
if (binary.length < 1024 * 1024) {
|
|
@@ -162,6 +179,32 @@ async function main() {
|
|
|
162
179
|
);
|
|
163
180
|
}
|
|
164
181
|
|
|
182
|
+
// SHA-256 verify against the .sha256 sibling published alongside the binary.
|
|
183
|
+
// Missing sibling is a warning (older releases don't publish it), not fatal.
|
|
184
|
+
const binaryDigest = crypto.createHash("sha256").update(binary).digest("hex");
|
|
185
|
+
try {
|
|
186
|
+
const shaUrl = sourceUrl + ".sha256";
|
|
187
|
+
const shaBytes = await download(shaUrl);
|
|
188
|
+
const expectedDigest = shaBytes.toString("utf8").trim().split(/\s+/)[0].toLowerCase();
|
|
189
|
+
if (expectedDigest && /^[0-9a-f]{64}$/.test(expectedDigest)) {
|
|
190
|
+
if (expectedDigest !== binaryDigest) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`SHA-256 mismatch:\n` +
|
|
193
|
+
` expected: ${expectedDigest}\n` +
|
|
194
|
+
` got: ${binaryDigest}\n` +
|
|
195
|
+
` source: ${sourceUrl}\n` +
|
|
196
|
+
`Install aborted. Try: npm cache clean --force && npm install -g watchmen-cli`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
console.log(` SHA-256 verified: ${binaryDigest.slice(0, 16)}...`);
|
|
200
|
+
} else {
|
|
201
|
+
console.warn(` (SHA-256 file present but unparseable; continuing without verification)`);
|
|
202
|
+
}
|
|
203
|
+
} catch (err) {
|
|
204
|
+
if (err.message && err.message.startsWith("SHA-256 mismatch")) throw err;
|
|
205
|
+
console.warn(` (SHA-256 sibling unavailable — continuing without verification: ${err.message.split("\n")[0]})`);
|
|
206
|
+
}
|
|
207
|
+
|
|
165
208
|
// Write binary
|
|
166
209
|
fs.mkdirSync(binDir, { recursive: true });
|
|
167
210
|
fs.writeFileSync(binPath, binary);
|
|
@@ -171,11 +214,28 @@ async function main() {
|
|
|
171
214
|
fs.chmodSync(binPath, 0o755);
|
|
172
215
|
}
|
|
173
216
|
|
|
174
|
-
//
|
|
217
|
+
// The downloaded binary must report the same version as the npm package.
|
|
218
|
+
// Fail-closed on mismatch: the /latest/ fallback could otherwise serve
|
|
219
|
+
// a stale version during a mid-promote window.
|
|
175
220
|
try {
|
|
176
|
-
execSync(`"${binPath}" version`, { encoding: "utf8", timeout: 10_000 });
|
|
177
|
-
|
|
221
|
+
const reported = execSync(`"${binPath}" version`, { encoding: "utf8", timeout: 10_000 });
|
|
222
|
+
const reportedMatch = reported.match(/(\d+\.\d+\.\d+)/);
|
|
223
|
+
const reportedVersion = reportedMatch ? reportedMatch[1] : null;
|
|
224
|
+
if (reportedVersion && reportedVersion !== VERSION) {
|
|
225
|
+
// Delete the bad binary so rerunning `npm install` actually retries
|
|
226
|
+
try { fs.unlinkSync(binPath); } catch {}
|
|
227
|
+
throw new Error(
|
|
228
|
+
`Downloaded binary reports version ${reportedVersion}, but npm package is ${VERSION}.\n` +
|
|
229
|
+
` This means the release pipeline has drift between dist-tag and served binary.\n` +
|
|
230
|
+
` Source: ${sourceUrl}\n` +
|
|
231
|
+
` Install aborted. This is almost certainly a transient CDN cache issue —\n` +
|
|
232
|
+
` try again in a few minutes, or: npm cache clean --force && npm install -g watchmen-cli`
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
console.log(` Installed successfully: ${binPath}`);
|
|
236
|
+
console.log(` Reports: ${reported.split("\n")[0]}\n`);
|
|
178
237
|
} catch (err) {
|
|
238
|
+
if (err.message && err.message.includes("reports version")) throw err;
|
|
179
239
|
console.warn(
|
|
180
240
|
` Warning: binary installed but verification failed.\n` +
|
|
181
241
|
` Path: ${binPath}\n` +
|
|
@@ -186,6 +246,22 @@ async function main() {
|
|
|
186
246
|
|
|
187
247
|
main().catch((err) => {
|
|
188
248
|
console.error(`\n Failed to install wm: ${err.message}\n`);
|
|
249
|
+
// On Linux/macOS permission errors, suggest the user-local npm prefix
|
|
250
|
+
// (most users don't have sudo-npm configured).
|
|
251
|
+
if (/EACCES|EPERM|permission denied/i.test(err.message || "") && process.platform !== "win32") {
|
|
252
|
+
console.error(
|
|
253
|
+
` Looks like a permissions problem. On Linux/macOS without sudo, npm's\n` +
|
|
254
|
+
` global install path is root-owned. The sustainable fix is a user-local\n` +
|
|
255
|
+
` prefix:\n` +
|
|
256
|
+
`\n` +
|
|
257
|
+
` mkdir -p ~/.npm-global\n` +
|
|
258
|
+
` npm config set prefix ~/.npm-global\n` +
|
|
259
|
+
` echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.bashrc # or ~/.zshrc\n` +
|
|
260
|
+
` source ~/.bashrc\n` +
|
|
261
|
+
` npm install -g watchmen-cli@${VERSION}\n` +
|
|
262
|
+
`\n`
|
|
263
|
+
);
|
|
264
|
+
}
|
|
189
265
|
console.error(
|
|
190
266
|
` Troubleshooting:\n` +
|
|
191
267
|
` 1. Check your internet connection\n` +
|