#!/bin/bash # CI 公共步骤:检测 push(develop/main) 事件的改动范围 # 输出 skip_backend / skip_frontend(复用 PR check 的语义) # - 纯前端改动(仅 apps/web/): skip_backend=true # - 纯后端改动(不含 apps/web/): skip_frontend=true # - 全栈 / 无法判断: 两者都 false(走全量,安全兜底) # 需要环境变量: GITHUB_TOKEN, GITHUB_API_URL, GITHUB_REPOSITORY, GITHUB_SHA set -eu OUTPUT="${GITHUB_OUTPUT:-/dev/stdout}" before="${GITHUB_EVENT_BEFORE:-}" after="${GITHUB_SHA:-}" repo="${GITHUB_REPOSITORY:-}" base="${GITHUB_API_URL:-}" ZERO="0000000000000000000000000000000000000000" # Gitea Actions 中 push 事件的前一个 SHA 在 event payload 的 before 字段 if [ -z "$before" ] && [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -f "$GITHUB_EVENT_PATH" ]; then before=$(python3 -c " import json,sys try: d=json.load(open('${GITHUB_EVENT_PATH}')) print(d.get('before','') or '') except Exception: print('') ") fi echo "改动范围检测: before=${before:-} after=${after}" # ── 安全回溯:确保 diff 基准覆盖所有未构建的改动 ── # 问题:concurrency 取消机制会导致前端改动被跳过。被取消的 push 的改动不会被 # 后续 push 的 diff 覆盖到,因为 GITHUB_EVENT_BEFORE 只指向上一次 push 的 SHA。 # 修复:查询最近一次**实际构建了 web 镜像**的成功 push run,用其 head_sha 作为 # diff 基准。这样被取消/跳过的 run 的改动都会被包含在当前 diff 中。 if [ -n "$before" ] && [ "$before" != "$ZERO" ] && [ -n "${GITHUB_TOKEN:-}" ]; then BRANCH="${GITHUB_REF_NAME:-}" if [ -n "$BRANCH" ]; then SAFE_BASE=$(python3 -c " import json, subprocess, sys base = '${base}' repo = '${repo}' token = '${GITHUB_TOKEN}' branch = '${BRANCH}' cur_sha = '${after}' def check_run(run_id): \"\"\"Check if this run actually built the web image.\"\"\" try: r = subprocess.run( ['curl', '-sf', '--max-time', '10', '-H', f'Authorization: token {token}', f'{base}/repos/{repo}/actions/runs/{run_id}/jobs'], capture_output=True, text=True, timeout=15) if r.returncode != 0: return False jobs = json.loads(r.stdout).get('jobs', []) return any( 'Build Staging Web' in j.get('name', '') and j.get('conclusion') == 'success' for j in jobs ) except Exception: return False try: r = subprocess.run( ['curl', '-sf', '--max-time', '15', '-H', f'Authorization: token {token}', f'{base}/repos/{repo}/actions/runs?status=success&event=push&branch={branch}&per_page=30'], capture_output=True, text=True, timeout=20) if r.returncode != 0: sys.exit(0) d = json.loads(r.stdout) runs = d.get('workflow_runs', []) if isinstance(d, dict) else d for run in runs: sha = run.get('head_sha', '') if sha and sha != cur_sha: if check_run(run['id']): print(sha) break except Exception: pass " 2>/dev/null || true) if [ -n "$SAFE_BASE" ] && [ "$SAFE_BASE" != "$before" ]; then echo "🔒 安全回溯: 使用最近实际构建 web 的 commit ${SAFE_BASE:0:8} 替代 before=${before:0:8}" before="$SAFE_BASE" elif [ -z "$SAFE_BASE" ]; then echo "⚠️ 未找到历史成功构建 web 的 push run,保守走全量构建" echo "skip_backend=false" >> "$OUTPUT" echo "skip_frontend=false" >> "$OUTPUT" exit 0 fi fi fi FILES="" if [ -n "$before" ] && [ "$before" != "$ZERO" ]; then # Gitea 1.26.x compare API 的顶层 files 字段不填充(始终为空), # 但响应里每个 commit 条目自带的 files 完整可用;聚合区间内所有提交的 files 即可。 API_URL="${base}/repos/${repo}/compare/${before}...${after}?per_page=300" for attempt in 1 2 3; do FILES=$(curl -s --max-time 30 -H "Authorization: token ${GITHUB_TOKEN}" "$API_URL" \ | python3 -c " import json,sys try: d=json.load(sys.stdin) files=set() for c in d.get('commits', []) or []: for f in (c.get('files') or []): n = f.get('filename') or '' if n: files.add(n) print('\n'.join(sorted(files))) except Exception: pass ") [ -n "$FILES" ] && break echo "compare API 无返回,重试 $attempt/3..." sleep 3 done fi if [ -z "$FILES" ]; then echo "⚠️ 无法获取改动文件列表(新分支/API异常),保守起见走全量构建" echo "skip_backend=false" >> "$OUTPUT" echo "skip_frontend=false" >> "$OUTPUT" exit 0 fi TOTAL=$(printf '%s\n' "$FILES" | grep -c . || true) FRONTEND_COUNT=$(printf '%s\n' "$FILES" | grep -c '^apps/web/' || true) BACKEND_COUNT=$(python3 -c "print($TOTAL - $FRONTEND_COUNT)") echo "变更文件: ${TOTAL} 个 (前端: ${FRONTEND_COUNT}, 后端/公共: ${BACKEND_COUNT})" if [ "$BACKEND_COUNT" = "0" ] && [ "$FRONTEND_COUNT" -gt 0 ]; then echo "skip_backend=true" >> "$OUTPUT" echo "skip_frontend=false" >> "$OUTPUT" echo "✅ 纯前端改动,跳过后端镜像构建" elif [ "$FRONTEND_COUNT" = "0" ] && [ "$BACKEND_COUNT" -gt 0 ]; then echo "skip_backend=false" >> "$OUTPUT" echo "skip_frontend=true" >> "$OUTPUT" echo "🔧 纯后端改动,跳过 Web 镜像构建" else echo "skip_backend=false" >> "$OUTPUT" echo "skip_frontend=false" >> "$OUTPUT" echo "🔧 包含全栈/公共变更,三个镜像全部构建" fi