import type { ForgeConfig } from '@electron-forge/shared-types'; import { MakerSquirrel } from '@electron-forge/maker-squirrel'; import { MakerZIP } from '@electron-forge/maker-zip'; import { MakerDeb } from '@electron-forge/maker-deb'; import { MakerRpm } from '@electron-forge/maker-rpm'; import { AutoUnpackNativesPlugin } from '@electron-forge/plugin-auto-unpack-natives'; import { VitePlugin } from '@electron-forge/plugin-vite'; import { FusesPlugin } from '@electron-forge/plugin-fuses'; import { FuseV1Options, FuseVersion } from '@electron/fuses'; import path from 'node:path'; import fs from 'node:fs'; import { execSync } from 'node:child_process'; // Packages externalized in vite.main.config.mts that must be installed at runtime. // Keep this list in sync with the Vite external array. const externalPackages = [ 'better-sqlite3', 'keytar', '@github/copilot-sdk', '@langchain/core', '@langchain/langgraph', '@langchain/openai', '@langchain/anthropic', 'vectordb', 'electron-squirrel-startup', 'electron-store', ]; const config: ForgeConfig = { packagerConfig: { asar: { unpack: '**/{*.node,*.dll,*.so,*.dylib}', }, name: 'adiuva', }, rebuildConfig: {}, hooks: { packageAfterCopy: async (_forgeConfig, buildPath, _electronVersion, platform, arch) => { // The VitePlugin's ignore filter only copies .vite/ into the build. // Externalized packages need to be installed into node_modules here. // At this point, only .vite/ exists. The VitePlugin writes package.json // in its own afterCopy hook (which may run after ours). Read from source. const srcPjPath = path.resolve(__dirname, 'package.json'); const pjPath = path.resolve(buildPath, 'package.json'); const pj = JSON.parse(fs.readFileSync(srcPjPath, 'utf-8')); // Keep only externalized packages in dependencies const filtered: Record = {}; for (const pkg of externalPackages) { if (pj.dependencies?.[pkg]) { filtered[pkg] = pj.dependencies[pkg]; } } pj.dependencies = filtered; delete pj.devDependencies; fs.writeFileSync(pjPath, JSON.stringify(pj, null, 2)); // Copy lockfile for reproducible installs const lockSrc = path.resolve(buildPath, '..', '..', 'package-lock.json'); if (fs.existsSync(lockSrc)) { fs.copyFileSync(lockSrc, path.resolve(buildPath, 'package-lock.json')); } // Install only the externalized runtime deps console.log('[forge] Installing externalized dependencies...'); execSync('npm install --omit=dev', { cwd: buildPath, stdio: 'inherit', env: { ...process.env, npm_config_nodedir: '' }, }); // vectordb uses platform-specific optional deps (@lancedb/vectordb---*). // npm install on Linux only pulls the Linux variant. Force-install the target's. const platformNativePackages: Record> = { 'win32-x64': { '@lancedb/vectordb-win32-x64-msvc': '', }, 'linux-x64': { '@lancedb/vectordb-linux-x64-gnu': '', }, 'darwin-x64': { '@lancedb/vectordb-darwin-x64': '', }, 'darwin-arm64': { '@lancedb/vectordb-darwin-arm64': '', }, }; const targetKey = `${platform}-${arch}`; const nativePkgs = platformNativePackages[targetKey]; if (nativePkgs) { // Remove wrong-platform lancedb native packages const nmPath = path.join(buildPath, 'node_modules', '@lancedb'); if (fs.existsSync(nmPath)) { for (const entry of fs.readdirSync(nmPath)) { if (entry.startsWith('vectordb-') && !Object.keys(nativePkgs).includes(`@lancedb/${entry}`)) { fs.rmSync(path.join(nmPath, entry), { recursive: true, force: true }); console.log(`[forge] Removed non-target native package: @lancedb/${entry}`); } } } // Install correct platform packages const pkgsToInstall = Object.keys(nativePkgs).join(' '); console.log(`[forge] Installing platform-specific packages for ${targetKey}: ${pkgsToInstall}`); execSync(`npm install ${pkgsToInstall} --omit=dev --no-save`, { cwd: buildPath, stdio: 'inherit', }); } // Remove cross-platform prebuilt binaries that don't match the target. // Packages like @github/copilot ship prebuilds for all platforms; // keeping foreign-arch .node files breaks rpmbuild's strip step. const targetDir = `${platform}-${arch}`; const nmPath = path.join(buildPath, 'node_modules'); const findPrebuilds = (dir: string): string[] => { const results: string[] = []; if (!fs.existsSync(dir)) return results; for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const full = path.join(dir, entry.name); if (entry.isDirectory()) { if (entry.name === 'prebuilds') { results.push(full); } else { results.push(...findPrebuilds(full)); } } } return results; }; for (const prebuildsDir of findPrebuilds(nmPath)) { for (const entry of fs.readdirSync(prebuildsDir)) { if (entry !== targetDir) { const fullPath = path.join(prebuildsDir, entry); fs.rmSync(fullPath, { recursive: true, force: true }); console.log(`[forge] Removed non-target prebuild: ${entry}`); } } } }, }, makers: [ new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({}), ], plugins: [ new AutoUnpackNativesPlugin({}), new VitePlugin({ // `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc. // If you are familiar with Vite configuration, it will look really familiar. build: [ { // `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`. entry: 'src/main/index.ts', config: 'vite.main.config.mts', target: 'main', }, { entry: 'src/preload/index.ts', config: 'vite.preload.config.mts', target: 'preload', }, ], renderer: [ { name: 'main_window', config: 'vite.renderer.config.mts', }, ], }), // Fuses are used to enable/disable various Electron functionality // at package time, before code signing the application new FusesPlugin({ version: FuseVersion.V1, [FuseV1Options.RunAsNode]: false, [FuseV1Options.EnableCookieEncryption]: true, [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false, [FuseV1Options.EnableNodeCliInspectArguments]: false, [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true, [FuseV1Options.OnlyLoadAppFromAsar]: true, }), ], }; export default config;