Add metadata regression test suite with Vitest #13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI/CD Pipeline | ||
| on: | ||
| push: | ||
| branches: ["main", "develop"] | ||
| pull_request: | ||
| branches: ["main", "develop"] | ||
| env: | ||
| REGISTRY: docker.io | ||
| IMAGE_NAME: spectracleanse-api | ||
| jobs: | ||
| # ───────────────────────────────────────────────────────────────────────── | ||
| # LINTING & SECURITY | ||
| # ───────────────────────────────────────────────────────────────────────── | ||
| security: | ||
| name: Security & Audit | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version-file: ".nvmrc" | ||
| cache: "npm" | ||
| cache-dependency-path: package-lock.json | ||
| - name: Install dependencies | ||
| run: npm ci | ||
| - name: Run npm audit (info level allowed) | ||
| run: npm audit --audit-level=moderate || true | ||
| - name: Scan for secrets with TruffleHog | ||
| uses: trufflesecurity/trufflehog@main | ||
| with: | ||
| path: ./ | ||
| base: ${{ github.event.repository.default_branch }} | ||
| head: HEAD | ||
| extra_args: --debug | ||
| # ───────────────────────────────────────────────────────────────────────── | ||
| # BACKEND: TYPE-CHECK, LINT, SMOKE TEST | ||
| # ───────────────────────────────────────────────────────────────────────── | ||
| backend: | ||
| name: Backend – Type-check & Smoke Test | ||
| runs-on: ubuntu-latest | ||
| needs: security | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version-file: ".nvmrc" | ||
| cache: "npm" | ||
| cache-dependency-path: package-lock.json | ||
| - name: Install dependencies | ||
| run: npm ci | ||
| - name: Type-check TypeScript | ||
| run: npx tsc --noEmit | ||
| - name: Smoke test – server startup | ||
| timeout-minutes: 2 | ||
| run: | | ||
| export JWT_SECRET=ci-test-secret-not-real | ||
| export STRIPE_SECRET_KEY=sk_test_ci_placeholder | ||
| export STRIPE_WEBHOOK_SECRET=whsec_ci_placeholder | ||
| export STRIPE_CREATOR_PRICE_ID=price_ci_creator | ||
| export STRIPE_STUDIO_PRICE_ID=price_ci_studio | ||
| export GEMINI_API_KEY=ci_gemini_placeholder | ||
| export FRONTEND_URL=http://localhost:5173 | ||
| export DB_PATH=/tmp/spectra-ci.db | ||
| export PORT=3001 | ||
| node server.js > /tmp/server.log 2>&1 & | ||
| SERVER_PID=$! | ||
| for i in $(seq 1 15); do | ||
| sleep 1 | ||
| if curl -sf http://localhost:3001/api/health; then | ||
| echo "Server health check passed" | ||
| kill $SERVER_PID 2>/dev/null || true | ||
| exit 0 | ||
| fi | ||
| done | ||
| echo "=== Server failed to start. Logs: ===" >&2 | ||
| cat /tmp/server.log >&2 | ||
| kill $SERVER_PID 2>/dev/null || true | ||
| exit 1 | ||
| # ───────────────────────────────────────────────────────────────────────── | ||
| # FRONTEND: TYPE-CHECK & BUILD | ||
| # ───────────────────────────────────────────────────────────────────────── | ||
| frontend: | ||
| name: Frontend – Type-check & Build | ||
| runs-on: ubuntu-latest | ||
| needs: security | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version-file: ".nvmrc" | ||
| cache: "npm" | ||
| cache-dependency-path: package-lock.json | ||
| - name: Install dependencies | ||
| run: npm ci | ||
| - name: Type-check | ||
| run: npx tsc --noEmit | ||
| - name: Run unit tests | ||
| run: npm run test:run | ||
| - name: Build frontend | ||
| env: | ||
| VITE_API_URL: https://spectracleanse.com | ||
| run: npm run build | ||
| - name: Upload frontend artifact | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: frontend-dist | ||
| path: dist/ | ||
| retention-days: 3 | ||
| # ───────────────────────────────────────────────────────────────────────── | ||
| # DOCKER: BUILD & PUSH | ||
| # ───────────────────────────────────────────────────────────────────────── | ||
| docker-build: | ||
| name: Docker – Build & Push | ||
| runs-on: ubuntu-latest | ||
| needs: [backend, frontend] | ||
| permissions: | ||
| contents: read | ||
| packages: write | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@v3 | ||
| with: | ||
| driver-options: image=moby/buildkit:latest | ||
| - name: Extract image metadata | ||
| id: meta | ||
| uses: docker/metadata-action@v5 | ||
| with: | ||
| images: | | ||
| ${{ env.REGISTRY }}/${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }} | ||
| tags: | | ||
| type=ref,event=branch | ||
| type=sha,prefix={{branch}}- | ||
| type=semver,pattern={{version}} | ||
| type=semver,pattern={{major}}.{{minor}} | ||
| type=raw,value=latest,enable={{is_default_branch}} | ||
| - name: Log in to Docker Hub (if pushing) | ||
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | ||
| uses: docker/login-action@v3 | ||
| with: | ||
| registry: ${{ env.REGISTRY }} | ||
| username: ${{ secrets.DOCKERHUB_USERNAME }} | ||
| password: ${{ secrets.DOCKERHUB_TOKEN }} | ||
| - name: Build and push Docker image | ||
| uses: docker/build-push-action@v5 | ||
| with: | ||
| context: . | ||
| file: ./Dockerfile | ||
| push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} | ||
| tags: ${{ steps.meta.outputs.tags }} | ||
| labels: ${{ steps.meta.outputs.labels }} | ||
| cache-from: type=gha | ||
| cache-to: type=gha,mode=max | ||
| build-args: | | ||
| BUILDKIT_INLINE_CACHE=1 | ||
| # ───────────────────────────────────────────────────────────────────────── | ||
| # DEPLOY: TRIGGER HYPERLIFT (only on main branch) | ||
| # ───────────────────────────────────────────────────────────────────────── | ||
| deploy: | ||
| name: Deploy to Hyperlift | ||
| runs-on: ubuntu-latest | ||
| needs: docker-build | ||
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
| - name: Trigger Hyperlift webhook | ||
| if: ${{ secrets.HYPERLIFT_DEPLOY_HOOK != '' }} | ||
| run: | | ||
| curl -sf -X POST "${{ secrets.HYPERLIFT_DEPLOY_HOOK }}" \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{ | ||
| "ref":"${{ github.sha }}", | ||
| "branch":"${{ github.ref_name }}", | ||
| "timestamp":"${{ github.event.head_commit.timestamp }}" | ||
| }' | ||
| - name: Deployment notification | ||
| run: | | ||
| echo "Deployment triggered for commit ${{ github.sha }}" | ||
| echo "Branch: ${{ github.ref_name }}" | ||
| echo "Author: ${{ github.actor }}" | ||