@just-every/code 0.2.3 → 0.2.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.
- package/bin/coder.js +82 -2
- package/package.json +1 -1
- package/postinstall.js +136 -23
package/bin/coder.js
CHANGED
|
@@ -10,6 +10,20 @@ const __dirname = path.dirname(__filename);
|
|
|
10
10
|
|
|
11
11
|
const { platform, arch } = process;
|
|
12
12
|
|
|
13
|
+
const isWSL = () => {
|
|
14
|
+
if (platform !== "linux") return false;
|
|
15
|
+
try {
|
|
16
|
+
const os = require("os");
|
|
17
|
+
const rel = os.release().toLowerCase();
|
|
18
|
+
if (rel.includes("microsoft")) return true;
|
|
19
|
+
const fs = require("fs");
|
|
20
|
+
const txt = fs.readFileSync("/proc/version", "utf8").toLowerCase();
|
|
21
|
+
return txt.includes("microsoft");
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
13
27
|
let targetTriple = null;
|
|
14
28
|
switch (platform) {
|
|
15
29
|
case "linux":
|
|
@@ -63,7 +77,7 @@ if (!existsSync(binaryPath)) {
|
|
|
63
77
|
}
|
|
64
78
|
|
|
65
79
|
// Check if binary exists and try to fix permissions if needed
|
|
66
|
-
import { existsSync, chmodSync } from "fs";
|
|
80
|
+
import { existsSync, chmodSync, statSync, openSync, readSync, closeSync } from "fs";
|
|
67
81
|
if (existsSync(binaryPath)) {
|
|
68
82
|
try {
|
|
69
83
|
// Ensure binary is executable on Unix-like systems
|
|
@@ -78,6 +92,59 @@ if (existsSync(binaryPath)) {
|
|
|
78
92
|
console.error(`Please try reinstalling the package:`);
|
|
79
93
|
console.error(` npm uninstall -g @just-every/code`);
|
|
80
94
|
console.error(` npm install -g @just-every/code`);
|
|
95
|
+
if (isWSL()) {
|
|
96
|
+
console.error("Detected WSL. Install inside WSL (Ubuntu) separately:");
|
|
97
|
+
console.error(" npx -y @just-every/code@latest (run inside WSL)");
|
|
98
|
+
console.error("If installed globally on Windows, those binaries are not usable from WSL.");
|
|
99
|
+
}
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Lightweight header validation to provide clearer errors before spawn
|
|
104
|
+
const validateBinary = (p) => {
|
|
105
|
+
try {
|
|
106
|
+
const st = statSync(p);
|
|
107
|
+
if (!st.isFile() || st.size === 0) {
|
|
108
|
+
return { ok: false, reason: "empty or not a regular file" };
|
|
109
|
+
}
|
|
110
|
+
const fd = openSync(p, "r");
|
|
111
|
+
try {
|
|
112
|
+
const buf = Buffer.alloc(4);
|
|
113
|
+
const n = readSync(fd, buf, 0, 4, 0);
|
|
114
|
+
if (n < 2) return { ok: false, reason: "too short" };
|
|
115
|
+
if (platform === "win32") {
|
|
116
|
+
if (!(buf[0] === 0x4d && buf[1] === 0x5a)) return { ok: false, reason: "invalid PE header (missing MZ)" };
|
|
117
|
+
} else if (platform === "linux" || platform === "android") {
|
|
118
|
+
if (!(buf[0] === 0x7f && buf[1] === 0x45 && buf[2] === 0x4c && buf[3] === 0x46)) return { ok: false, reason: "invalid ELF header" };
|
|
119
|
+
} else if (platform === "darwin") {
|
|
120
|
+
const isMachO = (buf[0] === 0xcf && buf[1] === 0xfa && buf[2] === 0xed && buf[3] === 0xfe) ||
|
|
121
|
+
(buf[0] === 0xca && buf[1] === 0xfe && buf[2] === 0xba && buf[3] === 0xbe);
|
|
122
|
+
if (!isMachO) return { ok: false, reason: "invalid Mach-O header" };
|
|
123
|
+
}
|
|
124
|
+
} finally {
|
|
125
|
+
closeSync(fd);
|
|
126
|
+
}
|
|
127
|
+
return { ok: true };
|
|
128
|
+
} catch (e) {
|
|
129
|
+
return { ok: false, reason: e.message };
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const validation = validateBinary(binaryPath);
|
|
134
|
+
if (!validation.ok) {
|
|
135
|
+
console.error(`The native binary at ${binaryPath} appears invalid: ${validation.reason}`);
|
|
136
|
+
console.error("This can happen if the download failed or was modified by antivirus/proxy.");
|
|
137
|
+
console.error("Please try reinstalling:");
|
|
138
|
+
console.error(" npm uninstall -g @just-every/code");
|
|
139
|
+
console.error(" npm install -g @just-every/code");
|
|
140
|
+
if (platform === "win32") {
|
|
141
|
+
console.error("If the issue persists, clear npm cache and disable antivirus temporarily:");
|
|
142
|
+
console.error(" npm cache clean --force");
|
|
143
|
+
}
|
|
144
|
+
if (isWSL()) {
|
|
145
|
+
console.error("Detected WSL. Ensure you install/run inside WSL, not Windows:");
|
|
146
|
+
console.error(" npx -y @just-every/code@latest (inside WSL)");
|
|
147
|
+
}
|
|
81
148
|
process.exit(1);
|
|
82
149
|
}
|
|
83
150
|
|
|
@@ -95,10 +162,23 @@ const child = spawn(binaryPath, process.argv.slice(2), {
|
|
|
95
162
|
|
|
96
163
|
child.on("error", (err) => {
|
|
97
164
|
// Typically triggered when the binary is missing or not executable.
|
|
98
|
-
|
|
165
|
+
const code = err && err.code;
|
|
166
|
+
if (code === 'EACCES') {
|
|
99
167
|
console.error(`Permission denied: ${binaryPath}`);
|
|
100
168
|
console.error(`Try running: chmod +x "${binaryPath}"`);
|
|
101
169
|
console.error(`Or reinstall the package with: npm install -g @just-every/code`);
|
|
170
|
+
} else if (code === 'EFTYPE' || code === 'ENOEXEC') {
|
|
171
|
+
console.error(`Failed to execute native binary: ${binaryPath}`);
|
|
172
|
+
console.error("The file may be corrupt or of the wrong type. Reinstall usually fixes this:");
|
|
173
|
+
console.error(" npm uninstall -g @just-every/code && npm install -g @just-every/code");
|
|
174
|
+
if (platform === 'win32') {
|
|
175
|
+
console.error("On Windows, ensure the .exe downloaded correctly (proxy/AV can interfere).");
|
|
176
|
+
console.error("Try clearing cache: npm cache clean --force");
|
|
177
|
+
}
|
|
178
|
+
if (isWSL()) {
|
|
179
|
+
console.error("Detected WSL. Windows binaries cannot be executed from WSL.");
|
|
180
|
+
console.error("Install inside WSL and run there: npx -y @just-every/code@latest");
|
|
181
|
+
}
|
|
102
182
|
} else {
|
|
103
183
|
console.error(err);
|
|
104
184
|
}
|
package/package.json
CHANGED
package/postinstall.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Non-functional change to trigger release workflow
|
|
3
3
|
|
|
4
|
-
import { existsSync, mkdirSync, createWriteStream, chmodSync, readFileSync, readSync, writeFileSync } from 'fs';
|
|
4
|
+
import { existsSync, mkdirSync, createWriteStream, chmodSync, readFileSync, readSync, writeFileSync, unlinkSync, statSync, openSync, closeSync } from 'fs';
|
|
5
5
|
import { join, dirname } from 'path';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { get } from 'https';
|
|
@@ -29,31 +29,123 @@ function getTargetTriple() {
|
|
|
29
29
|
return `${rustArch}-${rustPlatform}`;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
async function downloadBinary(url, dest) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
async function downloadBinary(url, dest, maxRedirects = 5, maxRetries = 3) {
|
|
33
|
+
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
34
|
+
|
|
35
|
+
const doAttempt = () => new Promise((resolve, reject) => {
|
|
36
|
+
const attempt = (currentUrl, redirectsLeft) => {
|
|
37
|
+
const req = get(currentUrl, (response) => {
|
|
38
|
+
const status = response.statusCode || 0;
|
|
39
|
+
const location = response.headers.location;
|
|
40
|
+
|
|
41
|
+
if ((status === 301 || status === 302 || status === 303 || status === 307 || status === 308) && location) {
|
|
42
|
+
if (redirectsLeft <= 0) {
|
|
43
|
+
reject(new Error(`Too many redirects while downloading ${currentUrl}`));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
attempt(location, redirectsLeft - 1);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (status === 200) {
|
|
51
|
+
const expected = parseInt(response.headers['content-length'] || '0', 10) || 0;
|
|
52
|
+
let bytes = 0;
|
|
53
|
+
let timer;
|
|
54
|
+
const timeoutMs = 30000; // 30s inactivity timeout
|
|
55
|
+
|
|
56
|
+
const resetTimer = () => {
|
|
57
|
+
if (timer) clearTimeout(timer);
|
|
58
|
+
timer = setTimeout(() => {
|
|
59
|
+
req.destroy(new Error('download stalled'));
|
|
60
|
+
}, timeoutMs);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
resetTimer();
|
|
64
|
+
response.on('data', (chunk) => {
|
|
65
|
+
bytes += chunk.length;
|
|
66
|
+
resetTimer();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const file = createWriteStream(dest);
|
|
70
|
+
response.pipe(file);
|
|
41
71
|
file.on('finish', () => {
|
|
72
|
+
if (timer) clearTimeout(timer);
|
|
42
73
|
file.close();
|
|
43
|
-
|
|
74
|
+
if (expected && bytes !== expected) {
|
|
75
|
+
try { unlinkSync(dest); } catch {}
|
|
76
|
+
reject(new Error(`incomplete download: got ${bytes} of ${expected} bytes`));
|
|
77
|
+
} else if (bytes === 0) {
|
|
78
|
+
try { unlinkSync(dest); } catch {}
|
|
79
|
+
reject(new Error('empty download'));
|
|
80
|
+
} else {
|
|
81
|
+
resolve();
|
|
82
|
+
}
|
|
44
83
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
84
|
+
file.on('error', (err) => {
|
|
85
|
+
if (timer) clearTimeout(timer);
|
|
86
|
+
try { unlinkSync(dest); } catch {}
|
|
87
|
+
reject(err);
|
|
88
|
+
});
|
|
89
|
+
} else {
|
|
90
|
+
reject(new Error(`Failed to download: HTTP ${status}`));
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
req.on('error', (err) => {
|
|
95
|
+
try { unlinkSync(dest); } catch {}
|
|
96
|
+
reject(err);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Absolute request timeout to avoid hanging forever
|
|
100
|
+
req.setTimeout(120000, () => {
|
|
101
|
+
req.destroy(new Error('download timed out'));
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
attempt(url, maxRedirects);
|
|
56
106
|
});
|
|
107
|
+
|
|
108
|
+
let attemptNum = 0;
|
|
109
|
+
while (true) {
|
|
110
|
+
try {
|
|
111
|
+
return await doAttempt();
|
|
112
|
+
} catch (e) {
|
|
113
|
+
attemptNum += 1;
|
|
114
|
+
if (attemptNum > maxRetries) throw e;
|
|
115
|
+
const backoff = Math.min(2000, 200 * attemptNum);
|
|
116
|
+
await sleep(backoff);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function validateDownloadedBinary(p) {
|
|
122
|
+
try {
|
|
123
|
+
const st = statSync(p);
|
|
124
|
+
if (!st.isFile() || st.size === 0) {
|
|
125
|
+
return { ok: false, reason: 'empty or not a regular file' };
|
|
126
|
+
}
|
|
127
|
+
const fd = openSync(p, 'r');
|
|
128
|
+
try {
|
|
129
|
+
const buf = Buffer.alloc(4);
|
|
130
|
+
const n = readSync(fd, buf, 0, 4, 0);
|
|
131
|
+
if (n < 2) return { ok: false, reason: 'too short' };
|
|
132
|
+
const plt = platform();
|
|
133
|
+
if (plt === 'win32') {
|
|
134
|
+
if (!(buf[0] === 0x4d && buf[1] === 0x5a)) return { ok: false, reason: 'invalid PE header (missing MZ)' };
|
|
135
|
+
} else if (plt === 'linux' || plt === 'android') {
|
|
136
|
+
if (!(buf[0] === 0x7f && buf[1] === 0x45 && buf[2] === 0x4c && buf[3] === 0x46)) return { ok: false, reason: 'invalid ELF header' };
|
|
137
|
+
} else if (plt === 'darwin') {
|
|
138
|
+
const isMachO = (buf[0] === 0xcf && buf[1] === 0xfa && buf[2] === 0xed && buf[3] === 0xfe) ||
|
|
139
|
+
(buf[0] === 0xca && buf[1] === 0xfe && buf[2] === 0xba && buf[3] === 0xbe);
|
|
140
|
+
if (!isMachO) return { ok: false, reason: 'invalid Mach-O header' };
|
|
141
|
+
}
|
|
142
|
+
return { ok: true };
|
|
143
|
+
} finally {
|
|
144
|
+
closeSync(fd);
|
|
145
|
+
}
|
|
146
|
+
} catch (e) {
|
|
147
|
+
return { ok: false, reason: e.message };
|
|
148
|
+
}
|
|
57
149
|
}
|
|
58
150
|
|
|
59
151
|
async function main() {
|
|
@@ -124,7 +216,14 @@ async function main() {
|
|
|
124
216
|
console.log(`Downloading ${binaryName}...`);
|
|
125
217
|
try {
|
|
126
218
|
await downloadBinary(downloadUrl, localPath);
|
|
127
|
-
|
|
219
|
+
|
|
220
|
+
// Validate header to avoid corrupt binaries causing spawn EFTYPE/ENOEXEC
|
|
221
|
+
const valid = validateDownloadedBinary(localPath);
|
|
222
|
+
if (!valid.ok) {
|
|
223
|
+
try { unlinkSync(localPath); } catch {}
|
|
224
|
+
throw new Error(`invalid binary (${valid.reason})`);
|
|
225
|
+
}
|
|
226
|
+
|
|
128
227
|
// Make executable on Unix-like systems
|
|
129
228
|
if (!isWindows) {
|
|
130
229
|
chmodSync(localPath, 0o755);
|
|
@@ -143,6 +242,20 @@ async function main() {
|
|
|
143
242
|
const mainBinaryPath = join(binDir, mainBinary);
|
|
144
243
|
|
|
145
244
|
if (existsSync(mainBinaryPath)) {
|
|
245
|
+
try {
|
|
246
|
+
const stats = statSync(mainBinaryPath);
|
|
247
|
+
if (!stats.size) {
|
|
248
|
+
throw new Error('binary is empty (download likely failed)');
|
|
249
|
+
}
|
|
250
|
+
const valid = validateDownloadedBinary(mainBinaryPath);
|
|
251
|
+
if (!valid.ok) {
|
|
252
|
+
console.warn(`⚠ Main code binary appears invalid: ${valid.reason}`);
|
|
253
|
+
console.warn(' Try reinstalling or check your network/proxy settings.');
|
|
254
|
+
}
|
|
255
|
+
} catch (e) {
|
|
256
|
+
console.warn(`⚠ Main code binary appears invalid: ${e.message}`);
|
|
257
|
+
console.warn(' Try reinstalling or check your network/proxy settings.');
|
|
258
|
+
}
|
|
146
259
|
console.log('Setting up main code binary...');
|
|
147
260
|
|
|
148
261
|
// On Windows, we can't use symlinks easily, so update the JS wrapper
|