feat: improve release workflow with test gate, changelog script and smart summary #125
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PR Check | |
| # PR 检查工作流:每次 PR 创建或更新时自动运行 | |
| # 确保代码质量、运行测试、验证构建 | |
| on: | |
| pull_request: | |
| branches: [ main ] | |
| # 触发事件类型: | |
| # - opened: PR 首次创建 | |
| # - synchronize: PR 分支有新 commit(关键!) | |
| # - reopened: PR 重新打开 | |
| types: [opened, synchronize, reopened] | |
| # 并发控制:同一 PR 的多次 push 只运行最新的一次 | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # ======================================== | |
| # Job 1: 后端检查 (Go) | |
| # ======================================== | |
| backend-check: | |
| name: Backend Check (Go) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.21' | |
| cache-dependency-path: './backend/go.sum' | |
| # Go 代码格式检查 | |
| - name: Go Fmt | |
| run: | | |
| cd backend | |
| echo "检查代码格式..." | |
| if [ -n "$(gofmt -l .)" ]; then | |
| echo "❌ 代码格式不符合规范,请运行 'gofmt -w .'" | |
| gofmt -d . | |
| exit 1 | |
| fi | |
| echo "✅ 代码格式检查通过" | |
| # Go 静态分析 | |
| - name: Go Vet | |
| run: | | |
| cd backend | |
| echo "运行静态分析..." | |
| go vet ./... | |
| # Go 依赖检查 | |
| - name: Go Mod Verify | |
| run: | | |
| cd backend | |
| echo "验证依赖完整性..." | |
| go mod verify | |
| # Go 单元测试 | |
| - name: Go Test | |
| run: | | |
| cd backend | |
| echo "运行单元测试..." | |
| go test ./... -v -race -coverprofile=coverage.out | |
| # 上传测试覆盖率 | |
| - name: Upload coverage | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| file: ./backend/coverage.out | |
| flags: backend | |
| fail_ci_if_error: false | |
| continue-on-error: true | |
| # ======================================== | |
| # Job 2: 前端检查 (React + TypeScript) | |
| # ======================================== | |
| frontend-check: | |
| name: Frontend Check (React) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: './desktop/package-lock.json' | |
| # 安装依赖 | |
| - name: Install dependencies | |
| run: | | |
| cd desktop | |
| npm ci | |
| # ESLint 代码检查 | |
| - name: Lint | |
| continue-on-error: true | |
| run: | | |
| cd desktop | |
| echo "运行 ESLint 检查..." | |
| npm run lint | |
| # TypeScript 类型检查 | |
| - name: Type Check | |
| run: | | |
| cd desktop | |
| echo "运行 TypeScript 类型检查..." | |
| npm run type-check || echo "⚠️ type-check 脚本未配置,跳过" | |
| continue-on-error: true | |
| # 构建检查 | |
| - name: Build | |
| run: | | |
| cd desktop | |
| echo "验证构建..." | |
| npm run build | |
| # ======================================== | |
| # Job 3: 烟雾测试(可选) | |
| # ======================================== | |
| smoke-test: | |
| name: Smoke Test | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: [backend-check, frontend-check] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.21' | |
| # 启动后端并验证健康检查 | |
| - name: Start backend and health check | |
| run: | | |
| cd backend | |
| # 复制配置文件(如果需要) | |
| if [ -f ".env.example" ]; then | |
| cp .env.example .env | |
| fi | |
| # 启动后端(后台运行) | |
| echo "启动后端服务..." | |
| go run cmd/server/main.go & | |
| SERVER_PID=$! | |
| # 等待后端启动(最多30秒) | |
| echo "等待后端启动..." | |
| MAX_WAIT=30 | |
| for i in $(seq 1 $MAX_WAIT); do | |
| # 检查进程是否存活 | |
| if ! kill -0 $SERVER_PID 2>/dev/null; then | |
| echo "❌ 后端进程意外退出" | |
| exit 1 | |
| fi | |
| # 检查健康检查接口(假设有 /health 或 /api/health) | |
| if curl -sf http://localhost:8080/health > /dev/null 2>&1 || \ | |
| curl -sf http://localhost:8080/api/health > /dev/null 2>&1; then | |
| echo "✅ 后端启动成功(${i}秒)" | |
| # 测试其他关键接口(可选) | |
| # curl -sf http://localhost:8080/api/status | |
| # 停止后端 | |
| kill $SERVER_PID | |
| exit 0 | |
| fi | |
| sleep 1 | |
| done | |
| # 超时 | |
| echo "❌ 后端启动超时(${MAX_WAIT}秒)" | |
| kill $SERVER_PID 2>/dev/null || true | |
| exit 1 | |
| continue-on-error: true | |
| # ======================================== | |
| # Job 4: PR 总结(可选) | |
| # ======================================== | |
| pr-summary: | |
| name: PR Summary | |
| runs-on: ubuntu-latest | |
| needs: [backend-check, frontend-check] | |
| if: always() | |
| steps: | |
| - name: Create PR summary | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const backend = '${{ needs.backend-check.result }}'; | |
| const frontend = '${{ needs.frontend-check.result }}'; | |
| const smoke = '${{ needs.smoke-test.result }}'; | |
| const statusEmoji = { | |
| 'success': '✅', | |
| 'failure': '❌', | |
| 'cancelled': '⚠️', | |
| 'skipped': '⏭️' | |
| }; | |
| const summary = `## 🔍 PR 检查结果 | |
| | 检查项 | 状态 | | |
| |--------|------| | |
| | **后端检查** (Go) | ${statusEmoji[backend] || '❓'} ${backend} | | |
| | **前端检查** (React) | ${statusEmoji[frontend] || '❓'} ${frontend} | | |
| | **烟雾测试** | ${statusEmoji[smoke] || '❓'} ${smoke} | | |
| ${backend === 'success' && frontend === 'success' ? '### ✅ 所有检查通过,可以合并!' : '### ⚠️ 请修复上述问题后再合并'} | |
| --- | |
| *由 GitHub Actions 自动生成* | |
| `; | |
| // 如果 PR 有更新,创建或更新评论 | |
| if (context.issue.number) { | |
| try { | |
| // 查找现有的总结评论 | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const botComment = comments.find(comment => | |
| comment.user.type === 'Bot' && | |
| comment.body.includes('🔍 PR 检查结果') | |
| ); | |
| if (botComment) { | |
| // 更新现有评论 | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body: summary | |
| }); | |
| } else { | |
| // 创建新评论 | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: summary | |
| }); | |
| } | |
| } catch (error) { | |
| console.log('无法创建 PR 评论:', error); | |
| } | |
| } | |
| core.summary.addRaw(summary).write(); |