mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
Add concise routed context, task-aware agent roles, focused skills, and structured learning promotion.\n\nUnify local development and workspace tooling, add layered test and E2E verification, harden MCP authentication, and gate deployment behind verification and explicit production promotion.
186 lines
7.2 KiB
YAML
186 lines
7.2 KiB
YAML
name: Deploy Supabase to Production
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
deploy_production:
|
|
description: Promote the verified change to production after non-production
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
push:
|
|
branches:
|
|
- master
|
|
paths:
|
|
- 'apps/supabase/**'
|
|
- 'terraform/**'
|
|
- '.github/workflows/deploy-supabase.yml'
|
|
|
|
jobs:
|
|
quality:
|
|
name: Verify deployable source
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version-file: .nvmrc
|
|
cache: npm
|
|
- run: npm ci
|
|
- run: npm run verify:fast
|
|
- run: npm run build
|
|
|
|
deploy-database:
|
|
name: Deploy Migrations & Functions
|
|
runs-on: ubuntu-latest
|
|
needs: quality
|
|
environment: ${{ matrix.environment }}
|
|
strategy:
|
|
max-parallel: 1
|
|
matrix:
|
|
environment: ${{ fromJSON(github.event_name == 'workflow_dispatch' && inputs.deploy_production && '["non-prod","prod"]' || '["non-prod"]') }}
|
|
|
|
env:
|
|
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
|
|
SUPABASE_PROJECT_REF: ${{ matrix.environment == 'prod' && 'ckjcrkjpmhqhiucifukx' || 'itbzhrvlgvdmzbnhzhyx' }}
|
|
ENVIRONMENT_NAME: ${{ matrix.environment }}
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Supabase CLI
|
|
uses: supabase/setup-cli@v1
|
|
with:
|
|
version: latest
|
|
|
|
- name: Link to ${{ matrix.environment }} Project
|
|
run: supabase link --project-ref $SUPABASE_PROJECT_REF --workdir apps
|
|
|
|
- name: Apply Database Migrations (${{ matrix.environment }})
|
|
run: supabase db push --workdir apps --include-all
|
|
|
|
- name: Sync Edge Function Secrets (${{ matrix.environment }})
|
|
env:
|
|
# Add secrets from GitHub Secrets
|
|
REVENUECAT_WEBHOOK_SECRET: ${{ secrets.REVENUECAT_WEBHOOK_SECRET }}
|
|
REVENUECAT_API_KEY: ${{ secrets.REVENUECAT_API_KEY }}
|
|
REVENUECAT_ENTITLEMENT_ID: ${{ secrets.REVENUECAT_ENTITLEMENT_ID }}
|
|
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
ADMIN_PRIVATE_KEY_JWK: ${{ secrets.ADMIN_PRIVATE_KEY_JWK }}
|
|
ADMIN_KEYS_JSON: ${{ secrets.ADMIN_KEYS_JSON }}
|
|
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
|
run: |
|
|
# Sync secrets to Supabase (only if they're set in GitHub)
|
|
secrets_to_set=""
|
|
|
|
[ -n "$REVENUECAT_WEBHOOK_SECRET" ] && secrets_to_set="$secrets_to_set REVENUECAT_WEBHOOK_SECRET=$REVENUECAT_WEBHOOK_SECRET"
|
|
[ -n "$REVENUECAT_API_KEY" ] && secrets_to_set="$secrets_to_set REVENUECAT_API_KEY=$REVENUECAT_API_KEY"
|
|
[ -n "$REVENUECAT_ENTITLEMENT_ID" ] && secrets_to_set="$secrets_to_set REVENUECAT_ENTITLEMENT_ID=$REVENUECAT_ENTITLEMENT_ID"
|
|
[ -n "$OPENROUTER_API_KEY" ] && secrets_to_set="$secrets_to_set OPENROUTER_API_KEY=$OPENROUTER_API_KEY"
|
|
[ -n "$ADMIN_PRIVATE_KEY_JWK" ] && secrets_to_set="$secrets_to_set ADMIN_PRIVATE_KEY_JWK=$ADMIN_PRIVATE_KEY_JWK"
|
|
[ -n "$ADMIN_KEYS_JSON" ] && secrets_to_set="$secrets_to_set ADMIN_KEYS_JSON=$ADMIN_KEYS_JSON"
|
|
[ -n "$DISCORD_WEBHOOK_URL" ] && secrets_to_set="$secrets_to_set DISCORD_WEBHOOK_URL=$DISCORD_WEBHOOK_URL"
|
|
|
|
if [ -n "$secrets_to_set" ]; then
|
|
echo "Syncing secrets to Supabase ($ENVIRONMENT_NAME)..."
|
|
supabase secrets set $secrets_to_set --project-ref $SUPABASE_PROJECT_REF
|
|
else
|
|
echo "No secrets to sync"
|
|
fi
|
|
|
|
- name: Deploy Edge Functions (${{ matrix.environment }})
|
|
run: |
|
|
# Deploy all edge functions (excluding _shared directory)
|
|
for dir in apps/supabase/functions/*/; do
|
|
function_name=$(basename "$dir")
|
|
|
|
# Skip _shared directory (it's a shared module, not a function)
|
|
if [ "$function_name" = "_shared" ]; then
|
|
echo "Skipping _shared (shared module)"
|
|
continue
|
|
fi
|
|
|
|
echo "Deploying function: $function_name to $ENVIRONMENT_NAME"
|
|
supabase functions deploy "$function_name" --no-verify-jwt --project-ref $SUPABASE_PROJECT_REF --workdir apps
|
|
done
|
|
|
|
- name: Remove Deleted Functions (${{ matrix.environment }})
|
|
run: |
|
|
# Get list of functions deployed on Supabase (skip header lines, extract slug column)
|
|
deployed=$(supabase functions list --project-ref $SUPABASE_PROJECT_REF | grep -E '^[a-z]' | awk '{print $1}')
|
|
|
|
# Get list of functions in the repo (excluding _shared)
|
|
local_functions=$(ls -d apps/supabase/functions/*/ 2>/dev/null | xargs -I {} basename {} | grep -v '^_shared$' || true)
|
|
|
|
# Delete any deployed functions that no longer exist in the repo
|
|
for func in $deployed; do
|
|
# Skip empty or invalid function names
|
|
[ -z "$func" ] && continue
|
|
[[ "$func" =~ ^[^a-z] ]] && continue
|
|
|
|
if ! echo "$local_functions" | grep -q "^${func}$"; then
|
|
echo "Deleting removed function from $ENVIRONMENT_NAME: $func"
|
|
supabase functions delete "$func" --project-ref $SUPABASE_PROJECT_REF
|
|
fi
|
|
done
|
|
|
|
deploy-infrastructure:
|
|
name: Deploy Infrastructure (Terraform)
|
|
runs-on: ubuntu-latest
|
|
needs: quality
|
|
|
|
env:
|
|
TF_VAR_supabase_access_token: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Terraform
|
|
uses: hashicorp/setup-terraform@v3
|
|
with:
|
|
terraform_version: "1.6"
|
|
|
|
# Non-Production Environment
|
|
- name: Terraform Init (Non-Prod)
|
|
working-directory: terraform/environments/non-prod
|
|
run: terraform init
|
|
|
|
- name: Terraform Format Check
|
|
working-directory: terraform/modules/supabase
|
|
run: terraform fmt -check
|
|
|
|
- name: Terraform Validate (Non-Prod)
|
|
working-directory: terraform/environments/non-prod
|
|
run: terraform validate
|
|
|
|
- name: Terraform Plan (Non-Prod)
|
|
working-directory: terraform/environments/non-prod
|
|
run: terraform plan -out=tfplan
|
|
|
|
- name: Terraform Apply (Non-Prod)
|
|
working-directory: terraform/environments/non-prod
|
|
run: terraform apply -auto-approve tfplan
|
|
|
|
# Production Environment
|
|
- name: Terraform Init (Prod)
|
|
if: github.event_name == 'workflow_dispatch' && inputs.deploy_production
|
|
working-directory: terraform/environments/prod
|
|
run: terraform init
|
|
|
|
- name: Terraform Validate (Prod)
|
|
if: github.event_name == 'workflow_dispatch' && inputs.deploy_production
|
|
working-directory: terraform/environments/prod
|
|
run: terraform validate
|
|
|
|
- name: Terraform Plan (Prod)
|
|
if: github.event_name == 'workflow_dispatch' && inputs.deploy_production
|
|
working-directory: terraform/environments/prod
|
|
run: terraform plan -out=tfplan
|
|
|
|
- name: Terraform Apply (Prod)
|
|
if: github.event_name == 'workflow_dispatch' && inputs.deploy_production
|
|
working-directory: terraform/environments/prod
|
|
run: terraform apply -auto-approve tfplan
|