fix(docker): push to Docker Hub with DOCKER_USERNAME/DOCKER_TOKEN sec… #27
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 Pipeline | |
| on: | |
| push: | |
| branches: [main, develop] | |
| pull_request: | |
| branches: [main] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # ────────────────────────────────────────────────── | |
| # Stage 1: Lint + Type Check (fast fail) | |
| # ────────────────────────────────────────────────── | |
| lint: | |
| name: Lint & Type Check | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Run ESLint | |
| run: bun run lint | |
| - name: Run TypeScript check | |
| run: bun run typecheck | |
| docs-parity: | |
| name: Docs Parity | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Check docs parity | |
| run: bun run docs:parity | |
| # ────────────────────────────────────────────────── | |
| # Stage 2: Security Audit | |
| # ────────────────────────────────────────────────── | |
| security: | |
| name: Security Gates | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Dependency audit (high+) | |
| run: bun pm audit --level=high || true | |
| # ────────────────────────────────────────────────── | |
| # Stage 3: Unit & Integration Tests + Coverage | |
| # ────────────────────────────────────────────────── | |
| test: | |
| name: Unit & Integration Tests | |
| runs-on: ubuntu-latest | |
| needs: lint | |
| timeout-minutes: 15 | |
| services: | |
| postgres: | |
| image: postgres:16-alpine | |
| env: | |
| POSTGRES_USER: test | |
| POSTGRES_PASSWORD: test | |
| POSTGRES_DB: test | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Run tests with coverage | |
| run: bun run test:coverage | |
| env: | |
| DATABASE_URL: postgresql://test:test@localhost:5432/test | |
| - name: Coverage threshold check (>50%) | |
| run: | | |
| if [ -f coverage/coverage-summary.json ]; then | |
| LINE_PCT=$(bun -e " | |
| const data = JSON.parse(require('fs').readFileSync('coverage/coverage-summary.json','utf8')); | |
| console.log(data.total?.lines?.pct ?? 0); | |
| ") | |
| echo "Line coverage: ${LINE_PCT}%" | |
| if (( $(echo "$LINE_PCT < 50" | bc -l 2>/dev/null || echo 0) )); then | |
| echo "::error::Coverage ${LINE_PCT}% is below 50% threshold" | |
| exit 1 | |
| fi | |
| else | |
| echo "::warning::Coverage report not found, skipping threshold check" | |
| fi | |
| - name: Upload coverage | |
| if: always() | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| file: ./coverage/lcov.info | |
| fail_ci_if_error: false | |
| - name: Upload coverage artifact | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-report | |
| path: coverage/ | |
| retention-days: 14 | |
| # ────────────────────────────────────────────────── | |
| # Stage 4: E2E Tests (Playwright) | |
| # ────────────────────────────────────────────────── | |
| e2e: | |
| name: E2E Tests (Playwright) | |
| runs-on: ubuntu-latest | |
| needs: test | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Install Playwright browsers | |
| run: bunx playwright install --with-deps chromium | |
| - name: Run E2E tests | |
| run: bunx playwright test | |
| env: | |
| DATABASE_URL: "file:./test.db" | |
| JWT_SECRET: "ci-test-secret-key-for-e2e-tests" | |
| SITE_URL: "http://localhost:4321" | |
| - name: Upload Playwright report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: playwright-report | |
| path: playwright-report/ | |
| retention-days: 14 | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: test-results | |
| path: test-results/ | |
| retention-days: 7 | |
| # ────────────────────────────────────────────────── | |
| # Stage 5: Container Security Scan (Trivy) | |
| # ────────────────────────────────────────────────── | |
| container-scan: | |
| name: Container Security (Trivy) | |
| runs-on: ubuntu-latest | |
| needs: lint | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Build Docker image | |
| run: docker build -t opencodehub:ci -f Dockerfile . | |
| - name: Run Trivy vulnerability scanner | |
| uses: aquasecurity/trivy-action@master | |
| with: | |
| image-ref: "opencodehub:ci" | |
| format: "sarif" | |
| output: "trivy-results.sarif" | |
| severity: "CRITICAL,HIGH" | |
| exit-code: "0" | |
| - name: Upload Trivy scan results | |
| if: always() | |
| uses: github/codeql-action/upload-sarif@v3 | |
| with: | |
| sarif_file: "trivy-results.sarif" | |
| continue-on-error: true | |
| # ────────────────────────────────────────────────── | |
| # Stage 6: SAST (Semgrep) | |
| # ────────────────────────────────────────────────── | |
| sast: | |
| name: SAST (Semgrep) | |
| runs-on: ubuntu-latest | |
| needs: lint | |
| timeout-minutes: 10 | |
| container: | |
| image: semgrep/semgrep | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Run Semgrep | |
| run: semgrep scan --config auto --error --json --output semgrep-results.json src/ || true | |
| env: | |
| SEMGREP_RULES: >- | |
| p/typescript | |
| p/javascript | |
| p/security-audit | |
| - name: Upload Semgrep results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: semgrep-results | |
| path: semgrep-results.json | |
| retention-days: 14 | |
| # ────────────────────────────────────────────────── | |
| # Stage 7: Performance Gate | |
| # ────────────────────────────────────────────────── | |
| performance: | |
| name: Performance Gate | |
| runs-on: ubuntu-latest | |
| needs: [build] | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Build application | |
| run: bun run build | |
| env: | |
| DATABASE_URL: "file:./test.db" | |
| REDIS_URL: redis://localhost:6379 | |
| - name: Start server and run perf check | |
| run: | | |
| bun run dev & | |
| SERVER_PID=$! | |
| sleep 8 | |
| bun run perf:baseline | |
| kill $SERVER_PID 2>/dev/null || true | |
| env: | |
| PERF_BASE_URL: http://127.0.0.1:4321 | |
| PERF_PATHS: /api/health,/api/metrics | |
| PERF_CONCURRENCY: 4 | |
| PERF_REQUESTS: 50 | |
| PERF_MAX_P95_MS: 500 | |
| PERF_OUTPUT: test-results/perf-report.json | |
| - name: Upload perf report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: perf-report | |
| path: test-results/perf-report.json | |
| retention-days: 14 | |
| # ────────────────────────────────────────────────── | |
| # Stage 8: Build Verification | |
| # ────────────────────────────────────────────────── | |
| build: | |
| name: Build | |
| runs-on: ubuntu-latest | |
| needs: [lint, docs-parity, security, test] | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Build application | |
| run: bun run build | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist | |
| path: dist/ | |
| retention-days: 7 | |
| # ────────────────────────────────────────────────── | |
| # Stage 8: Docker Build & Push | |
| # ────────────────────────────────────────────────── | |
| docker: | |
| name: Docker Build | |
| runs-on: ubuntu-latest | |
| needs: [build, e2e, container-scan] | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to Container Registry (Docker Hub) | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: docker.io | |
| username: ${{ secrets.DOCKER_USERNAME }} | |
| password: ${{ secrets.DOCKER_TOKEN }} | |
| - name: Extract metadata | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ${{ secrets.DOCKER_USERNAME }}/opencodehub | |
| tags: | | |
| type=ref,event=branch | |
| type=sha | |
| - name: Build and push | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| # ────────────────────────────────────────────────── | |
| # Quality Gate Summary | |
| # ────────────────────────────────────────────────── | |
| quality-gate: | |
| name: Quality Gate | |
| runs-on: ubuntu-latest | |
| needs: [lint, test, e2e, container-scan, sast, build] | |
| if: always() | |
| steps: | |
| - name: Check all gates | |
| run: | | |
| echo "=== Quality Gate Summary ===" | |
| echo "Lint & Typecheck: ${{ needs.lint.result }}" | |
| echo "Unit Tests: ${{ needs.test.result }}" | |
| echo "E2E Tests: ${{ needs.e2e.result }}" | |
| echo "Container Scan: ${{ needs.container-scan.result }}" | |
| echo "SAST: ${{ needs.sast.result }}" | |
| echo "Build: ${{ needs.build.result }}" | |
| if [[ "${{ needs.lint.result }}" != "success" ]] || \ | |
| [[ "${{ needs.test.result }}" != "success" ]] || \ | |
| [[ "${{ needs.build.result }}" != "success" ]]; then | |
| echo "::error::Quality gate FAILED — required checks did not pass" | |
| exit 1 | |
| fi | |
| if [[ "${{ needs.e2e.result }}" != "success" ]]; then | |
| echo "::warning::E2E tests failed — review Playwright report" | |
| fi | |
| if [[ "${{ needs.container-scan.result }}" != "success" ]] || \ | |
| [[ "${{ needs.sast.result }}" != "success" ]]; then | |
| echo "::warning::Security scans reported issues — review artifacts" | |
| fi | |
| echo "All quality gates PASSED" |