cli(downloads): verify Content-Length, add retries/timeouts; improve WSL guidance\n\n- postinstall: bytes count vs Content-Length, inactivity/absolute timeouts, retry with backoff\n- coder.js: WSL detection + tailored guidance when binaries missing/invalid

This commit is contained in:
James Peter
2025-08-20 07:46:25 +10:00
parent c0fe2c5009
commit ca55c2e1a9
2 changed files with 81 additions and 6 deletions
+27
View File
@@ -10,6 +10,20 @@ const __dirname = path.dirname(__filename);
const { platform, arch } = process;
const isWSL = () => {
if (platform !== "linux") return false;
try {
const os = require("os");
const rel = os.release().toLowerCase();
if (rel.includes("microsoft")) return true;
const fs = require("fs");
const txt = fs.readFileSync("/proc/version", "utf8").toLowerCase();
return txt.includes("microsoft");
} catch {
return false;
}
};
let targetTriple = null;
switch (platform) {
case "linux":
@@ -78,6 +92,11 @@ if (existsSync(binaryPath)) {
console.error(`Please try reinstalling the package:`);
console.error(` npm uninstall -g @just-every/code`);
console.error(` npm install -g @just-every/code`);
if (isWSL()) {
console.error("Detected WSL. Install inside WSL (Ubuntu) separately:");
console.error(" npx -y @just-every/code@latest (run inside WSL)");
console.error("If installed globally on Windows, those binaries are not usable from WSL.");
}
process.exit(1);
}
@@ -122,6 +141,10 @@ if (!validation.ok) {
console.error("If the issue persists, clear npm cache and disable antivirus temporarily:");
console.error(" npm cache clean --force");
}
if (isWSL()) {
console.error("Detected WSL. Ensure you install/run inside WSL, not Windows:");
console.error(" npx -y @just-every/code@latest (inside WSL)");
}
process.exit(1);
}
@@ -152,6 +175,10 @@ child.on("error", (err) => {
console.error("On Windows, ensure the .exe downloaded correctly (proxy/AV can interfere).");
console.error("Try clearing cache: npm cache clean --force");
}
if (isWSL()) {
console.error("Detected WSL. Windows binaries cannot be executed from WSL.");
console.error("Install inside WSL and run there: npx -y @just-every/code@latest");
}
} else {
console.error(err);
}
+54 -6
View File
@@ -29,10 +29,12 @@ function getTargetTriple() {
return `${rustArch}-${rustPlatform}`;
}
async function downloadBinary(url, dest, maxRedirects = 5) {
return new Promise((resolve, reject) => {
async function downloadBinary(url, dest, maxRedirects = 5, maxRetries = 3) {
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
const doAttempt = () => new Promise((resolve, reject) => {
const attempt = (currentUrl, redirectsLeft) => {
get(currentUrl, (response) => {
const req = get(currentUrl, (response) => {
const status = response.statusCode || 0;
const location = response.headers.location;
@@ -46,28 +48,74 @@ async function downloadBinary(url, dest, maxRedirects = 5) {
}
if (status === 200) {
// Only create the file stream after we know it's a successful response
const expected = parseInt(response.headers['content-length'] || '0', 10) || 0;
let bytes = 0;
let timer;
const timeoutMs = 30000; // 30s inactivity timeout
const resetTimer = () => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
req.destroy(new Error('download stalled'));
}, timeoutMs);
};
resetTimer();
response.on('data', (chunk) => {
bytes += chunk.length;
resetTimer();
});
const file = createWriteStream(dest);
response.pipe(file);
file.on('finish', () => {
if (timer) clearTimeout(timer);
file.close();
resolve();
if (expected && bytes !== expected) {
try { unlinkSync(dest); } catch {}
reject(new Error(`incomplete download: got ${bytes} of ${expected} bytes`));
} else if (bytes === 0) {
try { unlinkSync(dest); } catch {}
reject(new Error('empty download'));
} else {
resolve();
}
});
file.on('error', (err) => {
if (timer) clearTimeout(timer);
try { unlinkSync(dest); } catch {}
reject(err);
});
} else {
reject(new Error(`Failed to download: HTTP ${status}`));
}
}).on('error', (err) => {
});
req.on('error', (err) => {
try { unlinkSync(dest); } catch {}
reject(err);
});
// Absolute request timeout to avoid hanging forever
req.setTimeout(120000, () => {
req.destroy(new Error('download timed out'));
});
};
attempt(url, maxRedirects);
});
let attemptNum = 0;
while (true) {
try {
return await doAttempt();
} catch (e) {
attemptNum += 1;
if (attemptNum > maxRetries) throw e;
const backoff = Math.min(2000, 200 * attemptNum);
await sleep(backoff);
}
}
}
function validateDownloadedBinary(p) {