11#!/usr/bin/env node
22
3- import { cpSync , existsSync , lstatSync , readFileSync , readdirSync , rmSync } from "node:fs" ;
3+ import { existsSync , lstatSync , readdirSync , rmSync } from "node:fs" ;
44import { basename , dirname , join , relative } from "node:path" ;
55import { fileURLToPath } from "node:url" ;
6- import { spawnSync } from "node:child_process" ;
76import { assembleStandalone } from "./assembleStandalone.mjs" ;
8- import { buildRebuildSpawnPlan } from "./electronRebuildPlan.mjs" ;
7+ import { assertSqlitePrebuildExists } from "./electronRebuildPlan.mjs" ;
98import { pruneElectronRuntimeDocs } from "./electronRuntimeDocs.mjs" ;
109
1110const __filename = fileURLToPath ( import . meta. url ) ;
@@ -90,9 +89,7 @@ function removeNativeModules(baseDir, prefixes = ["keytar"]) {
9089// user machine as "Internal Server Error" on every route.
9190function assertNoStaleHashedNatives ( baseDir , prefixes ) {
9291 if ( ! existsSync ( baseDir ) ) return ;
93- const leftovers = readdirSync ( baseDir ) . filter ( ( dir ) =>
94- prefixes . some ( ( p ) => dir . startsWith ( p ) )
95- ) ;
92+ const leftovers = readdirSync ( baseDir ) . filter ( ( dir ) => prefixes . some ( ( p ) => dir . startsWith ( p ) ) ) ;
9693 if ( leftovers . length > 0 ) {
9794 throw new Error (
9895 `[electron] stale native module copies survived cleanup in ${ baseDir } : ` +
@@ -102,77 +99,43 @@ function assertNoStaleHashedNatives(baseDir, prefixes) {
10299 }
103100}
104101
105- // --- Electron-UNIQUE: rebuild better-sqlite3 against the Electron ABI --------
102+ // --- Electron-UNIQUE: verify better-sqlite3 Node-API prebuilds -------- --------
106103//
107- // The `npm ci` at the repo root compiles better-sqlite3 for the CI *Node* ABI
108- // (e.g. 137 for Node 24). The packaged app runs its Next.js server via
109- // ELECTRON_RUN_AS_NODE, so it needs the *Electron* ABI (146 for electron 42,
110- // 148 for electron 43). We cannot rely on electron-builder's @electron/rebuild
111- // here: it searches `electron/node_modules` (where better-sqlite3 does not live)
112- // and, with the default prebuild path, tries to fetch a prebuilt binary — but
113- // better-sqlite3@12.11.1 only ships prebuilds up to electron-v146, so electron
114- // 43 (v148) silently gets no rebuild and the app dies with "Nenhum driver
115- // SQLite disponível — better-sqlite3 (falhou)".
104+ // better-sqlite3 >= 13 ships Node-API (NAPI_VERSION=10) prebuilds for every
105+ // platform we package (darwin/linux/linuxmusl/win32 × x64/arm64) inside the
106+ // npm tarball. Node-API addons are ABI-independent, so the same prebuild runs
107+ // under plain Node (CI, CLI) and under the packaged app's ELECTRON_RUN_AS_NODE
108+ // server (verified against electron 43 / NODE_MODULE_VERSION 148 — issue
109+ // #10321 Stage 6). The historical source rebuild below existed because
110+ // better-sqlite3@12 only shipped prebuilds up to electron-v146 and electron 43
111+ // (v148) silently got no binary; v13 makes that obsolete.
116112//
117- // Instead we copy the *full* module (source + binding.gyp) from the root into
118- // the standalone and compile it from source against the Electron headers, so
119- // `bindings` finds a correct build/Release/better_sqlite3.node regardless of
120- // prebuild availability. Robust to any current/future electron version.
121-
122- function readElectronVersion ( ) {
123- const pkg = JSON . parse ( readFileSync ( join ( ROOT , "electron" , "package.json" ) , "utf8" ) ) ;
124- const raw = pkg . devDependencies ?. electron || pkg . dependencies ?. electron || "" ;
125- return String ( raw ) . replace ( / ^ [ \^ ~ ] / , "" ) ;
126- }
113+ // Instead of compiling from source on every build (tens of seconds to minutes
114+ // per platform), we fail fast when the prebuild for the CURRENT build platform
115+ // is missing — a missing prebuild must kill the build here, not the app on a
116+ // user machine with "Nenhum driver SQLite disponível — better-sqlite3 (falhou)".
127117
128- function rebuildBetterSqlite3ForElectron ( standaloneNodeModules ) {
129- const srcMod = join ( ROOT , "node_modules" , "better-sqlite3" ) ;
130- if ( ! existsSync ( srcMod ) ) {
131- console . warn ( "[electron] better-sqlite3 not found at repo root — skipping ABI rebuild." ) ;
132- return ;
133- }
134- const electronVersion = readElectronVersion ( ) ;
135- if ( ! electronVersion ) {
136- throw new Error ( "[electron] could not resolve electron version for better-sqlite3 rebuild." ) ;
137- }
118+ function verifyBetterSqlite3Prebuilds ( standaloneNodeModules ) {
138119 const destMod = join ( standaloneNodeModules , "better-sqlite3" ) ;
139- // copyNatives only copies build/; we need the full module (src + binding.gyp)
140- // to compile from source. Overwrite the copied Node-ABI build in the process.
141- cpSync ( srcMod , destMod , { recursive : true , force : true } ) ;
142- rmSync ( join ( destMod , "build" ) , { recursive : true , force : true } ) ;
143-
144- console . log ( `[electron] rebuilding better-sqlite3 against electron ${ electronVersion } ABI…` ) ;
145- const plan = buildRebuildSpawnPlan ( process . platform ) ;
146- const result = spawnSync (
147- plan . command ,
148- plan . args ,
149- {
150- cwd : destMod ,
151- stdio : "inherit" ,
152- // .cmd shims must go through a shell on Windows (CVE-2024-27980 hardening
153- // makes a shell-less spawn fail with status null); args are fixed literals.
154- shell : plan . shell ,
155- // Compile against the Electron headers (not Node's) so the .node lands in
156- // build/Release with the Electron NODE_MODULE_VERSION. No shell interpolation.
157- env : {
158- ...process . env ,
159- npm_config_runtime : "electron" ,
160- npm_config_target : electronVersion ,
161- npm_config_disturl : "https://electronjs.org/headers" ,
162- npm_config_arch : process . arch ,
163- npm_config_build_from_source : "true" ,
164- } ,
165- }
166- ) ;
167- if ( result . status !== 0 ) {
168- throw new Error (
169- `[electron] better-sqlite3 rebuild against electron ${ electronVersion } failed (exit ${ result . status } ).`
170- ) ;
120+ if ( ! existsSync ( destMod ) ) {
121+ console . warn ( "[electron] better-sqlite3 not found in standalone — skipping prebuild check." ) ;
122+ return ;
171123 }
172- // Drop the now-unneeded compile inputs to keep the packaged app lean.
173- for ( const dir of [ "deps" , "src" , "build/Debug" , "build/obj.target" ] ) {
124+
125+ // Fail fast when the loader would find no prebuild for THIS build platform.
126+ // Mirrors better-sqlite3's own lib/binding.js selection logic.
127+ const reportHeader = process . report ?. getReport ?. ( ) . header ;
128+ assertSqlitePrebuildExists ( destMod , process . platform , process . arch , reportHeader ) ;
129+
130+ // Drop compile inputs and stale Node-ABI build outputs to keep the packaged
131+ // app lean and to guarantee the loader resolves the prebuild, not a leftover
132+ // build/Release/better_sqlite3.node compiled for a different ABI.
133+ for ( const dir of [ "build" , "deps" , "src" ] ) {
174134 rmSync ( join ( destMod , dir ) , { recursive : true , force : true } ) ;
175135 }
136+ console . log (
137+ `[electron] better-sqlite3 Node-API prebuilds verified for ${ process . platform } -${ process . arch } .`
138+ ) ;
176139}
177140
178141function logContextualError ( error ) {
@@ -217,12 +180,12 @@ if (docsPrune.removedFiles > 0) {
217180// Electron-UNIQUE post-assembly steps
218181removeGeneratedElectronArtifacts ( ) ;
219182
220- // Rebuild better-sqlite3 from source against the Electron ABI in the primary
221- // node_modules (where the standalone server resolves it). keytar is still
222- // stripped so electron-builder's @electron/rebuild handles it (it has electron
223- // prebuilds); also drop any stray Node-ABI better-sqlite3 under .next/node_modules
224- // so it cannot shadow the rebuilt one.
225- rebuildBetterSqlite3ForElectron ( join ( ELECTRON_STANDALONE_DIR , "node_modules" ) ) ;
183+ // Verify better-sqlite3 Node-API prebuilds in the primary node_modules (where
184+ // the standalone server resolves it). keytar is still stripped so
185+ // electron-builder's @electron/rebuild handles it (it has electron prebuilds);
186+ // also drop any stray better-sqlite3 under .next/node_modules so it cannot
187+ // shadow the prebuild-backed one.
188+ verifyBetterSqlite3Prebuilds ( join ( ELECTRON_STANDALONE_DIR , "node_modules" ) ) ;
226189removeNativeModules ( join ( ELECTRON_STANDALONE_DIR , "node_modules" ) , [ "keytar" ] ) ;
227190removeNativeModules ( join ( ELECTRON_STANDALONE_DIR , NEXT_DIST_DIR , "node_modules" ) , [
228191 "better-sqlite3" ,
0 commit comments