From e6c14916d12a340144a182dfd6ac80d10f8084be Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Mon, 22 Dec 2025 20:46:05 -0500 Subject: [PATCH 1/7] fix(desktop): include bindings package for better-sqlite3 native module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit better-sqlite3 uses the `bindings` npm package to locate its native .node file at runtime. This package (and its dependency file-uri-to-path) were not being included in the electron-builder output, causing the app to crash on startup with "Cannot find module 'bindings'" error. Changes: - Add bindings and file-uri-to-path to electron-builder files section - Add both packages to asarUnpack for proper path resolution - Update copy-native-modules.ts to also handle these dependencies 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- apps/desktop/electron-builder.ts | 15 +++++ apps/desktop/scripts/copy-native-modules.ts | 69 ++++++++++++++------- 2 files changed, 62 insertions(+), 22 deletions(-) diff --git a/apps/desktop/electron-builder.ts b/apps/desktop/electron-builder.ts index 68820521693..6ade6f1e1f3 100644 --- a/apps/desktop/electron-builder.ts +++ b/apps/desktop/electron-builder.ts @@ -34,6 +34,9 @@ const config: Configuration = { asar: true, asarUnpack: [ "**/node_modules/better-sqlite3/**/*", + // better-sqlite3 uses `bindings` to locate native modules - must be unpacked together + "**/node_modules/bindings/**/*", + "**/node_modules/file-uri-to-path/**/*", "**/node_modules/node-pty/**/*", // Sound files must be unpacked so external audio players (afplay, paplay, etc.) can access them "**/resources/sounds/**/*", @@ -61,6 +64,18 @@ const config: Configuration = { to: "node_modules/better-sqlite3", filter: ["**/*"], }, + // better-sqlite3 uses `bindings` package to locate its native .node file + { + from: "node_modules/bindings", + to: "node_modules/bindings", + filter: ["**/*"], + }, + // `bindings` requires `file-uri-to-path` for file:// URL handling + { + from: "node_modules/file-uri-to-path", + to: "node_modules/file-uri-to-path", + filter: ["**/*"], + }, { from: "node_modules/node-pty", to: "node_modules/node-pty", diff --git a/apps/desktop/scripts/copy-native-modules.ts b/apps/desktop/scripts/copy-native-modules.ts index fc176626c12..2ea972edae9 100644 --- a/apps/desktop/scripts/copy-native-modules.ts +++ b/apps/desktop/scripts/copy-native-modules.ts @@ -16,42 +16,67 @@ import { cpSync, existsSync, lstatSync, realpathSync, rmSync } from "node:fs"; import { dirname, join } from "node:path"; +// Native modules that must exist for the app to work const NATIVE_MODULES = ["better-sqlite3", "node-pty"] as const; -function prepareNativeModules() { - console.log("Preparing native modules for electron-builder..."); +// Dependencies of native modules that need to be copied (may be hoisted or symlinked) +const NATIVE_MODULE_DEPS = ["bindings", "file-uri-to-path"] as const; - const nodeModulesDir = join(dirname(import.meta.dirname), "node_modules"); - - for (const moduleName of NATIVE_MODULES) { - const modulePath = join(nodeModulesDir, moduleName); +function copyModuleIfSymlink( + nodeModulesDir: string, + moduleName: string, + required: boolean, +): boolean { + const modulePath = join(nodeModulesDir, moduleName); - if (!existsSync(modulePath)) { + if (!existsSync(modulePath)) { + if (required) { console.error(` [ERROR] ${moduleName} not found at ${modulePath}`); process.exit(1); } + console.log(` ${moduleName}: not found (skipping)`); + return false; + } - const stats = lstatSync(modulePath); + const stats = lstatSync(modulePath); - if (stats.isSymbolicLink()) { - // Resolve symlink to get real path - const realPath = realpathSync(modulePath); - console.log(` ${moduleName}: symlink -> replacing with real files`); - console.log(` Real path: ${realPath}`); + if (stats.isSymbolicLink()) { + // Resolve symlink to get real path + const realPath = realpathSync(modulePath); + console.log(` ${moduleName}: symlink -> replacing with real files`); + console.log(` Real path: ${realPath}`); - // Remove the symlink - rmSync(modulePath); + // Remove the symlink + rmSync(modulePath); - // Copy the actual files - cpSync(realPath, modulePath, { recursive: true }); + // Copy the actual files + cpSync(realPath, modulePath, { recursive: true }); - console.log(` Copied to: ${modulePath}`); - } else { - console.log(` ${moduleName}: already real directory (not a symlink)`); - } + console.log(` Copied to: ${modulePath}`); + } else { + console.log(` ${moduleName}: already real directory (not a symlink)`); + } + + return true; +} + +function prepareNativeModules() { + console.log("Preparing native modules for electron-builder..."); + + const nodeModulesDir = join(dirname(import.meta.dirname), "node_modules"); + + // Copy required native modules + for (const moduleName of NATIVE_MODULES) { + copyModuleIfSymlink(nodeModulesDir, moduleName, true); + } + + // Copy native module dependencies (not required but needed if present) + console.log("\nPreparing native module dependencies..."); + for (const moduleName of NATIVE_MODULE_DEPS) { + copyModuleIfSymlink(nodeModulesDir, moduleName, false); } - console.log("Done!"); + console.log("\nDone!"); } prepareNativeModules(); From c7f0994038bf514018c5eff28e3dbe37a099a3ef Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Mon, 22 Dec 2025 21:05:42 -0500 Subject: [PATCH 2/7] fix(desktop): add bindings as explicit dependency for better-sqlite3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `bindings` package (used by better-sqlite3 to locate its native .node file) was not being installed into the local node_modules because it's a transitive dependency that bun may keep in its global cache. Adding `bindings` and `file-uri-to-path` as explicit dependencies ensures they're installed locally and can be properly packaged by electron-builder. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- apps/desktop/package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 5a3857f4a07..0b2f483465c 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -59,6 +59,7 @@ "@xterm/addon-webgl": "^0.18.0", "@xterm/xterm": "^5.5.0", "better-sqlite3": "12.5.0", + "bindings": "^1.5.0", "clsx": "^2.1.1", "culori": "^4.0.2", "date-fns": "^4.1.0", @@ -71,6 +72,7 @@ "execa": "^9.6.0", "express": "^5.1.0", "fast-glob": "^3.3.3", + "file-uri-to-path": "^1.0.0", "framer-motion": "^12.23.24", "http-proxy": "^1.18.1", "jose": "^6.1.3", From 00b93ad287ef3778239475d048d3cd4c86a39bf3 Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Mon, 22 Dec 2025 21:10:19 -0500 Subject: [PATCH 3/7] fix(desktop): use correct paths for hoisted node_modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In bun workspaces, packages are hoisted to the monorepo root node_modules. Updated electron-builder paths and copy-native-modules script to use ../../node_modules/ instead of the local node_modules/. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- apps/desktop/electron-builder.ts | 9 +++++---- apps/desktop/scripts/copy-native-modules.ts | 3 ++- bun.lock | 2 ++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/desktop/electron-builder.ts b/apps/desktop/electron-builder.ts index 6ade6f1e1f3..4a7abbddcb4 100644 --- a/apps/desktop/electron-builder.ts +++ b/apps/desktop/electron-builder.ts @@ -57,27 +57,28 @@ const config: Configuration = { filter: ["**/*"], }, // Native modules that can't be bundled by Vite. + // Paths are relative to monorepo root node_modules (hoisted by bun workspaces). // The copy:native-modules script replaces symlinks with real files // before building (required for Bun 1.3+ isolated installs). { - from: "node_modules/better-sqlite3", + from: "../../node_modules/better-sqlite3", to: "node_modules/better-sqlite3", filter: ["**/*"], }, // better-sqlite3 uses `bindings` package to locate its native .node file { - from: "node_modules/bindings", + from: "../../node_modules/bindings", to: "node_modules/bindings", filter: ["**/*"], }, // `bindings` requires `file-uri-to-path` for file:// URL handling { - from: "node_modules/file-uri-to-path", + from: "../../node_modules/file-uri-to-path", to: "node_modules/file-uri-to-path", filter: ["**/*"], }, { - from: "node_modules/node-pty", + from: "../../node_modules/node-pty", to: "node_modules/node-pty", filter: ["**/*"], }, diff --git a/apps/desktop/scripts/copy-native-modules.ts b/apps/desktop/scripts/copy-native-modules.ts index 2ea972edae9..f1e1924aa0c 100644 --- a/apps/desktop/scripts/copy-native-modules.ts +++ b/apps/desktop/scripts/copy-native-modules.ts @@ -63,7 +63,8 @@ function copyModuleIfSymlink( function prepareNativeModules() { console.log("Preparing native modules for electron-builder..."); - const nodeModulesDir = join(dirname(import.meta.dirname), "node_modules"); + // In bun workspaces, packages are hoisted to the monorepo root node_modules + const nodeModulesDir = join(dirname(import.meta.dirname), "..", "..", "node_modules"); // Copy required native modules for (const moduleName of NATIVE_MODULES) { diff --git a/bun.lock b/bun.lock index 121fd07fe6f..991f0e30f62 100644 --- a/bun.lock +++ b/bun.lock @@ -145,6 +145,7 @@ "@xterm/addon-webgl": "^0.18.0", "@xterm/xterm": "^5.5.0", "better-sqlite3": "12.5.0", + "bindings": "^1.5.0", "clsx": "^2.1.1", "culori": "^4.0.2", "date-fns": "^4.1.0", @@ -157,6 +158,7 @@ "execa": "^9.6.0", "express": "^5.1.0", "fast-glob": "^3.3.3", + "file-uri-to-path": "^1.0.0", "framer-motion": "^12.23.24", "http-proxy": "^1.18.1", "jose": "^6.1.3", From 6224c5ac572424b0347efb7d8efd5a2d0ef28f42 Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Mon, 22 Dec 2025 21:32:51 -0500 Subject: [PATCH 4/7] fix(desktop): revert paths to use workspace node_modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bun creates symlinks for direct dependencies in the workspace's node_modules, not the root. The explicit dependency additions (bindings, file-uri-to-path) will make bun create those symlinks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- apps/desktop/electron-builder.ts | 10 +++++----- apps/desktop/scripts/copy-native-modules.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/desktop/electron-builder.ts b/apps/desktop/electron-builder.ts index 4a7abbddcb4..3c1f03ce4ae 100644 --- a/apps/desktop/electron-builder.ts +++ b/apps/desktop/electron-builder.ts @@ -57,28 +57,28 @@ const config: Configuration = { filter: ["**/*"], }, // Native modules that can't be bundled by Vite. - // Paths are relative to monorepo root node_modules (hoisted by bun workspaces). + // bun creates symlinks for direct deps in workspace node_modules. // The copy:native-modules script replaces symlinks with real files // before building (required for Bun 1.3+ isolated installs). { - from: "../../node_modules/better-sqlite3", + from: "node_modules/better-sqlite3", to: "node_modules/better-sqlite3", filter: ["**/*"], }, // better-sqlite3 uses `bindings` package to locate its native .node file { - from: "../../node_modules/bindings", + from: "node_modules/bindings", to: "node_modules/bindings", filter: ["**/*"], }, // `bindings` requires `file-uri-to-path` for file:// URL handling { - from: "../../node_modules/file-uri-to-path", + from: "node_modules/file-uri-to-path", to: "node_modules/file-uri-to-path", filter: ["**/*"], }, { - from: "../../node_modules/node-pty", + from: "node_modules/node-pty", to: "node_modules/node-pty", filter: ["**/*"], }, diff --git a/apps/desktop/scripts/copy-native-modules.ts b/apps/desktop/scripts/copy-native-modules.ts index f1e1924aa0c..0f28a56b4bd 100644 --- a/apps/desktop/scripts/copy-native-modules.ts +++ b/apps/desktop/scripts/copy-native-modules.ts @@ -63,8 +63,8 @@ function copyModuleIfSymlink( function prepareNativeModules() { console.log("Preparing native modules for electron-builder..."); - // In bun workspaces, packages are hoisted to the monorepo root node_modules - const nodeModulesDir = join(dirname(import.meta.dirname), "..", "..", "node_modules"); + // bun creates symlinks for direct dependencies in the workspace's node_modules + const nodeModulesDir = join(dirname(import.meta.dirname), "node_modules"); // Copy required native modules for (const moduleName of NATIVE_MODULES) { From c4f87a3b1acf76a987a96dca35564910e2931e62 Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Mon, 22 Dec 2025 21:43:52 -0500 Subject: [PATCH 5/7] fix(desktop): enable npmRebuild for Electron native modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The better-sqlite3 native module was compiled for bun's Node.js version (NODE_MODULE_VERSION 127) but Electron 39 requires NODE_MODULE_VERSION 140. Enabling npmRebuild allows electron-builder to rebuild native modules for Electron's Node.js version. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- apps/desktop/electron-builder.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/desktop/electron-builder.ts b/apps/desktop/electron-builder.ts index 3c1f03ce4ae..159008887d0 100644 --- a/apps/desktop/electron-builder.ts +++ b/apps/desktop/electron-builder.ts @@ -85,10 +85,8 @@ const config: Configuration = { "!**/.DS_Store", ], - // Skip npm rebuild - dependencies already built in monorepo - npmRebuild: false, - buildDependenciesFromSource: false, - nodeGypRebuild: false, + // Rebuild native modules for Electron's Node.js version + npmRebuild: true, // macOS mac: { From 0028a2614e3f6c7ecf7e4fb0e7aa982244cd7b3a Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Mon, 22 Dec 2025 21:50:30 -0500 Subject: [PATCH 6/7] fix(desktop): add node-addon-api for node-pty rebuild node-pty requires node-addon-api during @electron/rebuild compilation. Adding as explicit dependency ensures bun creates symlink for it. --- apps/desktop/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 0b2f483465c..445c4523663 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -81,6 +81,7 @@ "lowdb": "^7.0.1", "monaco-editor": "^0.55.1", "nanoid": "^5.1.6", + "node-addon-api": "^7.1.0", "node-pty": "1.1.0-beta30", "os-locale": "^6.0.2", "posthog-js": "^1.306.1", From dd4246a30369c6472747417fc7970e25f58ede32 Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Mon, 22 Dec 2025 22:16:58 -0500 Subject: [PATCH 7/7] fix(desktop): use extraResources for drizzle migrations Migrations must be outside the asar archive for drizzle-orm to read them. Using extraResources places them at process.resourcesPath/resources/migrations/ --- apps/desktop/electron-builder.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/desktop/electron-builder.ts b/apps/desktop/electron-builder.ts index 159008887d0..a58f1210b4c 100644 --- a/apps/desktop/electron-builder.ts +++ b/apps/desktop/electron-builder.ts @@ -42,6 +42,16 @@ const config: Configuration = { "**/resources/sounds/**/*", ], + // Extra resources placed outside asar archive (accessible via process.resourcesPath) + extraResources: [ + // Database migrations - must be outside asar for drizzle-orm to read + { + from: "dist/resources/migrations", + to: "resources/migrations", + filter: ["**/*"], + }, + ], + files: [ "dist/**/*", "package.json", @@ -50,12 +60,6 @@ const config: Configuration = { to: "resources", filter: ["**/*"], }, - // Database migrations from local-db package (copied to dist/resources/migrations by vite) - { - from: "dist/resources/migrations", - to: "resources/migrations", - filter: ["**/*"], - }, // Native modules that can't be bundled by Vite. // bun creates symlinks for direct deps in workspace node_modules. // The copy:native-modules script replaces symlinks with real files