From afdfce650251af0d973660a6aa4ef3ee0c41d594 Mon Sep 17 00:00:00 2001 From: gmh01 Date: Thu, 23 Jul 2026 21:04:25 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=87=8D=E5=86=99=E6=89=93=E5=8C=85?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E4=B8=BA=E5=85=A8=E5=BC=82=E6=AD=A5=EF=BC=8C?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AD=89=E5=BE=85=E5=8A=A8=E7=94=BB=E5=92=8C?= =?UTF-8?q?=E5=AE=9E=E6=97=B6=E8=BF=9B=E5=BA=A6=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/builder.js | 175 +++++++++++++++++--------------- src/components/PackProgress.vue | 76 +++++++++++++- 2 files changed, 164 insertions(+), 87 deletions(-) diff --git a/electron/builder.js b/electron/builder.js index 6e544fa..ac465e9 100644 --- a/electron/builder.js +++ b/electron/builder.js @@ -1,6 +1,7 @@ const fs = require('fs') +const fsp = fs.promises const path = require('path') -const { exec, spawn } = require('child_process') +const { spawn } = require('child_process') const { getConfigPath, getSrcDir } = require('./project') let isCancelled = false @@ -14,13 +15,60 @@ function cancelPack() { } } -function runAsync(cmd, args, options) { +function yieldToEventLoop() { + return new Promise(resolve => setImmediate(resolve)) +} + +async function copyTree(src, dest) { + await fsp.mkdir(dest, { recursive: true }) + const entries = await fsp.readdir(src, { withFileTypes: true }) + for (const entry of entries) { + if (entry.name.startsWith('.')) continue + const s = path.join(src, entry.name) + const d = path.join(dest, entry.name) + if (entry.isDirectory()) { + await copyTree(s, d) + } else { + await fsp.copyFile(s, d) + } + } +} + +async function copyContents(src, dest) { + if (!fs.existsSync(src)) return + await fsp.mkdir(dest, { recursive: true }) + const entries = await fsp.readdir(src, { withFileTypes: true }) + for (const entry of entries) { + if (entry.name.startsWith('.')) continue + const s = path.join(src, entry.name) + const d = path.join(dest, entry.name) + if (entry.isDirectory()) { + await copyTree(s, d) + } else { + await fsp.copyFile(s, d) + } + } +} + +function runWithProgress(cmd, args, options, onProgress) { return new Promise((resolve, reject) => { const child = spawn(cmd, args, { ...options, shell: true, stdio: ['pipe', 'pipe', 'pipe'] }) let stdout = '' let stderr = '' - child.stdout.on('data', (d) => { stdout += d.toString() }) - child.stderr.on('data', (d) => { stderr += d.toString() }) + + child.stdout.on('data', (d) => { + const text = d.toString() + stdout += text + const line = text.trim() + if (line) { + onProgress({ stage: 'install', message: line, percent: 50 }) + } + }) + + child.stderr.on('data', (d) => { + stderr += d.toString() + }) + child.on('error', reject) child.on('close', (code) => { if (code === 0) resolve(stdout) @@ -32,33 +80,40 @@ function runAsync(cmd, args, options) { async function startPack(projectPath, outputPath, onProgress) { isCancelled = false buildingProcess = null - const config = JSON.parse(fs.readFileSync(getConfigPath(projectPath), 'utf-8')) + const config = JSON.parse(await fsp.readFile(getConfigPath(projectPath), 'utf-8')) const srcDir = getSrcDir(projectPath) const tempDir = path.join(projectPath, '.web2app', 'temp-build') + if (isCancelled) return { success: false, cancelled: true } + onProgress({ stage: 'prepare', message: '准备打包环境...', percent: 5 }) + await yieldToEventLoop() if (fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }) + await fsp.rm(tempDir, { recursive: true, force: true }) } if (isCancelled) return { success: false, cancelled: true } onProgress({ stage: 'prepare', message: '复制模板文件...', percent: 10 }) + await yieldToEventLoop() - copyTemplateSync(tempDir) + const templateDir = path.join(__dirname, '..', 'template') + await copyTree(templateDir, tempDir) if (isCancelled) return { success: false, cancelled: true } onProgress({ stage: 'copy-files', message: '复制前端资源...', percent: 20 }) + await yieldToEventLoop() const frontendDest = path.join(tempDir, 'frontend') - fs.mkdirSync(frontendDest, { recursive: true }) - copyDirContentsSync(srcDir, frontendDest) + await fsp.mkdir(frontendDest, { recursive: true }) + await copyContents(srcDir, frontendDest) if (isCancelled) return { success: false, cancelled: true } onProgress({ stage: 'configure', message: '配置应用信息...', percent: 35 }) + await yieldToEventLoop() const templatePackagePath = path.join(tempDir, 'package.json') - const templatePackage = JSON.parse(fs.readFileSync(templatePackagePath, 'utf-8')) + const templatePackage = JSON.parse(await fsp.readFile(templatePackagePath, 'utf-8')) templatePackage.name = config.name.toLowerCase().replace(/\s+/g, '-') templatePackage.productName = config.name templatePackage.version = config.version @@ -109,15 +164,20 @@ async function startPack(projectPath, outputPath, onProgress) { icon: config.icon ? path.join(frontendDest, config.icon) : undefined } } - fs.writeFileSync(templatePackagePath, JSON.stringify(templatePackage, null, 2)) + await fsp.writeFile(templatePackagePath, JSON.stringify(templatePackage, null, 2)) if (isCancelled) return { success: false, cancelled: true } onProgress({ stage: 'install', message: '正在安装依赖...', percent: 45 }) + await yieldToEventLoop() try { const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm' onProgress({ stage: 'install', message: '正在安装依赖 (首次可能需要几分钟)...', percent: 50 }) - await runAsync(npmCmd, ['install', '--production'], { cwd: tempDir, timeout: 300000 }) + await yieldToEventLoop() + await runWithProgress(npmCmd, ['install', '--production'], { + cwd: tempDir, + timeout: 300000 + }, onProgress) } catch (err) { onProgress({ stage: 'error', message: `依赖安装失败: ${err.message}`, percent: 0 }) return { success: false, error: err.message } @@ -125,6 +185,7 @@ async function startPack(projectPath, outputPath, onProgress) { if (isCancelled) return { success: false, cancelled: true } onProgress({ stage: 'build', message: '正在打包生成安装包...', percent: 65 }) + await yieldToEventLoop() return new Promise((resolve) => { const builderPath = path.join(tempDir, 'node_modules', '.bin', 'electron-builder') @@ -140,20 +201,18 @@ async function startPack(projectPath, outputPath, onProgress) { buildingProcess.stdout.on('data', (data) => { buildOutput += data.toString() - const msg = data.toString() - - if (msg.includes('packaging')) { - onProgress({ stage: 'build', message: '正在打包...', percent: 75 }) - } else if (msg.includes('signing')) { - onProgress({ stage: 'sign', message: '正在签名...', percent: 85 }) - } else if (msg.includes('downloading')) { - onProgress({ stage: 'download', message: msg.trim(), percent: 55 }) - } else if (msg.includes('building')) { - onProgress({ stage: 'build', message: msg.trim(), percent: 70 }) - } else { - const line = msg.trim() - if (line) { - onProgress({ stage: 'build', message: line, percent: 65 }) + const msg = data.toString().trim() + if (msg) { + if (msg.includes('packaging')) { + onProgress({ stage: 'build', message: '正在打包...', percent: 75 }) + } else if (msg.includes('signing')) { + onProgress({ stage: 'sign', message: '正在签名...', percent: 85 }) + } else if (msg.includes('downloading')) { + onProgress({ stage: 'download', message: msg, percent: 55 }) + } else if (msg.includes('building')) { + onProgress({ stage: 'build', message: msg, percent: 70 }) + } else { + onProgress({ stage: 'build', message: msg, percent: 65 }) } } }) @@ -162,7 +221,7 @@ async function startPack(projectPath, outputPath, onProgress) { buildOutput += data.toString() }) - buildingProcess.on('close', (code) => { + buildingProcess.on('close', async (code) => { buildingProcess = null if (isCancelled) { resolve({ success: false, cancelled: true }) @@ -177,21 +236,23 @@ async function startPack(projectPath, outputPath, onProgress) { } onProgress({ stage: 'complete', message: '打包完成!', percent: 100 }) + await yieldToEventLoop() const distDir = path.join(tempDir, 'dist') let installerPath = null if (fs.existsSync(distDir)) { - const files = fs.readdirSync(distDir) + const files = (await fsp.readdir(distDir)) .filter(f => f.endsWith('.exe') || f.endsWith('.dmg') || f.endsWith('.AppImage') || f.endsWith('.deb')) - .sort((a, b) => fs.statSync(path.join(distDir, b)).mtimeMs - fs.statSync(path.join(distDir, a)).mtimeMs) + .map(f => ({ name: f, mtime: fs.statSync(path.join(distDir, f)).mtimeMs })) + .sort((a, b) => b.mtime - a.mtime) + .map(f => f.name) if (files.length > 0) { installerPath = path.join(distDir, files[0]) if (outputPath) { try { - fs.copyFileSync(installerPath, outputPath) + await fsp.copyFile(installerPath, outputPath) installerPath = outputPath - } catch (e) { - } + } catch (e) {} } } } @@ -206,54 +267,4 @@ async function startPack(projectPath, outputPath, onProgress) { }) } -function copyTemplateSync(destDir) { - const templateDir = path.join(__dirname, '..', 'template') - const entries = fs.readdirSync(templateDir, { withFileTypes: true }) - for (const entry of entries) { - if (entry.name.startsWith('.')) continue - const srcPath = path.join(templateDir, entry.name) - const destPath = path.join(destDir, entry.name) - if (entry.isDirectory()) { - copyDirSync(srcPath, destPath) - } else { - fs.mkdirSync(path.dirname(destPath), { recursive: true }) - fs.copyFileSync(srcPath, destPath) - } - } - - fs.mkdirSync(path.join(destDir, 'frontend'), { recursive: true }) - fs.mkdirSync(path.join(destDir, 'backend'), { recursive: true }) -} - -function copyDirSync(src, dest) { - fs.mkdirSync(dest, { recursive: true }) - const entries = fs.readdirSync(src, { withFileTypes: true }) - for (const entry of entries) { - if (entry.name.startsWith('.')) continue - const srcPath = path.join(src, entry.name) - const destPath = path.join(dest, entry.name) - if (entry.isDirectory()) { - copyDirSync(srcPath, destPath) - } else { - fs.copyFileSync(srcPath, destPath) - } - } -} - -function copyDirContentsSync(src, dest) { - if (!fs.existsSync(src)) return - fs.mkdirSync(dest, { recursive: true }) - const entries = fs.readdirSync(src, { withFileTypes: true }) - for (const entry of entries) { - if (entry.name.startsWith('.')) continue - const srcPath = path.join(src, entry.name) - const destPath = path.join(dest, entry.name) - if (entry.isDirectory()) { - copyDirSync(srcPath, destPath) - } else { - fs.copyFileSync(srcPath, destPath) - } - } -} - module.exports = { startPack, cancelPack } diff --git a/src/components/PackProgress.vue b/src/components/PackProgress.vue index a5c0d74..940ec8d 100644 --- a/src/components/PackProgress.vue +++ b/src/components/PackProgress.vue @@ -1,14 +1,23 @@