Преглед изворни кода

Windows 上执行 .cmd 文件时,使用 shell: true 避免参数解析问题

黄中银 пре 3 недеља
родитељ
комит
660fdfd920

+ 31 - 29
ApqInstaller/electron/modules/installer.ts

@@ -32,10 +32,10 @@ export function cancelInstall(): boolean {
     installCancelled = true
     try {
       currentProcess.kill('SIGTERM')
-      logger.info('安装已取消')
+      logger.installInfo('安装已取消')
       return true
     } catch (error) {
-      logger.error('取消安装失败', error)
+      logger.installError('取消安装失败', error)
       return false
     }
   }
@@ -172,7 +172,7 @@ async function executeCommand(
   const quotedArgs = args.map(arg => quoteIfNeeded(arg))
   const fullCommand = `${quotedCommand} ${quotedArgs.join(' ')}`
 
-  logger.debug(`执行命令: ${fullCommand}`)
+  logger.installDebug(`执行命令: ${fullCommand}`)
 
   try {
     if (needAdmin) {
@@ -186,14 +186,16 @@ async function executeCommand(
       }
     } else {
       // 无需提权,直接执行
-      const proc = execa(command, args, { stdio: 'pipe' })
+      // 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.error(`命令执行失败: ${fullCommand}`, error)
+    logger.installError(`命令执行失败: ${fullCommand}`, error)
     throw error
   }
 }
@@ -431,7 +433,7 @@ export type ErrorCallback = (software: SoftwareTypeWithAll, message: string) =>
 export async function aptUpdate(onStatus?: StatusCallback, software?: SoftwareTypeWithAll): Promise<void> {
   if (os.platform() !== 'linux') return
   if (!needsAptUpdate()) {
-    logger.info('apt 源已在缓存期内,跳过更新')
+    logger.installInfo('apt 源已在缓存期内,跳过更新')
     return
   }
 
@@ -477,7 +479,7 @@ export async function installNodejs(
     const installerPath = path.join(tempDir, `node-v${targetVersion}-${arch}.msi`)
 
     onStatus('nodejs', `正在下载 Node.js ${targetVersion}...`, 10)
-    logger.info(`开始下载 Node.js: ${downloadUrl}`)
+    logger.installInfo(`开始下载 Node.js: ${downloadUrl}`)
 
     try {
       let lastReportedPercent = 0
@@ -492,7 +494,7 @@ export async function installNodejs(
         }
       })
     } catch (error) {
-      logger.error('下载 Node.js 失败', error)
+      logger.installError('下载 Node.js 失败', error)
       throw new Error(`下载 Node.js 失败: ${(error as Error).message}`)
     }
 
@@ -500,7 +502,7 @@ export async function installNodejs(
 
     // 执行 msi 静默安装
     onStatus('nodejs', `${STATUS_MESSAGES.INSTALLING} Node.js ${targetVersion}...`, 55)
-    logger.info(`开始安装 Node.js: ${installerPath}`)
+    logger.installInfo(`开始安装 Node.js: ${installerPath}`)
 
     try {
       // msiexec 静默安装参数
@@ -518,7 +520,7 @@ export async function installNodejs(
         // 忽略清理失败
       }
     } catch (error) {
-      logger.error('安装 Node.js 失败', error)
+      logger.installError('安装 Node.js 失败', error)
       throw error
     }
 
@@ -532,7 +534,7 @@ export async function installNodejs(
     try {
       await executeCommand(npmCmd, ['config', 'set', 'registry', NPM_REGISTRY], false)
     } catch (error) {
-      logger.warn('配置 npm 镜像失败(npm 可能还未加入 PATH)', error)
+      logger.installWarn('配置 npm 镜像失败(npm 可能还未加入 PATH)', error)
     }
 
     checkCancelled()
@@ -550,7 +552,7 @@ export async function installNodejs(
       try {
         await executeCommand(pnpmCmd, ['config', 'set', 'registry', NPM_REGISTRY], false)
       } catch (error) {
-        logger.warn('配置 pnpm 镜像失败', error)
+        logger.installWarn('配置 pnpm 镜像失败', error)
       }
     }
 
@@ -573,7 +575,7 @@ export async function installNodejs(
   try {
     await executeCommand(npmCmd, ['config', 'set', 'registry', NPM_REGISTRY], false)
   } catch (error) {
-    logger.warn('配置 npm 镜像失败(npm 可能还未加入 PATH)', error)
+    logger.installWarn('配置 npm 镜像失败(npm 可能还未加入 PATH)', error)
   }
 
   checkCancelled()
@@ -591,7 +593,7 @@ export async function installNodejs(
     try {
       await executeCommand(pnpmCmd, ['config', 'set', 'registry', NPM_REGISTRY], false)
     } catch (error) {
-      logger.warn('配置 pnpm 镜像失败', error)
+      logger.installWarn('配置 pnpm 镜像失败', error)
     }
   }
 
@@ -626,7 +628,7 @@ export async function installVscode(version = 'stable', onStatus: StatusCallback
     const installerPath = path.join(tempDir, `VSCodeUserSetup-${targetVersion}.exe`)
 
     onStatus('vscode', `正在下载 VS Code ${targetVersion}...`, 10)
-    logger.info(`开始下载 VS Code: ${downloadUrl}`)
+    logger.installInfo(`开始下载 VS Code: ${downloadUrl}`)
 
     try {
       let lastReportedPercent = 0
@@ -641,7 +643,7 @@ export async function installVscode(version = 'stable', onStatus: StatusCallback
         }
       })
     } catch (error) {
-      logger.error('下载 VS Code 失败', error)
+      logger.installError('下载 VS Code 失败', error)
       throw new Error(`下载 VS Code 失败: ${(error as Error).message}`)
     }
 
@@ -649,7 +651,7 @@ export async function installVscode(version = 'stable', onStatus: StatusCallback
 
     // 执行静默安装
     onStatus('vscode', `${STATUS_MESSAGES.INSTALLING} VS Code ${targetVersion}...`, 65)
-    logger.info(`开始安装 VS Code: ${installerPath}`)
+    logger.installInfo(`开始安装 VS Code: ${installerPath}`)
 
     try {
       // VS Code 安装程序支持的静默安装参数
@@ -672,7 +674,7 @@ export async function installVscode(version = 'stable', onStatus: StatusCallback
 
       onStatus('vscode', STATUS_MESSAGES.COMPLETE, 100)
     } catch (error) {
-      logger.error('安装 VS Code 失败', error)
+      logger.installError('安装 VS Code 失败', error)
       throw error
     }
 
@@ -719,7 +721,7 @@ export async function installGit(version = 'stable', onStatus: StatusCallback, c
           }
         }
       } catch (error) {
-        logger.warn('获取最新 Git 版本失败,使用备用版本', error)
+        logger.installWarn('获取最新 Git 版本失败,使用备用版本', error)
         // 使用备用版本
         targetVersion = FALLBACK_VERSIONS.git
       }
@@ -733,7 +735,7 @@ export async function installGit(version = 'stable', onStatus: StatusCallback, c
     const installerPath = path.join(tempDir, `Git-${targetVersion}-installer.exe`)
 
     onStatus('git', `正在下载 Git ${targetVersion}...`, 10)
-    logger.info(`开始下载 Git: ${downloadUrl}`)
+    logger.installInfo(`开始下载 Git: ${downloadUrl}`)
 
     try {
       let lastReportedPercent = 0
@@ -749,7 +751,7 @@ export async function installGit(version = 'stable', onStatus: StatusCallback, c
         }
       })
     } catch (error) {
-      logger.error('下载 Git 失败', error)
+      logger.installError('下载 Git 失败', error)
       throw new Error(`下载 Git 失败: ${(error as Error).message}`)
     }
 
@@ -757,7 +759,7 @@ export async function installGit(version = 'stable', onStatus: StatusCallback, c
 
     // 执行静默安装
     onStatus('git', `${STATUS_MESSAGES.INSTALLING} Git ${targetVersion}...`, 65)
-    logger.info(`开始安装 Git: ${installerPath}`)
+    logger.installInfo(`开始安装 Git: ${installerPath}`)
 
     try {
       // Git 安装程序支持的静默安装参数
@@ -783,7 +785,7 @@ export async function installGit(version = 'stable', onStatus: StatusCallback, c
 
       onStatus('git', STATUS_MESSAGES.COMPLETE, 100)
     } catch (error) {
-      logger.error('安装 Git 失败', error)
+      logger.installError('安装 Git 失败', error)
       throw error
     }
 
@@ -808,16 +810,16 @@ export async function installGit(version = 'stable', onStatus: StatusCallback, c
 export async function uninstallSoftware(software: SoftwareType): Promise<boolean> {
   // 只有 nodejs, vscode, git 支持卸载
   if (software !== 'nodejs' && software !== 'vscode' && software !== 'git') {
-    logger.warn(`${software} 不支持通过此方式卸载`)
+    logger.installWarn(`${software} 不支持通过此方式卸载`)
     return false
   }
   try {
     const args = getUninstallArgs(software)
     await executeCommand(args.command, args.args, true)
-    logger.info(`${software} 卸载成功`)
+    logger.installInfo(`${software} 卸载成功`)
     return true
   } catch (error) {
-    logger.error(`${software} 卸载失败`, error)
+    logger.installError(`${software} 卸载失败`, error)
     return false
   }
 }
@@ -901,7 +903,7 @@ export async function installAll(options: InstallOptions, onStatus: StatusCallba
     try {
       await executeCommand(npmCmd, ['config', 'set', 'registry', NPM_REGISTRY], false)
     } catch (error) {
-      logger.warn('配置 npm 镜像失败', error)
+      logger.installWarn('配置 npm 镜像失败', error)
     }
     currentStep++
 
@@ -915,7 +917,7 @@ export async function installAll(options: InstallOptions, onStatus: StatusCallba
       try {
         await executeCommand(pnpmCmd, ['config', 'set', 'registry', NPM_REGISTRY], false)
       } catch (error) {
-        logger.warn('配置 pnpm 镜像失败', error)
+        logger.installWarn('配置 pnpm 镜像失败', error)
       }
       currentStep++
     }
@@ -988,7 +990,7 @@ export async function installAll(options: InstallOptions, onStatus: StatusCallba
             }
           }
         } catch (error) {
-          logger.warn('获取最新 Git 版本失败,使用备用版本', error)
+          logger.installWarn('获取最新 Git 版本失败,使用备用版本', error)
           targetVersion = FALLBACK_VERSIONS.git
         }
       }

+ 5 - 1
ApqInstaller/electron/modules/ipc-handlers.ts

@@ -401,11 +401,15 @@ export function registerHandlers(): void {
       })
 
       // 使用 pnpm 或 npm 全局安装 @anthropic-ai/claude-code
+      // Windows 上执行 .cmd 文件时,使用 shell: true 避免参数解析问题
+      const isWindows = process.platform === 'win32'
       const result = await execa(pkgManagerPath, installArgs, {
         encoding: 'utf8',
         // 捕获输出以便记录日志
         stdout: 'pipe',
-        stderr: 'pipe'
+        stderr: 'pipe',
+        // Windows 上使用 shell 模式执行 .cmd 文件,避免参数解析问题
+        shell: isWindows
       })
 
       // 记录安装输出