name: CI # CI/CD 一体化流水线(no-push 本地构建部署) # 设计文档:docs/superpowers/specs/2026-07-08-cicd-no-push-local-build-design.md # # 流程: # PR 触发:仅跑 3 个 quality job(并行) # push main 触发:quality job 全绿后跑 deploy job(本地 build + compose up) # workflow_dispatch:支持指定 commit_sha 回滚 # # 不依赖任何自建镜像,全部使用官方镜像 # 不推送镜像到 registry,构建即部署 on: push: branches: [main] pull_request: branches: [main] workflow_dispatch: inputs: commit_sha: description: '回滚到指定 commit(留空则部署当前 HEAD)' required: false default: '' env: SKIP_ENV_VALIDATION: '1' NEXT_TELEMETRY_DISABLED: '1' jobs: # ===== TypeScript 质量检查 ===== quality-ts: runs-on: ubuntu-latest container: node:22-alpine steps: - uses: actions/checkout@v3 with: ref: ${{ github.event.inputs.commit_sha || github.ref }} - name: Install pnpm run: corepack enable && corepack prepare pnpm@11.13.0 --activate - name: Install dependencies run: pnpm install --frozen-lockfile - name: Lint run: pnpm -r run lint - name: Typecheck run: pnpm -r run typecheck - name: Test run: pnpm -r run test continue-on-error: true # P6: 部分服务无 test 脚本,待补全 - name: Build run: pnpm -r run build # ===== Go 质量检查 ===== quality-go: runs-on: ubuntu-latest container: golang:1.25-alpine defaults: run: working-directory: services/api-gateway steps: - uses: actions/checkout@v4 with: ref: ${{ github.event.inputs.commit_sha || github.ref }} - name: Download deps run: go mod download - name: Vet run: go vet ./... - name: Build run: go build ./... - name: Test run: go test ./... -v -coverprofile=coverage.out # ===== Proto 契约检查 ===== quality-proto: runs-on: ubuntu-latest container: bufbuild/buf:latest defaults: run: working-directory: packages/shared-proto steps: - uses: actions/checkout@v4 with: ref: ${{ github.event.inputs.commit_sha || github.ref }} fetch-depth: 0 # buf breaking 需要对比分支 - name: buf lint run: buf lint - name: buf breaking if: github.event_name == 'pull_request' run: buf breaking --against .git#branch=main,subdir=packages/shared-proto # ===== 部署(仅 push main 或手动触发)===== deploy: if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' needs: [quality-ts, quality-go, quality-proto] runs-on: ubuntu-latest container: docker:25-git options: --volume /var/run/docker.sock:/var/run/docker.sock environment: name: production env: DEPLOY_DIR: /opt/edu steps: - uses: actions/checkout@v4 with: ref: ${{ github.event.inputs.commit_sha || github.ref }} - name: Setup deploy dir run: | # 同步整个仓库到部署目录的 repo/ 子目录 # compose 文件中 build.context 指向 ./repo/services/... 或 ./repo mkdir -p ${{ env.DEPLOY_DIR }}/repo cp -a $GITHUB_WORKSPACE/. ${{ env.DEPLOY_DIR }}/repo/ cp infra/docker-compose.deploy.yml ${{ env.DEPLOY_DIR }}/docker-compose.yml # 确保 .env 存在(首次部署需人工创建) if [ ! -f ${{ env.DEPLOY_DIR }}/.env ]; then echo "::error::部署目录缺少 .env 文件,请参考 infra/deploy.env.example 创建" exit 1 fi - name: Deploy services (local build, no push) run: | cd ${{ env.DEPLOY_DIR }} echo "=== 构建并启动服务 ===" # --build 使用本地 Dockerfile 构建,不拉取 registry # build.context 在 compose 中指向 ./repo/...(相对于 /opt/edu/) docker compose up -d --build --remove-orphans - name: Wait for health run: | echo "等待服务健康..." sleep 10 for i in 1 2 3 4 5 6 7 8 9 10; do FAIL=0 echo "[尝试 $i] 检查 api-gateway..." if ! wget -q --spider http://localhost:8080/healthz 2>/dev/null; then echo " api-gateway 未就绪" FAIL=1 fi echo "[尝试 $i] 检查 classes..." if ! wget -q --spider http://localhost:3001/healthz 2>/dev/null; then echo " classes 未就绪" FAIL=1 fi echo "[尝试 $i] 检查 teacher-portal..." if ! wget -q --spider http://localhost:3000/ 2>/dev/null; then echo " teacher-portal 未就绪" FAIL=1 fi if [ $FAIL -eq 0 ]; then echo "✅ 所有服务健康" exit 0 fi sleep 6 done echo "::error::服务健康检查失败" echo "=== 容器状态 ===" cd ${{ env.DEPLOY_DIR }} && docker compose ps echo "=== api-gateway 日志 ===" docker compose logs --tail=50 api-gateway echo "=== classes 日志 ===" docker compose logs --tail=50 classes echo "=== teacher-portal 日志 ===" docker compose logs --tail=50 teacher-portal exit 1 - name: Summary if: always() run: | echo "### 部署结果" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "- 触发事件:\`${{ github.event_name }}\`" >> $GITHUB_STEP_SUMMARY echo "- 部署 commit:\`${{ github.event.inputs.commit_sha || github.sha }}\`" >> $GITHUB_STEP_SUMMARY echo "- 部署目录:\`${{ env.DEPLOY_DIR }}\`" >> $GITHUB_STEP_SUMMARY echo "- 部署方式:本地构建(no-push)" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY cd ${{ env.DEPLOY_DIR }} && docker compose ps >> $GITHUB_STEP_SUMMARY 2>&1 || true