黄中银 2 недель назад
Родитель
Сommit
89d1e7117c

+ 0 - 171
electron/main.ts

@@ -1,171 +0,0 @@
-// electron/main.ts - Electron 主进程入口
-
-import { app, BrowserWindow, Menu } from 'electron'
-import * as path from 'path'
-import * as os from 'os'
-import { registerHandlers, removeHandlers } from './modules/ipc-handlers'
-import { logger } from './modules/logger'
-import { initConfig, getWindowBounds, saveWindowBounds } from './modules/config'
-import { initAutoUpdater } from './modules/updater'
-
-// 全局窗口实例
-let mainWindow: BrowserWindow | null = null
-
-// 开发环境判断
-const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged
-
-// 根据平台获取图标路径
-function getIconPath(): string {
-  const platform = os.platform()
-  // 开发环境使用 public 目录,生产环境使用打包后的路径
-  const basePath = isDev
-    ? path.join(__dirname, '../public/icons')
-    : path.join(process.resourcesPath, 'icons')
-
-  if (platform === 'win32') {
-    return path.join(basePath, 'win/icon.ico')
-  } else if (platform === 'darwin') {
-    return path.join(basePath, 'mac/icon.icns')
-  } else {
-    return path.join(basePath, 'png/256x256.png')
-  }
-}
-
-/**
- * 创建主窗口
- */
-function createWindow(): void {
-  // 获取保存的窗口位置
-  const savedBounds = getWindowBounds()
-
-  mainWindow = new BrowserWindow({
-    width: savedBounds?.width ?? 1050,
-    height: savedBounds?.height ?? 810,
-    x: savedBounds?.x,
-    y: savedBounds?.y,
-    minWidth: 800,
-    minHeight: 810,
-    resizable: true, // 允许调整大小
-    frame: false, // 无边框窗口
-    titleBarStyle: 'hidden', // 隐藏标题栏
-    icon: getIconPath(),
-    webPreferences: {
-      preload: path.join(__dirname, 'preload.js'),
-      contextIsolation: true,
-      nodeIntegration: false,
-      sandbox: true
-    }
-  })
-
-  // 开发环境加载 Vite 开发服务器
-  if (isDev) {
-    mainWindow.loadURL('http://localhost:5173')
-    mainWindow.webContents.openDevTools({ mode: 'detach' })
-  } else {
-    // 生产环境加载打包后的文件
-    mainWindow.loadFile(path.join(__dirname, '../dist/index.html'))
-  }
-
-  // 窗口移动或调整大小时保存位置
-  mainWindow.on('moved', () => {
-    if (mainWindow) {
-      saveWindowBounds(mainWindow.getBounds())
-    }
-  })
-
-  mainWindow.on('resized', () => {
-    if (mainWindow) {
-      saveWindowBounds(mainWindow.getBounds())
-    }
-  })
-
-  mainWindow.on('closed', () => {
-    mainWindow = null
-  })
-
-  logger.info('主窗口已创建')
-}
-
-/**
- * 应用初始化
- */
-async function initialize(): Promise<void> {
-  // 初始化日志模块
-  logger.init()
-
-  // 写入安装日志,标记程序启动
-  logger.installInfo(`========== 程序启动 ${new Date().toLocaleString()} ==========`)
-
-  logger.info('应用启动', {
-    platform: os.platform(),
-    arch: os.arch(),
-    nodeVersion: process.version,
-    electronVersion: process.versions.electron,
-    isDev
-  })
-
-  // 初始化配置
-  await initConfig()
-
-  // 注册 IPC 处理器
-  registerHandlers()
-
-  // 隐藏菜单栏
-  Menu.setApplicationMenu(null)
-
-  // 创建窗口
-  createWindow()
-
-  // 生产环境初始化自动更新
-  if (!isDev && mainWindow) {
-    initAutoUpdater(mainWindow)
-  }
-
-  // 注册窗口级别的 F12 快捷键切换开发者工具
-  mainWindow?.webContents.on('before-input-event', (event, input) => {
-    if (input.key === 'F12' && input.type === 'keyDown') {
-      if (mainWindow?.webContents.isDevToolsOpened()) {
-        mainWindow.webContents.closeDevTools()
-      } else {
-        mainWindow?.webContents.openDevTools()
-      }
-      event.preventDefault()
-    }
-  })
-}
-
-// 应用就绪
-app.whenReady().then(initialize)
-
-// 所有窗口关闭
-app.on('window-all-closed', () => {
-  logger.info('所有窗口已关闭')
-
-  if (os.platform() !== 'darwin') {
-    app.quit()
-  }
-})
-
-// macOS 激活
-app.on('activate', () => {
-  if (BrowserWindow.getAllWindows().length === 0) {
-    createWindow()
-  }
-})
-
-// 应用退出前
-app.on('before-quit', () => {
-  logger.info('应用即将退出')
-  removeHandlers()
-  logger.close()
-})
-
-// 未捕获的异常
-process.on('uncaughtException', (error) => {
-  logger.error('未捕获的异常', error)
-})
-
-// 未处理的 Promise 拒绝
-process.on('unhandledRejection', (reason, promise) => {
-  logger.error('未处理的 Promise 拒绝', { reason, promise: String(promise) })
-})

+ 0 - 61
electron/modules/claude-code-installer.ts

@@ -1,61 +0,0 @@
-// electron/modules/claude-code-installer.ts - Claude Code 安装模块
-
-import { BrowserWindow } from 'electron'
-import type { SoftwareTypeWithAll } from './types'
-import { installClaudeCode as installClaudeCodeCore } from './installer'
-import logger from './logger'
-
-/**
- * 发送状态到渲染进程
- */
-function sendToRenderer(channel: string, data: unknown): void {
-  const windows = BrowserWindow.getAllWindows()
-  windows.forEach((win) => {
-    win.webContents.send(channel, data)
-  })
-}
-
-/**
- * 安装 Claude Code
- * 供 IPC handler 和 installer.ts 复用
- * @returns 安装结果
- */
-export async function installClaudeCode(): Promise<{ success: boolean; error?: string }> {
-  try {
-    // 状态回调,将状态发送到渲染进程
-    const onStatus = (software: SoftwareTypeWithAll, message: string, progress: number, skipLog?: boolean): void => {
-      sendToRenderer('install-status', { software, message, progress, skipLog })
-      if (!skipLog) {
-        logger.installInfo(`[${software}] ${message} (${progress}%)`)
-      }
-    }
-
-    await installClaudeCodeCore(onStatus)
-
-    sendToRenderer('install-complete', {
-      software: 'claudeCode',
-      message: '✅ Claude Code 安装完成!',
-      i18nKey: 'log.claudeCodeInstallSuccess'
-    })
-    return { success: true }
-  } catch (error) {
-    const execaError = error as { message?: string; stderr?: string; stdout?: string; shortMessage?: string }
-    let errorMessage = execaError.shortMessage || execaError.message || '未知错误'
-
-    // 过滤乱码字符
-    if (execaError.stderr) {
-      const stderrClean = execaError.stderr.replace(/[^\x20-\x7E\u4e00-\u9fa5\n\r]/g, '').trim()
-      if (stderrClean) {
-        errorMessage = `${errorMessage}\n${stderrClean}`
-      }
-    }
-
-    logger.installError('Claude Code 安装失败', error)
-    sendToRenderer('install-error', {
-      software: 'claudeCode',
-      message: `❌ 安装失败:${errorMessage}`,
-      i18nKey: 'log.claudeCodeInstallFailed'
-    })
-    return { success: false, error: errorMessage }
-  }
-}

+ 0 - 182
electron/modules/config.ts

@@ -1,182 +0,0 @@
-// electron/modules/config.ts - 配置管理模块
-
-import * as fs from 'fs/promises'
-import * as fsSync from 'fs'
-import * as path from 'path'
-import { app } from 'electron'
-import type { InstallHistoryItem, GitMirrorType, NodejsMirrorType } from './types'
-import { DEFAULT_GIT_MIRROR, DEFAULT_NODEJS_MIRROR } from './constants'
-import logger from './logger'
-
-interface WindowBounds {
-  x: number
-  y: number
-  width: number
-  height: number
-}
-
-interface AppConfig {
-  installHistory: InstallHistoryItem[]
-  theme: 'light' | 'dark' | 'system'
-  language: 'zh-CN' | 'en-US'
-  windowBounds?: WindowBounds
-  gitMirror: GitMirrorType
-  nodejsMirror: NodejsMirrorType
-}
-
-const DEFAULT_CONFIG: AppConfig = {
-  installHistory: [],
-  theme: 'system',
-  language: 'zh-CN',
-  gitMirror: DEFAULT_GIT_MIRROR,
-  nodejsMirror: DEFAULT_NODEJS_MIRROR
-}
-
-let configPath: string = ''
-let currentConfig: AppConfig = { ...DEFAULT_CONFIG }
-
-/**
- * 初始化配置
- */
-export async function initConfig(): Promise<void> {
-  configPath = path.join(app.getPath('userData'), 'config.json')
-  await loadConfig()
-}
-
-/**
- * 加载配置
- */
-async function loadConfig(): Promise<void> {
-  try {
-    if (fsSync.existsSync(configPath)) {
-      const data = await fs.readFile(configPath, 'utf-8')
-      const loaded = JSON.parse(data) as Partial<AppConfig>
-
-      currentConfig = {
-        ...DEFAULT_CONFIG,
-        ...loaded
-      }
-      logger.info('配置加载成功')
-    }
-  } catch (error) {
-    logger.error('加载配置失败,使用默认配置', error)
-    currentConfig = { ...DEFAULT_CONFIG }
-  }
-}
-
-/**
- * 保存配置(异步,不阻塞主进程)
- */
-function saveConfig(): void {
-  fs.writeFile(configPath, JSON.stringify(currentConfig, null, 2), 'utf-8')
-    .then(() => logger.debug('配置保存成功'))
-    .catch((error) => logger.error('保存配置失败', error))
-}
-
-/**
- * 获取主题配置
- */
-export function getThemeConfig(): 'light' | 'dark' | 'system' {
-  return currentConfig.theme
-}
-
-/**
- * 保存主题配置
- */
-export function saveThemeConfig(theme: 'light' | 'dark' | 'system'): void {
-  currentConfig.theme = theme
-  saveConfig()
-}
-
-/**
- * 获取语言配置
- */
-export function getLanguageConfig(): 'zh-CN' | 'en-US' {
-  return currentConfig.language
-}
-
-/**
- * 保存语言配置
- */
-export function saveLanguageConfig(language: 'zh-CN' | 'en-US'): void {
-  currentConfig.language = language
-  saveConfig()
-}
-
-/**
- * 添加安装历史
- */
-export function addInstallHistory(item: Omit<InstallHistoryItem, 'timestamp'>): void {
-  const historyItem: InstallHistoryItem = {
-    ...item,
-    timestamp: Date.now()
-  }
-  currentConfig.installHistory.unshift(historyItem)
-
-  // 只保留最近 100 条记录
-  if (currentConfig.installHistory.length > 100) {
-    currentConfig.installHistory = currentConfig.installHistory.slice(0, 100)
-  }
-
-  saveConfig()
-}
-
-/**
- * 获取安装历史
- */
-export function getInstallHistory(limit = 20): InstallHistoryItem[] {
-  return currentConfig.installHistory.slice(0, limit)
-}
-
-/**
- * 清除安装历史
- */
-export function clearInstallHistory(): void {
-  currentConfig.installHistory = []
-  saveConfig()
-}
-
-/**
- * 获取窗口位置配置
- */
-export function getWindowBounds(): WindowBounds | undefined {
-  return currentConfig.windowBounds
-}
-
-/**
- * 保存窗口位置配置
- */
-export function saveWindowBounds(bounds: WindowBounds): void {
-  currentConfig.windowBounds = bounds
-  saveConfig()
-}
-
-/**
- * 获取 Git 镜像配置
- */
-export function getGitMirrorFromConfig(): GitMirrorType {
-  return currentConfig.gitMirror
-}
-
-/**
- * 保存 Git 镜像配置
- */
-export function saveGitMirrorConfig(mirror: GitMirrorType): void {
-  currentConfig.gitMirror = mirror
-  saveConfig()
-}
-
-/**
- * 获取 Node.js 镜像配置
- */
-export function getNodejsMirrorFromConfig(): NodejsMirrorType {
-  return currentConfig.nodejsMirror
-}
-
-/**
- * 保存 Node.js 镜像配置
- */
-export function saveNodejsMirrorConfig(mirror: NodejsMirrorType): void {
-  currentConfig.nodejsMirror = mirror
-  saveConfig()
-}

+ 0 - 155
electron/modules/constants.ts

@@ -1,155 +0,0 @@
-// electron/modules/constants.ts - 常量定义
-
-import type { SoftwareType } from './types'
-
-// 支持的最低 Node.js 主版本
-export const MIN_SUPPORTED_NODE_VERSION = 18
-
-// 保留的主版本数量
-export const MAX_MAJOR_VERSIONS = 4
-
-// 网络请求超时时间(毫秒)
-export const REQUEST_TIMEOUT = 15000
-
-// 重试次数
-export const MAX_RETRIES = 3
-
-// 重试延迟(毫秒)
-export const RETRY_DELAY = 1000
-
-// 版本缓存时间(毫秒)- 30分钟(优化后)
-export const VERSION_CACHE_TTL = 30 * 60 * 1000
-
-// 包管理器源更新缓存时间(毫秒)- 1天
-export const SOURCE_UPDATE_CACHE_TTL = 24 * 60 * 60 * 1000
-
-// 需要版本管理的软件类型
-export type VersionedSoftwareType = 'nodejs' | 'vscode' | 'git'
-
-// npm 镜像地址
-export const NPM_REGISTRY = 'https://registry.npmmirror.com'
-
-// 需要版本管理的软件列表
-export const VERSIONED_SOFTWARE_LIST: VersionedSoftwareType[] = ['nodejs', 'vscode', 'git']
-
-// 软件显示名称
-export const SOFTWARE_NAMES: Record<SoftwareType | 'all', string> = {
-  nodejs: 'Node.js',
-  pnpm: 'pnpm',
-  vscode: 'VS Code',
-  git: 'Git',
-  claudeCode: 'Claude Code',
-  all: '一键安装'
-}
-
-// 错误消息
-export const ERROR_MESSAGES = {
-  NETWORK_ERROR: '网络连接失败,请检查网络设置',
-  TIMEOUT_ERROR: '请求超时,请稍后重试',
-  VERSION_FETCH_ERROR: '无法获取版本列表',
-  INSTALL_ERROR: '安装失败',
-  PERMISSION_ERROR: '权限不足,请以管理员身份运行',
-  UNSUPPORTED_PLATFORM: '不支持的操作系统',
-  UNKNOWN_SOFTWARE: '未知的软件',
-  INVALID_URL: '无效的 URL 地址',
-  PACKAGE_MANAGER_NOT_FOUND: '未检测到包管理器',
-  INSTALL_CANCELLED: '安装已被用户取消',
-  UNINSTALL_ERROR: '卸载失败'
-} as const
-
-// 状态消息
-export const STATUS_MESSAGES = {
-  PREPARING: '正在准备安装...',
-  INSTALLING: '正在安装',
-  CONFIGURING: '正在配置',
-  UPDATING_SOURCE: '正在更新软件源 (apt update)...',
-  COMPLETE: '安装完成',
-  READY: '就绪',
-  LOADING: '加载中...',
-  LOAD_FAILED: '加载失败',
-  UNINSTALLING: '正在卸载',
-  CHECKING_UPDATE: '正在检查更新'
-} as const
-
-// brew 包名映射
-export const BREW_PACKAGES = {
-  nodejs: {
-    default: 'node',
-    20: 'node@20',
-    18: 'node@18'
-  },
-  vscode: {
-    stable: 'visual-studio-code',
-    insiders: 'visual-studio-code-insiders'
-  },
-  git: {
-    stable: 'git',
-    lfs: 'git-lfs'
-  }
-} as const
-
-// 应用名称(用于 sudo-prompt)
-export const APP_NAME = 'ApqInstaller'
-
-// 备用版本(当无法获取最新版本时使用)
-export const FALLBACK_VERSIONS = {
-  nodejs: '22.12.0',
-  vscode: '1.96.0',
-  git: '2.47.1'
-} as const
-
-// Git for Windows 镜像配置
-export const GIT_MIRRORS = {
-  // 华为云镜像(默认,国内推荐)
-  huaweicloud: {
-    name: '华为云镜像',
-    // 华为云镜像下载地址格式: https://mirrors.huaweicloud.com/git-for-windows/v{version}.windows.1/Git-{version}-64-bit.exe
-    getDownloadUrl: (version: string, arch: '64' | '32') =>
-      `https://mirrors.huaweicloud.com/git-for-windows/v${version}.windows.1/Git-${version}-${arch}-bit.exe`,
-    // 华为云镜像版本列表 API
-    versionsUrl: 'https://mirrors.huaweicloud.com/git-for-windows/'
-  },
-  // GitHub 官方(需要代理)
-  github: {
-    name: 'GitHub 官方',
-    getDownloadUrl: (version: string, arch: '64' | '32') =>
-      `https://github.com/git-for-windows/git/releases/download/v${version}.windows.1/Git-${version}-${arch}-bit.exe`,
-    // GitHub API 获取版本列表
-    versionsUrl: 'https://api.github.com/repos/git-for-windows/git/releases'
-  }
-} as const
-
-// 默认 Git 镜像
-export const DEFAULT_GIT_MIRROR: 'huaweicloud' | 'github' = 'huaweicloud'
-
-// Node.js 镜像配置
-export const NODEJS_MIRRORS = {
-  // 官方源(默认)
-  official: {
-    name: 'Node.js 官方',
-    // 版本列表 API
-    versionsUrl: 'https://nodejs.org/dist/index.json',
-    // 下载地址格式
-    getDownloadUrl: (version: string, arch: 'x64' | 'x86' | 'arm64') =>
-      `https://nodejs.org/dist/v${version}/node-v${version}-win-${arch}.zip`
-  },
-  // npmmirror 镜像(国内推荐)
-  npmmirror: {
-    name: 'npmmirror 镜像',
-    versionsUrl: 'https://registry.npmmirror.com/-/binary/node/index.json',
-    getDownloadUrl: (version: string, arch: 'x64' | 'x86' | 'arm64') =>
-      `https://cdn.npmmirror.com/binaries/node/v${version}/node-v${version}-win-${arch}.zip`
-  }
-} as const
-
-// 默认 Node.js 镜像
-export const DEFAULT_NODEJS_MIRROR: 'official' | 'npmmirror' = 'npmmirror'
-
-// VS Code 版本 API(官方,国内可访问)
-export const VSCODE_API = {
-  // 版本列表 API
-  versionsUrl: 'https://update.code.visualstudio.com/api/releases/stable',
-  // 下载地址格式
-  getDownloadUrl: (version: string, arch: 'x64' | 'x86' | 'arm64', type: 'user' | 'system' = 'user') =>
-    `https://update.code.visualstudio.com/${version}/win32-${arch}-${type}/stable`
-} as const

