Build #135
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: Build | |
| on: | |
| workflow_dispatch: | |
| pull_request: | |
| paths: | |
| - "app/**" | |
| - "components/**" | |
| - "lib/**" | |
| - "actions/**" | |
| - "hooks/**" | |
| - "prisma/**" | |
| - "scripts/**" | |
| - "content/**" | |
| - "theme.config.tsx" | |
| - "mdx-components.tsx" | |
| - "package.json" | |
| - "pnpm-lock.yaml" | |
| - "Dockerfile" | |
| - ".github/workflows/build.yaml" | |
| push: | |
| branches: | |
| - main | |
| - staging | |
| paths: | |
| - "app/**" | |
| - "components/**" | |
| - "lib/**" | |
| - "actions/**" | |
| - "hooks/**" | |
| - "prisma/**" | |
| - "scripts/**" | |
| - "content/**" | |
| - "theme.config.tsx" | |
| - "mdx-components.tsx" | |
| - "package.json" | |
| - "pnpm-lock.yaml" | |
| - "Dockerfile" | |
| - ".github/workflows/build.yaml" | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: ${{ github.repository }}/tea-app | |
| PROD_APP_URL: https://assuranceplatform.azurewebsites.net | |
| STAGING_APP_URL: https://staging-assuranceplatform.azurewebsites.net | |
| jobs: | |
| validate: | |
| name: Validate Code Quality | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| - name: Setup Node.js with cache | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Generate Prisma client | |
| run: npx prisma generate --schema=prisma/schema.prisma | |
| env: | |
| DATABASE_URL: postgresql://build:build@localhost:5432/build | |
| - name: Run linter | |
| run: pnpm exec ultracite check | |
| - name: Run type check | |
| run: npx tsc --noEmit | |
| build: | |
| name: Build and Push Docker Image | |
| runs-on: ubuntu-latest | |
| needs: validate | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| # Stage A: pnpm caching for faster dependency resolution | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| - name: Setup Node.js with cache | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'pnpm' | |
| # Note: Documentation is now built as part of Next.js (Nextra integration) | |
| # No separate tea-docs build step needed | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to 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: | | |
| # Branch name | |
| type=ref,event=branch | |
| # PR number | |
| type=ref,event=pr | |
| # SHA (short) - only for branch pushes, not PRs | |
| type=sha,prefix={{branch}}-,enable=${{ github.event_name != 'pull_request' }} | |
| # For main branch | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| type=raw,value=main,enable=${{ github.ref == 'refs/heads/main' }} | |
| # For staging branch | |
| type=raw,value=staging,enable=${{ github.ref == 'refs/heads/staging' }} | |
| type=raw,value=staging-{{date 'YYYY-MM-DD'}}-{{sha}},enable=${{ github.ref == 'refs/heads/staging' }} | |
| # Semantic versions (for releases) | |
| type=semver,pattern={{version}} | |
| type=semver,pattern={{major}}.{{minor}} | |
| type=semver,pattern={{major}} | |
| - name: Build and push Docker image (Production - main branch) | |
| if: github.ref == 'refs/heads/main' | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| file: ./Dockerfile | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| build-args: | | |
| GITHUB_APP_CLIENT_ID=${{ secrets.GH_APP_CLIENT_ID }} | |
| GITHUB_APP_CLIENT_SECRET=${{ secrets.GH_APP_CLIENT_SECRET }} | |
| GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }} | |
| GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }} | |
| NEXTAUTH_SECRET=${{ secrets.NEXTAUTH_SECRET }} | |
| NEXTAUTH_URL=${{ secrets.NEXTAUTH_URL_MAIN }} | |
| DATABASE_URL=postgresql://build:build@localhost:5432/build | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| platforms: linux/amd64 | |
| - name: Deploy to Azure (Production) | |
| if: github.ref == 'refs/heads/main' | |
| id: deploy-prod | |
| env: | |
| WEBHOOK_URL: ${{ secrets.AZURE_WEBAPP_WEBHOOK_PROD }} | |
| run: | | |
| echo "Triggering Azure deployment webhook..." | |
| # Webhook with retry logic | |
| MAX_RETRIES=3 | |
| DEPLOY_SUCCESS=false | |
| for i in $(seq 1 $MAX_RETRIES); do | |
| echo "Deployment attempt $i of $MAX_RETRIES..." | |
| HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ | |
| --max-time 30 \ | |
| -X POST "${WEBHOOK_URL}" \ | |
| -H "Content-Length: 0") | |
| if [ "$HTTP_STATUS" -ge 200 ] && [ "$HTTP_STATUS" -lt 300 ]; then | |
| echo "Webhook triggered successfully (HTTP $HTTP_STATUS)" | |
| DEPLOY_SUCCESS=true | |
| break | |
| fi | |
| echo "Webhook returned HTTP $HTTP_STATUS" | |
| [ $i -lt $MAX_RETRIES ] && sleep 10 | |
| done | |
| if [ "$DEPLOY_SUCCESS" = "false" ]; then | |
| echo "::error::Deployment webhook failed after $MAX_RETRIES attempts" | |
| exit 1 | |
| fi | |
| # Wait for deployment to start | |
| echo "Waiting 60 seconds for deployment to propagate..." | |
| sleep 60 | |
| # Health check with retries | |
| echo "Checking application health..." | |
| HEALTH_SUCCESS=false | |
| for i in $(seq 1 10); do | |
| echo "Health check attempt $i of 10..." | |
| HEALTH_STATUS=$(curl -sL -o /dev/null -w "%{http_code}" \ | |
| --max-time 10 \ | |
| "${{ env.PROD_APP_URL }}/api/health") | |
| if [ "$HEALTH_STATUS" = "200" ]; then | |
| echo "Application is healthy!" | |
| HEALTH_SUCCESS=true | |
| break | |
| fi | |
| echo "Health check returned HTTP $HEALTH_STATUS" | |
| [ $i -lt 10 ] && sleep 15 | |
| done | |
| if [ "$HEALTH_SUCCESS" = "false" ]; then | |
| echo "::warning::Application health check did not pass after deployment" | |
| echo "DEPLOY_HEALTH=failed" >> $GITHUB_OUTPUT | |
| else | |
| echo "DEPLOY_HEALTH=success" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Build and push Docker image (Staging branch) | |
| if: github.ref == 'refs/heads/staging' | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| file: ./Dockerfile | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| build-args: | | |
| GITHUB_APP_CLIENT_ID=${{ secrets.GH_APP_CLIENT_ID_STAGING }} | |
| GITHUB_APP_CLIENT_SECRET=${{ secrets.GH_APP_CLIENT_SECRET_STAGING }} | |
| GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID_STAGING }} | |
| GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET_STAGING }} | |
| NEXTAUTH_SECRET=${{ secrets.NEXTAUTH_SECRET_STAGING }} | |
| NEXTAUTH_URL=${{ secrets.NEXTAUTH_URL_STAGING }} | |
| DATABASE_URL=postgresql://build:build@localhost:5432/build | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| platforms: linux/amd64 | |
| - name: Deploy to Azure (Staging) | |
| if: github.ref == 'refs/heads/staging' | |
| id: deploy-staging | |
| env: | |
| WEBHOOK_URL: ${{ secrets.AZURE_WEBAPP_WEBHOOK_STAGING }} | |
| run: | | |
| echo "Triggering Azure deployment webhook..." | |
| # Webhook with retry logic | |
| MAX_RETRIES=3 | |
| DEPLOY_SUCCESS=false | |
| for i in $(seq 1 $MAX_RETRIES); do | |
| echo "Deployment attempt $i of $MAX_RETRIES..." | |
| HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ | |
| --max-time 30 \ | |
| -X POST "${WEBHOOK_URL}" \ | |
| -H "Content-Length: 0") | |
| if [ "$HTTP_STATUS" -ge 200 ] && [ "$HTTP_STATUS" -lt 300 ]; then | |
| echo "Webhook triggered successfully (HTTP $HTTP_STATUS)" | |
| DEPLOY_SUCCESS=true | |
| break | |
| fi | |
| echo "Webhook returned HTTP $HTTP_STATUS" | |
| [ $i -lt $MAX_RETRIES ] && sleep 10 | |
| done | |
| if [ "$DEPLOY_SUCCESS" = "false" ]; then | |
| echo "::error::Deployment webhook failed after $MAX_RETRIES attempts" | |
| exit 1 | |
| fi | |
| # Wait for deployment to start | |
| echo "Waiting 60 seconds for deployment to propagate..." | |
| sleep 60 | |
| # Health check with retries | |
| echo "Checking application health..." | |
| HEALTH_SUCCESS=false | |
| for i in $(seq 1 10); do | |
| echo "Health check attempt $i of 10..." | |
| HEALTH_STATUS=$(curl -sL -o /dev/null -w "%{http_code}" \ | |
| --max-time 10 \ | |
| "${{ env.STAGING_APP_URL }}/api/health") | |
| if [ "$HEALTH_STATUS" = "200" ]; then | |
| echo "Application is healthy!" | |
| HEALTH_SUCCESS=true | |
| break | |
| fi | |
| echo "Health check returned HTTP $HEALTH_STATUS" | |
| [ $i -lt 10 ] && sleep 15 | |
| done | |
| if [ "$HEALTH_SUCCESS" = "false" ]; then | |
| echo "::warning::Application health check did not pass after deployment" | |
| echo "DEPLOY_HEALTH=failed" >> $GITHUB_OUTPUT | |
| else | |
| echo "DEPLOY_HEALTH=success" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Build and push Docker image (PR/other branches) | |
| if: github.ref != 'refs/heads/main' && github.ref != 'refs/heads/staging' | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| file: ./Dockerfile | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| build-args: | | |
| GITHUB_APP_CLIENT_ID=${{ secrets.GH_APP_CLIENT_ID_STAGING }} | |
| GITHUB_APP_CLIENT_SECRET=${{ secrets.GH_APP_CLIENT_SECRET_STAGING }} | |
| GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID_STAGING }} | |
| GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET_STAGING }} | |
| NEXTAUTH_SECRET=${{ secrets.NEXTAUTH_SECRET_STAGING }} | |
| NEXTAUTH_URL=${{ secrets.NEXTAUTH_URL_STAGING }} | |
| DATABASE_URL=postgresql://build:build@localhost:5432/build | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| platforms: linux/amd64 | |
| # GitHub Deployment Summary | |
| - name: Create Deployment Summary - Success | |
| if: success() && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging') | |
| run: | | |
| if [ "${{ github.ref }}" = "refs/heads/main" ]; then | |
| ENV="Production" | |
| HEALTH_STATUS="${{ steps.deploy-prod.outputs.DEPLOY_HEALTH }}" | |
| APP_URL="${{ env.PROD_APP_URL }}" | |
| else | |
| ENV="Staging" | |
| HEALTH_STATUS="${{ steps.deploy-staging.outputs.DEPLOY_HEALTH }}" | |
| APP_URL="${{ env.STAGING_APP_URL }}" | |
| fi | |
| if [ "$HEALTH_STATUS" = "success" ]; then | |
| HEALTH_ICON="✅" | |
| else | |
| HEALTH_ICON="⚠️" | |
| fi | |
| echo "## 🚀 Deployment Successful" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY | |
| echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Environment** | ${ENV} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Health Check** | ${HEALTH_ICON} ${HEALTH_STATUS} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Commit** | \`${{ github.sha }}\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Actor** | @${{ github.actor }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Application** | [${APP_URL}](${APP_URL}) |" >> $GITHUB_STEP_SUMMARY | |
| - name: Create Deployment Summary - Failure | |
| if: failure() && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging') | |
| run: | | |
| if [ "${{ github.ref }}" = "refs/heads/main" ]; then | |
| ENV="Production" | |
| else | |
| ENV="Staging" | |
| fi | |
| echo "## ❌ Deployment Failed" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY | |
| echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Environment** | ${ENV} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Branch** | ${{ github.ref_name }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Commit** | \`${{ github.sha }}\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Actor** | @${{ github.actor }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Please check the workflow logs for details." >> $GITHUB_STEP_SUMMARY |