mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user