+ 0 - 10
electron/modules/index.ts

@@ -1,10 +0,0 @@
-// electron/modules/index.ts - 模块导出
-
-export * from './types'
-export * from './constants'
-export { logger } from './logger'
-export * from './utils'
-export * from './config'
-export * from './version-fetcher'
-export * from './installer'
-export * from './ipc-handlers'

+ 0 - 1207
electron/modules/installer.ts

@@ -1,1207 +0,0 @@
-// electron/modules/installer.ts - 安装逻辑模块
-
-import * as os from 'os'
-import * as path from 'path'
-import * as fs from 'fs'
-import { execa, type ResultPromise } from 'execa'
-import * as sudo from 'sudo-prompt'
-import type { SoftwareType, SoftwareTypeWithAll, InstallOptions, CommandResult, InstalledInfo, AllInstalledInfo, Platform } from './types'
-import {
-  NPM_REGISTRY,
-  ERROR_MESSAGES,
-  STATUS_MESSAGES,
-  BREW_PACKAGES,
-  APP_NAME,
-  FALLBACK_VERSIONS
-} from './constants'
-import { commandExistsWithRefresh, getCommandVersionWithRefresh, downloadFile, getTempDir, cancelCurrentDownload } from './utils'
-import { installVscodeExtension } from './vscode-extension'
-import { needsAptUpdate, markAptUpdated } from './source-updater'
-import { getGitDownloadUrl, getNodejsDownloadUrl, getVSCodeDownloadUrl, getNodejsMirrorConfig } from './version-fetcher'
-import logger from './logger'
-
-// 当前安装进程
-let currentProcess: ResultPromise | null = null
-let installCancelled = false
-
-
-/**
- * 取消当前安装
- */
-export function cancelInstall(): boolean {
-  installCancelled = true
-  let cancelled = false
-
-  // 取消正在进行的下载
-  if (cancelCurrentDownload()) {
-    logger.installInfo('下载已取消')
-    cancelled = true
-  }
-
-  // 取消正在运行的进程
-  if (currentProcess) {
-    try {
-      currentProcess.kill('SIGTERM')
-      logger.installInfo('安装进程已取消')
-      cancelled = true
-    } catch (error) {
-      logger.installError('取消安装进程失败', error)
-    }
-  }
-
-  if (cancelled) {
-    logger.installInfo('安装已取消')
-  }
-
-  return cancelled
-}
-
-/**
- * 重置取消状态
- */
-function resetCancelState(): void {
-  installCancelled = false
-  currentProcess = null
-}
-
-/**
- * 获取 npm 的完整路径
- * Windows: 安装后 PATH 环境变量不会立即对当前进程生效,需要使用完整路径
- * macOS/Linux: 通常不需要,但为了健壮性也提供完整路径
- */
-function getNpmPath(): string {
-  const platform = os.platform() as Platform
-  switch (platform) {
-    case 'win32': {
-      const programFiles = process.env.ProgramFiles || 'C:\\Program Files'
-      return path.join(programFiles, 'nodejs', 'npm.cmd')
-    }
-    case 'darwin': {
-      // Homebrew 在 Apple Silicon 上安装到 /opt/homebrew,Intel 上安装到 /usr/local
-      const arch = os.arch()
-      if (arch === 'arm64') {
-        return '/opt/homebrew/bin/npm'
-      }
-      return '/usr/local/bin/npm'
-    }
-    case 'linux':
-      return '/usr/bin/npm'
-    default:
-      return 'npm'
-  }
-}
-
-/**
- * 获取 pnpm 的完整路径
- */
-function getPnpmPath(): string {
-  const platform = os.platform() as Platform
-  switch (platform) {
-    case 'win32': {
-      const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming')
-      return path.join(appData, 'npm', 'pnpm.cmd')
-    }
-    case 'darwin': {
-      const arch = os.arch()
-      if (arch === 'arm64') {
-        return '/opt/homebrew/bin/pnpm'
-      }
-      return '/usr/local/bin/pnpm'
-    }
-    case 'linux':
-      return '/usr/local/bin/pnpm'
-    default:
-      return 'pnpm'
-  }
-}
-
-/**
- * 检查是否已取消
- */
-function checkCancelled(): void {
-  if (installCancelled) {
-    throw new Error(ERROR_MESSAGES.INSTALL_CANCELLED)
-  }
-}
-
-/**
- * 使用 sudo-prompt 执行需要管理员权限的命令
- */
-function sudoExec(command: string): Promise<{ stdout: string; stderr: string }> {
-  return new Promise((resolve, reject) => {
-    const options = { name: APP_NAME }
-    sudo.exec(command, options, (error, stdout, stderr) => {
-      // 过滤乱码字符的辅助函数(Windows 命令行输出可能是 GBK 编码)
-      const cleanOutput = (str: string | Buffer | undefined): string => {
-        if (!str) return ''
-        const raw = str.toString()
-        // 过滤掉非 ASCII 可打印字符和非中文字符,保留换行符
-        return raw.replace(/[^\x20-\x7E\u4e00-\u9fa5\n\r]/g, '').trim()
-      }
-
-      if (error) {
-        // 包含 stderr 和 stdout 信息以便调试
-        const exitCode = (error as Error & { code?: number }).code
-        const stderrStr = cleanOutput(stderr)
-        const stdoutStr = cleanOutput(stdout)
-        let errorMessage = error.message
-        if (exitCode !== undefined) {
-          errorMessage += `\n退出码: ${exitCode}`
-        }
-        if (stderrStr) {
-          errorMessage += `\n详细信息: ${stderrStr}`
-        }
-        if (stdoutStr) {
-          errorMessage += `\n输出: ${stdoutStr}`
-        }
-        const enhancedError = new Error(errorMessage)
-        reject(enhancedError)
-      } else {
-        resolve({
-          stdout: cleanOutput(stdout),
-          stderr: cleanOutput(stderr)
-        })
-      }
-    })
-  })
-}
-
-/**
- * 为包含空格的路径添加引号(Windows)
- */
-function quoteIfNeeded(str: string): string {
-  // 如果字符串包含空格且没有被引号包围,则添加引号
-  if (str.includes(' ') && !str.startsWith('"') && !str.endsWith('"')) {
-    return `"${str}"`
-  }
-  return str
-}
-
-/**
- * 跨平台执行系统命令(带权限处理)
- */
-async function executeCommand(
-  command: string,
-  args: string[] = [],
-  needAdmin = false
-): Promise<{ stdout: string; stderr: string }> {
-  checkCancelled()
-
-  const platform = os.platform() as Platform
-
-  // 构建完整命令时,对包含空格的部分添加引号
-  const quotedCommand = quoteIfNeeded(command)
-  const quotedArgs = args.map(arg => quoteIfNeeded(arg))
-  const fullCommand = `${quotedCommand} ${quotedArgs.join(' ')}`
-
-  logger.installDebug(`执行命令: ${fullCommand}`)
-
-  try {
-    if (needAdmin) {
-      // 使用 sudo-prompt 执行需要管理员权限的命令
-      if (platform === 'win32') {
-        // Windows: 直接执行命令,sudo-prompt 会处理 UAC
-        return await sudoExec(fullCommand)
-      } else {
-        // Linux/macOS: 使用 sudo
-        return await sudoExec(fullCommand)
-      }
-    } else {
-      // 无需提权,直接执行
-      // Windows 上执行 .cmd 文件时,使用 shell: true 避免参数解析问题
-      const useShell = platform === 'win32' && command.endsWith('.cmd')
-      const proc = execa(command, args, { stdio: 'pipe', shell: useShell })
-      currentProcess = proc
-      const result = await proc
-      currentProcess = null
-      return { stdout: result.stdout, stderr: result.stderr }
-    }
-  } catch (error) {
-    logger.installError(`命令执行失败: ${fullCommand}`, error)
-    throw error
-  }
-}
-
-/**
- * 检测软件是否已安装
- * Windows: 安装后 PATH 环境变量不会立即对当前进程生效,
- * 所以使用 commandExistsWithRefresh 从注册表重新读取最新的 PATH
- */
-export async function checkInstalled(software: SoftwareType): Promise<InstalledInfo> {
-  let command: string
-  let versionArgs: string[]
-
-  switch (software) {
-    case 'nodejs':
-      command = 'node'
-      versionArgs = ['--version']
-      break
-    case 'pnpm':
-      command = 'pnpm'
-      versionArgs = ['--version']
-      break
-    case 'vscode':
-      command = 'code'
-      versionArgs = ['--version']
-      break
-    case 'git':
-      command = 'git'
-      versionArgs = ['--version']
-      break
-    case 'claudeCode':
-      command = 'claude'
-      versionArgs = ['--version']
-      break
-    default:
-      return { installed: false, version: null }
-  }
-
-  // 使用刷新后的 PATH 检测命令是否存在(Windows 上会从注册表重新读取 PATH)
-  const exists = await commandExistsWithRefresh(command)
-  if (!exists) {
-    return { installed: false, version: null }
-  }
-
-  // 使用刷新后的 PATH 获取版本
-  const version = await getCommandVersionWithRefresh(command, versionArgs)
-  return { installed: true, version }
-}
-
-/**
- * 检测所有软件的安装状态
- */
-export async function checkAllInstalled(): Promise<AllInstalledInfo> {
-  const results = await Promise.allSettled([
-    checkInstalled('nodejs'),
-    checkInstalled('pnpm'),
-    checkInstalled('vscode'),
-    checkInstalled('git'),
-    checkInstalled('claudeCode')
-  ])
-
-  return {
-    nodejs: results[0].status === 'fulfilled' ? results[0].value : { installed: false, version: null },
-    pnpm: results[1].status === 'fulfilled' ? results[1].value : { installed: false, version: null },
-    vscode: results[2].status === 'fulfilled' ? results[2].value : { installed: false, version: null },
-    git: results[3].status === 'fulfilled' ? results[3].value : { installed: false, version: null },
-    claudeCode: results[4].status === 'fulfilled' ? results[4].value : { installed: false, version: null }
-  }
-}
-
-// ==================== 安装命令生成 ====================
-
-/**
- * 获取 Node.js 安装命令 (macOS/Linux 使用包管理器)
- * Windows 使用下载 msi 安装包方式,不使用此函数
- * @param version 版本号
- */
-function getNodeInstallArgs(version = 'lts'): CommandResult {
-  const platform = os.platform() as Platform
-  const major = version.split('.')[0]
-  const majorNum = parseInt(major)
-
-  let brewPkg: string
-
-  if (majorNum === 20) {
-    brewPkg = BREW_PACKAGES.nodejs['20']
-  } else if (majorNum === 18) {
-    brewPkg = BREW_PACKAGES.nodejs['18']
-  } else {
-    brewPkg = BREW_PACKAGES.nodejs.default
-  }
-
-  switch (platform) {
-    case 'darwin':
-      return { command: 'brew', args: ['install', brewPkg] }
-    case 'linux':
-      return { command: 'apt', args: ['install', '-y', 'nodejs', 'npm'] }
-    default:
-      throw new Error(`${ERROR_MESSAGES.UNSUPPORTED_PLATFORM}: ${platform}`)
-  }
-}
-
-/**
- * 获取 VS Code 安装命令 (macOS/Linux 使用包管理器)
- * Windows 使用下载安装包方式,不使用此函数(除了 insiders 版本)
- * @param version 版本号
- */
-function getVSCodeInstallArgs(version = 'stable'): CommandResult {
-  const platform = os.platform() as Platform
-
-  if (version === 'insiders') {
-    switch (platform) {
-      case 'darwin':
-        return {
-          command: 'brew',
-          args: ['install', '--cask', BREW_PACKAGES.vscode.insiders]
-        }
-      case 'linux':
-        return { command: 'snap', args: ['install', 'code', '--channel=insiders'] }
-      default:
-        throw new Error(`${ERROR_MESSAGES.UNSUPPORTED_PLATFORM}: ${platform}`)
-    }
-  }
-
-  switch (platform) {
-    case 'darwin':
-      return {
-        command: 'brew',
-        args: ['install', '--cask', BREW_PACKAGES.vscode.stable]
-      }
-    case 'linux':
-      return { command: 'snap', args: ['install', 'code', '--classic'] }
-    default:
-      throw new Error(`${ERROR_MESSAGES.UNSUPPORTED_PLATFORM}: ${platform}`)
-  }
-}
-
-/**
- * 获取 Git 安装命令 (macOS/Linux 使用包管理器)
- * Windows 使用下载安装包方式,不使用此函数
- * @param version 版本号
- */
-function getGitInstallArgs(version = 'stable'): CommandResult {
-  const platform = os.platform() as Platform
-
-  if (version === 'mingit') {
-    switch (platform) {
-      case 'darwin':
-        return { command: 'brew', args: ['install', BREW_PACKAGES.git.stable] }
-      case 'linux':
-        return { command: 'apt', args: ['install', '-y', 'git'] }
-      default:
-        throw new Error(`${ERROR_MESSAGES.UNSUPPORTED_PLATFORM}: ${platform}`)
-    }
-  }
-
-  if (version === 'lfs') {
-    switch (platform) {
-      case 'darwin':
-        return { command: 'brew', args: ['install', BREW_PACKAGES.git.lfs] }
-      case 'linux':
-        return { command: 'apt', args: ['install', '-y', 'git-lfs'] }
-      default:
-        throw new Error(`${ERROR_MESSAGES.UNSUPPORTED_PLATFORM}: ${platform}`)
-    }
-  }
-
-  switch (platform) {
-    case 'darwin':
-      return { command: 'brew', args: ['install', BREW_PACKAGES.git.stable] }
-    case 'linux':
-      return { command: 'apt', args: ['install', '-y', 'git'] }
-    default:
-      throw new Error(`${ERROR_MESSAGES.UNSUPPORTED_PLATFORM}: ${platform}`)
-  }
-}
-
-// 可卸载的软件类型
-type UninstallableSoftwareType = 'nodejs' | 'vscode' | 'git'
-
-/**
- * 获取卸载命令 (macOS/Linux 使用包管理器)
- * Windows 暂不支持通过此工具卸载,请使用系统设置
- */
-function getUninstallArgs(software: UninstallableSoftwareType): CommandResult {
-  const platform = os.platform() as Platform
-
-  switch (platform) {
-    case 'win32':
-      // Windows 暂不支持通过此工具卸载,请使用系统设置中的"应用和功能"
-      throw new Error('Windows 暂不支持通过此工具卸载软件,请使用系统设置中的"应用和功能"')
-    case 'darwin': {
-      let brewPkg: string
-      switch (software) {
-        case 'nodejs':
-          brewPkg = BREW_PACKAGES.nodejs.default
-          break
-        case 'vscode':
-          brewPkg = BREW_PACKAGES.vscode.stable
-          break
-        case 'git':
-          brewPkg = BREW_PACKAGES.git.stable
-          break
-      }
-      return { command: 'brew', args: ['uninstall', brewPkg] }
-    }
-    case 'linux': {
-      let aptPkg: string
-      switch (software) {
-        case 'nodejs':
-          aptPkg = 'nodejs'
-          break
-        case 'vscode':
-          return { command: 'snap', args: ['remove', 'code'] }
-        case 'git':
-          aptPkg = 'git'
-          break
-      }
-      return { command: 'apt', args: ['remove', '-y', aptPkg] }
-    }
-    default:
-      throw new Error(`${ERROR_MESSAGES.UNSUPPORTED_PLATFORM}: ${platform}`)
-  }
-}
-
-// ==================== 安装流程 ====================
-
-export type StatusCallback = (software: SoftwareTypeWithAll, message: string, progress: number, skipLog?: boolean) => void
-export type CompleteCallback = (software: SoftwareTypeWithAll, message: string) => void
-export type ErrorCallback = (software: SoftwareTypeWithAll, message: string) => void
-
-/**
- * Linux 下执行 apt update(带缓存,1天内只更新一次)
- */
-export async function aptUpdate(onStatus?: StatusCallback, software?: SoftwareTypeWithAll): Promise<void> {
-  if (os.platform() !== 'linux') return
-  if (!needsAptUpdate()) {
-    logger.installInfo('apt 源已在缓存期内,跳过更新')
-    return
-  }
-
-  if (onStatus && software) {
-    onStatus(software, STATUS_MESSAGES.UPDATING_SOURCE, 10)
-  }
-  await executeCommand('apt', ['update'], true)
-  markAptUpdated()
-}
-
-/**
- * 安装 Node.js (Windows 使用 msi 安装包)
- * @param version 版本号
- * @param installPnpm 是否安装 pnpm
- * @param onStatus 状态回调
- * @param customPath 自定义安装路径 (仅 Windows 支持)
- */
-export async function installNodejs(
-  version = 'lts',
-  installPnpm = true,
-  onStatus: StatusCallback,
-  customPath?: string
-): Promise<void> {
-  resetCancelState()
-
-  const platform = os.platform() as Platform
-
-  // Windows: 直接下载 msi 安装包
-  if (platform === 'win32') {
-    // 确定目标版本
-    let targetVersion = version
-    if (version === 'lts' || !version || !/^\d+\.\d+\.\d+$/.test(version)) {
-      // 如果是 lts 或无效版本,使用备用版本
-      targetVersion = FALLBACK_VERSIONS.nodejs
-    }
-
-    checkCancelled()
-
-    // 下载 msi 安装包
-    const arch = os.arch() === 'x64' ? 'x64' : os.arch() === 'arm64' ? 'arm64' : 'x86'
-    const downloadUrl = getNodejsDownloadUrl(targetVersion).replace('.zip', '.msi').replace(`-win-${arch}`, `-${arch}`)
-    const tempDir = getTempDir()
-    const installerPath = path.join(tempDir, `node-v${targetVersion}-${arch}.msi`)
-
-    onStatus('nodejs', `正在下载 Node.js ${targetVersion}...`, 10)
-    logger.installInfo(`开始下载 Node.js: ${downloadUrl}`)
-
-    try {
-      let lastLoggedPercent = 0
-      await downloadFile(downloadUrl, installerPath, (downloaded, total, percent) => {
-        const progress = 10 + Math.round(percent * 0.4)
-        const downloadedMB = (downloaded / 1024 / 1024).toFixed(1)
-        const totalMB = (total / 1024 / 1024).toFixed(1)
-        // 每次都更新进度条,但只在每 5% 时记录日志
-        const shouldLog = percent - lastLoggedPercent >= 5
-        if (shouldLog) {
-          lastLoggedPercent = Math.floor(percent / 5) * 5
-        }
-        onStatus('nodejs', `正在下载 Node.js ${targetVersion}... (${downloadedMB}MB / ${totalMB}MB)`, progress, !shouldLog)
-      })
-    } catch (error) {
-      logger.installError('下载 Node.js 失败', error)
-      throw new Error(`下载 Node.js 失败: ${(error as Error).message}`)
-    }
-
-    checkCancelled()
-
-    // 执行 msi 静默安装
-    onStatus('nodejs', `${STATUS_MESSAGES.INSTALLING} Node.js ${targetVersion}...`, 55)
-    logger.installInfo(`开始安装 Node.js: ${installerPath}`)
-
-    try {
-      // msiexec 静默安装参数
-      const installArgs = ['/i', installerPath, '/qn', '/norestart']
-      if (customPath) {
-        installArgs.push(`INSTALLDIR="${customPath}"`)
-      }
-
-      await sudoExec(`msiexec ${installArgs.join(' ')}`)
-
-      // 清理安装包
-      try {
-        await fs.promises.unlink(installerPath)
-      } catch {
-        // 忽略清理失败
-      }
-    } catch (error) {
-      logger.installError('安装 Node.js 失败', error)
-      throw error
-    }
-
-    checkCancelled()
-
-    // 使用完整路径,避免 PATH 未生效的问题
-    const npmCmd = getNpmPath()
-
-    // 仅当使用国内镜像下载时,才配置 npm 国内镜像
-    const { mirror: nodejsMirror } = getNodejsMirrorConfig()
-    if (nodejsMirror === 'npmmirror') {
-      const npmConfigCmd = `${npmCmd} config set registry ${NPM_REGISTRY}`
-      onStatus('nodejs', `${STATUS_MESSAGES.CONFIGURING} npm 镜像: ${npmConfigCmd}`, 70)
-      try {
-        await executeCommand(npmCmd, ['config', 'set', 'registry', NPM_REGISTRY], false)
-      } catch (error) {
-        logger.installWarn('配置 npm 镜像失败(npm 可能还未加入 PATH)', error)
-      }
-    }
-
-    checkCancelled()
-
-    // 可选安装 pnpm
-    if (installPnpm) {
-      onStatus('nodejs', `${STATUS_MESSAGES.INSTALLING} pnpm...`, 80)
-      await executeCommand(npmCmd, ['install', '-g', 'pnpm'], true)
-
-      checkCancelled()
-
-      // 获取刷新后的 PATH,确保能找到刚安装的 pnpm
-      const { getRefreshedPath } = await import('./utils')
-      const refreshedPath = await getRefreshedPath()
-      const execEnv = { ...process.env, PATH: refreshedPath }
-
-      // 运行 pnpm setup 配置全局 bin 目录
-      onStatus('nodejs', `${STATUS_MESSAGES.CONFIGURING} pnpm 全局目录...`, 88)
-      try {
-        await execa('pnpm', ['setup'], { env: execEnv, shell: platform === 'win32' })
-        logger.installInfo('pnpm setup 完成')
-      } catch (error) {
-        logger.installWarn('pnpm setup 失败', error)
-      }
-
-      checkCancelled()
-
-      // 配置 pnpm 镜像
-      onStatus('nodejs', `${STATUS_MESSAGES.CONFIGURING} pnpm 镜像...`, 95)
-      try {
-        await execa('pnpm', ['config', 'set', 'registry', NPM_REGISTRY], { env: execEnv, shell: platform === 'win32' })
-      } catch (error) {
-        logger.installWarn('配置 pnpm 镜像失败', error)
-      }
-    }
-
-    onStatus('nodejs', STATUS_MESSAGES.COMPLETE, 100)
-    return
-  }
-
-  // macOS/Linux: 使用包管理器安装
-  onStatus('nodejs', `${STATUS_MESSAGES.INSTALLING} Node.js...`, 20)
-  const args = getNodeInstallArgs(version)
-  await executeCommand(args.command, args.args, true)
-
-  checkCancelled()
-
-  // 使用完整路径,避免 PATH 未生效的问题
-  const npmCmd = getNpmPath()
-
-  // 仅当使用国内镜像下载时,才配置 npm 国内镜像
-  const { mirror: nodejsMirrorMac } = getNodejsMirrorConfig()
-  if (nodejsMirrorMac === 'npmmirror') {
-    const npmConfigCmd = `${npmCmd} config set registry ${NPM_REGISTRY}`
-    onStatus('nodejs', `${STATUS_MESSAGES.CONFIGURING} npm 镜像: ${npmConfigCmd}`, 50)
-    try {
-      await executeCommand(npmCmd, ['config', 'set', 'registry', NPM_REGISTRY], false)
-    } catch (error) {
-      logger.installWarn('配置 npm 镜像失败(npm 可能还未加入 PATH)', error)
-    }
-  }
-
-  checkCancelled()
-
-  // 可选安装 pnpm
-  if (installPnpm) {
-    onStatus('nodejs', `${STATUS_MESSAGES.INSTALLING} pnpm...`, 70)
-    await executeCommand(npmCmd, ['install', '-g', 'pnpm'], true)
-
-    checkCancelled()
-
-    // 获取刷新后的 PATH,确保能找到刚安装的 pnpm
-    const { getRefreshedPath } = await import('./utils')
-    const refreshedPath = await getRefreshedPath()
-    const execEnv = { ...process.env, PATH: refreshedPath }
-
-    // 运行 pnpm setup 配置全局 bin 目录
-    onStatus('nodejs', `${STATUS_MESSAGES.CONFIGURING} pnpm 全局目录...`, 82)
-    try {
-      await execa('pnpm', ['setup'], { env: execEnv })
-      logger.installInfo('pnpm setup 完成')
-    } catch (error) {
-      logger.installWarn('pnpm setup 失败', error)
-    }
-
-    checkCancelled()
-
-    // 配置 pnpm 镜像
-    onStatus('nodejs', `${STATUS_MESSAGES.CONFIGURING} pnpm 镜像...`, 90)
-    try {
-      await execa('pnpm', ['config', 'set', 'registry', NPM_REGISTRY], { env: execEnv })
-    } catch (error) {
-      logger.installWarn('配置 pnpm 镜像失败', error)
-    }
-  }
-
-  onStatus('nodejs', STATUS_MESSAGES.COMPLETE, 100)
-}
-
-/**
- * 单独安装 pnpm (需要 Node.js 已安装)
- * @param onStatus 状态回调
- */
-export async function installPnpm(onStatus: StatusCallback): Promise<void> {
-  resetCancelState()
-
-  const platform = os.platform() as Platform
-
-  // 检查 Node.js 是否已安装
-  const nodeInstalled = await checkInstalled('nodejs')
-  if (!nodeInstalled.installed) {
-    throw new Error('请先安装 Node.js')
-  }
-
-  // 刷新系统环境变量,确保能找到刚安装的 Node.js
-  const { getRefreshedPath } = await import('./utils')
-  await getRefreshedPath()
-
-  // 使用完整路径,避免 PATH 未生效的问题
-  const npmCmd = getNpmPath()
-
-  // 检查 npm 命令是否存在
-  if (platform === 'win32' && !fs.existsSync(npmCmd)) {
-    throw new Error(`未找到 npm 命令: ${npmCmd},请确保 Node.js 已正确安装`)
-  }
-
-  checkCancelled()
-
-  // 安装 pnpm,需要管理员权限进行全局安装
-  const installCommand = `${npmCmd} install -g pnpm`
-  onStatus('pnpm', `正在安装 pnpm...`, 20)
-  onStatus('pnpm', `执行命令: ${installCommand}`, 25)
-  try {
-    await executeCommand(npmCmd, ['install', '-g', 'pnpm'], true)
-  } catch (error) {
-    // 提取更有意义的错误信息,过滤乱码
-    const execaError = error as { message?: string; stderr?: string; stdout?: string; shortMessage?: string; exitCode?: number }
-    let errorMessage = `npm install -g pnpm 失败`
-    if (execaError.exitCode !== undefined) {
-      errorMessage += ` (退出码: ${execaError.exitCode})`
-    }
-    // 过滤乱码字符,只保留可读字符
-    if (execaError.stderr) {
-      const stderrClean = execaError.stderr.replace(/[^\x20-\x7E\u4e00-\u9fa5\n\r]/g, '').trim()
-      if (stderrClean) {
-        errorMessage += `\n${stderrClean}`
-      }
-    }
-    if (execaError.stdout) {
-      const stdoutClean = execaError.stdout.replace(/[^\x20-\x7E\u4e00-\u9fa5\n\r]/g, '').trim()
-      if (stdoutClean) {
-        errorMessage += `\n${stdoutClean}`
-      }
-    }
-    logger.installError('安装 pnpm 失败', error)
-    throw new Error(errorMessage)
-  }
-
-  checkCancelled()
-
-  // 重新刷新 PATH,确保能找到刚安装的 pnpm
-  const pnpmRefreshedPath = await getRefreshedPath()
-
-  // 使用 pnpm 的完整路径,因为刚安装的 pnpm 可能还不在 PATH 中
-  const pnpmCmd = getPnpmPath()
-
-  // 构建环境变量,将 pnpm 所在目录添加到 PATH 开头
-  const pnpmDir = path.dirname(pnpmCmd)
-  const execEnv = { ...process.env, PATH: `${pnpmDir}${platform === 'win32' ? ';' : ':'}${pnpmRefreshedPath}` }
-
-  // 运行 pnpm setup 配置全局 bin 目录
-  onStatus('pnpm', `${STATUS_MESSAGES.CONFIGURING} pnpm 全局目录...`, 60)
-  try {
-    await execa(pnpmCmd, ['setup'], { env: execEnv, shell: platform === 'win32' })
-    logger.installInfo('pnpm setup 完成')
-  } catch (error) {
-    logger.installWarn('pnpm setup 失败', error)
-  }
-
-  checkCancelled()
-
-  // 配置 pnpm 镜像
-  onStatus('pnpm', `${STATUS_MESSAGES.CONFIGURING} pnpm 镜像...`, 80)
-  try {
-    await execa(pnpmCmd, ['config', 'set', 'registry', NPM_REGISTRY], { env: execEnv, shell: platform === 'win32' })
-  } catch (error) {
-    logger.installWarn('配置 pnpm 镜像失败', error)
-  }
-
-  onStatus('pnpm', STATUS_MESSAGES.COMPLETE, 100)
-}
-
-/**
- * 安装 VS Code (Windows 使用下载安装包)
- * @param version 版本号
- * @param onStatus 状态回调
- * @param customPath 自定义安装路径 (仅 Windows 支持)
- */
-export async function installVscode(version = 'stable', onStatus: StatusCallback, customPath?: string): Promise<void> {
-  resetCancelState()
-
-  const platform = os.platform() as Platform
-
-  // Windows: 直接下载安装包
-  if (platform === 'win32' && version !== 'insiders') {
-    // 确定目标版本
-    let targetVersion = version
-    if (version === 'stable' || !version || !/^\d+\.\d+\.\d+$/.test(version)) {
-      // 如果是 stable 或无效版本,使用备用版本
-      targetVersion = FALLBACK_VERSIONS.vscode
-    }
-
-    checkCancelled()
-
-    // 下载安装包
-    const downloadUrl = getVSCodeDownloadUrl(targetVersion)
-    const tempDir = getTempDir()
-    const installerPath = path.join(tempDir, `VSCodeUserSetup-${targetVersion}.exe`)
-
-    onStatus('vscode', `正在下载 VS Code ${targetVersion}...`, 10)
-    logger.installInfo(`开始下载 VS Code: ${downloadUrl}`)
-
-    try {
-      let lastLoggedPercent = 0
-      await downloadFile(downloadUrl, installerPath, (downloaded, total, percent) => {
-        const progress = 10 + Math.round(percent * 0.5)
-        const downloadedMB = (downloaded / 1024 / 1024).toFixed(1)
-        const totalMB = (total / 1024 / 1024).toFixed(1)
-        // 每次都更新进度条,但只在每 5% 时记录日志
-        const shouldLog = percent - lastLoggedPercent >= 5
-        if (shouldLog) {
-          lastLoggedPercent = Math.floor(percent / 5) * 5
-        }
-        onStatus('vscode', `正在下载 VS Code ${targetVersion}... (${downloadedMB}MB / ${totalMB}MB)`, progress, !shouldLog)
-      })
-    } catch (error) {
-      logger.installError('下载 VS Code 失败', error)
-      throw new Error(`下载 VS Code 失败: ${(error as Error).message}`)
-    }
-
-    checkCancelled()
-
-    // 执行静默安装
-    onStatus('vscode', `${STATUS_MESSAGES.INSTALLING} VS Code ${targetVersion}...`, 65)
-    logger.installInfo(`开始安装 VS Code: ${installerPath}`)
-
-    try {
-      // VS Code 安装程序支持的静默安装参数
-      // /VERYSILENT: 完全静默安装
-      // /NORESTART: 不重启
-      // /MERGETASKS: 指定安装任务
-      const installArgs = ['/VERYSILENT', '/NORESTART', '/MERGETASKS=!runcode,addcontextmenufiles,addcontextmenufolders,associatewithfiles,addtopath']
-      if (customPath) {
-        installArgs.push(`/DIR=${customPath}`)
-      }
-
-      await sudoExec(`"${installerPath}" ${installArgs.join(' ')}`)
-
-      // 清理安装包
-      try {
-        await fs.promises.unlink(installerPath)
-      } catch {
-        // 忽略清理失败
-      }
-
-      onStatus('vscode', STATUS_MESSAGES.COMPLETE, 100)
-    } catch (error) {
-      logger.installError('安装 VS Code 失败', error)
-      throw error
-    }
-
-    return
-  }
-
-  // macOS/Linux 或 insiders 版本: 使用包管理器安装
-  onStatus('vscode', `${STATUS_MESSAGES.INSTALLING} VS Code...`, 30)
-  const args = getVSCodeInstallArgs(version)
-  await executeCommand(args.command, args.args, true)
-  onStatus('vscode', STATUS_MESSAGES.COMPLETE, 100)
-}
-
-/**
- * 安装 Git (Windows 直接下载安装)
- * @param version 版本号
- * @param onStatus 状态回调
- * @param customPath 自定义安装路径 (仅 Windows 支持)
- */
-export async function installGit(version = 'stable', onStatus: StatusCallback, customPath?: string): Promise<void> {
-  resetCancelState()
-
-  const platform = os.platform() as Platform
-
-  // Windows: 直接从镜像下载安装
-  if (platform === 'win32' && version !== 'mingit' && version !== 'lfs') {
-    // 如果是 stable,尝试获取最新版本号
-    let targetVersion = version
-    if (version === 'stable') {
-      onStatus('git', '正在获取最新版本...', 5)
-      try {
-        const response = await fetch('https://api.github.com/repos/git-for-windows/git/releases/latest', {
-          headers: {
-            'Accept': 'application/vnd.github.v3+json',
-            'User-Agent': 'ApqInstaller'
-          },
-          signal: AbortSignal.timeout(5000) // 5秒超时
-        })
-        if (response.ok) {
-          const release = await response.json() as { tag_name: string }
-          const match = release.tag_name.match(/^v?(\d+\.\d+\.\d+)/)
-          if (match) {
-            targetVersion = match[1]
-          }
-        }
-      } catch (error) {
-        logger.installWarn('获取最新 Git 版本失败,使用备用版本', error)
-        // 使用备用版本
-        targetVersion = FALLBACK_VERSIONS.git
-      }
-    }
-
-    checkCancelled()
-
-    // 下载安装包
-    const downloadUrl = getGitDownloadUrl(targetVersion)
-    const tempDir = getTempDir()
-    const installerPath = path.join(tempDir, `Git-${targetVersion}-installer.exe`)
-
-    onStatus('git', `正在下载 Git ${targetVersion}...`, 10)
-    logger.installInfo(`开始下载 Git: ${downloadUrl}`)
-
-    try {
-      let lastLoggedPercent = 0
-      await downloadFile(downloadUrl, installerPath, (downloaded, total, percent) => {
-        // 下载进度占 10% - 60%
-        const progress = 10 + Math.round(percent * 0.5)
-        const downloadedMB = (downloaded / 1024 / 1024).toFixed(1)
-        const totalMB = (total / 1024 / 1024).toFixed(1)
-        // 每次都更新进度条,但只在每 5% 时记录日志
-        const shouldLog = percent - lastLoggedPercent >= 5
-        if (shouldLog) {
-          lastLoggedPercent = Math.floor(percent / 5) * 5
-        }
-        onStatus('git', `正在下载 Git ${targetVersion}... (${downloadedMB}MB / ${totalMB}MB)`, progress, !shouldLog)
-      })
-    } catch (error) {
-      logger.installError('下载 Git 失败', error)
-      throw new Error(`下载 Git 失败: ${(error as Error).message}`)
-    }
-
-    checkCancelled()
-
-    // 执行静默安装
-    onStatus('git', `${STATUS_MESSAGES.INSTALLING} Git ${targetVersion}...`, 65)
-    logger.installInfo(`开始安装 Git: ${installerPath}`)
-
-    try {
-      // Git 安装程序支持的静默安装参数
-      // /VERYSILENT: 完全静默安装
-      // /NORESTART: 不重启
-      // /NOCANCEL: 不显示取消按钮
-      // /SP-: 不显示 "This will install..." 提示
-      // /CLOSEAPPLICATIONS: 自动关闭相关应用
-      // /DIR: 指定安装目录
-      const installArgs = ['/VERYSILENT', '/NORESTART', '/NOCANCEL', '/SP-', '/CLOSEAPPLICATIONS']
-      if (customPath) {
-        installArgs.push(`/DIR=${customPath}`)
-      }
-
-      await sudoExec(`"${installerPath}" ${installArgs.join(' ')}`)
-
-      // 清理安装包
-      try {
-        await fs.promises.unlink(installerPath)
-      } catch {
-        // 忽略清理失败
-      }
-
-      onStatus('git', STATUS_MESSAGES.COMPLETE, 100)
-    } catch (error) {
-      logger.installError('安装 Git 失败', error)
-      throw error
-    }
-
-    return
-  }
-
-  // Windows 上的特殊版本 (mingit/lfs) 暂不支持
-  if (platform === 'win32') {
-    throw new Error(`Windows 暂不支持安装 ${version} 版本,请选择具体版本号`)
-  }
-
-  // macOS/Linux: 使用包管理器安装
-  onStatus('git', `${STATUS_MESSAGES.INSTALLING} Git...`, 30)
-  const args = getGitInstallArgs(version)
-  await executeCommand(args.command, args.args, true)
-  onStatus('git', STATUS_MESSAGES.COMPLETE, 100)
-}
-
-/**
- * 安装 Claude Code (通过 pnpm 或 npm 全局安装)
- * @param onStatus 状态回调
- */
-export async function installClaudeCode(onStatus: StatusCallback): Promise<void> {
-  resetCancelState()
-
-  const platform = os.platform() as Platform
-
-  onStatus('claudeCode', '正在安装 Claude Code...', 10)
-  logger.installInfo('开始安装 Claude Code...')
-
-  checkCancelled()
-
-  // 检测是否已安装 pnpm,优先使用 pnpm
-  const hasPnpm = await commandExistsWithRefresh('pnpm')
-  const pkgManager = hasPnpm ? 'pnpm' : 'npm'
-
-  // 获取刷新后的 PATH,确保能找到新安装的命令
-  const { getRefreshedPath } = await import('./utils')
-  const refreshedPath = await getRefreshedPath()
-  const execEnv: Record<string, string> = { ...process.env as Record<string, string>, PATH: refreshedPath }
-
-  if (hasPnpm) {
-    // 使用 pnpm 安装,需要确保 PNPM_HOME 环境变量已设置
-    onStatus('claudeCode', '配置 pnpm 全局目录...', 20)
-
-    // 先执行 pnpm setup
-    try {
-      await execa('pnpm', ['setup'], { env: execEnv, shell: platform === 'win32' })
-      logger.installInfo('pnpm setup 完成')
-    } catch (error) {
-      logger.installWarn('pnpm setup 失败', error)
-    }
-
-    // 手动设置 PNPM_HOME 环境变量
-    if (platform === 'win32') {
-      const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local')
-      const pnpmHome = path.join(localAppData, 'pnpm')
-      execEnv.PNPM_HOME = pnpmHome
-      execEnv.PATH = `${pnpmHome};${execEnv.PATH}`
-      logger.installInfo(`设置 PNPM_HOME: ${pnpmHome}`)
-    } else {
-      const pnpmHome = path.join(os.homedir(), '.local', 'share', 'pnpm')
-      execEnv.PNPM_HOME = pnpmHome
-      execEnv.PATH = `${pnpmHome}:${execEnv.PATH}`
-      logger.installInfo(`设置 PNPM_HOME: ${pnpmHome}`)
-    }
-  }
-
-  checkCancelled()
-
-  const installArgs = ['install', '-g', '@anthropic-ai/claude-code']
-  const fullCommand = `${pkgManager} ${installArgs.join(' ')}`
-  onStatus('claudeCode', `执行命令: ${fullCommand}`, 30)
-  logger.installInfo(`使用 ${pkgManager} 安装,执行命令: ${fullCommand}`)
-
-  try {
-    if (hasPnpm) {
-      // 使用 pnpm 安装 Claude Code
-      await execa('pnpm', installArgs, { env: execEnv, shell: platform === 'win32' })
-    } else {
-      // 使用 npm 安装
-      const npmCmd = getNpmPath()
-      await executeCommand(npmCmd, installArgs, true)
-    }
-  } catch (error) {
-    logger.installError('安装 Claude Code 失败', error)
-    throw error
-  }
-
-  checkCancelled()
-
-  // 验证安装
-  onStatus('claudeCode', '验证安装...', 90)
-  const claudeExists = await commandExistsWithRefresh('claude')
-
-  if (claudeExists) {
-    const { getCommandVersionWithRefresh } = await import('./utils')
-    const version = await getCommandVersionWithRefresh('claude', ['--version'])
-    logger.installInfo(`Claude Code 安装成功: ${version}`)
-  } else {
-    // 即使验证失败,安装可能已成功,只是 PATH 还没生效
-    logger.installInfo('Claude Code 安装完成,但验证失败(可能需要重启终端)')
-  }
-
-  onStatus('claudeCode', STATUS_MESSAGES.COMPLETE, 100)
-}
-
-/**
- * 安装 Claude Code for VS Code 扩展
- * 复用 ipc-handlers.ts 中的 installVscodeExtension 函数
- * @param _onStatus 状态回调(已由 installVscodeExtension 内部处理)
- */
-export async function installClaudeCodeExt(_onStatus: StatusCallback): Promise<void> {
-  resetCancelState()
-  checkCancelled()
-
-  const extensionId = 'anthropic.claude-code'
-  const result = await installVscodeExtension(extensionId)
-
-  if (!result.success) {
-    throw new Error(result.error || 'VS Code 插件安装失败')
-  }
-}
-
-/**
- * 卸载软件
- */
-export async function uninstallSoftware(software: SoftwareType): Promise<boolean> {
-  // 只有 nodejs, vscode, git 支持卸载
-  if (software !== 'nodejs' && software !== 'vscode' && software !== 'git') {
-    logger.installWarn(`${software} 不支持通过此方式卸载`)
-    return false
-  }
-  try {
-    const args = getUninstallArgs(software)
-    await executeCommand(args.command, args.args, true)
-    logger.installInfo(`${software} 卸载成功`)
-    return true
-  } catch (error) {
-    logger.installError(`${software} 卸载失败`, error)
-    return false
-  }
-}
-
-/**
- * 一键安装所有软件
- * 复用单独安装函数,避免代码重复
- */
-export async function installAll(options: InstallOptions, onStatus: StatusCallback): Promise<string[]> {
-  resetCancelState()
-
-  onStatus('all', '正在准备安装...', 5)
-
-  const {
-    installNodejs: doNodejs = true,
-    nodejsVersion = 'lts',
-    nodejsPath,
-    installPnpm: doPnpm = true,
-    installVscode: doVscode = true,
-    vscodeVersion = 'stable',
-    vscodePath,
-    installGit: doGit = true,
-    gitVersion = 'stable',
-    gitPath,
-    installClaudeCode: doClaudeCode = false,
-    installClaudeCodeExt: doClaudeCodeExt = false
-  } = options
-
-  // 计算总步骤数
-  const steps =
-    (doNodejs ? 1 : 0) +
-    (doPnpm && !doNodejs ? 1 : 0) + // 如果安装 Node.js,pnpm 会一起安装
-    (doGit ? 1 : 0) +
-    (doVscode ? 1 : 0) +
-    (doClaudeCode ? 1 : 0) +
-    (doClaudeCodeExt ? 1 : 0)
-  let currentStep = 0
-  const getProgress = (): number => Math.round(((currentStep + 0.5) / steps) * 100)
-
-  // 创建包装的状态回调,将子安装的状态转发到 'all'
-  const createWrappedStatus = (stepName: string): StatusCallback => {
-    return (_software, message, _progress, skipLog) => {
-      onStatus('all', `[${stepName}] ${message}`, getProgress(), skipLog)
-    }
-  }
-
-  // 安装 Node.js(包含 pnpm)
-  if (doNodejs) {
-    checkCancelled()
-    const wrappedStatus = createWrappedStatus('Node.js')
-    await installNodejs(nodejsVersion, doPnpm, wrappedStatus, nodejsPath)
-    currentStep++
-  } else if (doPnpm) {
-    // 如果不安装 Node.js 但需要安装 pnpm,单独安装 pnpm
-    checkCancelled()
-    const wrappedStatus = createWrappedStatus('pnpm')
-    await installPnpm(wrappedStatus)
-    currentStep++
-  }
-
-  // 安装 Git (Claude Code 运行时需要 Git)
-  if (doGit) {
-    checkCancelled()
-    const wrappedStatus = createWrappedStatus('Git')
-    await installGit(gitVersion, wrappedStatus, gitPath)
-    currentStep++
-  }
-
-  // 安装 Claude Code (需要 Node.js 和 Git,应在 Git 之后安装)
-  if (doClaudeCode) {
-    checkCancelled()
-    const wrappedStatus = createWrappedStatus('Claude Code')
-    await installClaudeCode(wrappedStatus)
-    currentStep++
-  }
-
-  // 安装 VS Code (Claude Code Ext 需要 VS Code)
-  if (doVscode) {
-    checkCancelled()
-    const wrappedStatus = createWrappedStatus('VS Code')
-    await installVscode(vscodeVersion, wrappedStatus, vscodePath)
-    currentStep++
-
-    // 如果需要安装扩展,等待 VS Code CLI 准备就绪
-    if (doClaudeCodeExt) {
-      onStatus('all', '[VS Code] 等待 VS Code CLI 准备就绪...', getProgress(), true)
-      await new Promise(resolve => setTimeout(resolve, 3000))
-    }
-  }
-
-  // 安装 Claude Code for VS Code 扩展 (需要 VS Code 和 Claude Code)
-  if (doClaudeCodeExt) {
-    checkCancelled()
-    const wrappedStatus = createWrappedStatus('Claude Code 插件')
-    try {
-      await installClaudeCodeExt(wrappedStatus)
-    } catch (error) {
-      // 插件安装失败不阻止整体流程
-      logger.installWarn('Claude Code for VS Code 扩展安装失败', error)
-    }
-    currentStep++
-  }
-
-  // 生成完成消息
-  const installed: string[] = []
-  if (doNodejs) installed.push('Node.js')
-  if (doPnpm) installed.push('pnpm')
-  if (doVscode) installed.push('VS Code')
-  if (doGit) installed.push('Git')
-  if (doClaudeCode) installed.push('Claude Code')
-  if (doClaudeCodeExt) installed.push('Claude Code 插件')
-
-  return installed
-}
-
-export {
-  executeCommand,
-  getNodeInstallArgs,
-  getVSCodeInstallArgs,
-  getGitInstallArgs,
-  getNpmPath,
-  getPnpmPath
-}

