Skip to content

fix(docker): push to Docker Hub with DOCKER_USERNAME/DOCKER_TOKEN sec… #27

fix(docker): push to Docker Hub with DOCKER_USERNAME/DOCKER_TOKEN sec…

fix(docker): push to Docker Hub with DOCKER_USERNAME/DOCKER_TOKEN sec… #27

Workflow file for this run

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"