Files
novalon-website/scripts/check-pr-checklist.sh
T
zhangxiang 6bb7c557ee test(qa): e2e 装配修正 + 集成/门禁测试与工具链重构
- e2e:修死选择器与蜜罐误命中、补断言、GA4/security-headers 用例诚实标注
  skip 边界;新增 assert-helpers/fixtures/hydrated/primary-nav/touch-targets
  等复用桩。
- 集成层:config/test/jest.integration.config.js + tests-integration/ 真库
  一次性 SQLite 用例,teardown 守卫开发库指纹。
- 门禁工具链:jest.setup 归位到根、scripts/accessibility 全站 axe 节点计数、
  check-brand-text-token/check-motion-constraints 机械守卫、stryker/lighthouse
  配置收敛。
2026-09-28 10:48:08 +08:00

169 lines
6.6 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# check-pr-checklist.sh — PR 提交门禁检查(对齐 AGENTS.md §5 质量门禁 / §6 文档同步清单)
#
# 检查 PR 描述文件中 checklist 子项的勾选完成情况,以及 PR 模板的结构完整性。
# 规则:
# ① PR 模板必须包含「全链路检查」「测试分层检查」「质量门禁」三节
# ② PR 描述文件中所有 checklist 子项(- [ ])必须已勾选(不适用项勾选并注明 N/A 理由)
#
# 用法:
# bash scripts/check-pr-checklist.sh # 检查模板结构
# bash scripts/check-pr-checklist.sh <pr-description-file> # 检查 PR 描述文件
# bash scripts/check-pr-checklist.sh --pr-dir <目录> # 扫描目录下所有 PR 文件
# bash scripts/check-pr-checklist.sh --quiet # 仅违规时输出
# bash scripts/check-pr-checklist.sh --help
#
# 退出码: 0=全部通过 1=发现违规 2=模板不存在/格式错误
set -euo pipefail
# 说明:AGENT_PROJECT_DIR 供测试与多工作树场景覆写模板根目录;默认取脚本所在仓库根,
# 因此从任意子目录调用都能定位到 .gitea/PULL_REQUEST_TEMPLATE.md。
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TEMPLATE_FILE="${AGENT_PROJECT_DIR:-$REPO_ROOT}/.gitea/PULL_REQUEST_TEMPLATE.md"
QUIET=0
PR_FILE=""
SCAN_DIR=""
# 采用 while+shift 而非 for:for arg in "$@" 里 shift 不会跳过已消费的值,
# `--pr-dir <目录>` 会把 <目录> 再当成一次位置参数误判为 PR 文件。
while [ $# -gt 0 ]; do
case "$1" in
--help | -h)
echo "check-pr-checklist.sh — PR 提交门禁检查"
echo ""
echo "用法:"
echo " bash scripts/check-pr-checklist.sh # 检查模板结构"
echo " bash scripts/check-pr-checklist.sh <pr-file> # 检查 PR 描述文件"
echo " bash scripts/check-pr-checklist.sh --pr-dir <目录> # 扫描目录下所有 PR 文件"
echo " bash scripts/check-pr-checklist.sh --quiet # 仅违规时输出"
echo ""
echo "检查项:"
echo " - PR 模板结构完整性(三节必填 + checklist 子项 ≥ 20)"
echo " - PR 描述文件中所有 checklist 子项均已勾选"
exit 0
;;
--quiet | -q) QUIET=1 ;;
--pr-dir)
if [ $# -lt 2 ]; then
echo "❌ --pr-dir 需要一个目录参数" >&2
exit 2
fi
SCAN_DIR="$2"
shift
;;
-*)
echo "❌ 未知选项: $1" >&2
exit 2
;;
*)
if [ -n "$PR_FILE" ]; then
echo "❌ 只接受一个 PR 描述文件,多余参数: $1" >&2
exit 2
fi
PR_FILE="$1"
;;
esac
shift
done
violations=()
# ── 读取 PR 描述正文:仅在文件确实以 YAML frontmatter 开头时才剥离 ──
# 若无 frontmatter 也无条件剥离,awk 的 fm 永远 <2 → 正文为空 → 三个标准节全部
# 「未发现」→ 任何普通描述都被误判为不是 PR 描述文件。故按首行判别并加空正文兜底。
read_pr_body() {
local file="$1" body=""
if [ "$(head -1 "$file" 2>/dev/null)" = "---" ]; then
body=$(awk 'BEGIN{fm=0} /^---$/{fm++; next} fm>=2{print}' "$file" 2>/dev/null || true)
fi
[ -n "$body" ] || body=$(cat "$file")
printf '%s\n' "$body"
}
report_and_exit() {
local ok_msg="$1"
if [ "${#violations[@]}" -gt 0 ]; then
[ "$QUIET" -eq 0 ] && echo "❌ ${2}:"
printf '%s\n' "${violations[@]}" >&2
exit 1
fi
[ "$QUIET" -eq 0 ] && echo "✅ ${ok_msg}"
exit 0
}
# ── 模式 1: 扫描目录 ──
if [ -n "$SCAN_DIR" ]; then
if [ ! -d "$SCAN_DIR" ]; then
echo "✘ PR 目录不存在: ${SCAN_DIR} —— 门禁根本没跑。退出 2 表示脚本自身/入参故障,不等于通过" >&2
exit 2
fi
scanned=0
while IFS= read -r f; do
[ -f "$f" ] || continue
scanned=$((scanned + 1))
body=$(read_pr_body "$f")
unchecked=$(printf '%s\n' "$body" | grep -c '^- \[ \]' || true)
if [ "$unchecked" -gt 0 ]; then
violations+=("$(basename "$f"): 存在 ${unchecked} 个未勾选子项——所有 checklist 子项须勾选后才可提交 PR")
fi
done < <(find "$SCAN_DIR" -name '*.md' -type f | sort)
if [ "$scanned" -eq 0 ]; then
echo "✘ ${SCAN_DIR} 内没有任何 *.md —— 0 个文件被检查不等于 0 个违规。退出 2" >&2
exit 2
fi
[ "$QUIET" -eq 0 ] && echo "(已扫描 ${scanned} 个 PR 描述文件)"
report_and_exit "PR 描述文件门禁通过:所有文件子项均已勾选" "PR 描述文件门禁未通过"
fi
# ── 模式 2: 检查单个 PR 描述文件 ──
if [ -n "$PR_FILE" ]; then
if [ ! -f "$PR_FILE" ]; then
echo "❌ PR 描述文件不存在: $PR_FILE" >&2
exit 2
fi
body=$(read_pr_body "$PR_FILE")
unchecked=$(printf '%s\n' "$body" | grep -c '^- \[ \]' || true)
has_section1=$(printf '%s\n' "$body" | grep -c '全链路检查' || true)
has_section2=$(printf '%s\n' "$body" | grep -c '测试分层检查' || true)
has_section3=$(printf '%s\n' "$body" | grep -c '质量门禁' || true)
if [ "$has_section1" -eq 0 ] && [ "$has_section2" -eq 0 ] && [ "$has_section3" -eq 0 ]; then
violations+=("$(basename "$PR_FILE"): 未发现 PR 模板标准节(全链路检查/测试分层检查/质量门禁)——可能不是 PR 描述文件,或模板结构被修改")
fi
if [ "$unchecked" -gt 0 ]; then
violations+=("$(basename "$PR_FILE"): 存在 ${unchecked} 个未勾选子项——所有 checklist 子项须勾选后才可提交 PR")
fi
report_and_exit "PR 描述文件门禁通过:所有子项均已勾选" "PR 描述文件门禁未通过"
fi
# ── 模式 3: 检查 PR 模板结构完整性(默认模式)──
if [ ! -f "$TEMPLATE_FILE" ]; then
echo "❌ PR 模板文件不存在: $TEMPLATE_FILE" >&2
exit 2
fi
required_sections=("全链路检查" "测试分层检查" "质量门禁")
missing_sections=()
for section in "${required_sections[@]}"; do
if ! grep -q "^##.*${section}" "$TEMPLATE_FILE"; then
missing_sections+=("${section}")
fi
done
if [ "${#missing_sections[@]}" -gt 0 ]; then
sections_str=$(IFS=, ; echo "${missing_sections[*]}")
violations+=("PR 模板缺失必要节: ${sections_str}(PR 模板须包含全链路检查/测试分层检查/质量门禁三节)")
fi
checklist_count=$(grep -c '^- \[ \]' "$TEMPLATE_FILE" || true)
if [ "$checklist_count" -lt 20 ]; then
violations+=("PR 模板中 checklist 子项数量异常(仅 ${checklist_count} 项,预期 ≥ 20 项)——模板可能被意外修改")
fi
report_and_exit "PR 模板结构门禁通过:${checklist_count} 项子项,三节完整" "PR 模板结构门禁未通过"