PR Check #250
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 | |
| # REF https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target | |
| # | |
| # 触发: | |
| # - pull_request_target:PR 打开 / 同步 / 重开 / 打 Check 标签时检查 | |
| # - schedule / workflow_dispatch:复检开放中的 ci-failed PR(作者常只改包仓库 Release) | |
| # schedule 按评论 meta 指纹变更 / 退避到期筛选;workflow_dispatch 强制全选(仍受 SELECT_LIMIT) | |
| # | |
| # 标签: | |
| # - Check:维护者打标以立即重跑本检查(打标后会自动去掉) | |
| # - ci-skip:维护者打标则跳过检查(与 Check 同时存在时以 ci-skip 为准) | |
| on: | |
| pull_request_target: | |
| types: | |
| - opened | |
| - synchronize | |
| - reopened | |
| - labeled | |
| branches: | |
| - main | |
| paths: | |
| - plugins.txt | |
| - themes.txt | |
| - icons.txt | |
| - templates.txt | |
| - widgets.txt | |
| # 黑名单路径:用于触发检查(命中后由 Go 以 FlowError 失败,不跑包检查) | |
| - config/themes-theme-js-allowlist.txt | |
| - stage/** | |
| schedule: | |
| # 每 20 分钟:select 按指纹 / 退避筛选后再全检 | |
| - cron: '7,27,47 * * * *' | |
| workflow_dispatch: | |
| # pull_request_target:同一 PR 重复触发时取消整次旧 run(含 prepare),减轻双开竞态 | |
| # schedule / workflow_dispatch:用 run_id 互不取消;同 PR 仍靠下方 check job 的 concurrency | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} | |
| cancel-in-progress: true | |
| jobs: | |
| # 产出待检 PR 矩阵:事件 PR 一条,或定时/手动筛选开放 ci-failed | |
| prepare: | |
| if: | | |
| github.repository_owner == 'siyuan-note' && | |
| ( | |
| ( | |
| github.event_name == 'pull_request_target' && | |
| !contains(toJSON(github.event.pull_request.labels.*.name), '"ci-skip"') && | |
| (github.event.action != 'labeled' || github.event.label.name == 'Check') | |
| ) || | |
| github.event_name == 'schedule' || | |
| github.event_name == 'workflow_dispatch' | |
| ) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| pull-requests: read | |
| contents: read | |
| outputs: | |
| matrix: ${{ steps.build.outputs.matrix || steps.build-select.outputs.matrix }} | |
| any: ${{ steps.build.outputs.any || steps.build-select.outputs.any }} | |
| steps: | |
| - name: Build PR matrix (event) | |
| id: build | |
| if: github.event_name == 'pull_request_target' | |
| env: | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} | |
| run: | | |
| set -euo pipefail | |
| include=$(jq -nc \ | |
| --argjson number "$PR_NUMBER" \ | |
| --arg head_sha "$PR_HEAD_SHA" \ | |
| --arg base_sha "$PR_BASE_SHA" \ | |
| --arg head_repo "$PR_HEAD_REPO" \ | |
| '[{number: $number, head_sha: $head_sha, base_sha: $base_sha, head_repo: $head_repo}]') | |
| matrix=$(jq -nc --argjson include "$include" '{include: $include}') | |
| any=$(jq -nc --argjson include "$include" '$include | length > 0') | |
| echo "PRs to check (any=$any): $include" | |
| echo "matrix=$matrix" >> "$GITHUB_OUTPUT" | |
| echo "any=$any" >> "$GITHUB_OUTPUT" | |
| - name: Check out bazaar (select) | |
| if: github.event_name != 'pull_request_target' | |
| uses: actions/checkout@v7 | |
| - name: Setup Golang (select) | |
| if: github.event_name != 'pull_request_target' | |
| uses: actions/setup-go@v7 | |
| with: | |
| go-version-file: 'go.mod' | |
| - name: Build PR matrix (schedule / dispatch) | |
| id: build-select | |
| if: github.event_name != 'pull_request_target' | |
| env: | |
| PAT: ${{ secrets.PAT }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # workflow_dispatch:强制纳入全部开放 ci-failed(仍受 SELECT_LIMIT) | |
| SELECT_FORCE_ALL: ${{ github.event_name == 'workflow_dispatch' }} | |
| run: go run ./actions/check -select | |
| check: | |
| needs: prepare | |
| if: needs.prepare.outputs.any == 'true' | |
| # 覆盖默认矩阵名(会把 sha 拼进去);显示 PR 号与 fork owner/repo | |
| name: "#${{ matrix.number }} ${{ matrix.head_repo }}" | |
| runs-on: ubuntu-latest | |
| # 正常单 PR 检查约 1 分钟;用于尽快杀掉卡在 Starting job 的幽灵占用,避免 concurrency 互等 | |
| timeout-minutes: 5 | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.prepare.outputs.matrix) }} | |
| # 同一 PR 只保留最新一次检查,避免 synchronize / 定时复检重叠写评论 | |
| # (与 workflow 级 concurrency 互补:覆盖 schedule 与 PR 事件撞同一 PR 的情况) | |
| concurrency: | |
| group: pr-check-${{ matrix.number }} | |
| cancel-in-progress: true | |
| # 改 PR 标题 / 同步标签 / 请求审查 / 发评论 / 去掉 Check 标签;Release 与签出 fork 仍用 PAT | |
| permissions: | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| env: | |
| PAT: ${{ secrets.PAT }} | |
| # 须显式传入;仅写 permissions 不会自动注入到进程环境,否则 Go 会回退 PAT | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # 检查通过后请求审查;名单在仓库 Actions Variables(勿写死在代码里) | |
| BAZAAR_REVIEWERS: ${{ vars.BAZAAR_REVIEWERS }} | |
| BAZAAR_HEAD_PATH: . | |
| PR_HEAD_PATH: ./pr-head | |
| PR_BASE_PATH: ./pr-base | |
| CHECK_RESULT_OUTPUT: ./check-result.md | |
| PR_NUMBER: ${{ matrix.number }} | |
| steps: | |
| # 签出 bazaar main 分支最新提交,用于过滤与 name 唯一性检查;fetch-depth: 0 以便后续计算 merge base | |
| - name: Check out bazaar head | |
| uses: actions/checkout@v7 | |
| with: | |
| ref: main | |
| fetch-depth: 0 | |
| # 获取 PR 的 merge base(与 GitHub "Files changed" 一致),避免 PR 分支未同步最新 main 时 diff 错位 | |
| # REF https://docs.github.com/zh/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-comparing-branches-in-pull-requests | |
| - name: Get PR merge base | |
| id: merge-base | |
| run: | | |
| set -e | |
| git fetch origin "pull/${{ matrix.number }}/head:pr-head-ref" | |
| merge_base_sha=$(git merge-base "${{ matrix.base_sha }}" pr-head-ref) || { | |
| echo "::error::Failed to find merge base (e.g. PR branch has no common history with base)." | |
| exit 1 | |
| } | |
| if [ -z "$merge_base_sha" ] || [ ${#merge_base_sha} -ne 40 ]; then | |
| echo "::error::Invalid merge base SHA: $merge_base_sha" | |
| exit 1 | |
| fi | |
| echo "sha=$merge_base_sha" >> $GITHUB_OUTPUT | |
| # 签出 PR 的 head 提交(PR 分支当前提交) | |
| # checkout@v7 默认拒绝在 pull_request_target 中签出 fork PR 代码;此处仅将列表文件当数据读取,不执行 PR 代码 | |
| # REF https://github.com/marketplace/actions/checkout#checkout-multiple-repos-nested | |
| # REF https://github.com/marketplace/actions/checkout#checkout-pull-request-head-commit-instead-of-merge-commit | |
| # REF https://gh.io/securely-using-pull_request_target | |
| - name: Check out PR head | |
| uses: actions/checkout@v7 | |
| with: | |
| path: ${{ env.PR_HEAD_PATH }} | |
| ref: ${{ matrix.head_sha }} | |
| repository: ${{ matrix.head_repo }} | |
| token: ${{ env.PAT }} | |
| allow-unsafe-pr-checkout: true | |
| # 签出 PR 的 merge base 提交(与 GitHub "Files changed" 的基准一致,确保检查的 diff 即本 PR 实际改动) | |
| - name: Check out PR base (merge base) | |
| uses: actions/checkout@v7 | |
| with: | |
| path: ${{ env.PR_BASE_PATH }} | |
| ref: ${{ steps.merge-base.outputs.sha }} | |
| - name: Setup Golang | |
| uses: actions/setup-go@v7 | |
| with: | |
| go-version-file: 'go.mod' | |
| - name: Go Check | |
| id: go-check | |
| run: go run ./actions/check | |
| # REF https://github.com/marketplace/actions/comment-pull-request | |
| # 无实际变更且 PR 已合并/关闭时 Go 会设 skip_side_effects,避免竞态误评 | |
| # result_hash 变化时 Go 输出 comment_mode=recreate,重建评论以便 @ 作者再次收到通知;未变则 upsert | |
| - name: Comment PR with check-result | |
| if: steps.go-check.outputs.skip_side_effects != 'true' | |
| uses: thollander/actions-comment-pull-request@v3 | |
| with: | |
| file-path: ${{ env.CHECK_RESULT_OUTPUT }} | |
| comment-tag: check-result | |
| pr-number: ${{ matrix.number }} | |
| mode: ${{ steps.go-check.outputs.comment_mode || 'upsert' }} | |
| # REF https://docs.github.com/zh/rest/issues/labels?apiVersion=2022-11-28#remove-all-labels-from-an-issue | |
| # REF https://docs.github.com/zh/actions/tutorials/authenticate-with-github_token#example-2-calling-the-rest-api | |
| - name: Remove "Check" label | |
| if: always() && github.event_name == 'pull_request_target' && github.event.action == 'labeled' && github.event.label.name == 'Check' | |
| run: | | |
| curl -L \ | |
| -X DELETE \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| "https://api.github.com/repos/${{ github.repository }}/issues/${{ matrix.number }}/labels/Check" |