Skip to content

Feat/orgs

Feat/orgs #7

Workflow file for this run

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 from Neon
id: get_db_url
run: |
echo "Getting DATABASE_URL from Neon branch..."
BRANCH_NAME="${{ needs.setup.outputs.neon_branch }}"
echo "Branch name: $BRANCH_NAME"
# Get the branch details from Neon API
BRANCH_DATA=$(curl -s -H "Authorization: Bearer ${{ secrets.NEON_API_KEY }}" \
"https://console.neon.tech/api/v2/projects/${{ vars.NEON_PROJECT_ID }}/branches" \
| jq -r ".branches[] | select(.name == \"$BRANCH_NAME\")")
if [[ "$BRANCH_DATA" != "null" && -n "$BRANCH_DATA" ]]; then
# Get the connection URI
CONNECTION_URI=$(echo "$BRANCH_DATA" | jq -r '.connection_uris[0].connection_uri')
echo "Found connection URI: ${CONNECTION_URI:0:20}..."
echo "DATABASE_URL=$CONNECTION_URI" >> $GITHUB_ENV
echo "DATABASE_URL retrieved successfully"
else
echo "Could not find branch: $BRANCH_NAME"
exit 1
fi
- name: Run Migrations with Retry
run: |
echo "Running migrations with retry logic..."
MAX_ATTEMPTS=5
ATTEMPT=1
while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
echo "Migration attempt $ATTEMPT/$MAX_ATTEMPTS"
if npm run migrate; then
echo "Migrations completed successfully!"
break
else
echo "Migration failed on attempt $ATTEMPT"
if [ $ATTEMPT -lt $MAX_ATTEMPTS ]; then
echo "Waiting 30 seconds before retry..."
sleep 30
else
echo "All migration attempts failed"
exit 1
fi
fi
ATTEMPT=$((ATTEMPT + 1))
done
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
id: get_api_env
run: |
if [[ "${{ needs.setup.outputs.is_production }}" == "true" ]]; then
# For production, pull from existing API project
vercel env pull --environment=production --token=${{ secrets.VERCEL_TOKEN }} --scope=${{ vars.VERCEL_TEAM }} --project=${{ vars.VERCEL_API_PROJECT }} .env.api
echo "DATABASE_URL=$(grep DATABASE_URL .env.api | cut -d= -f2-)" >> $GITHUB_ENV
else
# For preview, get from Neon branch
echo "Getting DATABASE_URL from Neon branch..."
BRANCH_NAME="${{ needs.setup.outputs.neon_branch }}"
echo "Branch name: $BRANCH_NAME"
# Get the branch details from Neon API
BRANCH_DATA=$(curl -s -H "Authorization: Bearer ${{ secrets.NEON_API_KEY }}" \
"https://console.neon.tech/api/v2/projects/${{ vars.NEON_PROJECT_ID }}/branches" \
| jq -r ".branches[] | select(.name == \"$BRANCH_NAME\")")
if [[ "$BRANCH_DATA" != "null" && -n "$BRANCH_DATA" ]]; then
# Get the connection URI (use pooled version if available)
CONNECTION_URI=$(echo "$BRANCH_DATA" | jq -r '.connection_uris[0].connection_uri')
echo "Found connection URI: ${CONNECTION_URI:0:20}..."
echo "DATABASE_URL=$CONNECTION_URI" >> $GITHUB_ENV
echo "DATABASE_URL retrieved successfully"
else
echo "Could not find branch: $BRANCH_NAME"
exit 1
fi
fi
- name: Deploy API
run: |
if [[ "${{ needs.setup.outputs.is_production }}" == "true" ]]; then
vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }} --scope=${{ vars.VERCEL_TEAM }} --yes
else
vercel deploy --token=${{ secrets.VERCEL_TOKEN }} --scope=${{ vars.VERCEL_TEAM }} --yes
fi
env:
DATABASE_URL: "${{ env.DATABASE_URL }}"
working-directory: packages/api
# 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
id: get_db_url
run: |
if [[ "${{ needs.setup.outputs.is_production }}" == "true" ]]; then
# Pull DATABASE_URL from API project for production
vercel env pull --environment=production --token=${{ secrets.VERCEL_TOKEN }} --scope=${{ vars.VERCEL_TEAM }} --project=${{ vars.VERCEL_API_PROJECT }} .env.api
echo "DATABASE_URL=$(grep DATABASE_URL .env.api | cut -d= -f2-)" >> $GITHUB_ENV
else
# For preview, get from Neon branch
echo "Getting DATABASE_URL from Neon branch..."
BRANCH_NAME="${{ needs.setup.outputs.neon_branch }}"
echo "Branch name: $BRANCH_NAME"
# Get the branch details from Neon API
BRANCH_DATA=$(curl -s -H "Authorization: Bearer ${{ secrets.NEON_API_KEY }}" \
"https://console.neon.tech/api/v2/projects/${{ vars.NEON_PROJECT_ID }}/branches" \
| jq -r ".branches[] | select(.name == \"$BRANCH_NAME\")")
if [[ "$BRANCH_DATA" != "null" && -n "$BRANCH_DATA" ]]; then
# Get the connection URI (use pooled version if available)
CONNECTION_URI=$(echo "$BRANCH_DATA" | jq -r '.connection_uris[0].connection_uri')
echo "Found connection URI: ${CONNECTION_URI:0:20}..."
echo "DATABASE_URL=$CONNECTION_URI" >> $GITHUB_ENV
echo "DATABASE_URL retrieved successfully"
else
echo "Could not find branch: $BRANCH_NAME"
exit 1
fi
fi
- 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 }} --yes
else
vercel deploy --token=${{ secrets.VERCEL_TOKEN }} --scope=${{ vars.VERCEL_TEAM }} --yes
fi
env:
DATABASE_URL: "${{ env.DATABASE_URL }}"
working-directory: packages/app
# 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