import { spawn } from 'child_process' import log from 'electron-log' import fs from 'fs' import os from 'os' import path from 'path' import { app } from 'electron' export function getResourcePath() { return path.join(app.getAppPath(), 'resources') } export function getBackendPath() { if (app.isPackaged) { // after packaging, backend is in extraResources return path.join(process.resourcesPath, 'backend') } else { // development environment return path.join(app.getAppPath(), 'backend') } } export function runInstallScript(scriptPath: string): Promise { return new Promise((resolve, reject) => { const installScriptPath = path.join(getResourcePath(), 'scripts', scriptPath) log.info(`Running script at: ${installScriptPath}`) const nodeProcess = spawn(process.execPath, [installScriptPath], { env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' } }) let stderrOutput = ''; nodeProcess.stdout.on('data', (data) => { log.info(`Script output: ${data}`) }) nodeProcess.stderr.on('data', (data) => { const errorMsg = data.toString(); stderrOutput += errorMsg; log.error(`Script error: ${errorMsg}`) }) nodeProcess.on('close', (code) => { if (code === 0) { log.info('Script completed successfully') resolve(true) } else { log.error(`Script exited with code ${code}`) const errorMessage = stderrOutput.trim() || `Script exited with code ${code}`; reject(new Error(errorMessage)) } }) }) } export async function getBinaryName(name: string): Promise { if (process.platform === 'win32') { return `${name}.exe` } return name } export async function getBinaryPath(name?: string): Promise { const binariesDir = path.join(os.homedir(), '.eigent', 'bin') // Ensure .eigent/bin directory exists if (!fs.existsSync(binariesDir)) { fs.mkdirSync(binariesDir, { recursive: true }) } if (!name) { return binariesDir } const binaryName = await getBinaryName(name) return path.join(binariesDir, binaryName) } export function getCachePath(folder: string): string { const cacheDir = path.join(os.homedir(), '.eigent', 'cache', folder) // Ensure cache directory exists if (!fs.existsSync(cacheDir)) { fs.mkdirSync(cacheDir, { recursive: true }) } return cacheDir } export function getVenvPath(version: string): string { const venvDir = path.join(os.homedir(), '.eigent', 'venvs', `backend-${version}`) // Ensure venvs directory exists (parent of the actual venv) const venvsBaseDir = path.dirname(venvDir) if (!fs.existsSync(venvsBaseDir)) { fs.mkdirSync(venvsBaseDir, { recursive: true }) } return venvDir } export function getVenvsBaseDir(): string { return path.join(os.homedir(), '.eigent', 'venvs') } export async function cleanupOldVenvs(currentVersion: string): Promise { const venvsBaseDir = getVenvsBaseDir() // Check if venvs directory exists if (!fs.existsSync(venvsBaseDir)) { return } try { const entries = fs.readdirSync(venvsBaseDir, { withFileTypes: true }) for (const entry of entries) { if (entry.isDirectory() && entry.name.startsWith('backend-')) { const versionMatch = entry.name.match(/^backend-(.+)$/) if (versionMatch && versionMatch[1] !== currentVersion) { const oldVenvPath = path.join(venvsBaseDir, entry.name) console.log(`Cleaning up old venv: ${oldVenvPath}`) try { // Remove old venv directory recursively fs.rmSync(oldVenvPath, { recursive: true, force: true }) console.log(`Successfully removed old venv: ${entry.name}`) } catch (err) { console.error(`Failed to remove old venv ${entry.name}:`, err) } } } } } catch (err) { console.error('Error during venv cleanup:', err) } } export async function isBinaryExists(name: string): Promise { const cmd = await getBinaryPath(name) return await fs.existsSync(cmd) } /** * Get unified UV environment variables for consistent Python environment management. * This ensures both installation and runtime use the same paths. * @param version - The app version for venv path * @returns Environment variables for UV commands */ export function getUvEnv(version: string): Record { return { UV_PYTHON_INSTALL_DIR: getCachePath('uv_python'), UV_TOOL_DIR: getCachePath('uv_tool'), UV_PROJECT_ENVIRONMENT: getVenvPath(version), UV_HTTP_TIMEOUT: '180', } } export async function killProcessByName(name: string): Promise { const platform = process.platform try { if (platform === 'win32') { await new Promise((resolve, reject) => { // /F = force, /IM = image name const cmd = spawn('taskkill', ['/F', '/IM', `${name}.exe`]) cmd.on('close', (code) => { // code 0 = success, code 128 = process not found (which is fine) if (code === 0 || code === 128) resolve() else reject(new Error(`taskkill exited with code ${code}`)) }) cmd.on('error', reject) }) } else { await new Promise((resolve, reject) => { const cmd = spawn('pkill', ['-9', name]) cmd.on('close', (code) => { // code 0 = success, code 1 = no process found (which is fine) if (code === 0 || code === 1) resolve() else reject(new Error(`pkill exited with code ${code}`)) }) cmd.on('error', reject) }) } } catch (err) { // Ignore errors, just best effort log.warn(`Failed to kill process ${name}:`, err) } }