#!/usr/bin/env node /** * Gelectron CLI - Run Electron apps with the Servo rendering engine. * * Usage: * gelectron Run an Electron app * gelectron --version Print version * gelectron --help Show help */ 'use strict'; const { spawn } = require('child_process'); const path = require('path'); const fs = require('fs'); const VERSION = require('../package.json').version; const args = process.argv.slice(2); if (args.includes('--version') || args.includes('-v')) { console.log(`gelectron v${VERSION}`); process.exit(0); } if (args.includes('--help') || args.includes('-h') || args.length === 0) { console.log(` gelectron v${VERSION} - Firefox-engine Electron alternative Usage: gelectron Run an Electron app gelectron Run a main process script directly gelectron --version Print version gelectron --help Show this help Examples: gelectron . Run the app in the current directory gelectron ./my-app Run a specific app gelectron main.js Run a main process script directly Environment Variables: GELECTRON_DEV=1 Enable development mode GELECTRON_LOG=1 Enable verbose logging VITE_DEV_SERVER_URL= Connect to a Vite dev server `.trim()); process.exit(0); } const appPath = path.resolve(args[0]); if (!fs.existsSync(appPath)) { console.error(`gelectron: error: path not found: ${appPath}`); process.exit(1); } let mainScript; let isPackaged = false; if (fs.statSync(appPath).isDirectory()) { const pkgPath = path.join(appPath, 'package.json'); if (!fs.existsSync(pkgPath)) { console.error(`gelectron: error: no package.json found in ${appPath}`); process.exit(1); } const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); mainScript = path.join(appPath, pkg.main || 'index.js'); isPackaged = true; } else { mainScript = appPath; } if (!fs.existsSync(mainScript)) { console.error(`gelectron: error: main script not found: ${mainScript}`); process.exit(1); } const nativeDir = path.join(__dirname, '..', 'crates', 'gelectron-core'); const platformSuffix = process.platform === 'win32' ? 'win32' : process.platform === 'darwin' ? 'darwin' : 'linux'; const archSuffix = process.arch === 'arm64' ? 'arm64' : 'x64'; const ext = process.platform === 'win32' ? 'dll' : process.platform === 'darwin' ? 'dylib' : 'so'; const napiPlatform = process.platform === 'win32' ? `${platformSuffix}-${archSuffix}-msvc` : process.platform === 'darwin' ? `${platformSuffix}-${archSuffix}` : `${platformSuffix}-${archSuffix}-gnu`; const candidates = [ path.join(nativeDir, `gelectron_core.${napiPlatform}.node`), path.join(__dirname, '..', `gelectron_core.${napiPlatform}.node`), path.join(__dirname, '..', 'npm', napiPlatform, `gelectron_core.${napiPlatform}.node`), path.join(nativeDir, 'release', `gelectron_core.${ext}`), path.join(nativeDir, 'debug', `gelectron_core.${ext}`), path.join(__dirname, '..', `gelectron_core.${ext}`), path.join(__dirname, '..', 'napi-dist', process.platform, process.arch, `gelectron_core.${ext}`), ]; let nativeAddonPath = null; for (const candidate of candidates) { if (fs.existsSync(candidate)) { nativeAddonPath = candidate; break; } } const env = { ...process.env, GELECTRON_MAIN_SCRIPT: mainScript, GELECTRON_APP_PATH: isPackaged ? appPath : path.dirname(mainScript), GELECTRON_NATIVE_ADDON: nativeAddonPath || '', GELECTRON_VERSION: VERSION, GELECTRON_DEV: process.env.GELECTRON_DEV || '', }; if (process.env.GELECTRON_LOG) { console.log(`[gelectron] main script: ${mainScript}`); console.log(`[gelectron] app path: ${env.GELECTRON_APP_PATH}`); console.log(`[gelectron] native addon: ${nativeAddonPath || 'not found (using build target)'}`); } const rustBin = path.join(__dirname, '..', 'target', 'release', 'gelectron'); const rustBinDebug = path.join(__dirname, '..', 'target', 'debug', 'gelectron'); function findNativeBinary() { const candidates = []; if (process.env.GELECTRON_BINARY) candidates.push(process.env.GELECTRON_BINARY); candidates.push(rustBin, rustBinDebug); // npm-installed layout: the binary ships alongside the CLI candidates.push(path.join(__dirname, '..', 'bin', 'gelectron')); const platform = process.platform; if (platform === 'win32') { for (const pf of [process.env.PROGRAMFILES, process.env['PROGRAMFILES(X86)'], 'C:\\Program Files']) { if (pf) candidates.push(path.join(pf, 'Gelectron', 'gelectron.exe')); } candidates.push(path.join(process.env.LOCALAPPDATA || '', 'gelectron', 'gelectron.exe')); } else { candidates.push('/usr/local/bin/gelectron'); candidates.push('/usr/local/lib/gelectron/gelectron'); candidates.push('/usr/bin/gelectron'); candidates.push('/opt/homebrew/bin/gelectron'); candidates.push(path.join(process.env.HOME || '', '.local', 'bin', 'gelectron')); } const exeSuffix = platform === 'win32' ? '.exe' : ''; for (const c of candidates) { for (const candidate of [c, c + exeSuffix]) { if (candidate && fs.existsSync(candidate)) return candidate; } } // PATH lookup (which / where) try { const { execSync } = require('child_process'); const cmd = platform === 'win32' ? 'where gelectron' : 'which gelectron'; const out = execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).toString(); const first = out.split(/\r?\n/).map((s) => s.trim()).find((s) => s.length > 0); if (first && fs.existsSync(first)) return first; } catch (e) { // Not on PATH } return null; } let executable = findNativeBinary(); if (executable && process.platform !== 'win32') { try { fs.accessSync(executable, fs.constants.X_OK); } catch (e) { fs.chmodSync(executable, 0o755); } } if (executable) { if (process.env.GELECTRON_LOG) console.log(`[gelectron] Using native binary: ${executable}`); const child = spawn(executable, args, { env, stdio: 'inherit', cwd: process.cwd(), }); child.on('exit', (code) => process.exit(code || 0)); } else { console.log(`[gelectron] Native runtime not found. Install it or run: cargo build --release -p gelectron`); console.log(`[gelectron] Falling back to the Node.js compatibility layer (no native window support)...\n`); require('../src/electron/runtime.js').run(mainScript, env); }