name: CI/CD Pipeline on: push: branches: [main, develop] pull_request: branches: [main] env: NODE_VERSION: '22' jobs: # ═══════════════════════════════════════════════════════════════ # 1. Lint # ═══════════════════════════════════════════════════════════════ lint: name: Lint runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - run: npm ci - run: npx prisma generate env: DATABASE_URL: mysql://placeholder:placeholder@localhost:3306/placeholder - run: npm run lint # ═══════════════════════════════════════════════════════════════ # 2. Test # ═══════════════════════════════════════════════════════════════ test: name: Test runs-on: ubuntu-latest timeout-minutes: 10 needs: lint steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - run: npm ci - run: npx prisma generate env: DATABASE_URL: mysql://placeholder:placeholder@localhost:3306/placeholder - run: npm test -- --passWithNoTests - name: Upload test results if: always() uses: actions/upload-artifact@v4 with: name: test-results path: coverage/ retention-days: 7 # ═══════════════════════════════════════════════════════════════ # 3. Build # ═══════════════════════════════════════════════════════════════ build: name: Build runs-on: ubuntu-latest timeout-minutes: 10 needs: test steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - run: npm ci - run: npx prisma generate env: DATABASE_URL: mysql://placeholder:placeholder@localhost:3306/placeholder - run: npm run build - name: Verify dist output run: test -f dist/main.js && echo "Build OK" # ═══════════════════════════════════════════════════════════════ # 4. Docker Build (integration test) # ═══════════════════════════════════════════════════════════════ docker-build: name: Docker Build runs-on: ubuntu-latest timeout-minutes: 15 needs: build steps: - uses: actions/checkout@v4 - name: Build API image run: docker build -t zhixi-api:${{ github.sha }} -f Dockerfile . - name: Build Worker image run: docker build -t zhixi-worker:${{ github.sha }} -f Dockerfile.worker . # ═══════════════════════════════════════════════════════════════ # 5. Deploy (main branch only) # ═══════════════════════════════════════════════════════════════ deploy: name: Deploy runs-on: ubuntu-latest timeout-minutes: 15 needs: docker-build if: github.ref == 'refs/heads/main' && github.event_name == 'push' environment: production steps: - uses: actions/checkout@v4 - name: Docker login uses: docker/login-action@v3 with: registry: ${{ vars.DOCKER_REGISTRY }} username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push API image uses: docker/build-push-action@v6 with: context: . file: Dockerfile push: true tags: | ${{ vars.DOCKER_REGISTRY }}/zhixi-api:latest ${{ vars.DOCKER_REGISTRY }}/zhixi-api:${{ github.sha }} - name: Build and push Worker image uses: docker/build-push-action@v6 with: context: . file: Dockerfile.worker push: true tags: | ${{ vars.DOCKER_REGISTRY }}/zhixi-worker:latest ${{ vars.DOCKER_REGISTRY }}/zhixi-worker:${{ github.sha }} - name: Deploy via SSH uses: appleboy/ssh-action@v1 with: host: ${{ secrets.DEPLOY_HOST }} username: ${{ secrets.DEPLOY_USER }} key: ${{ secrets.DEPLOY_SSH_KEY }} script: | cd /opt/zhixi/api-server docker compose pull docker compose up -d --remove-orphans docker compose exec -T api npx prisma migrate deploy