mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 09:45:36 +00:00
fix(npm): install to a user cache when the package is read-only; signal-safe first run
- When the package's bin/ is not writable (a root-owned `sudo npm i -g` whose postinstall npm 12 skipped), install the binary to a per-user cache ($XDG_CACHE_HOME, ~/.cache, ~/Library/Caches on macOS, under <package>/<version>/); the launcher looks in both. Previously the first run failed with EACCES and advice to rerun npm install. - Run the first-run installer asynchronously with SIGTERM/SIGHUP forwarded, so a supervisor stopping the launcher stops the download; the installer removes its partial file and the launcher exits by the same signal. - Verify an IDX_BINARY_URL local file's checksum on the copy, not the source, so the installed bytes are the checked ones. - npm smoke covers the read-only fallback. The last three come from a Codex review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
This commit is contained in:
parent
6f44c3ce50
commit
907223a838
4 changed files with 156 additions and 51 deletions
|
|
@ -3,51 +3,83 @@
|
|||
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const { spawn, spawnSync } = require("node:child_process");
|
||||
const { spawn } = require("node:child_process");
|
||||
|
||||
const packageManifest = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8"),
|
||||
);
|
||||
const nativeBinary = packageManifest.nativeBinary || "idx";
|
||||
const packageName = packageManifest.name;
|
||||
const binaryName = process.platform === "win32" ? `${nativeBinary}.exe` : nativeBinary;
|
||||
const binaryPath = path.join(__dirname, "..", "bin", binaryName);
|
||||
const installer = path.join(__dirname, "install.js");
|
||||
// Requiring the installer only reads its paths; it installs when run directly.
|
||||
const { binaryLocations } = require(installer);
|
||||
|
||||
// postinstall never ran (ignore-scripts) or its download failed: install now.
|
||||
// Its log goes to stderr, since stdout is the MCP channel for idx-mcp.
|
||||
if (!fs.existsSync(binaryPath)) {
|
||||
console.error(`${packageName}: binary not installed; running the installer`);
|
||||
spawnSync(process.execPath, [path.join(__dirname, "install.js")], {
|
||||
stdio: ["ignore", 2, 2],
|
||||
const findBinary = () => binaryLocations.find((candidate) => fs.existsSync(candidate));
|
||||
|
||||
// The running child: the first-run installer, then the binary.
|
||||
let child = null;
|
||||
// A termination signal received while installing; the installer cleans up
|
||||
// and exits, and the launcher then stops the same way.
|
||||
let stopping = null;
|
||||
|
||||
// Pass termination signals from a supervisor (kill, docker stop) to the child.
|
||||
for (const signal of ["SIGTERM", "SIGHUP"]) {
|
||||
process.on(signal, () => {
|
||||
stopping = signal;
|
||||
if (child) {
|
||||
child.kill(signal);
|
||||
} else {
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!fs.existsSync(binaryPath)) {
|
||||
console.error(`${packageName}: installed binary not found at ${binaryPath}`);
|
||||
console.error(`${packageName}: rerun npm install or set IDX_BINARY_URL for a local binary`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const child = spawn(binaryPath, process.argv.slice(2), { stdio: "inherit" });
|
||||
|
||||
// Pass termination signals from a supervisor (kill, docker stop) to the binary.
|
||||
for (const signal of ["SIGTERM", "SIGHUP"]) {
|
||||
process.on(signal, () => child.kill(signal));
|
||||
}
|
||||
// Ctrl+C already reaches the binary through the terminal's process group.
|
||||
// Ctrl+C already reaches the child through the terminal's process group.
|
||||
process.on("SIGINT", () => {});
|
||||
|
||||
child.once("error", (error) => {
|
||||
console.error(`${packageName}: failed to start ${binaryPath}: ${error.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
child.once("exit", (code, signal) => {
|
||||
function exitLike(code, signal) {
|
||||
if (signal) {
|
||||
process.removeAllListeners(signal);
|
||||
process.kill(process.pid, signal);
|
||||
return;
|
||||
}
|
||||
|
||||
process.exit(code === null ? 1 : code);
|
||||
});
|
||||
}
|
||||
|
||||
function run(binaryPath) {
|
||||
child = spawn(binaryPath, process.argv.slice(2), { stdio: "inherit" });
|
||||
child.once("error", (error) => {
|
||||
console.error(`${packageName}: failed to start ${binaryPath}: ${error.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
child.once("exit", exitLike);
|
||||
}
|
||||
|
||||
const installed = findBinary();
|
||||
if (installed) {
|
||||
run(installed);
|
||||
} else {
|
||||
// postinstall never ran (npm 12 `allow-scripts`, pnpm, bun, --ignore-scripts)
|
||||
// or its download failed: install now. Its log goes to stderr, since stdout
|
||||
// is the MCP channel for idx-mcp.
|
||||
console.error(`${packageName}: binary not installed; running the installer`);
|
||||
child = spawn(process.execPath, [installer], { stdio: ["ignore", 2, 2] });
|
||||
child.once("error", (error) => {
|
||||
console.error(`${packageName}: failed to run the installer: ${error.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
child.once("exit", (code, signal) => {
|
||||
child = null;
|
||||
if (signal || stopping) {
|
||||
exitLike(code, signal || stopping);
|
||||
return;
|
||||
}
|
||||
const binaryPath = findBinary();
|
||||
if (!binaryPath) {
|
||||
console.error(`${packageName}: no installed binary at ${binaryLocations.join(" or ")}`);
|
||||
console.error(
|
||||
`${packageName}: check access to github.com and rerun, or set IDX_BINARY_URL to a local binary`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
run(binaryPath);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue