chroe: prebuilt

This commit is contained in:
4pmtong 2026-01-17 16:52:49 +08:00
parent 48dbae3f1a
commit ed9d0bfdb6
7 changed files with 1440 additions and 354 deletions

View file

@ -1,146 +1,227 @@
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'
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')
return path.join(app.getAppPath(), 'resources');
}
export function getBackendPath() {
if (app.isPackaged) {
// after packaging, backend is in extraResources
return path.join(process.resourcesPath, 'backend')
return path.join(process.resourcesPath, 'backend');
} else {
// development environment
return path.join(app.getAppPath(), 'backend')
return path.join(app.getAppPath(), 'backend');
}
}
export function runInstallScript(scriptPath: string): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
const installScriptPath = path.join(getResourcePath(), 'scripts', scriptPath)
log.info(`Running script at: ${installScriptPath}`)
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' }
})
env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' },
});
let stderrOutput = '';
nodeProcess.stdout.on('data', (data) => {
log.info(`Script output: ${data}`)
})
log.info(`Script output: ${data}`);
});
nodeProcess.stderr.on('data', (data) => {
const errorMsg = data.toString();
stderrOutput += errorMsg;
log.error(`Script error: ${errorMsg}`)
})
log.error(`Script error: ${errorMsg}`);
});
nodeProcess.on('close', (code) => {
if (code === 0) {
log.info('Script completed successfully')
resolve(true)
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))
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<string> {
if (process.platform === 'win32') {
return `${name}.exe`
return `${name}.exe`;
}
return name
return name;
}
export async function getBinaryPath(name?: string): Promise<string> {
const binariesDir = path.join(os.homedir(), '.eigent', 'bin')
/**
* Get path to prebuilt binary (if available in packaged app)
*/
export function getPrebuiltBinaryPath(name?: string): string | null {
if (!app.isPackaged) {
return null;
}
// Ensure .eigent/bin directory exists
if (!fs.existsSync(binariesDir)) {
fs.mkdirSync(binariesDir, { recursive: true })
const prebuiltBinDir = path.join(process.resourcesPath, 'prebuilt', 'bin');
if (!fs.existsSync(prebuiltBinDir)) {
return null;
}
if (!name) {
return binariesDir
return prebuiltBinDir;
}
const binaryName = await getBinaryName(name)
return path.join(binariesDir, binaryName)
const binaryName = process.platform === 'win32' ? `${name}.exe` : name;
const binaryPath = path.join(prebuiltBinDir, binaryName);
return fs.existsSync(binaryPath) ? binaryPath : null;
}
export async function getBinaryPath(name?: string): Promise<string> {
// First check for prebuilt binary in packaged app
if (app.isPackaged) {
const prebuiltPath = getPrebuiltBinaryPath(name);
if (prebuiltPath) {
log.info(`Using prebuilt binary: ${prebuiltPath}`);
return prebuiltPath;
}
}
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)
// For packaged app, try to use prebuilt cache first
if (app.isPackaged) {
const prebuiltCachePath = path.join(
process.resourcesPath,
'prebuilt',
'cache',
folder
);
if (fs.existsSync(prebuiltCachePath)) {
log.info(`Using prebuilt cache: ${prebuiltCachePath}`);
return prebuiltCachePath;
}
}
const cacheDir = path.join(os.homedir(), '.eigent', 'cache', folder);
// Ensure cache directory exists
if (!fs.existsSync(cacheDir)) {
fs.mkdirSync(cacheDir, { recursive: true })
fs.mkdirSync(cacheDir, { recursive: true });
}
return cacheDir
return cacheDir;
}
/**
* Get path to prebuilt venv (if available in packaged app)
*/
export function getPrebuiltVenvPath(): string | null {
if (!app.isPackaged) {
return null;
}
const prebuiltVenvPath = path.join(process.resourcesPath, 'prebuilt', 'venv');
if (fs.existsSync(prebuiltVenvPath)) {
const pyvenvCfg = path.join(prebuiltVenvPath, 'pyvenv.cfg');
if (fs.existsSync(pyvenvCfg)) {
log.info(`Using prebuilt venv: ${prebuiltVenvPath}`);
return prebuiltVenvPath;
}
}
return null;
}
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 })
// First check for prebuilt venv in packaged app
if (app.isPackaged) {
const prebuiltVenv = getPrebuiltVenvPath();
if (prebuiltVenv) {
return prebuiltVenv;
}
}
return venvDir
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')
return path.join(os.homedir(), '.eigent', 'venvs');
}
export async function cleanupOldVenvs(currentVersion: string): Promise<void> {
const venvsBaseDir = getVenvsBaseDir()
const venvsBaseDir = getVenvsBaseDir();
// Check if venvs directory exists
if (!fs.existsSync(venvsBaseDir)) {
return
return;
}
try {
const entries = fs.readdirSync(venvsBaseDir, { withFileTypes: true })
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-(.+)$/)
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}`)
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}`)
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)
console.error(`Failed to remove old venv ${entry.name}:`, err);
}
}
}
}
} catch (err) {
console.error('Error during venv cleanup:', err)
console.error('Error during venv cleanup:', err);
}
}
export async function isBinaryExists(name: string): Promise<boolean> {
const cmd = await getBinaryPath(name)
const cmd = await getBinaryPath(name);
return await fs.existsSync(cmd)
return await fs.existsSync(cmd);
}
/**
@ -155,36 +236,36 @@ export function getUvEnv(version: string): Record<string, string> {
UV_TOOL_DIR: getCachePath('uv_tool'),
UV_PROJECT_ENVIRONMENT: getVenvPath(version),
UV_HTTP_TIMEOUT: '300',
}
};
}
export async function killProcessByName(name: string): Promise<void> {
const platform = process.platform
const platform = process.platform;
try {
if (platform === 'win32') {
await new Promise<void>((resolve, reject) => {
// /F = force, /IM = image name
const cmd = spawn('taskkill', ['/F', '/IM', `${name}.exe`])
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)
})
if (code === 0 || code === 128) resolve();
else reject(new Error(`taskkill exited with code ${code}`));
});
cmd.on('error', reject);
});
} else {
await new Promise<void>((resolve, reject) => {
const cmd = spawn('pkill', ['-9', name])
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)
})
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)
log.warn(`Failed to kill process ${name}:`, err);
}
}