|
|
@@ -130,11 +130,19 @@ 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 = stderr?.toString() || ''
|
|
|
- const stdoutStr = stdout?.toString() || ''
|
|
|
+ const stderrStr = cleanOutput(stderr)
|
|
|
+ const stdoutStr = cleanOutput(stdout)
|
|
|
let errorMessage = error.message
|
|
|
if (exitCode !== undefined) {
|
|
|
errorMessage += `\n退出码: ${exitCode}`
|
|
|
@@ -149,8 +157,8 @@ function sudoExec(command: string): Promise<{ stdout: string; stderr: string }>
|
|
|
reject(enhancedError)
|
|
|
} else {
|
|
|
resolve({
|
|
|
- stdout: stdout?.toString() || '',
|
|
|
- stderr: stderr?.toString() || ''
|
|
|
+ stdout: cleanOutput(stdout),
|
|
|
+ stderr: cleanOutput(stderr)
|
|
|
})
|
|
|
}
|
|
|
})
|
|
|
@@ -436,7 +444,7 @@ function getUninstallArgs(software: UninstallableSoftwareType): CommandResult {
|
|
|
|
|
|
// ==================== 安装流程 ====================
|
|
|
|
|
|
-export type StatusCallback = (software: SoftwareTypeWithAll, message: string, progress: number) => void
|
|
|
+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
|
|
|
|
|
|
@@ -495,16 +503,17 @@ export async function installNodejs(
|
|
|
logger.installInfo(`开始下载 Node.js: ${downloadUrl}`)
|
|
|
|
|
|
try {
|
|
|
- let lastReportedPercent = 0
|
|
|
+ let lastLoggedPercent = 0
|
|
|
await downloadFile(downloadUrl, installerPath, (downloaded, total, percent) => {
|
|
|
- // 每 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)
|
|
|
+ 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 || percent >= 100
|
|
|
+ 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)
|
|
|
@@ -662,7 +671,7 @@ export async function installPnpm(onStatus: StatusCallback): Promise<void> {
|
|
|
|
|
|
// 刷新系统环境变量,确保能找到刚安装的 Node.js
|
|
|
const { getRefreshedPath } = await import('./utils')
|
|
|
- const refreshedPath = await getRefreshedPath()
|
|
|
+ await getRefreshedPath()
|
|
|
|
|
|
// 使用完整路径,避免 PATH 未生效的问题
|
|
|
const npmCmd = getNpmPath()
|
|
|
@@ -674,10 +683,33 @@ export async function installPnpm(onStatus: StatusCallback): Promise<void> {
|
|
|
|
|
|
checkCancelled()
|
|
|
|
|
|
- // 安装 pnpm,使用刷新后的 PATH
|
|
|
+ // 安装 pnpm,需要管理员权限进行全局安装
|
|
|
onStatus('pnpm', `${STATUS_MESSAGES.INSTALLING} pnpm...`, 40)
|
|
|
- const installEnv = { ...process.env, PATH: refreshedPath }
|
|
|
- await execa(npmCmd, ['install', '-g', 'pnpm'], { env: installEnv, shell: platform === 'win32' })
|
|
|
+ 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()
|
|
|
|
|
|
@@ -744,16 +776,17 @@ export async function installVscode(version = 'stable', onStatus: StatusCallback
|
|
|
logger.installInfo(`开始下载 VS Code: ${downloadUrl}`)
|
|
|
|
|
|
try {
|
|
|
- let lastReportedPercent = 0
|
|
|
+ let lastLoggedPercent = 0
|
|
|
await downloadFile(downloadUrl, installerPath, (downloaded, total, percent) => {
|
|
|
- // 每 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)
|
|
|
+ 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 || percent >= 100
|
|
|
+ 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)
|
|
|
@@ -851,17 +884,18 @@ export async function installGit(version = 'stable', onStatus: StatusCallback, c
|
|
|
logger.installInfo(`开始下载 Git: ${downloadUrl}`)
|
|
|
|
|
|
try {
|
|
|
- let lastReportedPercent = 0
|
|
|
+ let lastLoggedPercent = 0
|
|
|
await downloadFile(downloadUrl, installerPath, (downloaded, total, percent) => {
|
|
|
- // 每 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)
|
|
|
+ // 下载进度占 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 || percent >= 100
|
|
|
+ if (shouldLog) {
|
|
|
+ lastLoggedPercent = Math.floor(percent / 5) * 5
|
|
|
}
|
|
|
+ onStatus('git', `正在下载 Git ${targetVersion}... (${downloadedMB}MB / ${totalMB}MB)`, progress, !shouldLog)
|
|
|
})
|
|
|
} catch (error) {
|
|
|
logger.installError('下载 Git 失败', error)
|