import { defineConfig } from 'electron-vite' import { sentryVitePlugin } from '@sentry/vite-plugin' import react from '@vitejs/plugin-react' import { readFileSync } from 'fs' import { resolve } from 'path' import type { Plugin } from 'vite' // Read all production dependencies from package.json // so they get bundled into the main process output. // This avoids pnpm symlink issues with electron-builder's asar packaging. const pkg = JSON.parse(readFileSync(resolve(__dirname, 'package.json'), 'utf-8')) const prodDeps = Object.keys(pkg.dependencies || {}) // Fastify and its plugins rely on runtime module resolution that breaks when bundled. const runtimeExternalDeps = new Set([ 'node-pty', 'agent-teams-controller', 'fastify', '@fastify/cors', '@fastify/static', ]) // node-pty is a native addon that cannot be bundled by Rollup. // It must remain external and be loaded at runtime via require(). const bundledDeps = prodDeps.filter(d => !runtimeExternalDeps.has(d)) // Rollup plugin: stub out native .node addon imports with empty modules. // ssh2 and cpu-features use optional native bindings that can't be bundled, // but they have pure JS fallbacks when the native module isn't available. function nativeModuleStub(): Plugin { const STUB_ID = '\0native-stub' const NODE_MODULE_RE = /\.node(?:\?.*)?$/ return { name: 'native-module-stub', enforce: 'pre', resolveId(source) { if (NODE_MODULE_RE.test(source)) return `${STUB_ID}:${source}` return null }, load(id) { if (id.startsWith(STUB_ID) || NODE_MODULE_RE.test(id)) return 'export default {}' return null } } } const sentrySourceMapTargets = { main: { assets: ['./dist-electron/main/**/*.{js,cjs,mjs,map}'], filesToDeleteAfterUpload: ['./dist-electron/main/**/*.map'], }, renderer: { assets: ['./out/renderer/**/*.{js,cjs,mjs,map}'], filesToDeleteAfterUpload: ['./out/renderer/**/*.map'], }, } as const const sourceMapSetting = process.env.AGENT_TEAMS_DISABLE_SOURCEMAPS === '1' ? false : 'hidden' // Sentry source map upload - only active in CI when SENTRY_AUTH_TOKEN is set. function createSentryPlugins(target: keyof typeof sentrySourceMapTargets): Plugin[] { if (!process.env.SENTRY_AUTH_TOKEN) return [] return [ sentryVitePlugin({ org: process.env.SENTRY_ORG ?? 'quant-jump-pro', project: process.env.SENTRY_PROJECT ?? 'electron', authToken: process.env.SENTRY_AUTH_TOKEN, telemetry: false, release: { name: `agent-teams-ai@${pkg.version}` }, sourcemaps: sentrySourceMapTargets[target], }) as Plugin, ] } export default defineConfig({ main: { plugins: [ nativeModuleStub(), ...createSentryPlugins('main'), ], define: { __APP_VERSION__: JSON.stringify(pkg.version), // Inject DSN at compile time - process.env.SENTRY_DSN is NOT available // at runtime in packaged Electron apps (only during CI build). 'process.env.SENTRY_DSN': JSON.stringify(process.env.SENTRY_DSN ?? ''), }, resolve: { alias: { '@features': resolve(__dirname, 'src/features'), '@main': resolve(__dirname, 'src/main'), '@shared': resolve(__dirname, 'src/shared'), '@preload': resolve(__dirname, 'src/preload') } }, build: { externalizeDeps: { exclude: bundledDeps }, commonjsOptions: { strictRequires: [/node_modules\/.*ssh2\//], }, sourcemap: sourceMapSetting, outDir: 'dist-electron/main', rollupOptions: { input: { index: resolve(__dirname, 'src/main/index.ts'), 'team-fs-worker': resolve(__dirname, 'src/main/workers/team-fs-worker.ts'), 'task-change-worker': resolve(__dirname, 'src/main/workers/task-change-worker.ts'), 'team-data-worker': resolve(__dirname, 'src/main/workers/team-data-worker.ts') }, output: { // CJS format so bundled deps can use __dirname/require. // Use .cjs extension since package.json has "type": "module". format: 'cjs', entryFileNames: '[name].cjs', // Set UV_THREADPOOL_SIZE before any module code runs. // Must be in the banner because ESM→CJS hoists imports above top-level code. // On Windows, fs.watch({recursive:true}) occupies a UV pool thread per watcher; // with 3+ watchers + concurrent fs/DNS/spawn, the default 4 threads deadlock. banner: `if(!process.env.UV_THREADPOOL_SIZE){process.env.UV_THREADPOOL_SIZE='24'}` } } } }, preload: { resolve: { alias: { '@features': resolve(__dirname, 'src/features'), '@preload': resolve(__dirname, 'src/preload'), '@shared': resolve(__dirname, 'src/shared'), '@main': resolve(__dirname, 'src/main') } }, build: { outDir: 'dist-electron/preload', rollupOptions: { input: { index: resolve(__dirname, 'src/preload/index.ts') }, output: { format: 'cjs', entryFileNames: '[name].js' } } } }, renderer: { cacheDir: resolve(__dirname, 'node_modules/.vite/electron-renderer'), optimizeDeps: { include: ['@codemirror/language-data'], exclude: ['@claude-teams/agent-graph'] }, define: { __APP_VERSION__: JSON.stringify(pkg.version), // Pass SENTRY_DSN to renderer as VITE_SENTRY_DSN (Vite replaces at compile time) 'import.meta.env.VITE_SENTRY_DSN': JSON.stringify(process.env.SENTRY_DSN ?? ''), }, resolve: { alias: { '@features': resolve(__dirname, 'src/features'), '@renderer': resolve(__dirname, 'src/renderer'), '@shared': resolve(__dirname, 'src/shared'), '@main': resolve(__dirname, 'src/main'), '@radix-ui/react-compose-refs': resolve( __dirname, 'src/renderer/vendor/radixComposeRefs.ts' ), '@claude-teams/agent-graph': resolve(__dirname, 'packages/agent-graph/src/index.ts') } }, plugins: [react(), ...createSentryPlugins('renderer')], build: { sourcemap: sourceMapSetting, rollupOptions: { input: { index: resolve(__dirname, 'src/renderer/index.html') } } } } })