| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616 |
- // electron/modules/ipc-handlers.ts - IPC 处理模块
- import { dialog, BrowserWindow } from 'electron'
- import { registerHandler } from './ipc-registry'
- import * as path from 'path'
- import * as os from 'os'
- import { execa } from 'execa'
- import type { SoftwareType, SoftwareTypeWithAll, Platform, InstallOptions, GitMirrorType, NodejsMirrorType } from './types'
- import { ERROR_MESSAGES } from './constants'
- import { commandExists, checkNetworkConnection, getVscodeCliPath } from './utils'
- import { installVscodeExtension } from './vscode-extension'
- import { installClaudeCode as installClaudeCodeWithStatus } from './claude-code-installer'
- import { getVersions, setGitMirror, getGitMirrorConfig, setNodejsMirror, getNodejsMirrorConfig } from './version-fetcher'
- import {
- checkInstalled,
- checkAllInstalled,
- cancelInstall,
- aptUpdate,
- installNodejs,
- installPnpm,
- installVscode,
- installGit,
- installAll,
- uninstallSoftware
- } from './installer'
- import { addInstallHistory, getInstallHistory } from './config'
- import logger from './logger'
- import {
- checkForUpdates,
- downloadUpdate,
- installUpdate,
- getCurrentVersion,
- isPortableMode
- } from './updater'
- /**
- * 检测管理员权限
- */
- async function checkAdminPrivilege(): Promise<boolean> {
- const platform = os.platform()
- try {
- if (platform === 'win32') {
- const result = await execa('powershell', [
- '-Command',
- '([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)'
- ])
- return result.stdout.trim().toLowerCase() === 'true'
- } else {
- return process.getuid ? process.getuid() === 0 : false
- }
- } catch (error) {
- logger.warn('检测管理员权限失败', error)
- return false
- }
- }
- /**
- * 检测 brew 是否安装
- */
- async function checkBrew(): Promise<boolean> {
- return await commandExists('brew')
- }
- /**
- * 安装 Homebrew
- */
- async function installBrew(): Promise<void> {
- const installScript = '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
- await execa('bash', ['-c', installScript])
- }
- /**
- * 发送状态到渲染进程
- */
- function sendToRenderer(channel: string, data: unknown): void {
- const windows = BrowserWindow.getAllWindows()
- windows.forEach((win) => {
- win.webContents.send(channel, data)
- })
- }
- /**
- * 注册所有 IPC 处理器
- */
- export function registerHandlers(): void {
- // 检测管理员权限
- registerHandler('check-admin', async () => {
- return await checkAdminPrivilege()
- })
- // 检测包管理器
- // Windows 使用直接下载方式安装,不需要包管理器
- // macOS 需要 brew,Linux 使用 apt
- registerHandler('check-package-manager', async () => {
- const platform = os.platform()
- if (platform === 'win32') {
- // Windows 不需要包管理器,直接下载安装
- return { exists: true, manager: 'none' as const }
- } else if (platform === 'darwin') {
- return { exists: await checkBrew(), manager: 'brew' }
- } else {
- return { exists: true, manager: 'apt' }
- }
- })
- // 安装包管理器 (仅 macOS 需要安装 brew)
- registerHandler('install-package-manager', async (_event, manager: string) => {
- try {
- logger.info(`开始安装包管理器: ${manager}`)
- if (manager === 'brew') {
- await installBrew()
- logger.info(`包管理器安装成功: ${manager}`)
- return { success: true }
- }
- // Windows 不需要包管理器,Linux apt 已预装
- return { success: true }
- } catch (error) {
- logger.error(`包管理器安装失败: ${manager}`, error)
- return { success: false, error: (error as Error).message }
- }
- })
- // 获取平台信息
- registerHandler('get-platform', (): Platform => {
- return os.platform() as Platform
- })
- // 检测网络连接
- registerHandler('check-network', async () => {
- return await checkNetworkConnection()
- })
- // 获取软件版本列表
- registerHandler('get-versions', async (_event, software: SoftwareType) => {
- try {
- return await getVersions(software)
- } catch (error) {
- logger.error(`获取 ${software} 版本列表失败`, error)
- throw error
- }
- })
- // 检查更新
- registerHandler('check-update', async (_event, software: SoftwareType) => {
- try {
- const installed = await checkInstalled(software)
- if (!installed.installed || !installed.version) {
- return { hasUpdate: false }
- }
- const versions = await getVersions(software)
- const latestVersion = versions.versions.find((v) => !v.disabled && !v.separator)
- if (!latestVersion) {
- return { hasUpdate: false }
- }
- const hasUpdate = latestVersion.value !== installed.version
- return { hasUpdate, latestVersion: latestVersion.value }
- } catch (error) {
- logger.error(`检查 ${software} 更新失败`, error)
- return { hasUpdate: false }
- }
- })
- // 检测软件是否已安装
- registerHandler('check-installed', async (_event, software: SoftwareTypeWithAll) => {
- if (software === 'all') {
- return await checkAllInstalled()
- }
- return await checkInstalled(software)
- })
- // 取消安装
- registerHandler('cancel-install', () => {
- return cancelInstall()
- })
- // 卸载软件
- registerHandler('uninstall', async (_event, software: SoftwareType) => {
- try {
- return await uninstallSoftware(software)
- } catch (error) {
- logger.error(`卸载 ${software} 失败`, error)
- return false
- }
- })
- // 获取安装历史
- registerHandler('get-install-history', (_event, limit?: number) => {
- return getInstallHistory(limit)
- })
- // 获取日志
- registerHandler('get-logs', () => {
- return logger.getRecentLogs(200)
- })
- // 写入安装日志(供渲染进程调用)
- registerHandler('write-install-log', (_event, message: string, level: 'info' | 'warn' | 'error' = 'info') => {
- switch (level) {
- case 'warn':
- logger.installWarn(message)
- break
- case 'error':
- logger.installError(message)
- break
- default:
- logger.installInfo(message)
- }
- })
- // 获取日志文件路径
- registerHandler('get-log-paths', () => {
- return {
- appLog: logger.getAppLogPath(),
- installLog: logger.getInstallLogPath()
- }
- })
- // 设置窗口标题
- registerHandler('set-window-title', (_event, title: string) => {
- const windows = BrowserWindow.getAllWindows()
- windows.forEach((win) => {
- win.setTitle(title)
- })
- })
- // 窗口最小化
- registerHandler('window-minimize', () => {
- const win = BrowserWindow.getFocusedWindow()
- win?.minimize()
- })
- // 窗口最大化/还原
- registerHandler('window-maximize', () => {
- const win = BrowserWindow.getFocusedWindow()
- if (win?.isMaximized()) {
- win.unmaximize()
- } else {
- win?.maximize()
- }
- return win?.isMaximized() ?? false
- })
- // 关闭窗口
- registerHandler('window-close', () => {
- const win = BrowserWindow.getFocusedWindow()
- win?.close()
- })
- // 获取窗口最大化状态
- registerHandler('window-is-maximized', () => {
- const win = BrowserWindow.getFocusedWindow()
- return win?.isMaximized() ?? false
- })
- // 检测 Claude Code 是否已安装
- registerHandler('check-claude-code', async () => {
- try {
- // 使用 checkInstalled 函数,它会刷新 PATH 环境变量来检测新安装的软件
- const result = await checkInstalled('claudeCode')
- if (result.installed) {
- logger.info(`检测到 Claude Code: ${result.version}`)
- } else {
- logger.info('未检测到 Claude Code')
- }
- return result
- } catch (error) {
- logger.warn('检测 Claude Code 失败', error)
- return { installed: false, version: null }
- }
- })
- // 启动 Claude Code (打开 Git Bash 并执行 claude 命令)
- registerHandler('launch-claude-code', async () => {
- const platform = os.platform()
- if (platform !== 'win32') {
- throw new Error('此功能仅支持 Windows 系统')
- }
- // 查找 Git Bash 路径
- const possiblePaths = [
- 'C:\\Program Files\\Git\\git-bash.exe',
- 'C:\\Program Files (x86)\\Git\\git-bash.exe',
- path.join(os.homedir(), 'AppData', 'Local', 'Programs', 'Git', 'git-bash.exe')
- ]
- let gitBashPath = ''
- for (const p of possiblePaths) {
- try {
- const fs = await import('fs')
- if (fs.existsSync(p)) {
- gitBashPath = p
- break
- }
- } catch {
- continue
- }
- }
- if (!gitBashPath) {
- throw new Error('未找到 Git Bash,请确保已安装 Git')
- }
- logger.info(`启动 Git Bash: ${gitBashPath}`)
- // 获取 pnpm 全局安装路径(claude 命令所在目录)
- // Windows 上 pnpm 全局安装的可执行文件在 %LOCALAPPDATA%\pnpm 目录下
- const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local')
- const pnpmGlobalBin = path.join(localAppData, 'pnpm')
- // 转换为 Git Bash 可识别的路径格式(将反斜杠转为正斜杠,将 C: 转为 /c)
- const pnpmGlobalBinUnix = pnpmGlobalBin.replace(/\\/g, '/').replace(/^([A-Za-z]):/, '/$1').toLowerCase()
- logger.info(`pnpm 全局路径: ${pnpmGlobalBin} -> ${pnpmGlobalBinUnix}`)
- // 获取 npm 全局安装路径(备用)
- // Windows 上 npm 全局安装的可执行文件在 %APPDATA%\npm 目录下
- const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming')
- const npmGlobalBin = path.join(appData, 'npm')
- const npmGlobalBinUnix = npmGlobalBin.replace(/\\/g, '/').replace(/^([A-Za-z]):/, '/$1').toLowerCase()
- logger.info(`npm 全局路径: ${npmGlobalBin} -> ${npmGlobalBinUnix}`)
- // 获取 Node.js 路径
- // 常见的 Node.js 安装路径
- const fs = await import('fs')
- const possibleNodePaths = [
- 'C:\\Program Files\\nodejs',
- 'C:\\Program Files (x86)\\nodejs',
- path.join(os.homedir(), 'AppData', 'Local', 'Programs', 'nodejs'),
- // fnm 安装的 Node.js
- path.join(os.homedir(), 'AppData', 'Local', 'fnm_multishells'),
- // nvm-windows 安装的 Node.js
- path.join(os.homedir(), 'AppData', 'Roaming', 'nvm')
- ]
- let nodePath = ''
- for (const p of possibleNodePaths) {
- if (fs.existsSync(p)) {
- // 检查是否有 node.exe
- const nodeExe = path.join(p, 'node.exe')
- if (fs.existsSync(nodeExe)) {
- nodePath = p
- break
- }
- }
- }
- // 如果没找到,尝试从 PATH 环境变量中获取
- if (!nodePath) {
- const pathEnv = process.env.PATH || ''
- const pathDirs = pathEnv.split(';')
- for (const dir of pathDirs) {
- const nodeExe = path.join(dir, 'node.exe')
- if (fs.existsSync(nodeExe)) {
- nodePath = dir
- break
- }
- }
- }
- // 构建额外的 PATH(pnpm 优先,因为 claude 是通过 pnpm 安装的)
- const pathParts: string[] = [pnpmGlobalBinUnix, npmGlobalBinUnix]
- if (nodePath) {
- const nodePathUnix = nodePath.replace(/\\/g, '/').replace(/^([A-Za-z]):/, '/$1').toLowerCase()
- pathParts.unshift(nodePathUnix)
- logger.info(`Node.js 路径: ${nodePath} -> ${nodePathUnix}`)
- } else {
- logger.warn('未找到 Node.js 路径,可能会导致 claude 命令无法运行')
- }
- const extraPaths = pathParts.join(':')
- logger.info(`额外 PATH: ${extraPaths}`)
- // 使用 execa 启动 Git Bash 并执行 claude 命令
- // 先将 Node.js、pnpm 和 npm 全局路径添加到 PATH,然后执行 claude 命令
- const bashCommand = `export PATH="${extraPaths}:$PATH"; claude; exec bash`
- execa(gitBashPath, ['-c', bashCommand], {
- detached: true,
- stdio: 'ignore'
- }).unref()
- return { success: true }
- })
- // 安装 Claude Code(复用 installClaudeCodeWithStatus 函数)
- registerHandler('install-claude-code', async () => {
- return await installClaudeCodeWithStatus()
- })
- // 检查 VS Code 插件是否已安装
- registerHandler('check-vscode-extension', async (_event, extensionId: string) => {
- try {
- const codePath = await getVscodeCliPath()
- logger.info(`使用 VS Code CLI: ${codePath}`)
- // 使用 --show-versions 参数获取插件版本信息
- const result = await execa(codePath, ['--list-extensions', '--show-versions'], {
- encoding: 'utf8',
- stdout: 'pipe',
- stderr: 'pipe'
- })
- // 输出格式为 "extensionId@version",例如 "[email protected]"
- const extensions = result.stdout.split('\n').map(ext => ext.trim())
- const targetExtLower = extensionId.toLowerCase()
- let installed = false
- let version: string | undefined
- for (const ext of extensions) {
- const [extId, extVersion] = ext.split('@')
- if (extId && extId.toLowerCase() === targetExtLower) {
- installed = true
- version = extVersion
- break
- }
- }
- logger.info(`检查 VS Code 插件 ${extensionId}: ${installed ? `已安装 v${version}` : '未安装'}`)
- return { installed, version }
- } catch (error) {
- logger.warn(`检查 VS Code 插件失败: ${extensionId}`, error)
- return { installed: false }
- }
- })
- // 安装 VS Code 插件(复用 installVscodeExtension 函数)
- registerHandler('install-vscode-extension', async (_event, extensionId: string) => {
- return await installVscodeExtension(extensionId)
- })
- // ==================== 文件夹选择 ====================
- // 选择文件夹
- registerHandler('select-directory', async (_event, defaultPath?: string) => {
- const win = BrowserWindow.getFocusedWindow()
- const result = await dialog.showOpenDialog(win!, {
- properties: ['openDirectory'],
- defaultPath: defaultPath || os.homedir()
- })
- if (result.canceled || result.filePaths.length === 0) {
- return { canceled: true, path: null }
- }
- return { canceled: false, path: result.filePaths[0] }
- })
- // ==================== Git 镜像配置 ====================
- // 设置 Git 镜像
- registerHandler('set-git-mirror', (_event, mirror: GitMirrorType) => {
- logger.info(`设置 Git 镜像: ${mirror}`)
- setGitMirror(mirror)
- })
- // 获取 Git 镜像配置
- registerHandler('get-git-mirror-config', () => {
- return getGitMirrorConfig()
- })
- // ==================== Node.js 镜像配置 ====================
- // 设置 Node.js 镜像
- registerHandler('set-nodejs-mirror', (_event, mirror: NodejsMirrorType) => {
- logger.info(`设置 Node.js 镜像: ${mirror}`)
- setNodejsMirror(mirror)
- })
- // 获取 Node.js 镜像配置
- registerHandler('get-nodejs-mirror-config', () => {
- return getNodejsMirrorConfig()
- })
- // ==================== 自动更新相关 ====================
- // 检查应用更新
- registerHandler('updater:check', async () => {
- return await checkForUpdates()
- })
- // 下载更新
- registerHandler('updater:download', async () => {
- return await downloadUpdate()
- })
- // 安装更新并重启
- registerHandler('updater:install', () => {
- installUpdate()
- })
- // 获取当前版本
- registerHandler('updater:version', () => {
- return getCurrentVersion()
- })
- // 检测是否为 Portable 模式
- registerHandler('updater:is-portable', () => {
- return isPortableMode()
- })
- // ==================== 软件安装相关 ====================
- // 统一安装入口
- registerHandler('install', async (_event, software: SoftwareTypeWithAll, options: InstallOptions = {}) => {
- const { version, nodejsPath, vscodePath, gitPath } = options
- const startTime = Date.now()
- // 状态回调
- const onStatus = (sw: SoftwareTypeWithAll, message: string, progress: number, skipLog?: boolean): void => {
- sendToRenderer('install-status', { software: sw, message, progress, skipLog })
- if (!skipLog) {
- logger.installInfo(`[${sw}] ${message} (${progress}%)`)
- }
- }
- try {
- logger.installInfo(`开始安装: ${software}`, options)
- // Linux 下先 apt update
- if (os.platform() === 'linux' && ['nodejs', 'git', 'all'].includes(software)) {
- await aptUpdate(onStatus, software)
- }
- switch (software) {
- case 'nodejs':
- await installNodejs(version, false, onStatus, nodejsPath)
- sendToRenderer('install-complete', {
- software: 'nodejs',
- message: '✅ Node.js 安装完成!',
- i18nKey: 'log.nodejsComplete'
- })
- break
- case 'vscode':
- await installVscode(version, onStatus, vscodePath)
- sendToRenderer('install-complete', {
- software: 'vscode',
- message: '✅ VS Code 安装完成!',
- i18nKey: 'log.vscodeComplete'
- })
- break
- case 'git':
- await installGit(version, onStatus, gitPath)
- sendToRenderer('install-complete', {
- software: 'git',
- message: '✅ Git 安装完成!',
- i18nKey: 'log.gitComplete'
- })
- break
- case 'pnpm':
- await installPnpm(onStatus)
- sendToRenderer('install-complete', {
- software: 'pnpm',
- message: '✅ pnpm 安装完成!',
- i18nKey: 'log.pnpmComplete'
- })
- break
- case 'all': {
- const installed = await installAll(options, onStatus)
- sendToRenderer('install-complete', {
- software: 'all',
- message: `✅ ${installed.join('、')} 安装完成!`,
- i18nKey: 'log.allComplete',
- i18nParams: { software: installed.join(', ') }
- })
- break
- }
- default:
- throw new Error(`${ERROR_MESSAGES.UNKNOWN_SOFTWARE}: ${software}`)
- }
- // 记录安装历史
- const duration = Date.now() - startTime
- addInstallHistory({
- software,
- version: version || 'latest',
- options,
- success: true,
- duration
- })
- } catch (error) {
- const errorMessage = (error as Error).message
- const isCancelled = errorMessage.includes('取消')
- if (!isCancelled) {
- // 注意:dialog.showErrorBox 在主进程中无法使用 i18n,保留中文作为后备
- dialog.showErrorBox('Installation Failed / 安装失败', errorMessage || 'Unknown error / 未知错误')
- }
- sendToRenderer('install-error', {
- software,
- message: isCancelled ? '⚠️ 安装已取消' : `❌ 安装失败:${errorMessage}`,
- i18nKey: isCancelled ? 'log.installCancelled' : 'log.installError',
- i18nParams: isCancelled ? undefined : { error: errorMessage }
- })
- // 记录失败历史
- addInstallHistory({
- software,
- version: version || 'latest',
- options,
- success: false,
- error: errorMessage,
- cancelled: isCancelled
- })
- }
- })
- }
- /**
- * 移除所有 IPC 处理器
- * 使用自动注册模块,无需手动维护 handler 列表
- */
- export { removeAllHandlers as removeHandlers } from './ipc-registry'
|