diff --git a/electron/main/ui/window.ts b/electron/main/ui/window.ts index 7d5d1af..550c56c 100644 --- a/electron/main/ui/window.ts +++ b/electron/main/ui/window.ts @@ -9,6 +9,9 @@ export function createWindow(rendererURL: string) { const bounds = store.get('bounds'); console.log('restored bounds:', bounds); + // preload path + const preloadPath = path.join(isDev ? process.cwd() : app.getAppPath(), 'build', 'electron', 'preload', 'index.cjs'); + const win = new BrowserWindow({ ...{ width: 1200, @@ -18,7 +21,7 @@ export function createWindow(rendererURL: string) { vibrancy: 'under-window', visualEffectState: 'active', webPreferences: { - preload: path.join(app.getAppPath(), 'build', 'electron', 'preload', 'index.cjs'), + preload: preloadPath, }, }); diff --git a/package.json b/package.json index a8c90b5..9352fae 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ "preview": "pnpm run build && pnpm run start", "prepare": "husky", "clean": "node scripts/clean.js", + "electron:dev": "node scripts/electron-dev.mjs", + "electron:dev:inspect": "NODE_ENV=development electron --inspect=9229 build/electron/main/index.mjs", "electron:build:deps": "concurrently \"pnpm electron:build:main\" \"pnpm electron:build:preload\" --kill-others-on-fail", "electron:build:main": "vite build --config ./electron/main/vite.config.ts", "electron:build:preload": "vite build --config ./electron/preload/vite.config.ts", diff --git a/scripts/electron-dev.mjs b/scripts/electron-dev.mjs new file mode 100644 index 0000000..f899455 --- /dev/null +++ b/scripts/electron-dev.mjs @@ -0,0 +1,181 @@ +#!/usr/bin/env node + +/** + * Electron Development Script + * + * This script provides hot-reload development mode for Electron applications. + * It automatically builds Electron dependencies, starts the Remix development server, + * and launches the Electron application with hot-reload capabilities. + */ + +import { spawn, exec } from 'node:child_process'; +import path from 'node:path'; +import fs from 'node:fs'; +import { fileURLToPath } from 'node:url'; + +// Get current file directory +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Constants +const REMIX_PORT = 5173; +const CHECK_INTERVAL = 1000; +const MAX_RETRIES = 30; + +// Set environment variables +process.env.NODE_ENV = 'development'; + +console.log('šŸš€ Starting Electron hot-reload development mode...'); +console.log('šŸ”§ Environment:', process.env.NODE_ENV); + +let electronProcess = null; +let remixProcess = null; + +/** + * Cleanup function to gracefully shutdown all processes + */ +function cleanup() { + console.log('\nšŸ›‘ Shutting down all processes...'); + + if (electronProcess) { + electronProcess.kill('SIGTERM'); + } + + if (remixProcess) { + remixProcess.kill('SIGTERM'); + } + + process.exit(0); +} + +// Handle process exit signals +process.on('SIGINT', cleanup); +process.on('SIGTERM', cleanup); + +/** + * Wait for a server to start on the specified port + * @param {number} port - Port number to check + * @param {string} serverName - Name of the server for logging + * @returns {Promise} + */ +async function waitForServer(port, serverName) { + return new Promise((resolve, reject) => { + let retries = 0; + + const checkServer = () => { + exec(`lsof -i :${port}`, (error, stdout) => { + if (stdout) { + console.log(`āœ… ${serverName} started`); + resolve(); + } else if (retries >= MAX_RETRIES) { + reject(new Error(`Timeout waiting for ${serverName} to start`)); + } else { + retries++; + setTimeout(checkServer, CHECK_INTERVAL); + } + }); + }; + + checkServer(); + }); +} + +/** + * Build Electron dependencies + * @returns {Promise} + */ +async function buildElectronDeps() { + return new Promise((resolve, reject) => { + const buildProcess = spawn('pnpm', ['electron:build:deps'], { + stdio: 'inherit', + env: { ...process.env }, + }); + + buildProcess.on('close', (code) => { + if (code === 0) { + console.log('āœ… Electron dependencies built successfully'); + resolve(); + } else { + reject(new Error(`Build failed with exit code: ${code}`)); + } + }); + + buildProcess.on('error', (error) => { + reject(new Error(`Build process error: ${error.message}`)); + }); + }); +} + +/** + * Main function to start Electron development mode + * @returns {Promise} + */ +async function startElectronDev() { + try { + // 1. Build Electron dependencies first + console.log('šŸ“¦ Building Electron dependencies...'); + await buildElectronDeps(); + + // 2. Start Remix development server + console.log('🌐 Starting Remix development server...'); + remixProcess = spawn('pnpm', ['dev'], { + stdio: 'pipe', + env: { ...process.env }, + }); + + remixProcess.stdout.on('data', (data) => { + const output = data.toString(); + console.log(`[Remix] ${output.trim()}`); + }); + + remixProcess.stderr.on('data', (data) => { + console.error(`[Remix Error] ${data.toString().trim()}`); + }); + + // Wait for Remix server to start + await waitForServer(REMIX_PORT, 'Remix development server'); + + // 3. Start Electron application + console.log('⚔ Starting Electron application...'); + + const electronPath = path.join(__dirname, '..', 'node_modules', '.bin', 'electron'); + const mainPath = path.join(__dirname, '..', 'build', 'electron', 'main', 'index.mjs'); + + // Check if main process file exists + if (!fs.existsSync(mainPath)) { + throw new Error(`Main process file not found: ${mainPath}`); + } + + electronProcess = spawn(electronPath, [mainPath], { + stdio: 'inherit', + env: { + ...process.env, + NODE_ENV: 'development', + ELECTRON_IS_DEV: '1', + }, + }); + + electronProcess.on('error', (error) => { + console.error('āŒ Failed to start Electron:', error); + cleanup(); + }); + + electronProcess.on('exit', (code) => { + console.log(`šŸ“± Electron process exited with code: ${code}`); + + if (code !== 0) { + cleanup(); + } + }); + + console.log('šŸŽ‰ Electron hot-reload development mode started!'); + console.log('šŸ’” Code changes will automatically reload'); + console.log('šŸ›‘ Press Ctrl+C to exit'); + } catch (error) { + console.error('āŒ Startup failed:', error.message); + cleanup(); + } +} + +// Start development mode +startElectronDev();