Pārlūkot izejas kodu

优化使用pnpm安装ClaudeCode

黄中银 3 nedēļas atpakaļ
vecāks
revīzija
09e301d020

+ 28 - 13
ApqInstaller/electron/modules/installer.ts

@@ -480,11 +480,16 @@ export async function installNodejs(
     logger.info(`开始下载 Node.js: ${downloadUrl}`)
 
     try {
+      let lastReportedPercent = 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)
-        onStatus('nodejs', `正在下载 Node.js ${targetVersion}... (${downloadedMB}MB / ${totalMB}MB)`, progress)
+        // 每 5% 报告一次进度,减少日志量
+        if (percent - lastReportedPercent >= 5 || percent >= 100) {
+          lastReportedPercent = Math.floor(percent / 5) * 5
+          const progress = 10 + Math.round(percent * 0.4)
+          const downloadedMB = (downloaded / 1024 / 1024).toFixed(1)
+          const totalMB = (total / 1024 / 1024).toFixed(1)
+          onStatus('nodejs', `正在下载 Node.js ${targetVersion}... (${downloadedMB}MB / ${totalMB}MB)`, progress)
+        }
       })
     } catch (error) {
       logger.error('下载 Node.js 失败', error)
@@ -624,11 +629,16 @@ export async function installVscode(version = 'stable', onStatus: StatusCallback
     logger.info(`开始下载 VS Code: ${downloadUrl}`)
 
     try {
+      let lastReportedPercent = 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)
-        onStatus('vscode', `正在下载 VS Code ${targetVersion}... (${downloadedMB}MB / ${totalMB}MB)`, progress)
+        // 每 5% 报告一次进度,减少日志量
+        if (percent - lastReportedPercent >= 5 || percent >= 100) {
+          lastReportedPercent = Math.floor(percent / 5) * 5
+          const progress = 10 + Math.round(percent * 0.5)
+          const downloadedMB = (downloaded / 1024 / 1024).toFixed(1)
+          const totalMB = (total / 1024 / 1024).toFixed(1)
+          onStatus('vscode', `正在下载 VS Code ${targetVersion}... (${downloadedMB}MB / ${totalMB}MB)`, progress)
+        }
       })
     } catch (error) {
       logger.error('下载 VS Code 失败', error)
@@ -726,12 +736,17 @@ export async function installGit(version = 'stable', onStatus: StatusCallback, c
     logger.info(`开始下载 Git: ${downloadUrl}`)
 
     try {
+      let lastReportedPercent = 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)
-        onStatus('git', `正在下载 Git ${targetVersion}... (${downloadedMB}MB / ${totalMB}MB)`, progress)
+        // 每 5% 报告一次进度,减少日志量
+        if (percent - lastReportedPercent >= 5 || percent >= 100) {
+          lastReportedPercent = Math.floor(percent / 5) * 5
+          // 下载进度占 10% - 60%
+          const progress = 10 + Math.round(percent * 0.5)
+          const downloadedMB = (downloaded / 1024 / 1024).toFixed(1)
+          const totalMB = (total / 1024 / 1024).toFixed(1)
+          onStatus('git', `正在下载 Git ${targetVersion}... (${downloadedMB}MB / ${totalMB}MB)`, progress)
+        }
       })
     } catch (error) {
       logger.error('下载 Git 失败', error)

+ 90 - 19
ApqInstaller/electron/modules/ipc-handlers.ts

@@ -19,7 +19,8 @@ import {
   installGit,
   installAll,
   uninstallSoftware,
-  getNpmPath
+  getNpmPath,
+  getPnpmPath
 } from './installer'
 import { addInstallHistory, getInstallHistory } from './config'
 import logger from './logger'
@@ -288,9 +289,57 @@ export function registerHandlers(): void {
 
     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
+    let extraPaths = npmGlobalBinUnix
+    if (nodePath) {
+      const nodePathUnix = nodePath.replace(/\\/g, '/').replace(/^([A-Za-z]):/, '/$1')
+      extraPaths = `${nodePathUnix}:${npmGlobalBinUnix}`
+      logger.info(`Node.js 路径: ${nodePath} -> ${nodePathUnix}`)
+    } else {
+      logger.warn('未找到 Node.js 路径,可能会导致 claude 命令无法运行')
+    }
+
     // 使用 execa 启动 Git Bash 并执行 claude 命令
-    // 先将 npm 全局路径添加到 PATH,然后执行 claude 命令
-    const bashCommand = `export PATH="${npmGlobalBinUnix}:$PATH"; claude; exec bash`
+    // 先将 Node.js 和 npm 全局路径添加到 PATH,然后执行 claude 命令
+    const bashCommand = `export PATH="${extraPaths}:$PATH"; claude; exec bash`
     execa(gitBashPath, ['-c', bashCommand], {
       detached: true,
       stdio: 'ignore'
@@ -299,7 +348,7 @@ export function registerHandlers(): void {
     return { success: true }
   })
 
-  // 安装 Claude Code (通过 npm 全局安装)
+  // 安装 Claude Code (通过 pnpm 或 npm 全局安装)
   registerHandler('install-claude-code', async () => {
     try {
       logger.info('开始安装 Claude Code...')
@@ -310,11 +359,15 @@ export function registerHandlers(): void {
         i18nKey: 'log.installingClaudeCode'
       })
 
-      // 使用完整的 npm 路径,避免 PATH 未生效的问题
-      const npmPath = getNpmPath()
+      // 检测是否已安装 pnpm,优先使用 pnpm
+      const { commandExistsWithRefresh } = await import('./utils')
+      const hasPnpm = await commandExistsWithRefresh('pnpm')
+      const pkgManagerPath = hasPnpm ? getPnpmPath() : getNpmPath()
+      const pkgManagerName = hasPnpm ? 'pnpm' : 'npm'
+
       const installArgs = ['install', '-g', '@anthropic-ai/claude-code']
-      const fullCommand = `${npmPath} ${installArgs.join(' ')}`
-      logger.info(`执行命令: ${fullCommand}`)
+      const fullCommand = `${pkgManagerPath} ${installArgs.join(' ')}`
+      logger.info(`使用 ${pkgManagerName} 安装,执行命令: ${fullCommand}`)
 
       // 发送执行命令的日志
       sendToRenderer('install-status', {
@@ -325,8 +378,8 @@ export function registerHandlers(): void {
         i18nParams: { command: fullCommand }
       })
 
-      // 使用 npm 全局安装 @anthropic-ai/claude-code
-      const result = await execa(npmPath, installArgs, {
+      // 使用 pnpm 或 npm 全局安装 @anthropic-ai/claude-code
+      const result = await execa(pkgManagerPath, installArgs, {
         encoding: 'utf8',
         // 捕获输出以便记录日志
         stdout: 'pipe',
@@ -335,20 +388,20 @@ export function registerHandlers(): void {
 
       // 记录安装输出
       if (result.stdout) {
-        logger.info(`npm install stdout: ${result.stdout}`)
+        logger.info(`${pkgManagerName} install stdout: ${result.stdout}`)
         sendToRenderer('install-status', {
           software: 'claudeCode',
-          message: `npm 输出: ${result.stdout}`,
+          message: `${pkgManagerName} 输出: ${result.stdout}`,
           progress: 80,
           i18nKey: 'log.npmInstallStdout',
           i18nParams: { output: result.stdout }
         })
       }
       if (result.stderr) {
-        logger.warn(`npm install stderr: ${result.stderr}`)
+        logger.warn(`${pkgManagerName} install stderr: ${result.stderr}`)
         sendToRenderer('install-status', {
           software: 'claudeCode',
-          message: `npm 警告: ${result.stderr}`,
+          message: `${pkgManagerName} 警告: ${result.stderr}`,
           progress: 85,
           i18nKey: 'log.npmInstallStderr',
           i18nParams: { output: result.stderr }
@@ -362,8 +415,8 @@ export function registerHandlers(): void {
         i18nKey: 'log.installComplete'
       })
 
-      // 验证安装 - 使用 commandExistsWithRefresh 来刷新 PATH
-      const { commandExistsWithRefresh, getCommandVersionWithRefresh } = await import('./utils')
+      // 验证安装 - 使用 getCommandVersionWithRefresh 来刷新 PATH
+      const { getCommandVersionWithRefresh } = await import('./utils')
       const claudeExists = await commandExistsWithRefresh('claude')
 
       if (claudeExists) {
@@ -412,7 +465,11 @@ export function registerHandlers(): void {
   // 检查 VS Code 插件是否已安装
   registerHandler('check-vscode-extension', async (_event, extensionId: string) => {
     try {
-      const result = await execa('code', ['--list-extensions'])
+      const result = await execa('code', ['--list-extensions'], {
+        encoding: 'utf8',
+        stdout: 'pipe',
+        stderr: 'pipe'
+      })
       const extensions = result.stdout.split('\n').map(ext => ext.trim().toLowerCase())
       const installed = extensions.includes(extensionId.toLowerCase())
       logger.info(`检查 VS Code 插件 ${extensionId}: ${installed ? '已安装' : '未安装'}`)
@@ -427,11 +484,25 @@ export function registerHandlers(): void {
   registerHandler('install-vscode-extension', async (_event, extensionId: string) => {
     try {
       logger.info(`开始安装 VS Code 插件: ${extensionId}`)
-      await execa('code', ['--install-extension', extensionId])
+      await execa('code', ['--install-extension', extensionId], {
+        encoding: 'utf8',
+        stdout: 'pipe',
+        stderr: 'pipe'
+      })
       logger.info(`VS Code 插件安装成功: ${extensionId}`)
       return { success: true }
     } catch (error) {
-      const errorMessage = (error as Error).message
+      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.error(`VS Code 插件安装失败: ${extensionId}`, error)
       return { success: false, error: errorMessage }
     }