diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index 1ee5f0c20..1788bdde0 100755 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -269,91 +269,14 @@ jobs: run: | set +e echo "=== 覆盖率汇总 ===" - if [ -f coverage.xml ]; then - python3 -c " -import xml.etree.ElementTree as ET -tree = ET.parse('coverage.xml') -root = tree.getroot() -line_rate = float(root.get('line-rate', 0)) * 100 -branch_rate = float(root.get('branch-rate', 0)) * 100 -lines_covered = int(root.get('lines-covered', 0)) -lines_valid = int(root.get('lines-valid', 0)) -print(f'行覆盖率: {line_rate:.2f}% ({lines_covered}/{lines_valid})') -print(f'分支覆盖率: {branch_rate:.2f}%') -print(f'门槛: 65%') -print(f'状态: {"PASS ✅" if line_rate >= 65 else "FAIL ❌"}') -" - else - echo "coverage.xml 不存在,跳过汇总" - fi - + python3 scripts/ci_coverage_summary.py - name: Notify CI failure if: failure() shell: sh run: | set +e echo "=== CI 失败通知 ===" - - # 收集失败信息 - FAILED_JOB="Validate Code Quality And Tests" - BRANCH="${GITHUB_REF_NAME:-unknown}" - COMMIT="${GITHUB_SHA:0:8}" - ACTOR="${GITHUB_ACTOR:-unknown}" - RUN_ID="${GITHUB_RUN_ID:-unknown}" - REPO="${GITHUB_REPOSITORY:-unknown}" - RUN_URL="https://git.xiaoxiajianji.com/${REPO}/actions/runs/${RUN_ID}" - - # 构造通知消息 - PAYLOAD=$(cat < /dev/null 2>&1 && echo "通知已发送" || echo "通知发送失败" - else - echo "未配置 CI_NOTIFY_WEBHOOK,跳过通知" - echo "如需启用,请在仓库 Settings -> Secrets and variables -> Actions 中添加 CI_NOTIFY_WEBHOOK" - fi + FAILED_JOB="Validate Code Quality And Tests" python3 scripts/ci_notify_failure.py - name: Build summary if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main' @@ -364,15 +287,7 @@ print(f'状态: {"PASS ✅" if line_rate >= 65 else "FAIL ❌"}') echo "Branch: ${GITHUB_REF_NAME}" echo "Commit: ${GITHUB_SHA}" # 输出最终覆盖率 - if [ -f coverage.xml ]; then - python3 -c " -import xml.etree.ElementTree as ET -tree = ET.parse('coverage.xml') -root = tree.getroot() -line_rate = float(root.get('line-rate', 0)) * 100 -print(f'Total coverage: {line_rate:.2f}%') -" - fi + python3 scripts/ci_coverage_summary.py frontend-lint: name: Frontend Lint diff --git a/scripts/ci_coverage_summary.py b/scripts/ci_coverage_summary.py new file mode 100755 index 000000000..c66cf543b --- /dev/null +++ b/scripts/ci_coverage_summary.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +"""解析 coverage.xml 并输出覆盖率汇总。""" +import sys +import xml.etree.ElementTree as ET + +THRESHOLD = 65 # 行覆盖率门槛,百分比 + + +def main() -> int: + try: + tree = ET.parse("coverage.xml") + except FileNotFoundError: + print("coverage.xml 不存在,跳过汇总") + return 0 + + root = tree.getroot() + line_rate = float(root.get("line-rate", 0)) * 100 + branch_rate = float(root.get("branch-rate", 0)) * 100 + lines_covered = int(root.get("lines-covered", 0)) + lines_valid = int(root.get("lines-valid", 0)) + + print(f"行覆盖率: {line_rate:.2f}% ({lines_covered}/{lines_valid})") + print(f"分支覆盖率: {branch_rate:.2f}%") + print(f"门槛: {THRESHOLD}%") + status = "PASS ✅" if line_rate >= THRESHOLD else "FAIL ❌" + print(f"状态: {status}") + + return 0 if line_rate >= THRESHOLD else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/ci_notify_failure.py b/scripts/ci_notify_failure.py new file mode 100755 index 000000000..8e4faa74f --- /dev/null +++ b/scripts/ci_notify_failure.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +"""发送 CI 失败通知到飞书/项目群 webhook。""" +import json +import os +import sys +import urllib.request + + +def main() -> int: + webhook = os.environ.get("CI_NOTIFY_WEBHOOK", "") + if not webhook: + print("未配置 CI_NOTIFY_WEBHOOK,跳过通知") + print("如需启用,请在仓库 Settings -> Secrets and variables -> Actions 中添加 CI_NOTIFY_WEBHOOK") + return 0 + + failed_job = os.environ.get("FAILED_JOB", "Unknown Job") + branch = os.environ.get("GITHUB_REF_NAME", "unknown") + commit = os.environ.get("GITHUB_SHA", "unknown")[:8] + actor = os.environ.get("GITHUB_ACTOR", "unknown") + run_id = os.environ.get("GITHUB_RUN_ID", "unknown") + repo = os.environ.get("GITHUB_REPOSITORY", "unknown") + run_url = f"https://git.xiaoxiajianji.com/{repo}/actions/runs/{run_id}" + + payload = { + "msg_type": "interactive", + "card": { + "header": { + "title": { + "tag": "plain_text", + "content": "❌ CI 构建失败", + }, + "status": "red", + }, + "elements": [ + { + "tag": "div", + "text": { + "tag": "lark_md", + "content": ( + f"**任务**: {failed_job}\n" + f"**分支**: {branch}\n" + f"**提交**: {commit}\n" + f"**提交者**: {actor}\n" + f"**Run ID**: {run_id}" + ), + }, + }, + { + "tag": "action", + "actions": [ + { + "tag": "button", + "text": {"tag": "plain_text", "content": "查看失败日志"}, + "url": run_url, + "type": "danger", + } + ], + }, + ], + }, + } + + data = json.dumps(payload).encode("utf-8") + req = urllib.request.Request( + webhook, + data=data, + headers={"Content-Type": "application/json"}, + method="POST", + ) + try: + with urllib.request.urlopen(req, timeout=10) as resp: + resp.read() + print("通知已发送") + except Exception as e: + print(f"通知发送失败: {e}", file=sys.stderr) + return 1 + + return 0 + + +if __name__ == "__main__": + sys.exit(main())