From 6283281bd88ff3594a3ecc4377742b5fd471074c Mon Sep 17 00:00:00 2001 From: digua Date: Fri, 10 Jul 2026 18:07:03 +0800 Subject: [PATCH] fix(desktop): resolve packaged sqlite binding --- apps/desktop/main/native-sqlite.test.ts | 37 ++++++++++++++++++++++++- apps/desktop/main/native-sqlite.ts | 30 ++++++++++++++++---- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/apps/desktop/main/native-sqlite.test.ts b/apps/desktop/main/native-sqlite.test.ts index 833d2b14..4858a5e2 100644 --- a/apps/desktop/main/native-sqlite.test.ts +++ b/apps/desktop/main/native-sqlite.test.ts @@ -2,7 +2,11 @@ import assert from 'node:assert/strict' import test from 'node:test' import * as path from 'node:path' -import { findDesktopNativeBinding, resolveDesktopNativeBinding } from './native-sqlite' +import { + findDesktopNativeBinding, + resolveDesktopNativeBinding, + resolveElectronNativeBindingPath, +} from './native-sqlite' const BINDING = path.join('native', 'better_sqlite3.node') @@ -35,6 +39,37 @@ test('findDesktopNativeBinding stops at the filesystem root without hanging', () assert.ok(calls <= 5) }) +test('resolveElectronNativeBindingPath prefers packaged resources binding', () => { + const resourcesPath = path.join('/package', 'resources') + const packaged = path.join(resourcesPath, BINDING) + const desktopRoot = path.join('/repo', 'apps', 'desktop') + const dev = path.join(desktopRoot, BINDING) + const exists = (candidate: string) => candidate === packaged || candidate === dev + + assert.equal( + resolveElectronNativeBindingPath({ + startDir: path.join(desktopRoot, 'out', 'main'), + resourcesPath, + exists, + }), + packaged + ) +}) + +test('resolveElectronNativeBindingPath falls back to the dev binding', () => { + const desktopRoot = path.join('/repo', 'apps', 'desktop') + const dev = path.join(desktopRoot, BINDING) + + assert.equal( + resolveElectronNativeBindingPath({ + startDir: path.join(desktopRoot, 'out', 'main'), + resourcesPath: path.join('/Electron.app', 'Contents', 'Resources'), + exists: (candidate) => candidate === dev, + }), + dev + ) +}) + test('resolveDesktopNativeBinding returns undefined under plain Node', () => { // 测试始终跑在系统 Node(非 Electron):node_modules 保持 Node ABI,无需注入 assert.equal(process.versions.electron, undefined) diff --git a/apps/desktop/main/native-sqlite.ts b/apps/desktop/main/native-sqlite.ts index da9431d7..45774155 100644 --- a/apps/desktop/main/native-sqlite.ts +++ b/apps/desktop/main/native-sqlite.ts @@ -7,9 +7,9 @@ * copy at apps/desktop/native/better_sqlite3.node (prepared by * scripts/ensure-native.mjs). * - * In the packaged app this resolver returns undefined: electron-builder - * (npmRebuild: true) rebuilds the bundled node_modules copy for Electron, so - * the default resolution is already correct there. + * Windows packaged apps ship the desktop-owned binding at + * resources/native/better_sqlite3.node and load it explicitly. Other packaged + * platforms can still fall back to electron-builder's rebuilt node_modules copy. * * Worker threads cannot compute this reliably from their own __dirname, so the * main process resolves once and passes the path through workerData. @@ -41,12 +41,29 @@ export function findDesktopNativeBinding( return undefined } +interface ElectronNativeBindingPathOptions { + startDir: string + resourcesPath: string + exists?: (candidate: string) => boolean +} + +/** + * 优先解析成品包 resources/native,其次回退到 dev 工作区 native 目录。 + * 这里只选择 nativeBinding 路径,不加载数据库,也不接触用户数据。 + */ +export function resolveElectronNativeBindingPath(options: ElectronNativeBindingPathOptions): string | undefined { + const exists = options.exists ?? fs.existsSync + const packagedCandidate = path.join(options.resourcesPath, NATIVE_RELATIVE_PATH) + if (exists(packagedCandidate)) return packagedCandidate + return findDesktopNativeBinding(options.startDir, exists) +} + let cachedBinding: string | undefined let resolved = false /** * Resolve the Electron-ABI better-sqlite3 binding for the current process. - * Returns undefined under plain Node (tests/tools) and in the packaged app. + * Returns undefined under plain Node (tests/tools). */ export function resolveDesktopNativeBinding(): string | undefined { if (resolved) return cachedBinding @@ -57,7 +74,10 @@ export function resolveDesktopNativeBinding(): string | undefined { return cachedBinding } - cachedBinding = findDesktopNativeBinding(__dirname) + cachedBinding = resolveElectronNativeBindingPath({ + startDir: __dirname, + resourcesPath: process.resourcesPath, + }) if (!cachedBinding && !__dirname.includes('app.asar')) { console.warn( '[native-sqlite] Electron-ABI better-sqlite3 binding not found; ' +