+ 0 - 616
electron/modules/ipc-handlers.ts

@@ -1,616 +0,0 @@
-// 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'

+ 0 - 74
electron/modules/ipc-registry.ts

@@ -1,74 +0,0 @@
-// electron/modules/ipc-registry.ts - IPC 处理器自动注册模块
-
-import { ipcMain, type IpcMainInvokeEvent } from 'electron'
-import logger from './logger'
-
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-type IpcHandler = (event: IpcMainInvokeEvent, ...args: any[]) => any
-
-// 存储所有已注册的 handler 名称
-const registeredHandlers = new Set<string>()
-
-/**
- * 注册 IPC 处理器
- * @param channel IPC 通道名称
- * @param handler 处理函数
- */
-export function registerHandler(channel: string, handler: IpcHandler): void {
-  if (registeredHandlers.has(channel)) {
-    logger.warn(`IPC handler "${channel}" 已存在,将被覆盖`)
-    ipcMain.removeHandler(channel)
-  }
-
-  ipcMain.handle(channel, handler)
-  registeredHandlers.add(channel)
-  logger.debug(`已注册 IPC handler: ${channel}`)
-}
-
-/**
- * 批量注册 IPC 处理器
- * @param handlers 处理器映射对象
- */
-export function registerHandlers(handlers: Record<string, IpcHandler>): void {
-  for (const [channel, handler] of Object.entries(handlers)) {
-    registerHandler(channel, handler)
-  }
-}
-
-/**
- * 移除指定的 IPC 处理器
- * @param channel IPC 通道名称
- */
-export function removeHandler(channel: string): void {
-  if (registeredHandlers.has(channel)) {
-    ipcMain.removeHandler(channel)
-    registeredHandlers.delete(channel)
-    logger.debug(`已移除 IPC handler: ${channel}`)
-  }
-}
-
-/**
- * 移除所有已注册的 IPC 处理器
- */
-export function removeAllHandlers(): void {
-  for (const channel of registeredHandlers) {
-    ipcMain.removeHandler(channel)
-  }
-  const count = registeredHandlers.size
-  registeredHandlers.clear()
-  logger.info(`已移除所有 IPC handlers (共 ${count} 个)`)
-}
-
-/**
- * 获取所有已注册的 handler 名称
- */
-export function getRegisteredHandlers(): string[] {
-  return Array.from(registeredHandlers)
-}
-
-/**
- * 检查 handler 是否已注册
- */
-export function isHandlerRegistered(channel: string): boolean {
-  return registeredHandlers.has(channel)
-}

