refactor: use class to organize install

This commit is contained in:
a7m-1st 2025-09-18 21:14:30 +03:00
parent c4f4de6c2e
commit bd875d1aeb

View file

@ -4,6 +4,7 @@ import log from 'electron-log'
import { getMainWindow } from './init'
import fs from 'node:fs'
import { getBackendPath, getBinaryPath, getCachePath, isBinaryExists, runInstallScript } from './utils/process'
import { spawn } from 'child_process'
const userData = app.getPath('userData');
const versionFile = path.join(userData, 'version.txt');
@ -76,7 +77,7 @@ export async function checkAndInstallDepsOnUpdate(win:BrowserWindow): Promise<bo
*/
export async function installCommandTool() {
return new Promise(async (resolve, reject) => {
const ensureInstalled = async (toolName: 'uv' | 'bun', scriptName: string): Promise<boolean> => {
const ensureInstalled = async (toolName: 'uv' | 'bun', scriptName: string): Promise<PromiseReturnType> => {
if (await isBinaryExists(toolName)) {
return true;
}
@ -115,120 +116,149 @@ export async function installCommandTool() {
})
}
export async function installDependencies() {
const { spawn } = await import('child_process');
return new Promise<boolean>(async (resolve, reject) => {
console.log('start install dependencies')
let uv_path:string;
const mainWindow = getMainWindow();
const backendPath = getBackendPath();
const installingLockPath = path.join(backendPath, 'uv_installing.lock')
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 mainWindow = getMainWindow();
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('install-dependencies-start');
class InstallLogs {
private node_process;
constructor(extraArgs:string[]) {
console.log('start install dependencies', extraArgs)
this.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'),
}
})
}
/**Handle stdout data */
onStdout() {
this.node_process.stdout.on('data', (data:any) => {
log.info(`Script output: ${data}`)
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('install-dependencies-log', { type: 'stdout', data: data.toString() });
}
})
}
const isInstalCommandTool = await installCommandTool()
if (!isInstalCommandTool) {
resolve(false)
return
/**Handle stderr data */
onStderr() {
this.node_process.stderr.on('data', (data:any) => {
log.error(`Script error: ${data}`)
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('install-dependencies-log', { type: 'stderr', data: data.toString() });
}
})
}
const uv_path = await getBinaryPath('uv')
const backendPath = getBackendPath()
if (!fs.existsSync(backendPath)) {
fs.mkdirSync(backendPath, { recursive: true })
/**Handle process close event */
onClose(resolveInner:(code: number | null) => void) {
this.node_process.on('close', resolveInner);
}
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)
}
})
/**
* Set installing Lock Path
* Creates uv_installing.lock file to indicate installation in progress
* Creates backend directory if not exists
*/
static setLockPath() {
if (!fs.existsSync(backendPath)) {
fs.mkdirSync(backendPath, { recursive: true })
}
fs.writeFileSync(installingLockPath, '')
}
// 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
/**Clean installing Lock Path */
static cleanLockPath() {
if (fs.existsSync(installingLockPath)) {
fs.unlinkSync(installingLockPath);
}
}
}
// 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)
}
const runInstall = (extraArgs: string[]) => {
const installLogs = new InstallLogs(extraArgs);
return new Promise<PromiseReturnType>((resolveInner, rejectInner) => {
try {
installLogs.onStdout();
installLogs.onStderr();
installLogs.onClose((code) => {
console.log('install dependencies end', code === 0)
InstallLogs.cleanLockPath()
resolveInner(code === 0)
})
} catch (err) {
log.error('run install failed', err)
// Clean up uv_installing.lock file if installation fails
InstallLogs.cleanLockPath();
rejectInner(err)
}
})
}
export async function installDependencies() {
uv_path = await getBinaryPath('uv');
const handleCompletion = {
spawnBabel: (type:"mirror"|"main"="main") => {
fs.writeFileSync(installedLockPath, '')
log.info('Script completed successfully')
console.log(`Install Dependencies completed ${type}`)
spawn(uv_path, ['run', 'task', 'babel'], { cwd: backendPath })
}
}
return new Promise<PromiseReturnType>(async (resolve, reject) => {
console.log('start install dependencies')
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('install-dependencies-start');
} else {
resolve(false)
return
}
const isInstalCommandTool = await installCommandTool()
if (!isInstalCommandTool) {
resolve(false)
return
}
// Set Installing Lock Files
InstallLogs.setLockPath();
// try default install
const installSuccess = await runInstall([])
if (installSuccess) {
handleCompletion.spawnBabel()
resolve(true)
return
}
// try mirror install
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
let mirrorInstallSuccess = false
mirrorInstallSuccess = (timezone === 'Asia/Shanghai')? await runInstall(proxyArgs) :await runInstall([])
if (mirrorInstallSuccess) {
handleCompletion.spawnBabel("mirror")
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)
}
})
}