mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-05-16 19:50:50 +00:00
refactor: isolate install logic
This commit is contained in:
parent
1908467adb
commit
c4f4de6c2e
3 changed files with 238 additions and 230 deletions
|
|
@ -4,7 +4,7 @@ import path from 'node:path'
|
|||
import os, { homedir } from 'node:os'
|
||||
import log from 'electron-log'
|
||||
import { update, registerUpdateIpcHandlers } from './update'
|
||||
import { checkToolInstalled, installDependencies, killProcessOnPort, startBackend } from './init'
|
||||
import { checkToolInstalled, killProcessOnPort, startBackend } from './init'
|
||||
import { WebViewManager } from './webview'
|
||||
import { FileReader } from './fileReader'
|
||||
import { ChildProcessWithoutNullStreams } from 'node:child_process'
|
||||
|
|
@ -18,9 +18,9 @@ import kill from 'tree-kill';
|
|||
import { zipFolder } from './utils/log'
|
||||
import axios from 'axios';
|
||||
import FormData from 'form-data';
|
||||
import { checkAndInstallDepsOnUpdate, installDependencies } from './install-deps'
|
||||
|
||||
const userData = app.getPath('userData');
|
||||
const versionFile = path.join(userData, 'version.txt');
|
||||
|
||||
// ==================== constants ====================
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
|
@ -50,69 +50,6 @@ findAvailablePort(browser_port).then(port => {
|
|||
app.commandLine.appendSwitch('remote-debugging-port', port + '');
|
||||
});
|
||||
|
||||
// Read last run version and install dependencies on update
|
||||
async function checkAndInstallDepsOnUpdate(): Promise<boolean> {
|
||||
const currentVersion = app.getVersion();
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
log.info(' start check version', { currentVersion });
|
||||
|
||||
// Check if version file exists
|
||||
const versionExists = fs.existsSync(versionFile);
|
||||
let savedVersion = '';
|
||||
|
||||
if (versionExists) {
|
||||
savedVersion = fs.readFileSync(versionFile, 'utf-8').trim();
|
||||
log.info(' read saved version', { savedVersion });
|
||||
} else {
|
||||
log.info(' version file not exist, will create new file');
|
||||
}
|
||||
|
||||
// If version file does not exist or version does not match, reinstall dependencies
|
||||
if (!versionExists || savedVersion !== currentVersion) {
|
||||
log.info(' version changed, prepare to reinstall uv dependencies...', {
|
||||
currentVersion,
|
||||
savedVersion: versionExists ? savedVersion : 'none',
|
||||
reason: !versionExists ? 'version file not exist' : 'version not match'
|
||||
});
|
||||
|
||||
// Notify frontend to update
|
||||
if (win && !win.isDestroyed()) {
|
||||
win.webContents.send('update-notification', {
|
||||
type: 'version-update',
|
||||
currentVersion,
|
||||
previousVersion: versionExists ? savedVersion : 'none',
|
||||
reason: !versionExists ? 'version file not exist' : 'version not match'
|
||||
});
|
||||
}
|
||||
|
||||
// Update version file
|
||||
fs.writeFileSync(versionFile, currentVersion);
|
||||
log.info(' version file updated', { currentVersion });
|
||||
|
||||
// Install dependencies
|
||||
const result = await installDependencies();
|
||||
if (!result) {
|
||||
log.error(' install dependencies failed');
|
||||
resolve(false);
|
||||
return
|
||||
}
|
||||
resolve(true);
|
||||
log.info(' install dependencies complete');
|
||||
return
|
||||
} else {
|
||||
log.info(' version not changed, skip install dependencies', { currentVersion });
|
||||
resolve(true);
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(' check version and install dependencies error:', error);
|
||||
resolve(false);
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== app config ====================
|
||||
process.env.APP_ROOT = MAIN_DIST;
|
||||
process.env.VITE_PUBLIC = VITE_PUBLIC;
|
||||
|
|
@ -1006,7 +943,7 @@ async function createWindow() {
|
|||
update(win);
|
||||
|
||||
// ==================== check tool installed ====================
|
||||
let res = await checkAndInstallDepsOnUpdate();
|
||||
let res = await checkAndInstallDepsOnUpdate(win);
|
||||
if (!res) {
|
||||
log.info('checkAndInstallDepsOnUpdate,install dependencies failed');
|
||||
win.webContents.send('install-dependencies-complete', { success: false, code: 2 });
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { promisify } from 'util'
|
|||
const execAsync = promisify(exec);
|
||||
|
||||
// helper function to get main window
|
||||
function getMainWindow(): BrowserWindow | null {
|
||||
export function getMainWindow(): BrowserWindow | null {
|
||||
const windows = BrowserWindow.getAllWindows();
|
||||
return windows.length > 0 ? windows[0] : null;
|
||||
}
|
||||
|
|
@ -31,51 +31,6 @@ export async function checkToolInstalled() {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if command line tools are installed, install if not
|
||||
*/
|
||||
export async function installCommandTool() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const ensureInstalled = async (toolName: 'uv' | 'bun', scriptName: string): Promise<boolean> => {
|
||||
if (await isBinaryExists(toolName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log(`start install ${toolName}`);
|
||||
await runInstallScript(scriptName);
|
||||
const installed = await isBinaryExists(toolName);
|
||||
|
||||
const mainWindow = getMainWindow();
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
if (installed) {
|
||||
mainWindow.webContents.send('install-dependencies-log', {
|
||||
type: 'stdout',
|
||||
data: `${toolName} installed successfully`,
|
||||
});
|
||||
} else {
|
||||
mainWindow.webContents.send('install-dependencies-complete', {
|
||||
success: false,
|
||||
code: 2,
|
||||
error: `${toolName} installation failed (script exit code 2)`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return installed;
|
||||
};
|
||||
|
||||
if (!(await ensureInstalled('uv', 'install-uv.js'))) {
|
||||
return reject("uv install failed");
|
||||
}
|
||||
if (!(await ensureInstalled('bun', 'install-bun.js'))) {
|
||||
return reject("bun install failed");
|
||||
}
|
||||
|
||||
return resolve(true);
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// export async function installDependencies() {
|
||||
// return new Promise<boolean>(async (resolve, reject) => {
|
||||
// console.log('start install dependencies')
|
||||
|
|
@ -157,124 +112,6 @@ export async function installCommandTool() {
|
|||
// })
|
||||
// })
|
||||
// }
|
||||
export async function installDependencies() {
|
||||
return new Promise<boolean>(async (resolve, reject) => {
|
||||
console.log('start install dependencies')
|
||||
|
||||
const mainWindow = getMainWindow();
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('install-dependencies-start');
|
||||
}
|
||||
|
||||
const isInstalCommandTool = await installCommandTool()
|
||||
if (!isInstalCommandTool) {
|
||||
resolve(false)
|
||||
return
|
||||
}
|
||||
|
||||
const uv_path = await getBinaryPath('uv')
|
||||
const backendPath = getBackendPath()
|
||||
|
||||
if (!fs.existsSync(backendPath)) {
|
||||
fs.mkdirSync(backendPath, { recursive: true })
|
||||
}
|
||||
|
||||
const installingLockPath = path.join(backendPath, 'uv_installing.lock')
|
||||
fs.writeFileSync(installingLockPath, '')
|
||||
|
||||
const installedLockPath = path.join(backendPath, 'uv_installed.lock')
|
||||
// const proxyArgs = ['--default-index', 'https://pypi.tuna.tsinghua.edu.cn/simple']
|
||||
const proxyArgs = ['--default-index', 'https://mirrors.aliyun.com/pypi/simple/']
|
||||
const runInstall = (extraArgs: string[]) => {
|
||||
return new Promise<boolean>((resolveInner, rejectInner) => {
|
||||
try {
|
||||
const node_process = spawn(uv_path, [
|
||||
'sync',
|
||||
'--no-dev',
|
||||
'--cache-dir', getCachePath('uv_cache'),
|
||||
...extraArgs], {
|
||||
cwd: backendPath,
|
||||
env: {
|
||||
...process.env,
|
||||
UV_TOOL_DIR: getCachePath('uv_tool'),
|
||||
UV_PYTHON_INSTALL_DIR: getCachePath('uv_python'),
|
||||
}
|
||||
})
|
||||
console.log('start install dependencies', extraArgs)
|
||||
node_process.stdout.on('data', (data) => {
|
||||
|
||||
log.info(`Script output: ${data}`)
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('install-dependencies-log', { type: 'stdout', data: data.toString() });
|
||||
}
|
||||
})
|
||||
|
||||
node_process.stderr.on('data', (data) => {
|
||||
log.error(`Script error: ${data}`)
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('install-dependencies-log', { type: 'stderr', data: data.toString() });
|
||||
}
|
||||
})
|
||||
|
||||
node_process.on('close', (code) => {
|
||||
console.log('install dependencies end', code === 0)
|
||||
resolveInner(code === 0)
|
||||
})
|
||||
}catch(err) {
|
||||
log.error('run install failed', err)
|
||||
// Clean up uv_installing.lock file if installation fails
|
||||
if (fs.existsSync(installingLockPath)) {
|
||||
fs.unlinkSync(installingLockPath);
|
||||
}
|
||||
rejectInner(err)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// try default install
|
||||
const installSuccess = await runInstall([])
|
||||
|
||||
if (installSuccess) {
|
||||
fs.unlinkSync(installingLockPath)
|
||||
fs.writeFileSync(installedLockPath, '')
|
||||
log.info('Script completed successfully')
|
||||
console.log('end install dependencies')
|
||||
spawn(uv_path, ['run', 'task', 'babel'], { cwd: backendPath })
|
||||
resolve(true)
|
||||
return
|
||||
}
|
||||
|
||||
// try mirror install
|
||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
let mirrorInstallSuccess = false
|
||||
|
||||
if (timezone === 'Asia/Shanghai') {
|
||||
mirrorInstallSuccess = await runInstall(proxyArgs)
|
||||
} else {
|
||||
mirrorInstallSuccess = await runInstall([])
|
||||
}
|
||||
|
||||
|
||||
fs.existsSync(installingLockPath) && fs.unlinkSync(installingLockPath)
|
||||
|
||||
if (mirrorInstallSuccess) {
|
||||
fs.writeFileSync(installedLockPath, '')
|
||||
log.info('Mirror script completed successfully')
|
||||
console.log('end install dependencies (mirror)')
|
||||
spawn(uv_path, ['run', 'task', 'babel'], { cwd: backendPath })
|
||||
resolve(true)
|
||||
} else {
|
||||
log.error('Both default and mirror install failed')
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('install-dependencies-complete', { success: false, error: 'Both default and mirror install failed' });
|
||||
}
|
||||
resolve(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
export async function startBackend(setPort?: (port: number) => void): Promise<any> {
|
||||
console.log('start fastapi')
|
||||
|
|
|
|||
234
electron/main/install-deps.ts
Normal file
234
electron/main/install-deps.ts
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
import { app, BrowserWindow } from 'electron'
|
||||
import path from 'node:path'
|
||||
import log from 'electron-log'
|
||||
import { getMainWindow } from './init'
|
||||
import fs from 'node:fs'
|
||||
import { getBackendPath, getBinaryPath, getCachePath, isBinaryExists, runInstallScript } from './utils/process'
|
||||
|
||||
const userData = app.getPath('userData');
|
||||
const versionFile = path.join(userData, 'version.txt');
|
||||
|
||||
// Read last run version and install dependencies on update
|
||||
export async function checkAndInstallDepsOnUpdate(win:BrowserWindow): Promise<boolean> {
|
||||
const currentVersion = app.getVersion();
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
log.info(' start check version', { currentVersion });
|
||||
|
||||
// Check if version file exists
|
||||
const versionExists = fs.existsSync(versionFile);
|
||||
let savedVersion = '';
|
||||
|
||||
if (versionExists) {
|
||||
savedVersion = fs.readFileSync(versionFile, 'utf-8').trim();
|
||||
log.info(' read saved version', { savedVersion });
|
||||
} else {
|
||||
log.info(' version file not exist, will create new file');
|
||||
}
|
||||
|
||||
// If version file does not exist or version does not match, reinstall dependencies
|
||||
if (!versionExists || savedVersion !== currentVersion) {
|
||||
log.info(' version changed, prepare to reinstall uv dependencies...', {
|
||||
currentVersion,
|
||||
savedVersion: versionExists ? savedVersion : 'none',
|
||||
reason: !versionExists ? 'version file not exist' : 'version not match'
|
||||
});
|
||||
|
||||
// Notify frontend to update
|
||||
if (win && !win.isDestroyed()) {
|
||||
win.webContents.send('update-notification', {
|
||||
type: 'version-update',
|
||||
currentVersion,
|
||||
previousVersion: versionExists ? savedVersion : 'none',
|
||||
reason: !versionExists ? 'version file not exist' : 'version not match'
|
||||
});
|
||||
}
|
||||
|
||||
// Update version file
|
||||
fs.writeFileSync(versionFile, currentVersion);
|
||||
log.info(' version file updated', { currentVersion });
|
||||
|
||||
// Install dependencies
|
||||
const result = await installDependencies();
|
||||
if (!result) {
|
||||
log.error(' install dependencies failed');
|
||||
resolve(false);
|
||||
return
|
||||
}
|
||||
resolve(true);
|
||||
log.info(' install dependencies complete');
|
||||
return
|
||||
} else {
|
||||
log.info(' version not changed, skip install dependencies', { currentVersion });
|
||||
resolve(true);
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(' check version and install dependencies error:', error);
|
||||
resolve(false);
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if command line tools are installed, install if not
|
||||
*/
|
||||
export async function installCommandTool() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const ensureInstalled = async (toolName: 'uv' | 'bun', scriptName: string): Promise<boolean> => {
|
||||
if (await isBinaryExists(toolName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log(`start install ${toolName}`);
|
||||
await runInstallScript(scriptName);
|
||||
const installed = await isBinaryExists(toolName);
|
||||
|
||||
const mainWindow = getMainWindow();
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
if (installed) {
|
||||
mainWindow.webContents.send('install-dependencies-log', {
|
||||
type: 'stdout',
|
||||
data: `${toolName} installed successfully`,
|
||||
});
|
||||
} else {
|
||||
mainWindow.webContents.send('install-dependencies-complete', {
|
||||
success: false,
|
||||
code: 2,
|
||||
error: `${toolName} installation failed (script exit code 2)`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return installed;
|
||||
};
|
||||
|
||||
if (!(await ensureInstalled('uv', 'install-uv.js'))) {
|
||||
return reject("uv install failed");
|
||||
}
|
||||
if (!(await ensureInstalled('bun', 'install-bun.js'))) {
|
||||
return reject("bun install failed");
|
||||
}
|
||||
|
||||
return resolve(true);
|
||||
})
|
||||
}
|
||||
|
||||
export async function installDependencies() {
|
||||
const { spawn } = await import('child_process');
|
||||
return new Promise<boolean>(async (resolve, reject) => {
|
||||
console.log('start install dependencies')
|
||||
|
||||
const mainWindow = getMainWindow();
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('install-dependencies-start');
|
||||
}
|
||||
|
||||
const isInstalCommandTool = await installCommandTool()
|
||||
if (!isInstalCommandTool) {
|
||||
resolve(false)
|
||||
return
|
||||
}
|
||||
|
||||
const uv_path = await getBinaryPath('uv')
|
||||
const backendPath = getBackendPath()
|
||||
|
||||
if (!fs.existsSync(backendPath)) {
|
||||
fs.mkdirSync(backendPath, { recursive: true })
|
||||
}
|
||||
|
||||
const installingLockPath = path.join(backendPath, 'uv_installing.lock')
|
||||
fs.writeFileSync(installingLockPath, '')
|
||||
|
||||
const installedLockPath = path.join(backendPath, 'uv_installed.lock')
|
||||
// const proxyArgs = ['--default-index', 'https://pypi.tuna.tsinghua.edu.cn/simple']
|
||||
const proxyArgs = ['--default-index', 'https://mirrors.aliyun.com/pypi/simple/']
|
||||
const runInstall = (extraArgs: string[]) => {
|
||||
return new Promise<boolean>((resolveInner, rejectInner) => {
|
||||
try {
|
||||
const node_process = spawn(uv_path, [
|
||||
'sync',
|
||||
'--no-dev',
|
||||
'--cache-dir', getCachePath('uv_cache'),
|
||||
...extraArgs], {
|
||||
cwd: backendPath,
|
||||
env: {
|
||||
...process.env,
|
||||
UV_TOOL_DIR: getCachePath('uv_tool'),
|
||||
UV_PYTHON_INSTALL_DIR: getCachePath('uv_python'),
|
||||
}
|
||||
})
|
||||
console.log('start install dependencies', extraArgs)
|
||||
node_process.stdout.on('data', (data) => {
|
||||
|
||||
log.info(`Script output: ${data}`)
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('install-dependencies-log', { type: 'stdout', data: data.toString() });
|
||||
}
|
||||
})
|
||||
|
||||
node_process.stderr.on('data', (data) => {
|
||||
log.error(`Script error: ${data}`)
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('install-dependencies-log', { type: 'stderr', data: data.toString() });
|
||||
}
|
||||
})
|
||||
|
||||
node_process.on('close', (code) => {
|
||||
console.log('install dependencies end', code === 0)
|
||||
resolveInner(code === 0)
|
||||
})
|
||||
}catch(err) {
|
||||
log.error('run install failed', err)
|
||||
// Clean up uv_installing.lock file if installation fails
|
||||
if (fs.existsSync(installingLockPath)) {
|
||||
fs.unlinkSync(installingLockPath);
|
||||
}
|
||||
rejectInner(err)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// try default install
|
||||
const installSuccess = await runInstall([])
|
||||
|
||||
if (installSuccess) {
|
||||
fs.unlinkSync(installingLockPath)
|
||||
fs.writeFileSync(installedLockPath, '')
|
||||
log.info('Script completed successfully')
|
||||
console.log('end install dependencies')
|
||||
spawn(uv_path, ['run', 'task', 'babel'], { cwd: backendPath })
|
||||
resolve(true)
|
||||
return
|
||||
}
|
||||
|
||||
// try mirror install
|
||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
let mirrorInstallSuccess = false
|
||||
|
||||
if (timezone === 'Asia/Shanghai') {
|
||||
mirrorInstallSuccess = await runInstall(proxyArgs)
|
||||
} else {
|
||||
mirrorInstallSuccess = await runInstall([])
|
||||
}
|
||||
|
||||
|
||||
fs.existsSync(installingLockPath) && fs.unlinkSync(installingLockPath)
|
||||
|
||||
if (mirrorInstallSuccess) {
|
||||
fs.writeFileSync(installedLockPath, '')
|
||||
log.info('Mirror script completed successfully')
|
||||
console.log('end install dependencies (mirror)')
|
||||
spawn(uv_path, ['run', 'task', 'babel'], { cwd: backendPath })
|
||||
resolve(true)
|
||||
} else {
|
||||
log.error('Both default and mirror install failed')
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('install-dependencies-complete', { success: false, error: 'Both default and mirror install failed' });
|
||||
}
|
||||
resolve(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue