From 38c9c545cf24371dd80c89508b73c80b07aeffcb Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Wed, 22 Jul 2026 00:14:10 +0800 Subject: [PATCH 1/2] =?UTF-8?q?ci:=20PR=E6=9E=84=E5=BB=BA=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=20+=20Agent=E6=8F=90=E4=BA=A4=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E5=8C=96=E8=84=9A=E6=9C=AC=20+=20buildx=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增build-pr job:PR阶段只构建不推送,验证Dockerfile/构建脚本 - auto-approve/auto-merge的CONTEXTS加入3个PR Build检查 - build-staging构建后自动清理buildx builder,防磁盘泄漏 - 新增scripts/ci/docker_build_only.sh:PR构建专用(只读缓存不写) - 新增scripts/agent-commit.sh:Agent提交前自动black+isort+ruff检查 --- .gitea/workflows/ci-pipeline.yml | 128 +++++++++++++++++++++++++++++ .gitea/workflows/pr-automation.yml | 6 ++ scripts/agent-commit.sh | 99 ++++++++++++++++++++++ scripts/ci/docker_build_only.sh | 84 +++++++++++++++++++ 4 files changed, 317 insertions(+) create mode 100755 scripts/agent-commit.sh create mode 100755 scripts/ci/docker_build_only.sh diff --git a/.gitea/workflows/ci-pipeline.yml b/.gitea/workflows/ci-pipeline.yml index 76c8a79e2..64c9551ea 100755 --- a/.gitea/workflows/ci-pipeline.yml +++ b/.gitea/workflows/ci-pipeline.yml @@ -416,6 +416,125 @@ jobs: [ -f /tmp/ci_job_start_time ] && START_TIME=$(cat /tmp/ci_job_start_time) python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true + + build-pr: + name: PR Build ${{ matrix.service_display }} Image + runs-on: runtime-builder + timeout-minutes: ${{ matrix.timeout }} + if: github.event_name == 'pull_request' + strategy: + fail-fast: false + matrix: + include: + - service: api + service_display: API + dockerfile: infra/docker/api.Dockerfile + image_name: xiaoxia-saas-api + cache_name: api-cache + timeout: 30 + - service: worker + service_display: Worker + dockerfile: infra/docker/worker.Dockerfile + image_name: xiaoxia-saas-worker + cache_name: worker-cache + timeout: 40 + - service: web + service_display: Web + dockerfile: infra/docker/web.Dockerfile + image_name: xiaoxia-saas-web + cache_name: web-cache + timeout: 30 + steps: + - name: Checkout code + shell: sh + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + curl -sH "Authorization: token $GITHUB_TOKEN" "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/raw/scripts/ci/step_checkout.sh?ref=${GITHUB_SHA}" | bash + - name: Record job start time + shell: sh + run: bash scripts/ci/step_timer_start.sh + - name: Docker login to Registry (for cache read) + shell: sh + env: + ACR_USERNAME: ${{ secrets.ACR_USERNAME }} + ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }} + GITEA_REGISTRY_USER: xiaoxia + GITEA_REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} + run: | + set -eu + for i in 1 2 3; do + echo "Docker login attempt $i/3" + if printf '%s' "${ACR_PASSWORD}" | docker login xiaoxia-registry.cn-hangzhou.cr.aliyuncs.com -u "${ACR_USERNAME}" --password-stdin && docker login git.xiaoxiajianji.com -u "${GITEA_REGISTRY_USER}" -p "${GITEA_REGISTRY_TOKEN}"; then + echo "Docker login successful" + break + fi + echo "Docker login failed ($i/3), retrying in 5s..." + sleep 5 + done + - name: Build PR image (verify only, no push) + shell: sh + run: | + set -eu + REGISTRY="xiaoxia-registry.cn-hangzhou.cr.aliyuncs.com/xiaoxiakeji" + IMAGE_TAG="${REGISTRY}/${{ matrix.image_name }}:pr-${GITHUB_SHA}" + CACHE_REF="${REGISTRY}/${{ matrix.cache_name }}:develop" + + EXTRA_BUILD_ARGS="APP_VERSION=\"${GITHUB_SHA}\"" + if [ "${{ matrix.service }}" = "web" ]; then + EXTRA_BUILD_ARGS="$EXTRA_BUILD_ARGS NGINX_CONF=infra/docker/nginx-staging.conf" + fi + + NO_CACHE_FLAG="" + for i in 1 2 3; do + echo "PR Build attempt $i/3" + if bash scripts/ci/docker_build_only.sh $NO_CACHE_FLAG ${{ matrix.dockerfile }} "${IMAGE_TAG}" "${CACHE_REF}" $EXTRA_BUILD_ARGS; then + echo "PR Build successful" + break + fi + echo "PR Build failed (attempt $i/3)" + [ $i -eq 3 ] && exit 1 + sleep 10 + if [ $i -eq 2 ]; then + NO_CACHE_FLAG="--no-cache" + echo "Next retry with --no-cache" + fi + done + echo + echo "${{ matrix.service_display }} PR build verified: ${IMAGE_TAG}" + - name: Cleanup buildx builder + if: always() + shell: sh + run: | + BUILDER_NAME="ci-pr-builder-${GITHUB_RUN_ID:-local}" + docker buildx rm "$BUILDER_NAME" 2>/dev/null || true + docker buildx prune -f 2>/dev/null || true + echo "Builder cleanup done" + - name: Job duration summary + if: always() + shell: sh + run: bash scripts/ci/step_timer_end.sh + - name: Notify on failure + continue-on-error: true + if: failure() + shell: sh + env: + CI_NOTIFY_WEBHOOK: ${{ secrets.CI_NOTIFY_WEBHOOK }} + run: | + set +e + NOTIFY_MODE=failure JOB_NAME="PR Build ${{ matrix.service_display }} Image" python3 scripts/ci_notify.py + - name: Report CI trace + if: always() + shell: sh + env: + AGENTLOOP_LICENSE_KEY: ${{ secrets.AGENTLOOP_LICENSE_KEY }} + run: | + STATUS="ok" + [ ${{ job.status }} = "success" ] || STATUS="error" + START_TIME="" + [ -f /tmp/ci_job_start_time ] && START_TIME=$(cat /tmp/ci_job_start_time) + python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true + build-staging: name: Build Staging ${{ matrix.service_display }} Image runs-on: runtime-builder @@ -530,6 +649,15 @@ jobs: echo echo "${{ matrix.service_display }} image pushed: ${IMAGE_TAG}" + - name: Cleanup buildx builder + if: always() + shell: sh + run: | + docker buildx rm ci-builder-${GITHUB_RUN_ID}-${GITHUB_JOB}-${{ matrix.cache_name }} 2>/dev/null || true + docker buildx rm ci-builder 2>/dev/null || true + docker buildx prune -f 2>/dev/null || true + echo "Builder cleanup done" + - name: Job duration summary if: always() shell: sh diff --git a/.gitea/workflows/pr-automation.yml b/.gitea/workflows/pr-automation.yml index 917f2e46f..3ad1df1b4 100755 --- a/.gitea/workflows/pr-automation.yml +++ b/.gitea/workflows/pr-automation.yml @@ -56,6 +56,9 @@ jobs: CONTEXTS=( "CI/CD Pipeline / Validate Code Quality And Tests (pull_request)" "CI/CD Pipeline / Frontend Lint (pull_request)" + "CI/CD Pipeline / PR Build API Image (pull_request)" + "CI/CD Pipeline / PR Build Worker Image (pull_request)" + "CI/CD Pipeline / PR Build Web Image (pull_request)" ) fi @@ -235,6 +238,9 @@ jobs: CONTEXTS=( "CI/CD Pipeline / Validate Code Quality And Tests (pull_request)" "CI/CD Pipeline / Frontend Lint (pull_request)" + "CI/CD Pipeline / PR Build API Image (pull_request)" + "CI/CD Pipeline / PR Build Worker Image (pull_request)" + "CI/CD Pipeline / PR Build Web Image (pull_request)" ) echo "检查required门禁(与分支保护一致)" fi diff --git a/scripts/agent-commit.sh b/scripts/agent-commit.sh new file mode 100755 index 000000000..a628ce637 --- /dev/null +++ b/scripts/agent-commit.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# Agent代码提交前自动格式化+质量检查脚本 +# 用法: scripts/agent-commit.sh [files...] +# 效果: 自动跑black+isort+ruff check,通过后才commit+push +set -e + +if [ $# -lt 1 ]; then + echo "用法: $0 [file1 file2 ...]" + echo "示例: $0 \"feat: add new api\" apps/api/src/" + exit 1 +fi + +COMMIT_MSG="$1" +shift + +TARGETS="${@:-.}" + +cd "$(dirname "$0")/.." +REPO_ROOT=$(pwd) +echo "仓库根目录: $REPO_ROOT" +echo "提交信息: $COMMIT_MSG" +echo "目标路径: $TARGETS" +echo "" + +# 后端代码格式化(Python文件) +PYTHON_FILES=$(find $TARGETS -name "*.py" -type f 2>/dev/null | head -100 || true) +if [ -n "$PYTHON_FILES" ]; then + echo "=== Step 1/4: 后端代码格式化 (black) ===" + if command -v black &> /dev/null; then + black $TARGETS 2>&1 | tail -3 + echo "✅ black 完成" + else + echo "⚠️ 未安装black,跳过" + fi + echo "" + + echo "=== Step 2/4: import排序 (isort) ===" + if command -v isort &> /dev/null; then + isort $TARGETS 2>&1 | tail -3 + echo "✅ isort 完成" + else + echo "⚠️ 未安装isort,跳过" + fi + echo "" + + echo "=== Step 3/4: 代码质量检查 (ruff check) ===" + if command -v ruff &> /dev/null; then + RUFF_OUTPUT=$(ruff check $TARGETS 2>&1) || true + RUFF_ERRORS=$(echo "$RUFF_OUTPUT" | grep -c "^" || echo 0) + if [ "$RUFF_ERRORS" -le 2 ] || echo "$RUFF_OUTPUT" | grep -q "All checks passed"; then + echo "✅ ruff 检查通过(错误数: $RUFF_ERRORS)" + else + echo "❌ ruff 发现以下问题:" + echo "$RUFF_OUTPUT" | head -30 + echo "" + echo "请修复后重新提交,或手动忽略特定问题" + exit 1 + fi + else + echo "⚠️ 未安装ruff,跳过" + fi + echo "" +else + echo "ℹ️ 未检测到Python文件,跳过后端格式化" + echo "" +fi + +# 前端代码格式化(TS/TSX文件) +TS_FILES=$(find $TARGETS -name "*.ts" -o -name "*.tsx" -type f 2>/dev/null | head -100 || true) +if [ -n "$TS_FILES" ] && [ -f "apps/web/package.json" ]; then + echo "=== Step 4/4: 前端代码格式化 (prettier) ===" + if command -v npx &> /dev/null; then + cd apps/web && npx prettier --write "src/**/*.{ts,tsx}" 2>&1 | tail -3 || true + cd "$REPO_ROOT" + echo "✅ prettier 完成" + else + echo "⚠️ 未安装npx,跳过前端格式化" + fi + echo "" +fi + +# Git操作 +echo "=== 提交代码 ===" +git add -A +git diff --cached --stat +echo "" +git commit -m "$COMMIT_MSG" +echo "" +echo "✅ 本地提交完成" + +# 可选:自动推送 +if [ "$AGENT_AUTO_PUSH" = "true" ]; then + echo "正在推送到远程..." + git push + echo "✅ 推送完成" +else + echo "ℹ️ 本地已提交,如需推送执行: git push" + echo " 设置 AGENT_AUTO_PUSH=true 可自动推送" +fi diff --git a/scripts/ci/docker_build_only.sh b/scripts/ci/docker_build_only.sh new file mode 100755 index 000000000..ad5c78e4b --- /dev/null +++ b/scripts/ci/docker_build_only.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# PR构建专用:只构建不推送,只读缓存不写,用于PR阶段验证Dockerfile +set -eu + +NO_CACHE_FLAG="" +if [ "$1" = "--no-cache" ]; then + NO_CACHE_FLAG="--no-cache" + shift +fi + +DOCKERFILE="$1" +IMAGE_TAG="$2" +CACHE_REF="$3" +shift 3 +BUILD_ARGS="" +for arg in "$@"; do + BUILD_ARGS="$BUILD_ARGS --build-arg $arg" +done + +BUILDER_NAME="ci-pr-builder-${GITHUB_RUN_ID:-local}" +if ! docker buildx inspect "$BUILDER_NAME" > /dev/null 2>&1; then + docker buildx create --use --name "$BUILDER_NAME" --driver docker-container +else + docker buildx use "$BUILDER_NAME" +fi +docker buildx inspect --bootstrap + +CACHE_NAME=$(echo "$CACHE_REF" | tr "/" "_" | tr ":" "-") +LOCAL_CACHE_DIR="/tmp/buildx-cache/${CACHE_NAME}" +mkdir -p "$LOCAL_CACHE_DIR" + +echo "=== PR Build: build only, no push, read-only cache ===" +echo "Dockerfile: ${DOCKERFILE}" +echo "Image tag: ${IMAGE_TAG}" +echo "" + +build_with_retry() { + local attempt=1 + local max_attempts=2 + while [ $attempt -le $max_attempts ]; do + local build_output + local exit_code + set +e + build_output=$(docker buildx build \ + $NO_CACHE_FLAG \ + $BUILD_ARGS \ + --cache-from "type=local,src=${LOCAL_CACHE_DIR}" \ + --cache-from "type=registry,ref=${CACHE_REF}" \ + -f "${DOCKERFILE}" \ + -t "${IMAGE_TAG}" \ + --load \ + . 2>&1) + exit_code=$? + set -e + if [ $exit_code -eq 0 ]; then + echo "$build_output" + return 0 + fi + if echo "$build_output" | grep -qE "parent snapshot.*not found|snapshot.*does not exist|cache.*corrupt|failed to compute cache key"; then + echo "$build_output" + echo "Local cache corrupted, cleaning and retrying ($attempt/$max_attempts)..." + rm -rf "${LOCAL_CACHE_DIR}" + mkdir -p "${LOCAL_CACHE_DIR}" + docker buildx prune -f -a >/dev/null 2>&1 || true + attempt=$((attempt + 1)) + else + echo "$build_output" + return $exit_code + fi + done + echo "Local cache failed, building with registry cache only..." + docker buildx build \ + $NO_CACHE_FLAG \ + $BUILD_ARGS \ + --cache-from "type=registry,ref=${CACHE_REF}" \ + -f "${DOCKERFILE}" \ + -t "${IMAGE_TAG}" \ + --load \ + . +} + +build_with_retry +echo "" +echo "PR build OK (not pushed): ${IMAGE_TAG}" -- 2.54.0 From 71740879c86cce413afc501ddd42cee928cd27a6 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Wed, 22 Jul 2026 00:19:45 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20auto-approve=E7=94=A8=E5=BF=AB?= =?UTF-8?q?=E9=80=9F=E6=A3=80=E6=9F=A5=EF=BC=8Cauto-merge=E6=89=8D?= =?UTF-8?q?=E7=AD=89=E5=85=A8=E9=83=A8=E6=9E=84=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - auto-approve只检查Validate+Frontend Lint,快速给审批(~3min) - auto-merge检查全部5个门禁,含3个PR Build,确保全绿才合 - 避免Worker Build 20min+导致auto-approve超时无人审批 --- .gitea/workflows/pr-automation.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitea/workflows/pr-automation.yml b/.gitea/workflows/pr-automation.yml index 3ad1df1b4..4a06fcd2a 100755 --- a/.gitea/workflows/pr-automation.yml +++ b/.gitea/workflows/pr-automation.yml @@ -56,9 +56,6 @@ jobs: CONTEXTS=( "CI/CD Pipeline / Validate Code Quality And Tests (pull_request)" "CI/CD Pipeline / Frontend Lint (pull_request)" - "CI/CD Pipeline / PR Build API Image (pull_request)" - "CI/CD Pipeline / PR Build Worker Image (pull_request)" - "CI/CD Pipeline / PR Build Web Image (pull_request)" ) fi -- 2.54.0