|
|
@@ -0,0 +1,457 @@
|
|
|
+#!/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 options = {
|
|
|
+ version: null,
|
|
|
+ tag: false,
|
|
|
+ push: false,
|
|
|
+ platforms: ['win', 'mac', 'linux'],
|
|
|
+ dryRun: false,
|
|
|
+ help: false,
|
|
|
+ };
|
|
|
+
|
|
|
+ for (let i = 0; i < args.length; i++) {
|
|
|
+ const arg = args[i];
|
|
|
+ switch (arg) {
|
|
|
+ case '--tag':
|
|
|
+ case '-t':
|
|
|
+ options.tag = true;
|
|
|
+ break;
|
|
|
+ case '--push':
|
|
|
+ options.push = true;
|
|
|
+ break;
|
|
|
+ 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.js [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}
|
|
|
+ --tag, -t 创建 Git tag
|
|
|
+ --push 推送 tag 到远程仓库
|
|
|
+ --platform, -p 指定平台 (逗号分隔): win,mac,linux
|
|
|
+ --dry-run 仅显示将要执行的操作,不实际执行
|
|
|
+ --help, -h 显示此帮助信息
|
|
|
+
|
|
|
+${colors.cyan}示例:${colors.reset}
|
|
|
+ node scripts/release.js patch # 更新补丁版本并构建
|
|
|
+ node scripts/release.js 1.0.0 --tag # 设置版本为 1.0.0 并创建 tag
|
|
|
+ node scripts/release.js minor -p win,mac # 更新次版本,仅构建 Win 和 Mac
|
|
|
+ node scripts/release.js patch --tag --push # 更新版本、创建 tag 并推送
|
|
|
+`);
|
|
|
+}
|
|
|
+
|
|
|
+// 读取 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;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 检查 Git 状态
|
|
|
+function checkGitStatus() {
|
|
|
+ log.step('检查 Git 状态...');
|
|
|
+
|
|
|
+ try {
|
|
|
+ const status = execSync('git status --porcelain', {
|
|
|
+ cwd: projectRoot,
|
|
|
+ encoding: 'utf-8',
|
|
|
+ });
|
|
|
+
|
|
|
+ if (status.trim()) {
|
|
|
+ log.warn('工作目录有未提交的更改:');
|
|
|
+ console.log(status);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ log.success('Git 工作目录干净');
|
|
|
+ return true;
|
|
|
+ } catch (error) {
|
|
|
+ log.warn('无法检查 Git 状态');
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 更新版本号
|
|
|
+function updateVersion(newVersion, dryRun) {
|
|
|
+ log.step(`更新版本号到 ${newVersion}...`);
|
|
|
+
|
|
|
+ if (dryRun) {
|
|
|
+ log.info('[DRY RUN] 将更新 package.json 版本号');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const pkg = readPackageJson();
|
|
|
+ pkg.version = newVersion;
|
|
|
+ writePackageJson(pkg);
|
|
|
+
|
|
|
+ log.success(`版本号已更新: ${newVersion}`);
|
|
|
+}
|
|
|
+
|
|
|
+// 构建所有平台
|
|
|
+function buildAllPlatforms(platforms, dryRun) {
|
|
|
+ log.step('构建应用...');
|
|
|
+
|
|
|
+ for (const platform of platforms) {
|
|
|
+ log.info(`构建 ${platform} 平台...`);
|
|
|
+
|
|
|
+ if (dryRun) {
|
|
|
+ log.info(`[DRY RUN] 将构建 ${platform} 平台`);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ exec(`node scripts/build.js -p ${platform}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ log.success('所有平台构建完成');
|
|
|
+}
|
|
|
+
|
|
|
+// 创建 Git tag
|
|
|
+function createGitTag(version, dryRun) {
|
|
|
+ log.step(`创建 Git tag v${version}...`);
|
|
|
+
|
|
|
+ if (dryRun) {
|
|
|
+ log.info(`[DRY RUN] 将创建 tag: v${version}`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提交版本更新
|
|
|
+ exec('git add package.json');
|
|
|
+ exec(`git commit -m "chore: release v${version}"`);
|
|
|
+
|
|
|
+ // 创建 tag
|
|
|
+ exec(`git tag -a v${version} -m "Release v${version}"`);
|
|
|
+
|
|
|
+ log.success(`Git tag v${version} 已创建`);
|
|
|
+}
|
|
|
+
|
|
|
+// 推送到远程
|
|
|
+function pushToRemote(version, dryRun) {
|
|
|
+ log.step('推送到远程仓库...');
|
|
|
+
|
|
|
+ if (dryRun) {
|
|
|
+ log.info('[DRY RUN] 将推送 commits 和 tags');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ exec('git push');
|
|
|
+ exec('git push --tags');
|
|
|
+
|
|
|
+ log.success('已推送到远程仓库');
|
|
|
+}
|
|
|
+
|
|
|
+// 生成发布说明
|
|
|
+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', '.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);
|
|
|
+ console.log(` ${colors.green}•${colors.reset} ${file} (${sizeMB} MB)`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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}`);
|
|
|
+
|
|
|
+ // 检查 Git 状态
|
|
|
+ if (options.tag && !options.dryRun) {
|
|
|
+ const isClean = checkGitStatus();
|
|
|
+ if (!isClean) {
|
|
|
+ log.warn('建议先提交或暂存更改');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新版本号
|
|
|
+ if (options.version) {
|
|
|
+ updateVersion(newVersion, options.dryRun);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建所有平台
|
|
|
+ buildAllPlatforms(options.platforms, options.dryRun);
|
|
|
+
|
|
|
+ // 生成发布说明
|
|
|
+ if (!options.dryRun) {
|
|
|
+ generateReleaseNotes(newVersion);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建 Git tag
|
|
|
+ if (options.tag) {
|
|
|
+ createGitTag(newVersion, options.dryRun);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 推送到远程
|
|
|
+ if (options.push) {
|
|
|
+ pushToRemote(newVersion, options.dryRun);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 显示摘要
|
|
|
+ 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();
|