#!/usr/bin/env node /** * 发布脚本 * Release script for Claude AI Installer * * 功能: * - 自动更新版本号 * - 构建所有平台的安装包 * - 生成发布说明 * - 可选: 创建 Git tag 并推送 * * 用法 / Usage: * node scripts/release.js [version] [options] * * 示例: * node scripts/release.js patch # 0.0.1 -> 0.0.2 * node scripts/release.js minor # 0.0.1 -> 0.1.0 * node scripts/release.js major # 0.0.1 -> 1.0.0 * node scripts/release.js 1.2.3 # 设置为指定版本 * node scripts/release.js patch --tag # 更新版本并创建 Git tag */ 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', magenta: '\x1b[35m', }; 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 projectRoot = path.resolve(__dirname, '..'); const packageJsonPath = path.join(projectRoot, 'package.json'); // 解析命令行参数 function parseArgs() { const args = process.argv.slice(2); // 每个平台默认只构建对应的版本,避免跨平台构建失败 const platformMap = { win32: 'win', darwin: 'mac', linux: 'linux' }; const currentPlatform = platformMap[process.platform] || 'linux'; const defaultPlatforms = [currentPlatform]; const options = { version: null, platforms: defaultPlatforms, dryRun: false, help: false, }; for (let i = 0; i < args.length; i++) { const arg = args[i]; switch (arg) { case '--platform': case '-p': options.platforms = args[++i].split(','); break; case '--dry-run': options.dryRun = true; break; case '--help': case '-h': options.help = true; break; default: if (!arg.startsWith('-') && !options.version) { options.version = arg; } } } return options; } // 显示帮助信息 function showHelp() { console.log(` ${colors.bright}Claude AI Installer 发布脚本${colors.reset} ${colors.cyan}用法:${colors.reset} node scripts/release.cjs [version] [options] ${colors.cyan}版本参数:${colors.reset} patch 补丁版本 (0.0.1 -> 0.0.2) minor 次版本 (0.0.1 -> 0.1.0) major 主版本 (0.0.1 -> 1.0.0) x.y.z 指定版本号 ${colors.cyan}选项:${colors.reset} --platform, -p 指定平台 (逗号分隔): win,mac,linux --dry-run 仅显示将要执行的操作,不实际执行 --help, -h 显示此帮助信息 ${colors.cyan}示例:${colors.reset} node scripts/release.cjs patch # 更新补丁版本并构建 node scripts/release.cjs 1.0.0 # 设置版本为 1.0.0 并构建 node scripts/release.cjs minor -p win,mac # 更新次版本,仅构建 Win 和 Mac node scripts/release.cjs --dry-run # 预览发布操作 `); } // 读取 package.json function readPackageJson() { return JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); } // 写入 package.json function writePackageJson(pkg) { fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n'); } // 计算新版本号 function calculateNewVersion(currentVersion, versionArg) { if (!versionArg) { return currentVersion; } // 如果是具体版本号 if (/^\d+\.\d+\.\d+/.test(versionArg)) { return versionArg; } const parts = currentVersion.split('.').map(Number); switch (versionArg) { case 'major': return `${parts[0] + 1}.0.0`; case 'minor': return `${parts[0]}.${parts[1] + 1}.0`; case 'patch': return `${parts[0]}.${parts[1]}.${parts[2] + 1}`; default: throw new Error(`无效的版本参数: ${versionArg}`); } } // 执行命令 function exec(command, options = {}) { log.info(`执行: ${command}`); try { return execSync(command, { stdio: options.silent ? 'pipe' : 'inherit', cwd: projectRoot, encoding: 'utf-8', ...options, }); } catch (error) { if (!options.ignoreError) { throw error; } return null; } } // 更新版本号 function updateVersion(newVersion, dryRun) { log.step(`更新版本号到 ${newVersion}...`); if (dryRun) { log.info('[DRY RUN] 将更新 package.json 和 tauri.conf.json 版本号'); return; } // 更新 package.json const pkg = readPackageJson(); pkg.version = newVersion; writePackageJson(pkg); log.success(`package.json 版本号已更新: ${newVersion}`); // 更新 tauri.conf.json const tauriConfPath = path.join(projectRoot, 'src-tauri', 'tauri.conf.json'); if (fs.existsSync(tauriConfPath)) { const tauriConf = JSON.parse(fs.readFileSync(tauriConfPath, 'utf-8')); tauriConf.version = newVersion; fs.writeFileSync(tauriConfPath, JSON.stringify(tauriConf, null, 2) + '\n'); log.success(`tauri.conf.json 版本号已更新: ${newVersion}`); } // 更新 Cargo.toml const cargoTomlPath = path.join(projectRoot, 'src-tauri', 'Cargo.toml'); if (fs.existsSync(cargoTomlPath)) { let cargoToml = fs.readFileSync(cargoTomlPath, 'utf-8'); cargoToml = cargoToml.replace(/^version = ".*"$/m, `version = "${newVersion}"`); fs.writeFileSync(cargoTomlPath, cargoToml); log.success(`Cargo.toml 版本号已更新: ${newVersion}`); } } // 构建所有平台 function buildAllPlatforms(platforms, dryRun) { log.step('构建应用...'); for (const platform of platforms) { log.info(`构建 ${platform} 平台...`); if (dryRun) { log.info(`[DRY RUN] 将构建 ${platform} 平台`); continue; } // Tauri 构建(build.js 不支持 -p 参数,直接调用) exec(`node scripts/build.js`); } // 复制构建产物到 release 目录 if (!dryRun) { copyArtifactsToRelease(); } log.success('所有平台构建完成'); } // 复制 Tauri 构建产物到 release 目录 function copyArtifactsToRelease() { log.step('复制构建产物到 release 目录...'); const releaseDir = path.join(projectRoot, 'release'); const tauriDir = path.join(projectRoot, 'src-tauri'); const bundleDir = path.join(tauriDir, 'target', 'release', 'bundle'); // 清空 release 目录(保留目录本身) if (fs.existsSync(releaseDir)) { const files = fs.readdirSync(releaseDir); for (const file of files) { const filePath = path.join(releaseDir, file); const stat = fs.statSync(filePath); if (stat.isFile()) { fs.unlinkSync(filePath); } else if (stat.isDirectory()) { fs.rmSync(filePath, { recursive: true }); } } log.success('已清空 release 目录'); } else { fs.mkdirSync(releaseDir, { recursive: true }); } // 复制 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 src = path.join(nsisDir, file); const dest = path.join(releaseDir, file); fs.copyFileSync(src, dest); log.success(`复制: ${file}`); } } // 复制 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 src = path.join(msiDir, file); const dest = path.join(releaseDir, file); fs.copyFileSync(src, dest); log.success(`复制: ${file}`); } } // 复制更新签名文件(如果存在) if (fs.existsSync(nsisDir)) { const sigFiles = fs.readdirSync(nsisDir).filter(f => f.endsWith('.sig')); for (const file of sigFiles) { const src = path.join(nsisDir, file); const dest = path.join(releaseDir, file); fs.copyFileSync(src, dest); log.success(`复制: ${file}`); } } // 复制 Portable exe(从 bundle 目录) const pkg = readPackageJson(); const version = pkg.version; const portableExeName = `Claude-AI-Installer-${version}-win-x64-portable.exe`; const portableExePath = path.join(bundleDir, portableExeName); if (fs.existsSync(portableExePath)) { const dest = path.join(releaseDir, portableExeName); fs.copyFileSync(portableExePath, dest); log.success(`复制: ${portableExeName}`); } } // 提交版本更新 function commitVersionUpdate(version) { log.step(`提交版本更新 v${version}...`); // 添加所有版本相关文件 exec('git add package.json', { ignoreError: true }); exec('git add src-tauri/tauri.conf.json', { ignoreError: true }); exec('git add src-tauri/Cargo.toml', { ignoreError: true }); // 提交 exec(`git commit -m "本地发布版本: ${version}"`, { ignoreError: true }); log.success(`版本更新已提交: v${version}`); } // 生成发布说明 function generateReleaseNotes(version) { log.step('生成发布说明...'); const releaseDir = path.join(projectRoot, 'release'); const notesPath = path.join(releaseDir, `RELEASE_NOTES_v${version}.md`); // 获取最近的 commits let commits = ''; try { commits = execSync('git log --oneline -20', { cwd: projectRoot, encoding: 'utf-8', }); } catch (e) { commits = '无法获取提交历史'; } // 获取构建产物列表 let artifacts = []; if (fs.existsSync(releaseDir)) { artifacts = fs.readdirSync(releaseDir).filter(f => { const ext = path.extname(f).toLowerCase(); return ['.exe', '.dmg', '.appimage', '.deb', '.rpm', '.zip'].includes(ext); }); } const notes = `# Claude AI Installer v${version} ## 发布日期 ${new Date().toISOString().split('T')[0]} ## 下载 ${artifacts.map(a => `- ${a}`).join('\n') || '暂无构建产物'} ## 平台支持 | 平台 | 文件格式 | 架构 | |------|----------|------| | Windows | NSIS 安装包 (.exe) | x64 | | macOS | DMG 镜像 (.dmg) | x64, arm64 | | Linux | AppImage (.AppImage) | x64 | ## 更新内容 请查看提交历史了解详细更新内容。 ## 最近提交 \`\`\` ${commits} \`\`\` ## 安装说明 ### Windows 1. 下载 \`.exe\` 安装包 2. 双击运行安装程序 3. 按照向导完成安装 ### macOS 1. 下载 \`.dmg\` 文件 2. 双击打开 DMG 镜像 3. 将应用拖拽到 Applications 文件夹 ### Linux 1. 下载 \`.AppImage\` 文件 2. 添加执行权限: \`chmod +x *.AppImage\` 3. 双击运行或在终端执行 --- 🤖 Generated with Claude AI Installer Build System `; fs.writeFileSync(notesPath, notes); log.success(`发布说明已生成: ${notesPath}`); } // 显示发布摘要 function showSummary(version, platforms) { const releaseDir = path.join(projectRoot, 'release'); console.log(` ${colors.bright}╔════════════════════════════════════════════╗ ║ 发布摘要 - v${version.padEnd(20)}║ ╚════════════════════════════════════════════╝${colors.reset} `); if (fs.existsSync(releaseDir)) { const files = fs.readdirSync(releaseDir).filter(f => { const ext = path.extname(f).toLowerCase(); return ['.exe', '.msi', '.dmg', '.appimage', '.deb', '.rpm', '.zip'].includes(ext); }); if (files.length > 0) { console.log(`${colors.cyan}构建产物:${colors.reset}`); for (const file of files) { const filePath = path.join(releaseDir, file); const stats = fs.statSync(filePath); const sizeMB = (stats.size / (1024 * 1024)).toFixed(2); // 根据文件名添加描述 let desc = ''; if (file.includes('-setup.exe')) { desc = ' - NSIS 安装程序'; } else if (file.endsWith('.msi')) { desc = ' - MSI 安装程序'; } else if (file.includes('-portable.exe')) { desc = ' - 便携版'; } else if (file.endsWith('.dmg')) { desc = ' - macOS 安装镜像'; } else if (file.endsWith('.AppImage')) { desc = ' - Linux 可执行程序'; } console.log(` ${colors.green}•${colors.reset} ${file} (${sizeMB} MB)${desc}`); } } } console.log(` ${colors.cyan}输出目录:${colors.reset} ${releaseDir} ${colors.cyan}版本号:${colors.reset} v${version} ${colors.cyan}构建平台:${colors.reset} ${platforms.join(', ')} `); } // 主函数 async function main() { const options = parseArgs(); if (options.help) { showHelp(); process.exit(0); } console.log(` ${colors.bright}${colors.magenta}╔════════════════════════════════════════════╗ ║ Claude AI Installer 发布脚本 ║ ╚════════════════════════════════════════════╝${colors.reset} `); if (options.dryRun) { log.warn('DRY RUN 模式 - 不会执行实际操作'); } const startTime = Date.now(); try { // 读取当前版本 const pkg = readPackageJson(); const currentVersion = pkg.version; log.info(`当前版本: ${currentVersion}`); // 计算新版本 const newVersion = calculateNewVersion(currentVersion, options.version); log.info(`目标版本: ${newVersion}`); // 更新版本号 if (options.version) { updateVersion(newVersion, options.dryRun); } // 构建所有平台 buildAllPlatforms(options.platforms, options.dryRun); // 生成发布说明 if (!options.dryRun) { generateReleaseNotes(newVersion); } // 提交版本更新 if (options.version && !options.dryRun) { commitVersionUpdate(newVersion); } // 显示摘要 if (!options.dryRun) { showSummary(newVersion, options.platforms); } const duration = ((Date.now() - startTime) / 1000).toFixed(1); log.success(`发布完成! 耗时: ${duration}s`); } catch (error) { log.error(error.message); process.exit(1); } } main();