+ 0 - 262
electron/modules/logger.ts

@@ -1,262 +0,0 @@
-// electron/modules/logger.ts - 日志模块 (基于 electron-log)
-
-import log from 'electron-log/main'
-import * as path from 'path'
-import * as fs from 'fs'
-import { app } from 'electron'
-import type { LogEntry, LogCategory } from './types'
-
-// 日志配置常量
-const MAX_LOG_FILE_SIZE = 5 * 1024 * 1024 // 5MB
-const DEFAULT_MAX_FILES = 3
-const DEFAULT_MAX_RECENT_LOGS = 500
-
-// 日志实例
-let appLog: ReturnType<typeof log.create> | null = null
-let installLog: ReturnType<typeof log.create> | null = null
-
-class Logger {
-  private recentLogs: LogEntry[] = []
-  private maxRecentLogs = DEFAULT_MAX_RECENT_LOGS
-  private logDir: string = ''
-  private initialized = false
-
-  init(): void {
-    if (this.initialized) return
-    this.initialized = true
-
-    // 日志目录设置 - 统一使用 Electron 推荐的用户数据目录
-    // Windows: C:\Users\用户名\AppData\Roaming\claude-ai-installer\logs
-    const userDataDir = app.getPath('userData')
-    console.log('[Logger] 使用用户数据目录:', userDataDir)
-    this.logDir = path.join(userDataDir, 'logs')
-
-    // 确保日志目录存在
-    console.log('[Logger] 尝试创建日志目录:', this.logDir)
-    try {
-      if (!fs.existsSync(this.logDir)) {
-        fs.mkdirSync(this.logDir, { recursive: true })
-        console.log('[Logger] 日志目录创建成功')
-      } else {
-        console.log('[Logger] 日志目录已存在')
-      }
-    } catch (err) {
-      console.error('[Logger] 创建日志目录失败:', err)
-    }
-
-    // 保存日志目录路径供 resolvePathFn 使用
-    const logDir = this.logDir
-
-    // 创建应用日志实例
-    console.log('[Logger] 创建 appLog 实例...')
-    appLog = log.create({ logId: 'app' })
-    const appLogPath = path.join(logDir, 'app.log')
-    appLog.transports.file.resolvePathFn = () => appLogPath
-    appLog.transports.file.level = 'debug'
-    appLog.transports.file.maxSize = MAX_LOG_FILE_SIZE
-    appLog.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}'
-    appLog.transports.file.archiveLogFn = (file) => this.archiveLog(file)
-    appLog.transports.console.level = 'debug'
-    appLog.transports.console.format = '[{y}-{m}-{d} {h}:{i}:{s}] [{level}] {text}'
-    console.log('[Logger] appLog 配置完成,路径:', appLogPath)
-
-    // 创建安装日志实例
-    console.log('[Logger] 创建 installLog 实例...')
-    installLog = log.create({ logId: 'install' })
-    const installLogPath = path.join(logDir, 'install.log')
-    installLog.transports.file.resolvePathFn = () => installLogPath
-    installLog.transports.file.level = 'debug'
-    installLog.transports.file.maxSize = MAX_LOG_FILE_SIZE
-    installLog.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}'
-    installLog.transports.file.archiveLogFn = (file) => this.archiveLog(file)
-    installLog.transports.console.level = 'debug'
-    installLog.transports.console.format = '[{y}-{m}-{d} {h}:{i}:{s}] [{level}] {text}'
-    console.log('[Logger] installLog 配置完成,路径:', installLogPath)
-
-    // 写入初始化日志
-    this.info('日志系统初始化完成', { logDir: this.logDir })
-    this.installInfo('安装日志系统初始化完成', { logDir: this.logDir })
-  }
-
-  /**
-   * 日志文件轮转
-   */
-  private archiveLog(file: { path: string; toString: () => string }): void {
-    const filePath = file.toString()
-    const info = path.parse(filePath)
-
-    // 删除最旧的轮转文件
-    for (let i = DEFAULT_MAX_FILES; i >= 1; i--) {
-      const oldFile = path.join(info.dir, `${info.name}.${i}${info.ext}`)
-      const newFile = path.join(info.dir, `${info.name}.${i + 1}${info.ext}`)
-      try {
-        if (fs.existsSync(oldFile)) {
-          if (i === DEFAULT_MAX_FILES) {
-            fs.unlinkSync(oldFile)
-          } else {
-            fs.renameSync(oldFile, newFile)
-          }
-        }
-      } catch {
-        // 忽略轮转错误
-      }
-    }
-
-    // 重命名当前日志文件
-    try {
-      fs.renameSync(filePath, path.join(info.dir, `${info.name}.1${info.ext}`))
-    } catch {
-      // 忽略重命名错误
-    }
-  }
-
-  getLogDir(): string {
-    return this.logDir
-  }
-
-  getAppLogPath(): string {
-    return path.join(this.logDir, 'app.log')
-  }
-
-  getInstallLogPath(): string {
-    return path.join(this.logDir, 'install.log')
-  }
-
-  private addToRecent(level: LogEntry['level'], message: string, category: LogCategory, data?: unknown): void {
-    const entry: LogEntry = {
-      level,
-      message,
-      timestamp: new Date().toISOString(),
-      category,
-      data
-    }
-    this.recentLogs.push(entry)
-    if (this.recentLogs.length > this.maxRecentLogs) {
-      this.recentLogs.shift()
-    }
-  }
-
-  // ==================== 应用日志方法 ====================
-
-  debug(message: string, data?: unknown): void {
-    if (appLog) {
-      if (data !== undefined) {
-        appLog.debug(message, data)
-      } else {
-        appLog.debug(message)
-      }
-    } else {
-      console.debug('[Logger]', message, data ?? '')
-    }
-    this.addToRecent('DEBUG', message, 'app', data)
-  }
-
-  info(message: string, data?: unknown): void {
-    if (appLog) {
-      if (data !== undefined) {
-        appLog.info(message, data)
-      } else {
-        appLog.info(message)
-      }
-    } else {
-      console.info('[Logger]', message, data ?? '')
-    }
-    this.addToRecent('INFO', message, 'app', data)
-  }
-
-  warn(message: string, data?: unknown): void {
-    if (appLog) {
-      if (data !== undefined) {
-        appLog.warn(message, data)
-      } else {
-        appLog.warn(message)
-      }
-    } else {
-      console.warn('[Logger]', message, data ?? '')
-    }
-    this.addToRecent('WARN', message, 'app', data)
-  }
-
-  error(message: string, data?: unknown): void {
-    if (appLog) {
-      if (data !== undefined) {
-        appLog.error(message, data)
-      } else {
-        appLog.error(message)
-      }
-    } else {
-      console.error('[Logger]', message, data ?? '')
-    }
-    this.addToRecent('ERROR', message, 'app', data)
-  }
-
-  // ==================== 安装日志方法 ====================
-
-  installDebug(message: string, data?: unknown): void {
-    if (installLog) {
-      if (data !== undefined) {
-        installLog.debug(message, data)
-      } else {
-        installLog.debug(message)
-      }
-    } else {
-      console.debug('[InstallLog]', message, data ?? '')
-    }
-    this.addToRecent('DEBUG', message, 'install', data)
-  }
-
-  installInfo(message: string, data?: unknown): void {
-    if (installLog) {
-      if (data !== undefined) {
-        installLog.info(message, data)
-      } else {
-        installLog.info(message)
-      }
-    } else {
-      console.info('[InstallLog]', message, data ?? '')
-    }
-    this.addToRecent('INFO', message, 'install', data)
-  }
-
-  installWarn(message: string, data?: unknown): void {
-    if (installLog) {
-      if (data !== undefined) {
-        installLog.warn(message, data)
-      } else {
-        installLog.warn(message)
-      }
-    } else {
-      console.warn('[InstallLog]', message, data ?? '')
-    }
-    this.addToRecent('WARN', message, 'install', data)
-  }
-
-  installError(message: string, data?: unknown): void {
-    if (installLog) {
-      if (data !== undefined) {
-        installLog.error(message, data)
-      } else {
-        installLog.error(message)
-      }
-    } else {
-      console.error('[InstallLog]', message, data ?? '')
-    }
-    this.addToRecent('ERROR', message, 'install', data)
-  }
-
-  // ==================== 日志查询方法 ====================
-
-  getRecentLogs(limit = 200, category?: LogCategory): LogEntry[] {
-    if (category) {
-      return this.recentLogs.filter(log => log.category === category).slice(-limit)
-    }
-    return this.recentLogs.slice(-limit)
-  }
-
-  close(): void {
-    this.recentLogs = []
-  }
-}
-
-export const logger = new Logger()
-export default logger

