name: CI on: push: branches: [main] pull_request: branches: [main] workflow_dispatch: # 沿用 AIOPS 設計: 新 commit 自動取消舊 workflow concurrency: group: ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: NODE_VERSION: '20' PNPM_VERSION: '9' PYTHON_VERSION: '3.11' jobs: # ==================== Lint & Type Check ==================== lint: name: Lint & Type Check runs-on: self-hosted steps: - uses: actions/checkout@v4 - name: Setup pnpm uses: pnpm/action-setup@v3 with: version: ${{ env.PNPM_VERSION }} - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'pnpm' - name: Install dependencies run: pnpm install --frozen-lockfile - name: Lint run: pnpm lint - name: Type check run: pnpm typecheck - name: ADR Compliance Check run: | echo "🔍 正在檢查是否違反 ADR 規定..." # 檢查 1: 前端禁止直連資料庫 (違反 ADR-005 BFF 原則) if grep -rE "psycopg2|asyncpg|redis|sqlalchemy|pg|ioredis" apps/web/src/ 2>/dev/null; then echo "❌ 嚴重違規 (ADR-005): 前端程式碼中發現直連資料庫的套件!" exit 1 fi # 檢查 2: 狀態管理嚴禁使用 Redux (違反 ADR-004 必須用 Zustand) if grep -rE "@reduxjs/toolkit|react-redux" apps/web/package.json 2>/dev/null; then echo "❌ 違規 (ADR-004): 發現 Redux,請全面改用 Zustand!" exit 1 fi # 檢查 3: 禁止 import 舊專案 (違反 .awoooi-agent-rules.md) if grep -rE "from ['\"].*wooo-aiops" apps/ packages/ 2>/dev/null; then echo "❌ 嚴重違規: 禁止 import 舊專案 wooo-aiops!" exit 1 fi # 檢查 4: 禁止硬編碼機密 if grep -rE "(sk-[a-zA-Z0-9]{20,}|password\s*=\s*['\"][^'\"]+['\"])" apps/ packages/ 2>/dev/null; then echo "❌ 嚴重違規: 發現硬編碼機密!" exit 1 fi echo "✅ ADR 規範檢查通過!" # ==================== Test ==================== test: name: Test runs-on: self-hosted needs: lint steps: - uses: actions/checkout@v4 - name: Setup pnpm uses: pnpm/action-setup@v3 with: version: ${{ env.PNPM_VERSION }} - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'pnpm' - name: Install dependencies run: pnpm install --frozen-lockfile - name: Run tests run: pnpm test continue-on-error: true - name: Upload coverage uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: false # ==================== Build ==================== build: name: Build runs-on: self-hosted needs: lint steps: - uses: actions/checkout@v4 - name: Setup pnpm uses: pnpm/action-setup@v3 with: version: ${{ env.PNPM_VERSION }} - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'pnpm' - name: Install dependencies run: pnpm install --frozen-lockfile - name: Setup Turborepo Cache uses: dtinth/setup-github-actions-caching-for-turbo@v1 - name: Build packages env: # Next.js 需要 NEXT_PUBLIC_* 在 build-time (統帥鐵律) NEXT_PUBLIC_API_URL: https://awoooi.wooo.work run: pnpm turbo build - name: Upload build artifacts uses: actions/upload-artifact@v4 with: name: build-artifacts path: | apps/*/dist packages/*/dist retention-days: 7 # ==================== API (Python) ==================== api-lint: name: API Lint (Python) runs-on: self-hosted steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install uv uses: astral-sh/setup-uv@v3 - name: Install dependencies working-directory: apps/api run: uv sync - name: Lint with ruff working-directory: apps/api run: uv run ruff check . - name: Type check with mypy working-directory: apps/api # 漸進式類型檢查: 只檢查核心 src/,排除 scripts/ 和 tests/ run: uv run mypy src/ --exclude 'tests/|scripts/' || echo "::warning::mypy 有錯誤,但不阻止 CI (漸進式採用中)" continue-on-error: true api-test: name: API Test (Python) runs-on: self-hosted needs: api-lint steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install uv uses: astral-sh/setup-uv@v3 - name: Install dependencies working-directory: apps/api run: uv sync - name: Run tests working-directory: apps/api env: PYTHONPATH: ${{ github.workspace }}/apps/api continue-on-error: true run: | uv run python --version uv run pytest tests/ --cov=src --cov-report=xml -v || echo "::warning::部分測試失敗" # ==================== OpenAPI Validation ==================== openapi-validate: name: Validate OpenAPI Spec runs-on: self-hosted steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - name: Install spectral run: npm install -g @stoplight/spectral-cli - name: Validate OpenAPI run: spectral lint docs/api/api-contract.yaml # ==================== Docker Build (驗證 Dockerfile) ==================== docker-build: name: Docker Build Verify runs-on: self-hosted needs: [test, api-test, build] strategy: matrix: app: [web, api] steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build image (no push) uses: docker/build-push-action@v5 with: context: . file: apps/${{ matrix.app }}/Dockerfile push: false tags: awoooi-${{ matrix.app }}:test build-args: | NEXT_PUBLIC_API_URL=https://awoooi.wooo.work cache-from: type=gha cache-to: type=gha,mode=max