Spaces:
Runtime error
Runtime error
| /** | |
| * OmniRoute — Postinstall Native Module Fix | |
| * | |
| * The npm package ships with a Next.js standalone build that includes | |
| * native modules compiled for the build platform (Linux x64) inside | |
| * dist/node_modules/. However, npm also installs these as top-level | |
| * dependencies (in the root node_modules/), correctly compiled for | |
| * the user's platform. | |
| * | |
| * This script copies the correctly-built native binaries from the root | |
| * into the standalone dist directory — no rebuild or build tools needed. | |
| * | |
| * Modules repaired: | |
| * - better-sqlite3 (SQLite bindings) | |
| * - wreq-js (TLS client for OAuth providers) | |
| * | |
| * Fixes: https://github.com/diegosouzapw/OmniRoute/issues/129 | |
| * Fixes: https://github.com/diegosouzapw/OmniRoute/issues/321 | |
| * Fixes: https://github.com/diegosouzapw/OmniRoute/issues/426 | |
| * Fixes: https://github.com/diegosouzapw/OmniRoute/issues/1634 | |
| */ | |
| import { copyFileSync, cpSync, existsSync, mkdirSync, readdirSync } from "node:fs"; | |
| import { dirname, join } from "node:path"; | |
| import { fileURLToPath } from "node:url"; | |
| import { PUBLISHED_BUILD_ARCH, PUBLISHED_BUILD_PLATFORM } from "./native-binary-compat.mjs"; | |
| import { hasStandaloneAppBundle, isTermux } from "./postinstallSupport.mjs"; | |
| import { colocateLlmlinguaOptionals } from "./colocateOptionals.mjs"; | |
| const __filename = fileURLToPath(import.meta.url); | |
| const __dirname = dirname(__filename); | |
| const ROOT = join(__dirname, "..", ".."); | |
| const appBinary = join( | |
| ROOT, | |
| "dist", | |
| "node_modules", | |
| "better-sqlite3", | |
| "build", | |
| "Release", | |
| "better_sqlite3.node" | |
| ); | |
| const rootBinary = join( | |
| ROOT, | |
| "node_modules", | |
| "better-sqlite3", | |
| "build", | |
| "Release", | |
| "better_sqlite3.node" | |
| ); | |
| async function fixBetterSqliteBinary() { | |
| if (!existsSync(join(ROOT, "dist", "node_modules", "better-sqlite3"))) { | |
| return; | |
| } | |
| const platformMatch = | |
| process.platform === PUBLISHED_BUILD_PLATFORM && process.arch === PUBLISHED_BUILD_ARCH; | |
| if (platformMatch) { | |
| try { | |
| process.dlopen({ exports: {} }, appBinary); | |
| return; | |
| } catch (err) { | |
| console.warn(` ⚠️ Bundled binary incompatible despite platform match: ${err.message}`); | |
| } | |
| } | |
| console.log(`\n 🔧 Fixing better-sqlite3 binary for ${process.platform}-${process.arch}...`); | |
| if (existsSync(rootBinary)) { | |
| try { | |
| mkdirSync(dirname(appBinary), { recursive: true }); | |
| copyFileSync(rootBinary, appBinary); | |
| } catch (err) { | |
| console.warn(` ⚠️ Failed to copy binary: ${err.message}`); | |
| } | |
| try { | |
| process.dlopen({ exports: {} }, appBinary); | |
| console.log(" ✅ Native module fixed successfully!\n"); | |
| return; | |
| } catch (err) { | |
| console.warn(` ⚠️ Copied binary failed to load: ${err.message}`); | |
| } | |
| } | |
| console.log(" 📥 Attempting to download prebuilt binary via node-pre-gyp..."); | |
| try { | |
| const { execSync } = await import("node:child_process"); | |
| const preGypBin = join( | |
| ROOT, | |
| "dist", | |
| "node_modules", | |
| ".bin", | |
| process.platform === "win32" ? "node-pre-gyp.cmd" : "node-pre-gyp" | |
| ); | |
| const preGypFallback = join( | |
| ROOT, | |
| "dist", | |
| "node_modules", | |
| "@mapbox", | |
| "node-pre-gyp", | |
| "bin", | |
| "node-pre-gyp" | |
| ); | |
| const preGypCmd = existsSync(preGypBin) ? preGypBin : preGypFallback; | |
| if (existsSync(preGypCmd)) { | |
| execSync(`"${process.execPath}" "${preGypCmd}" install --fallback-to-build=false`, { | |
| cwd: join(ROOT, "dist", "node_modules", "better-sqlite3"), | |
| stdio: "inherit", | |
| timeout: 60_000, | |
| }); | |
| mkdirSync(dirname(appBinary), { recursive: true }); | |
| try { | |
| process.dlopen({ exports: {} }, appBinary); | |
| console.log(" ✅ Prebuilt binary downloaded and loaded successfully!\n"); | |
| return; | |
| } catch (loadErr) { | |
| console.warn(` ⚠️ Downloaded binary failed to load: ${loadErr.message}`); | |
| } | |
| } else { | |
| console.warn(" ⚠️ node-pre-gyp not found, skipping prebuilt download."); | |
| } | |
| } catch (err) { | |
| console.warn(` ⚠️ node-pre-gyp download failed: ${err.message.split("\n")[0]}`); | |
| } | |
| console.log(" ⚠️ Attempting npm rebuild (requires build tools)..."); | |
| try { | |
| const { execSync } = await import("node:child_process"); | |
| // On Android/Termux, rebuild from source with --build-from-source flag | |
| const isAndroid = process.platform === "android" || isTermux(); | |
| const rebuildCmd = isAndroid | |
| ? "npm install better-sqlite3 --build-from-source --force" | |
| : "npm rebuild better-sqlite3"; | |
| const env = { ...process.env }; | |
| if (isAndroid) { | |
| env.GYP_DEFINES = "android_ndk_path=''"; | |
| } | |
| execSync(rebuildCmd, { | |
| cwd: join(ROOT, "dist"), | |
| stdio: "inherit", | |
| timeout: isAndroid ? 600_000 : 300_000, // ARM compilation is slower | |
| env, | |
| }); | |
| process.dlopen({ exports: {} }, appBinary); | |
| console.log(" ✅ Native module rebuilt successfully!\n"); | |
| return; | |
| } catch (err) { | |
| const isTimeout = err.killed || err.signal === "SIGTERM"; | |
| if (isTimeout) { | |
| const secs = isAndroid ? 600 : 300; | |
| console.warn(` ⚠️ npm rebuild timed out after ${secs}s.`); | |
| } else { | |
| console.warn(` ⚠️ npm rebuild failed: ${err.message}`); | |
| } | |
| } | |
| console.warn("\n ⚠️ Could not fix better-sqlite3 native module automatically."); | |
| console.warn(" The server may not start correctly."); | |
| console.warn(" Manual fix options:"); | |
| if (process.platform === "win32") { | |
| console.warn(" Option A (easiest — no build tools needed):"); | |
| console.warn(` cd "${join(ROOT, "dist", "node_modules", "better-sqlite3")}"`); | |
| console.warn(" npx @mapbox/node-pre-gyp install --fallback-to-build=false"); | |
| console.warn(" Option B (requires Build Tools for Visual Studio):"); | |
| console.warn(` cd "${join(ROOT, "dist")}" && npm rebuild better-sqlite3`); | |
| console.warn(" Install from: https://visualstudio.microsoft.com/visual-cpp-build-tools/"); | |
| console.warn(" Also ensure Python is installed: https://python.org"); | |
| } else if (process.platform === "darwin") { | |
| console.warn(` cd ${join(ROOT, "dist")} && npm rebuild better-sqlite3`); | |
| console.warn(" If build tools are missing: xcode-select --install"); | |
| } else { | |
| console.warn(` cd ${join(ROOT, "dist")} && npm rebuild better-sqlite3`); | |
| } | |
| console.warn(""); | |
| } | |
| /** | |
| * Fix wreq-js native binary for the standalone dist directory. | |
| * | |
| * wreq-js ships platform-specific .node binaries under rust/. | |
| * The standalone build may only contain Linux binaries from the CI. | |
| * This copies the correct platform binary from the root install. | |
| * | |
| * Fixes: https://github.com/diegosouzapw/OmniRoute/issues/1634 | |
| */ | |
| async function fixWreqJsBinary() { | |
| // wreq-js native module is not loadable in Termux (libgcc path mismatch). | |
| // The runtime already falls back gracefully when wreq-js is unavailable. | |
| if (process.platform === "android" || isTermux()) { | |
| console.log( | |
| " [postinstall] wreq-js: skipped on Termux/Android " + | |
| "(libgcc not available — OAuth TLS fingerprinting will use the fallback path)" | |
| ); | |
| return; | |
| } | |
| const appWreqDir = join(ROOT, "dist", "node_modules", "wreq-js", "rust"); | |
| const rootWreqDir = join(ROOT, "node_modules", "wreq-js", "rust"); | |
| if (!existsSync(join(ROOT, "dist", "node_modules", "wreq-js"))) { | |
| return; | |
| } | |
| const binaryName = `wreq-js.${process.platform}-${process.arch}.node`; | |
| const appBinaryPath = join(appWreqDir, binaryName); | |
| const rootBinaryPath = join(rootWreqDir, binaryName); | |
| // Check if the platform binary already exists and loads | |
| if (existsSync(appBinaryPath)) { | |
| try { | |
| process.dlopen({ exports: {} }, appBinaryPath); | |
| return; // Already working | |
| } catch (err) { | |
| console.warn(` ⚠️ wreq-js binary exists but failed to load: ${err.message}`); | |
| } | |
| } | |
| console.log(`\n 🔧 Fixing wreq-js binary for ${process.platform}-${process.arch}...`); | |
| // Strategy 1: Copy from root node_modules | |
| if (existsSync(rootBinaryPath)) { | |
| try { | |
| mkdirSync(appWreqDir, { recursive: true }); | |
| copyFileSync(rootBinaryPath, appBinaryPath); | |
| process.dlopen({ exports: {} }, appBinaryPath); | |
| console.log(" ✅ wreq-js native module fixed successfully!\n"); | |
| return; | |
| } catch (err) { | |
| console.warn(` ⚠️ Copied wreq-js binary failed to load: ${err.message}`); | |
| } | |
| } | |
| // Strategy 2: Copy entire rust/ directory from root (gets all platform binaries) | |
| if (existsSync(rootWreqDir)) { | |
| try { | |
| mkdirSync(appWreqDir, { recursive: true }); | |
| const files = readdirSync(rootWreqDir); | |
| for (const file of files) { | |
| if (file.endsWith(".node")) { | |
| copyFileSync(join(rootWreqDir, file), join(appWreqDir, file)); | |
| } | |
| } | |
| if (existsSync(appBinaryPath)) { | |
| process.dlopen({ exports: {} }, appBinaryPath); | |
| console.log(" ✅ wreq-js native module fixed (full copy) successfully!\n"); | |
| return; | |
| } | |
| } catch (err) { | |
| console.warn(` ⚠️ wreq-js full copy failed: ${err.message}`); | |
| } | |
| } | |
| // Strategy 3: Rebuild wreq-js inside dist/ | |
| console.log(" 📥 Attempting npm rebuild wreq-js..."); | |
| try { | |
| const { execSync } = await import("node:child_process"); | |
| execSync("npm rebuild wreq-js", { | |
| cwd: join(ROOT, "dist"), | |
| stdio: "inherit", | |
| timeout: 120_000, | |
| }); | |
| if (existsSync(appBinaryPath)) { | |
| process.dlopen({ exports: {} }, appBinaryPath); | |
| console.log(" ✅ wreq-js native module rebuilt successfully!\n"); | |
| return; | |
| } | |
| } catch (err) { | |
| console.warn(` ⚠️ wreq-js rebuild failed: ${err.message}`); | |
| } | |
| console.warn( | |
| `\n ⚠️ Could not fix wreq-js native module for ${process.platform}-${process.arch}.` | |
| ); | |
| console.warn(" OAuth-based providers (Codex, Cursor, etc.) may not work."); | |
| console.warn(` Manual fix: cd ${join(ROOT, "dist")} && npm install wreq-js --no-save\n`); | |
| } | |
| async function ensureSwcHelpers() { | |
| if (!hasStandaloneAppBundle(ROOT)) { | |
| return; | |
| } | |
| const swcHelpersApp = join(ROOT, "dist", "node_modules", "@swc", "helpers"); | |
| const swcHelpersRoot = join(ROOT, "node_modules", "@swc", "helpers"); | |
| if (existsSync(swcHelpersApp)) { | |
| return; | |
| } | |
| if (existsSync(swcHelpersRoot)) { | |
| try { | |
| const { cpSync } = await import("node:fs"); | |
| mkdirSync(join(ROOT, "dist", "node_modules", "@swc"), { recursive: true }); | |
| cpSync(swcHelpersRoot, swcHelpersApp, { recursive: true }); | |
| console.log(" ✅ @swc/helpers copied to standalone dist/node_modules.\n"); | |
| } catch (err) { | |
| console.warn(` ⚠️ Could not copy @swc/helpers: ${err.message}`); | |
| console.warn( | |
| " Try manually: cp -r node_modules/@swc/helpers dist/node_modules/@swc/helpers\n" | |
| ); | |
| } | |
| return; | |
| } | |
| console.warn(" ⚠️ @swc/helpers not found in root node_modules either."); | |
| console.warn(" Try: npm install --save-exact @swc/helpers@0.5.19\n"); | |
| } | |
| async function syncProjectEnv() { | |
| try { | |
| const { syncEnv } = await import("./sync-env.mjs"); | |
| syncEnv({ rootDir: ROOT }); | |
| } catch (err) { | |
| console.warn(` ⚠️ .env sync skipped: ${err.message}`); | |
| } | |
| } | |
| /** | |
| * Co-locate the LLMLingua-2 SLM optional dependency closure into dist/node_modules so the | |
| * compression "ultra" SLM tier (PR #4257) resolves a single @huggingface/transformers instance at | |
| * runtime. No-op unless the optionals were installed (`--include=optional`). See colocateOptionals.mjs. | |
| */ | |
| async function ensureLlmlinguaOptionals() { | |
| try { | |
| colocateLlmlinguaOptionals({ rootDir: ROOT, log: (m) => console.log(m) }); | |
| } catch (err) { | |
| // Best-effort: the SLM tier is itself fail-open, so a co-location hiccup never fails the install. | |
| console.warn(` ⚠️ LLMLingua optional co-location skipped: ${err.message}`); | |
| } | |
| } | |
| await fixBetterSqliteBinary(); | |
| await fixWreqJsBinary(); | |
| await ensureSwcHelpers(); | |
| await ensureLlmlinguaOptionals(); | |
| await syncProjectEnv(); | |
| // Warm up native runtimes (better-sqlite3 in ~/.omniroute/runtime/). | |
| // Non-fatal: errors are caught inside postinstall.mjs. | |
| try { | |
| await import("../postinstall.mjs"); | |
| } catch { | |
| // Silently skip — runtime warm-up is best-effort. | |
| } | |