+ 0 - 65
electron/modules/source-updater.ts

@@ -1,65 +0,0 @@
-// electron/modules/source-updater.ts - 包管理器源更新模块
-
-import * as os from 'os'
-import { execa } from 'execa'
-import { needsSourceUpdate, markSourceUpdated } from './utils'
-import logger from './logger'
-
-// 正在进行中的更新操作(用于防止并发重复更新)
-let aptUpdatePromise: Promise<void> | null = null
-
-/**
- * 检查 apt 源是否需要更新
- * @returns 是否需要更新
- */
-export function needsAptUpdate(): boolean {
-  if (os.platform() !== 'linux') return false
-  return needsSourceUpdate('apt')
-}
-
-/**
- * 标记 apt 源已更新
- */
-export function markAptUpdated(): void {
-  markSourceUpdated('apt')
-  logger.info('apt 源已更新')
-}
-
-/**
- * 更新 apt 源(Linux 专用,带缓存,1天内只更新一次)
- * 用于版本查询前的源更新
- * 注意:这个函数用于版本查询,实际安装时使用 installer.ts 中的 aptUpdate
- * 支持并发调用:多个调用会共享同一个更新操作
- */
-export async function updateAptSourceForQuery(): Promise<void> {
-  if (os.platform() !== 'linux') return
-  if (!needsSourceUpdate('apt')) {
-    logger.info('apt 源已在缓存期内,跳过更新')
-    return
-  }
-
-  // 如果已有更新操作在进行中,等待它完成
-  if (aptUpdatePromise) {
-    logger.info('apt 源更新已在进行中,等待完成...')
-    return aptUpdatePromise
-  }
-
-  // 创建新的更新操作
-  aptUpdatePromise = (async () => {
-    try {
-      logger.info('正在更新 apt 源...')
-      // 使用 sudo 执行 apt update
-      await execa('sudo', ['apt', 'update'], { stdio: 'pipe' })
-      markSourceUpdated('apt')
-      logger.info('apt 源更新完成')
-    } catch (error) {
-      logger.warn('apt 源更新失败,继续查询', error)
-      // 即使更新失败也标记为已尝试,避免当天重复尝试
-      markSourceUpdated('apt')
-    } finally {
-      aptUpdatePromise = null
-    }
-  })()
-
-  return aptUpdatePromise
-}

+ 0 - 4
electron/modules/types.ts

@@ -1,4 +0,0 @@
-// electron/modules/types.ts - 类型定义
-// 从共享类型文件重新导出所有类型
-
-export * from '../../shared/types'

+ 0 - 297
electron/modules/updater.ts

@@ -1,297 +0,0 @@
-// electron/modules/updater.ts - 自动更新模块
-
-import { autoUpdater, UpdateInfo } from 'electron-updater'
-import { BrowserWindow, app } from 'electron'
-import * as path from 'path'
-import * as fs from 'fs'
-import logger from './logger'
-
-// 更新状态
-export type UpdateStatus =
-  | 'checking'
-  | 'available'
-  | 'not-available'
-  | 'downloading'
-  | 'downloaded'
-  | 'error'
-
-// 更新进度信息
-export interface UpdateProgress {
-  percent: number
-  bytesPerSecond: number
-  total: number
-  transferred: number
-}
-
-// 更新信息
-export interface UpdateResult {
-  status: UpdateStatus
-  info?: UpdateInfo
-  progress?: UpdateProgress
-  error?: string
-}
-
-// 主窗口引用
-let mainWindow: BrowserWindow | null = null
-
-/**
- * 检测是否为 Windows Portable 模式(启动器模式)
- * 启动器模式的特征:exe 位于 app 子目录中
- */
-export function isPortableMode(): boolean {
-  const exePath = process.execPath.toLowerCase()
-  // 检查是否在 app 子目录中(启动器模式)
-  const isInAppDir = exePath.includes(`${path.sep}app${path.sep}`)
-  // 或者文件名包含 portable
-  const isPortableName = exePath.includes('portable')
-  return isInAppDir || isPortableName
-}
-
-/**
- * 获取 Portable 根目录(启动器所在目录)
- */
-function getPortableRootDir(): string {
-  const exeDir = path.dirname(process.execPath)
-  // 如果在 app 子目录中,返回上级目录
-  if (exeDir.toLowerCase().endsWith(`${path.sep}app`)) {
-    return path.dirname(exeDir)
-  }
-  return exeDir
-}
-
-/**
- * 获取更新目录(Portable 模式专用)
- */
-function getUpdateDir(): string {
-  return path.join(getPortableRootDir(), 'update')
-}
-
-/**
- * 获取应用目录(Portable 模式专用)
- */
-function getAppDir(): string {
-  return path.join(getPortableRootDir(), 'app')
-}
-
-/**
- * 发送更新状态到渲染进程
- */
-function sendUpdateStatus(result: UpdateResult): void {
-  if (mainWindow && !mainWindow.isDestroyed()) {
-    mainWindow.webContents.send('updater:status', result)
-  }
-}
-
-/**
- * 初始化自动更新器
- */
-export function initAutoUpdater(window: BrowserWindow): void {
-  mainWindow = window
-
-  // 配置
-  autoUpdater.autoDownload = false // 不自动下载,让用户确认
-  autoUpdater.autoInstallOnAppQuit = true
-  autoUpdater.autoRunAppAfterInstall = true
-
-  // 检查更新中
-  autoUpdater.on('checking-for-update', () => {
-    logger.info('正在检查更新...')
-    sendUpdateStatus({ status: 'checking' })
-  })
-
-  // 有可用更新
-  autoUpdater.on('update-available', (info: UpdateInfo) => {
-    logger.info('发现新版本', { version: info.version })
-    sendUpdateStatus({ status: 'available', info })
-  })
-
-  // 没有更新
-  autoUpdater.on('update-not-available', (info: UpdateInfo) => {
-    logger.info('当前已是最新版本', { version: info.version })
-    sendUpdateStatus({ status: 'not-available', info })
-  })
-
-  // 下载进度
-  autoUpdater.on('download-progress', (progress) => {
-    logger.debug('下载进度', { percent: progress.percent.toFixed(2) })
-    sendUpdateStatus({
-      status: 'downloading',
-      progress: {
-        percent: progress.percent,
-        bytesPerSecond: progress.bytesPerSecond,
-        total: progress.total,
-        transferred: progress.transferred
-      }
-    })
-  })
-
-  // 下载完成
-  autoUpdater.on('update-downloaded', (info: UpdateInfo) => {
-    logger.info('更新下载完成', { version: info.version })
-    sendUpdateStatus({ status: 'downloaded', info })
-  })
-
-  // 错误
-  autoUpdater.on('error', (error) => {
-    logger.error('更新错误', error)
-    sendUpdateStatus({ status: 'error', error: error.message })
-  })
-
-  logger.info('自动更新器已初始化', {
-    isPortable: isPortableMode(),
-    currentVersion: app.getVersion()
-  })
-}
-
-/**
- * 检查更新
- */
-export async function checkForUpdates(): Promise<UpdateResult> {
-  try {
-    logger.info('手动检查更新')
-    const result = await autoUpdater.checkForUpdates()
-    if (result?.updateInfo) {
-      return { status: 'available', info: result.updateInfo }
-    }
-    return { status: 'not-available' }
-  } catch (error) {
-    const message = error instanceof Error ? error.message : String(error)
-    logger.error('检查更新失败', error)
-    return { status: 'error', error: message }
-  }
-}
-
-/**
- * 下载更新
- */
-export async function downloadUpdate(): Promise<UpdateResult> {
-  try {
-    logger.info('开始下载更新')
-    await autoUpdater.downloadUpdate()
-    return { status: 'downloading' }
-  } catch (error) {
-    const message = error instanceof Error ? error.message : String(error)
-    logger.error('下载更新失败', error)
-    return { status: 'error', error: message }
-  }
-}
-
-/**
- * 安装更新并重启
- * 自动检测是否为 Portable 模式并使用对应的更新方式
- */
-export function installUpdate(): void {
-  logger.info('安装更新并重启', { isPortable: isPortableMode() })
-
-  if (isPortableMode()) {
-    // Portable 模式:通过启动器更新
-    installPortableUpdate()
-  } else {
-    // 标准模式:使用 electron-updater
-    autoUpdater.quitAndInstall(false, true)
-  }
-}
-
-/**
- * 获取当前版本
- */
-export function getCurrentVersion(): string {
-  return app.getVersion()
-}
-
-/**
- * Portable 模式:将下载的更新文件移动到 update 目录
- * 启动器会在下次启动时自动应用更新
- */
-export async function preparePortableUpdate(): Promise<boolean> {
-  if (!isPortableMode()) {
-    logger.info('非 Portable 模式,跳过 Portable 更新准备')
-    return false
-  }
-
-  try {
-    const updateDir = getUpdateDir()
-
-    // 确保更新目录存在
-    if (!fs.existsSync(updateDir)) {
-      fs.mkdirSync(updateDir, { recursive: true })
-    }
-
-    // electron-updater 下载的文件位置
-    const downloadedUpdatePath = path.join(app.getPath('userData'), 'pending')
-
-    if (fs.existsSync(downloadedUpdatePath)) {
-      // 查找下载的 exe 文件
-      const files = fs.readdirSync(downloadedUpdatePath)
-      const exeFile = files.find(f => f.endsWith('.exe'))
-
-      if (exeFile) {
-        const srcPath = path.join(downloadedUpdatePath, exeFile)
-        const destPath = path.join(updateDir, exeFile)
-
-        // 复制到 update 目录
-        fs.copyFileSync(srcPath, destPath)
-        logger.info('Portable 更新文件已准备', { destPath })
-
-        return true
-      }
-    }
-
-    logger.warn('未找到下载的更新文件')
-    return false
-  } catch (error) {
-    logger.error('准备 Portable 更新失败', error)
-    return false
-  }
-}
-
-/**
- * Portable 模式:安装更新
- * 通过重启应用让启动器完成更新
- */
-export function installPortableUpdate(): void {
-  if (!isPortableMode()) {
-    // 非 Portable 模式,使用标准更新
-    autoUpdater.quitAndInstall(false, true)
-    return
-  }
-
-  logger.info('Portable 模式:准备重启以应用更新')
-
-  // 查找启动器
-  const rootDir = getPortableRootDir()
-  const launcherPath = path.join(rootDir, 'Claude-AI-Installer.exe')
-
-  if (fs.existsSync(launcherPath)) {
-    // 通过启动器重启
-    const { spawn } = require('child_process')
-    spawn(launcherPath, [], {
-      detached: true,
-      stdio: 'ignore',
-      cwd: rootDir
-    }).unref()
-
-    logger.info('已启动启动器,准备退出当前应用')
-    app.quit()
-  } else {
-    // 没有启动器,直接退出(用户需要手动重启)
-    logger.warn('未找到启动器,请手动重启应用')
-    app.quit()
-  }
-}
-
-/**
- * 获取 Portable 更新信息
- */
-export function getPortableUpdateInfo(): { hasUpdate: boolean; updateDir: string; appDir: string } {
-  const updateDir = getUpdateDir()
-  const appDir = getAppDir()
-
-  let hasUpdate = false
-  if (fs.existsSync(updateDir)) {
-    const files = fs.readdirSync(updateDir)
-    hasUpdate = files.some(f => f.endsWith('.exe'))
-  }
-
-  return { hasUpdate, updateDir, appDir }
-}

+ 0 - 746
electron/modules/utils.ts

