From 28ba9bb61d4f9b2754b135480e1de1966f662f07 Mon Sep 17 00:00:00 2001 From: Sparsh Date: Mon, 11 May 2026 12:50:03 +0530 Subject: [PATCH] feat(runtime): bundle node.exe for Windows to prevent access violations with native modules --- gitnexus-desktop/electron-builder.yml | 6 ++++++ gitnexus-desktop/scripts/package.mjs | 6 ++++++ gitnexus-desktop/src/main/main.ts | 24 ++++++++++++++++++++++-- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/gitnexus-desktop/electron-builder.yml b/gitnexus-desktop/electron-builder.yml index 61fa41057..29029f3a8 100644 --- a/gitnexus-desktop/electron-builder.yml +++ b/gitnexus-desktop/electron-builder.yml @@ -32,6 +32,12 @@ extraResources: - '**/*' - from: '${env.GITNEXUS_DESKTOP_GITNEXUS_PACKAGE_JSON}' to: gitnexus/package.json + - from: '${env.GITNEXUS_DESKTOP_NODE_BINARY}' + to: runtime/node.exe + filter: + - '**/*' + platform: + - win asar: false npmRebuild: false win: diff --git a/gitnexus-desktop/scripts/package.mjs b/gitnexus-desktop/scripts/package.mjs index b261a73fe..6205511e2 100644 --- a/gitnexus-desktop/scripts/package.mjs +++ b/gitnexus-desktop/scripts/package.mjs @@ -155,6 +155,12 @@ const builderEnvironment = { GITNEXUS_DESKTOP_GITNEXUS_SKILLS: toBuilderRelativePath(path.join(gitnexusRoot, 'skills')), GITNEXUS_DESKTOP_GITNEXUS_VENDOR: toBuilderRelativePath(path.join(gitnexusRoot, 'vendor')), GITNEXUS_DESKTOP_WEB_DIST: toBuilderRelativePath(path.join(gitnexusWebRoot, 'dist')), + // On Windows, lbugjs.node PE-imports node.exe by name, which fails under Electron's binary. + // electron-builder copies this into resources/runtime/node.exe so it lands in both + // win-unpacked (smoke test) and the NSIS installer (end-user install). + // The extraResources entry in electron-builder.yml is platform: [win] so this value is + // ignored on macOS/Linux (empty string prevents electron-builder from touching it there). + GITNEXUS_DESKTOP_NODE_BINARY: process.platform === 'win32' ? process.execPath : '', }; const builderCliArgs = [ diff --git a/gitnexus-desktop/src/main/main.ts b/gitnexus-desktop/src/main/main.ts index 6d4d8714f..e0720aad5 100644 --- a/gitnexus-desktop/src/main/main.ts +++ b/gitnexus-desktop/src/main/main.ts @@ -38,6 +38,10 @@ const GITNEXUS_WEB_DEV_ROOT = path.resolve(__dirname, '../../gitnexus-web'); const GITNEXUS_WEB_DEV_ROOT_FALLBACK = path.resolve(__dirname, '../../../gitnexus-web'); const GITNEXUS_PACKAGED_RUNTIME_DIR = path.join(process.resourcesPath, 'gitnexus'); const GITNEXUS_WEB_PACKAGED_DIR = path.join(process.resourcesPath, 'gitnexus-web'); +// On Windows, native modules (e.g. lbugjs.node) PE-import node.exe by name. +// Electron's binary is not node.exe, so LoadLibrary fails with an access violation. +// Bundling a real node.exe and using it as the subprocess host avoids the crash. +const GITNEXUS_PACKAGED_NODE_BINARY = path.join(process.resourcesPath, 'runtime', 'node.exe'); const GITNEXUS_WEB_EXPECTED_MARKERS = ['GitNexus', '
']; const GITNEXUS_SERVER_READY_TIMEOUT_MS = 30_000; const GITNEXUS_WEB_READY_TIMEOUT_MS = 60_000; @@ -240,6 +244,10 @@ const getGitNexusWebViteCliEntry = (): string => { const getNodeCommand = (): string => { if (app.isPackaged) { + if (process.platform === 'win32' && existsSync(GITNEXUS_PACKAGED_NODE_BINARY)) { + return GITNEXUS_PACKAGED_NODE_BINARY; + } + return process.execPath; } @@ -257,8 +265,14 @@ const getNodeProcessEnvironment = (overrides: NodeJS.ProcessEnv = {}): NodeJS.Pr }; if (app.isPackaged) { - // Packaged mode reuses Electron's embedded Node runtime via process.execPath. - environment.ELECTRON_RUN_AS_NODE = '1'; + // Only set ELECTRON_RUN_AS_NODE when falling back to the Electron binary as the node host. + // When using the bundled node.exe, the host is already a real node process — no flag needed. + const usingBundledNode = + process.platform === 'win32' && existsSync(GITNEXUS_PACKAGED_NODE_BINARY); + + if (!usingBundledNode) { + environment.ELECTRON_RUN_AS_NODE = '1'; + } } return environment; @@ -657,6 +671,12 @@ const registerWindowIpcHandlers = (): void => { const showStartupError = (error: unknown): void => { console.error('[gitnexus-desktop] Startup failed.', error); + // In CI smoke tests, dialog.showErrorBox() blocks synchronously forever (no display). + // Log the error above and let process.exit() handle the failure signal instead. + if (IS_DESKTOP_SMOKE_TEST) { + return; + } + if (isAddressInUseError(error)) { dialog.showErrorBox( 'GitNexus Desktop Startup Failed',