@just-every/code 0.2.118 → 0.2.120
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 +38 -14
- package/package.json +9 -6
- package/postinstall.js +149 -49
- package/scripts/preinstall.js +69 -0
- package/scripts/windows-cleanup.ps1 +32 -0
package/bin/coder.js
CHANGED
|
@@ -177,9 +177,12 @@ const tryBootstrapBinary = async () => {
|
|
|
177
177
|
if (existsSync(cachePath)) {
|
|
178
178
|
const v = validateBinary(cachePath);
|
|
179
179
|
if (v.ok) {
|
|
180
|
-
|
|
181
|
-
if (platform !== "win32")
|
|
182
|
-
|
|
180
|
+
// Prefer running directly from cache; mirror into node_modules on Unix
|
|
181
|
+
if (platform !== "win32") {
|
|
182
|
+
copyFileSync(cachePath, binaryPath);
|
|
183
|
+
try { chmodSync(binaryPath, 0o755); } catch {}
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
183
186
|
}
|
|
184
187
|
}
|
|
185
188
|
|
|
@@ -202,11 +205,13 @@ const tryBootstrapBinary = async () => {
|
|
|
202
205
|
const pkgDir = path.dirname(pkgJson);
|
|
203
206
|
const src = path.join(pkgDir, "bin", `code-${targetTriple}${platform === "win32" ? ".exe" : ""}`);
|
|
204
207
|
if (existsSync(src)) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
208
|
+
// Always ensure cache has the binary; on Unix mirror into node_modules
|
|
209
|
+
copyFileSync(src, cachePath);
|
|
210
|
+
if (platform !== "win32") {
|
|
211
|
+
copyFileSync(cachePath, binaryPath);
|
|
212
|
+
try { chmodSync(binaryPath, 0o755); } catch {}
|
|
213
|
+
}
|
|
214
|
+
return true;
|
|
210
215
|
}
|
|
211
216
|
} catch { /* ignore and fall back */ }
|
|
212
217
|
}
|
|
@@ -239,10 +244,19 @@ const tryBootstrapBinary = async () => {
|
|
|
239
244
|
try { unlinkSync(tmp); } catch {}
|
|
240
245
|
}
|
|
241
246
|
}
|
|
242
|
-
|
|
247
|
+
// On Windows, prefer cache and avoid leaving the executable in node_modules
|
|
248
|
+
if (platform === "win32") {
|
|
249
|
+
try {
|
|
250
|
+
copyFileSync(binaryPath, cachePath);
|
|
251
|
+
} catch {}
|
|
252
|
+
try { unlinkSync(binaryPath); } catch {}
|
|
253
|
+
} else {
|
|
254
|
+
try { copyFileSync(binaryPath, cachePath); } catch {}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const v = validateBinary(platform === "win32" ? cachePath : binaryPath);
|
|
243
258
|
if (!v.ok) throw new Error(`invalid binary (${v.reason})`);
|
|
244
|
-
if (platform !== "win32") chmodSync(binaryPath, 0o755);
|
|
245
|
-
try { copyFileSync(binaryPath, cachePath); } catch {}
|
|
259
|
+
if (platform !== "win32") try { chmodSync(binaryPath, 0o755); } catch {}
|
|
246
260
|
return true;
|
|
247
261
|
})
|
|
248
262
|
.catch((_e) => false);
|
|
@@ -262,9 +276,19 @@ if (!existsSync(binaryPath) && !existsSync(legacyBinaryPath)) {
|
|
|
262
276
|
}
|
|
263
277
|
}
|
|
264
278
|
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
|
|
279
|
+
// Prefer cached binary when available
|
|
280
|
+
try {
|
|
281
|
+
const pkg = JSON.parse(readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
|
|
282
|
+
const version = pkg.version;
|
|
283
|
+
const cached = getCachedBinaryPath(version);
|
|
284
|
+
const v = existsSync(cached) ? validateBinary(cached) : { ok: false };
|
|
285
|
+
if (v.ok) {
|
|
286
|
+
binaryPath = cached;
|
|
287
|
+
} else if (!existsSync(binaryPath) && existsSync(legacyBinaryPath)) {
|
|
288
|
+
binaryPath = legacyBinaryPath;
|
|
289
|
+
}
|
|
290
|
+
} catch {
|
|
291
|
+
// ignore
|
|
268
292
|
}
|
|
269
293
|
|
|
270
294
|
// Check if binary exists and try to fix permissions if needed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@just-every/code",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.120",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Lightweight coding agent that runs in your terminal - fork of OpenAI Codex",
|
|
6
6
|
"bin": {
|
|
@@ -14,9 +14,12 @@
|
|
|
14
14
|
"bin/coder.js",
|
|
15
15
|
"bin/codex.js",
|
|
16
16
|
"postinstall.js",
|
|
17
|
+
"scripts/preinstall.js",
|
|
18
|
+
"scripts/windows-cleanup.ps1",
|
|
17
19
|
"dist"
|
|
18
20
|
],
|
|
19
21
|
"scripts": {
|
|
22
|
+
"preinstall": "node scripts/preinstall.js",
|
|
20
23
|
"postinstall": "node postinstall.js",
|
|
21
24
|
"prepublishOnly": "node -e \"const fs=require('fs'),path=require('path'); const repoGit=path.join(__dirname,'..','.git'); const inCi=process.env.GITHUB_ACTIONS==='true'||process.env.CI==='true'; if(fs.existsSync(repoGit) && !inCi){ console.error('Refusing to publish from codex-cli. Use codex-cli/scripts/stage_release.sh to stage a release.'); process.exit(1);} else { console.log(inCi ? 'CI publish detected.' : 'Publishing staged package...'); }\""
|
|
22
25
|
},
|
|
@@ -32,10 +35,10 @@
|
|
|
32
35
|
"prettier": "^3.3.3"
|
|
33
36
|
},
|
|
34
37
|
"optionalDependencies": {
|
|
35
|
-
"@just-every/code-darwin-arm64": "0.2.
|
|
36
|
-
"@just-every/code-darwin-x64": "0.2.
|
|
37
|
-
"@just-every/code-linux-x64-musl": "0.2.
|
|
38
|
-
"@just-every/code-linux-arm64-musl": "0.2.
|
|
39
|
-
"@just-every/code-win32-x64": "0.2.
|
|
38
|
+
"@just-every/code-darwin-arm64": "0.2.120",
|
|
39
|
+
"@just-every/code-darwin-x64": "0.2.120",
|
|
40
|
+
"@just-every/code-linux-x64-musl": "0.2.120",
|
|
41
|
+
"@just-every/code-linux-arm64-musl": "0.2.120",
|
|
42
|
+
"@just-every/code-win32-x64": "0.2.120"
|
|
40
43
|
}
|
|
41
44
|
}
|
package/postinstall.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
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, unlinkSync, statSync, openSync, closeSync, copyFileSync } from 'fs';
|
|
4
|
+
import { existsSync, mkdirSync, createWriteStream, chmodSync, readFileSync, readSync, writeFileSync, unlinkSync, statSync, openSync, closeSync, copyFileSync, fsyncSync, renameSync, realpathSync } from 'fs';
|
|
5
5
|
import { join, dirname } from 'path';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { get } from 'https';
|
|
8
|
-
import { platform, arch } from 'os';
|
|
8
|
+
import { platform, arch, tmpdir } from 'os';
|
|
9
9
|
import { execSync } from 'child_process';
|
|
10
10
|
import { createRequire } from 'module';
|
|
11
11
|
|
|
@@ -54,6 +54,56 @@ function getCachedBinaryPath(version, targetTriple, isWindows) {
|
|
|
54
54
|
return join(cacheDir, `code-${targetTriple}${ext}`);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
function isWSL() {
|
|
58
|
+
if (platform() !== 'linux') return false;
|
|
59
|
+
try {
|
|
60
|
+
const ver = readFileSync('/proc/version', 'utf8').toLowerCase();
|
|
61
|
+
return ver.includes('microsoft') || !!process.env.WSL_DISTRO_NAME;
|
|
62
|
+
} catch { return false; }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isPathOnWindowsFs(p) {
|
|
66
|
+
try {
|
|
67
|
+
const mounts = readFileSync('/proc/mounts', 'utf8').split(/\n/).filter(Boolean);
|
|
68
|
+
let best = { mount: '/', type: 'unknown', len: 1 };
|
|
69
|
+
for (const line of mounts) {
|
|
70
|
+
const parts = line.split(' ');
|
|
71
|
+
if (parts.length < 3) continue;
|
|
72
|
+
const mnt = parts[1];
|
|
73
|
+
const typ = parts[2];
|
|
74
|
+
if (p.startsWith(mnt) && mnt.length > best.len) best = { mount: mnt, type: typ, len: mnt.length };
|
|
75
|
+
}
|
|
76
|
+
return best.type === 'drvfs' || best.type === 'cifs';
|
|
77
|
+
} catch { return false; }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function writeCacheAtomic(srcPath, cachePath) {
|
|
81
|
+
try {
|
|
82
|
+
if (existsSync(cachePath)) {
|
|
83
|
+
const ok = validateDownloadedBinary(cachePath).ok;
|
|
84
|
+
if (ok) return;
|
|
85
|
+
}
|
|
86
|
+
} catch {}
|
|
87
|
+
const dir = dirname(cachePath);
|
|
88
|
+
if (!existsSync(dir)) { try { mkdirSync(dir, { recursive: true }); } catch {} }
|
|
89
|
+
const tmp = cachePath + '.tmp-' + Math.random().toString(36).slice(2, 8);
|
|
90
|
+
copyFileSync(srcPath, tmp);
|
|
91
|
+
try { const fd = openSync(tmp, 'r'); try { fsyncSync(fd); } finally { closeSync(fd); } } catch {}
|
|
92
|
+
// Retry with exponential backoff up to ~1.6s total
|
|
93
|
+
const delays = [100, 200, 400, 800, 1200, 1600];
|
|
94
|
+
for (let i = 0; i < delays.length; i++) {
|
|
95
|
+
try {
|
|
96
|
+
if (existsSync(cachePath)) { try { unlinkSync(cachePath); } catch {} }
|
|
97
|
+
renameSync(tmp, cachePath);
|
|
98
|
+
return;
|
|
99
|
+
} catch {
|
|
100
|
+
await new Promise(r => setTimeout(r, delays[i]));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (existsSync(cachePath)) { try { unlinkSync(cachePath); } catch {} }
|
|
104
|
+
renameSync(tmp, cachePath);
|
|
105
|
+
}
|
|
106
|
+
|
|
57
107
|
async function downloadBinary(url, dest, maxRedirects = 5, maxRetries = 3) {
|
|
58
108
|
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
59
109
|
|
|
@@ -227,30 +277,26 @@ async function main() {
|
|
|
227
277
|
const localPath = join(binDir, binaryName);
|
|
228
278
|
const cachePath = getCachedBinaryPath(version, targetTriple, isWindows);
|
|
229
279
|
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
chmodSync(localPath, 0o755);
|
|
236
|
-
console.log(`✓ ${binaryName} already exists (permissions fixed)`);
|
|
237
|
-
} catch (e) {
|
|
238
|
-
console.log(`✓ ${binaryName} already exists`);
|
|
239
|
-
}
|
|
240
|
-
} else {
|
|
241
|
-
console.log(`✓ ${binaryName} already exists`);
|
|
242
|
-
}
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
280
|
+
// On Windows we avoid placing the executable inside node_modules to prevent
|
|
281
|
+
// EBUSY/EPERM during global upgrades when the binary is in use.
|
|
282
|
+
// We treat the user cache path as the canonical home of the native binary.
|
|
283
|
+
// For macOS/Linux we keep previous behavior and also place a copy in binDir
|
|
284
|
+
// for convenience.
|
|
245
285
|
|
|
246
286
|
// Fast path: if a valid cached binary exists for this version+triple, reuse it.
|
|
247
287
|
try {
|
|
248
288
|
if (existsSync(cachePath)) {
|
|
249
289
|
const valid = validateDownloadedBinary(cachePath);
|
|
250
290
|
if (valid.ok) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
291
|
+
// Avoid mirroring into node_modules on Windows or WSL-on-NTFS.
|
|
292
|
+
const wsl = isWSL();
|
|
293
|
+
const binDirReal = (() => { try { return realpathSync(binDir); } catch { return binDir; } })();
|
|
294
|
+
const mirrorToLocal = !(isWindows || (wsl && isPathOnWindowsFs(binDirReal)));
|
|
295
|
+
if (mirrorToLocal) {
|
|
296
|
+
copyFileSync(cachePath, localPath);
|
|
297
|
+
try { chmodSync(localPath, 0o755); } catch {}
|
|
298
|
+
}
|
|
299
|
+
console.log(`✓ ${binaryName} ready from user cache`);
|
|
254
300
|
continue; // next binary
|
|
255
301
|
}
|
|
256
302
|
}
|
|
@@ -288,15 +334,17 @@ async function main() {
|
|
|
288
334
|
if (!existsSync(src)) {
|
|
289
335
|
throw new Error(`platform package missing binary: ${platformPkg.name}`);
|
|
290
336
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
try {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
337
|
+
// Populate cache first (canonical location) atomically
|
|
338
|
+
await writeCacheAtomic(src, cachePath);
|
|
339
|
+
// Mirror into local bin only on Unix-like filesystems (not Windows/WSL-on-NTFS)
|
|
340
|
+
const wsl = isWSL();
|
|
341
|
+
const binDirReal = (() => { try { return realpathSync(binDir); } catch { return binDir; } })();
|
|
342
|
+
const mirrorToLocal = !(isWindows || (wsl && isPathOnWindowsFs(binDirReal)));
|
|
343
|
+
if (mirrorToLocal) {
|
|
344
|
+
copyFileSync(cachePath, localPath);
|
|
345
|
+
try { chmodSync(localPath, 0o755); } catch {}
|
|
346
|
+
}
|
|
347
|
+
console.log(`✓ Installed ${binaryName} from ${platformPkg.name} (cached)`);
|
|
300
348
|
continue; // next binary
|
|
301
349
|
} catch (e) {
|
|
302
350
|
console.warn(`⚠ Failed platform package install (${e.message}), falling back to GitHub download`);
|
|
@@ -307,6 +355,15 @@ async function main() {
|
|
|
307
355
|
// - Windows: .zip
|
|
308
356
|
// - macOS/Linux: prefer .zst if `zstd` CLI is available; otherwise use .tar.gz
|
|
309
357
|
const isWin = isWindows;
|
|
358
|
+
const isWSL = (() => {
|
|
359
|
+
if (platform() !== 'linux') return false;
|
|
360
|
+
try {
|
|
361
|
+
const ver = readFileSync('/proc/version', 'utf8').toLowerCase();
|
|
362
|
+
return ver.includes('microsoft') || !!process.env.WSL_DISTRO_NAME;
|
|
363
|
+
} catch { return false; }
|
|
364
|
+
})();
|
|
365
|
+
const binDirReal = (() => { try { return realpathSync(binDir); } catch { return binDir; } })();
|
|
366
|
+
const mirrorToLocal = !(isWin || (isWSL && isPathOnWindowsFs(binDirReal)));
|
|
310
367
|
let useZst = false;
|
|
311
368
|
if (!isWin) {
|
|
312
369
|
try {
|
|
@@ -321,24 +378,57 @@ async function main() {
|
|
|
321
378
|
|
|
322
379
|
console.log(`Downloading ${archiveName}...`);
|
|
323
380
|
try {
|
|
324
|
-
const
|
|
381
|
+
const needsIsolation = isWin || (!isWin && !mirrorToLocal); // Windows or WSL-on-NTFS
|
|
382
|
+
let safeTempDir = needsIsolation ? join(tmpdir(), 'just-every', 'code', version) : binDir;
|
|
383
|
+
// Ensure staging dir exists; if tmp fails (permissions/space), fall back to user cache.
|
|
384
|
+
if (needsIsolation) {
|
|
385
|
+
try {
|
|
386
|
+
if (!existsSync(safeTempDir)) mkdirSync(safeTempDir, { recursive: true });
|
|
387
|
+
} catch {
|
|
388
|
+
try {
|
|
389
|
+
safeTempDir = getCacheDir(version);
|
|
390
|
+
if (!existsSync(safeTempDir)) mkdirSync(safeTempDir, { recursive: true });
|
|
391
|
+
} catch {}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const tmpPath = join(needsIsolation ? safeTempDir : binDir, `.${archiveName}.part`);
|
|
325
395
|
await downloadBinary(downloadUrl, tmpPath);
|
|
326
396
|
|
|
327
397
|
if (isWin) {
|
|
328
|
-
// Unzip
|
|
398
|
+
// Unzip to a temp directory, then move into the per-user cache.
|
|
399
|
+
const unzipDest = safeTempDir;
|
|
329
400
|
try {
|
|
330
|
-
const
|
|
331
|
-
|
|
401
|
+
const sysRoot = process.env.SystemRoot || process.env.windir || 'C:\\Windows';
|
|
402
|
+
const psFull = join(sysRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
403
|
+
const psCmd = `Expand-Archive -Path '${tmpPath}' -DestinationPath '${unzipDest}' -Force`;
|
|
404
|
+
let ok = false;
|
|
405
|
+
// Attempt full-path powershell.exe
|
|
406
|
+
try { execSync(`"${psFull}" -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: 'ignore' }); ok = true; } catch {}
|
|
407
|
+
// Fallback to powershell in PATH
|
|
408
|
+
if (!ok) { try { execSync(`powershell -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: 'ignore' }); ok = true; } catch {} }
|
|
409
|
+
// Fallback to pwsh (PowerShell 7)
|
|
410
|
+
if (!ok) { try { execSync(`pwsh -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: 'ignore' }); ok = true; } catch {} }
|
|
411
|
+
// Final fallback: bsdtar can extract .zip
|
|
412
|
+
if (!ok) { execSync(`tar -xf "${tmpPath}" -C "${unzipDest}"`, { stdio: 'ignore', shell: true }); }
|
|
332
413
|
} catch (e) {
|
|
333
414
|
throw new Error(`failed to unzip archive: ${e.message}`);
|
|
334
415
|
} finally {
|
|
335
416
|
try { unlinkSync(tmpPath); } catch {}
|
|
336
417
|
}
|
|
418
|
+
// Move the extracted file from temp to cache; do not leave a copy in node_modules
|
|
419
|
+
try {
|
|
420
|
+
const extractedPath = join(unzipDest, binaryName);
|
|
421
|
+
await writeCacheAtomic(extractedPath, cachePath);
|
|
422
|
+
try { unlinkSync(extractedPath); } catch {}
|
|
423
|
+
} catch (e) {
|
|
424
|
+
throw new Error(`failed to move binary to cache: ${e.message}`);
|
|
425
|
+
}
|
|
337
426
|
} else {
|
|
338
427
|
if (useZst) {
|
|
339
428
|
// Decompress .zst via system zstd
|
|
340
429
|
try {
|
|
341
|
-
|
|
430
|
+
const outPath = mirrorToLocal ? localPath : join(safeTempDir, binaryName);
|
|
431
|
+
execSync(`zstd -d '${tmpPath}' -o '${outPath}'`, { stdio: 'ignore', shell: true });
|
|
342
432
|
} catch (e) {
|
|
343
433
|
try { unlinkSync(tmpPath); } catch {}
|
|
344
434
|
throw new Error(`failed to decompress .zst (need zstd CLI): ${e.message}`);
|
|
@@ -347,32 +437,43 @@ async function main() {
|
|
|
347
437
|
} else {
|
|
348
438
|
// Extract .tar.gz using system tar
|
|
349
439
|
try {
|
|
350
|
-
|
|
440
|
+
const dest = mirrorToLocal ? binDir : safeTempDir;
|
|
441
|
+
execSync(`tar -xzf '${tmpPath}' -C '${dest}'`, { stdio: 'ignore', shell: true });
|
|
351
442
|
} catch (e) {
|
|
352
443
|
try { unlinkSync(tmpPath); } catch {}
|
|
353
444
|
throw new Error(`failed to extract .tar.gz: ${e.message}`);
|
|
354
445
|
}
|
|
355
446
|
try { unlinkSync(tmpPath); } catch {}
|
|
356
447
|
}
|
|
448
|
+
if (!mirrorToLocal) {
|
|
449
|
+
try {
|
|
450
|
+
const extractedPath = join(safeTempDir, binaryName);
|
|
451
|
+
await writeCacheAtomic(extractedPath, cachePath);
|
|
452
|
+
try { unlinkSync(extractedPath); } catch {}
|
|
453
|
+
} catch (e) {
|
|
454
|
+
throw new Error(`failed to move binary to cache: ${e.message}`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
357
457
|
}
|
|
358
458
|
|
|
359
459
|
// Validate header to avoid corrupt binaries causing spawn EFTYPE/ENOEXEC
|
|
360
|
-
|
|
460
|
+
|
|
461
|
+
const valid = validateDownloadedBinary(isWin ? cachePath : (mirrorToLocal ? localPath : cachePath));
|
|
361
462
|
if (!valid.ok) {
|
|
362
|
-
try { unlinkSync(localPath); } catch {}
|
|
463
|
+
try { (isWin || !mirrorToLocal) ? unlinkSync(cachePath) : unlinkSync(localPath); } catch {}
|
|
363
464
|
throw new Error(`invalid binary (${valid.reason})`);
|
|
364
465
|
}
|
|
365
466
|
|
|
366
467
|
// Make executable on Unix-like systems
|
|
367
|
-
if (!
|
|
468
|
+
if (!isWin && mirrorToLocal) {
|
|
368
469
|
chmodSync(localPath, 0o755);
|
|
369
470
|
}
|
|
370
471
|
|
|
371
|
-
console.log(`✓ Installed ${binaryName}`);
|
|
372
|
-
//
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
}
|
|
472
|
+
console.log(`✓ Installed ${binaryName}${(isWin || !mirrorToLocal) ? ' (cached)' : ''}`);
|
|
473
|
+
// Ensure persistent cache holds the binary (already true for Windows path)
|
|
474
|
+
if (!isWin && mirrorToLocal) {
|
|
475
|
+
try { await writeCacheAtomic(localPath, cachePath); } catch {}
|
|
476
|
+
}
|
|
376
477
|
} catch (error) {
|
|
377
478
|
console.error(`✗ Failed to install ${binaryName}: ${error.message}`);
|
|
378
479
|
console.error(` Downloaded from: ${downloadUrl}`);
|
|
@@ -384,13 +485,12 @@ async function main() {
|
|
|
384
485
|
const mainBinary = `code-${targetTriple}${binaryExt}`;
|
|
385
486
|
const mainBinaryPath = join(binDir, mainBinary);
|
|
386
487
|
|
|
387
|
-
if (existsSync(mainBinaryPath)) {
|
|
488
|
+
if (existsSync(mainBinaryPath) || existsSync(getCachedBinaryPath(version, targetTriple, platform() === 'win32'))) {
|
|
388
489
|
try {
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const valid = validateDownloadedBinary(mainBinaryPath);
|
|
490
|
+
const probePath = existsSync(mainBinaryPath) ? mainBinaryPath : getCachedBinaryPath(version, targetTriple, platform() === 'win32');
|
|
491
|
+
const stats = statSync(probePath);
|
|
492
|
+
if (!stats.size) throw new Error('binary is empty (download likely failed)');
|
|
493
|
+
const valid = validateDownloadedBinary(probePath);
|
|
394
494
|
if (!valid.ok) {
|
|
395
495
|
console.warn(`⚠ Main code binary appears invalid: ${valid.reason}`);
|
|
396
496
|
console.warn(' Try reinstalling or check your network/proxy settings.');
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Windows-friendly preinstall: proactively free file locks from prior installs
|
|
3
|
+
// so npm/yarn/pnpm can stage the new package. No-ops on non-Windows.
|
|
4
|
+
|
|
5
|
+
import { platform } from 'os';
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
import { existsSync, readdirSync, rmSync, readFileSync, statSync } from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
11
|
+
function isWSL() {
|
|
12
|
+
if (platform() !== 'linux') return false;
|
|
13
|
+
try {
|
|
14
|
+
const rel = readFileSync('/proc/version', 'utf8').toLowerCase();
|
|
15
|
+
return rel.includes('microsoft') || !!process.env.WSL_DISTRO_NAME;
|
|
16
|
+
} catch { return false; }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const isWin = platform() === 'win32';
|
|
20
|
+
const wsl = isWSL();
|
|
21
|
+
const isWinLike = isWin || wsl;
|
|
22
|
+
|
|
23
|
+
// Scope: only run for global installs, unless explicitly forced. Allow opt-out.
|
|
24
|
+
const isGlobal = process.env.npm_config_global === 'true';
|
|
25
|
+
const force = process.env.CODE_FORCE_PREINSTALL === '1';
|
|
26
|
+
const skip = process.env.CODE_SKIP_PREINSTALL === '1';
|
|
27
|
+
if (!isWinLike || skip || (!isGlobal && !force)) process.exit(0);
|
|
28
|
+
|
|
29
|
+
function tryExec(cmd, opts = {}) {
|
|
30
|
+
try { execSync(cmd, { stdio: ['ignore', 'ignore', 'ignore'], shell: true, ...opts }); } catch { /* ignore */ }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 1) Stop our native binary if it is holding locks. Avoid killing unrelated tools.
|
|
34
|
+
// Only available on native Windows; skip entirely on WSL to avoid noise.
|
|
35
|
+
if (isWin) {
|
|
36
|
+
tryExec('taskkill /IM code-x86_64-pc-windows-msvc.exe /F');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 2) Remove stale staging dirs from previous failed installs under the global
|
|
40
|
+
// @just-every scope, which npm will reuse (e.g., .code-XXXXX). Remove only
|
|
41
|
+
// old entries and never the current staging or live package.
|
|
42
|
+
try {
|
|
43
|
+
let scopeDir = '';
|
|
44
|
+
try {
|
|
45
|
+
const root = execSync('npm root -g', { stdio: ['ignore', 'pipe', 'ignore'], shell: true }).toString().trim();
|
|
46
|
+
scopeDir = path.join(root, '@just-every');
|
|
47
|
+
} catch {
|
|
48
|
+
// Fall back to guessing from this script location: <staging>\..\..\
|
|
49
|
+
const here = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
|
|
50
|
+
scopeDir = path.resolve(here, '..');
|
|
51
|
+
}
|
|
52
|
+
if (existsSync(scopeDir)) {
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
const maxAgeMs = 2 * 60 * 60 * 1000; // 2 hours
|
|
55
|
+
const currentDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
56
|
+
for (const name of readdirSync(scopeDir)) {
|
|
57
|
+
if (!name.startsWith('.code-')) continue;
|
|
58
|
+
const p = path.join(scopeDir, name);
|
|
59
|
+
if (path.resolve(p) === currentDir) continue; // never remove our current dir
|
|
60
|
+
try {
|
|
61
|
+
const st = statSync(p);
|
|
62
|
+
const age = now - st.mtimeMs;
|
|
63
|
+
if (age > maxAgeMs) rmSync(p, { recursive: true, force: true });
|
|
64
|
+
} catch { /* ignore */ }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} catch { /* ignore */ }
|
|
68
|
+
|
|
69
|
+
process.exit(0);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<#
|
|
2
|
+
Helper to recover from EBUSY/EPERM during global npm upgrades on Windows.
|
|
3
|
+
Closes running processes and removes stale package folders.
|
|
4
|
+
|
|
5
|
+
Usage (PowerShell):
|
|
6
|
+
Set-ExecutionPolicy -Scope Process Bypass -Force
|
|
7
|
+
./codex-cli/scripts/windows-cleanup.ps1
|
|
8
|
+
#>
|
|
9
|
+
|
|
10
|
+
$ErrorActionPreference = 'SilentlyContinue'
|
|
11
|
+
|
|
12
|
+
Write-Host "Stopping running Code/Coder processes..."
|
|
13
|
+
taskkill /IM code-x86_64-pc-windows-msvc.exe /F 2>$null | Out-Null
|
|
14
|
+
taskkill /IM code.exe /F 2>$null | Out-Null
|
|
15
|
+
taskkill /IM coder.exe /F 2>$null | Out-Null
|
|
16
|
+
|
|
17
|
+
Write-Host "Removing old global package (if present)..."
|
|
18
|
+
$npmRoot = (& npm root -g).Trim()
|
|
19
|
+
$pkgPath = Join-Path $npmRoot "@just-every\code"
|
|
20
|
+
if (Test-Path $pkgPath) {
|
|
21
|
+
try { Remove-Item -LiteralPath $pkgPath -Recurse -Force -ErrorAction Stop } catch {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
Write-Host "Removing temp staging directories (if present)..."
|
|
25
|
+
Get-ChildItem -LiteralPath (Join-Path $npmRoot "@just-every") -Force -ErrorAction SilentlyContinue |
|
|
26
|
+
Where-Object { $_.Name -like '.code-*' } |
|
|
27
|
+
ForEach-Object {
|
|
28
|
+
try { Remove-Item -LiteralPath $_.FullName -Recurse -Force -ErrorAction Stop } catch {}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
Write-Host "Cleanup complete. You can now run: npm install -g @just-every/code@latest"
|
|
32
|
+
|