From 3203984fb4a64e98a55f976e0141f7ce419da4c1 Mon Sep 17 00:00:00 2001 From: voson Date: Wed, 18 Mar 2026 14:30:54 +0800 Subject: [PATCH] =?UTF-8?q?ci:=20=E4=BC=98=E5=8C=96=20workflow=EF=BC=8C?= =?UTF-8?q?=E6=8B=86=E5=88=86=20check=20job=EF=BC=8C=E9=A2=84=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=20Release=EF=BC=8C=E8=B6=85=E6=97=B6=2010m?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 check job:fmt/clippy/test 仅在 Linux 跑一次 - version job 预创建 Release,消除多 job 竞态 - build job 只编译+上传,加 --locked - 超时从 30m 改为 10m - AGENTS.md 补充提交前检查规范 Made-with: Cursor --- .gitea/workflows/secrets.yml | 268 +++++++++++++++-------------------- AGENTS.md | 16 +++ 2 files changed, 134 insertions(+), 150 deletions(-) diff --git a/.gitea/workflows/secrets.yml b/.gitea/workflows/secrets.yml index 14aeea9..29a0023 100644 --- a/.gitea/workflows/secrets.yml +++ b/.gitea/workflows/secrets.yml @@ -24,21 +24,22 @@ env: RUST_BACKTRACE: short jobs: - # ========== 版本检查(只跑一次,供后续 job 共用)========== + # ========== 版本检查 + Release 预创建(单次运行,消除多 job 竞态)========== version: - name: 检查版本 + name: 版本 & Release runs-on: debian outputs: - version: ${{ steps.version.outputs.version }} - tag: ${{ steps.version.outputs.tag }} - tag_exists: ${{ steps.version.outputs.tag_exists }} + version: ${{ steps.ver.outputs.version }} + tag: ${{ steps.ver.outputs.tag }} + tag_exists: ${{ steps.ver.outputs.tag_exists }} + release_id: ${{ steps.release.outputs.release_id }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: 检查版本 - id: version + - name: 解析版本 + id: ver run: | version=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)".*/\1/') tag="secrets-${version}" @@ -54,19 +55,73 @@ jobs: fi - name: 创建 Tag - if: steps.version.outputs.tag_exists == 'false' + if: steps.ver.outputs.tag_exists == 'false' run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}" - git push origin "${{ steps.version.outputs.tag }}" + git tag -a "${{ steps.ver.outputs.tag }}" -m "Release ${{ steps.ver.outputs.tag }}" + git push origin "${{ steps.ver.outputs.tag }}" - # ========== 矩阵构建 ========== + - name: 预创建 Release + id: release + if: steps.ver.outputs.tag_exists == 'false' + env: + RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} + run: | + if [ -z "$RELEASE_TOKEN" ]; then + echo "release_id=" >> $GITHUB_OUTPUT + exit 0 + fi + + command -v jq &>/dev/null || (sudo apt-get update -qq && sudo apt-get install -y -qq jq) + + tag="${{ steps.ver.outputs.tag }}" + url="${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases" + release_id=$(curl -sS -H "Authorization: token $RELEASE_TOKEN" \ + -H "Content-Type: application/json" -X POST "$url" \ + -d "{\"tag_name\":\"${tag}\",\"name\":\"${tag}\",\"body\":\"Release ${tag}\"}" \ + | jq -r '.id // empty') + + echo "release_id=${release_id}" >> $GITHUB_OUTPUT + + # ========== 代码质量检查(只在 Linux 跑一次)========== + check: + name: 质量检查 (fmt / clippy / test) + runs-on: debian + timeout-minutes: 10 + steps: + - name: 安装 Rust + run: | + if ! command -v cargo &>/dev/null; then + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + fi + source "$HOME/.cargo/env" 2>/dev/null || true + rustup component add rustfmt clippy + + - uses: actions/checkout@v4 + + - name: 缓存 Cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry/index + ~/.cargo/registry/cache + ~/.cargo/git/db + target + key: cargo-check-${{ hashFiles('Cargo.lock') }} + restore-keys: cargo-check- + + - run: cargo fmt -- --check + - run: cargo clippy --locked -- -D warnings + - run: cargo test --locked + + # ========== 多平台构建 ========== build: name: Build (${{ matrix.target }}) - needs: version - continue-on-error: true # 某平台失败/超时不阻断其他平台和 notify - timeout-minutes: 30 # runner 不在线时 30 分钟后放弃 + needs: [version, check] + continue-on-error: true + timeout-minutes: 10 strategy: fail-fast: false matrix: @@ -86,35 +141,24 @@ jobs: runs-on: ${{ matrix.runner }} steps: - # ========== 环境准备 ========== - name: 安装依赖 (Linux) if: matrix.os == 'linux' run: | - sudo apt-get update - sudo apt-get install -y jq curl git pkg-config musl-tools binutils - + sudo apt-get update && sudo apt-get install -y pkg-config musl-tools binutils curl if ! command -v cargo &>/dev/null; then - echo "安装 Rust 工具链..." curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable - source "$HOME/.cargo/env" - rustup component add rustfmt clippy fi - + source "$HOME/.cargo/env" 2>/dev/null || true rustup target add ${{ matrix.target }} echo "$HOME/.cargo/bin" >> $GITHUB_PATH - name: 安装依赖 (macOS) if: matrix.os == 'macos' run: | - brew install jq - if ! command -v cargo &>/dev/null; then - echo "安装 Rust 工具链..." curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable - source "$HOME/.cargo/env" - rustup component add rustfmt clippy fi - + source "$HOME/.cargo/env" 2>/dev/null || true rustup target add ${{ matrix.target }} echo "$HOME/.cargo/bin" >> $GITHUB_PATH @@ -122,22 +166,16 @@ jobs: if: matrix.os == 'windows' shell: pwsh run: | - # 检查 Rust 是否已安装 if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) { - Write-Host "安装 Rust 工具链..." Invoke-WebRequest -Uri "https://win.rustup.rs/x86_64" -OutFile rustup-init.exe .\rustup-init.exe -y --default-toolchain stable Remove-Item rustup-init.exe } - rustup component add rustfmt clippy rustup target add ${{ matrix.target }} - uses: actions/checkout@v4 - with: - fetch-depth: 0 - # ========== Cargo 缓存 ========== - - name: 缓存 Cargo 依赖 + - name: 缓存 Cargo uses: actions/cache@v4 with: path: | @@ -145,169 +183,99 @@ jobs: ~/.cargo/registry/cache ~/.cargo/git/db target - key: cargo-secrets-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }} - restore-keys: | - cargo-secrets-${{ matrix.target }}- - - - name: 检查代码格式 - if: matrix.os != 'windows' - run: cargo fmt -- --check - - - name: 检查代码格式 (Windows) - if: matrix.os == 'windows' - shell: pwsh - run: cargo fmt -- --check - - - name: 运行 Clippy 检查 - if: matrix.os != 'windows' - run: cargo clippy --release --target ${{ matrix.target }} -- -D warnings - - - name: 运行 Clippy 检查 (Windows) - if: matrix.os == 'windows' - shell: pwsh - run: cargo clippy --release --target ${{ matrix.target }} -- -D warnings + key: cargo-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }} + restore-keys: cargo-${{ matrix.target }}- - name: 构建 if: matrix.os != 'windows' - env: - GIT_TAG: ${{ needs.version.outputs.version }} - run: cargo build --release --target ${{ matrix.target }} --verbose + run: cargo build --release --locked --target ${{ matrix.target }} - name: 构建 (Windows) if: matrix.os == 'windows' shell: pwsh - env: - GIT_TAG: ${{ needs.version.outputs.version }} - run: cargo build --release --target ${{ matrix.target }} --verbose + run: cargo build --release --locked --target ${{ matrix.target }} - - name: Strip 二进制 (Linux) + - name: Strip (Linux) if: matrix.os == 'linux' run: strip target/${{ matrix.target }}/release/${{ env.BINARY_NAME }} - - name: Strip 二进制 (macOS) + - name: Strip (macOS) if: matrix.os == 'macos' run: strip -x target/${{ matrix.target }}/release/${{ env.BINARY_NAME }} - # ========== 上传 Release 产物 ========== - - name: 上传 Release 产物 (Linux/macOS) - if: needs.version.outputs.tag_exists == 'false' && matrix.os != 'windows' + - name: 上传产物 (Linux/macOS) + if: needs.version.outputs.tag_exists == 'false' && needs.version.outputs.release_id && matrix.os != 'windows' env: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | - [ -z "$RELEASE_TOKEN" ] && echo "跳过:未配置 RELEASE_TOKEN" && exit 0 - + [ -z "$RELEASE_TOKEN" ] && exit 0 tag="${{ needs.version.outputs.tag }}" - binary="target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}" + bin="target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}" archive="${{ env.BINARY_NAME }}-${tag}-${{ matrix.archive_suffix }}.tar.gz" - - tar -czf "$archive" -C "$(dirname $binary)" "$(basename $binary)" - - # 查找已有 Release(由首个完成的 job 创建,后续 job 直接上传) - release_url="${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases" - release_id=$(curl -sS -H "Authorization: token $RELEASE_TOKEN" \ - "${release_url}/tags/${tag}" | jq -r '.id // empty') - - if [ -z "$release_id" ]; then - release_id=$(curl -sS -H "Authorization: token $RELEASE_TOKEN" \ - -H "Content-Type: application/json" \ - -X POST "$release_url" \ - -d "{\"tag_name\": \"${tag}\", \"name\": \"${tag}\", \"body\": \"Release ${tag}\"}" \ - | jq -r '.id') - fi - - upload_url="${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/${release_id}/assets" + tar -czf "$archive" -C "$(dirname $bin)" "$(basename $bin)" curl -sS -H "Authorization: token $RELEASE_TOKEN" \ -F "attachment=@${archive}" \ - "$upload_url" + "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/${{ needs.version.outputs.release_id }}/assets" + echo "已上传: ${archive}" - echo "已上传: ${archive} → Release ${tag}" - - - name: 上传 Release 产物 (Windows) - if: needs.version.outputs.tag_exists == 'false' && matrix.os == 'windows' + - name: 上传产物 (Windows) + if: needs.version.outputs.tag_exists == 'false' && needs.version.outputs.release_id && matrix.os == 'windows' shell: pwsh env: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | - if (-not $env:RELEASE_TOKEN) { Write-Host "跳过:未配置 RELEASE_TOKEN"; exit 0 } - + if (-not $env:RELEASE_TOKEN) { exit 0 } $tag = "${{ needs.version.outputs.tag }}" - $binary = "target\${{ matrix.target }}\release\${{ env.BINARY_NAME }}.exe" + $bin = "target\${{ matrix.target }}\release\${{ env.BINARY_NAME }}.exe" $archive = "${{ env.BINARY_NAME }}-${tag}-${{ matrix.archive_suffix }}.zip" + Compress-Archive -Path $bin -DestinationPath $archive + $url = "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/${{ needs.version.outputs.release_id }}/assets" + Invoke-RestMethod -Uri $url -Method Post ` + -Headers @{ "Authorization" = "token $env:RELEASE_TOKEN" } ` + -Form @{ attachment = Get-Item $archive } + Write-Host "已上传: ${archive}" - Compress-Archive -Path $binary -DestinationPath $archive - - $headers = @{ "Authorization" = "token $env:RELEASE_TOKEN"; "Content-Type" = "application/json" } - $release_url = "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases" - - # 查找已有 Release - $existing = Invoke-RestMethod -Uri "${release_url}/tags/${tag}" -Headers $headers -ErrorAction SilentlyContinue - if ($existing.id) { - $release_id = $existing.id - } else { - $body = @{ tag_name = $tag; name = $tag; body = "Release ${tag}" } | ConvertTo-Json - $release_id = (Invoke-RestMethod -Uri $release_url -Method Post -Headers $headers -Body $body).id - } - - $upload_url = "${release_url}/${release_id}/assets" - $upload_headers = @{ "Authorization" = "token $env:RELEASE_TOKEN" } - $form = @{ attachment = Get-Item $archive } - Invoke-RestMethod -Uri $upload_url -Method Post -Headers $upload_headers -Form $form - - Write-Host "已上传: ${archive} → Release ${tag}" - - # ========== 汇总通知 ========== + # ========== 通知 ========== notify: - name: 发送通知 + name: 通知 needs: [version, build] if: always() && github.event_name == 'push' runs-on: debian steps: - uses: actions/checkout@v4 - - name: 发送通知 + - name: 发送飞书通知 continue-on-error: true env: WEBHOOK_URL: ${{ vars.WEBHOOK_URL }} run: | [ -z "$WEBHOOK_URL" ] && exit 0 + command -v jq &>/dev/null || (sudo apt-get update -qq && sudo apt-get install -y -qq jq) tag="${{ needs.version.outputs.tag }}" tag_exists="${{ needs.version.outputs.tag_exists }}" - build_result="${{ needs.build.result }}" + result="${{ needs.build.result }}" + [ "$result" = "success" ] && status="构建成功 ✅" || status="构建失败 ❌" - if [ "$build_result" = "success" ]; then - status_text="构建成功 ✅" - else - status_text="构建失败 ❌" - fi + commit=$(git log -1 --pretty=format:"%s" 2>/dev/null || echo "N/A") + url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_number }}" - commit_title=$(git log -1 --pretty=format:"%s" 2>/dev/null || echo "N/A") - workflow_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_number }}" - - if [ "$build_result" != "success" ]; then - payload=$(jq -n \ - --arg title "${{ env.BINARY_NAME }} ${status_text}" \ - --arg commit "$commit_title" \ - --arg version "$tag" \ - --arg author "${{ github.actor }}" \ - --arg url "$workflow_url" \ - '{msg_type: "text", content: {text: "\($title)\n提交:\($commit)\n版本:\($version)\n作者:\($author)\n详情:\($url)"}}') + if [ "$result" != "success" ]; then + extra="" elif [ "$tag_exists" = "false" ]; then - payload=$(jq -n \ - --arg title "${{ env.BINARY_NAME }} ${status_text}" \ - --arg commit "$commit_title" \ - --arg version "$tag" \ - --arg author "${{ github.actor }}" \ - --arg url "$workflow_url" \ - '{msg_type: "text", content: {text: "\($title)\n🆕 新版本已发布 (linux / macOS / windows)\n提交:\($commit)\n版本:\($version)\n作者:\($author)\n详情:\($url)"}}') + extra="🆕 新版本已发布 (linux / macOS / windows)" else - payload=$(jq -n \ - --arg title "${{ env.BINARY_NAME }} ${status_text}" \ - --arg commit "$commit_title" \ - --arg version "$tag" \ - --arg author "${{ github.actor }}" \ - --arg url "$workflow_url" \ - '{msg_type: "text", content: {text: "\($title)\n🔄 重复构建\n提交:\($commit)\n版本:\($version)\n作者:\($author)\n详情:\($url)"}}') + extra="🔄 重复构建" fi + msg="${{ env.BINARY_NAME }} ${status}" + [ -n "$extra" ] && msg="${msg} + ${extra}" + msg="${msg} + 提交:${commit} + 版本:${tag} + 作者:${{ github.actor }} + 详情:${url}" + + payload=$(jq -n --arg text "$msg" '{msg_type:"text",content:{text:$text}}') curl -sS -H "Content-Type: application/json" -X POST -d "$payload" "$WEBHOOK_URL" diff --git a/AGENTS.md b/AGENTS.md index 1febb54..bc47127 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -114,6 +114,22 @@ secrets search --tag hongkong - 新增 `kind` 类型时:只需在 `add` 调用时传入,无需改代码 - 字段命名:CLI 短标志 `-n`=namespace,`-m`=meta,`-s`=secret,`-q`=query +## 提交前检查(必须全部通过) + +每次提交代码前,请在本地依次执行以下三项检查,**全部通过后再 push**: + +```bash +cargo fmt -- --check # 格式检查(不通过则运行 cargo fmt 修复) +cargo clippy -- -D warnings # Lint 检查(消除所有 warning) +cargo test # 单元/集成测试 +``` + +或一次性执行: + +```bash +cargo fmt -- --check && cargo clippy -- -D warnings && cargo test +``` + ## CI/CD - Gitea Actions(runner: debian)