| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572 |
- #!/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 <target> 指定目标平台
- --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();
|