From e193c6ac34d51160089dee863d3a41c9ec620e60 Mon Sep 17 00:00:00 2001 From: lucas77778 <3098274296@qq.com> Date: Thu, 27 Aug 2026 20:59:57 +0800 Subject: [PATCH] fix(desktop): verify better-sqlite3's shipped prebuild --- apps/daemon/AGENTS.md | 6 +++--- apps/daemon/scripts/package-daemon.mts | 5 +++-- apps/desktop/electron-builder.yml | 6 +++--- apps/desktop/scripts/verify-artifacts.mts | 26 ++++++++++++++++------- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/apps/daemon/AGENTS.md b/apps/daemon/AGENTS.md index 37525e949..9b7ac11b1 100644 --- a/apps/daemon/AGENTS.md +++ b/apps/daemon/AGENTS.md @@ -165,9 +165,9 @@ Runs via `tsx` in dev (`pnpm -F @linkcode/daemon dev`) and a `tsup` bundle in pr materializes a self-contained dir at `apps/daemon/standalone` (gitignored; pass an explicit path as argv for CI) via `pnpm --prod deploy` — the tsup bundle plus its runtime externals flat in the dir's own `node_modules`, runnable anywhere as `node --import ./dist/instrument.js dist/index.js`. This is - distinct from the desktop bundle: it targets **plain Node** (better-sqlite3 keeps its prebuild-install - binary — a **same-platform** artifact, build per target), and it prunes the host-arch agent CLI - platform packages (the daemon downloads them at runtime via `@linkcode/assets`, as the desktop + distinct from the desktop bundle: it targets **plain Node** (still a **same-platform** artifact — the + napi-rs optional deps and @sentry's profiler resolve host-only — build per target), and it prunes + the host-arch agent CLI platform packages (the daemon downloads them at runtime via `@linkcode/assets`, as the desktop does). The pi npm closure needs no prune (CODE-219): its SDK is a devDependency of agent-adapter, so the `--prod` deploy never materializes it — the daemon downloads the managed closure on first use. diff --git a/apps/daemon/scripts/package-daemon.mts b/apps/daemon/scripts/package-daemon.mts index 2e0dbda86..e9b773282 100644 --- a/apps/daemon/scripts/package-daemon.mts +++ b/apps/daemon/scripts/package-daemon.mts @@ -13,8 +13,9 @@ * `node --import ./dist/instrument.js dist/index.js` with nothing else on disk. * * Unlike the desktop bundle (Electron `utilityProcess`, native modules rebuilt to Electron's ABI), - * this targets plain Node: better-sqlite3 keeps the prebuild-install binary for the build host's - * Node/OS/arch. It is therefore a same-platform artifact — build it on (or for) each target. + * this targets plain Node. better-sqlite3 carries a NAPI prebuild per target, but the host-only + * napi-rs optional deps and @sentry's ABI-pinned profiler do not: a same-platform artifact — build + * it on (or for) each target. * * Agent CLI platform binaries are pruned: they are host-arch, ~230 MB each, and the daemon * provisions them at runtime through its managed-asset store (@linkcode/assets, CODE-111) exactly diff --git a/apps/desktop/electron-builder.yml b/apps/desktop/electron-builder.yml index dc73ef4f7..dd5d5f7ae 100644 --- a/apps/desktop/electron-builder.yml +++ b/apps/desktop/electron-builder.yml @@ -58,9 +58,9 @@ files: # verify-artifacts.mts guards against reintroduction. # Dead weight in the deploy closure (CODE-215). @linkcode/* are raw-TS workspace sources — # main/preload/daemon all bundle them, and migrations run from out/drizzle, so nothing resolves - # them from node_modules at runtime. better-sqlite3/deps is the sqlite3 C amalgamation: - # @electron/rebuild needs it at BUILD time (which is why the staging prune must not delete it), - # but the compiled build/Release binding is all the app loads. + # them from node_modules at runtime. better-sqlite3/deps is the sqlite3 C amalgamation, only ever + # compiled if a from-source @electron/rebuild happens (which is why the staging prune must not + # delete it); the app loads the shipped NAPI prebuild under better-sqlite3/prebuilds instead. - '!node_modules/@linkcode/**' - '!node_modules/better-sqlite3/deps/**' diff --git a/apps/desktop/scripts/verify-artifacts.mts b/apps/desktop/scripts/verify-artifacts.mts index d95da51f1..95ac5afb3 100644 --- a/apps/desktop/scripts/verify-artifacts.mts +++ b/apps/desktop/scripts/verify-artifacts.mts @@ -75,15 +75,23 @@ const EXPECTED: Partial> = { }; const SIDECAR_BINARY = argv[2] === 'win' ? 'linkcode-pty.exe' : 'linkcode-pty'; +/** Node's platform name per builder platform — the token better-sqlite3 names its prebuilds by. */ +const NODE_PLATFORM: Partial> = { + mac: 'darwin', + win: 'win32', + linux: 'linux', +}; /** - * better-sqlite3's compiled binding, smartUnpacked beside the asar; the daemon requires it at boot. - * A build where @electron/rebuild silently rebuilt nothing ships the wrong CPU/ABI and every client - * shows "Unable to connect to the daemon" (broke every release through 0.2.1; see package-app.mts). + * better-sqlite3's binding, smartUnpacked beside the asar; the daemon requires it at boot. Since + * v13 it is one NAPI prebuild per platform-arch shipped in the tarball (`build/Release` is only + * written when node-gyp actually compiles, which it now skips), so what breaks is the staging + * prune keeping the wrong target — every client then shows "Unable to connect to the daemon". */ -const NATIVE_BINDING = 'node_modules/better-sqlite3/build/Release/better_sqlite3.node'.replaceAll( - '/', - sep, -); +function sqliteBinding(platform: string, arch: string): string | null { + const nodePlatform = NODE_PLATFORM[platform]; + if (nodePlatform === undefined) return null; + return `node_modules/better-sqlite3/prebuilds/${nodePlatform}-${arch}.node`.replaceAll('/', sep); +} /** * napi-rs platform-package triple for the target this artifact was packed for. napi-rs ships one * optional dependency per triple and installs only the host's, so a cross-packed build carries no @@ -183,8 +191,10 @@ function readBinaryArch(file: string): 'x64' | 'arm64' | null { */ function verifyNativeBindings(platform: string, resourceDir: string, problems: string[]): void { const expectedArch = resourceDir.includes('arm64') ? 'arm64' : 'x64'; + const sqlite = sqliteBinding(platform, expectedArch); const keyring = keyringBinding(platform, expectedArch); - const bindings: Array<[label: string, path: string]> = [['better-sqlite3', NATIVE_BINDING]]; + const bindings: Array<[label: string, path: string]> = []; + if (sqlite !== null) bindings.push(['better-sqlite3', sqlite]); if (keyring !== null) bindings.push(['@napi-rs/keyring', keyring]); for (const [label, relative] of bindings) {