diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..8309b46 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,174 @@ +name: Release + +# 触发条件 +on: + # 手动触发 + workflow_dispatch: + inputs: + version: + description: '版本号 (例如: v1.0.0)' + required: true + type: string + prerelease: + description: '是否为预发布版本' + required: false + type: boolean + default: false + + # 当推送 tag 时自动触发(格式:v*.*.* ) + push: + tags: + - 'v*.*.*' + +jobs: + build-and-release: + name: 构建并发布 + runs-on: ubuntu-latest + + permissions: + contents: write + packages: write + + steps: + # 1. 检出代码 + - name: 检出代码 + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # 2. 设置 Node.js 环境 + - name: 设置 Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'npm' + + # 3. 安装 pnpm + - name: 安装 pnpm + run: npm install -g pnpm + + # 4. 安装依赖 + - name: 安装依赖 + run: pnpm install --no-frozen-lockfile + + # 5. 构建前端项目 + - name: 构建项目 + run: pnpm build + env: + # 这里可以添加构建时需要的环境变量 + VITE_USE_LOCAL_DB: 'true' + + # 6. 确定版本号 + - name: 确定版本号 + id: version + run: | + if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT + echo "IS_PRERELEASE=${{ github.event.inputs.prerelease }}" >> $GITHUB_OUTPUT + else + echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + echo "IS_PRERELEASE=false" >> $GITHUB_OUTPUT + fi + + # 7. 打包构建产物 + - name: 打包构建产物 + run: | + # 创建发布目录 + mkdir -p release + + # 打包前端构建产物 + tar -czf release/xcode-reviewer-frontend-${{ steps.version.outputs.VERSION }}.tar.gz -C dist . + + # 打包完整源码(包括配置文件) + tar -czf release/xcode-reviewer-source-${{ steps.version.outputs.VERSION }}.tar.gz \ + --exclude=node_modules \ + --exclude=dist \ + --exclude=.git \ + --exclude=release \ + . + + # 创建 checksums + cd release + sha256sum * > checksums.txt + cd .. + + # 8. 生成更新日志 + - name: 生成更新日志 + id: changelog + run: | + # 获取上一个 tag + PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") + + if [ -z "$PREVIOUS_TAG" ]; then + echo "这是第一个发布版本" > CHANGELOG.md + git log --pretty=format:"- %s (%h)" >> CHANGELOG.md + else + echo "自 $PREVIOUS_TAG 以来的变更:" > CHANGELOG.md + echo "" >> CHANGELOG.md + git log $PREVIOUS_TAG..HEAD --pretty=format:"- %s (%h)" >> CHANGELOG.md + fi + + echo "" >> CHANGELOG.md + echo "" >> CHANGELOG.md + echo "## 下载说明" >> CHANGELOG.md + echo "" >> CHANGELOG.md + echo "- \`xcode-reviewer-frontend-*.tar.gz\`: 前端构建产物(用于部署)" >> CHANGELOG.md + echo "- \`xcode-reviewer-source-*.tar.gz\`: 完整源码包" >> CHANGELOG.md + echo "- \`checksums.txt\`: 文件校验和" >> CHANGELOG.md + + # 9. 创建 GitHub Release + - name: 创建 Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ steps.version.outputs.VERSION }} + name: Release ${{ steps.version.outputs.VERSION }} + body_path: CHANGELOG.md + draft: false + prerelease: ${{ steps.version.outputs.IS_PRERELEASE }} + files: | + release/* + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # 10. 登录 GitHub Container Registry + - name: 登录到 GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # 11. 设置 Docker Buildx + - name: 设置 Docker Buildx + uses: docker/setup-buildx-action@v3 + + # 12. 构建并推送 Docker 镜像 + - name: 构建并推送 Docker 镜像 + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: | + ghcr.io/${{ github.repository }}:${{ steps.version.outputs.VERSION }} + ghcr.io/${{ github.repository }}:latest + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + VITE_USE_LOCAL_DB=true + + # 13. 更新 package.json 版本号(可选) + - name: 更新 package.json 版本 + if: github.event_name == 'workflow_dispatch' + run: | + VERSION="${{ steps.version.outputs.VERSION }}" + VERSION_NO_V="${VERSION#v}" + npm version $VERSION_NO_V --no-git-tag-version + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add package.json + git commit -m "chore: bump version to $VERSION" || true + git push origin HEAD:main || true + + diff --git a/.github/workflows/scheduled-release.yml b/.github/workflows/scheduled-release.yml new file mode 100644 index 0000000..605fab4 --- /dev/null +++ b/.github/workflows/scheduled-release.yml @@ -0,0 +1,77 @@ +name: 定时发布 + +# 定时触发(每月1号自动发布) +on: + schedule: + # 每月1号的 UTC 00:00 (北京时间 08:00) + - cron: '0 0 1 * *' + + # 也支持手动触发 + workflow_dispatch: + +jobs: + check-and-release: + name: 检查并发布 + runs-on: ubuntu-latest + + permissions: + contents: write + + steps: + # 1. 检出代码 + - name: 检出代码 + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # 2. 检查是否有新的提交 + - name: 检查是否有新提交 + id: check + run: | + # 获取最后一个 tag + LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + + if [ -z "$LAST_TAG" ]; then + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "version=v0.1.0" >> $GITHUB_OUTPUT + else + # 检查自上次 tag 以来是否有新的提交 + COMMITS_SINCE_TAG=$(git rev-list $LAST_TAG..HEAD --count) + + if [ "$COMMITS_SINCE_TAG" -gt "0" ]; then + echo "has_changes=true" >> $GITHUB_OUTPUT + + # 自动计算下一个版本号(小版本号 +1) + VERSION_NO_V="${LAST_TAG#v}" + IFS='.' read -r -a VERSION_PARTS <<< "$VERSION_NO_V" + MAJOR="${VERSION_PARTS[0]}" + MINOR="${VERSION_PARTS[1]}" + PATCH="${VERSION_PARTS[2]}" + + # 增加 minor 版本 + NEXT_MINOR=$((MINOR + 1)) + NEXT_VERSION="v${MAJOR}.${NEXT_MINOR}.0" + + echo "version=$NEXT_VERSION" >> $GITHUB_OUTPUT + else + echo "has_changes=false" >> $GITHUB_OUTPUT + fi + fi + + # 3. 创建新的 tag + - name: 创建版本标签 + if: steps.check.outputs.has_changes == 'true' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag -a ${{ steps.check.outputs.version }} -m "自动发布: ${{ steps.check.outputs.version }}" + git push origin ${{ steps.check.outputs.version }} + + # 4. 触发发布工作流 + - name: 触发发布 + if: steps.check.outputs.has_changes == 'true' + run: | + echo "新版本 ${{ steps.check.outputs.version }} 已创建,将触发发布流程" + echo "请查看 Release 工作流的执行情况" + + diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..064bd89 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,136 @@ +#!/bin/bash + +# 版本发布辅助脚本 +# 用法: ./scripts/release.sh [major|minor|patch|version] + +set -e + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# 打印彩色消息 +print_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +print_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# 检查是否在 git 仓库中 +if ! git rev-parse --git-dir > /dev/null 2>&1; then + print_error "当前目录不是 Git 仓库" + exit 1 +fi + +# 检查工作区是否干净 +if [ -n "$(git status --porcelain)" ]; then + print_warn "工作区有未提交的更改" + read -p "是否继续? (y/N) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi +fi + +# 获取当前版本号 +CURRENT_VERSION=$(node -p "require('./package.json').version") +print_info "当前版本: v$CURRENT_VERSION" + +# 解析版本号 +IFS='.' read -r -a VERSION_PARTS <<< "$CURRENT_VERSION" +MAJOR="${VERSION_PARTS[0]}" +MINOR="${VERSION_PARTS[1]}" +PATCH="${VERSION_PARTS[2]}" + +# 确定新版本号 +if [ -z "$1" ]; then + print_error "请指定版本类型: major, minor, patch 或具体版本号" + echo "用法: ./scripts/release.sh [major|minor|patch|version]" + echo "" + echo "示例:" + echo " ./scripts/release.sh patch # 0.0.1 -> 0.0.2" + echo " ./scripts/release.sh minor # 0.0.1 -> 0.1.0" + echo " ./scripts/release.sh major # 0.0.1 -> 1.0.0" + echo " ./scripts/release.sh 1.2.3 # 直接指定版本号" + exit 1 +fi + +case "$1" in + major) + NEW_MAJOR=$((MAJOR + 1)) + NEW_VERSION="${NEW_MAJOR}.0.0" + ;; + minor) + NEW_MINOR=$((MINOR + 1)) + NEW_VERSION="${MAJOR}.${NEW_MINOR}.0" + ;; + patch) + NEW_PATCH=$((PATCH + 1)) + NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}" + ;; + *) + # 假设是具体版本号 + NEW_VERSION="$1" + # 去掉可能的 v 前缀 + NEW_VERSION="${NEW_VERSION#v}" + ;; +esac + +print_info "新版本: v$NEW_VERSION" + +# 确认发布 +read -p "确认发布版本 v$NEW_VERSION? (y/N) " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + print_info "已取消" + exit 0 +fi + +# 更新 package.json +print_info "更新 package.json..." +npm version "$NEW_VERSION" --no-git-tag-version + +# 提交更改 +print_info "提交版本更改..." +git add package.json package-lock.json 2>/dev/null || true +git commit -m "chore: bump version to v$NEW_VERSION" || true + +# 创建 tag +print_info "创建 Git tag..." +git tag -a "v$NEW_VERSION" -m "Release v$NEW_VERSION" + +# 推送 +print_info "推送到远程仓库..." +echo "" +print_warn "即将执行以下操作:" +echo " 1. git push origin main" +echo " 2. git push origin v$NEW_VERSION" +echo "" +read -p "确认推送? (y/N) " -n 1 -r +echo + +if [[ $REPLY =~ ^[Yy]$ ]]; then + git push origin main || print_warn "推送 main 分支失败(可能没有更改)" + git push origin "v$NEW_VERSION" + + echo "" + print_info "✅ 版本 v$NEW_VERSION 发布成功!" + echo "" + print_info "GitHub Actions 将自动开始构建和发布流程" + print_info "查看进度: https://github.com/$(git remote get-url origin | sed 's/.*github.com[:/]\(.*\)\.git/\1/')/actions" +else + print_warn "已创建本地 tag,但未推送到远程" + print_info "如需推送,请手动执行:" + echo " git push origin main" + echo " git push origin v$NEW_VERSION" +fi + +