const { execSync, spawn } = require('child_process') const path = require('path') const fs = require('fs') function runElectronBuilder(cwd) { const binName = process.platform === 'win32' ? 'electron-builder.cmd' : 'electron-builder' const electronBuilderBin = path.join(cwd, 'node_modules', '.bin', binName) if (!fs.existsSync(electronBuilderBin)) { return Promise.reject(new Error('未找到 electron-builder,请先在项目根目录执行 npm install。')) } const env = { ...process.env, DEBUG: 'electron-builder', // 使用国内镜像,避免 x64 Electron 下载不完整导致「缺少v8 startup snapshot」 ELECTRON_MIRROR: process.env.ELECTRON_MIRROR || 'https://npmmirror.com/mirrors/electron/' } return new Promise((resolve, reject) => { const args = ['--win', '--x64'] const opts = { cwd, stdio: ['inherit', 'inherit', 'inherit'], env, windowsHide: false } if (process.platform === 'win32') { opts.shell = true } const child = spawn(electronBuilderBin, args, opts) let elapsed = 0 const progressInterval = setInterval(() => { elapsed += 10 console.log(` → 打包进行中... (已等待 ${elapsed} 秒,请勿关闭)`) }, 10000) child.on('close', (code) => { clearInterval(progressInterval) if (code !== 0) { const err = new Error(`electron-builder 退出码: ${code}`) err.exitCode = code reject(err) } else resolve() }) child.on('error', (err) => { clearInterval(progressInterval) reject(err) }) }) } const projectRoot = path.resolve(__dirname, '..') const distDir = path.join(projectRoot, 'dist') function cleanDist(keepDir) { if (!fs.existsSync(distDir)) return for (const name of fs.readdirSync(distDir)) { if (name === keepDir) continue const full = path.join(distDir, name) try { if (fs.statSync(full).isDirectory()) fs.rmSync(full, { recursive: true }) else fs.unlinkSync(full) } catch (e) { console.warn('clean:', e.message) } } } async function main() { const keepDir = 'win-unpacked' try { console.log('[1/3] Vite build...') execSync('npm run build', { cwd: projectRoot, stdio: 'inherit' }) } catch (e) { console.error('\n[错误] 第 1 步 Vite build 失败,已停止打包。') console.error(e.message || e) process.exit(1) } try { console.log('[2/3] Electron builder win x64...(每 10 秒会打印进度,请勿关闭)') console.log(' 若出现「缺少v8 startup snapshot」:请执行 rd /s /q "%LOCALAPPDATA%\\electron\\Cache" 后重试。') await runElectronBuilder(projectRoot) } catch (e) { console.error('\n[错误] 第 2 步 electron-builder 失败,已停止打包。') console.error(e.message || e) if (/v8|snapshot|startup/i.test(String(e.message))) { console.error('\n提示:可尝试清理 Electron 缓存后重试:') console.error(' rd /s /q "%LOCALAPPDATA%\\electron\\Cache"') } process.exit(1) } try { console.log('[3/3] 清理 dist,只保留 win-unpacked...') cleanDist(keepDir) const unpackedDir = path.join(distDir, keepDir) const readmePath = path.join(unpackedDir, '使用说明.txt') const readme = [ 'AndroidRemoteController - 开包即用', '', '使用:直接双击运行 AndroidRemoteController.exe', '首次运行会在本目录自动创建 static 文件夹(用于存放运行时数据)。', '', '分发:可将本「win-unpacked」整个文件夹复制到任意 Windows 电脑使用,无需安装。', '请勿单独只复制 .exe,必须复制整个文件夹。', '' ].join('\r\n') try { fs.writeFileSync(readmePath, readme, 'utf8') console.log(' 已写入 使用说明.txt') } catch (e) { console.warn(' 写入使用说明.txt 失败:', e.message) } console.log('') console.log('Done. 输出目录: dist\\win-unpacked') console.log(' 可将该文件夹整体复制到任意 Windows 电脑,直接运行其中的 .exe 即可(开包即用)。') } catch (e) { console.error('\n[错误] 第 3 步 清理 dist 失败。') console.error(e.message || e) process.exit(1) } } if (require.main === module) { main().catch((e) => { console.error(e) process.exit(1) }) } else { module.exports = { cleanDist } }