watchmen-cli 1.1.4 → 1.1.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.
Files changed (2) hide show
  1. package/install.js +82 -6
  2. 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 url = `${DOWNLOAD_BASE}/${platformSlug}`;
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
- const binary = await download(url);
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
- // Verify the binary runs
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
- console.log(` Installed successfully: ${binPath}\n`);
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` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "watchmen-cli",
3
- "version": "1.1.4",
3
+ "version": "1.1.7",
4
4
  "description": "Capture, compare, and transfer your complete development environment",
5
5
  "main": "install.js",
6
6
  "keywords": [