feat: 添加 Windows 自动打包脚本,支持本地缓存 Electron

This commit is contained in:
impressionyang 2026-05-20 16:56:40 +08:00
parent 6681397167
commit 3e8be47295

232
scripts/build-windows.js Normal file
View File

@ -0,0 +1,232 @@
/**
* 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();