@@ -1,746 +0,0 @@
-// electron/modules/utils.ts - 工具函数
-
-import axios, { AxiosError, CancelTokenSource } from 'axios'
-import * as os from 'os'
-import * as fs from 'fs'
-import * as path from 'path'
-import { execa } from 'execa'
-import { REQUEST_TIMEOUT, MAX_RETRIES, RETRY_DELAY, ERROR_MESSAGES, SOURCE_UPDATE_CACHE_TTL } from './constants'
-
-// 版本缓存
-const versionCache = new Map<string, { data: unknown; expiry: number }>()
-
-// 包管理器源更新缓存(记录上次更新时间)
-const sourceUpdateCache = new Map<string, number>()
-
-// 创建 axios 实例
-const axiosInstance = axios.create({
-  headers: { 'User-Agent': 'ApqInstaller/2.0' },
-  timeout: REQUEST_TIMEOUT
-})
-
-/**
- * 验证 URL 是否合法
- */
-export function isValidUrl(url: string): boolean {
-  try {
-    const parsed = new URL(url)
-    return ['http:', 'https:'].includes(parsed.protocol)
-  } catch {
-    return false
-  }
-}
-
-/**
- * 带超时和重试的 HTTP GET 请求(使用 axios)
- */
-export async function httpsGet<T = unknown>(
-  url: string,
-  options: {
-    timeout?: number
-    retries?: number
-    retryDelay?: number
-  } = {}
-): Promise<T> {
-  const {
-    timeout = REQUEST_TIMEOUT,
-    retries = MAX_RETRIES,
-    retryDelay = RETRY_DELAY
-  } = options
-
-  let lastError: Error | null = null
-
-  for (let attempt = 1; attempt <= retries; attempt++) {
-    try {
-      const response = await axiosInstance.get<T>(url, {
-        timeout,
-        maxRedirects: 5
-      })
-      return response.data
-    } catch (error) {
-      lastError = error as Error
-      const axiosError = error as AxiosError
-
-      if (axiosError.code === 'ECONNABORTED' || axiosError.message.includes('timeout')) {
-        console.log(`请求超时,${retryDelay}ms 后重试... (剩余 ${retries - attempt} 次)`)
-      } else {
-        console.log(`请求失败: ${axiosError.message},${retryDelay}ms 后重试... (剩余 ${retries - attempt} 次)`)
-      }
-
-      if (attempt < retries) {
-        await delay(retryDelay)
-      }
-    }
-  }
-
-  if (lastError) {
-    const axiosError = lastError as AxiosError
-    if (axiosError.code === 'ECONNABORTED' || axiosError.message.includes('timeout')) {
-      throw new Error(ERROR_MESSAGES.TIMEOUT_ERROR)
-    }
-    throw new Error(ERROR_MESSAGES.NETWORK_ERROR + ': ' + lastError.message)
-  }
-
-  throw new Error(ERROR_MESSAGES.NETWORK_ERROR)
-}
-
-/**
- * 获取 Windows 系统最新的 PATH 环境变量
- * 安装软件后,系统 PATH 会更新,但当前进程的 PATH 不会自动更新
- * 通过 PowerShell 从注册表读取最新的 PATH
- * 同时添加 pnpm 和 npm 的全局 bin 目录,确保能找到全局安装的命令
- */
-async function getRefreshedWindowsPath(): Promise<string> {
-  try {
-    const result = await execa('powershell', [
-      '-NoProfile',
-      '-Command',
-      `[Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + [Environment]::GetEnvironmentVariable('Path', 'User')`
-    ])
-    let newPath = result.stdout.trim()
-
-    // 添加 pnpm 全局 bin 目录(如果不在 PATH 中)
-    // pnpm 在 Windows 上的默认全局 bin 目录是 %LOCALAPPDATA%\pnpm
-    const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local')
-    const pnpmGlobalBin = path.join(localAppData, 'pnpm')
-    if (!newPath.toLowerCase().includes(pnpmGlobalBin.toLowerCase())) {
-      newPath = `${pnpmGlobalBin};${newPath}`
-    }
-
-    // 添加 npm 全局 bin 目录(如果不在 PATH 中)
-    // npm 在 Windows 上的默认全局 bin 目录是 %APPDATA%\npm
-    const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming')
-    const npmGlobalBin = path.join(appData, 'npm')
-    if (!newPath.toLowerCase().includes(npmGlobalBin.toLowerCase())) {
-      newPath = `${npmGlobalBin};${newPath}`
-    }
-
-    return newPath
-  } catch {
-    // 如果失败,返回当前进程的 PATH,并添加常用的全局 bin 目录
-    let fallbackPath = process.env.PATH || ''
-    const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local')
-    const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming')
-    fallbackPath = `${path.join(localAppData, 'pnpm')};${path.join(appData, 'npm')};${fallbackPath}`
-    return fallbackPath
-  }
-}
-
-/**
- * 获取刷新后的 PATH 环境变量(跨平台)
- * Windows: 从注册表读取最新的 PATH
- * 其他平台: 返回当前进程的 PATH
- */
-export async function getRefreshedPath(): Promise<string> {
-  if (os.platform() === 'win32') {
-    return await getRefreshedWindowsPath()
-  }
-  return process.env.PATH || ''
-}
-
-/**
- * 检测命令是否存在
- * Windows: 会尝试使用刷新后的 PATH 来检测新安装的软件
- */
-export async function commandExists(command: string, refreshPath = false): Promise<boolean> {
-  try {
-    const platform = os.platform()
-    if (platform === 'win32') {
-      if (refreshPath) {
-        // 使用刷新后的 PATH 执行 where 命令
-        const newPath = await getRefreshedWindowsPath()
-        await execa('where', [command], { env: { ...process.env, PATH: newPath } })
-      } else {
-        await execa('where', [command])
-      }
-    } else {
-      await execa('which', [command])
-    }
-    return true
-  } catch {
-    return false
-  }
-}
-
-/**
- * 使用刷新后的 PATH 检测命令是否存在
- * Windows: 直接从注册表读取最新的 PATH 来检测
- * 其他平台: 使用当前 PATH
- */
-export async function commandExistsWithRefresh(command: string): Promise<boolean> {
-  const platform = os.platform()
-  if (platform === 'win32') {
-    // Windows 上直接使用刷新后的 PATH 检测
-    return await commandExists(command, true)
-  }
-  return await commandExists(command, false)
-}
-
-/**
- * 从 Windows 注册表获取 VS Code 安装路径
- * VS Code 安装后会在多个注册表位置写入信息
- * 使用 reg query 命令直接查询,比 PowerShell 更快更可靠
- * @returns VS Code 的 code.cmd 路径,如果找不到则返回 null
- */
-async function getVscodePathFromRegistry(): Promise<string | null> {
-  if (os.platform() !== 'win32') return null
-
-  // VS Code 可能的注册表位置(使用固定的 GUID 键,与 ipc-handlers.ts 保持一致)
-  const registryPaths = [
-    // 系统安装 (64位)
-    { key: 'HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{EA457B21-F73E-494C-ACAB-524FDE069978}_is1', value: 'InstallLocation' },
-    // 系统安装 (32位 on 64位系统)
-    { key: 'HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{EA457B21-F73E-494C-ACAB-524FDE069978}_is1', value: 'InstallLocation' },
-    // 用户安装
-    { key: 'HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{771FD6B0-FA20-440A-A002-3B3BAC16DC50}_is1', value: 'InstallLocation' },
-    // VS Code Insiders 系统安装
-    { key: 'HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{1287CAD5-7C8D-410D-88B9-0D1EE4A83FF2}_is1', value: 'InstallLocation' },
-    // VS Code Insiders 用户安装
-    { key: 'HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{217B4C08-948D-4276-BFBB-BEE930AE5A2C}_is1', value: 'InstallLocation' },
-  ]
-
-  for (const reg of registryPaths) {
-    try {
-      const result = await execa('reg', ['query', reg.key, '/v', reg.value], {
-        encoding: 'utf8',
-        timeout: 5000
-      })
-      // 解析注册表输出,格式如: "    InstallLocation    REG_SZ    C:\Program Files\Microsoft VS Code\"
-      const match = result.stdout.match(/InstallLocation\s+REG_SZ\s+(.+)/i)
-      if (match) {
-        const installLocation = match[1].trim()
-        // 构建 code.cmd 的完整路径
-        const codeCmd = path.join(installLocation, 'bin', 'code.cmd')
-        if (fs.existsSync(codeCmd)) {
-          return codeCmd
-        }
-        // 也检查 code.exe (某些版本可能直接使用 exe)
-        const codeExe = path.join(installLocation, 'bin', 'code.exe')
-        if (fs.existsSync(codeExe)) {
-          return codeExe
-        }
-      }
-    } catch {
-      // 该注册表项不存在,继续尝试下一个
-    }
-  }
-
-  return null
-}
-
-/**
- * 获取命令的完整路径
- * Windows: 使用刷新后的 PATH 执行 where 命令
- * macOS/Linux: 使用 which 命令
- * 注意:对于 VS Code,请使用专门的 getVscodeCliPath 函数
- * @param command 命令名称
- * @returns 命令的完整路径,如果找不到则返回 null
- */
-export async function getCommandPath(command: string): Promise<string | null> {
-  try {
-    const platform = os.platform()
-
-    if (platform === 'win32') {
-      // 使用刷新后的 PATH 执行 where 命令
-      const newPath = await getRefreshedWindowsPath()
-      const result = await execa('where', [command], { env: { ...process.env, PATH: newPath } })
-      // where 可能返回多行(多个路径)
-      // 优先选择 .cmd 或 .exe 文件,因为这些是 Windows 上可直接执行的
-      const paths = result.stdout.trim().split(/\r?\n/)
-      const cmdPath = paths.find(p => p.toLowerCase().endsWith('.cmd'))
-      const exePath = paths.find(p => p.toLowerCase().endsWith('.exe'))
-      // 优先返回 .cmd,其次 .exe,最后返回第一个结果
-      return cmdPath || exePath || paths[0] || null
-    } else {
-      const result = await execa('which', [command])
-      // which 通常只返回一个路径
-      return result.stdout.trim() || null
-    }
-  } catch {
-    return null
-  }
-}
-
-/**
- * 获取 VS Code CLI (code) 命令的路径
- * 优先级:PATH 中的 code 命令 > 注册表路径 > 常见安装路径
- * @returns VS Code CLI 的完整路径,如果都找不到则返回 'code'
- */
-export async function getVscodeCliPath(): Promise<string> {
-  const platform = os.platform()
-
-  // 首先检查 code 命令是否在 PATH 中
-  try {
-    await execa('code', ['--version'], { timeout: 5000 })
-    return 'code'
-  } catch {
-    // code 命令不在 PATH 中,尝试其他方法
-  }
-
-  if (platform === 'win32') {
-    // Windows: 优先从注册表获取安装路径
-    const registryPath = await getVscodePathFromRegistry()
-    if (registryPath) {
-      return registryPath
-    }
-
-    // 注册表找不到,尝试常见安装路径作为后备
-    const fallbackPaths = [
-      path.join(process.env.ProgramFiles || 'C:\\Program Files', 'Microsoft VS Code', 'bin', 'code.cmd'),
-      path.join(os.homedir(), 'AppData', 'Local', 'Programs', 'Microsoft VS Code', 'bin', 'code.cmd'),
-    ]
-
-    for (const codePath of fallbackPaths) {
-      if (fs.existsSync(codePath)) {
-        return codePath
-      }
-    }
-  } else if (platform === 'darwin') {
-    // macOS: 使用 mdfind 查找应用程序
-    try {
-      const result = await execa('mdfind', ['kMDItemCFBundleIdentifier == "com.microsoft.VSCode"'], {
-        encoding: 'utf8',
-        timeout: 5000
-      })
-      const appPath = result.stdout.trim().split('\n')[0]
-      if (appPath) {
-        const codePath = path.join(appPath, 'Contents', 'Resources', 'app', 'bin', 'code')
-        if (fs.existsSync(codePath)) {
-          return codePath
-        }
-      }
-    } catch {
-      // mdfind 失败,尝试常见路径
-    }
-
-    const fallbackPaths = [
-      '/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code',
-      path.join(os.homedir(), 'Applications', 'Visual Studio Code.app', 'Contents', 'Resources', 'app', 'bin', 'code'),
-    ]
-
-    for (const codePath of fallbackPaths) {
-      if (fs.existsSync(codePath)) {
-        return codePath
-      }
-    }
-  } else {
-    // Linux: 使用 which 或检查常见路径
-    try {
-      const result = await execa('which', ['code'], { encoding: 'utf8', timeout: 5000 })
-      const codePath = result.stdout.trim()
-      if (codePath && fs.existsSync(codePath)) {
-        return codePath
-      }
-    } catch {
-      // which 失败
-    }
-
-    const fallbackPaths = [
-      '/usr/bin/code',
-      '/usr/share/code/bin/code',
-      '/snap/bin/code',
-    ]
-
-    for (const codePath of fallbackPaths) {
-      if (fs.existsSync(codePath)) {
-        return codePath
-      }
-    }
-  }
-
-  // 如果都找不到,返回 'code' 让系统尝试
-  return 'code'
-}
-
-/**
- * 获取命令版本
- * @param command 命令名称
- * @param versionArgs 版本参数
- * @param refreshPath 是否使用刷新后的 PATH(仅 Windows)
- */
-export async function getCommandVersion(
-  command: string,
-  versionArgs: string[] = ['--version'],
-  refreshPath = false
-): Promise<string | null> {
-  try {
-    let result
-    if (refreshPath && os.platform() === 'win32') {
-      const newPath = await getRefreshedWindowsPath()
-      result = await execa(command, versionArgs, { env: { ...process.env, PATH: newPath } })
-    } else {
-      result = await execa(command, versionArgs)
-    }
-    const output = result.stdout || result.stderr
-    const match = output.match(/(\d+\.\d+\.\d+)/)
-    return match ? match[1] : null
-  } catch {
-    return null
-  }
-}
-
-/**
- * 使用刷新后的 PATH 获取命令版本
- * Windows: 直接从注册表读取最新的 PATH 来执行命令
- * 其他平台: 使用当前 PATH
- */
-export async function getCommandVersionWithRefresh(
-  command: string,
-  versionArgs: string[] = ['--version']
-): Promise<string | null> {
-  const platform = os.platform()
-  if (platform === 'win32') {
-    // Windows 上直接使用刷新后的 PATH
-    return await getCommandVersion(command, versionArgs, true)
-  }
-  return await getCommandVersion(command, versionArgs, false)
-}
-
-/**
- * 设置版本缓存
- */
-export function setCache(key: string, data: unknown, ttl: number): void {
-  versionCache.set(key, {
-    data,
-    expiry: Date.now() + ttl
-  })
-}
-
-/**
- * 获取版本缓存
- */
-export function getCache<T = unknown>(key: string): T | null {
-  const cached = versionCache.get(key)
-  if (cached && cached.expiry > Date.now()) {
-    return cached.data as T
-  }
-  versionCache.delete(key)
-  return null
-}
-
-/**
- * 清除版本缓存
- */
-export function clearCache(key?: string): void {
-  if (key) {
-    versionCache.delete(key)
-  } else {
-    versionCache.clear()
-  }
-}
-
-/**
- * 延迟函数
- */
-export function delay(ms: number): Promise<void> {
-  return new Promise((resolve) => setTimeout(resolve, ms))
-}
-
-/**
- * 检测网络连接
- */
-export async function checkNetworkConnection(): Promise<boolean> {
-  try {
-    await httpsGet('https://www.baidu.com', { timeout: 5000, retries: 1 })
-    return true
-  } catch {
-    try {
-      await httpsGet('https://www.google.com', { timeout: 5000, retries: 1 })
-      return true
-    } catch {
-      return false
-    }
-  }
-}
-
-/**
- * 下载进度回调类型
- */
-export type DownloadProgressCallback = (downloaded: number, total: number, percent: number) => void
-
-/**
- * 下载任务信息
- */
-interface DownloadTask {
-  cancelSource: CancelTokenSource
-  tempPath: string
-  destPath: string
-}
-
-// 活跃的下载任务(支持多个并行下载)
-const activeDownloads = new Map<string, DownloadTask>()
-
-/**
- * 取消指定下载并删除临时文件
- * @param downloadId 下载ID(通常是目标文件路径)
- * @returns 是否成功取消
- */
-export function cancelDownload(downloadId: string): boolean {
-  const task = activeDownloads.get(downloadId)
-  if (task) {
-    task.cancelSource.cancel('用户取消下载')
-    // 删除临时文件
-    fs.unlink(task.tempPath, () => {})
-    activeDownloads.delete(downloadId)
-    return true
-  }
-  return false
-}
-
-/**
- * 取消所有下载并删除临时文件
- * @returns 取消的下载数量
- */
-export function cancelAllDownloads(): number {
-  let count = 0
-  for (const [downloadId, task] of activeDownloads) {
-    task.cancelSource.cancel('用户取消下载')
-    fs.unlink(task.tempPath, () => {})
-    activeDownloads.delete(downloadId)
-    count++
-  }
-  return count
-}
-
-/**
- * 取消当前下载并删除临时文件(兼容旧接口)
- * @returns 是否有下载被取消
- */
-export function cancelCurrentDownload(): boolean {
-  return cancelAllDownloads() > 0
-}
-
-/**
- * 获取已下载的文件大小
- */
-function getDownloadedSize(tempPath: string): number {
-  try {
-    if (fs.existsSync(tempPath)) {
-      return fs.statSync(tempPath).size
-    }
-  } catch {
-    // 忽略错误
-  }
-  return 0
-}
-
-/**
- * 下载文件到指定路径(支持断点续传和多文件并行下载)
- * @param url 下载地址
- * @param destPath 目标文件路径
- * @param onProgress 进度回调
- * @param options 下载选项
- */
-export async function downloadFile(
-  url: string,
-  destPath: string,
-  onProgress?: DownloadProgressCallback,
-  options: {
-    timeout?: number
-  } = {}
-): Promise<string> {
-  const { timeout = 60000 } = options
-
-  // 临时文件路径(用于断点续传)
-  const tempPath = destPath + '.downloading'
-
-  // 创建取消令牌
-  const cancelSource = axios.CancelToken.source()
-
-  // 注册下载任务
-  const downloadId = destPath
-  activeDownloads.set(downloadId, {
-    cancelSource,
-    tempPath,
-    destPath
-  })
-
-  try {
-    // 获取已下载的文件大小
-    const downloadedSize = getDownloadedSize(tempPath)
-
-    // 构建请求头,支持断点续传
-    const headers: Record<string, string> = {}
-    if (downloadedSize > 0) {
-      headers['Range'] = `bytes=${downloadedSize}-`
-      console.log(`断点续传: 从 ${(downloadedSize / 1024 / 1024).toFixed(1)}MB 处继续下载`)
-    }
-
-    // 发起请求
-    const response = await axiosInstance.get(url, {
-      headers,
-      timeout,
-      responseType: 'stream',
-      cancelToken: cancelSource.token,
-      maxRedirects: 5,
-      // 不验证状态码,手动处理
-      validateStatus: () => true
-    })
-
-    // 处理 HTTP 错误状态码
-    if (response.status >= 400) {
-      // 416 表示 Range 请求无效(可能文件已完成或服务器不支持)
-      if (response.status === 416 && downloadedSize > 0) {
-        console.log('文件可能已下载完成,尝试验证...')
-        try {
-          if (fs.existsSync(destPath)) {
-            fs.unlinkSync(destPath)
-          }
-          fs.renameSync(tempPath, destPath)
-          activeDownloads.delete(downloadId)
-          return destPath
-        } catch {
-          // 如果重命名失败,删除临时文件重新下载
-          fs.unlinkSync(tempPath)
-        }
-      }
-      throw new Error(`HTTP ${response.status}: 下载失败`)
-    }
-
-    // 206 表示部分内容(断点续传成功)
-    const isPartialContent = response.status === 206
-
-    // 计算总大小
-    let totalSize = 0
-    if (isPartialContent) {
-      // 从 Content-Range 头获取总大小: bytes 0-999/1000
-      const contentRange = response.headers['content-range']
-      if (contentRange) {
-        const match = contentRange.match(/\/(\d+)$/)
-        if (match) {
-          totalSize = parseInt(match[1], 10)
-        }
-      }
-    } else {
-      // 新下载,获取 Content-Length
-      totalSize = parseInt(response.headers['content-length'] || '0', 10)
-      // 如果是新下载但存在临时文件,说明服务器不支持断点续传,删除重新下载
-      if (downloadedSize > 0) {
-        console.log('服务器不支持断点续传,重新下载')
-        fs.unlinkSync(tempPath)
-      }
-    }
-
-    // 确保目标目录存在
-    const dir = path.dirname(destPath)
-    if (!fs.existsSync(dir)) {
-      fs.mkdirSync(dir, { recursive: true })
-    }
-
-    // 以追加模式打开文件(断点续传)或创建新文件
-    const fileStream = fs.createWriteStream(tempPath, {
-      flags: isPartialContent ? 'a' : 'w'
-    })
-
-    let currentDownloaded = isPartialContent ? downloadedSize : 0
-
-    // 返回 Promise
-    return new Promise<string>((resolve, reject) => {
-      response.data.on('data', (chunk: Buffer) => {
-        currentDownloaded += chunk.length
-        if (onProgress && totalSize > 0) {
-          const percent = Math.round((currentDownloaded / totalSize) * 100)
-          onProgress(currentDownloaded, totalSize, percent)
-        }
-      })
-
-      response.data.on('error', (err: Error) => {
-        fileStream.close()
-        activeDownloads.delete(downloadId)
-        reject(new Error(ERROR_MESSAGES.NETWORK_ERROR + ': ' + err.message))
-      })
-
-      response.data.pipe(fileStream)
-
-      fileStream.on('finish', () => {
-        fileStream.close()
-
-        // 验证文件大小
-        const finalSize = getDownloadedSize(tempPath)
-        if (totalSize > 0 && finalSize < totalSize) {
-          // 文件不完整,保留临时文件以便下次续传
-          console.log(`下载不完整: ${finalSize}/${totalSize} 字节,可点击重新安装继续下载`)
-          activeDownloads.delete(downloadId)
-          reject(new Error('下载不完整,请重试'))
-          return
-        }
-
-        // 重命名临时文件为最终文件
-        try {
-          if (fs.existsSync(destPath)) {
-            fs.unlinkSync(destPath)
-          }
-          fs.renameSync(tempPath, destPath)
-          activeDownloads.delete(downloadId)
-          resolve(destPath)
-        } catch (err) {
-          activeDownloads.delete(downloadId)
-          reject(err)
-        }
-      })
-
-      fileStream.on('error', (err) => {
-        // 保留临时文件以便续传
-        activeDownloads.delete(downloadId)
-        reject(err)
-      })
-    })
-  } catch (error) {
-    activeDownloads.delete(downloadId)
-
-    if (axios.isCancel(error)) {
-      // 用户取消,删除临时文件
-      fs.unlink(tempPath, () => {})
-      throw new Error(ERROR_MESSAGES.INSTALL_CANCELLED || '下载已取消')
-    }
-
-    const axiosError = error as AxiosError
-    if (axiosError.code === 'ECONNABORTED' || axiosError.message?.includes('timeout')) {
-      // 超时,保留临时文件以便续传
-      throw new Error(ERROR_MESSAGES.TIMEOUT_ERROR)
-    }
-
-    // 其他错误,保留临时文件以便续传
-    throw error
-  }
-}
-
-/**
- * 获取临时目录路径
- */
-export function getTempDir(): string {
-  return os.tmpdir()
-}
-
-/**
- * 检查包管理器源是否需要更新
- * @param manager 包管理器名称 (apt, brew)
- * @returns 是否需要更新
- */
-export function needsSourceUpdate(manager: string): boolean {
-  const lastUpdate = sourceUpdateCache.get(manager)
-  if (!lastUpdate) {
-    return true
-  }
-  return Date.now() - lastUpdate > SOURCE_UPDATE_CACHE_TTL
-}
-
-/**
- * 标记包管理器源已更新
- * @param manager 包管理器名称
- */
-export function markSourceUpdated(manager: string): void {
-  sourceUpdateCache.set(manager, Date.now())
-}
-
-/**
- * 清除源更新缓存
- * @param manager 可选,指定清除某个包管理器的缓存,不传则清除所有
- */
-export function clearSourceUpdateCache(manager?: string): void {
-  if (manager) {
-    sourceUpdateCache.delete(manager)
-  } else {
-    sourceUpdateCache.clear()
-  }
-}

