watchmen-cli 1.0.10 → 1.0.12
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/wm.js +28 -0
- package/install.js +124 -24
- package/package.json +3 -3
- package/bin/wm +0 -5
package/bin/wm.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { execFileSync } = require("child_process");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
|
|
8
|
+
const binDir = __dirname;
|
|
9
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
10
|
+
const binPath = path.join(binDir, `wm${ext}`);
|
|
11
|
+
|
|
12
|
+
if (!fs.existsSync(binPath)) {
|
|
13
|
+
console.error(
|
|
14
|
+
"wm binary not found. Run: npm rebuild watchmen-cli\n" +
|
|
15
|
+
`Expected at: ${binPath}`
|
|
16
|
+
);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
execFileSync(binPath, process.argv.slice(2), {
|
|
22
|
+
stdio: "inherit",
|
|
23
|
+
env: process.env,
|
|
24
|
+
});
|
|
25
|
+
} catch (err) {
|
|
26
|
+
// execFileSync throws on non-zero exit — forward the exit code
|
|
27
|
+
process.exit(err.status || 1);
|
|
28
|
+
}
|
package/install.js
CHANGED
|
@@ -5,17 +5,21 @@ const https = require("https");
|
|
|
5
5
|
const http = require("http");
|
|
6
6
|
const fs = require("fs");
|
|
7
7
|
const path = require("path");
|
|
8
|
-
const { createHash } = require("crypto");
|
|
9
8
|
const { execSync } = require("child_process");
|
|
10
9
|
|
|
11
10
|
const VERSION = require("./package.json").version;
|
|
12
11
|
const DOWNLOAD_BASE = "https://releases.trywatchmen.cloud/download/community";
|
|
13
12
|
|
|
13
|
+
// 60s connect timeout, 120s total — covers slow connections without hanging forever
|
|
14
|
+
const CONNECT_TIMEOUT_MS = 60_000;
|
|
15
|
+
const TOTAL_TIMEOUT_MS = 120_000;
|
|
16
|
+
|
|
14
17
|
// Platform → download path mapping
|
|
15
18
|
const PLATFORMS = {
|
|
16
19
|
"darwin-arm64": "macos-arm64",
|
|
17
20
|
"darwin-x64": "macos-arm64", // Rosetta 2 fallback
|
|
18
21
|
"linux-x64": "linux-x86_64",
|
|
22
|
+
"linux-arm64": "linux-x86_64", // best-effort — no native arm64 yet
|
|
19
23
|
"win32-x64": "windows-x86_64",
|
|
20
24
|
};
|
|
21
25
|
|
|
@@ -28,69 +32,165 @@ function getPlatformSlug() {
|
|
|
28
32
|
const slug = PLATFORMS[key];
|
|
29
33
|
if (!slug) {
|
|
30
34
|
console.error(
|
|
31
|
-
|
|
35
|
+
`\n Unsupported platform: ${key}\n` +
|
|
36
|
+
` Supported: ${Object.keys(PLATFORMS).join(", ")}\n\n` +
|
|
37
|
+
` You can download manually from: https://trywatchmen.cloud/docs/getting-started/installation\n`
|
|
32
38
|
);
|
|
33
39
|
process.exit(1);
|
|
34
40
|
}
|
|
35
41
|
return slug;
|
|
36
42
|
}
|
|
37
43
|
|
|
38
|
-
function
|
|
44
|
+
function formatBytes(bytes) {
|
|
45
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
46
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
|
|
47
|
+
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function download(url, maxRedirects = 5) {
|
|
39
51
|
return new Promise((resolve, reject) => {
|
|
52
|
+
if (maxRedirects <= 0) {
|
|
53
|
+
return reject(new Error("Too many redirects"));
|
|
54
|
+
}
|
|
55
|
+
|
|
40
56
|
const client = url.startsWith("https") ? https : http;
|
|
41
|
-
client
|
|
42
|
-
|
|
43
|
-
|
|
57
|
+
const req = client.get(
|
|
58
|
+
url,
|
|
59
|
+
{
|
|
60
|
+
headers: { "User-Agent": `watchmen-cli-npm/${VERSION}` },
|
|
61
|
+
timeout: CONNECT_TIMEOUT_MS,
|
|
62
|
+
},
|
|
63
|
+
(res) => {
|
|
64
|
+
// Follow redirects
|
|
44
65
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
45
|
-
|
|
66
|
+
res.resume(); // drain response
|
|
67
|
+
return download(res.headers.location, maxRedirects - 1).then(resolve).catch(reject);
|
|
46
68
|
}
|
|
47
69
|
if (res.statusCode !== 200) {
|
|
48
|
-
|
|
70
|
+
res.resume();
|
|
71
|
+
return reject(new Error(`HTTP ${res.statusCode} from ${url.split("?")[0]}`));
|
|
49
72
|
}
|
|
73
|
+
|
|
74
|
+
const contentLength = parseInt(res.headers["content-length"], 10) || 0;
|
|
50
75
|
const chunks = [];
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
76
|
+
let received = 0;
|
|
77
|
+
const startTime = Date.now();
|
|
78
|
+
|
|
79
|
+
// Progress reporting
|
|
80
|
+
const progressInterval = setInterval(() => {
|
|
81
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(0);
|
|
82
|
+
if (contentLength > 0) {
|
|
83
|
+
const pct = ((received / contentLength) * 100).toFixed(0);
|
|
84
|
+
process.stdout.write(`\r Downloading... ${formatBytes(received)} / ${formatBytes(contentLength)} (${pct}%) [${elapsed}s]`);
|
|
85
|
+
} else {
|
|
86
|
+
process.stdout.write(`\r Downloading... ${formatBytes(received)} [${elapsed}s]`);
|
|
87
|
+
}
|
|
88
|
+
}, 500);
|
|
89
|
+
|
|
90
|
+
res.on("data", (chunk) => {
|
|
91
|
+
chunks.push(chunk);
|
|
92
|
+
received += chunk.length;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
res.on("end", () => {
|
|
96
|
+
clearInterval(progressInterval);
|
|
97
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
98
|
+
process.stdout.write(`\r Downloaded ${formatBytes(received)} in ${elapsed}s${" ".repeat(20)}\n`);
|
|
99
|
+
resolve(Buffer.concat(chunks));
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
res.on("error", (err) => {
|
|
103
|
+
clearInterval(progressInterval);
|
|
104
|
+
reject(err);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
req.on("timeout", () => {
|
|
110
|
+
req.destroy();
|
|
111
|
+
reject(new Error(`Connection timed out after ${CONNECT_TIMEOUT_MS / 1000}s`));
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
req.on("error", reject);
|
|
115
|
+
|
|
116
|
+
// Hard total timeout
|
|
117
|
+
setTimeout(() => {
|
|
118
|
+
req.destroy();
|
|
119
|
+
reject(new Error(`Download timed out after ${TOTAL_TIMEOUT_MS / 1000}s — try again or download manually from https://trywatchmen.cloud`));
|
|
120
|
+
}, TOTAL_TIMEOUT_MS);
|
|
56
121
|
});
|
|
57
122
|
}
|
|
58
123
|
|
|
59
124
|
async function main() {
|
|
60
125
|
const platformSlug = getPlatformSlug();
|
|
126
|
+
const platformKey = getPlatformKey();
|
|
127
|
+
const isWindows = process.platform === "win32";
|
|
61
128
|
const binDir = path.join(__dirname, "bin");
|
|
62
|
-
const binName =
|
|
129
|
+
const binName = isWindows ? "wm.exe" : "wm";
|
|
63
130
|
const binPath = path.join(binDir, binName);
|
|
64
131
|
|
|
65
132
|
// Skip if already installed at correct version
|
|
66
133
|
if (fs.existsSync(binPath)) {
|
|
67
134
|
try {
|
|
68
|
-
const installed = execSync(`"${binPath}" version`, {
|
|
135
|
+
const installed = execSync(`"${binPath}" version`, {
|
|
136
|
+
encoding: "utf8",
|
|
137
|
+
timeout: 10_000,
|
|
138
|
+
});
|
|
69
139
|
if (installed.includes(VERSION)) {
|
|
70
|
-
console.log(`wm v${VERSION} already installed`);
|
|
140
|
+
console.log(` wm v${VERSION} already installed at ${binPath}`);
|
|
71
141
|
return;
|
|
72
142
|
}
|
|
73
|
-
|
|
143
|
+
console.log(` Upgrading wm to v${VERSION}...`);
|
|
144
|
+
} catch {
|
|
145
|
+
// Binary exists but broken/wrong version — redownload
|
|
146
|
+
}
|
|
74
147
|
}
|
|
75
148
|
|
|
76
|
-
// Pre-signed URL API redirects to S3
|
|
77
149
|
const url = `${DOWNLOAD_BASE}/${platformSlug}`;
|
|
78
150
|
|
|
79
|
-
console.log(
|
|
80
|
-
console.log(` ${url}`);
|
|
151
|
+
console.log(`\n WatchmenCLI v${VERSION} — ${platformKey}`);
|
|
152
|
+
console.log(` ${url}\n`);
|
|
81
153
|
|
|
82
|
-
// Download binary (URL redirects to pre-signed S3 URL)
|
|
83
154
|
const binary = await download(url);
|
|
84
155
|
|
|
156
|
+
// Validate minimum binary size (real binaries are >5MB)
|
|
157
|
+
if (binary.length < 1024 * 1024) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`Downloaded file is only ${formatBytes(binary.length)} — expected >5MB.\n` +
|
|
160
|
+
` This usually means the download URL returned an error page.\n` +
|
|
161
|
+
` Try: npm cache clean --force && npm install -g watchmen-cli`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
85
165
|
// Write binary
|
|
86
166
|
fs.mkdirSync(binDir, { recursive: true });
|
|
87
167
|
fs.writeFileSync(binPath, binary);
|
|
88
|
-
fs.chmodSync(binPath, 0o755);
|
|
89
168
|
|
|
90
|
-
|
|
169
|
+
// chmod on Unix (no-op concept on Windows, but doesn't hurt)
|
|
170
|
+
if (!isWindows) {
|
|
171
|
+
fs.chmodSync(binPath, 0o755);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Verify the binary runs
|
|
175
|
+
try {
|
|
176
|
+
execSync(`"${binPath}" version`, { encoding: "utf8", timeout: 10_000 });
|
|
177
|
+
console.log(` Installed successfully: ${binPath}\n`);
|
|
178
|
+
} catch (err) {
|
|
179
|
+
console.warn(
|
|
180
|
+
` Warning: binary installed but verification failed.\n` +
|
|
181
|
+
` Path: ${binPath}\n` +
|
|
182
|
+
` You may need to run: wm doctor\n`
|
|
183
|
+
);
|
|
184
|
+
}
|
|
91
185
|
}
|
|
92
186
|
|
|
93
187
|
main().catch((err) => {
|
|
94
|
-
console.error(
|
|
188
|
+
console.error(`\n Failed to install wm: ${err.message}\n`);
|
|
189
|
+
console.error(
|
|
190
|
+
` Troubleshooting:\n` +
|
|
191
|
+
` 1. Check your internet connection\n` +
|
|
192
|
+
` 2. Try: npm cache clean --force && npm install -g watchmen-cli\n` +
|
|
193
|
+
` 3. Download manually: https://trywatchmen.cloud/docs/getting-started/installation\n`
|
|
194
|
+
);
|
|
95
195
|
process.exit(1);
|
|
96
196
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "watchmen-cli",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.12",
|
|
4
|
+
"description": "Capture, compare, and transfer your complete development environment",
|
|
5
5
|
"main": "install.js",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"cli",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"author": "Cristiano Pereira da Silva Muniz, LLC",
|
|
25
25
|
"bin": {
|
|
26
|
-
"wm": "bin/wm"
|
|
26
|
+
"wm": "bin/wm.js"
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
29
|
"postinstall": "node install.js"
|