@just-every/code 0.2.2 → 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 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
- if (err.code === 'EACCES') {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@just-every/code",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Lightweight coding agent that runs in your terminal - fork of OpenAI Codex",
6
6
  "bin": {
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 file = createWriteStream(dest);
35
-
36
- get(url, (response) => {
37
- if (response.statusCode === 302 || response.statusCode === 301) {
38
- // Follow redirect
39
- get(response.headers.location, (redirectResponse) => {
40
- redirectResponse.pipe(file);
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
- }).on('error', reject);
46
- } else if (response.statusCode === 200) {
47
- response.pipe(file);
48
- file.on('finish', () => {
49
- file.close();
50
- resolve();
51
- });
52
- } else {
53
- reject(new Error(`Failed to download: ${response.statusCode}`));
54
- }
55
- }).on('error', reject);
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