|
1 | 1 | #!/usr/bin/env node |
2 | 2 | /** |
3 | | - * Postinstall script: triggers the nuwax-codex JS launcher so it downloads |
4 | | - * the native binary from Alibaba Cloud OSS at install time. The launcher's |
5 | | - * `ensureBinary()` call caches the binary under `~/.nuwax-codex-cache/`. |
6 | | - * |
7 | | - * After this completes, calls like `nuwax-codex-acp-ts` (which internally |
8 | | - * spawn `nuwax-codex app-server`) will be instant — no lazy download on |
| 3 | + * Postinstall script: pre-downloads the native `nuwax-codex` binary from |
| 4 | + * Alibaba Cloud OSS at install time. After this completes, the ACP bridge |
| 5 | + * can spawn `nuwax-codex app-server` instantly without a lazy download on |
9 | 6 | * first use. |
10 | 7 | */ |
11 | 8 |
|
12 | | -import { spawnSync } from "node:child_process"; |
13 | 9 | import { createRequire } from "node:module"; |
| 10 | +import { execSync } from "node:child_process"; |
| 11 | +import { |
| 12 | + createWriteStream, |
| 13 | + existsSync, |
| 14 | + mkdirSync, |
| 15 | + chmodSync, |
| 16 | + unlinkSync, |
| 17 | +} from "node:fs"; |
| 18 | +import { homedir } from "node:os"; |
| 19 | +import { join, dirname } from "node:path"; |
14 | 20 |
|
15 | 21 | const require = createRequire(import.meta.url); |
16 | 22 |
|
17 | | -let launcherPath; |
| 23 | +// Resolve nuwax-codex package version — the binary version must match. |
| 24 | +let CODEX_VERSION; |
18 | 25 | try { |
19 | | - launcherPath = require.resolve("nuwax-codex/bin/nuwax-codex.js"); |
| 26 | + CODEX_VERSION = require("nuwax-codex/package.json").version; |
20 | 27 | } catch { |
21 | | - // nuwax-codex not installed as dependency — nothing to pre-download |
22 | | - process.exit(0); |
| 28 | + process.exit(0); // nuwax-codex not installed — nothing to pre-download |
23 | 29 | } |
24 | 30 |
|
25 | | -process.stderr.write("Pre-downloading nuwax-codex binary …\n"); |
| 31 | +const OSS_CDN_BASE = |
| 32 | + "https://nuwa-packages.oss-rg-china-mainland.aliyuncs.com/nuwax-codex"; |
26 | 33 |
|
27 | | -const result = spawnSync(process.execPath, [launcherPath, "--version"], { |
28 | | - stdio: "inherit", |
29 | | - timeout: 180_000, // 3 minutes for OSS download + extraction |
30 | | -}); |
| 34 | +// --------------------------------------------------------------------------- |
| 35 | +// Platform helpers |
| 36 | +// --------------------------------------------------------------------------- |
| 37 | + |
| 38 | +function getTargetTriple() { |
| 39 | + const p = process.platform; |
| 40 | + const a = process.arch; |
| 41 | + if (p === "darwin") { |
| 42 | + return a === "arm64" ? "aarch64-apple-darwin" : "x86_64-apple-darwin"; |
| 43 | + } |
| 44 | + if (p === "linux") { |
| 45 | + if (a !== "x64") throw new Error("Unsupported Linux arch: " + a); |
| 46 | + let family = "gnu"; |
| 47 | + try { |
| 48 | + const { familySync } = require("detect-libc"); |
| 49 | + family = familySync() === "musl" ? "musl" : "gnu"; |
| 50 | + } catch { |
| 51 | + /* musl detection best-effort */ |
| 52 | + } |
| 53 | + return `x86_64-unknown-linux-${family}`; |
| 54 | + } |
| 55 | + if (p === "win32") { |
| 56 | + return a === "arm64" |
| 57 | + ? "aarch64-pc-windows-msvc" |
| 58 | + : "x86_64-pc-windows-msvc"; |
| 59 | + } |
| 60 | + throw new Error("Unsupported platform: " + p); |
| 61 | +} |
| 62 | + |
| 63 | +function getArchiveExt() { |
| 64 | + return process.platform === "win32" ? "zip" : "tar.gz"; |
| 65 | +} |
| 66 | + |
| 67 | +function getBinaryName() { |
| 68 | + return process.platform === "win32" ? "nuwax-codex.exe" : "nuwax-codex"; |
| 69 | +} |
| 70 | + |
| 71 | +// --------------------------------------------------------------------------- |
| 72 | +// Main |
| 73 | +// --------------------------------------------------------------------------- |
| 74 | + |
| 75 | +function cacheDir() { |
| 76 | + return join(homedir(), ".nuwax-codex-cache", CODEX_VERSION); |
| 77 | +} |
| 78 | + |
| 79 | +function cachedBinaryPath() { |
| 80 | + return join(cacheDir(), getBinaryName()); |
| 81 | +} |
| 82 | + |
| 83 | +async function download(url, outPath) { |
| 84 | + mkdirSync(dirname(outPath), { recursive: true }); |
| 85 | + const res = await fetch(url, { redirect: "follow" }); |
| 86 | + if (!res.ok) { |
| 87 | + throw new Error( |
| 88 | + `Download failed: HTTP ${res.status} ${res.statusText}\nURL: ${url}`, |
| 89 | + ); |
| 90 | + } |
| 91 | + const total = parseInt(res.headers.get("content-length") || "0", 10); |
| 92 | + let downloaded = 0; |
| 93 | + const reader = res.body.getReader(); |
| 94 | + const ws = createWriteStream(outPath); |
| 95 | + const logInterval = setInterval(() => { |
| 96 | + if (total > 0) { |
| 97 | + process.stderr.write( |
| 98 | + `\r Downloading nuwax-codex ${CODEX_VERSION} … ${((downloaded / total) * 100).toFixed(0)}%`, |
| 99 | + ); |
| 100 | + } |
| 101 | + }, 500); |
| 102 | + try { |
| 103 | + while (true) { |
| 104 | + const { done, value } = await reader.read(); |
| 105 | + if (done) break; |
| 106 | + ws.write(value); |
| 107 | + downloaded += value.length; |
| 108 | + } |
| 109 | + } finally { |
| 110 | + clearInterval(logInterval); |
| 111 | + ws.end(); |
| 112 | + } |
| 113 | + process.stderr.write("\n"); |
| 114 | +} |
| 115 | + |
| 116 | +async function main() { |
| 117 | + const cached = cachedBinaryPath(); |
| 118 | + if (existsSync(cached)) { |
| 119 | + process.stderr.write( |
| 120 | + `nuwax-codex ${CODEX_VERSION} already cached, skipping.\n`, |
| 121 | + ); |
| 122 | + return; |
| 123 | + } |
| 124 | + |
| 125 | + const target = getTargetTriple(); |
| 126 | + const ext = getArchiveExt(); |
| 127 | + const url = `${OSS_CDN_BASE}/v${CODEX_VERSION}/nuwax-codex-${CODEX_VERSION}-${target}.${ext}`; |
31 | 128 |
|
32 | | -if (result.status !== 0) { |
33 | | - // Don't fail the install — the binary will be downloaded on first run |
34 | 129 | process.stderr.write( |
35 | | - `Warning: nuwax-codex pre-download exited with status ${result.status}. It will retry on first use.\n`, |
| 130 | + `Pre-downloading nuwax-codex ${CODEX_VERSION} for ${target} …\n ${url}\n`, |
36 | 131 | ); |
| 132 | + |
| 133 | + const dir = cacheDir(); |
| 134 | + mkdirSync(dir, { recursive: true }); |
| 135 | + const archivePath = join(dir, `nuwax-codex.${ext}`); |
| 136 | + |
| 137 | + // 1. Download |
| 138 | + await download(url, archivePath); |
| 139 | + |
| 140 | + // 2. Extract using system tools (reliable, no custom tar-parser bugs) |
| 141 | + process.stderr.write(" Extracting …\n"); |
| 142 | + if (ext === "tar.gz") { |
| 143 | + execSync(`tar xzf "${archivePath}" -C "${dir}"`, { stdio: "inherit" }); |
| 144 | + } else { |
| 145 | + // Windows: use PowerShell Expand-Archive |
| 146 | + execSync( |
| 147 | + `powershell -NoProfile -Command "Expand-Archive -Force '${archivePath}' '${dir}'"`, |
| 148 | + { stdio: "inherit" }, |
| 149 | + ); |
| 150 | + } |
| 151 | + |
| 152 | + // 3. Clean up archive |
| 153 | + try { |
| 154 | + unlinkSync(archivePath); |
| 155 | + } catch { |
| 156 | + /* best-effort */ |
| 157 | + } |
| 158 | + |
| 159 | + // 4. Ensure executable |
| 160 | + if (process.platform !== "win32") { |
| 161 | + chmodSync(cached, 0o755); |
| 162 | + } |
| 163 | + |
| 164 | + if (existsSync(cached)) { |
| 165 | + process.stderr.write(`✓ nuwax-codex ${CODEX_VERSION} ready.\n`); |
| 166 | + } else { |
| 167 | + process.stderr.write( |
| 168 | + `⚠ Download completed but binary not found at ${cached} — it will retry on first run.\n`, |
| 169 | + ); |
| 170 | + } |
37 | 171 | } |
| 172 | + |
| 173 | +main().catch((err) => { |
| 174 | + process.stderr.write(`⚠ nuwax-codex pre-download skipped: ${err.message}\n`); |
| 175 | + process.stderr.write(` The binary will be downloaded on first use instead.\n`); |
| 176 | + // Never fail the install |
| 177 | +}); |
0 commit comments