Skip to content

PR Check

PR Check #250

Workflow file for this run

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"