Skip to content

Commit ff168ab

Browse files
authored
fix(electron): use NEXT_DIST_DIR when stripping stale native modules (#8794)
removeNativeModules() was called with a hardcoded ".next" path while the actual distDir is NEXT_DIST_DIR (".build/next" by default). Because the function early-returns when the directory does not exist, the cleanup silently no-opped and the plain-Node-ABI better-sqlite3 copy produced by `next build` survived into the packaged app. At runtime the standalone server runs under ELECTRON_RUN_AS_NODE, so it needs the Electron ABI (148 for electron 43). Loading the ABI-137 copy fails with ERR_DLOPEN_FAILED, the app falls back to the sql.js WASM driver, the connection is closed and retried in a loop, WASM memory is never reclaimed and the process OOMs -> HTTP 500 on every route. Also adds assertNoStaleHashedNatives() so a wrong baseDir fails the build instead of silently shipping a broken installer. This has regressed at least twice (#1497 with ABI 127 vs 145, #7082/#7681 with 137 vs 148). Refs #7082, #7681, #1497, #8792. Supersedes the abandoned #7123.
1 parent 0bd7cea commit ff168ab

1 file changed

Lines changed: 28 additions & 1 deletion

File tree

scripts/build/prepare-electron-standalone.mjs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,24 @@ function removeNativeModules(baseDir, prefixes = ["keytar"]) {
8383
}
8484
}
8585

86+
// Fail the build if hashed native copies survived the cleanup above. Without this,
87+
// a wrong baseDir makes removeNativeModules() a silent no-op (it early-returns when
88+
// the directory does not exist) and the ABI mismatch only surfaces at runtime on a
89+
// user machine as "Internal Server Error" on every route.
90+
function assertNoStaleHashedNatives(baseDir, prefixes) {
91+
if (!existsSync(baseDir)) return;
92+
const leftovers = readdirSync(baseDir).filter((dir) =>
93+
prefixes.some((p) => dir.startsWith(p))
94+
);
95+
if (leftovers.length > 0) {
96+
throw new Error(
97+
`[electron] stale native module copies survived cleanup in ${baseDir}: ` +
98+
`${leftovers.join(", ")}. These carry the plain-Node ABI and shadow the ` +
99+
`Electron-rebuilt binaries at runtime (ERR_DLOPEN_FAILED -> sql.js fallback -> OOM).`
100+
);
101+
}
102+
}
103+
86104
// --- Electron-UNIQUE: rebuild better-sqlite3 against the Electron ABI --------
87105
//
88106
// The `npm ci` at the repo root compiles better-sqlite3 for the CI *Node* ABI
@@ -197,7 +215,16 @@ removeGeneratedElectronArtifacts();
197215
// so it cannot shadow the rebuilt one.
198216
rebuildBetterSqlite3ForElectron(join(ELECTRON_STANDALONE_DIR, "node_modules"));
199217
removeNativeModules(join(ELECTRON_STANDALONE_DIR, "node_modules"), ["keytar"]);
200-
removeNativeModules(join(ELECTRON_STANDALONE_DIR, ".next", "node_modules"), [
218+
removeNativeModules(join(ELECTRON_STANDALONE_DIR, NEXT_DIST_DIR, "node_modules"), [
219+
"better-sqlite3",
220+
"keytar",
221+
]);
222+
223+
// Post-condition: the cleanup above must actually have removed the stale Node-ABI
224+
// copies. It silently no-opped across releases because the path was hardcoded to
225+
// ".next" while distDir is ".build/next", so an ABI-mismatched better_sqlite3.node
226+
// shipped inside the installer and the app fell back to sql.js and OOM-ed.
227+
assertNoStaleHashedNatives(join(ELECTRON_STANDALONE_DIR, NEXT_DIST_DIR, "node_modules"), [
201228
"better-sqlite3",
202229
"keytar",
203230
]);

0 commit comments

Comments
 (0)