Feat/orgs #11
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: Deploy Vercel + Neon (Web + API) | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - "packages/app/**" | |
| - "packages/api/**" | |
| - "packages/common/**" | |
| - ".github/workflows/deploy.yml" | |
| pull_request: | |
| types: [opened, synchronize, reopened, closed] | |
| paths: | |
| - "packages/app/**" | |
| - "packages/api/**" | |
| - "packages/common/**" | |
| - ".github/workflows/deploy.yml" | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # Determine if this is a production or preview deployment | |
| setup: | |
| name: Setup | |
| outputs: | |
| is_production: ${{ steps.deployment_type.outputs.is_production }} | |
| is_preview: ${{ steps.deployment_type.outputs.is_preview }} | |
| branch: ${{ steps.branch_name.outputs.current_branch }} | |
| neon_branch: ${{ steps.neon_branch.outputs.neon_branch }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Get branch name | |
| id: branch_name | |
| uses: tj-actions/branch-names@v8 | |
| - name: Determine deployment type | |
| id: deployment_type | |
| run: | | |
| if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then | |
| echo "is_production=true" >> $GITHUB_OUTPUT | |
| echo "is_preview=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "is_production=false" >> $GITHUB_OUTPUT | |
| echo "is_preview=true" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Generate Neon branch name | |
| id: neon_branch | |
| run: | | |
| if [[ "${{ steps.deployment_type.outputs.is_production }}" == "true" ]]; then | |
| echo "neon_branch=main" >> $GITHUB_OUTPUT | |
| else | |
| # Use branch name + commit hash for unique, descriptive branch names | |
| COMMIT_HASH="${{ github.sha }}" | |
| SHORT_HASH="${COMMIT_HASH:0:8}" | |
| BRANCH_NAME="${{ steps.branch_name.outputs.current_branch }}" | |
| # Clean branch name (remove special characters that might cause issues) | |
| CLEAN_BRANCH_NAME=$(echo "$BRANCH_NAME" | sed 's/[^a-zA-Z0-9-]/-/g' | sed 's/--*/-/g' | sed 's/^-\|-$//g') | |
| echo "neon_branch=preview/${CLEAN_BRANCH_NAME}/${SHORT_HASH}" >> $GITHUB_OUTPUT | |
| fi | |
| # Create/Delete Neon branch for preview deployments | |
| neon_branch_management: | |
| name: Neon Branch Management | |
| needs: setup | |
| outputs: | |
| db_url: ${{ steps.set_outputs.outputs.db_url }} | |
| db_url_with_pooler: ${{ steps.set_outputs.outputs.db_url_with_pooler }} | |
| has_urls: ${{ steps.set_outputs.outputs.has_urls }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Create Neon Branch | |
| id: create_neon_branch | |
| if: needs.setup.outputs.is_preview && github.event.action != 'closed' | |
| uses: neondatabase/create-branch-action@v5 | |
| with: | |
| project_id: ${{ vars.NEON_PROJECT_ID }} | |
| branch_name: ${{ needs.setup.outputs.neon_branch }} | |
| api_key: ${{ secrets.NEON_API_KEY }} | |
| - name: Install jq | |
| if: needs.setup.outputs.is_preview && github.event.action != 'closed' | |
| run: sudo apt-get update && sudo apt-get install -y jq | |
| - name: Wait for Neon Branch to be Ready | |
| if: needs.setup.outputs.is_preview && github.event.action != 'closed' | |
| run: | | |
| echo "Waiting for Neon branch to be ready..." | |
| BRANCH_ID="${{ steps.create_neon_branch.outputs.branch_id }}" | |
| echo "Branch ID: $BRANCH_ID" | |
| # Wait up to 5 minutes for the branch to be ready | |
| for i in {1..30}; do | |
| echo "Checking branch status (attempt $i/30)..." | |
| # Check branch status using curl to Neon API | |
| STATUS=$(curl -s -H "Authorization: Bearer ${{ secrets.NEON_API_KEY }}" \ | |
| "https://console.neon.tech/api/v2/projects/${{ vars.NEON_PROJECT_ID }}/branches/$BRANCH_ID" \ | |
| | jq -r '.branch.current_state' 2>/dev/null || echo "unknown") | |
| echo "Current status: $STATUS" | |
| if [[ "$STATUS" == "ready" ]]; then | |
| echo "Branch is ready!" | |
| break | |
| elif [[ "$STATUS" == "init" || "$STATUS" == "unknown" ]]; then | |
| echo "Branch still initializing, waiting 10 seconds..." | |
| sleep 10 | |
| else | |
| echo "Unexpected status: $STATUS" | |
| break | |
| fi | |
| done | |
| # Final check | |
| FINAL_STATUS=$(curl -s -H "Authorization: Bearer ${{ secrets.NEON_API_KEY }}" \ | |
| "https://console.neon.tech/api/v2/projects/${{ vars.NEON_PROJECT_ID }}/branches/$BRANCH_ID" \ | |
| | jq -r '.branch.current_state' 2>/dev/null || echo "unknown") | |
| if [[ "$FINAL_STATUS" != "ready" ]]; then | |
| echo "Warning: Branch may not be fully ready (status: $FINAL_STATUS)" | |
| fi | |
| - name: Debug info | |
| run: | | |
| echo "Debug information:" | |
| echo "is_preview: ${{ needs.setup.outputs.is_preview }}" | |
| echo "github.event.action: ${{ github.event.action }}" | |
| echo "github.event_name: ${{ github.event_name }}" | |
| echo "github.sha: ${{ github.sha }}" | |
| echo "neon_branch: ${{ needs.setup.outputs.neon_branch }}" | |
| - name: Set outputs | |
| id: set_outputs | |
| run: | | |
| if [[ "${{ needs.setup.outputs.is_preview }}" == "true" && "${{ github.event.action }}" != "closed" ]]; then | |
| echo "Setting Neon branch URLs..." | |
| echo "db_url=${{ steps.create_neon_branch.outputs.db_url }}" >> $GITHUB_OUTPUT | |
| echo "db_url_with_pooler=${{ steps.create_neon_branch.outputs.db_url_with_pooler }}" >> $GITHUB_OUTPUT | |
| echo "has_urls=true" >> $GITHUB_OUTPUT | |
| echo "Neon branch URLs set successfully" | |
| else | |
| echo "Not setting Neon branch URLs (not a preview deployment or PR closed)" | |
| echo "db_url=" >> $GITHUB_OUTPUT | |
| echo "db_url_with_pooler=" >> $GITHUB_OUTPUT | |
| echo "has_urls=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Delete Neon Branch | |
| if: needs.setup.outputs.is_preview && github.event.action == 'closed' | |
| uses: neondatabase/delete-branch-action@v3 | |
| with: | |
| project_id: ${{ vars.NEON_PROJECT_ID }} | |
| branch: ${{ needs.setup.outputs.neon_branch }} | |
| api_key: ${{ secrets.NEON_API_KEY }} | |
| continue-on-error: true | |
| # Run database migrations | |
| migrate: | |
| name: Run Database Migrations | |
| needs: [setup, neon_branch_management] | |
| if: needs.setup.outputs.is_preview && needs.neon_branch_management.outputs.has_urls == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| cache: "npm" | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Get DATABASE_URL | |
| uses: ./.github/actions/get-database-url | |
| with: | |
| is_production: "false" | |
| neon_branch: "${{ needs.setup.outputs.neon_branch }}" | |
| neon_project_id: "${{ vars.NEON_PROJECT_ID }}" | |
| neon_api_key: "${{ secrets.NEON_API_KEY }}" | |
| vercel_token: "${{ secrets.VERCEL_TOKEN }}" | |
| vercel_team: "${{ vars.VERCEL_TEAM }}" | |
| vercel_api_project: "${{ vars.VERCEL_API_PROJECT }}" | |
| - name: Run Migrations | |
| run: npm run migrate | |
| env: | |
| DATABASE_URL: "${{ env.DATABASE_URL }}" | |
| working-directory: packages/common | |
| # Deploy API project | |
| deploy_api: | |
| name: Deploy API | |
| needs: [setup, neon_branch_management] | |
| if: always() && (needs.setup.outputs.is_production || (needs.setup.outputs.is_preview && needs.neon_branch_management.outputs.has_urls == 'true')) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| cache: "npm" | |
| - name: Install Vercel CLI | |
| run: npm install -g vercel@latest | |
| - name: Get DATABASE_URL | |
| uses: ./.github/actions/get-database-url | |
| with: | |
| is_production: "${{ needs.setup.outputs.is_production }}" | |
| neon_branch: "${{ needs.setup.outputs.neon_branch }}" | |
| neon_project_id: "${{ vars.NEON_PROJECT_ID }}" | |
| neon_api_key: "${{ secrets.NEON_API_KEY }}" | |
| vercel_token: "${{ secrets.VERCEL_TOKEN }}" | |
| vercel_team: "${{ vars.VERCEL_TEAM }}" | |
| vercel_api_project: "${{ vars.VERCEL_API_PROJECT }}" | |
| - name: Deploy API | |
| run: | | |
| if [[ "${{ needs.setup.outputs.is_production }}" == "true" ]]; then | |
| vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }} --scope=${{ vars.VERCEL_TEAM }} --cwd=packages/api --yes | |
| else | |
| vercel deploy --token=${{ secrets.VERCEL_TOKEN }} --scope=${{ vars.VERCEL_TEAM }} --cwd=packages/api --yes | |
| fi | |
| env: | |
| DATABASE_URL: "${{ env.DATABASE_URL }}" | |
| # Deploy Web project | |
| deploy_web: | |
| name: Deploy Web | |
| needs: [setup, deploy_api] | |
| if: always() && needs.deploy_api.result == 'success' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| cache: "npm" | |
| - name: Install Vercel CLI | |
| run: npm install -g vercel@latest | |
| - name: Get DATABASE_URL | |
| uses: ./.github/actions/get-database-url | |
| with: | |
| is_production: "${{ needs.setup.outputs.is_production }}" | |
| neon_branch: "${{ needs.setup.outputs.neon_branch }}" | |
| neon_project_id: "${{ vars.NEON_PROJECT_ID }}" | |
| neon_api_key: "${{ secrets.NEON_API_KEY }}" | |
| vercel_token: "${{ secrets.VERCEL_TOKEN }}" | |
| vercel_team: "${{ vars.VERCEL_TEAM }}" | |
| vercel_api_project: "${{ vars.VERCEL_API_PROJECT }}" | |
| - name: Sync DATABASE_URL to Web project | |
| run: | | |
| if [[ "${{ needs.setup.outputs.is_production }}" == "true" ]]; then | |
| echo "${{ env.DATABASE_URL }}" | vercel env add DATABASE_URL production --token=${{ secrets.VERCEL_TOKEN }} --scope=${{ vars.VERCEL_TEAM }} --project=${{ vars.VERCEL_WEB_PROJECT }} --yes | |
| else | |
| echo "${{ env.DATABASE_URL }}" | vercel env add DATABASE_URL preview --token=${{ secrets.VERCEL_TOKEN }} --scope=${{ vars.VERCEL_TEAM }} --project=${{ vars.VERCEL_WEB_PROJECT }} --yes | |
| fi | |
| - name: Deploy Web | |
| run: | | |
| if [[ "${{ needs.setup.outputs.is_production }}" == "true" ]]; then | |
| vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }} --scope=${{ vars.VERCEL_TEAM }} --cwd=packages/app --yes | |
| else | |
| vercel deploy --token=${{ secrets.VERCEL_TOKEN }} --scope=${{ vars.VERCEL_TEAM }} --cwd=packages/app --yes | |
| fi | |
| env: | |
| DATABASE_URL: "${{ env.DATABASE_URL }}" | |
| # Post deployment summary | |
| deployment_summary: | |
| name: Deployment Summary | |
| needs: [setup, deploy_api, deploy_web] | |
| if: always() && (needs.deploy_api.result == 'success' || needs.deploy_web.result == 'success') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Deployment Summary | |
| run: | | |
| echo "## 🚀 Deployment Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Deployment Type:** ${{ needs.setup.outputs.is_production == 'true' && 'Production' || 'Preview' }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Branch:** ${{ needs.setup.outputs.branch }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Neon Branch:** ${{ needs.setup.outputs.neon_branch }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Deployment Status:" >> $GITHUB_STEP_SUMMARY | |
| echo "- **API:** ${{ needs.deploy_api.result == 'success' && '✅ Success' || '❌ Failed' }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Web:** ${{ needs.deploy_web.result == 'success' && '✅ Success' || '❌ Failed' }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [[ "${{ needs.setup.outputs.is_preview }}" == "true" ]]; then | |
| echo "### Preview URLs:" >> $GITHUB_STEP_SUMMARY | |
| echo "- Check the Vercel dashboard for preview URLs" >> $GITHUB_STEP_SUMMARY | |
| fi |