import { copyFileSync } from 'node:fs' import { build } from 'esbuild' const watch = process.argv.includes('--watch') // Optional build-time default server origin (pre-release shares pointed at a // non-prod environment): SIM_DESKTOP_DEFAULT_ORIGIN=https://www.dev.sim.ai. // Baked into the bundle so it applies to fresh installs with no settings — // unlike the SIM_DESKTOP_ORIGIN env var, which only affects terminal-launched // processes. Official builds leave it unset (default https://www.sim.ai). const bakedDefaultOrigin = process.env.SIM_DESKTOP_DEFAULT_ORIGIN ?? '' if ( bakedDefaultOrigin && !/^https:\/\/[^\s/]+$/.test(bakedDefaultOrigin) && !/^http:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(bakedDefaultOrigin) ) { console.error( `SIM_DESKTOP_DEFAULT_ORIGIN must be a bare https origin or http://localhost (got "${bakedDefaultOrigin}")` ) process.exit(1) } if (bakedDefaultOrigin) { console.log(`• Baking default server origin: ${bakedDefaultOrigin}`) } /** Selects the branded app icon that matches the build's baked environment. */ function iconForOrigin(origin: string): string { if (!origin) return 'build/icon.icns' const host = new URL(origin).hostname.toLowerCase() if (host === 'localhost' || host === '127.0.0.1') return 'build/icon-local.icns' if (host === 'dev.sim.ai' || host.endsWith('.dev.sim.ai')) return 'build/icon-dev.icns' if (host === 'staging.sim.ai' || host.endsWith('.staging.sim.ai')) { return 'build/icon-staging.icns' } return 'build/icon.icns' } const appIcon = iconForOrigin(bakedDefaultOrigin) copyFileSync(appIcon, 'build/generated-icon.icns') console.log(`• Selecting desktop icon: ${appIcon}`) const common = { bundle: true, platform: 'node' as const, format: 'cjs' as const, target: 'node22', sourcemap: true, // node-pty resolves a prebuilt .node binary at runtime, so it must stay // external and be loaded from node_modules rather than inlined here. external: ['electron', '@lydell/node-pty'], tsconfig: 'tsconfig.json', logLevel: 'info' as const, define: { 'process.env.SIM_DESKTOP_DEFAULT_ORIGIN': JSON.stringify(bakedDefaultOrigin), }, } async function run(): Promise { if (watch) { const { context } = await import('esbuild') const mainCtx = await context({ ...common, entryPoints: ['src/main/index.ts'], outfile: 'dist/main.cjs', }) const preloadCtx = await context({ ...common, entryPoints: ['src/preload/index.ts'], outfile: 'dist/preload.cjs', }) // Separate from the main-window preload: this one is injected into // untrusted pages in the built-in browser and must stay minimal. const browserPreloadCtx = await context({ ...common, entryPoints: ['src/preload/browser/index.ts'], outfile: 'dist/browser-preload.cjs', }) await Promise.all([mainCtx.watch(), preloadCtx.watch(), browserPreloadCtx.watch()]) return } await Promise.all([ build({ ...common, entryPoints: ['src/main/index.ts'], outfile: 'dist/main.cjs' }), build({ ...common, entryPoints: ['src/preload/index.ts'], outfile: 'dist/preload.cjs' }), build({ ...common, entryPoints: ['src/preload/browser/index.ts'], outfile: 'dist/browser-preload.cjs', }), ]) } run().catch((error) => { console.error(error) process.exit(1) })