+ 0 - 742
electron/modules/version-fetcher.ts

@@ -1,742 +0,0 @@
-// electron/modules/version-fetcher.ts - 版本获取模块
-// 通过各平台包管理器查询可用版本
-
-import * as os from 'os'
-import { execa } from 'execa'
-import type { SoftwareType, VersionItem, VersionResult, Platform, GitMirrorType, NodejsMirrorType } from './types'
-import {
-  MIN_SUPPORTED_NODE_VERSION,
-  MAX_MAJOR_VERSIONS,
-  VERSION_CACHE_TTL,
-  ERROR_MESSAGES,
-  BREW_PACKAGES,
-  GIT_MIRRORS,
-  NODEJS_MIRRORS,
-  VSCODE_API
-} from './constants'
-import { setCache, getCache, clearCache } from './utils'
-import { updateAptSourceForQuery } from './source-updater'
-import {
-  getGitMirrorFromConfig,
-  saveGitMirrorConfig,
-  getNodejsMirrorFromConfig,
-  saveNodejsMirrorConfig
-} from './config'
-
-/**
- * 版本比较函数(降序)
- */
-function compareVersions(a: string, b: string): number {
-  const aParts = a.split('.').map(Number)
-  const bParts = b.split('.').map(Number)
-  for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
-    const diff = (bParts[i] || 0) - (aParts[i] || 0)
-    if (diff !== 0) return diff
-  }
-  return 0
-}
-
-/**
- * 添加版本到分组 Map
- */
-function addToVersionMap(
-  map: Map<string, VersionItem[]>,
-  fullVersion: string,
-  label: string,
-  options?: { extra?: Partial<VersionItem>; groupKey?: string }
-): void {
-  const parts = fullVersion.split('.')
-  const key = options?.groupKey ?? `${parts[0]}.${parts[1]}`
-  const list = map.get(key) ?? []
-  // 检查是否已存在相同版本,避免重复
-  if (!list.some((item) => item.value === fullVersion)) {
-    list.push({ value: fullVersion, label, ...options?.extra })
-  }
-  map.set(key, list)
-}
-
-/**
- * 通用版本列表构建器
- */
-function buildVersionList(
-  versionsByMajor: Map<string, VersionItem[]>,
-  options: {
-    maxMajors?: number
-    specialVersions?: VersionItem[]
-  } = {}
-): VersionItem[] {
-  const { maxMajors = MAX_MAJOR_VERSIONS, specialVersions = [] } = options
-
-  const versions: VersionItem[] = []
-
-  // 按版本号降序排列
-  const sortedMajors = Array.from(versionsByMajor.keys())
-    .sort(compareVersions)
-    .slice(0, maxMajors)
-
-  sortedMajors.forEach((major, index) => {
-    const isFirst = index === 0
-    const label = isFirst ? `── v${major}.x (最新) ──` : `── v${major}.x ──`
-    versions.push({ value: '', label, disabled: true, separator: true })
-
-    const majorVersions = versionsByMajor.get(major) ?? []
-    const sorted = majorVersions.sort((a, b) => compareVersions(a.value, b.value))
-    versions.push(...sorted)
-  })
-
-  // 添加特殊版本
-  if (specialVersions.length > 0) {
-    versions.push({ value: '', label: '── 其他版本 ──', disabled: true, separator: true })
-    versions.push(...specialVersions)
-  }
-
-  return versions
-}
-
-/**
- * 获取当前平台
- */
-function getPlatform(): Platform {
-  return os.platform() as Platform
-}
-
-// ==================== Node.js 镜像配置 ====================
-
-/**
- * 设置 Node.js 镜像(会持久化保存)
- */
-export function setNodejsMirror(mirror: NodejsMirrorType): void {
-  saveNodejsMirrorConfig(mirror)
-  // 清除 Node.js 版本缓存,以便重新获取
-  clearCache('versions_nodejs')
-}
-
-/**
- * 获取当前 Node.js 镜像配置
- */
-export function getNodejsMirrorConfig(): { mirror: NodejsMirrorType } {
-  return { mirror: getNodejsMirrorFromConfig() }
-}
-
-/**
- * 获取当前 Node.js 镜像类型(内部使用)
- */
-function getCurrentNodejsMirror(): NodejsMirrorType {
-  return getNodejsMirrorFromConfig()
-}
-
-/**
- * 获取 Node.js 下载 URL
- */
-export function getNodejsDownloadUrl(version: string): string {
-  const arch = os.arch() === 'x64' ? 'x64' : os.arch() === 'arm64' ? 'arm64' : 'x86'
-  const mirror = NODEJS_MIRRORS[getCurrentNodejsMirror()]
-  return mirror.getDownloadUrl(version, arch)
-}
-
-// ==================== Node.js API 版本数据类型 ====================
-
-interface NodejsVersionInfo {
-  version: string
-  date: string
-  lts: boolean | string
-  security: boolean
-}
-
-// ==================== macOS brew 版本查询 ====================
-
-/**
- * 使用 brew 查询软件信息
- * brew 不支持安装历史版本,只返回当前可安装的版本
- */
-async function getBrewVersion(formula: string): Promise<string | null> {
-  try {
-    const result = await execa('brew', ['info', '--json=v2', formula], {
-      timeout: 30000
-    })
-
-    const data = JSON.parse(result.stdout)
-    // 处理 formula 和 cask 两种情况
-    if (data.formulae && data.formulae.length > 0) {
-      return data.formulae[0].versions?.stable || null
-    }
-    if (data.casks && data.casks.length > 0) {
-      return data.casks[0].version || null
-    }
-    return null
-  } catch (error) {
-    console.error(`brew 查询 ${formula} 版本失败:`, error)
-    return null
-  }
-}
-
-/**
- * 获取 brew 可用的 Node.js 版本
- * brew 支持 node (最新), node@20, node@18 等
- */
-async function getBrewNodeVersions(): Promise<Map<string, VersionItem[]>> {
-  const versionsByMajor = new Map<string, VersionItem[]>()
-
-  // 查询各个 formula 的版本
-  const formulas = [
-    { name: BREW_PACKAGES.nodejs.default, majorHint: null },
-    { name: BREW_PACKAGES.nodejs['20'], majorHint: '20' },
-    { name: BREW_PACKAGES.nodejs['18'], majorHint: '18' }
-  ]
-
-  const results = await Promise.allSettled(
-    formulas.map(async (f) => {
-      const version = await getBrewVersion(f.name)
-      return { formula: f.name, majorHint: f.majorHint, version }
-    })
-  )
-
-  for (const result of results) {
-    if (result.status === 'fulfilled' && result.value.version) {
-      const { formula, version } = result.value
-      const major = version.split('.')[0]
-      const majorNum = parseInt(major)
-
-      if (majorNum >= MIN_SUPPORTED_NODE_VERSION) {
-        const label = formula === 'node'
-          ? `Node.js ${version} (最新)`
-          : `Node.js ${version}`
-        addToVersionMap(versionsByMajor, version, label, { groupKey: major })
-      }
-    }
-  }
-
-  return versionsByMajor
-}
-
-// ==================== Linux apt 版本查询 ====================
-
-/**
- * 使用 apt 查询软件可用版本
- * apt 通常只有仓库中的一个版本
- */
-async function getAptVersion(packageName: string): Promise<string | null> {
-  try {
-    const result = await execa('apt-cache', ['policy', packageName], {
-      timeout: 30000
-    })
-
-    // 解析输出,查找候选版本
-    const match = result.stdout.match(/Candidate:\s*(\S+)/)
-    if (match) {
-      // 提取版本号(去除 epoch 和 debian 修订号)
-      const fullVersion = match[1]
-      const versionMatch = fullVersion.match(/(\d+\.\d+\.\d+)/)
-      return versionMatch ? versionMatch[1] : fullVersion
-    }
-    return null
-  } catch (error) {
-    console.error(`apt 查询 ${packageName} 版本失败:`, error)
-    return null
-  }
-}
-
-// ==================== Node.js 版本获取 ====================
-
-/**
- * 从 Node.js API 获取版本列表
- */
-async function getNodeVersionsFromAPI(): Promise<NodejsVersionInfo[]> {
-  const mirror = NODEJS_MIRRORS[getCurrentNodejsMirror()]
-
-  try {
-    const response = await fetch(mirror.versionsUrl, {
-      headers: { 'User-Agent': 'ApqInstaller' },
-      signal: AbortSignal.timeout(15000)
-    })
-
-    if (!response.ok) {
-      throw new Error(`API 请求失败: ${response.status}`)
-    }
-
-    return await response.json() as NodejsVersionInfo[]
-  } catch (error) {
-    console.error(`从 ${mirror.name} 获取 Node.js 版本失败:`, error)
-    return []
-  }
-}
-
-async function getNodeVersionsWindows(): Promise<VersionResult> {
-  const versionsByMajor = new Map<string, VersionItem[]>()
-
-  const versions = await getNodeVersionsFromAPI()
-
-  if (versions.length === 0) {
-    throw new Error(ERROR_MESSAGES.VERSION_FETCH_ERROR)
-  }
-
-  // 用于记录每个主版本的最新 LTS 版本
-  const ltsVersions = new Map<string, string>()
-
-  for (const info of versions) {
-    // 版本格式: v22.11.0 -> 22.11.0
-    const version = info.version.replace(/^v/, '')
-    const major = version.split('.')[0]
-    const majorNum = parseInt(major)
-
-    if (majorNum >= MIN_SUPPORTED_NODE_VERSION) {
-      const isLts = info.lts !== false
-      const ltsName = typeof info.lts === 'string' ? info.lts : null
-
-      // 记录每个主版本的第一个 LTS 版本(最新的)
-      if (isLts && !ltsVersions.has(major)) {
-        ltsVersions.set(major, version)
-      }
-
-      let label = `Node.js ${version}`
-      if (ltsName) {
-        label += ` (${ltsName})`
-      }
-
-      addToVersionMap(versionsByMajor, version, label, {
-        groupKey: major,
-        extra: { lts: isLts }
-      })
-    }
-  }
-
-  if (versionsByMajor.size === 0) {
-    throw new Error(ERROR_MESSAGES.VERSION_FETCH_ERROR)
-  }
-
-  const mirrorName = NODEJS_MIRRORS[getCurrentNodejsMirror()].name
-
-  return {
-    versions: buildVersionList(versionsByMajor),
-    warning: `下载源: ${mirrorName}`
-  }
-}
-
-async function getNodeVersionsMac(): Promise<VersionResult> {
-  const versionsByMajor = await getBrewNodeVersions()
-
-  if (versionsByMajor.size === 0) {
-    throw new Error(ERROR_MESSAGES.VERSION_FETCH_ERROR)
-  }
-
-  return {
-    versions: buildVersionList(versionsByMajor),
-    warning: 'brew 仅支持安装当前可用版本,不支持选择历史版本'
-  }
-}
-
-async function getNodeVersionsLinux(): Promise<VersionResult> {
-  const version = await getAptVersion('nodejs')
-
-  if (!version) {
-    throw new Error(ERROR_MESSAGES.VERSION_FETCH_ERROR)
-  }
-
-  const major = version.split('.')[0]
-  const versionsByMajor = new Map<string, VersionItem[]>()
-  addToVersionMap(versionsByMajor, version, `Node.js ${version}`, { groupKey: major })
-
-  return {
-    versions: buildVersionList(versionsByMajor),
-    warning: 'apt 仅支持安装仓库中的版本,如需其他版本请使用 nvm'
-  }
-}
-
-async function getNodeVersions(): Promise<VersionResult> {
-  const cacheKey = 'versions_nodejs'
-  const cached = getCache<VersionResult>(cacheKey)
-  if (cached) return cached
-
-  const platform = getPlatform()
-  let result: VersionResult
-
-  switch (platform) {
-    case 'win32':
-      result = await getNodeVersionsWindows()
-      break
-    case 'darwin':
-      result = await getNodeVersionsMac()
-      break
-    case 'linux':
-      result = await getNodeVersionsLinux()
-      break
-    default:
-      throw new Error(`${ERROR_MESSAGES.UNSUPPORTED_PLATFORM}: ${platform}`)
-  }
-
-  setCache(cacheKey, result, VERSION_CACHE_TTL)
-  return result
-}
-
-// ==================== VS Code 版本获取 ====================
-
-/**
- * 从 VS Code API 获取版本列表
- */
-async function getVSCodeVersionsFromAPI(): Promise<string[]> {
-  try {
-    const response = await fetch(VSCODE_API.versionsUrl, {
-      headers: { 'User-Agent': 'ApqInstaller' },
-      signal: AbortSignal.timeout(15000)
-    })
-
-    if (!response.ok) {
-      throw new Error(`API 请求失败: ${response.status}`)
-    }
-
-    return await response.json() as string[]
-  } catch (error) {
-    console.error('从 VS Code API 获取版本失败:', error)
-    return []
-  }
-}
-
-/**
- * 获取 VS Code 下载 URL
- */
-export function getVSCodeDownloadUrl(version: string): string {
-  const arch = os.arch() === 'x64' ? 'x64' : os.arch() === 'arm64' ? 'arm64' : 'x86'
-  return VSCODE_API.getDownloadUrl(version, arch, 'user')
-}
-
-async function getVSCodeVersionsWindows(): Promise<VersionResult> {
-  const versionsByMajor = new Map<string, VersionItem[]>()
-
-  const versions = await getVSCodeVersionsFromAPI()
-
-  if (versions.length === 0) {
-    throw new Error(ERROR_MESSAGES.VERSION_FETCH_ERROR)
-  }
-
-  for (const version of versions) {
-    if (/^\d+\.\d+\.\d+$/.test(version)) {
-      addToVersionMap(versionsByMajor, version, `VS Code ${version}`)
-    }
-  }
-
-  if (versionsByMajor.size === 0) {
-    throw new Error(ERROR_MESSAGES.VERSION_FETCH_ERROR)
-  }
-
-  return {
-    versions: buildVersionList(versionsByMajor, {
-      specialVersions: [{ value: 'insiders', label: 'Insiders (预览版)' }]
-    }),
-    warning: null
-  }
-}
-
-async function getVSCodeVersionsMac(): Promise<VersionResult> {
-  const version = await getBrewVersion(BREW_PACKAGES.vscode.stable)
-
-  if (!version) {
-    throw new Error(ERROR_MESSAGES.VERSION_FETCH_ERROR)
-  }
-
-  const versionsByMajor = new Map<string, VersionItem[]>()
-  addToVersionMap(versionsByMajor, version, `VS Code ${version}`)
-
-  return {
-    versions: buildVersionList(versionsByMajor, {
-      specialVersions: [{ value: 'insiders', label: 'Insiders (预览版)' }]
-    }),
-    warning: 'brew 仅支持安装当前最新版本'
-  }
-}
-
-async function getVSCodeVersionsLinux(): Promise<VersionResult> {
-  // Linux 使用 snap 安装 VS Code,snap 不支持版本选择
-  // 返回一个占位版本
-  const versions: VersionItem[] = [
-    { value: 'stable', label: 'VS Code (最新稳定版)' },
-    { value: 'insiders', label: 'Insiders (预览版)' }
-  ]
-
-  return {
-    versions,
-    warning: 'snap 仅支持安装最新版本'
-  }
-}
-
-async function getVSCodeVersions(): Promise<VersionResult> {
-  const cacheKey = 'versions_vscode'
-  const cached = getCache<VersionResult>(cacheKey)
-  if (cached) return cached
-
-  const platform = getPlatform()
-  let result: VersionResult
-
-  switch (platform) {
-    case 'win32':
-      result = await getVSCodeVersionsWindows()
-      break
-    case 'darwin':
-      result = await getVSCodeVersionsMac()
-      break
-    case 'linux':
-      result = await getVSCodeVersionsLinux()
-      break
-    default:
-      throw new Error(`${ERROR_MESSAGES.UNSUPPORTED_PLATFORM}: ${platform}`)
-  }
-
-  setCache(cacheKey, result, VERSION_CACHE_TTL)
-  return result
-}
-
-// ==================== Git 版本获取 ====================
-
-/**
- * 设置 Git 镜像(会持久化保存)
- */
-export function setGitMirror(mirror: GitMirrorType): void {
-  saveGitMirrorConfig(mirror)
-  // 清除版本缓存,以便重新获取
-  clearCache()
-}
-
-/**
- * 获取当前 Git 镜像配置
- */
-export function getGitMirrorConfig(): { mirror: GitMirrorType } {
-  return { mirror: getGitMirrorFromConfig() }
-}
-
-/**
- * 获取当前 Git 镜像类型(内部使用)
- */
-function getCurrentGitMirror(): GitMirrorType {
-  return getGitMirrorFromConfig()
-}
-
-/**
- * 获取 Git 下载 URL
- */
-export function getGitDownloadUrl(version: string): string {
-  const arch = os.arch() === 'x64' ? '64' : '32'
-  const mirror = GIT_MIRRORS[getCurrentGitMirror()]
-  return mirror.getDownloadUrl(version, arch)
-}
-
-// 备用版本列表(当无法从任何源获取时使用)
-const FALLBACK_GIT_VERSIONS = [
-  '2.47.1', '2.47.0',
-  '2.46.2', '2.46.1', '2.46.0',
-  '2.45.2', '2.45.1', '2.45.0',
-  '2.44.0',
-  '2.43.0',
-  '2.42.0'
-]
-
-/**
- * 从 GitHub API 获取版本列表
- */
-async function getGitVersionsFromGitHub(): Promise<string[]> {
-  try {
-    const response = await fetch('https://api.github.com/repos/git-for-windows/git/releases', {
-      headers: {
-        'Accept': 'application/vnd.github.v3+json',
-        'User-Agent': 'ApqInstaller'
-      },
-      signal: AbortSignal.timeout(10000)
-    })
-
-    if (!response.ok) {
-      throw new Error(`GitHub API 请求失败: ${response.status}`)
-    }
-
-    const releases = await response.json() as Array<{ tag_name: string; prerelease: boolean }>
-    const versions: string[] = []
-
-    for (const release of releases) {
-      if (release.prerelease) continue
-      const match = release.tag_name.match(/^v?(\d+\.\d+\.\d+)/)
-      if (match && !versions.includes(match[1])) {
-        versions.push(match[1])
-      }
-    }
-
-    return versions
-  } catch (error) {
-    console.error('从 GitHub 获取 Git 版本失败:', error)
-    return []
-  }
-}
-
-/**
- * 从华为云镜像获取版本列表
- * 通过解析目录页面获取可用版本
- */
-async function getGitVersionsFromHuaweicloud(): Promise<string[]> {
-  try {
-    const response = await fetch('https://mirrors.huaweicloud.com/git-for-windows/', {
-      headers: { 'User-Agent': 'ApqInstaller' },
-      signal: AbortSignal.timeout(10000)
-    })
-
-    if (!response.ok) {
-      throw new Error(`华为云镜像请求失败: ${response.status}`)
-    }
-
-    const html = await response.text()
-    const versions: string[] = []
-
-    // 解析 HTML 页面,查找版本目录链接
-    // 格式: <a href="v2.47.1.windows.1/">v2.47.1.windows.1/</a>
-    const regex = /href="v(\d+\.\d+\.\d+)\.windows\.\d+\/"/g
-    let match
-    while ((match = regex.exec(html)) !== null) {
-      const version = match[1]
-      if (!versions.includes(version)) {
-        versions.push(version)
-      }
-    }
-
-    // 按版本号降序排序
-    versions.sort((a, b) => {
-      const partsA = a.split('.').map(Number)
-      const partsB = b.split('.').map(Number)
-      for (let i = 0; i < 3; i++) {
-        if (partsA[i] !== partsB[i]) {
-          return partsB[i] - partsA[i]
-        }
-      }
-      return 0
-    })
-
-    return versions
-  } catch (error) {
-    console.error('从华为云镜像获取 Git 版本失败:', error)
-    return []
-  }
-}
-
-async function getGitVersionsWindows(): Promise<VersionResult> {
-  const versionsByMajor = new Map<string, VersionItem[]>()
-
-  // 根据当前镜像源获取版本列表
-  let versions: string[] = []
-  const currentMirror = getCurrentGitMirror()
-
-  if (currentMirror === 'github') {
-    versions = await getGitVersionsFromGitHub()
-  } else {
-    // 华为云镜像
-    versions = await getGitVersionsFromHuaweicloud()
-  }
-
-  // 如果获取失败,使用备用版本列表
-  if (versions.length === 0) {
-    console.log('使用备用 Git 版本列表')
-    versions = FALLBACK_GIT_VERSIONS
-  }
-
-  for (const version of versions) {
-    if (/^\d+\.\d+\.\d+/.test(version)) {
-      addToVersionMap(versionsByMajor, version, `Git ${version}`)
-    }
-  }
-
-  if (versionsByMajor.size === 0) {
-    throw new Error(ERROR_MESSAGES.VERSION_FETCH_ERROR)
-  }
-
-  const mirrorName = GIT_MIRRORS[getCurrentGitMirror()].name
-
-  return {
-    versions: buildVersionList(versionsByMajor, {
-      specialVersions: [
-        { value: 'mingit', label: 'MinGit (精简版)' },
-        { value: 'lfs', label: 'Git LFS (大文件支持)' }
-      ]
-    }),
-    warning: `下载源: ${mirrorName}`
-  }
-}
-
-async function getGitVersionsMac(): Promise<VersionResult> {
-  const version = await getBrewVersion(BREW_PACKAGES.git.stable)
-
-  if (!version) {
-    throw new Error(ERROR_MESSAGES.VERSION_FETCH_ERROR)
-  }
-
-  const versionsByMajor = new Map<string, VersionItem[]>()
-  addToVersionMap(versionsByMajor, version, `Git ${version}`)
-
-  return {
-    versions: buildVersionList(versionsByMajor, {
-      specialVersions: [{ value: 'lfs', label: 'Git LFS (大文件支持)' }]
-    }),
-    warning: 'brew 仅支持安装当前最新版本'
-  }
-}
-
-async function getGitVersionsLinux(): Promise<VersionResult> {
-  const version = await getAptVersion('git')
-
-  if (!version) {
-    throw new Error(ERROR_MESSAGES.VERSION_FETCH_ERROR)
-  }
-
-  const versionsByMajor = new Map<string, VersionItem[]>()
-  addToVersionMap(versionsByMajor, version, `Git ${version}`)
-
-  return {
-    versions: buildVersionList(versionsByMajor, {
-      specialVersions: [{ value: 'lfs', label: 'Git LFS (大文件支持)' }]
-    }),
-    warning: 'apt 仅支持安装仓库中的版本'
-  }
-}
-
-async function getGitVersions(): Promise<VersionResult> {
-  const cacheKey = 'versions_git'
-  const cached = getCache<VersionResult>(cacheKey)
-  if (cached) return cached
-
-  const platform = getPlatform()
-  let result: VersionResult
-
-  switch (platform) {
-    case 'win32':
-      result = await getGitVersionsWindows()
-      break
-    case 'darwin':
-      result = await getGitVersionsMac()
-      break
-    case 'linux':
-      result = await getGitVersionsLinux()
-      break
-    default:
-      throw new Error(`${ERROR_MESSAGES.UNSUPPORTED_PLATFORM}: ${platform}`)
-  }
-
-  setCache(cacheKey, result, VERSION_CACHE_TTL)
-  return result
-}
-
-// ==================== 统一导出 ====================
-
-export async function getVersions(software: SoftwareType): Promise<VersionResult> {
-  // Linux 下获取版本前先更新 apt 源(带缓存,1天内只更新一次)
-  const platform = getPlatform()
-  if (platform === 'linux') {
-    await updateAptSourceForQuery()
-  }
-  // Windows 使用 API 获取版本,不需要更新源
-  // macOS 的 brew 不需要手动更新源,brew info 会自动获取最新信息
-
-  switch (software) {
-    case 'nodejs':
-      return await getNodeVersions()
-    case 'vscode':
-      return await getVSCodeVersions()
-    case 'git':
-      return await getGitVersions()
-    default:
-      return { versions: [], warning: ERROR_MESSAGES.UNKNOWN_SOFTWARE }
-  }
-}
-
-export { clearCache }

