From c859665c6b35dbf2f475e02b7ac0b1ee7a896709 Mon Sep 17 00:00:00 2001 From: ardaaltinors Date: Thu, 12 Mar 2026 10:00:32 +0300 Subject: [PATCH] fix(cli): copy native binary from root node_modules instead of rebuilding (#321) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The standalone app/ directory created by Next.js only contains runtime files for better-sqlite3 (no binding.gyp, no source, no prebuild-install), so `npm rebuild` inside app/ is a no-op. The previous fix (#312) added exit(1) on rebuild failure, which caused npm to rollback the entire package installation — leaving users with nothing to fix manually. New approach: 1. Check if existing binary is already compatible (dlopen) 2. Copy the correctly-built binary from root node_modules/ (npm already compiles it for the correct platform during install) 3. Fall back to npm rebuild if root binary is unavailable 4. Warn but don't fail the install if nothing works — the package stays installed and the CLI pre-flight check gives a clear error at startup --- scripts/postinstall.mjs | 114 +++++++++++++++++++++++++--------------- 1 file changed, 72 insertions(+), 42 deletions(-) diff --git a/scripts/postinstall.mjs b/scripts/postinstall.mjs index 449947b2bc..d8379f6b43 100644 --- a/scripts/postinstall.mjs +++ b/scripts/postinstall.mjs @@ -1,18 +1,22 @@ #!/usr/bin/env node /** - * OmniRoute — Postinstall Native Module Rebuild + * OmniRoute — Postinstall Native Module Fix * * The npm package ships with a Next.js standalone build that includes - * better-sqlite3 compiled for the build platform (Linux x64). - * This script detects platform mismatches and rebuilds the native - * module for the user's actual OS/architecture. + * better-sqlite3 compiled for the build platform (Linux x64) inside + * app/node_modules/. However, npm also installs better-sqlite3 as a + * top-level dependency (in the root node_modules/), correctly compiled + * for the user's platform. + * + * This script copies the correctly-built native binary from the root + * into the standalone app directory — no rebuild or build tools needed. * * Fixes: https://github.com/diegosouzapw/OmniRoute/issues/129 + * Fixes: https://github.com/diegosouzapw/OmniRoute/issues/321 */ -import { execSync } from "node:child_process"; -import { existsSync } from "node:fs"; +import { existsSync, copyFileSync, mkdirSync } from "node:fs"; import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; @@ -20,62 +24,88 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const ROOT = join(__dirname, ".."); -// The standalone build bundles better-sqlite3 inside app/node_modules -const appNodeModules = join(ROOT, "app", "node_modules", "better-sqlite3"); +const appBinary = join( + ROOT, + "app", + "node_modules", + "better-sqlite3", + "build", + "Release", + "better_sqlite3.node" +); +const rootBinary = join( + ROOT, + "node_modules", + "better-sqlite3", + "build", + "Release", + "better_sqlite3.node" +); -if (!existsSync(appNodeModules)) { - // No bundled better-sqlite3 — nothing to do (dev install, not npm global) +if (!existsSync(join(ROOT, "app", "node_modules", "better-sqlite3"))) { + // No standalone app directory — nothing to do (dev install, not npm global) process.exit(0); } -const buildInfoPath = join(appNodeModules, "build", "Release", "better_sqlite3.node"); - -// The published binary is compiled for linux-x64. -// On any other platform/arch, we must rebuild — dlopen alone is unreliable -// because macOS may load an incompatible binary without throwing. +// The published binary is compiled for linux-x64. On any other platform/arch, +// always replace it — dlopen alone is unreliable because macOS can load an +// incompatible binary without throwing (the exact bug fixed in #312). const BUILD_PLATFORM = "linux"; const BUILD_ARCH = "x64"; -const needsRebuild = process.platform !== BUILD_PLATFORM || process.arch !== BUILD_ARCH; +const platformMatch = process.platform === BUILD_PLATFORM && process.arch === BUILD_ARCH; -if (!needsRebuild) { +if (platformMatch) { try { - process.dlopen({ exports: {} }, buildInfoPath); + process.dlopen({ exports: {} }, appBinary); process.exit(0); } catch { - // Same platform but binary still incompatible (e.g. Node.js ABI mismatch) — rebuild + // Same platform but binary still incompatible (e.g. Node.js ABI mismatch) } } -console.log(`\n 🔧 Rebuilding better-sqlite3 for ${process.platform}-${process.arch}...`); +console.log(`\n 🔧 Fixing better-sqlite3 binary for ${process.platform}-${process.arch}...`); + +// Strategy 1: Copy the correctly-built binary from root node_modules +if (existsSync(rootBinary)) { + try { + mkdirSync(dirname(appBinary), { recursive: true }); + copyFileSync(rootBinary, appBinary); + + // Verify the copied binary loads + process.dlopen({ exports: {} }, appBinary); + console.log(" ✅ Native module fixed successfully!\n"); + process.exit(0); + } catch { + // Copy succeeded but binary still doesn't load — fall through + } +} + +// Strategy 2: Fall back to npm rebuild (may work if build tools are available) +console.log(" ⚠️ Root binary not available, attempting npm rebuild..."); try { + const { execSync } = await import("node:child_process"); execSync("npm rebuild better-sqlite3", { cwd: join(ROOT, "app"), stdio: "inherit", timeout: 120_000, }); -} catch (error) { - console.error(" ❌ Failed to rebuild better-sqlite3 automatically."); - console.error(" You can fix this manually by running:"); - console.error(` cd ${join(ROOT, "app")} && npm rebuild better-sqlite3`); - if (process.platform === "darwin") { - console.error(" If build tools are missing: xcode-select --install"); - } - console.error(""); - process.exit(1); + + // Verify rebuild worked + process.dlopen({ exports: {} }, appBinary); + console.log(" ✅ Native module rebuilt successfully!\n"); + process.exit(0); +} catch { + // Rebuild failed or binary still incompatible } -// Verify the rebuilt binary actually loads -try { - process.dlopen({ exports: {} }, buildInfoPath); - console.log(" ✅ Native module rebuilt successfully!\n"); -} catch { - console.error(" ❌ Rebuild completed but binary is still incompatible."); - console.error(" Try manually:"); - console.error(` cd ${join(ROOT, "app")} && npm rebuild better-sqlite3`); - if (process.platform === "darwin") { - console.error(" If build tools are missing: xcode-select --install"); - } - console.error(""); - process.exit(1); +// If nothing worked, warn but don't fail the install — let the package stay +// installed so users can fix manually or use the pre-flight check in the CLI +console.warn(" ⚠️ Could not fix better-sqlite3 native module automatically."); +console.warn(" The server may not start correctly."); +console.warn(" Try manually:"); +console.warn(` cd ${join(ROOT, "app")} && npm rebuild better-sqlite3`); +if (process.platform === "darwin") { + console.warn(" If build tools are missing: xcode-select --install"); } +console.warn("");