Repository navigation
ci: add GitHub Actions workflows and CodeRabbit config #1
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
| # Release 自动创建工作流 | |
| # 功能: | |
| # 1. 手动触发或 Agent 触发创建 Release | |
| # 2. 收集上次 Release 以来的所有 PR | |
| # 3. 调用 AI (DeepSeek) 分析变更并决定版本号 | |
| # 4. 自动生成 Release Notes | |
| # 5. 创建 GitHub Release | |
| # 6. 触发 Steam 上传工作流 | |
| name: Create Release | |
| on: | |
| # 手动触发 | |
| workflow_dispatch: | |
| inputs: | |
| bump_type: | |
| description: '版本号升级类型 (可选)' | |
| required: false | |
| default: 'auto' | |
| type: choice | |
| options: | |
| - auto | |
| - major | |
| - minor | |
| - patch | |
| prerelease: | |
| description: '是否为预发布版本' | |
| required: false | |
| default: false | |
| type: boolean | |
| notes: | |
| description: '补充说明 (可选)' | |
| required: false | |
| type: string | |
| # 当推送到 main/master 分支且包含 version-bump 标记时自动触发 | |
| push: | |
| branches: | |
| - main | |
| - master | |
| paths-ignore: | |
| - '**.md' | |
| - 'docs/**' | |
| - '.github/**' | |
| permissions: | |
| contents: write | |
| pull-requests: read | |
| actions: write | |
| env: | |
| PYTHON_VERSION: '3.11' | |
| jobs: | |
| # 准备阶段:收集信息和检查 | |
| prepare: | |
| name: Prepare Release | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_release: ${{ steps.check.outputs.should_release }} | |
| current_version: ${{ steps.version.outputs.current_version }} | |
| mod_name: ${{ steps.mod_info.outputs.mod_name }} | |
| prs_since_last_release: ${{ steps.collect_prs.outputs.prs }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Get Mod Info | |
| id: mod_info | |
| run: | | |
| # 从 About.xml 读取 Mod 名称 | |
| if [ -f "About/About.xml" ]; then | |
| MOD_NAME=$(grep -oP '(?<=<name>)[^<]+' About/About.xml | head -1) | |
| echo "mod_name=$MOD_NAME" >> $GITHUB_OUTPUT | |
| else | |
| # 使用仓库名 | |
| echo "mod_name=${{ github.event.repository.name }}" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Get Current Version | |
| id: version | |
| run: | | |
| # 获取最新的 release tag | |
| LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") | |
| echo "current_version=$LATEST_TAG" >> $GITHUB_OUTPUT | |
| echo "当前版本: $LATEST_TAG" | |
| - name: Check Should Release | |
| id: check | |
| run: | | |
| # 如果是手动触发,总是执行 | |
| if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then | |
| echo "should_release=true" >> $GITHUB_OUTPUT | |
| echo "手动触发,执行发布" | |
| exit 0 | |
| fi | |
| # 检查上次 release 以来是否有新的 commit | |
| LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") | |
| if [ -z "$LATEST_TAG" ]; then | |
| echo "should_release=true" >> $GITHUB_OUTPUT | |
| echo "没有找到之前的 release,执行发布" | |
| else | |
| COMMITS_SINCE=$(git log $LATEST_TAG..HEAD --oneline | wc -l) | |
| if [ "$COMMITS_SINCE" -gt 0 ]; then | |
| echo "should_release=true" >> $GITHUB_OUTPUT | |
| echo "自上次 release 有 $COMMITS_SINCE 个新提交,执行发布" | |
| else | |
| echo "should_release=false" >> $GITHUB_OUTPUT | |
| echo "没有新提交,跳过发布" | |
| fi | |
| fi | |
| - name: Collect PRs Since Last Release | |
| id: collect_prs | |
| if: steps.check.outputs.should_release == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| // 获取上次 release 的日期 | |
| const { data: releases } = await github.rest.repos.listReleases({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| per_page: 1 | |
| }); | |
| let since = null; | |
| if (releases.length > 0 && releases[0].published_at) { | |
| since = new Date(releases[0].published_at); | |
| } | |
| // 获取所有 merged 的 PR | |
| const { data: pulls } = await github.rest.pulls.list({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: 'closed', | |
| sort: 'updated', | |
| direction: 'desc', | |
| per_page: 100 | |
| }); | |
| const mergedPRs = []; | |
| for (const pr of pulls) { | |
| if (!pr.merged_at) continue; | |
| if (since && new Date(pr.merged_at) <= since) continue; | |
| // 获取 PR 的文件变更 | |
| const { data: files } = await github.rest.pulls.listFiles({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: pr.number | |
| }); | |
| mergedPRs.push({ | |
| number: pr.number, | |
| title: pr.title, | |
| body: pr.body, | |
| author: pr.user.login, | |
| merged_at: pr.merged_at, | |
| labels: pr.labels.map(l => l.name), | |
| files: files.map(f => ({ | |
| path: f.filename, | |
| changeType: f.status | |
| })) | |
| }); | |
| } | |
| // 按合并时间排序 | |
| mergedPRs.sort((a, b) => new Date(a.merged_at) - new Date(b.merged_at)); | |
| console.log(`找到 ${mergedPRs.length} 个 PR 自上次 release`); | |
| for (const pr of mergedPRs) { | |
| console.log(` #${pr.number}: ${pr.title}`); | |
| } | |
| // 输出为 JSON 字符串 | |
| const prsJson = JSON.stringify(mergedPRs).replace(/"/g, '\\"'); | |
| core.setOutput('prs', prsJson); | |
| # AI 分析阶段:决定版本号 | |
| analyze: | |
| name: AI Analyze Release | |
| needs: prepare | |
| if: needs.prepare.outputs.should_release == 'true' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| bump: ${{ steps.ai.outputs.bump }} | |
| new_version: ${{ steps.ai.outputs.new_version }} | |
| release_notes: ${{ steps.ai.outputs.release_notes }} | |
| reason: ${{ steps.ai.outputs.reason }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Install Dependencies | |
| run: pip install openai | |
| - name: Download Analyze Script | |
| run: | | |
| if [ -f ".github/scripts/analyze_release.py" ]; then | |
| echo "使用本地脚本" | |
| else | |
| echo "::error::找不到 analyze_release.py 脚本" | |
| exit 1 | |
| fi | |
| - name: Save PRs to File | |
| run: | | |
| echo "${{ needs.prepare.outputs.prs_since_last_release }}" > /tmp/prs.json | |
| - name: Run AI Analysis | |
| id: ai | |
| env: | |
| DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }} | |
| DEEPSEEK_BASE_URL: ${{ secrets.DEEPSEEK_BASE_URL || 'https://api.deepseek.com' }} | |
| DEEPSEEK_MODEL: ${{ secrets.DEEPSEEK_MODEL || 'deepseek-chat' }} | |
| run: | | |
| MOD_NAME="${{ needs.prepare.outputs.mod_name }}" | |
| CURRENT_VERSION="${{ needs.prepare.outputs.current_version }}" | |
| MANUAL_BUMP="${{ github.event.inputs.bump_type || 'auto' }}" | |
| echo "分析 Mod: $MOD_NAME" | |
| echo "当前版本: $CURRENT_VERSION" | |
| echo "手动指定: $MANUAL_BUMP" | |
| # 运行分析脚本 | |
| python .github/scripts/analyze_release.py \ | |
| --mod-name "$MOD_NAME" \ | |
| --prs-file /tmp/prs.json \ | |
| --current-version "$CURRENT_VERSION" \ | |
| --manual-bump "$MANUAL_BUMP" | |
| - name: Check Analysis Result | |
| run: | | |
| if [ "${{ steps.ai.outputs.bump }}" == "none" ]; then | |
| echo "::warning::AI 判断无需升级版本号" | |
| exit 0 | |
| fi | |
| echo "AI 建议升级: ${{ steps.ai.outputs.bump }} -> ${{ steps.ai.outputs.new_version }}" | |
| echo "原因: ${{ steps.ai.outputs.reason }}" | |
| # 创建 Release | |
| create-release: | |
| name: Create GitHub Release | |
| needs: [prepare, analyze] | |
| if: needs.prepare.outputs.should_release == 'true' && needs.analyze.outputs.bump != 'none' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| upload_url: ${{ steps.create_release.outputs.upload_url }} | |
| release_id: ${{ steps.create_release.outputs.id }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Update About.xml Version | |
| run: | | |
| NEW_VERSION="${{ needs.analyze.outputs.new_version }}" | |
| # 去掉 v 前缀 | |
| VERSION_NUM="${NEW_VERSION#v}" | |
| if [ -f "About/About.xml" ]; then | |
| # 更新 modVersion | |
| sed -i "s|<modVersion>[^<]*</modVersion>|<modVersion>${VERSION_NUM}</modVersion>|g" About/About.xml | |
| echo "已更新 About.xml 版本号为: $VERSION_NUM" | |
| fi | |
| - name: Commit Version Update | |
| run: | | |
| git config user.name "GitHub Actions" | |
| git config user.email "actions@github.com" | |
| if git diff --quiet; then | |
| echo "没有变更需要提交" | |
| else | |
| git add About/About.xml | |
| git commit -m "chore: bump version to ${{ needs.analyze.outputs.new_version }}" | |
| git push | |
| fi | |
| - name: Create Release Notes File | |
| run: | | |
| NOTES="${{ needs.analyze.outputs.release_notes }}" | |
| # 解码换行符 | |
| echo -e "$NOTES" > /tmp/release_notes.md | |
| # 添加补充说明 | |
| if [ -n "${{ github.event.inputs.notes }}" ]; then | |
| echo "" >> /tmp/release_notes.md | |
| echo "---" >> /tmp/release_notes.md | |
| echo "" >> /tmp/release_notes.md | |
| echo "## 补充说明" >> /tmp/release_notes.md | |
| echo "${{ github.event.inputs.notes }}" >> /tmp/release_notes.md | |
| fi | |
| - name: Create Tag | |
| run: | | |
| git tag "${{ needs.analyze.outputs.new_version }}" | |
| git push origin "${{ needs.analyze.outputs.new_version }}" | |
| - name: Create GitHub Release | |
| id: create_release | |
| uses: actions/create-release@v1 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| tag_name: ${{ needs.analyze.outputs.new_version }} | |
| release_name: ${{ needs.prepare.outputs.mod_name }} ${{ needs.analyze.outputs.new_version }} | |
| body_path: /tmp/release_notes.md | |
| prerelease: ${{ github.event.inputs.prerelease || false }} | |
| # 打包 Mod 文件 | |
| package: | |
| name: Package Mod | |
| needs: [prepare, analyze, create-release] | |
| if: needs.analyze.outputs.bump != 'none' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Package Mod Files | |
| run: | | |
| MOD_NAME="${{ needs.prepare.outputs.mod_name }}" | |
| VERSION="${{ needs.analyze.outputs.new_version }}" | |
| # 创建临时目录 | |
| mkdir -p /tmp/mod-package | |
| # 复制文件 (排除开发文件) | |
| rsync -av --delete \ | |
| --exclude='.git/' \ | |
| --exclude='.github/' \ | |
| --exclude='Source/' \ | |
| --exclude='Tests/' \ | |
| --exclude='script/' \ | |
| --exclude='.claude/' \ | |
| --exclude='.trae/' \ | |
| --exclude='docs/' \ | |
| --exclude='*.csproj' \ | |
| --exclude='*.sln' \ | |
| --exclude='.gitignore' \ | |
| --exclude='.gitattributes' \ | |
| --exclude='*.md' \ | |
| ./ /tmp/mod-package/ | |
| # 打包 | |
| cd /tmp | |
| zip -r "${MOD_NAME}-${VERSION}.zip" mod-package/ | |
| echo "打包完成: ${MOD_NAME}-${VERSION}.zip" | |
| - name: Upload Release Asset | |
| uses: actions/upload-release-asset@v1 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| upload_url: ${{ needs.create-release.outputs.upload_url }} | |
| asset_path: /tmp/${{ needs.prepare.outputs.mod_name }}-${{ needs.analyze.outputs.new_version }}.zip | |
| asset_name: ${{ needs.prepare.outputs.mod_name }}-${{ needs.analyze.outputs.new_version }}.zip | |
| asset_content_type: application/zip | |
| # 触发 Steam 上传 | |
| trigger-steam-upload: | |
| name: Trigger Steam Upload | |
| needs: [prepare, analyze, create-release] | |
| if: needs.analyze.outputs.bump != 'none' && !github.event.inputs.prerelease | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Trigger Steam Upload Workflow | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| await github.rest.actions.createWorkflowDispatch({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| workflow_id: 'steam-upload.yml', | |
| ref: 'main', | |
| inputs: { | |
| release_tag: '${{ needs.analyze.outputs.new_version }}', | |
| release_id: '${{ needs.create-release.outputs.release_id }}' | |
| } | |
| }); | |
| console.log('已触发 Steam 上传工作流'); |