From ae74f8ea837d31f6d0a7ba644b1453098dc25e69 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 2 Sep 2025 10:45:42 +0300 Subject: [PATCH] feat(dx/desktop): isolate node_modules dependency --- apps/desktop/.npmrc | 1 - apps/desktop/package.json | 5 +-- apps/desktop/scripts/rebuild.mts | 57 ++++++++++++++++++++++++++++++++ apps/desktop/scripts/start.mts | 22 ++++++++++++ pnpm-workspace.yaml | 3 +- scripts/utils.mts | 20 +++++++++++ 6 files changed, 103 insertions(+), 5 deletions(-) delete mode 100644 apps/desktop/.npmrc create mode 100644 apps/desktop/scripts/rebuild.mts create mode 100644 apps/desktop/scripts/start.mts create mode 100644 scripts/utils.mts diff --git a/apps/desktop/.npmrc b/apps/desktop/.npmrc deleted file mode 100644 index 1d6a0d53c..000000000 --- a/apps/desktop/.npmrc +++ /dev/null @@ -1 +0,0 @@ -node-linker = hoisted \ No newline at end of file diff --git a/apps/desktop/package.json b/apps/desktop/package.json index f59c4f0b6..1eb5233ad 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -32,9 +32,10 @@ "forge": "./electron-forge/forge.config.ts" }, "scripts": { - "dev": "cross-env NODE_OPTIONS=\"--import tsx\" NODE_ENV=development TRILIUM_ENV=dev TRILIUM_DATA_DIR=data TRILIUM_RESOURCE_DIR=../server/src electron .", + "dev": "tsx scripts/start.mts", "build": "tsx scripts/build.ts", - "start-prod": "pnpm build && cross-env TRILIUM_DATA_DIR=data TRILIUM_PORT=37841 electron dist" + "start-prod": "pnpm build && cross-env TRILIUM_DATA_DIR=data TRILIUM_PORT=37841 electron dist", + "postinstall": "tsx scripts/rebuild.mts" }, "license": "AGPL-3.0-only", "author": { diff --git a/apps/desktop/scripts/rebuild.mts b/apps/desktop/scripts/rebuild.mts new file mode 100644 index 000000000..8f05c9db5 --- /dev/null +++ b/apps/desktop/scripts/rebuild.mts @@ -0,0 +1,57 @@ +import { join } from "path"; +import { cpSync, existsSync, mkdirSync, readFileSync, rmSync } from "fs"; +import { execSync } from "child_process"; +import { rebuild } from "@electron/rebuild" +import { isNixOS, resetPath } from "../../../scripts/utils.mjs"; + +const desktopProjectRoot = join(import.meta.dirname, ".."); +const workspaceRoot = join(desktopProjectRoot, "../.."); + +function copyNativeDependencies() { + const destPath = join(desktopProjectRoot, "node_modules/better-sqlite3"); + + if (existsSync(destPath)) { + rmSync(destPath, { recursive: true }); + } + mkdirSync(destPath); + cpSync(join(workspaceRoot, "node_modules/better-sqlite3"), destPath, { recursive: true, dereference: true }); +} + +function rebuildNativeDependencies() { + const electronVersion = determineElectronVersion(); + + if (!electronVersion) { + console.error("Unable to determine Electron version."); + process.exit(1); + } + + console.log(`Rebuilding ${desktopProjectRoot} with ${electronVersion}...`); + + rebuild({ + projectRootPath: desktopProjectRoot, + buildPath: desktopProjectRoot, + // on NixOS the prebuilt native fails with "Error: libstdc++.so.6: cannot open shared object file: No such file or directory" so we need to build from source. + force: isNixOS(), + electronVersion: electronVersion, + buildFromSource: true + }); +} + +function determineElectronVersion() { + if (isNixOS()) { + console.log("Detected NixOS, reading Electron version from PATH"); + resetPath(); + + try { + return execSync("electron --version", { }).toString("utf-8"); + } catch (e) { + console.error("Got error while trying to read the Electron version from shell. Make sure that an Electron version is in the PATH (e.g. `nix-shell -p electron`)"); + process.exit(1); + } + } else { + console.log("Using Electron version from package.json"); + } +} + +copyNativeDependencies(); +rebuildNativeDependencies(); diff --git a/apps/desktop/scripts/start.mts b/apps/desktop/scripts/start.mts new file mode 100644 index 000000000..192f36f4c --- /dev/null +++ b/apps/desktop/scripts/start.mts @@ -0,0 +1,22 @@ +import { execSync, spawnSync } from "child_process"; +import { isNixOS, resetPath } from "../../../scripts/utils.mjs"; +import { join } from "path"; + +const projectRoot = join(import.meta.dirname, ".."); + +if (isNixOS()) { + resetPath(); +} + +execSync("electron ./src/main.ts", { + stdio: "inherit", + cwd: projectRoot, + env: { + ...process.env, + NODE_OPTIONS: "--import tsx", + NODE_ENV: "development", + TRILIUM_ENV: "dev", + TRILIUM_DATA_DIR: "data", + TRILIUM_RESOURCE_DIR: "../server/src" + } +}); diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 2c068ac8f..cf29be32e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,5 +2,4 @@ packages: - packages/* - apps/* -shamefullyHoist: true -nodeLinker: isolated +nodeLinker: hoisted diff --git a/scripts/utils.mts b/scripts/utils.mts new file mode 100644 index 000000000..d8cefe4c5 --- /dev/null +++ b/scripts/utils.mts @@ -0,0 +1,20 @@ +import { readFileSync } from "fs"; +import { platform } from "os"; + +export function isNixOS() { + if (platform() !== "linux") return false; + const osReleaseFile = readFileSync("/etc/os-release", "utf-8"); + return osReleaseFile.includes("ID=nixos"); +} + +export function resetPath() { + // On Unix-like systems, PATH is usually inherited from login shell + // but npm prepends node_modules/.bin. Let's remove it: + const origPath = process.env.PATH || ""; + + // npm usually adds something like ".../node_modules/.bin" + process.env.PATH = origPath + .split(":") + .filter(p => !p.includes("node_modules/.bin")) + .join(":"); +}