fix(cli): install better-sqlite3 without build tools on Node 22+
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/<platform>-<arch>.node. Node < 22 stays on 12.6.2 (13.x requires Node >= 22), and an existing working install is left untouched either way.
This commit is contained in:
@@ -6,7 +6,13 @@ const fs = require("fs");
|
|||||||
const os = require("os");
|
const os = require("os");
|
||||||
const path = require("path");
|
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";
|
const SQL_JS_VERSION = "1.14.1";
|
||||||
|
|
||||||
function getDataDir() {
|
function getDataDir() {
|
||||||
@@ -45,9 +51,23 @@ function hasModule(name) {
|
|||||||
return fs.existsSync(path.join(getRuntimeNodeModules(), name, "package.json"));
|
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/<platform>-<arch>.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() {
|
function isBetterSqliteBinaryValid() {
|
||||||
const binary = path.join(getRuntimeNodeModules(), "better-sqlite3", "build", "Release", "better_sqlite3.node");
|
const binary = getBetterSqliteBinary();
|
||||||
if (!fs.existsSync(binary)) return false;
|
if (!binary) return false;
|
||||||
try {
|
try {
|
||||||
const fd = fs.openSync(binary, "r");
|
const fd = fs.openSync(binary, "r");
|
||||||
const buf = Buffer.alloc(4);
|
const buf = Buffer.alloc(4);
|
||||||
@@ -91,6 +111,7 @@ function runNpmInstall({ cwd, pkgs, extraArgs = [], timeout = 180000 }) {
|
|||||||
function npmInstall(pkgs, opts = {}) {
|
function npmInstall(pkgs, opts = {}) {
|
||||||
const cwd = ensureRuntimeDir();
|
const cwd = ensureRuntimeDir();
|
||||||
const extra = opts.optional ? ["--no-save"] : [];
|
const extra = opts.optional ? ["--no-save"] : [];
|
||||||
|
if (opts.ignoreScripts) extra.push("--ignore-scripts");
|
||||||
if (!opts.silent) console.log("⏳ Installing SQLite engine (first run)...");
|
if (!opts.silent) console.log("⏳ Installing SQLite engine (first run)...");
|
||||||
const res = runNpmInstall({ cwd, pkgs, extraArgs: extra, timeout: opts.timeout || 180000 });
|
const res = runNpmInstall({ cwd, pkgs, extraArgs: extra, timeout: opts.timeout || 180000 });
|
||||||
if (!res.ok && !opts.silent) {
|
if (!res.ok && !opts.silent) {
|
||||||
@@ -129,7 +150,10 @@ function ensureSqliteRuntime({ silent = false } = {}) {
|
|||||||
return { betterSqlite: true, sqlJs: sqlJsOk };
|
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 {
|
return {
|
||||||
betterSqlite: ok && hasModule("better-sqlite3") && isBetterSqliteBinaryValid(),
|
betterSqlite: ok && hasModule("better-sqlite3") && isBetterSqliteBinaryValid(),
|
||||||
sqlJs: sqlJsOk,
|
sqlJs: sqlJsOk,
|
||||||
|
|||||||
Reference in New Issue
Block a user