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 { 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;
|
let targetTriple = null;
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
case "linux":
|
case "linux":
|
||||||
@@ -78,6 +92,11 @@ if (existsSync(binaryPath)) {
|
|||||||
console.error(`Please try reinstalling the package:`);
|
console.error(`Please try reinstalling the package:`);
|
||||||
console.error(` npm uninstall -g @just-every/code`);
|
console.error(` npm uninstall -g @just-every/code`);
|
||||||
console.error(` npm install -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);
|
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("If the issue persists, clear npm cache and disable antivirus temporarily:");
|
||||||
console.error(" npm cache clean --force");
|
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);
|
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("On Windows, ensure the .exe downloaded correctly (proxy/AV can interfere).");
|
||||||
console.error("Try clearing cache: npm cache clean --force");
|
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 {
|
} else {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,10 +29,12 @@ function getTargetTriple() {
|
|||||||
return `${rustArch}-${rustPlatform}`;
|
return `${rustArch}-${rustPlatform}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadBinary(url, dest, maxRedirects = 5) {
|
async function downloadBinary(url, dest, maxRedirects = 5, maxRetries = 3) {
|
||||||
return new Promise((resolve, reject) => {
|
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
||||||
|
|
||||||
|
const doAttempt = () => new Promise((resolve, reject) => {
|
||||||
const attempt = (currentUrl, redirectsLeft) => {
|
const attempt = (currentUrl, redirectsLeft) => {
|
||||||
get(currentUrl, (response) => {
|
const req = get(currentUrl, (response) => {
|
||||||
const status = response.statusCode || 0;
|
const status = response.statusCode || 0;
|
||||||
const location = response.headers.location;
|
const location = response.headers.location;
|
||||||
|
|
||||||
@@ -46,28 +48,74 @@ async function downloadBinary(url, dest, maxRedirects = 5) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (status === 200) {
|
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);
|
const file = createWriteStream(dest);
|
||||||
response.pipe(file);
|
response.pipe(file);
|
||||||
file.on('finish', () => {
|
file.on('finish', () => {
|
||||||
|
if (timer) clearTimeout(timer);
|
||||||
file.close();
|
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) => {
|
file.on('error', (err) => {
|
||||||
|
if (timer) clearTimeout(timer);
|
||||||
try { unlinkSync(dest); } catch {}
|
try { unlinkSync(dest); } catch {}
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
reject(new Error(`Failed to download: HTTP ${status}`));
|
reject(new Error(`Failed to download: HTTP ${status}`));
|
||||||
}
|
}
|
||||||
}).on('error', (err) => {
|
});
|
||||||
|
|
||||||
|
req.on('error', (err) => {
|
||||||
try { unlinkSync(dest); } catch {}
|
try { unlinkSync(dest); } catch {}
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Absolute request timeout to avoid hanging forever
|
||||||
|
req.setTimeout(120000, () => {
|
||||||
|
req.destroy(new Error('download timed out'));
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
attempt(url, maxRedirects);
|
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) {
|
function validateDownloadedBinary(p) {
|
||||||
|
|||||||
Reference in New Issue
Block a user