@just-every/code 0.2.3 → 0.2.4
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 +55 -2
- package/package.json +1 -1
- package/postinstall.js +86 -21
package/bin/coder.js
CHANGED
|
@@ -63,7 +63,7 @@ if (!existsSync(binaryPath)) {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// Check if binary exists and try to fix permissions if needed
|
|
66
|
-
import { existsSync, chmodSync } from "fs";
|
|
66
|
+
import { existsSync, chmodSync, statSync, openSync, readSync, closeSync } from "fs";
|
|
67
67
|
if (existsSync(binaryPath)) {
|
|
68
68
|
try {
|
|
69
69
|
// Ensure binary is executable on Unix-like systems
|
|
@@ -81,6 +81,50 @@ if (existsSync(binaryPath)) {
|
|
|
81
81
|
process.exit(1);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
// Lightweight header validation to provide clearer errors before spawn
|
|
85
|
+
const validateBinary = (p) => {
|
|
86
|
+
try {
|
|
87
|
+
const st = statSync(p);
|
|
88
|
+
if (!st.isFile() || st.size === 0) {
|
|
89
|
+
return { ok: false, reason: "empty or not a regular file" };
|
|
90
|
+
}
|
|
91
|
+
const fd = openSync(p, "r");
|
|
92
|
+
try {
|
|
93
|
+
const buf = Buffer.alloc(4);
|
|
94
|
+
const n = readSync(fd, buf, 0, 4, 0);
|
|
95
|
+
if (n < 2) return { ok: false, reason: "too short" };
|
|
96
|
+
if (platform === "win32") {
|
|
97
|
+
if (!(buf[0] === 0x4d && buf[1] === 0x5a)) return { ok: false, reason: "invalid PE header (missing MZ)" };
|
|
98
|
+
} else if (platform === "linux" || platform === "android") {
|
|
99
|
+
if (!(buf[0] === 0x7f && buf[1] === 0x45 && buf[2] === 0x4c && buf[3] === 0x46)) return { ok: false, reason: "invalid ELF header" };
|
|
100
|
+
} else if (platform === "darwin") {
|
|
101
|
+
const isMachO = (buf[0] === 0xcf && buf[1] === 0xfa && buf[2] === 0xed && buf[3] === 0xfe) ||
|
|
102
|
+
(buf[0] === 0xca && buf[1] === 0xfe && buf[2] === 0xba && buf[3] === 0xbe);
|
|
103
|
+
if (!isMachO) return { ok: false, reason: "invalid Mach-O header" };
|
|
104
|
+
}
|
|
105
|
+
} finally {
|
|
106
|
+
closeSync(fd);
|
|
107
|
+
}
|
|
108
|
+
return { ok: true };
|
|
109
|
+
} catch (e) {
|
|
110
|
+
return { ok: false, reason: e.message };
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const validation = validateBinary(binaryPath);
|
|
115
|
+
if (!validation.ok) {
|
|
116
|
+
console.error(`The native binary at ${binaryPath} appears invalid: ${validation.reason}`);
|
|
117
|
+
console.error("This can happen if the download failed or was modified by antivirus/proxy.");
|
|
118
|
+
console.error("Please try reinstalling:");
|
|
119
|
+
console.error(" npm uninstall -g @just-every/code");
|
|
120
|
+
console.error(" npm install -g @just-every/code");
|
|
121
|
+
if (platform === "win32") {
|
|
122
|
+
console.error("If the issue persists, clear npm cache and disable antivirus temporarily:");
|
|
123
|
+
console.error(" npm cache clean --force");
|
|
124
|
+
}
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
|
|
84
128
|
// Use an asynchronous spawn instead of spawnSync so that Node is able to
|
|
85
129
|
// respond to signals (e.g. Ctrl-C / SIGINT) while the native binary is
|
|
86
130
|
// executing. This allows us to forward those signals to the child process
|
|
@@ -95,10 +139,19 @@ const child = spawn(binaryPath, process.argv.slice(2), {
|
|
|
95
139
|
|
|
96
140
|
child.on("error", (err) => {
|
|
97
141
|
// Typically triggered when the binary is missing or not executable.
|
|
98
|
-
|
|
142
|
+
const code = err && err.code;
|
|
143
|
+
if (code === 'EACCES') {
|
|
99
144
|
console.error(`Permission denied: ${binaryPath}`);
|
|
100
145
|
console.error(`Try running: chmod +x "${binaryPath}"`);
|
|
101
146
|
console.error(`Or reinstall the package with: npm install -g @just-every/code`);
|
|
147
|
+
} else if (code === 'EFTYPE' || code === 'ENOEXEC') {
|
|
148
|
+
console.error(`Failed to execute native binary: ${binaryPath}`);
|
|
149
|
+
console.error("The file may be corrupt or of the wrong type. Reinstall usually fixes this:");
|
|
150
|
+
console.error(" npm uninstall -g @just-every/code && npm install -g @just-every/code");
|
|
151
|
+
if (platform === 'win32') {
|
|
152
|
+
console.error("On Windows, ensure the .exe downloaded correctly (proxy/AV can interfere).");
|
|
153
|
+
console.error("Try clearing cache: npm cache clean --force");
|
|
154
|
+
}
|
|
102
155
|
} else {
|
|
103
156
|
console.error(err);
|
|
104
157
|
}
|
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,33 +29,77 @@ function getTargetTriple() {
|
|
|
29
29
|
return `${rustArch}-${rustPlatform}`;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
async function downloadBinary(url, dest) {
|
|
32
|
+
async function downloadBinary(url, dest, maxRedirects = 5) {
|
|
33
33
|
return new Promise((resolve, reject) => {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
const attempt = (currentUrl, redirectsLeft) => {
|
|
35
|
+
get(currentUrl, (response) => {
|
|
36
|
+
const status = response.statusCode || 0;
|
|
37
|
+
const location = response.headers.location;
|
|
38
|
+
|
|
39
|
+
if ((status === 301 || status === 302 || status === 303 || status === 307 || status === 308) && location) {
|
|
40
|
+
if (redirectsLeft <= 0) {
|
|
41
|
+
reject(new Error(`Too many redirects while downloading ${currentUrl}`));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
attempt(location, redirectsLeft - 1);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (status === 200) {
|
|
49
|
+
// Only create the file stream after we know it's a successful response
|
|
50
|
+
const file = createWriteStream(dest);
|
|
51
|
+
response.pipe(file);
|
|
41
52
|
file.on('finish', () => {
|
|
42
53
|
file.close();
|
|
43
54
|
resolve();
|
|
44
55
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
file.on('error', (err) => {
|
|
57
|
+
try { unlinkSync(dest); } catch {}
|
|
58
|
+
reject(err);
|
|
59
|
+
});
|
|
60
|
+
} else {
|
|
61
|
+
reject(new Error(`Failed to download: HTTP ${status}`));
|
|
62
|
+
}
|
|
63
|
+
}).on('error', (err) => {
|
|
64
|
+
try { unlinkSync(dest); } catch {}
|
|
65
|
+
reject(err);
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
attempt(url, maxRedirects);
|
|
56
70
|
});
|
|
57
71
|
}
|
|
58
72
|
|
|
73
|
+
function validateDownloadedBinary(p) {
|
|
74
|
+
try {
|
|
75
|
+
const st = statSync(p);
|
|
76
|
+
if (!st.isFile() || st.size === 0) {
|
|
77
|
+
return { ok: false, reason: 'empty or not a regular file' };
|
|
78
|
+
}
|
|
79
|
+
const fd = openSync(p, 'r');
|
|
80
|
+
try {
|
|
81
|
+
const buf = Buffer.alloc(4);
|
|
82
|
+
const n = readSync(fd, buf, 0, 4, 0);
|
|
83
|
+
if (n < 2) return { ok: false, reason: 'too short' };
|
|
84
|
+
const plt = platform();
|
|
85
|
+
if (plt === 'win32') {
|
|
86
|
+
if (!(buf[0] === 0x4d && buf[1] === 0x5a)) return { ok: false, reason: 'invalid PE header (missing MZ)' };
|
|
87
|
+
} else if (plt === 'linux' || plt === 'android') {
|
|
88
|
+
if (!(buf[0] === 0x7f && buf[1] === 0x45 && buf[2] === 0x4c && buf[3] === 0x46)) return { ok: false, reason: 'invalid ELF header' };
|
|
89
|
+
} else if (plt === 'darwin') {
|
|
90
|
+
const isMachO = (buf[0] === 0xcf && buf[1] === 0xfa && buf[2] === 0xed && buf[3] === 0xfe) ||
|
|
91
|
+
(buf[0] === 0xca && buf[1] === 0xfe && buf[2] === 0xba && buf[3] === 0xbe);
|
|
92
|
+
if (!isMachO) return { ok: false, reason: 'invalid Mach-O header' };
|
|
93
|
+
}
|
|
94
|
+
return { ok: true };
|
|
95
|
+
} finally {
|
|
96
|
+
closeSync(fd);
|
|
97
|
+
}
|
|
98
|
+
} catch (e) {
|
|
99
|
+
return { ok: false, reason: e.message };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
59
103
|
async function main() {
|
|
60
104
|
// Detect potential PATH conflict with an existing `code` command (e.g., VS Code)
|
|
61
105
|
try {
|
|
@@ -124,7 +168,14 @@ async function main() {
|
|
|
124
168
|
console.log(`Downloading ${binaryName}...`);
|
|
125
169
|
try {
|
|
126
170
|
await downloadBinary(downloadUrl, localPath);
|
|
127
|
-
|
|
171
|
+
|
|
172
|
+
// Validate header to avoid corrupt binaries causing spawn EFTYPE/ENOEXEC
|
|
173
|
+
const valid = validateDownloadedBinary(localPath);
|
|
174
|
+
if (!valid.ok) {
|
|
175
|
+
try { unlinkSync(localPath); } catch {}
|
|
176
|
+
throw new Error(`invalid binary (${valid.reason})`);
|
|
177
|
+
}
|
|
178
|
+
|
|
128
179
|
// Make executable on Unix-like systems
|
|
129
180
|
if (!isWindows) {
|
|
130
181
|
chmodSync(localPath, 0o755);
|
|
@@ -143,6 +194,20 @@ async function main() {
|
|
|
143
194
|
const mainBinaryPath = join(binDir, mainBinary);
|
|
144
195
|
|
|
145
196
|
if (existsSync(mainBinaryPath)) {
|
|
197
|
+
try {
|
|
198
|
+
const stats = statSync(mainBinaryPath);
|
|
199
|
+
if (!stats.size) {
|
|
200
|
+
throw new Error('binary is empty (download likely failed)');
|
|
201
|
+
}
|
|
202
|
+
const valid = validateDownloadedBinary(mainBinaryPath);
|
|
203
|
+
if (!valid.ok) {
|
|
204
|
+
console.warn(`⚠ Main code binary appears invalid: ${valid.reason}`);
|
|
205
|
+
console.warn(' Try reinstalling or check your network/proxy settings.');
|
|
206
|
+
}
|
|
207
|
+
} catch (e) {
|
|
208
|
+
console.warn(`⚠ Main code binary appears invalid: ${e.message}`);
|
|
209
|
+
console.warn(' Try reinstalling or check your network/proxy settings.');
|
|
210
|
+
}
|
|
146
211
|
console.log('Setting up main code binary...');
|
|
147
212
|
|
|
148
213
|
// On Windows, we can't use symlinks easily, so update the JS wrapper
|