+ 0 - 93
electron/modules/vscode-extension.ts

@@ -1,93 +0,0 @@
-// electron/modules/vscode-extension.ts - VS Code 插件安装模块
-
-import { BrowserWindow } from 'electron'
-import { execa } from 'execa'
-import { getVscodeCliPath } from './utils'
-import logger from './logger'
-
-/**
- * 发送状态到渲染进程
- */
-function sendToRenderer(channel: string, data: unknown): void {
-  const windows = BrowserWindow.getAllWindows()
-  windows.forEach((win) => {
-    win.webContents.send(channel, data)
-  })
-}
-
-/**
- * 安装 VS Code 插件
- * 供 IPC handler 和 installer.ts 复用
- * @param extensionId 插件 ID
- * @returns 安装结果
- */
-export async function installVscodeExtension(extensionId: string): Promise<{ success: boolean; error?: string }> {
-  try {
-    const codePath = await getVscodeCliPath()
-    const fullCommand = `${codePath} --install-extension ${extensionId}`
-    logger.installInfo(`开始安装 VS Code 插件: ${extensionId},使用 CLI: ${codePath}`)
-    // 使用 install-status 事件发送日志,这样可以被 App.vue 中的监听器捕获
-    sendToRenderer('install-status', {
-      software: 'vscode',
-      message: `正在安装 VS Code 插件: ${extensionId}...`,
-      progress: 30,
-      i18nKey: 'log.vscodeExtInstalling',
-      i18nParams: { extensionId }
-    })
-    // 发送执行命令的日志
-    sendToRenderer('install-status', {
-      software: 'vscode',
-      message: `执行命令: ${fullCommand}`,
-      progress: 40,
-      i18nKey: 'log.executingCommand',
-      i18nParams: { command: fullCommand }
-    })
-    const result = await execa(codePath, ['--install-extension', extensionId], {
-      encoding: 'utf8',
-      stdout: 'pipe',
-      stderr: 'pipe'
-    })
-    // 记录命令输出
-    if (result.stdout) {
-      logger.installInfo(`VS Code 插件安装输出: ${result.stdout}`)
-      sendToRenderer('install-status', {
-        software: 'vscode',
-        message: `code 输出: ${result.stdout}`,
-        progress: 80,
-        i18nKey: 'log.vscodeExtOutput',
-        i18nParams: { output: result.stdout }
-      })
-    }
-    logger.installInfo(`VS Code 插件安装成功: ${extensionId}`)
-    sendToRenderer('install-status', {
-      software: 'vscode',
-      message: `✅ VS Code 插件安装成功: ${extensionId}`,
-      progress: 100,
-      i18nKey: 'log.vscodeExtInstallSuccess',
-      i18nParams: { extensionId }
-    })
-    return { success: true }
-  } catch (error) {
-    const execaError = error as { message?: string; stderr?: string; shortMessage?: string }
-    // 过滤乱码字符,只保留可读字符(ASCII 可打印字符和中文)
-    let errorMessage = execaError.shortMessage || execaError.message || '未知错误'
-    if (execaError.stderr) {
-      const stderrClean = execaError.stderr.replace(/[^\x20-\x7E\u4e00-\u9fa5\n\r]/g, '').trim()
-      if (stderrClean) {
-        errorMessage = stderrClean
-      }
-    }
-    // 从错误消息中也过滤乱码
-    errorMessage = errorMessage.replace(/[^\x20-\x7E\u4e00-\u9fa5\n\r:.\-_/\\]/g, '').trim()
-    logger.installError(`VS Code 插件安装失败: ${extensionId}`, error)
-    // 发送错误到渲染进程的安装日志
-    sendToRenderer('install-status', {
-      software: 'vscode',
-      message: `❌ VS Code 插件安装失败: ${errorMessage}`,
-      progress: 0,
-      i18nKey: 'log.vscodeExtInstallFailed',
-      i18nParams: { extensionId, error: errorMessage }
-    })
-    return { success: false, error: errorMessage }
-  }
-}

+ 0 - 170
electron/preload.ts

@@ -1,170 +0,0 @@
-// electron/preload.ts - Electron 预加载脚本
-
-import { contextBridge, ipcRenderer } from 'electron'
-import type { ElectronAPI } from './modules/types'
-
-// 向渲染进程暴露安全的 API
-const electronAPI: ElectronAPI = {
-  // ==================== 安装相关 ====================
-
-  // 安装指定软件
-  install: (software, options) => ipcRenderer.invoke('install', software, options),
-
-  // 取消安装
-  cancelInstall: () => ipcRenderer.invoke('cancel-install'),
-
-  // 检测软件是否已安装
-  checkInstalled: (software) => ipcRenderer.invoke('check-installed', software),
-
-  // 卸载软件
-  uninstall: (software) => ipcRenderer.invoke('uninstall', software),
-
-  // ==================== 系统检测 ====================
-
-  // 检测管理员权限
-  checkAdmin: () => ipcRenderer.invoke('check-admin'),
-
-  // 检测包管理器
-  checkPackageManager: () => ipcRenderer.invoke('check-package-manager'),
-
-  // 安装包管理器
-  installPackageManager: (manager) => ipcRenderer.invoke('install-package-manager', manager),
-
-  // 获取平台信息
-  getPlatform: () => ipcRenderer.invoke('get-platform'),
-
-  // 检测网络连接
-  checkNetwork: () => ipcRenderer.invoke('check-network'),
-
-  // ==================== 版本 ====================
-
-  // 获取软件版本列表
-  getVersions: (software) => ipcRenderer.invoke('get-versions', software),
-
-  // 检查更新
-  checkUpdate: (software) => ipcRenderer.invoke('check-update', software),
-
-  // ==================== Git 镜像配置 ====================
-
-  // 设置 Git 镜像
-  setGitMirror: (mirror) => ipcRenderer.invoke('set-git-mirror', mirror),
-
-  // 获取 Git 镜像配置
-  getGitMirrorConfig: () => ipcRenderer.invoke('get-git-mirror-config'),
-
-  // ==================== Node.js 镜像配置 ====================
-
-  // 设置 Node.js 镜像
-  setNodejsMirror: (mirror) => ipcRenderer.invoke('set-nodejs-mirror', mirror),
-
-  // 获取 Node.js 镜像配置
-  getNodejsMirrorConfig: () => ipcRenderer.invoke('get-nodejs-mirror-config'),
-
-  // ==================== 历史和日志 ====================
-
-  // 获取安装历史
-  getInstallHistory: (limit) => ipcRenderer.invoke('get-install-history', limit),
-
-  // 获取日志
-  getLogs: () => ipcRenderer.invoke('get-logs'),
-
-  // 写入安装日志
-  writeInstallLog: (message: string, level?: 'info' | 'warn' | 'error') => ipcRenderer.invoke('write-install-log', message, level),
-
-  // 获取日志文件路径
-  getLogPaths: () => ipcRenderer.invoke('get-log-paths'),
-
-  // ==================== 窗口操作 ====================
-
-  // 设置窗口标题
-  setWindowTitle: (title) => ipcRenderer.invoke('set-window-title', title),
-
-  // ==================== Claude Code ====================
-
-  // 检测 Claude Code 是否已安装
-  checkClaudeCode: () => ipcRenderer.invoke('check-claude-code'),
-
-  // 启动 Claude Code (打开 Git Bash 并执行 claude 命令)
-  launchClaudeCode: () => ipcRenderer.invoke('launch-claude-code'),
-
-  // 安装 Claude Code
-  installClaudeCode: () => ipcRenderer.invoke('install-claude-code'),
-
-  // ==================== VS Code Extensions ====================
-
-  // 检查 VS Code 插件是否已安装
-  checkVscodeExtension: (extensionId: string) => ipcRenderer.invoke('check-vscode-extension', extensionId),
-
-  // 安装 VS Code 插件
-  installVscodeExtension: (extensionId: string) => ipcRenderer.invoke('install-vscode-extension', extensionId),
-
-  // 选择文件夹
-  selectDirectory: (defaultPath?: string) => ipcRenderer.invoke('select-directory', defaultPath),
-
-  // 窗口最小化
-  windowMinimize: () => ipcRenderer.invoke('window-minimize'),
-
-  // 窗口最大化/还原
-  windowMaximize: () => ipcRenderer.invoke('window-maximize'),
-
-  // 关闭窗口
-  windowClose: () => ipcRenderer.invoke('window-close'),
-
-  // 获取窗口最大化状态
-  windowIsMaximized: () => ipcRenderer.invoke('window-is-maximized'),
-
-  // ==================== 事件监听 ====================
-
-  // 监听安装状态
-  onInstallStatus: (callback) => {
-    ipcRenderer.on('install-status', (_event, data) => callback(data))
-  },
-
-  // 监听安装完成
-  onInstallComplete: (callback) => {
-    ipcRenderer.on('install-complete', (_event, data) => callback(data))
-  },
-
-  // 监听安装错误
-  onInstallError: (callback) => {
-    ipcRenderer.on('install-error', (_event, data) => callback(data))
-  },
-
-  // 监听网络状态变化
-  onNetworkChange: (callback) => {
-    ipcRenderer.on('network-change', (_event, online) => callback(online))
-  },
-
-  // 移除所有监听(避免内存泄漏)
-  removeAllListeners: () => {
-    ipcRenderer.removeAllListeners('install-status')
-    ipcRenderer.removeAllListeners('install-complete')
-    ipcRenderer.removeAllListeners('install-error')
-    ipcRenderer.removeAllListeners('network-change')
-    ipcRenderer.removeAllListeners('updater:status')
-  },
-
-  // ==================== 自动更新 ====================
-
-  // 检查应用更新
-  updaterCheck: () => ipcRenderer.invoke('updater:check'),
-
-  // 下载更新
-  updaterDownload: () => ipcRenderer.invoke('updater:download'),
-
-  // 安装更新并重启
-  updaterInstall: () => ipcRenderer.invoke('updater:install'),
-
-  // 获取当前版本
-  updaterVersion: () => ipcRenderer.invoke('updater:version'),
-
-  // 检测是否为 Portable 模式
-  updaterIsPortable: () => ipcRenderer.invoke('updater:is-portable'),
-
-  // 监听更新状态
-  onUpdaterStatus: (callback) => {
-    ipcRenderer.on('updater:status', (_event, data) => callback(data))
-  }
-}
-
-contextBridge.exposeInMainWorld('electronAPI', electronAPI)