#!/usr/bin/env node /** * Tauri 构建脚本 (跨平台) * Build script for Claude AI Installer (Tauri) * * 用法 / Usage: * node scripts/build.js [options] * * 选项 / Options: * --debug, -d 调试模式构建 * --target, -t 指定目标平台 (如 aarch64-apple-darwin) * --help, -h 显示帮助信息 */ import { execSync } from 'child_process'; import path from 'path'; import fs from 'fs'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // 颜色输出 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}`), }; // 项目路径 const PROJECT_ROOT = path.resolve(__dirname, '..'); const TAURI_DIR = path.join(PROJECT_ROOT, 'src-tauri'); const BUILT_DIR = path.join(PROJECT_ROOT, 'built'); // 检测当前平台 function detectPlatform() { const platform = process.platform; const arch = process.arch; if (platform === 'win32') { return { os: 'windows', arch: 'x64', target: 'x86_64-pc-windows-msvc' }; } else if (platform === 'darwin') { return { os: 'macos', arch: arch === 'arm64' ? 'aarch64' : 'x64', target: arch === 'arm64' ? 'aarch64-apple-darwin' : 'x86_64-apple-darwin', }; } else if (platform === 'linux') { return { os: 'linux', arch: 'x64', target: 'x86_64-unknown-linux-gnu' }; } return { os: platform, arch, target: null }; } // 从 target 获取平台信息 function getPlatformFromTarget(target) { if (target.includes('windows') || target.includes('msvc')) { return { os: 'windows', arch: 'x64' }; } else if (target.includes('apple-darwin')) { const arch = target.includes('aarch64') ? 'aarch64' : 'x64'; return { os: 'macos', arch }; } else if (target.includes('linux')) { return { os: 'linux', arch: 'x64' }; } return { os: 'unknown', arch: 'unknown' }; } // 解析命令行参数 function parseArgs() { const args = process.argv.slice(2); const options = { debug: false, help: false, target: null, }; for (let i = 0; i < args.length; i++) { const arg = args[i]; switch (arg) { case '--debug': case '-d': options.debug = true; break; case '--help': case '-h': options.help = true; break; case '--target': case '-t': options.target = args[++i]; break; } } return options; } // 显示帮助信息 function showHelp() { console.log(` ${colors.bright}Claude AI Installer 构建脚本 (Tauri - 跨平台)${colors.reset} ${colors.cyan}用法:${colors.reset} node scripts/build.js [options] ${colors.cyan}选项:${colors.reset} --debug, -d 以调试模式构建 --target, -t 指定目标平台 --help, -h 显示此帮助信息 ${colors.cyan}支持的目标平台:${colors.reset} x86_64-pc-windows-msvc Windows x64 aarch64-apple-darwin macOS Apple Silicon x86_64-apple-darwin macOS Intel universal-apple-darwin macOS 通用二进制 x86_64-unknown-linux-gnu Linux x64 ${colors.cyan}示例:${colors.reset} node scripts/build.js # 当前平台发布构建 node scripts/build.js --debug # 调试构建 node scripts/build.js --target aarch64-apple-darwin # macOS ARM 构建 ${colors.cyan}输出 (Windows):${colors.reset} built/ ├── *-portable.exe # 便携版 ├── *-setup.exe # NSIS 安装程序 └── *.msi # MSI 安装程序 ${colors.cyan}输出 (macOS):${colors.reset} built/ ├── *.dmg # DMG 安装包 └── *.app.tar.gz # 压缩的应用程序 ${colors.cyan}输出 (Linux):${colors.reset} built/ ├── *.deb # Debian 包 └── *.AppImage # AppImage `); } // 执行命令 function exec(command, options = {}) { log.info(`执行: ${command}`); try { execSync(command, { stdio: 'inherit', cwd: PROJECT_ROOT, ...options, }); return true; } catch (error) { log.error(`命令执行失败: ${command}`); return false; } } // 获取版本号 function getVersion() { const tauriConfPath = path.join(TAURI_DIR, 'tauri.conf.json'); const tauriConf = JSON.parse(fs.readFileSync(tauriConfPath, 'utf8')); return tauriConf.version; } // 获取产品名称 function getProductName() { const tauriConfPath = path.join(TAURI_DIR, 'tauri.conf.json'); const tauriConf = JSON.parse(fs.readFileSync(tauriConfPath, 'utf8')); return tauriConf.productName || 'Claude AI Installer'; } // 清理之前的 portable 版本 (仅 Windows) function cleanPortable(options) { const platform = options.target ? getPlatformFromTarget(options.target) : detectPlatform(); if (platform.os !== 'windows') return; const { bundleDir } = getBuildPaths(options); if (fs.existsSync(bundleDir)) { const portableFiles = fs.readdirSync(bundleDir).filter(f => f.endsWith('-portable.exe')); for (const file of portableFiles) { const filePath = path.join(bundleDir, file); fs.unlinkSync(filePath); log.info(`已删除旧的便携版: ${file}`); } } } // 构建 Tauri 应用 function buildTauri(options) { log.step(`构建 Tauri 应用 (${options.debug ? '调试' : '发布'}模式)...`); let command = 'npm run tauri build'; const extraArgs = []; if (options.debug) { extraArgs.push('--debug'); } if (options.target) { extraArgs.push('--target', options.target); } if (extraArgs.length > 0) { command += ' -- ' + extraArgs.join(' '); } if (!exec(command)) { throw new Error('Tauri 构建失败'); } log.success('Tauri 构建完成'); } // 获取构建目录路径 function getBuildPaths(options) { const mode = options.debug ? 'debug' : 'release'; let targetDir; if (options.target) { targetDir = path.join(TAURI_DIR, 'target', options.target, mode); } else { targetDir = path.join(TAURI_DIR, 'target', mode); } const bundleDir = path.join(targetDir, 'bundle'); return { targetDir, bundleDir, mode }; } // 获取平台标识符用于文件命名 function getPlatformSuffix(options) { const platform = options.target ? getPlatformFromTarget(options.target) : detectPlatform(); if (platform.os === 'windows') { return 'win-x64'; } else if (platform.os === 'macos') { if (options.target && options.target.includes('universal')) { return 'mac-universal'; } return platform.arch === 'aarch64' ? 'mac-arm64' : 'mac-x64'; } else if (platform.os === 'linux') { return 'linux-x64'; } return 'unknown'; } // 重命名构建产物,使用统一的命名格式 function renameArtifacts(options) { log.step('重命名构建产物...'); const { targetDir, bundleDir } = getBuildPaths(options); const version = getVersion(); const platformSuffix = getPlatformSuffix(options); const platform = options.target ? getPlatformFromTarget(options.target) : detectPlatform(); // Windows 平台 if (platform.os === 'windows') { // 创建 portable 版本 const exeName = 'claude-ai-installer.exe'; const exePath = path.join(targetDir, exeName); if (fs.existsSync(exePath)) { if (!fs.existsSync(bundleDir)) { fs.mkdirSync(bundleDir, { recursive: true }); } const portableName = `Claude-AI-Installer-${version}-${platformSuffix}-portable.exe`; const portablePath = path.join(bundleDir, portableName); if (fs.existsSync(portablePath)) { fs.unlinkSync(portablePath); } fs.copyFileSync(exePath, portablePath); log.success(`创建便携版: ${portableName}`); } // 重命名 NSIS 安装程序 const nsisDir = path.join(bundleDir, 'nsis'); if (fs.existsSync(nsisDir)) { const files = fs.readdirSync(nsisDir).filter(f => f.endsWith('.exe')); for (const file of files) { const oldPath = path.join(nsisDir, file); const newName = `Claude-AI-Installer-${version}-${platformSuffix}-setup.exe`; const newPath = path.join(nsisDir, newName); if (file !== newName) { if (fs.existsSync(newPath)) fs.unlinkSync(newPath); fs.renameSync(oldPath, newPath); log.success(`重命名: ${file} -> ${newName}`); } } } // 重命名 MSI 安装程序 const msiDir = path.join(bundleDir, 'msi'); if (fs.existsSync(msiDir)) { const files = fs.readdirSync(msiDir).filter(f => f.endsWith('.msi')); for (const file of files) { const oldPath = path.join(msiDir, file); const newName = `Claude-AI-Installer-${version}-${platformSuffix}.msi`; const newPath = path.join(msiDir, newName); if (file !== newName) { if (fs.existsSync(newPath)) fs.unlinkSync(newPath); fs.renameSync(oldPath, newPath); log.success(`重命名: ${file} -> ${newName}`); } } } } // macOS 平台 if (platform.os === 'macos') { // 重命名 DMG const dmgDir = path.join(bundleDir, 'dmg'); if (fs.existsSync(dmgDir)) { const files = fs.readdirSync(dmgDir).filter(f => f.endsWith('.dmg')); for (const file of files) { const oldPath = path.join(dmgDir, file); const newName = `Claude-AI-Installer-${version}-${platformSuffix}.dmg`; const newPath = path.join(dmgDir, newName); if (file !== newName) { if (fs.existsSync(newPath)) fs.unlinkSync(newPath); fs.renameSync(oldPath, newPath); log.success(`重命名: ${file} -> ${newName}`); } } } // 重命名 .app.tar.gz (macos 目录) const macosDir = path.join(bundleDir, 'macos'); if (fs.existsSync(macosDir)) { const files = fs.readdirSync(macosDir).filter(f => f.endsWith('.tar.gz')); for (const file of files) { const oldPath = path.join(macosDir, file); const newName = `Claude-AI-Installer-${version}-${platformSuffix}.app.tar.gz`; const newPath = path.join(macosDir, newName); if (file !== newName) { if (fs.existsSync(newPath)) fs.unlinkSync(newPath); fs.renameSync(oldPath, newPath); log.success(`重命名: ${file} -> ${newName}`); } } } } // Linux 平台 if (platform.os === 'linux') { // 重命名 deb const debDir = path.join(bundleDir, 'deb'); if (fs.existsSync(debDir)) { const files = fs.readdirSync(debDir).filter(f => f.endsWith('.deb')); for (const file of files) { const oldPath = path.join(debDir, file); const newName = `Claude-AI-Installer-${version}-${platformSuffix}.deb`; const newPath = path.join(debDir, newName); if (file !== newName) { if (fs.existsSync(newPath)) fs.unlinkSync(newPath); fs.renameSync(oldPath, newPath); log.success(`重命名: ${file} -> ${newName}`); } } } // 重命名 AppImage const appimageDir = path.join(bundleDir, 'appimage'); if (fs.existsSync(appimageDir)) { const files = fs.readdirSync(appimageDir).filter(f => f.endsWith('.AppImage')); for (const file of files) { const oldPath = path.join(appimageDir, file); const newName = `Claude-AI-Installer-${version}-${platformSuffix}.AppImage`; const newPath = path.join(appimageDir, newName); if (file !== newName) { if (fs.existsSync(newPath)) fs.unlinkSync(newPath); fs.renameSync(oldPath, newPath); log.success(`重命名: ${file} -> ${newName}`); } } } // 重命名 rpm const rpmDir = path.join(bundleDir, 'rpm'); if (fs.existsSync(rpmDir)) { const files = fs.readdirSync(rpmDir).filter(f => f.endsWith('.rpm')); for (const file of files) { const oldPath = path.join(rpmDir, file); const newName = `Claude-AI-Installer-${version}-${platformSuffix}.rpm`; const newPath = path.join(rpmDir, newName); if (file !== newName) { if (fs.existsSync(newPath)) fs.unlinkSync(newPath); fs.renameSync(oldPath, newPath); log.success(`重命名: ${file} -> ${newName}`); } } } } } // 显示构建结果 function showResults(options) { log.step('构建结果:'); const { targetDir, bundleDir } = getBuildPaths(options); console.log(''); // 显示 built 目录中的文件 if (fs.existsSync(BUILT_DIR)) { const files = fs.readdirSync(BUILT_DIR); for (const file of files) { const filePath = path.join(BUILT_DIR, file); const stats = fs.statSync(filePath); const sizeMB = (stats.size / (1024 * 1024)).toFixed(2); console.log(` ${colors.green}•${colors.reset} ${file} (${sizeMB} MB)`); } } console.log(''); log.info(`输出目录: ${targetDir}`); if (fs.existsSync(bundleDir)) { log.info(`安装包目录: ${bundleDir}`); } log.info(`构建产物汇总: ${BUILT_DIR}`); } // 复制构建产物到 built 目录 function copyToBuiltDir(options) { log.step('复制构建产物到 built 目录...'); const { bundleDir } = getBuildPaths(options); const platform = options.target ? getPlatformFromTarget(options.target) : detectPlatform(); // 确保 built 目录存在 if (!fs.existsSync(BUILT_DIR)) { fs.mkdirSync(BUILT_DIR, { recursive: true }); } // 清空 built 目录 const existingFiles = fs.readdirSync(BUILT_DIR); for (const file of existingFiles) { const filePath = path.join(BUILT_DIR, file); fs.unlinkSync(filePath); } let copiedCount = 0; // 辅助函数:复制目录中的文件 const copyFilesFromDir = (dir, extensions) => { if (!fs.existsSync(dir)) return; const files = fs.readdirSync(dir).filter(f => extensions.some(ext => f.endsWith(ext)) ); for (const file of files) { const srcPath = path.join(dir, file); const destPath = path.join(BUILT_DIR, file); fs.copyFileSync(srcPath, destPath); log.success(`复制: ${file}`); copiedCount++; } }; // Windows 平台 if (platform.os === 'windows') { // 复制便携版 if (fs.existsSync(bundleDir)) { copyFilesFromDir(bundleDir, ['-portable.exe']); } // 复制 NSIS 安装程序 copyFilesFromDir(path.join(bundleDir, 'nsis'), ['.exe']); // 复制 MSI 安装程序 copyFilesFromDir(path.join(bundleDir, 'msi'), ['.msi']); } // macOS 平台 if (platform.os === 'macos') { // 复制 DMG copyFilesFromDir(path.join(bundleDir, 'dmg'), ['.dmg']); // 复制 .app.tar.gz copyFilesFromDir(path.join(bundleDir, 'macos'), ['.tar.gz']); } // Linux 平台 if (platform.os === 'linux') { // 复制 deb copyFilesFromDir(path.join(bundleDir, 'deb'), ['.deb']); // 复制 AppImage copyFilesFromDir(path.join(bundleDir, 'appimage'), ['.AppImage']); // 复制 rpm copyFilesFromDir(path.join(bundleDir, 'rpm'), ['.rpm']); } if (copiedCount > 0) { log.success(`共复制 ${copiedCount} 个文件到 ${BUILT_DIR}`); } else { log.warn('没有找到可复制的构建产物'); } } // 主函数 async function main() { const options = parseArgs(); if (options.help) { showHelp(); process.exit(0); } console.log(` ${colors.bright}╔════════════════════════════════════════════╗ ║ Claude AI Installer 构建脚本 ║ ║ (Tauri 2.0) ║ ╚════════════════════════════════════════════╝${colors.reset} `); const platform = options.target ? getPlatformFromTarget(options.target) : detectPlatform(); const platformSuffix = getPlatformSuffix(options); log.info(`构建模式: ${options.debug ? '调试' : '发布'}`); log.info(`目标平台: ${platform.os} (${platformSuffix})`); if (options.target) { log.info(`目标架构: ${options.target}`); } log.info(`版本: ${getVersion()}`); log.info(`产品名称: ${getProductName()}`); const startTime = Date.now(); try { // 清理之前的 portable 版本 cleanPortable(options); // 构建 Tauri 应用 buildTauri(options); // 重命名构建产物(仅发布模式) if (!options.debug) { renameArtifacts(options); } // 复制构建产物到 built 目录 copyToBuiltDir(options); // 显示结果 showResults(options); const duration = ((Date.now() - startTime) / 1000).toFixed(1); log.success(`构建完成! 耗时: ${duration}s`); } catch (error) { log.error(error.message); process.exit(1); } } main();