import { context } from 'esbuild'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { spawn } from 'node:child_process'; import http from 'node:http'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const root = path.resolve(__dirname, '..'); const electronEnv = { ...process.env, NODE_ENV: 'development', DND_SKIP_LICENSE: process.env.DND_SKIP_LICENSE ?? '1', VITE_DEV_SERVER_URL: 'http://localhost:5173/', }; function spawnShell(command, opts = {}) { const child = spawn(command, { cwd: root, stdio: 'inherit', shell: true, ...opts, }); child.on('exit', (code) => { if (code !== 0 && code !== null) { process.exitCode = code; } }); return child; } /** Убивает дерево процессов (на Windows `child.kill()` часто не гасит Vite на 5173). */ function killTree(child) { if (!child || child.killed) return; if (typeof child.exitCode === 'number' && child.exitCode !== null) return; if (process.platform === 'win32' && child.pid) { spawn('taskkill', ['/PID', String(child.pid), '/T', '/F'], { stdio: 'ignore', windowsHide: true, detached: true, }); } else { try { child.kill('SIGTERM'); } catch { /* ignore */ } } } function waitForVite(url = 'http://localhost:5173/editor.html', timeoutMs = 60000) { const started = Date.now(); return new Promise((resolve, reject) => { const tick = () => { const req = http.get(url, (res) => { res.resume(); resolve(); }); req.on('error', () => { if (Date.now() - started > timeoutMs) { reject(new Error(`Timed out waiting for Vite at ${url}`)); return; } setTimeout(tick, 300); }); req.setTimeout(2000, () => { req.destroy(); }); }; tick(); }); } let shuttingDown = false; let electron = null; let vite = null; let dispose = async () => {}; let restartTimer = null; let electronStarted = false; let restartingElectron = false; async function shutdown() { if (shuttingDown) return; shuttingDown = true; if (restartTimer) clearTimeout(restartTimer); killTree(vite); killTree(electron); await dispose(); process.exit(0); } function startElectron() { if (shuttingDown) return; restartingElectron = false; if (electron) killTree(electron); electron = spawnShell('npx electron .', { env: electronEnv }); electron.once('exit', () => { if (restartingElectron || shuttingDown) return; void shutdown(); }); electronStarted = true; } function scheduleElectronRestart() { if (shuttingDown || !electronStarted) return; if (restartTimer) clearTimeout(restartTimer); restartTimer = setTimeout(() => { restartTimer = null; console.log('[dev] main/preload rebuilt — restarting Electron…'); restartingElectron = true; startElectron(); }, 300); } function createRestartPlugin() { return { name: 'restart-electron-on-rebuild', setup(build) { build.onEnd((result) => { if (result.errors.length === 0) scheduleElectronRestart(); }); }, }; } async function watchMainAndPreload() { const restartPlugin = createRestartPlugin(); const main = await context({ entryPoints: [path.join(root, 'app/main/index.ts')], outfile: path.join(root, 'dist/main/index.cjs'), platform: 'node', target: 'node22', format: 'cjs', bundle: true, sourcemap: true, external: ['electron'], define: { 'process.env.NODE_ENV': JSON.stringify('development') }, plugins: [restartPlugin], }); await main.rebuild(); await main.watch(); const preload = await context({ entryPoints: [path.join(root, 'app/preload/index.ts')], outfile: path.join(root, 'dist/preload/index.cjs'), platform: 'node', target: 'node22', format: 'cjs', bundle: true, sourcemap: true, external: ['electron'], define: { 'process.env.NODE_ENV': JSON.stringify('development') }, plugins: [restartPlugin], }); await preload.rebuild(); await preload.watch(); return async () => { await Promise.all([main.dispose(), preload.dispose()]); }; } dispose = await watchMainAndPreload(); vite = spawnShell('npx vite dev --strictPort', { env: electronEnv, }); try { await waitForVite(); } catch (err) { console.error('[dev] Vite did not start in time:', err); } startElectron(); process.on('SIGINT', () => void shutdown()); process.on('SIGTERM', () => void shutdown()); vite.once('exit', (code) => { if (code !== 0 && code !== null && !shuttingDown) { void shutdown(); } });