| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- #!/usr/bin/env node
- /**
- * 多平台构建脚本
- * Multi-platform build script for Claude AI Installer
- *
- * 用法 / Usage:
- * node scripts/build.js [options]
- *
- * 选项 / Options:
- * --platform, -p 目标平台: win, mac, linux, all (默认: 当前平台)
- * --arch, -a 架构: x64, arm64, all (默认: 当前架构)
- * --publish, -P 发布模式: always, onTag, never (默认: never)
- * --skip-build 跳过前端构建,仅打包
- * --skip-typecheck 跳过 TypeScript 类型检查
- * --help, -h 显示帮助信息
- */
- const { execSync } = require('child_process');
- const path = require('path');
- const fs = require('fs');
- // 颜色输出
- const colors = {
- reset: '\x1b[0m',
- bright: '\x1b[1m',
- red: '\x1b[31m',
- green: '\x1b[32m',
- yellow: '\x1b[33m',
- blue: '\x1b[34m',
- cyan: '\x1b[36m',
- };
- const log = {
- info: (msg) => console.log(`${colors.blue}ℹ${colors.reset} ${msg}`),
- success: (msg) => console.log(`${colors.green}✔${colors.reset} ${msg}`),
- warn: (msg) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`),
- error: (msg) => console.log(`${colors.red}✖${colors.reset} ${msg}`),
- step: (msg) => console.log(`\n${colors.cyan}${colors.bright}▶ ${msg}${colors.reset}`),
- };
- // 解析命令行参数
- function parseArgs() {
- const args = process.argv.slice(2);
- const options = {
- platform: process.platform === 'win32' ? 'win' : process.platform === 'darwin' ? 'mac' : 'linux',
- arch: process.arch,
- publish: 'never',
- skipBuild: false,
- skipTypecheck: false,
- help: false,
- };
- for (let i = 0; i < args.length; i++) {
- const arg = args[i];
- switch (arg) {
- case '--platform':
- case '-p':
- options.platform = args[++i];
- break;
- case '--arch':
- case '-a':
- options.arch = args[++i];
- break;
- case '--publish':
- case '-P':
- options.publish = args[++i];
- break;
- case '--skip-build':
- case '-s':
- options.skipBuild = true;
- break;
- case '--skip-typecheck':
- options.skipTypecheck = true;
- break;
- case '--help':
- case '-h':
- options.help = true;
- break;
- }
- }
- return options;
- }
- // 显示帮助信息
- function showHelp() {
- console.log(`
- ${colors.bright}Claude AI Installer 构建脚本${colors.reset}
- ${colors.cyan}用法:${colors.reset}
- node scripts/build.js [options]
- ${colors.cyan}选项:${colors.reset}
- --platform, -p <platform> 目标平台
- win - Windows (NSIS 安装包)
- mac - macOS (DMG)
- linux - Linux (AppImage)
- all - 所有平台
- 默认: 当前平台
- --arch, -a <arch> 目标架构
- x64 - 64位 Intel/AMD
- arm64 - ARM64 (Apple Silicon, etc.)
- all - 所有架构
- 默认: 当前架构
- --publish, -P <mode> 发布模式
- always - 总是发布
- onTag - 仅在 Git tag 时发布
- never - 不发布 (默认)
- --skip-build, -s 跳过前端构建,仅执行打包
- --skip-typecheck 跳过 TypeScript 类型检查
- --help, -h 显示此帮助信息
- ${colors.cyan}示例:${colors.reset}
- node scripts/build.js # 构建当前平台
- node scripts/build.js -p win # 构建 Windows 版本
- node scripts/build.js -p mac -a arm64 # 构建 macOS ARM64 版本
- node scripts/build.js -p all # 构建所有平台
- node scripts/build.js -p win -P always # 构建并发布 Windows 版本
- `);
- }
- // 执行命令
- function exec(command, options = {}) {
- log.info(`执行: ${command}`);
- try {
- execSync(command, {
- stdio: 'inherit',
- cwd: path.resolve(__dirname, '..'),
- ...options,
- });
- return true;
- } catch (error) {
- log.error(`命令执行失败: ${command}`);
- return false;
- }
- }
- // 获取 electron-builder 平台参数
- function getPlatformArgs(platform, arch) {
- const platformMap = {
- win: '--win',
- mac: '--mac',
- linux: '--linux',
- };
- const archMap = {
- x64: '--x64',
- arm64: '--arm64',
- all: '--x64 --arm64',
- };
- let args = [];
- if (platform === 'all') {
- args.push('--win', '--mac', '--linux');
- } else {
- args.push(platformMap[platform] || platformMap.win);
- }
- if (arch && arch !== 'all') {
- args.push(archMap[arch] || '');
- } else if (arch === 'all') {
- args.push('--x64', '--arm64');
- }
- return args.join(' ');
- }
- // 清理旧的构建文件
- function cleanBuild() {
- log.step('清理旧的构建文件...');
- const dirsToClean = ['dist', 'dist-electron'];
- const projectRoot = path.resolve(__dirname, '..');
- for (const dir of dirsToClean) {
- const fullPath = path.join(projectRoot, dir);
- if (fs.existsSync(fullPath)) {
- fs.rmSync(fullPath, { recursive: true, force: true });
- log.info(`已删除: ${dir}`);
- }
- }
- // 清空 release 目录内容,但保留目录本身
- const releaseDir = path.join(projectRoot, 'release');
- if (fs.existsSync(releaseDir)) {
- const files = fs.readdirSync(releaseDir);
- for (const file of files) {
- const filePath = path.join(releaseDir, file);
- fs.rmSync(filePath, { recursive: true, force: true });
- }
- log.info('已清空: release');
- }
- log.success('清理完成');
- }
- // 类型检查
- function typeCheck() {
- log.step('执行类型检查...');
- if (!exec('npx vue-tsc --noEmit')) {
- throw new Error('类型检查失败');
- }
- log.success('类型检查通过');
- }
- // 构建前端
- function buildFrontend() {
- log.step('构建前端资源...');
- if (!exec('npx vite build')) {
- throw new Error('前端构建失败');
- }
- log.success('前端构建完成');
- }
- // 打包 Electron 应用
- function packageApp(options) {
- log.step(`打包应用 (平台: ${options.platform}, 架构: ${options.arch})...`);
- const platformArgs = getPlatformArgs(options.platform, options.arch);
- const publishArg = options.publish !== 'never' ? `--publish ${options.publish}` : '';
- const command = `npx electron-builder ${platformArgs} ${publishArg}`.trim();
- if (!exec(command)) {
- throw new Error('应用打包失败');
- }
- log.success('应用打包完成');
- }
- // 显示构建结果
- function showResults() {
- log.step('构建结果:');
- const releaseDir = path.resolve(__dirname, '..', 'release');
- if (!fs.existsSync(releaseDir)) {
- log.warn('release 目录不存在');
- return;
- }
- const files = fs.readdirSync(releaseDir);
- const packages = files.filter(f => {
- const ext = path.extname(f).toLowerCase();
- return ['.exe', '.dmg', '.appimage', '.deb', '.rpm', '.zip'].includes(ext);
- });
- if (packages.length === 0) {
- log.warn('未找到打包文件');
- return;
- }
- console.log('');
- for (const pkg of packages) {
- const filePath = path.join(releaseDir, pkg);
- const stats = fs.statSync(filePath);
- const sizeMB = (stats.size / (1024 * 1024)).toFixed(2);
- console.log(` ${colors.green}•${colors.reset} ${pkg} (${sizeMB} MB)`);
- }
- console.log('');
- log.info(`输出目录: ${releaseDir}`);
- }
- // ==================== Portable 版本构建 ====================
- const LAUNCHER_DIR = path.resolve(__dirname, '..', 'launcher');
- const RELEASE_DIR = path.resolve(__dirname, '..', 'release');
- /**
- * 查找 portable exe 文件
- */
- function findPortableExe() {
- const files = fs.readdirSync(RELEASE_DIR);
- return files.find(f => f.includes('portable') && f.endsWith('.exe'));
- }
- /**
- * 使用 pkg 打包启动器
- */
- function buildLauncher() {
- log.step('构建启动器...');
- // 检查是否安装了 pkg
- try {
- execSync('npx pkg --version', { stdio: 'ignore' });
- } catch {
- log.info('安装 pkg...');
- execSync('npm install -g pkg', { stdio: 'inherit' });
- }
- const launcherScript = path.join(LAUNCHER_DIR, 'launcher.js');
- const outputPath = path.join(RELEASE_DIR, 'launcher.exe');
- // 使用 pkg 打包
- execSync(`npx pkg "${launcherScript}" --target node18-win-x64 --output "${outputPath}"`, {
- stdio: 'inherit',
- cwd: path.resolve(__dirname, '..'),
- });
- log.success(`启动器构建完成: ${outputPath}`);
- return outputPath;
- }
- /**
- * 创建 Portable 目录结构
- */
- function createPortableStructure(portableExe, launcherExe) {
- const packageJson = require(path.resolve(__dirname, '..', 'package.json'));
- const version = packageJson.version;
- const portableDirName = `Claude-AI-Installer-${version}-portable`;
- const portableDir = path.join(RELEASE_DIR, portableDirName);
- const appDir = path.join(portableDir, 'app');
- const updateDir = path.join(portableDir, 'update');
- log.step('创建 Portable 目录结构...');
- // 清理旧目录
- if (fs.existsSync(portableDir)) {
- fs.rmSync(portableDir, { recursive: true });
- }
- // 创建目录
- fs.mkdirSync(appDir, { recursive: true });
- fs.mkdirSync(updateDir, { recursive: true });
- // 复制启动器
- const launcherDest = path.join(portableDir, 'Claude-AI-Installer.exe');
- fs.copyFileSync(launcherExe, launcherDest);
- log.info(`复制启动器: ${launcherDest}`);
- // 复制主程序到 app 目录
- const portableExePath = path.join(RELEASE_DIR, portableExe);
- const appExeName = `Claude-AI-Installer-${version}.exe`;
- const appExeDest = path.join(appDir, appExeName);
- fs.copyFileSync(portableExePath, appExeDest);
- log.info(`复制主程序: ${appExeDest}`);
- // 创建 .gitkeep 文件保持 update 目录
- fs.writeFileSync(path.join(updateDir, '.gitkeep'), '');
- // 创建 README
- const readme = `Claude AI Installer - Portable 版本
- 使用方法:
- 1. 双击 Claude-AI-Installer.exe 启动程序
- 2. 程序会自动检查更新
- 3. 更新下载后,重启程序即可完成更新
- 目录说明:
- - Claude-AI-Installer.exe: 启动器
- - app/: 主程序目录
- - update/: 更新文件临时目录
- 版本: ${version}
- `;
- fs.writeFileSync(path.join(portableDir, 'README.txt'), readme, 'utf8');
- log.success(`Portable 目录创建完成: ${portableDir}`);
- // 创建 zip 包
- createPortableZip(portableDir, portableDirName);
- return portableDir;
- }
- /**
- * 创建 ZIP 压缩包
- */
- function createPortableZip(sourceDir, name) {
- const zipPath = path.join(RELEASE_DIR, `${name}.zip`);
- log.step('创建 ZIP 压缩包...');
- // 使用 PowerShell 创建 zip
- const psCommand = `Compress-Archive -Path "${sourceDir}\\*" -DestinationPath "${zipPath}" -Force`;
- execSync(`powershell -Command "${psCommand}"`, { stdio: 'inherit' });
- log.success(`ZIP 压缩包创建完成: ${zipPath}`);
- }
- /**
- * 构建 Portable 版本(带启动器)
- */
- function buildPortableVersion() {
- log.step('构建 Portable 版本(带启动器)...');
- // 查找 portable exe
- const portableExe = findPortableExe();
- if (!portableExe) {
- throw new Error('未找到 portable exe 文件');
- }
- log.info(`找到 Portable 文件: ${portableExe}`);
- // 构建启动器
- const launcherExe = buildLauncher();
- // 创建 Portable 目录结构
- createPortableStructure(portableExe, launcherExe);
- log.success('Portable 版本构建完成');
- }
- // 主函数
- async function main() {
- const options = parseArgs();
- if (options.help) {
- showHelp();
- process.exit(0);
- }
- console.log(`
- ${colors.bright}╔════════════════════════════════════════════╗
- ║ Claude AI Installer 构建脚本 ║
- ╚════════════════════════════════════════════╝${colors.reset}
- `);
- log.info(`目标平台: ${options.platform}`);
- log.info(`目标架构: ${options.arch}`);
- log.info(`发布模式: ${options.publish}`);
- log.info(`跳过构建: ${options.skipBuild}`);
- log.info(`跳过类型检查: ${options.skipTypecheck}`);
- const startTime = Date.now();
- try {
- // 清理旧的构建文件
- cleanBuild();
- if (!options.skipBuild) {
- // 类型检查
- if (!options.skipTypecheck) {
- typeCheck();
- } else {
- log.warn('跳过类型检查');
- }
- // 构建前端
- buildFrontend();
- } else {
- log.warn('跳过前端构建');
- }
- packageApp(options);
- // Windows 平台自动构建带启动器的 Portable 版本
- if (options.platform === 'win' || options.platform === 'all') {
- buildPortableVersion();
- }
- showResults();
- const duration = ((Date.now() - startTime) / 1000).toFixed(1);
- log.success(`构建完成! 耗时: ${duration}s`);
- } catch (error) {
- log.error(error.message);
- process.exit(1);
- }
- }
- main();
|