From 90a0005845c1d807cfb4fb420ba2919ea63a14b8 Mon Sep 17 00:00:00 2001 From: vianhanif Date: Thu, 27 Aug 2026 20:24:22 +0700 Subject: [PATCH] fix(cli): install better-sqlite3 without build tools on Node 22+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The runtime hook pinned better-sqlite3 12.6.2, whose prebuilds stop at Node ABI 141 — on Node 26 the install fell back to a node-gyp source build and failed on machines without build tools, silently degrading to the sql.js fallback. Node >= 22 now installs 13.0.3, which is N-API and ships per-platform prebuilds inside the package. Two things were needed to make that actually work: - npm injects an implicit `node-gyp rebuild` for any package shipping a binding.gyp, so the install still demanded build tools; `--ignore-scripts` skips it and uses the bundled prebuild as-is. - the binary check only looked at build/Release, which 13.x no longer creates, so every start re-ran npm install; it now also accepts prebuilds/-.node. Node < 22 stays on 12.6.2 (13.x requires Node >= 22), and an existing working install is left untouched either way. --- cli/hooks/sqliteRuntime.js | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/cli/hooks/sqliteRuntime.js b/cli/hooks/sqliteRuntime.js index feca2f59..3cb286e7 100644 --- a/cli/hooks/sqliteRuntime.js +++ b/cli/hooks/sqliteRuntime.js @@ -6,7 +6,13 @@ const fs = require("fs"); const os = require("os"); const path = require("path"); -const BETTER_SQLITE3_VERSION = "12.6.2"; +// Gate the pinned version by Node major, mirroring src/lib/db/driver.js gating +// style: 13.x is N-API and ships per-platform prebuilds inside the package, so +// it needs no ABI-specific download. It requires Node >= 22; older runtimes stay +// on 12.6.2, which fetches an ABI-specific binary via prebuild-install. +const [NODE_MAJOR] = process.versions.node.split(".").map(Number); +const USE_NAPI_BUILD = NODE_MAJOR >= 22; +const BETTER_SQLITE3_VERSION = USE_NAPI_BUILD ? "13.0.3" : "12.6.2"; const SQL_JS_VERSION = "1.14.1"; function getDataDir() { @@ -45,9 +51,23 @@ function hasModule(name) { return fs.existsSync(path.join(getRuntimeNodeModules(), name, "package.json")); } +function isGlibcRuntime() { + try { return Boolean(process.report?.getReport()?.header?.glibcVersionRuntime); } catch { return true; } +} + +// 12.x compiles/downloads into build/Release; 13.x ships prebuilds/-.node. +function getBetterSqliteBinary() { + const root = path.join(getRuntimeNodeModules(), "better-sqlite3"); + const platform = process.platform === "linux" && !isGlibcRuntime() ? "linuxmusl" : process.platform; + return [ + path.join(root, "build", "Release", "better_sqlite3.node"), + path.join(root, "prebuilds", `${platform}-${process.arch}.node`), + ].find((file) => fs.existsSync(file)); +} + function isBetterSqliteBinaryValid() { - const binary = path.join(getRuntimeNodeModules(), "better-sqlite3", "build", "Release", "better_sqlite3.node"); - if (!fs.existsSync(binary)) return false; + const binary = getBetterSqliteBinary(); + if (!binary) return false; try { const fd = fs.openSync(binary, "r"); const buf = Buffer.alloc(4); @@ -91,6 +111,7 @@ function runNpmInstall({ cwd, pkgs, extraArgs = [], timeout = 180000 }) { function npmInstall(pkgs, opts = {}) { const cwd = ensureRuntimeDir(); const extra = opts.optional ? ["--no-save"] : []; + if (opts.ignoreScripts) extra.push("--ignore-scripts"); if (!opts.silent) console.log("⏳ Installing SQLite engine (first run)..."); const res = runNpmInstall({ cwd, pkgs, extraArgs: extra, timeout: opts.timeout || 180000 }); if (!res.ok && !opts.silent) { @@ -129,7 +150,10 @@ function ensureSqliteRuntime({ silent = false } = {}) { return { betterSqlite: true, sqlJs: sqlJsOk }; } - const ok = npmInstall([`better-sqlite3@${BETTER_SQLITE3_VERSION}`], { optional: true, silent }); + // npm injects an implicit `node-gyp rebuild` for any package carrying a + // binding.gyp, which would demand build tools even though 13.x already bundles + // the binary — skip scripts so the bundled prebuild is used as-is. + const ok = npmInstall([`better-sqlite3@${BETTER_SQLITE3_VERSION}`], { optional: true, silent, ignoreScripts: USE_NAPI_BUILD }); return { betterSqlite: ok && hasModule("better-sqlite3") && isBetterSqliteBinaryValid(), sqlJs: sqlJsOk,