import { execSync } from 'node:child_process'; import { readFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import tailwindcss from '@tailwindcss/vite'; import react from '@vitejs/plugin-react-swc'; import { defineConfig } from 'electron-vite'; import { dependencyInventoryPlugin } from './scripts/electron-dependency-inventory.mjs'; import webPkg from './web/package.json' with { type: 'json' }; const __dirname = dirname(fileURLToPath(import.meta.url)); const ELECTRON_ESM_CJS_SHIM = ` // -- CommonJS Shims -- import __cjs_mod__ from 'node:module'; var __filename = import.meta.filename; var __dirname = import.meta.dirname; var require = __cjs_mod__.createRequire(import.meta.url); `; function tryGitSha(): string { try { return execSync('git rev-parse HEAD', { cwd: __dirname, encoding: 'utf8' }).trim(); } catch { return 'unknown'; } } /** Packaged Electron loads the embedded gateway URL; renderer build is dev/preview-only. */ const skipRenderer = process.env['ELECTRON_VITE_SKIP_RENDERER'] === '1'; export default defineConfig({ main: { plugins: [dependencyInventoryPlugin(__dirname), { name: 'xopc:explicit-esm-shim', configResolved(config) { // The banner supplies the shim without scanning bundled strings as imports. config.plugins = config.plugins.filter(plugin => plugin.name !== 'vite:esm-shim'); }, }], build: { // Packaged asar only ships minimal node_modules for the gateway subprocess (see // scripts/electron-runtime-externals.mjs). Main-process deps (zod, pino, dotenv, …) must // be bundled — electron-vite defaults to externalizeDeps=true which leaves bare imports. externalizeDeps: false, // Keep post-minification; electron-builder also compresses the packaged artifact. minify: 'esbuild', rollupOptions: { // IMPORTANT: In the Electron main process, `electron` is a runtime-provided module. // If Rollup resolves it to the npm package `electron`, the bundle will include // `node_modules/electron/index.js`, which throws at runtime inside packaged apps. // Do not mark `thread-stream` external: pnpm may not hoist it, so `require('thread-stream')` // from `out/main/chunks/*` fails. Bundled `thread-stream` + `thread-stream-bundle-shim.ts` // fixes pino transport worker path (see electron/thread-stream-bundle-shim.ts). // `@vscode/ripgrep` resolves a platform optionalDep at import time; packaged apps use extraResources `bin/rg`. // `node-pty` must resolve from its real package directory so it can load native binaries and spawn-helper. external: ['electron', '@vscode/ripgrep', 'node-pty', '@trycua/cua-driver'], input: { index: resolve(__dirname, 'electron/index.ts'), // Keep main's import.meta.dirname at out/main for preload and native helper paths. main: resolve(__dirname, 'electron/main.ts'), compatibility: resolve(__dirname, 'electron/gateway-compatibility.ts'), }, // electron-vite 6 beta locates the last ESM import with a regex that can overrun into // bundled source. Providing the shim explicitly bypasses that unsafe insertion path. output: { banner: ELECTRON_ESM_CJS_SHIM }, }, }, resolve: { alias: { '@xopcai/xopc': resolve(__dirname, 'src'), }, }, }, preload: { plugins: [dependencyInventoryPlugin(__dirname), { name: 'computer-preview-document', async generateBundle() { this.emitFile({ type: 'asset', fileName: 'computer-preview.html', source: readFileSync(resolve(__dirname, 'electron/computer/preview-worker.html'), 'utf8') }); } }], build: { minify: 'esbuild', rollupOptions: { // Same as main: keep `electron` as runtime-provided, not the npm package. external: ['electron'], input: { index: resolve(__dirname, 'electron/preload.ts'), 'computer-preview': resolve(__dirname, 'electron/computer/preview-worker.ts'), }, // Root package.json is `"type": "module"`, so electron-vite would default preload to ESM // (`index.mjs`). Electron does not execute that preload as an ES module. Force CJS (`index.cjs`). output: { format: 'cjs', }, }, }, }, ...(skipRenderer ? {} : { renderer: { root: resolve(__dirname, 'web'), base: './', define: { __XOPC_WEB_VERSION__: JSON.stringify(webPkg.version), __XOPC_WEB_COMMIT__: JSON.stringify(process.env['VITE_XOPC_WEB_COMMIT'] ?? tryGitSha()), __XOPC_WEB_BUILD_TIME__: JSON.stringify( process.env['VITE_XOPC_WEB_BUILD_TIME'] ?? new Date().toISOString(), ), }, plugins: [react(), tailwindcss()], resolve: { alias: { '@': resolve(__dirname, 'web/src'), }, }, build: { rollupOptions: { input: resolve(__dirname, 'web/index.html'), }, outDir: resolve(__dirname, 'out/renderer'), emptyOutDir: true, }, server: { port: 5173, // Same-origin API calls use `window.location.origin` (this dev server). Mirror `web/vite.config.ts` // so `/api/*` reaches the xopc gateway — required for Electron dev (renderer loads from :5173). proxy: { '/api': { target: 'http://localhost:18790', changeOrigin: true, ws: true, }, '/health': { target: 'http://localhost:18790', changeOrigin: true, }, '/status': { target: 'http://localhost:18790', changeOrigin: true, }, '/favicon.ico': { target: 'http://localhost:18790', changeOrigin: true, }, }, }, }, }), });