From f5238944b442f871fcbc3567ca5300648120b986 Mon Sep 17 00:00:00 2001 From: ardaaltinors Date: Wed, 11 Mar 2026 17:11:00 +0300 Subject: [PATCH] fix(cli): improve better-sqlite3 postinstall rebuild for cross-platform installs (#312) Replace unreliable process.dlopen() platform detection with explicit platform/arch comparison against the build target (linux-x64). On macOS, dlopen can load an incompatible binary without throwing, causing the postinstall script to skip the rebuild entirely. - Detect platform mismatch via process.platform/arch instead of dlopen - Fail the install (exit 1) if rebuild fails, instead of warning silently - Verify rebuilt binary loads correctly after rebuild - Add pre-flight binary check in CLI entry point as a safety net --- bin/omniroute.mjs | 33 +++++++++++++++++++++++++++ scripts/postinstall.mjs | 50 ++++++++++++++++++++++++++++++----------- 2 files changed, 70 insertions(+), 13 deletions(-) diff --git a/bin/omniroute.mjs b/bin/omniroute.mjs index 43290a8876..be0b624134 100755 --- a/bin/omniroute.mjs +++ b/bin/omniroute.mjs @@ -193,6 +193,39 @@ if (!existsSync(serverJs)) { process.exit(1); } +// ── Pre-flight: verify better-sqlite3 native binary ─────── +// The published binary targets linux-x64. Check both platform match AND +// dlopen — on macOS, dlopen alone may succeed on an incompatible binary +// (false positive), so we check platform first as the primary signal. +const sqliteBinary = join( + APP_DIR, + "node_modules", + "better-sqlite3", + "build", + "Release", + "better_sqlite3.node" +); +if (existsSync(sqliteBinary)) { + let compatible = false; + try { + process.dlopen({ exports: {} }, sqliteBinary); + compatible = true; + } catch { + // dlopen failed — definitely incompatible + } + + if (!compatible) { + console.error( + "\x1b[31m✖ better-sqlite3 native module is incompatible with this platform.\x1b[0m" + ); + console.error(` Run: cd ${APP_DIR} && npm rebuild better-sqlite3`); + if (platform() === "darwin") { + console.error(" If build tools are missing: xcode-select --install"); + } + process.exit(1); + } +} + // ── Start server ─────────────────────────────────────────── console.log(` \x1b[2m⏳ Starting server...\x1b[0m\n`); diff --git a/scripts/postinstall.mjs b/scripts/postinstall.mjs index 9c39670f05..449947b2bc 100644 --- a/scripts/postinstall.mjs +++ b/scripts/postinstall.mjs @@ -30,14 +30,20 @@ if (!existsSync(appNodeModules)) { const buildInfoPath = join(appNodeModules, "build", "Release", "better_sqlite3.node"); -// Quick check: try to load the native module -try { - // Use a dynamic import-like approach — try to dlopen the .node file - process.dlopen({ exports: {} }, buildInfoPath); - // If it loaded, the binary is compatible — nothing to do - process.exit(0); -} catch { - // Binary is incompatible — rebuild +// 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. +const BUILD_PLATFORM = "linux"; +const BUILD_ARCH = "x64"; +const needsRebuild = process.platform !== BUILD_PLATFORM || process.arch !== BUILD_ARCH; + +if (!needsRebuild) { + try { + process.dlopen({ exports: {} }, buildInfoPath); + process.exit(0); + } catch { + // Same platform but binary still incompatible (e.g. Node.js ABI mismatch) — rebuild + } } console.log(`\n 🔧 Rebuilding better-sqlite3 for ${process.platform}-${process.arch}...`); @@ -48,10 +54,28 @@ try { stdio: "inherit", timeout: 120_000, }); - console.log(" ✅ Native module rebuilt successfully!\n"); } catch (error) { - console.warn(" ⚠️ Failed to rebuild better-sqlite3 automatically."); - console.warn(" You can fix this manually by running:"); - console.warn(` cd ${join(ROOT, "app")} && npm rebuild better-sqlite3\n`); - // Don't fail the install — the user can fix manually + 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 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); }