fix: handle access_token param in auth callback + accept bearer header #104
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] | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: ${{ github.repository }} | |
| # Grant GITHUB_TOKEN the permissions required for this workflow | |
| permissions: | |
| contents: read | |
| packages: write | |
| jobs: | |
| # Step 1: Quick Validation - Fail fast on basic issues | |
| validate: | |
| name: Quick Validation | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Get pnpm store directory | |
| id: pnpm-cache | |
| shell: bash | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT | |
| - name: Setup pnpm cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm-store- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Run linter | |
| run: pnpm run lint || echo "::warning::Linting failed but continuing..." | |
| - name: Type check | |
| run: pnpm exec tsc --noEmit || echo "::warning::Type checking failed but continuing..." | |
| - name: Run tests (REQUIRED) | |
| run: pnpm run test:ci | |
| - name: Verify build can complete | |
| run: pnpm run build || echo "::error::Build failed but continuing to show all issues" | |
| # Step 2: Full Build | |
| build: | |
| name: Full Build | |
| runs-on: ubuntu-latest | |
| needs: validate # Only proceed if validation passes | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Get pnpm store directory | |
| id: pnpm-cache | |
| shell: bash | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT | |
| - name: Setup pnpm cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm-store- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build application | |
| run: pnpm run build | |
| env: | |
| NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL || 'http://localhost:3000' }} | |
| - name: Run tests | |
| run: pnpm test || true # Add tests when available | |
| # Cache build artifacts for deployment | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: build-artifacts | |
| path: | | |
| .next | |
| public | |
| package.json | |
| pnpm-lock.yaml | |
| next.config.js | |
| retention-days: 1 | |
| # Step 3: Build Docker Image | |
| docker: | |
| name: Build Docker Image | |
| runs-on: ubuntu-latest | |
| needs: build | |
| if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') | |
| permissions: | |
| contents: read | |
| packages: write | |
| outputs: | |
| image-tag: ${{ steps.meta.outputs.tags }} | |
| image-digest: ${{ steps.build.outputs.digest }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract metadata | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
| tags: | | |
| type=ref,event=branch | |
| type=sha,prefix={{branch}}- | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| - name: Build and push Docker image | |
| id: build | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| file: ./Dockerfile.production | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| build-args: | | |
| NEXT_PUBLIC_API_URL=${{ secrets.NEXT_PUBLIC_API_URL || 'http://localhost:3000' }} | |
| # Step 4: Deploy to Digital Ocean App Platform | |
| # Note: DO App Platform auto-deploys on push via deploy_on_push: true | |
| # This job waits for deployment and verifies health | |
| deploy: | |
| name: Deploy to Digital Ocean | |
| runs-on: ubuntu-latest | |
| needs: [build] | |
| if: github.ref == 'refs/heads/main' && needs.build.result == 'success' | |
| environment: | |
| name: production | |
| url: https://hanzo.app | |
| steps: | |
| - name: Install doctl | |
| uses: digitalocean/action-doctl@v2 | |
| with: | |
| token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} | |
| - name: Wait for DO deployment | |
| run: | | |
| echo "Waiting for Digital Ocean App Platform to deploy..." | |
| # DO auto-deploys on push, wait for it to complete | |
| sleep 60 | |
| # Check deployment status | |
| for i in {1..20}; do | |
| STATUS=$(doctl apps list-deployments ${{ secrets.DO_APP_ID }} --format Phase --no-header | head -1) | |
| echo "Deployment status: $STATUS" | |
| if [ "$STATUS" = "ACTIVE" ]; then | |
| echo "Deployment successful!" | |
| break | |
| elif [ "$STATUS" = "ERROR" ]; then | |
| echo "Deployment failed!" | |
| exit 1 | |
| fi | |
| echo "Attempt $i: Waiting for deployment to complete..." | |
| sleep 30 | |
| done | |
| - name: Get app URL | |
| id: app-url | |
| run: | | |
| APP_URL=$(doctl apps get ${{ secrets.DO_APP_ID }} --format DefaultIngress --no-header) | |
| echo "url=$APP_URL" >> $GITHUB_OUTPUT | |
| echo "App URL: $APP_URL" | |
| - name: Health check | |
| run: | | |
| for i in {1..10}; do | |
| if curl -f -s -o /dev/null -w "%{http_code}" ${{ steps.app-url.outputs.url }}/api/health | grep -q "200"; then | |
| echo "Health check passed" | |
| exit 0 | |
| fi | |
| echo "Attempt $i: Waiting for app to be healthy..." | |
| sleep 10 | |
| done | |
| echo "Health check failed after 10 attempts" | |
| exit 1 | |
| # Step 5: Notify on Success/Failure | |
| notify: | |
| name: Send Notifications | |
| runs-on: ubuntu-latest | |
| needs: [validate, build, deploy] | |
| if: always() | |
| steps: | |
| - name: Determine status | |
| id: status | |
| run: | | |
| if [ "${{ needs.deploy.result }}" == "success" ]; then | |
| echo "status=success" >> $GITHUB_OUTPUT | |
| echo "emoji=✅" >> $GITHUB_OUTPUT | |
| echo "message=Successfully deployed to production" >> $GITHUB_OUTPUT | |
| elif [ "${{ needs.deploy.result }}" == "skipped" ]; then | |
| echo "status=skipped" >> $GITHUB_OUTPUT | |
| echo "emoji=⏭️" >> $GITHUB_OUTPUT | |
| echo "message=Deployment skipped (not main branch)" >> $GITHUB_OUTPUT | |
| else | |
| echo "status=failure" >> $GITHUB_OUTPUT | |
| echo "emoji=❌" >> $GITHUB_OUTPUT | |
| echo "message=Deployment failed" >> $GITHUB_OUTPUT | |
| fi | |
| # Discord webhook notification (optional - fails silently if not configured) | |
| - name: Discord Notification | |
| run: | | |
| curl -H "Content-Type: application/json" \ | |
| -d '{ | |
| "embeds": [{ | |
| "title": "${{ steps.status.outputs.emoji }} Deployment ${{ steps.status.outputs.status }}", | |
| "description": "${{ steps.status.outputs.message }}", | |
| "color": ${{ steps.status.outputs.status == 'success' && '3066993' || '15158332' }}, | |
| "fields": [ | |
| { | |
| "name": "Branch", | |
| "value": "${{ github.ref_name }}", | |
| "inline": true | |
| }, | |
| { | |
| "name": "Commit", | |
| "value": "[`${{ github.sha }}`](${{ github.event.head_commit.url }})", | |
| "inline": true | |
| }, | |
| { | |
| "name": "Author", | |
| "value": "${{ github.actor }}", | |
| "inline": true | |
| }, | |
| { | |
| "name": "Message", | |
| "value": "${{ github.event.head_commit.message }}" | |
| } | |
| ], | |
| "timestamp": "${{ github.event.head_commit.timestamp }}" | |
| }] | |
| }' \ | |
| ${{ secrets.DISCORD_WEBHOOK_URL }} || true | |
| # Slack webhook notification (optional - fails silently if not configured) | |
| - name: Slack Notification | |
| run: | | |
| curl -X POST \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "text": "${{ steps.status.outputs.emoji }} *Deployment ${{ steps.status.outputs.status }}*", | |
| "blocks": [ | |
| { | |
| "type": "section", | |
| "text": { | |
| "type": "mrkdwn", | |
| "text": "*${{ steps.status.outputs.message }}*\n\n*Branch:* ${{ github.ref_name }}\n*Commit:* <${{ github.event.head_commit.url }}|${{ github.sha }}>\n*Author:* ${{ github.actor }}\n*Message:* ${{ github.event.head_commit.message }}" | |
| } | |
| } | |
| ] | |
| }' \ | |
| ${{ secrets.SLACK_WEBHOOK_URL }} || true |