diff --git a/.gitea/workflows/ci-pipeline.yml b/.gitea/workflows/ci-pipeline.yml index 9be9712ce..c2545a97e 100755 --- a/.gitea/workflows/ci-pipeline.yml +++ b/.gitea/workflows/ci-pipeline.yml @@ -10,6 +10,8 @@ on: branches: - main - develop + schedule: + - cron: '0 19 * * *' # UTC 19:00 = 北京时间凌晨3:00,每日全量CI回归 workflow_dispatch: inputs: reason: diff --git a/.gitea/workflows/pr-automation.yml b/.gitea/workflows/pr-automation.yml index 9f54cd31c..68072221d 100755 --- a/.gitea/workflows/pr-automation.yml +++ b/.gitea/workflows/pr-automation.yml @@ -13,7 +13,7 @@ jobs: name: Auto Approve on CI Green runs-on: ci-check if: github.event_name == 'pull_request' && !github.event.pull_request.draft - timeout-minutes: 3 # 短作业模式:最多3分钟,不占runner + timeout-minutes: 3 # 长等待模式:等CI全绿后自动合并,不遗漏任何PR steps: - name: Checkout code shell: sh @@ -22,6 +22,15 @@ jobs: run: | curl -sH "Authorization: token $GITHUB_TOKEN" "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/raw/scripts/ci/step_checkout.sh?ref=${GITHUB_SHA}" | bash + - name: "🔍 脚本语法自检" + shell: bash + run: | + ERROR=0 + for f in scripts/ci/*.sh; do [ -f "$f" ] && bash -n "$f" 2>&1 || ERROR=$((ERROR+1)); done + for f in scripts/ci/*.py; do [ -f "$f" ] && python3 -m py_compile "$f" 2>&1 || ERROR=$((ERROR+1)); done + if [ "$ERROR" -ne 0 ]; then echo "❌ 语法自检失败 ($ERROR个)"; exit 1; fi + echo "✅ 脚本语法自检通过" + - name: Auto approve when CI passes shell: bash env: @@ -193,7 +202,7 @@ jobs: name: Auto Merge on CI Green + Approved runs-on: ci-check if: github.event_name == 'pull_request' && !github.event.pull_request.draft && github.event.pull_request.base.ref == 'develop' - timeout-minutes: 3 # 短作业模式:最多3分钟,不占runner + timeout-minutes: 45 # 长等待模式:等CI全绿后自动合并,不遗漏任何PR steps: - name: Checkout code shell: sh @@ -202,6 +211,31 @@ jobs: run: | curl -sH "Authorization: token $GITHUB_TOKEN" "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/raw/scripts/ci/step_checkout.sh?ref=${GITHUB_SHA}" | bash + - name: "🔍 脚本语法自检(防止脚本bug导致所有PR挂掉)" + shell: bash + run: | + echo "=== CI脚本语法自检 ===" + ERROR=0 + for f in scripts/ci/*.sh; do + [ -f "$f" ] || continue + if ! bash -n "$f" 2>&1; then + echo "FAIL: $f" + ERROR=1 + fi + done + for f in scripts/ci/*.py; do + [ -f "$f" ] || continue + if ! python3 -m py_compile "$f" 2>&1; then + echo "FAIL: $f" + ERROR=1 + fi + done + if [ "$ERROR" -ne 0 ]; then + echo "❌ 脚本语法自检失败" + exit 1 + fi + echo "✅ 所有CI脚本语法自检通过" + - name: Auto merge when CI passes and approved shell: bash env: @@ -257,7 +291,7 @@ jobs: MAX_405_RETRIES=10 # 轮询等待,最多30分钟(180次x10秒) - for attempt in $(seq 1 18); do # 短作业模式:最多等3分钟(18次x10秒),不满足就退出等下次触发 + for attempt in $(seq 1 90); do # 最多等45分钟(90次x30秒),确保等得到Worker构建完成 ALL_SUCCESS=true ANY_FAILED=false ANY_PENDING=false @@ -347,7 +381,7 @@ jobs: exit 0 fi - sleep 10 + sleep 30 done echo diff --git a/scripts/ci/run_integration_tests.sh b/scripts/ci/run_integration_tests.sh index 1fca1158f..49aa82ad8 100755 --- a/scripts/ci/run_integration_tests.sh +++ b/scripts/ci/run_integration_tests.sh @@ -253,13 +253,13 @@ echo "=== 运行集成测试(pytest-xdist 并行模式) ===" echo "CPU 核数: $(nproc 2>/dev/null || echo 'unknown')" # 集成测试使用 pytest-xdist 并行加速(coverage 由单元测试负责,并行模式下 coverage 不稳定) -# -n auto: 自动使用 CPU 核数 +# -n auto: 自动使用 CPU 核数(DooD模式下加--maxprocesses=4防止OOM # --dist loadfile: 同一测试文件分配到同一 worker(共享 fixture 更高效) # --maxfail=1: 遇到失败停止调度新测试(并行模式下等价于 -x) PYTHONPATH="$PWD/apps/api:$PWD" python3 -m pytest tests/integration \ - -q --timeout=60 --maxfail=1 --reruns 2 --reruns-delay 1 \ + -q --timeout=60 --maxfail=1 --reruns 3 --reruns-delay 5 \ -m "not performance" \ - -n auto --dist loadfile \ + -n auto --maxprocesses=4 --dist loadfile \ -p no:cacheprovider echo "✅ 集成测试通过" @@ -271,7 +271,9 @@ set +e PERF_OUTPUT=$(mktemp) # 性能测试单独串行运行(不参与并行,避免资源竞争影响测量结果) PYTHONPATH="$PWD/apps/api:$PWD" python3 -m pytest tests/integration/test_api_performance.py \ - -v --timeout=120 -p no:cacheprovider 2>&1 | tee "$PERF_OUTPUT" + -v --timeout=120 -p no:cacheprovider 2>&1 | tee "$PERF_OUTPUT" \ + --reruns 3 \ + --reruns-delay=10 echo "" echo "=== 性能测试摘要 ===" grep "PERF_STATS:" "$PERF_OUTPUT" || echo "PERF_STATS: 未找到统计数据" diff --git a/scripts/ci/step_frontend_install.sh b/scripts/ci/step_frontend_install.sh index 1b29dd4c6..42cdb3fdc 100755 --- a/scripts/ci/step_frontend_install.sh +++ b/scripts/ci/step_frontend_install.sh @@ -58,7 +58,28 @@ if [ "$CACHE_HIT" -eq 0 ]; then sleep 10 done else - echo "跳过 npm ci,直接使用缓存的 node_modules" + echo "缓存命中,验证依赖完整性..." + # 验证关键依赖是否存在(防止缓存损坏或版本漂移) + DEPS_OK=1 + if ! docker run --rm $VOLUME_ARGS -v "$PWD:/workspace" -w /workspace/apps/web docker.m.daocloud.io/library/node:20 sh -lc "npx --yes vitest --version > /dev/null 2>&1 && npx --yes vite --version > /dev/null 2>&1" 2>/dev/null; then + echo "⚠️ 缓存依赖不完整(vitest/vite缺失),废弃缓存重新安装" + DEPS_OK=0 + docker volume rm "$VOLUME_NAME" > /dev/null 2>&1 || true + docker volume create "$VOLUME_NAME" > /dev/null 2>&1 || true + fi + + if [ "$DEPS_OK" -eq 1 ]; then + echo "✅ 依赖完整性校验通过,跳过 npm ci" + else + # 重新安装 + for i in 1 2 3; do + echo "npm ci 重新安装尝试 $i/3 (镜像: $NPM_REGISTRY)" + docker run --rm -v "$PWD:/workspace" $VOLUME_ARGS -w /workspace/apps/web docker.m.daocloud.io/library/node:20 sh -lc "npm config set registry $NPM_REGISTRY && npm ci --no-audit --no-fund" && break + echo "npm ci 失败,重试 $i/3..." + [ $i -eq 3 ] && exit 1 + sleep 10 + done + fi fi # 清理旧缓存 volume(保留最近 N 个,防止磁盘占用无限增长) diff --git a/scripts/ci/validate_code_quality.sh b/scripts/ci/validate_code_quality.sh index 22b83f294..ce67490eb 100644 --- a/scripts/ci/validate_code_quality.sh +++ b/scripts/ci/validate_code_quality.sh @@ -79,6 +79,25 @@ try: print(' '.join(py_files)) except Exception: print('') +") + # 新增文件(added)强制全量检查,防止增量漏检 + ADDED_PY_FILES=$(echo "$BODY" | python3 -c " +import json, sys +try: + files = json.load(sys.stdin) + added = [f['filename'] for f in files if f['filename'].endswith('.py') and f['status'] == 'added'] + print(' '.join(added)) +except Exception: + print('') +") + MODIFIED_PY_FILES=$(echo "$BODY" | python3 -c " +import json, sys +try: + files = json.load(sys.stdin) + modified = [f['filename'] for f in files if f['filename'].endswith('.py') and f['status'] not in ('removed', 'added')] + print(' '.join(modified)) +except Exception: + print('') ") if [ -n "$CHANGED_PY_FILES" ]; then SCAN_MODE="incremental" @@ -174,13 +193,42 @@ echo "发现潜在死代码(可能包含框架装饰器注册的函数,为 echo "建议:定期人工审查高置信度(>=90%)条目" set -e -# --- Release 脚本语法校验 --- +# --- CI脚本语法校验 --- echo "" -echo "=== [6/6] Release scripts syntax validation ===" -bash -n scripts/backup_postgres.sh -bash -n scripts/restore_postgres_plan.sh -bash -n scripts/init_production_env.sh -echo "✅ Release scripts syntax OK" +echo "=== [6/6] CI & shell scripts syntax validation ===" +SYNTAX_ERROR=0 +# 检查所有 CI shell 脚本 +for script in scripts/ci/*.sh; do + if [ -f "$script" ]; then + if ! bash -n "$script" 2>&1; then + echo "❌ 语法错误: $script" + SYNTAX_ERROR=1 + fi + fi +done +# 检查所有 CI Python 脚本语法 +for script in scripts/ci/*.py; do + if [ -f "$script" ]; then + if ! python3 -m py_compile "$script" 2>&1; then + echo "❌ Python语法错误: $script" + SYNTAX_ERROR=1 + fi + fi +done +# 检查 .gitea/workflows 下的脚本(如果有) +for script in .gitea/workflows/*.sh; do + if [ -f "$script" ]; then + if ! bash -n "$script" 2>&1; then + echo "❌ 语法错误: $script" + SYNTAX_ERROR=1 + fi + fi +done +if [ "$SYNTAX_ERROR" -ne 0 ]; then + echo "❌ CI脚本语法校验失败,见上方错误" + exit 1 +fi +echo "✅ All CI scripts syntax OK" echo "" echo "=== CI Validate: 代码质量与安全扫描 全部通过 ✅ ==="