* Spawn a detached child to run the install command. Stdout/stderr land in * the log file; the child is `unref()`d so the parent exits immediately * regardless of install duration. * * The child is responsible for writing `completedUpdate` to the config when * it finishes — we express that by r
(installCommand: string, version: string)
| 75 | * one spawned process with no extra binary to distribute. |
| 76 | */ |
| 77 | function launchDetachedInstall(installCommand: string, version: string): void { |
| 78 | mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 }); |
| 79 | const configFile = join(CONFIG_DIR, "config.json"); |
| 80 | |
| 81 | // The child script: |
| 82 | // 1. Runs the install command, capturing exit code + stderr tail. |
| 83 | // 2. Rewrites the config file with completedUpdate, clears pendingUpdate. |
| 84 | // We shell out to `node -e` so we don't need to ship a separate file. |
| 85 | const nodeScript = ` |
| 86 | const { exec } = require("node:child_process"); |
| 87 | const { readFileSync, renameSync, writeFileSync } = require("node:fs"); |
| 88 | const CFG = ${JSON.stringify(configFile)}; |
| 89 | const TMP = \`\${CFG}.tmp\`; |
| 90 | const VERSION = ${JSON.stringify(version)}; |
| 91 | const CMD = ${JSON.stringify(installCommand)}; |
| 92 | exec(CMD, { windowsHide: true, maxBuffer: 4 * 1024 * 1024 }, (err, _stdout, stderr) => { |
| 93 | let cfg = {}; |
| 94 | try { cfg = JSON.parse(readFileSync(CFG, "utf-8")); } catch (e) {} |
| 95 | cfg.completedUpdate = { |
| 96 | version: VERSION, |
| 97 | ok: !err, |
| 98 | finishedAt: new Date().toISOString(), |
| 99 | ...(err ? { error: String(stderr || err.message || "install failed").slice(-400) } : {}), |
| 100 | }; |
| 101 | delete cfg.pendingUpdate; |
| 102 | try { |
| 103 | writeFileSync(TMP, JSON.stringify(cfg, null, 2) + "\\n", { mode: 0o600 }); |
| 104 | renameSync(TMP, CFG); |
| 105 | } catch (e) {} |
| 106 | }); |
| 107 | `; |
| 108 | |
| 109 | const out = openSync(LOG_FILE, "a", 0o600); |
| 110 | const child = spawn(process.execPath, ["-e", nodeScript], { |
| 111 | detached: true, |
| 112 | stdio: ["ignore", out, out], |
| 113 | windowsHide: true, |
| 114 | env: { ...process.env, HYPERFRAMES_NO_UPDATE_CHECK: "1", HYPERFRAMES_NO_AUTO_INSTALL: "1" }, |
| 115 | }); |
| 116 | child.unref(); |
| 117 | log(`[launch] pid=${child.pid ?? "?"} cmd=${installCommand} version=${version}`); |
| 118 | } |
| 119 | |
| 120 | /** |
| 121 | * If a new version is available and policy allows, kick off a detached |
no test coverage detected