-
Notifications
You must be signed in to change notification settings - Fork 0
Add deployment pipeline and production assets #41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| node_modules | ||
| npm-debug.log | ||
| dist | ||
| .git | ||
| .gitignore | ||
| README.md | ||
| deploy.md | ||
| .env | ||
| .env.local | ||
| .env.*.local | ||
| .DS_Store | ||
| .vscode | ||
| .idea | ||
| *.swp | ||
| *.swo | ||
| *~ | ||
| .coverage | ||
| coverage/ | ||
| .next | ||
| .nuxt | ||
| build/ | ||
| .cache | ||
| .turbo | ||
| docs/ | ||
| .github/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,221 @@ | ||
| 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: "18" | ||
| 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: "18" | ||
| 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: "18" | ||
| cache: "npm" | ||
| cache-dependency-path: package-lock.json | ||
|
|
||
| - name: Install dependencies | ||
| run: npm ci | ||
|
|
||
| - name: Type-check | ||
| run: npx tsc --noEmit | ||
|
|
||
| - name: Build frontend | ||
| env: | ||
| VITE_BACKEND_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 }}" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| name: Staging Validation | ||
|
|
||
| on: | ||
| workflow_run: | ||
| workflows: ["CI/CD Pipeline"] | ||
| branches: ["develop"] | ||
| types: [completed] | ||
|
|
||
| jobs: | ||
| staging-deploy: | ||
| name: Deploy to Staging | ||
| runs-on: ubuntu-latest | ||
| if: ${{ github.event.workflow_run.conclusion == 'success' }} | ||
|
|
||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@v3 | ||
|
|
||
| - name: Log in to Docker Hub | ||
| uses: docker/login-action@v3 | ||
| with: | ||
| registry: docker.io | ||
| username: ${{ secrets.DOCKERHUB_USERNAME }} | ||
| password: ${{ secrets.DOCKERHUB_TOKEN }} | ||
|
|
||
| - name: Build and push staging image | ||
| uses: docker/build-push-action@v5 | ||
| with: | ||
| context: . | ||
| file: ./Dockerfile | ||
| push: true | ||
| tags: | | ||
| ${{ secrets.DOCKERHUB_USERNAME }}/spectracleanse-api:staging-${{ github.event.workflow_run.head_commit.sha }} | ||
|
Comment on lines
+35
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): Use
|
||
| ${{ secrets.DOCKERHUB_USERNAME }}/spectracleanse-api:staging-latest | ||
| cache-from: type=gha | ||
| cache-to: type=gha,mode=max | ||
|
|
||
| - name: Trigger staging deployment | ||
| if: ${{ secrets.STAGING_DEPLOY_HOOK != '' }} | ||
| run: | | ||
| curl -sf -X POST "${{ secrets.STAGING_DEPLOY_HOOK }}" \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{ | ||
| "image":"${{ secrets.DOCKERHUB_USERNAME }}/spectracleanse-api:staging-${{ github.event.workflow_run.head_commit.sha }}", | ||
| "environment":"staging" | ||
| }' | ||
|
|
||
| - name: Run smoke tests on staging | ||
| run: | | ||
| echo "Waiting for staging deployment..." | ||
| sleep 10 | ||
|
|
||
| if [ ! -z "${{ secrets.STAGING_URL }}" ]; then | ||
| for i in $(seq 1 10); do | ||
| if curl -sf "${{ secrets.STAGING_URL }}/api/health"; then | ||
| echo "Staging health check passed" | ||
| exit 0 | ||
| fi | ||
| sleep 5 | ||
| done | ||
| echo "Staging failed to respond" | ||
| exit 1 | ||
| fi | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚨 suggestion (security): Pin the TruffleHog action to a version or commit instead of using @main.
Referencing
trufflesecurity/trufflehog@mainmeans the workflow may change or break as the main branch evolves. For stability and supply-chain security, pin this action to a specific version tag or commit SHA (e.g.@v3or@<sha>).