// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= import { spawn } from 'child_process'; import { app, BrowserWindow } from 'electron'; import log from 'electron-log'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { cleanupOldVenvs, getBackendPath, getBinaryPath, getCachePath, getPrebuiltPythonDir, getPrebuiltTerminalVenvPath, getTerminalVenvPath, getUvEnv, getVenvPath, getVenvPythonPath, isBinaryExists, runInstallScript, TERMINAL_BASE_PACKAGES, } from './utils/process'; import { safeMainWindowSend } from './utils/safeWebContentsSend'; const userData = app.getPath('userData'); const versionFile = path.join(userData, 'version.txt'); export type PromiseReturnType = { message: string; success: boolean; }; interface checkInstallProps { win: BrowserWindow | null; forceInstall?: boolean; } // Read last run version and install dependencies on update export const checkAndInstallDepsOnUpdate = async ({ win, forceInstall = false, }: checkInstallProps): Promise => { const currentVersion = app.getVersion(); let savedVersion = ''; // Check if prebuilt dependencies are available const hasPrebuiltDeps = (): boolean => { if (!app.isPackaged) { return false; } const prebuiltBinDir = path.join(process.resourcesPath, 'prebuilt', 'bin'); const prebuiltDir = path.join(process.resourcesPath, 'prebuilt'); const prebuiltVenvDir = path.join(prebuiltDir, 'venv'); const uvPath = path.join( prebuiltBinDir, process.platform === 'win32' ? 'uv.exe' : 'uv' ); const bunPath = path.join( prebuiltBinDir, process.platform === 'win32' ? 'bun.exe' : 'bun' ); const pyvenvCfg = path.join(prebuiltVenvDir, 'pyvenv.cfg'); const hasBinaries = fs.existsSync(uvPath) && fs.existsSync(bunPath); const hasVenv = fs.existsSync(pyvenvCfg); if (hasBinaries && hasVenv) { log.info( '[DEPS INSTALL] Prebuilt dependencies found, skipping installation' ); return true; } return false; }; const checkInstallOperations = { getSavedVersion: (): boolean => { // Check if version file exists const versionExists = fs.existsSync(versionFile); if (versionExists) { log.info('[DEPS INSTALL] start check version', { currentVersion }); savedVersion = fs.readFileSync(versionFile, 'utf-8').trim(); log.info('[DEPS INSTALL] read saved version', { savedVersion }); } else { log.info('[DEPS INSTALL] version file not exist, will create new file'); } return versionExists; }, handleUpdateNotification: (versionExists: boolean) => { 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', }); } else { log.warn( '[DEPS INSTALL] Cannot send update notification - window not available' ); } }, createVersionFile: () => { fs.writeFileSync(versionFile, currentVersion); log.info('[DEPS INSTALL] version file updated', { currentVersion }); }, }; return new Promise(async (resolve, _reject) => { try { // If prebuilt dependencies are available, use them and skip main installation if (hasPrebuiltDeps()) { log.info( '[DEPS INSTALL] Using prebuilt dependencies, creating version file' ); checkInstallOperations.createVersionFile(); // Check if prebuilt terminal venv exists const prebuiltTerminalVenv = getPrebuiltTerminalVenvPath(); if (prebuiltTerminalVenv) { log.info( '[DEPS INSTALL] Using prebuilt terminal venv:', prebuiltTerminalVenv ); } else { // Create terminal base venv if not prebuilt log.info( '[DEPS INSTALL] Creating terminal base venv (not prebuilt)...' ); try { uv_path = await getBinaryPath('uv'); const terminalResult = await installTerminalBaseVenv(currentVersion); if (!terminalResult.success) { log.warn( '[DEPS INSTALL] Terminal base venv installation failed, but continuing...', terminalResult.message ); } else { log.info( '[DEPS INSTALL] Terminal base venv created successfully' ); } } catch (error) { log.warn( '[DEPS INSTALL] Failed to create terminal base venv:', error ); } } resolve({ message: 'Using prebuilt dependencies', success: true }); return; } // Clean up cache in production environment BEFORE any checks // This ensures users always get fresh dependencies in production if (app.isPackaged) { log.info( '[CACHE CLEANUP] Production environment detected, cleaning cache before dependency check...' ); cleanupCacheInProduction(); } const versionExists: boolean = checkInstallOperations.getSavedVersion(); // Check if command tools are installed const uvExists = await isBinaryExists('uv'); const bunExists = await isBinaryExists('bun'); const toolsMissing = !uvExists || !bunExists; // If version file does not exist or version does not match, reinstall dependencies // Or if command tools are missing, need to install them if ( forceInstall || !versionExists || savedVersion !== currentVersion || toolsMissing ) { if (toolsMissing) { log.info( '[DEPS INSTALL] Command tools missing, starting installation...', { uvExists, bunExists, } ); } else { log.info( '[DEPS INSTALL] 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 checkInstallOperations.handleUpdateNotification(versionExists); // Install dependencies (version.txt will be updated AFTER successful install) const result = await installDependencies(currentVersion); if (!result.success) { log.error(' install dependencies failed'); resolve({ message: `Install dependencies failed, msg ${result.message}`, success: false, }); return; } // Update version file ONLY after successful installation checkInstallOperations.createVersionFile(); resolve({ message: 'Dependencies installed successfully after update', success: true, }); log.info('[DEPS INSTALL] install dependencies complete'); return; } else { log.info( '[DEPS INSTALL] version not changed and tools installed, skip install dependencies', { currentVersion } ); resolve({ message: 'Version not changed and tools installed, skipped installation', success: true, }); return; } } catch (error) { log.error(' check version and install dependencies error:', error); resolve({ message: `Error checking version: ${error}`, success: false }); return; } }); }; /** * Check if command line tools are installed, install if not */ export async function installCommandTool(): Promise { try { const ensureInstalled = async ( toolName: 'uv' | 'bun', scriptName: string ): Promise => { if (await isBinaryExists(toolName)) { return { message: `${toolName} already installed`, success: true }; } console.log(`start install ${toolName}`); try { await runInstallScript(scriptName); const installed = await isBinaryExists(toolName); if (installed) { safeMainWindowSend('install-dependencies-log', { type: 'stdout', data: `${toolName} installed successfully`, }); return { message: `${toolName} installed successfully`, success: true, }; } else { const errorMsg = `${toolName} installation failed: binary not found after installation`; safeMainWindowSend('install-dependencies-complete', { success: false, code: 2, error: errorMsg, }); return { message: errorMsg, success: false, }; } } catch (scriptError) { const errorMsg = `${toolName} installation failed: ${ scriptError instanceof Error ? scriptError.message : String(scriptError) }`; safeMainWindowSend('install-dependencies-complete', { success: false, code: 2, error: errorMsg, }); return { message: errorMsg, success: false, }; } }; const uvResult = await ensureInstalled('uv', 'install-uv.js'); if (!uvResult.success) { return { message: uvResult.message, success: false }; } const bunResult = await ensureInstalled('bun', 'install-bun.js'); if (!bunResult.success) { return { message: bunResult.message, success: false }; } return { message: 'Command tools installed successfully', success: true }; } catch (error) { const errorMessage = `Command tool installation failed: ${error}`; log.error( '[DEPS INSTALL] Exception during command tool installation:', error ); safeMainWindowSend('install-dependencies-complete', { success: false, code: 2, error: errorMessage, }); return { message: errorMessage, success: false }; } } let uv_path: string; const backendPath = getBackendPath(); // Ensure backend directory exists if (!fs.existsSync(backendPath)) { log.info(`Creating backend directory: ${backendPath}`); fs.mkdirSync(backendPath, { recursive: true }); } 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/', ]; /** * Get current installation status by checking lock files * @returns Object with installation status information */ export async function getInstallationStatus(): Promise<{ isInstalling: boolean; hasLockFile: boolean; installedExists: boolean; }> { try { const installingExists = fs.existsSync(installingLockPath); const installedExists = fs.existsSync(installedLockPath); // If installing lock exists, installation is in progress // If installed lock exists, installation completed previously return { isInstalling: installingExists, hasLockFile: installingExists || installedExists, installedExists: installedExists, }; } catch (error) { console.error( '[getInstallationStatus] Error checking installation status:', error ); return { isInstalling: false, hasLockFile: false, installedExists: false, }; } } class InstallLogs { private node_process; private version: string; constructor(extraArgs: string[], version: string) { console.log('start install dependencies', extraArgs, 'version:', version); this.version = version; this.node_process = spawn( uv_path, [ 'sync', '--no-dev', '--cache-dir', getCachePath('uv_cache'), ...extraArgs, ], { cwd: backendPath, env: { ...process.env, ...getUvEnv(version), }, } ); } /**Display filtered logs based on severity */ displayFilteredLogs(data: String) { if (!data) return; const msg = data.toString().trimEnd(); if ( msg.toLowerCase().includes('error') || msg.toLowerCase().includes('traceback') ) { log.error(`BACKEND: [DEPS INSTALL] ${msg}`); safeMainWindowSend('install-dependencies-log', { type: 'stderr', data: data.toString(), }); } else { log.info(`BACKEND: [DEPS INSTALL] ${msg}`); safeMainWindowSend('install-dependencies-log', { type: 'stdout', data: data.toString(), }); } } /**Handle stdout data */ onStdout() { this.node_process.stdout.on('data', (data: any) => { this.displayFilteredLogs(data); }); } /**Handle stderr data */ onStderr() { this.node_process.stderr.on('data', (data: any) => { this.displayFilteredLogs(data); }); } /**Handle process close event */ onClose(resolveInner: (code: number | null) => void) { this.node_process.on('close', resolveInner); } /** * 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, ''); } /**Clean installing Lock Path */ static cleanLockPath() { if (fs.existsSync(installingLockPath)) { fs.unlinkSync(installingLockPath); } } } /** * Clean up cache directory * This ensures users get fresh dependencies * Note: Only call this in production environment (caller should check app.isPackaged) */ function cleanupCacheInProduction(): void { try { const cacheBaseDir = path.join(os.homedir(), '.eigent', 'cache'); if (!fs.existsSync(cacheBaseDir)) { log.info( '[CACHE CLEANUP] Cache directory does not exist, nothing to clean' ); return; } log.info('[CACHE CLEANUP] Cleaning cache directory:', cacheBaseDir); fs.rmSync(cacheBaseDir, { recursive: true, force: true }); log.info('[CACHE CLEANUP] Cache directory cleaned successfully'); fs.mkdirSync(cacheBaseDir, { recursive: true }); log.info('[CACHE CLEANUP] Empty cache directory recreated'); } catch (error) { log.error('[CACHE CLEANUP] Failed to clean cache directory:', error); } } const runInstall = (extraArgs: string[], version: string) => { const installLogs = new InstallLogs(extraArgs, version); return new Promise((resolveInner, rejectInner) => { try { installLogs.onStdout(); installLogs.onStderr(); installLogs.onClose((code) => { console.log('install dependencies end', code === 0); InstallLogs.cleanLockPath(); resolveInner({ message: code === 0 ? 'Installation completed successfully' : `Installation failed with code ${code}`, success: code === 0, }); }); } catch (err) { log.error('run install failed', err); // Clean up uv_installing.lock file if installation fails InstallLogs.cleanLockPath(); rejectInner({ message: `Installation failed: ${err}`, success: false }); } }); }; /** * Find Python executable in prebuilt Python directory * UV stores Python installations in directories like: cpython-3.11.x+.../install/bin/python */ function findPrebuiltPythonExecutable(): string | null { const prebuiltPythonDir = getPrebuiltPythonDir(); if (!prebuiltPythonDir) { return null; } // Look for Python executable in the prebuilt directory // UV stores Python in subdirectories like: cpython-3.11.x+.../install/bin/python const possiblePaths: string[] = []; // First, try common direct paths possiblePaths.push( path.join(prebuiltPythonDir, 'install', 'bin', 'python'), path.join(prebuiltPythonDir, 'install', 'python.exe'), path.join(prebuiltPythonDir, 'bin', 'python'), path.join(prebuiltPythonDir, 'python.exe') ); // Then, search in subdirectories (UV stores Python in versioned directories) try { if (fs.existsSync(prebuiltPythonDir)) { const entries = fs.readdirSync(prebuiltPythonDir, { withFileTypes: true, }); for (const entry of entries) { if (entry.isDirectory() && entry.name.startsWith('cpython-')) { const subDir = path.join(prebuiltPythonDir, entry.name); possiblePaths.push( path.join(subDir, 'install', 'bin', 'python'), path.join(subDir, 'install', 'python.exe'), path.join(subDir, 'bin', 'python'), path.join(subDir, 'python.exe') ); } } } } catch (error) { log.warn('[DEPS INSTALL] Error searching for prebuilt Python:', error); } for (const pythonPath of possiblePaths) { if (fs.existsSync(pythonPath)) { log.info( `[DEPS INSTALL] Found prebuilt Python executable: ${pythonPath}` ); return pythonPath; } } log.info( '[DEPS INSTALL] Prebuilt Python directory found but executable not found, will use UV_PYTHON_INSTALL_DIR' ); return null; } /** * Install terminal base venv with common packages for terminal tasks. * This is a lightweight venv separate from the backend venv. */ async function installTerminalBaseVenv( version: string ): Promise { const terminalVenvPath = getTerminalVenvPath(version); const pythonPath = process.platform === 'win32' ? path.join(terminalVenvPath, 'Scripts', 'python.exe') : path.join(terminalVenvPath, 'bin', 'python'); // Marker file to indicate packages were installed successfully const installedMarker = path.join(terminalVenvPath, '.packages_installed'); // Check if terminal base venv already exists and packages are installed if (fs.existsSync(pythonPath) && fs.existsSync(installedMarker)) { log.info( '[DEPS INSTALL] Terminal base venv already exists with packages, skipping creation' ); return { message: 'Terminal base venv already exists', success: true }; } // If python exists but marker doesn't, packages may not be installed - need to reinstall const needsPackageInstall = fs.existsSync(pythonPath) && !fs.existsSync(installedMarker); if (needsPackageInstall) { log.info( '[DEPS INSTALL] Terminal venv exists but packages not installed, installing packages...' ); } else { log.info('[DEPS INSTALL] Creating terminal base venv...'); } safeMainWindowSend('install-dependencies-log', { type: 'stdout', data: needsPackageInstall ? 'Installing missing packages in terminal environment...\n' : 'Creating terminal base environment...\n', }); try { // Get UV environment variables (includes prebuilt Python if available) const uvEnv = getUvEnv(version); // Create the venv using uv (skip if only need package install) if (!needsPackageInstall) { // Try to use prebuilt Python directly if available const prebuiltPython = findPrebuiltPythonExecutable(); const venvArgs = prebuiltPython ? ['venv', '--python', prebuiltPython, terminalVenvPath] : ['venv', '--python', '3.11', terminalVenvPath]; await new Promise((resolve, reject) => { const createVenv = spawn(uv_path, venvArgs, { env: { ...process.env, ...uvEnv, }, }); createVenv.stdout.on('data', (data) => { log.info(`[DEPS INSTALL] terminal venv: ${data}`); }); createVenv.stderr.on('data', (data) => { log.info(`[DEPS INSTALL] terminal venv: ${data}`); }); createVenv.on('close', (code) => { if (code === 0) { resolve(); } else { reject( new Error(`Failed to create terminal venv, exit code: ${code}`) ); } }); createVenv.on('error', reject); }); } // Install base packages log.info('[DEPS INSTALL] Installing terminal base packages...'); safeMainWindowSend('install-dependencies-log', { type: 'stdout', data: `Installing packages: ${TERMINAL_BASE_PACKAGES.join(', ')}...\n`, }); await new Promise((resolve, reject) => { const installPkgs = spawn( uv_path, ['pip', 'install', '--python', pythonPath, ...TERMINAL_BASE_PACKAGES], { env: { ...process.env, ...uvEnv, }, } ); installPkgs.stdout.on('data', (data) => { log.info(`[DEPS INSTALL] terminal packages: ${data}`); safeMainWindowSend('install-dependencies-log', { type: 'stdout', data: data.toString(), }); }); installPkgs.stderr.on('data', (data) => { log.info(`[DEPS INSTALL] terminal packages: ${data}`); safeMainWindowSend('install-dependencies-log', { type: 'stdout', data: data.toString(), }); }); installPkgs.on('close', (code) => { if (code === 0) { resolve(); } else { reject( new Error(`Failed to install terminal packages, exit code: ${code}`) ); } }); installPkgs.on('error', reject); }); // Create marker file to indicate successful installation fs.writeFileSync(installedMarker, new Date().toISOString()); log.info('[DEPS INSTALL] Terminal base venv created successfully'); return { message: 'Terminal base venv created successfully', success: true, }; } catch (error) { log.error('[DEPS INSTALL] Failed to create terminal base venv:', error); return { message: `Failed to create terminal base venv: ${error}`, success: false, }; } } export async function installDependencies( version: string ): Promise { uv_path = await getBinaryPath('uv'); const venvPath = getVenvPath(version); const handleInstallOperations = { spawnBabel: (message: 'mirror' | 'main' = 'main') => { fs.writeFileSync(installedLockPath, ''); log.info('[DEPS INSTALL] Script completed successfully'); console.log( `Install Dependencies completed ${message} for version ${version}` ); console.log(`Virtual environment path: ${venvPath}`); const pythonPath = getVenvPythonPath(venvPath); spawn( pythonPath, ['-m', 'babel.messages.frontend', 'compile', '-d', 'lang'], { cwd: backendPath, env: { ...process.env }, } ); }, notifyInstallDependenciesPage: (): boolean => { const success = safeMainWindowSend('install-dependencies-start'); if (!success) { log.warn( '[DEPS INSTALL] Main window not available, continuing installation without UI updates' ); } return success; }, installHybridBrowserDependencies: async (): Promise => { try { // Find the hybrid_browser_toolkit ts directory in the virtual environment // Need to determine the Python version to construct the correct path let sitePackagesPath: string | null = null; const libPath = path.join(venvPath, 'lib'); // Try to find the site-packages directory (it varies by Python version) if (fs.existsSync(libPath)) { const libContents = fs.readdirSync(libPath); const pythonDir = libContents.find((name) => name.startsWith('python') ); if (pythonDir) { sitePackagesPath = path.join(libPath, pythonDir, 'site-packages'); } } if (!sitePackagesPath || !fs.existsSync(sitePackagesPath)) { log.warn( '[DEPS INSTALL] site-packages directory not found in venv, skipping npm install' ); return true; // Not an error if the venv structure is different } const toolkitPath = path.join( sitePackagesPath, 'camel', 'toolkits', 'hybrid_browser_toolkit', 'ts' ); if (!fs.existsSync(toolkitPath)) { log.warn( '[DEPS INSTALL] hybrid_browser_toolkit ts directory not found at ' + toolkitPath + ', skipping npm install' ); return true; // Not an error if the toolkit isn't installed } // Check if npm dependencies are already installed const npmMarkerPath = path.join( toolkitPath, '.npm_dependencies_installed' ); const nodeModulesPath = path.join(toolkitPath, 'node_modules'); const distPath = path.join(toolkitPath, 'dist'); // Check if marker exists and verify version if ( fs.existsSync(npmMarkerPath) && fs.existsSync(nodeModulesPath) && fs.existsSync(distPath) ) { try { const markerContent = JSON.parse( fs.readFileSync(npmMarkerPath, 'utf-8') ); if (markerContent.version === version) { log.info( '[DEPS INSTALL] hybrid_browser_toolkit npm dependencies already installed for current version, skipping...' ); return true; } else { log.info( '[DEPS INSTALL] npm dependencies installed for different version, will reinstall...' ); // Clean up old installation fs.unlinkSync(npmMarkerPath); } } catch (error) { log.warn( '[DEPS INSTALL] Could not read npm marker file, will reinstall...', error ); // If we can't read the marker, assume we need to reinstall } } log.info( '[DEPS INSTALL] Installing hybrid_browser_toolkit npm dependencies...' ); safeMainWindowSend('install-dependencies-log', { type: 'stdout', data: 'Installing browser toolkit dependencies...\n', }); // Try to find npm - first try system npm, then try uv run npm let npmCommand: string[]; const testNpm = spawn('npm', ['--version'], { shell: true }); const npmExists = await new Promise((resolve) => { testNpm.on('close', (code) => resolve(code === 0)); testNpm.on('error', () => resolve(false)); }); if (npmExists) { // Use system npm directly npmCommand = ['npm']; log.info('[DEPS INSTALL] Using system npm for installation'); } else { // Try uv run npm (might not work if nodejs-wheel isn't properly set up) // Quote the path to handle spaces in username on Windows npmCommand = [`"${uv_path}"`, 'run', 'npm']; log.info('[DEPS INSTALL] Attempting to use uv run npm'); } // Run npm install const npmCacheDir = path.join(venvPath, '.npm-cache'); if (!fs.existsSync(npmCacheDir)) { fs.mkdirSync(npmCacheDir, { recursive: true }); } const npmInstall = spawn( npmCommand[0], [...npmCommand.slice(1), 'install'], { cwd: toolkitPath, env: { ...process.env, UV_PROJECT_ENVIRONMENT: venvPath, npm_config_cache: npmCacheDir, }, shell: true, // Important for Windows } ); await new Promise((resolve, reject) => { if (npmInstall.stdout) { npmInstall.stdout.on('data', (data) => { log.info(`[DEPS INSTALL] npm install: ${data}`); safeMainWindowSend('install-dependencies-log', { type: 'stdout', data: data.toString(), }); }); } if (npmInstall.stderr) { npmInstall.stderr.on('data', (data) => { log.warn(`[DEPS INSTALL] npm install stderr: ${data}`); safeMainWindowSend('install-dependencies-log', { type: 'stderr', data: data.toString(), }); }); } npmInstall.on('close', (code) => { if (code === 0) { log.info('[DEPS INSTALL] npm install completed successfully'); resolve(); } else { log.error(`[DEPS INSTALL] npm install failed with code ${code}`); reject(new Error(`npm install failed with code ${code}`)); } }); npmInstall.on('error', (err) => { log.error(`[DEPS INSTALL] npm install process error: ${err}`); reject(err); }); }); // Run npm build (use the same npm command as install) log.info( '[DEPS INSTALL] Building hybrid_browser_toolkit TypeScript...' ); safeMainWindowSend('install-dependencies-log', { type: 'stdout', data: 'Building browser toolkit TypeScript...\n', }); const buildArgs = npmCommand[0] === 'npm' ? ['run', 'build'] : [...npmCommand.slice(1), 'run', 'build']; const npmBuild = spawn(npmCommand[0], buildArgs, { cwd: toolkitPath, env: { ...process.env, UV_PROJECT_ENVIRONMENT: venvPath, npm_config_cache: npmCacheDir, }, shell: true, // Important for Windows }); await new Promise((resolve, reject) => { if (npmBuild.stdout) { npmBuild.stdout.on('data', (data) => { log.info(`[DEPS INSTALL] npm build: ${data}`); safeMainWindowSend('install-dependencies-log', { type: 'stdout', data: data.toString(), }); }); } if (npmBuild.stderr) { npmBuild.stderr.on('data', (data) => { // TypeScript build warnings are common, don't treat as errors log.info(`[DEPS INSTALL] npm build output: ${data}`); safeMainWindowSend('install-dependencies-log', { type: 'stdout', data: data.toString(), }); }); } npmBuild.on('close', (code) => { if (code === 0) { log.info( '[DEPS INSTALL] TypeScript build completed successfully' ); resolve(); } else { log.error( `[DEPS INSTALL] TypeScript build failed with code ${code}` ); reject(new Error(`TypeScript build failed with code ${code}`)); } }); npmBuild.on('error', (err) => { log.error(`[DEPS INSTALL] npm build process error: ${err}`); reject(err); }); }); // Optionally install Playwright browsers try { log.info('[DEPS INSTALL] Installing Playwright browsers...'); const npxCommand = npmCommand[0] === 'npm' ? ['npx'] : [`"${uv_path}"`, 'run', 'npx']; const playwrightInstall = spawn( npxCommand[0], [...npxCommand.slice(1), 'playwright', 'install'], { cwd: toolkitPath, env: { ...process.env, UV_PROJECT_ENVIRONMENT: venvPath, }, shell: true, } ); await new Promise((resolve) => { playwrightInstall.on('close', (code) => { if (code === 0) { log.info( '[DEPS INSTALL] Playwright browsers installed successfully' ); // Create marker file const markerPath = path.join( toolkitPath, '.playwright_installed' ); fs.writeFileSync(markerPath, 'installed'); } else { log.warn( '[DEPS INSTALL] Playwright installation failed, but continuing anyway' ); } resolve(); }); playwrightInstall.on('error', (err) => { log.warn( '[DEPS INSTALL] Playwright installation process error:', err ); resolve(); // Non-critical, continue }); }); } catch (error) { log.warn( '[DEPS INSTALL] Failed to install Playwright browsers:', error ); // Non-critical, continue } // Create marker file to indicate npm dependencies are installed fs.writeFileSync( npmMarkerPath, JSON.stringify({ installedAt: new Date().toISOString(), version: version, }) ); log.info('[DEPS INSTALL] Created npm dependencies marker file'); log.info( '[DEPS INSTALL] hybrid_browser_toolkit dependencies installed successfully' ); return true; } catch (error) { log.error( '[DEPS INSTALL] Failed to install hybrid_browser_toolkit dependencies:', error ); // Don't fail the entire installation if this fails return false; } }, }; return new Promise(async (resolve, _reject) => { console.log('start install dependencies'); const mainWindowAvailable = handleInstallOperations.notifyInstallDependenciesPage(); if (!mainWindowAvailable) { log.info( '[DEPS INSTALL] Proceeding with installation without UI notifications' ); } const isInstalCommandTool = await installCommandTool(); if (!isInstalCommandTool.success) { log.error( '[DEPS INSTALL] Command tool installation failed:', isInstalCommandTool.message ); safeMainWindowSend('install-dependencies-complete', { success: false, code: 2, error: isInstalCommandTool.message || 'Command tool installation failed', }); resolve({ message: 'Command tool installation failed', success: false }); return; } // Set Installing Lock Files InstallLogs.setLockPath(); // Clean up npm dependencies marker when reinstalling Python deps // This ensures npm deps are reinstalled when Python environment changes try { let sitePackagesPath: string | null = null; const libPath = path.join(venvPath, 'lib'); if (fs.existsSync(libPath)) { const libContents = fs.readdirSync(libPath); const pythonDir = libContents.find((name) => name.startsWith('python')); if (pythonDir) { sitePackagesPath = path.join(libPath, pythonDir, 'site-packages'); } } if (sitePackagesPath) { const npmMarkerPath = path.join( sitePackagesPath, 'camel', 'toolkits', 'hybrid_browser_toolkit', 'ts', '.npm_dependencies_installed' ); if (fs.existsSync(npmMarkerPath)) { fs.unlinkSync(npmMarkerPath); log.info( '[DEPS INSTALL] Removed npm dependencies marker for fresh installation' ); } } } catch (error) { log.warn('[DEPS INSTALL] Could not clean npm marker file:', error); // Non-critical, continue } // try default install const installSuccess = await runInstall([], version); if (installSuccess.success) { // Install terminal base venv (lightweight venv for terminal tasks) log.info('[DEPS INSTALL] Installing terminal base venv...'); const terminalResult = await installTerminalBaseVenv(version); if (!terminalResult.success) { log.warn( '[DEPS INSTALL] Terminal base venv installation failed, but continuing...', terminalResult.message ); } // Install hybrid_browser_toolkit npm dependencies after Python packages are installed log.info( '[DEPS INSTALL] Installing hybrid_browser_toolkit dependencies...' ); await handleInstallOperations.installHybridBrowserDependencies(); handleInstallOperations.spawnBabel(); // Clean up old venvs after successful installation log.info('[DEPS INSTALL] Cleaning up old virtual environments...'); await cleanupOldVenvs(version); log.info('[DEPS INSTALL] Old venvs cleanup completed'); resolve({ message: 'Dependencies installed successfully', success: true, }); return; } // try mirror install const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; let mirrorInstallSuccess: PromiseReturnType = { message: '', success: false, }; mirrorInstallSuccess = timezone === 'Asia/Shanghai' ? await runInstall(proxyArgs, version) : await runInstall([], version); if (mirrorInstallSuccess.success) { // Install terminal base venv (lightweight venv for terminal tasks) log.info('[DEPS INSTALL] Installing terminal base venv...'); const terminalResult = await installTerminalBaseVenv(version); if (!terminalResult.success) { log.warn( '[DEPS INSTALL] Terminal base venv installation failed, but continuing...', terminalResult.message ); } // Install hybrid_browser_toolkit npm dependencies after Python packages are installed log.info( '[DEPS INSTALL] Installing hybrid_browser_toolkit dependencies...' ); await handleInstallOperations.installHybridBrowserDependencies(); handleInstallOperations.spawnBabel('mirror'); // Clean up old venvs after successful installation log.info('[DEPS INSTALL] Cleaning up old virtual environments...'); await cleanupOldVenvs(version); log.info('[DEPS INSTALL] Old venvs cleanup completed'); resolve({ message: 'Dependencies installed successfully with mirror', success: true, }); } else { log.error('Both default and mirror install failed'); safeMainWindowSend('install-dependencies-complete', { success: false, error: 'Both default and mirror install failed', }); resolve({ message: 'Both default and mirror install failed', success: false, }); } }); }