/** * Windows x64 自动打包脚本 * 功能: * 1. 自动下载 Electron 并缓存到用户目录 * 2. 编译 TypeScript 并复制 UI 文件 * 3. 打包成 ZIP 文件 * 4. 支持增量打包(使用缓存的 Electron) */ import { existsSync, mkdirSync, rmSync, writeFileSync, readFileSync, cpSync } from 'fs'; import { join } from 'path'; import { execSync } from 'child_process'; import { homedir } from 'os'; // 路径配置 const rootDir = process.cwd(); const userCacheDir = join(homedir(), '.impress-asr-input', 'cache'); const electronCacheDir = join(userCacheDir, 'electron'); const releaseDir = join(rootDir, 'release'); const distDir = join(rootDir, 'dist'); // Electron 版本 const ELECTRON_VERSION = '28.3.3'; const ELECTRON_ZIP_NAME = `electron-v${ELECTRON_VERSION}-win32-x64.zip`; const ELECTRON_ZIP_PATH = join(electronCacheDir, ELECTRON_ZIP_NAME); const ELECTRON_URL = `https://npmmirror.com/mirrors/electron/${ELECTRON_VERSION}/${ELECTRON_ZIP_NAME}`; // 颜色输出 const colors = { reset: '\x1b[0m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', red: '\x1b[31m', }; function log(color, message) { console.log(`${color}${message}${colors.reset}`); } /** * 创建必要的目录 */ function ensureDirs() { log(colors.blue, '📁 创建目录...'); if (!existsSync(userCacheDir)) { mkdirSync(userCacheDir, { recursive: true }); log(colors.green, ` ✅ 缓存目录:${userCacheDir}`); } if (!existsSync(electronCacheDir)) { mkdirSync(electronCacheDir, { recursive: true }); } if (!existsSync(releaseDir)) { mkdirSync(releaseDir, { recursive: true }); } } /** * 下载 Electron */ function downloadElectron() { if (existsSync(ELECTRON_ZIP_PATH)) { log(colors.green, `✅ Electron 已缓存:${ELECTRON_ZIP_NAME}`); return; } log(colors.yellow, `📥 下载 Electron ${ELECTRON_VERSION}...`); log(colors.blue, ` URL: ${ELECTRON_URL}`); try { const proxy = process.env.http_proxy || process.env.https_proxy || ''; const curlCmd = proxy ? `curl -L -o "${ELECTRON_ZIP_PATH}" --proxy "${proxy}" "${ELECTRON_URL}"` : `curl -L -o "${ELECTRON_ZIP_PATH}" "${ELECTRON_URL}"`; execSync(curlCmd, { stdio: 'inherit' }); if (existsSync(ELECTRON_ZIP_PATH)) { const size = Math.round(readFileSync(ELECTRON_ZIP_PATH).length / 1024 / 1024 * 100) / 100; log(colors.green, `✅ 下载完成:${size}MB`); } } catch (error) { log(colors.red, '❌ 下载失败,请检查网络连接或代理设置'); throw error; } } /** * 编译 TypeScript */ function compileTypeScript() { log(colors.blue, '📝 编译 TypeScript...'); execSync('npm run build', { stdio: 'inherit' }); log(colors.green, '✅ 编译完成'); } /** * 解压 Electron */ function unzipElectron(outputDir) { if (existsSync(outputDir)) { rmSync(outputDir, { recursive: true, force: true }); } mkdirSync(outputDir, { recursive: true }); log(colors.blue, '📦 解压 Electron...'); execSync(`unzip -o "${ELECTRON_ZIP_PATH}" -d "${outputDir}"`, { stdio: 'inherit' }); log(colors.green, '✅ 解压完成'); } /** * 复制应用文件 */ function copyAppFiles(appDir) { log(colors.blue, '📋 复制应用文件...'); const resourcesAppDir = join(appDir, 'resources', 'app'); mkdirSync(resourcesAppDir, { recursive: true }); // 复制 dist 目录 const distFiles = ['core', 'utils', 'ui', 'electron-main.js', 'preload.js', 'main.js', 'package.json']; for (const file of distFiles) { const src = join(distDir, file); const dest = join(resourcesAppDir, file); if (existsSync(src)) { cpSync(src, dest, { recursive: true }); log(colors.green, ` ✅ ${file}`); } } // 复制 node_modules const nodeModulesDir = join(resourcesAppDir, 'node_modules'); mkdirSync(nodeModulesDir, { recursive: true }); const deps = ['onnxruntime-web', 'clipboardy', 'commander']; for (const dep of deps) { const src = join(rootDir, 'node_modules', dep); const dest = join(nodeModulesDir, dep); if (existsSync(src)) { cpSync(src, dest, { recursive: true }); log(colors.green, ` ✅ node_modules/${dep}`); } } // 创建空的 models 目录 mkdirSync(join(resourcesAppDir, 'models'), { recursive: true }); log(colors.green, ' ✅ models/'); } /** * 打包成 ZIP */ function createZip(packageName) { log(colors.blue, '📦 打包 ZIP...'); const zipPath = join(releaseDir, packageName); const appName = 'impress-asr-input-win-x64'; execSync(`zip -rq "${zipPath}" "${appName}"`, { cwd: releaseDir, stdio: 'inherit' }); const size = Math.round(readFileSync(zipPath).length / 1024 / 1024 * 100) / 100; log(colors.green, `✅ 打包完成:${packageName} (${size}MB)`); } /** * 清理临时目录 */ function cleanup() { log(colors.blue, '🧹 清理临时文件...'); const tempDir = join(releaseDir, 'impress-asr-input-win-x64'); if (existsSync(tempDir)) { rmSync(tempDir, { recursive: true, force: true }); } log(colors.green, '✅ 清理完成'); } /** * 主函数 */ function main() { console.log(''); log(colors.green, '╔════════════════════════════════════════════╗'); log(colors.green, '║ Impress ASR Input - Windows x64 打包工具 ║'); log(colors.green, '╚════════════════════════════════════════════╝'); console.log(''); try { // 1. 创建目录 ensureDirs(); // 2. 下载 Electron downloadElectron(); // 3. 编译 TypeScript compileTypeScript(); // 4. 解压 Electron 到临时目录 const tempDir = join(releaseDir, 'impress-asr-input-win-x64'); unzipElectron(tempDir); // 5. 复制应用文件 copyAppFiles(tempDir); // 6. 打包 const version = '0.1.0'; const packageName = `Impress_ASR_Input-${version}-win-x64.zip`; createZip(packageName); // 7. 清理 cleanup(); console.log(''); log(colors.green, '═══════════════════════════════════════'); log(colors.green, '✅ Windows x64 版本打包完成!'); log(colors.green, '═══════════════════════════════════════'); log(colors.blue, `📦 输出文件:release/${packageName}`); log(colors.blue, `💾 缓存目录:${userCacheDir}`); console.log(''); } catch (error) { console.log(''); log(colors.red, '❌ 打包失败!'); throw error; } } // 运行 main();