Skip to content

Feature/claude helps with cicd #64

Feature/claude helps with cicd

Feature/claude helps with cicd #64

Workflow file for this run

name: Merge To Main
on:
pull_request:
branches:
- main
permissions:
contents: write
pull-requests: write
env:
TEST_GCP_PROJECT: ${{ vars.IM_TEST_PROJECT_ID }}
TEST_FIREBASE_PROJECT: ${{ vars.IM_TEST_PROJECT_ID }}
TEST_DB_INSTANCE: ${{ vars.IM_TEST_DB_INSTANCE }}
NODE_VERSION: 22.13.0
CLOUD_SQL_PROXY_VERSION: v2.18.0
CLOUD_SQL_PROXY_CHECKSUM: e48f49397a69a1b509a74ce025dcf76019da0dcaae17f744c524dd19e9824800
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ACTIONS_STEP_DEBUG: true
IM_DB_INSTANCE_NAME: ${{ vars.IM_DB_INSTANCE_NAME }}
jobs:
# ===== MIGRATION PIPELINE (Pre-Deployment) =====
check-pending-migrations:
name: 🔍 Check for Pending Migrations
runs-on: ubuntu-latest
environment: Test-Database-NoApproval
env:
DB_HOST: ${{ secrets.DB_HOST }}
DB_PORT: ${{ secrets.DB_PORT }}
DB_USER: ${{ secrets.DB_USER }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
DB_NAME: ${{ secrets.DB_NAME }}
outputs:
pending-migrations: ${{ steps.check-pending.outputs.pending-migrations }}
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: npm ci
- name: Authenticate to GCP
uses: google-github-actions/auth@v2.1.0
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Setup Cloud SQL Proxy
run: |
# Download Cloud SQL Proxy with checksum verification
curl -o cloud-sql-proxy https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/${{ env.CLOUD_SQL_PROXY_VERSION }}/cloud-sql-proxy.linux.amd64
echo "${{ env.CLOUD_SQL_PROXY_CHECKSUM }} cloud-sql-proxy" | sha256sum --check
chmod +x cloud-sql-proxy
# Start proxy in background
./cloud-sql-proxy --port 5432 ${{ env.TEST_DB_INSTANCE }} &
PROXY_PID=$!
# Wait for proxy to be ready with retry logic
echo "⏳ Waiting for Cloud SQL Proxy to be ready..."
for i in {1..30}; do
if nc -z 127.0.0.1 5432; then
echo "✅ Cloud SQL Proxy is ready (attempt $i)"
break
fi
if [ $i -eq 30 ]; then
echo "❌ Cloud SQL Proxy failed to start after 30 attempts"
echo "📋 Proxy logs:"
ps aux | grep cloud-sql-proxy || echo "No proxy process found"
kill $PROXY_PID 2>/dev/null || true
exit 1
fi
echo "⏳ Attempt $i: Proxy not ready, waiting 2 seconds..."
sleep 2
done
- name: Check for pending migrations
id: check-pending
run: |
npx ts-node --project ./util/tsconfig.json ./util/check_for_pending_migrations.ts > ./migration_check.log
if grep -q 'MIGRATION_PENDING=true' migration_check.log; then
echo "pending-migrations=true" >> $GITHUB_OUTPUT
echo "⚠️ Pending migrations detected"
else
echo "pending-migrations=false" >> $GITHUB_OUTPUT
echo "✅ No pending migrations"
fi
cat migration_check.log
- name: Show pending migrations
if: steps.check-pending.outputs.pending-migrations == 'true'
id: show-migrations
run: |
echo "📋 PENDING MIGRATIONS PREVIEW:"
echo ""
npx ts-node --project ./util/tsconfig.json ./node_modules/typeorm/cli.js migration:show -f util/ormconfig.js || echo "Unable to show migrations - will be visible during application"
echo ""
echo "📁 Migration files to be applied:"
ls -la libs/migration/*.ts 2>/dev/null || echo "No migration files found in libs/migration/"
apply-pending-migrations:
name: 🔍 Apply Pending Migrations (View Check Pending Migrations job to see migrations before running)
runs-on: ubuntu-latest
needs: [check-pending-migrations]
environment: Test-Database-Approval
env:
DB_HOST: ${{ secrets.DB_HOST }}
DB_PORT: ${{ secrets.DB_PORT }}
DB_USER: ${{ secrets.DB_USER }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
DB_NAME: ${{ secrets.DB_NAME }}
outputs:
backup-id: ${{ steps.create-backup-pending.outputs.backup-id }}
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
if: needs.check-pending-migrations.outputs.pending-migrations == 'true'
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
if: needs.check-pending-migrations.outputs.pending-migrations == 'true'
run: npm ci
- name: Authenticate to GCP
uses: google-github-actions/auth@v2.1.0
if: needs.check-pending-migrations.outputs.pending-migrations == 'true'
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Setup Cloud SQL Proxy
if: needs.check-pending-migrations.outputs.pending-migrations == 'true'
run: |
# Download Cloud SQL Proxy with checksum verification
curl -o cloud-sql-proxy https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/${{ env.CLOUD_SQL_PROXY_VERSION }}/cloud-sql-proxy.linux.amd64
echo "${{ env.CLOUD_SQL_PROXY_CHECKSUM }} cloud-sql-proxy" | sha256sum --check
chmod +x cloud-sql-proxy
# Start proxy in background
./cloud-sql-proxy --port 5432 ${{ env.TEST_DB_INSTANCE }} &
PROXY_PID=$!
# Wait for proxy to be ready with retry logic
echo "⏳ Waiting for Cloud SQL Proxy to be ready..."
for i in {1..30}; do
if nc -z 127.0.0.1 5432; then
echo "✅ Cloud SQL Proxy is ready (attempt $i)"
break
fi
if [ $i -eq 30 ]; then
echo "❌ Cloud SQL Proxy failed to start after 30 attempts"
echo "📋 Proxy logs:"
ps aux | grep cloud-sql-proxy || echo "No proxy process found"
kill $PROXY_PID 2>/dev/null || true
exit 1
fi
echo "⏳ Attempt $i: Proxy not ready, waiting 2 seconds..."
sleep 2
done
- name: Create database backup for pending migrations
id: create-backup-pending
if: needs.check-pending-migrations.outputs.pending-migrations == 'true'
run: |
echo "📦 Creating on-demand database backup for pending migrations..."
echo "🔧 Using instance: ${{ env.IM_DB_INSTANCE_NAME }}"
# Create backup and capture the full output for debugging
echo "🔄 Creating backup for instance: ${{ env.IM_DB_INSTANCE_NAME }}"
# Create backup with quiet flag to reduce noise, capture JSON output separately
if gcloud sql backups create --instance=${{ env.IM_DB_INSTANCE_NAME }} --description="Pending migration backup $(date +%Y-%m-%d_%H:%M:%S)" --quiet; then
echo "✅ Backup creation command completed"
# Get the most recent backup (the one we just created)
BACKUP_OUTPUT=$(gcloud sql backups list --instance=${{ env.IM_DB_INSTANCE_NAME }} --limit=1 --format="json" 2>/dev/null)
echo "🔍 Recent backup info: $BACKUP_OUTPUT"
else
echo "❌ Backup creation failed"
exit 1
fi
# Extract backup ID from JSON response (handle both object and array formats)
BACKUP_ID=$(echo "$BACKUP_OUTPUT" | jq -r 'if type == "array" then .[0].name // .[0].id else .name // .id end // empty')
echo "🆔 Extracted backup ID: '$BACKUP_ID'"
if [ -z "$BACKUP_ID" ] || [ "$BACKUP_ID" = "null" ]; then
echo "❌ Failed to extract backup ID from response"
echo "🔍 Attempting alternative backup creation method..."
# Try alternative method: create backup without JSON format and get ID separately
TIMESTAMP=$(date +%Y%m%d%H%M%S)
BACKUP_DESC="PendingMigrationBackup-$TIMESTAMP"
echo "🔄 Creating backup with description: $BACKUP_DESC"
if gcloud sql backups create --instance=${{ env.IM_DB_INSTANCE_NAME }} --description="$BACKUP_DESC" --quiet; then
echo "✅ Backup creation command succeeded"
# Wait a moment for backup to appear in list
sleep 5
# Find the backup we just created
BACKUP_ID=$(gcloud sql backups list --instance=${{ env.IM_DB_INSTANCE_NAME }} --filter="description:$BACKUP_DESC" --format="value(name)" --limit=1)
if [ -n "$BACKUP_ID" ]; then
echo "✅ Found backup with ID: $BACKUP_ID"
else
echo "⏳ Backup not yet visible, checking recent backups..."
BACKUP_ID=$(gcloud sql backups list --instance=${{ env.IM_DB_INSTANCE_NAME }} --limit=1 --format="value(name)")
if [ -n "$BACKUP_ID" ]; then
echo "✅ Using most recent backup: $BACKUP_ID"
else
echo "❌ No backup found after creation"
exit 1
fi
fi
else
echo "❌ Alternative backup creation also failed"
exit 1
fi
fi
echo "backup-id=$BACKUP_ID" >> $GITHUB_OUTPUT
echo "✅ On-demand backup created: $BACKUP_ID"
- name: Apply pending migrations (if any)
if: needs.check-pending-migrations.outputs.pending-migrations == 'true'
run: |
echo "⚙️ Applying pending migrations..."
npx ts-node --project ./util/tsconfig.json ./node_modules/typeorm/cli.js migration:run -f util/ormconfig.js
echo "✅ Pending migrations applied successfully"
check-schema-drift:
name: 🔍 Check for Schema Drift
runs-on: ubuntu-latest
needs: [apply-pending-migrations]
environment: Test-Database-NoApproval
env:
DB_HOST: ${{ secrets.DB_HOST }}
DB_PORT: ${{ secrets.DB_PORT }}
DB_USER: ${{ secrets.DB_USER }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
DB_NAME: ${{ secrets.DB_NAME }}
outputs:
migration-needed: ${{ steps.check-drift.outputs.migration-needed }}
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: npm ci
- name: Authenticate to GCP
uses: google-github-actions/auth@v2.1.0
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Setup Cloud SQL Proxy
run: |
# Download Cloud SQL Proxy with checksum verification
curl -o cloud-sql-proxy https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/${{ env.CLOUD_SQL_PROXY_VERSION }}/cloud-sql-proxy.linux.amd64
echo "${{ env.CLOUD_SQL_PROXY_CHECKSUM }} cloud-sql-proxy" | sha256sum --check
chmod +x cloud-sql-proxy
# Start proxy in background
./cloud-sql-proxy --port 5432 ${{ env.TEST_DB_INSTANCE }} &
PROXY_PID=$!
# Wait for proxy to be ready with retry logic
echo "⏳ Waiting for Cloud SQL Proxy to be ready..."
for i in {1..30}; do
if nc -z 127.0.0.1 5432; then
echo "✅ Cloud SQL Proxy is ready (attempt $i)"
break
fi
if [ $i -eq 30 ]; then
echo "❌ Cloud SQL Proxy failed to start after 30 attempts"
echo "📋 Proxy logs:"
ps aux | grep cloud-sql-proxy || echo "No proxy process found"
kill $PROXY_PID 2>/dev/null || true
exit 1
fi
echo "⏳ Attempt $i: Proxy not ready, waiting 2 seconds..."
sleep 2
done
- name: Check for schema drift
id: check-drift
run: |
npx ts-node --project ./util/tsconfig.json ./util/detect_db_entity_drift.ts > ./drift.log
if grep -q 'MIGRATION_NEEDED=true' drift.log; then
echo "migration-needed=true" >> $GITHUB_OUTPUT
echo "📈 Schema drift detected - new migration needed"
else
echo "migration-needed=false" >> $GITHUB_OUTPUT
echo "✅ No schema drift detected"
fi
- name: Preview new migration
id: preview-migration
if: steps.check-drift.outputs.migration-needed == 'true'
run: |
echo "📋 NEW MIGRATION PREVIEW:"
echo ""
echo "🔄 Generating migration preview (dry-run)..."
npx ts-node --project ./util/tsconfig.json ./node_modules/typeorm/cli.js migration:generate -n PreviewMigration-$(date +%Y%m%d%H%M%S) -f util/ormconfig.js --dryrun 2>/dev/null || {
echo "⚠️ TypeORM dry-run not supported, generating actual migration for preview..."
npx ts-node --project ./util/tsconfig.json ./node_modules/typeorm/cli.js migration:generate -n PreviewMigration-$(date +%Y%m%d%H%M%S) -f util/ormconfig.js
echo ""
echo "📁 Generated migration preview:"
ls -la libs/migration/*PreviewMigration*.ts 2>/dev/null | tail -1
echo ""
echo "📄 Migration content:"
cat $(ls -t libs/migration/*PreviewMigration*.ts 2>/dev/null | head -1) || echo "Unable to display migration content"
echo ""
echo "🗑️ Cleaning up preview migration..."
rm -f libs/migration/*PreviewMigration*.ts
}
- name: New migration review required
if: steps.check-drift.outputs.migration-needed == 'true'
run: |
echo "🔍 NEW MIGRATION REQUIRED"
echo ""
echo "📈 Schema changes detected that require a new migration."
echo ""
echo "Please verify:"
echo "- Review the migration preview shown above"
echo "- Ensure the generated SQL is safe to apply"
echo "- Confirm no data loss will occur"
echo "- Check that indexes and constraints are properly handled"
echo ""
echo "After approval, the system will:"
echo "- Generate a new migration file"
echo "- Commit it to this PR"
echo "- Apply the migration to the database"
echo ""
echo "✅ Approve to proceed with generating and applying the new migration."
echo ""
generate-and-apply-migration:
name: ⚙️ Generate & Apply New Migration
runs-on: ubuntu-latest
environment: Test-Database-Approval
needs: [check-schema-drift, apply-pending-migrations]
outputs:
backup-id: ${{ steps.create-backup-new.outputs.backup-id || steps.use-existing-backup.outputs.backup-id }}
env:
DB_HOST: ${{ secrets.DB_HOST }}
DB_PORT: ${{ secrets.DB_PORT }}
DB_USER: ${{ secrets.DB_USER }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
DB_NAME: ${{ secrets.DB_NAME }}
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
if: needs.check-schema-drift.outputs.migration-needed == 'true'
run: npm ci
- name: Authenticate to GCP
uses: google-github-actions/auth@v2.1.0
if: needs.check-schema-drift.outputs.migration-needed == 'true'
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Setup Cloud SQL Proxy
if: needs.check-schema-drift.outputs.migration-needed == 'true'
run: |
# Download Cloud SQL Proxy with checksum verification
curl -o cloud-sql-proxy https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/${{ env.CLOUD_SQL_PROXY_VERSION }}/cloud-sql-proxy.linux.amd64
echo "${{ env.CLOUD_SQL_PROXY_CHECKSUM }} cloud-sql-proxy" | sha256sum --check
chmod +x cloud-sql-proxy
# Start proxy in background
./cloud-sql-proxy --port 5432 ${{ env.TEST_DB_INSTANCE }} &
PROXY_PID=$!
# Wait for proxy to be ready with retry logic
echo "⏳ Waiting for Cloud SQL Proxy to be ready..."
for i in {1..30}; do
if nc -z 127.0.0.1 5432; then
echo "✅ Cloud SQL Proxy is ready (attempt $i)"
break
fi
if [ $i -eq 30 ]; then
echo "❌ Cloud SQL Proxy failed to start after 30 attempts"
echo "📋 Proxy logs:"
ps aux | grep cloud-sql-proxy || echo "No proxy process found"
kill $PROXY_PID 2>/dev/null || true
exit 1
fi
echo "⏳ Attempt $i: Proxy not ready, waiting 2 seconds..."
sleep 2
done
- name: Configure git
if: needs.check-schema-drift.outputs.migration-needed == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
# Checkout the actual branch instead of detached HEAD
git fetch origin ${{ github.head_ref }}
git checkout -B ${{ github.head_ref }} origin/${{ github.head_ref }}
- name: Create database backup (if not already done)
id: create-backup-new
if: needs.check-schema-drift.outputs.migration-needed == 'true' && (needs.apply-pending-migrations.outputs.backup-id == '' || needs.apply-pending-migrations.outputs.backup-id == null)
run: |
echo "📦 Creating on-demand database backup for new migration..."
echo "🔧 Using instance: ${{ env.IM_DB_INSTANCE_NAME }}"
# Create backup and capture the full output for debugging
echo "🔄 Creating backup for instance: ${{ env.IM_DB_INSTANCE_NAME }}"
# Create backup with quiet flag to reduce noise, capture JSON output separately
if gcloud sql backups create --instance=${{ env.IM_DB_INSTANCE_NAME }} --description="New migration backup $(date +%Y-%m-%d_%H:%M:%S)" --quiet; then
echo "✅ Backup creation command completed"
# Get the most recent backup (the one we just created)
BACKUP_OUTPUT=$(gcloud sql backups list --instance=${{ env.IM_DB_INSTANCE_NAME }} --limit=1 --format="json" 2>/dev/null)
echo "🔍 Recent backup info: $BACKUP_OUTPUT"
else
echo "❌ Backup creation failed"
exit 1
fi
# Extract backup ID from JSON response (handle both object and array formats)
BACKUP_ID=$(echo "$BACKUP_OUTPUT" | jq -r 'if type == "array" then .[0].name // .[0].id else .name // .id end // empty')
echo "🆔 Extracted backup ID: '$BACKUP_ID'"
if [ -z "$BACKUP_ID" ] || [ "$BACKUP_ID" = "null" ]; then
echo "❌ Failed to extract backup ID from response"
echo "🔍 Attempting alternative backup creation method..."
# Try alternative method: create backup without JSON format and get ID separately
TIMESTAMP=$(date +%Y%m%d%H%M%S)
BACKUP_DESC="PendingMigrationBackup-$TIMESTAMP"
echo "🔄 Creating backup with description: $BACKUP_DESC"
if gcloud sql backups create --instance=${{ env.IM_DB_INSTANCE_NAME }} --description="$BACKUP_DESC" --quiet; then
echo "✅ Backup creation command succeeded"
# Wait a moment for backup to appear in list
sleep 5
# Find the backup we just created
BACKUP_ID=$(gcloud sql backups list --instance=${{ env.IM_DB_INSTANCE_NAME }} --filter="description:$BACKUP_DESC" --format="value(name)" --limit=1)
if [ -n "$BACKUP_ID" ]; then
echo "✅ Found backup with ID: $BACKUP_ID"
else
echo "⏳ Backup not yet visible, checking recent backups..."
BACKUP_ID=$(gcloud sql backups list --instance=${{ env.IM_DB_INSTANCE_NAME }} --limit=1 --format="value(name)")
if [ -n "$BACKUP_ID" ]; then
echo "✅ Using most recent backup: $BACKUP_ID"
else
echo "❌ No backup found after creation"
exit 1
fi
fi
else
echo "❌ Alternative backup creation also failed"
exit 1
fi
fi
echo "backup-id=$BACKUP_ID" >> $GITHUB_OUTPUT
echo "✅ On-demand backup created: $BACKUP_ID"
- name: Use existing backup if available
id: use-existing-backup
if: needs.check-schema-drift.outputs.migration-needed == 'true' && needs.apply-pending-migrations.outputs.backup-id != '' && needs.apply-pending-migrations.outputs.backup-id != null
run: |
echo "♾️ Using existing backup from pending migrations: ${{ needs.apply-pending-migrations.outputs.backup-id }}"
echo "backup-id=${{ needs.apply-pending-migrations.outputs.backup-id }}" >> $GITHUB_OUTPUT
- name: Generate and apply migration
if: needs.check-schema-drift.outputs.migration-needed == 'true'
run: |
set -e # Exit on any error
echo "🔄 Generating new migration..."
MIGRATION_NAME="AutoMigration-$(date +%Y%m%d%H%M%S)"
if ! npx ts-node --project ./util/tsconfig.json ./node_modules/typeorm/cli.js migration:generate -n "$MIGRATION_NAME" -f util/ormconfig.js; then
echo "❌ Failed to generate migration"
exit 1
fi
# Verify migration file was created
MIGRATION_FILE=$(ls libs/migration/*"$MIGRATION_NAME"*.ts 2>/dev/null | head -1)
if [ -z "$MIGRATION_FILE" ]; then
echo "❌ Migration file not found after generation"
exit 1
fi
echo "✅ Generated migration: $MIGRATION_FILE"
echo "💾 Committing generated migration..."
if ! git add libs/migration/*.ts; then
echo "❌ Failed to stage migration files"
exit 1
fi
if ! git commit -m "chore: auto-generated migration - approved 🤖 This migration was automatically generated and approved. Generated and applied at: $(date -u)"; then
echo "❌ Failed to commit migration"
exit 1
fi
# Pull any remote changes before pushing
git pull origin ${{ github.head_ref }} --rebase || echo "No remote changes to pull"
if ! git push origin ${{ github.head_ref }}; then
echo "❌ Failed to push migration commit"
exit 1
fi
echo "✅ Migration committed and pushed"
echo "⚙️ Applying new migration..."
if ! npx ts-node --project ./util/tsconfig.json ./node_modules/typeorm/cli.js migration:run -f util/ormconfig.js; then
echo "❌ Failed to apply migration"
echo "⚠️ Migration was committed but failed to apply - manual intervention required"
exit 1
fi
echo "✅ New migration applied successfully"
deploy-test:
name: ▶️ Deploy → Test (MANUAL)
environment: Test
runs-on: ubuntu-latest
needs: [generate-and-apply-migration]
if: needs.generate-and-apply-migration.result == 'success'
concurrency:
group: test-environment
# allow only one "waiting for approval" + "running" job in that group
cancel-in-progress: false
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Determine new version name
id: get-version
run: |
TAG=${GITHUB_REF#refs/}
TAG=${TAG#merge}
SHORT_SHA=${GITHUB_SHA:0:7}
V="${TAG,,}-${SHORT_SHA}"
V=${V//[^a-z0-9-]/-}
echo $V
echo "NEW_VERSION=${V}" >> $GITHUB_OUTPUT
shell: bash
- name: Install dependencies
run: npm ci
- name: Install nx@12.3.6
run: npm install -g nx@12.3.6
- name: Authenticate to GCP
uses: google-github-actions/auth@v2.1.0
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Generate API Environment (test)
run: npx ts-node ./util/generate_environment.ts generate test
env:
INVOLVEMINT_SETTINGS: ${{ secrets.INVOLVEMINT_TEST_SETTINGS_SERVER }}
- name: Build API (test)
run: |
export NODE_OPTIONS=--openssl-legacy-provider
nx run-many --target=build --projects=api,involvemint --configuration=test --skip-nx-cache
- name: Store current versions for rollback
id: store-versions
run: |
# Store current API version
CURRENT_API_VERSION=$(gcloud app versions list \
--service=default \
--format="value(version.id)" \
--sort-by="~version.createTime" \
--limit=1 2>/dev/null || echo "none")
echo "current-api-version=$CURRENT_API_VERSION" >> $GITHUB_OUTPUT
echo "📋 Current API version: $CURRENT_API_VERSION"
- name: 🚀 Deploy API to App Engine (Test) - No Traffic Initially
id: deploy-api
run: |
set -e
gcloud config set project ${{ env.TEST_GCP_PROJECT }}
# Deploy without stopping previous version and without traffic
echo "🚀 Deploying new API version without traffic..."
gcloud app deploy app.yaml \
--no-stop-previous-version \
--no-promote \
--quiet \
--version=${{ steps.get-version.outputs.NEW_VERSION }}
echo "✅ API deployed successfully (no traffic yet)"
- name: Delete API Environment (test)
if: steps.deploy-api.outcome == 'success'
run: npx ts-node ./util/generate_environment.ts delete test
- name: Generate Client Environment (test)
if: steps.deploy-api.outcome == 'success'
run: npx ts-node ./util/generate_environment.ts generate test
env:
INVOLVEMINT_SETTINGS: ${{ secrets.INVOLVEMINT_TEST_SETTINGS_CLIENT }}
- name: Build Client (test)
if: steps.deploy-api.outcome == 'success'
run: |
export NODE_OPTIONS=--openssl-legacy-provider
nx run-many --target=build --projects=involvemint --configuration=test --skip-nx-cache
- name: Install firebase-tools@13.29.0
if: steps.deploy-api.outcome == 'success'
run: npm install -g firebase-tools@13.29.0
- name: 🚀 Deploy Client to Firebase
id: deploy-client
if: steps.deploy-api.outcome == 'success'
run: |
set -e
firebase use ${{ env.TEST_FIREBASE_PROJECT }}
firebase deploy --message "${{ steps.get-version.outputs.NEW_VERSION }}"
echo "✅ Client deployed successfully"
- name: Promote API version (switch traffic)
id: promote-api
if: steps.deploy-client.outcome == 'success'
run: |
set -e
echo "🔄 Switching traffic to new API version..."
gcloud app services set-traffic default \
--splits="${{ steps.get-version.outputs.NEW_VERSION }}=1" \
--quiet
echo "🧹 Cleaning up old API versions (keeping last 3)..."
# Keep the last 3 versions for rollback capability, delete older ones
gcloud app versions list --service=default --format="value(version.id)" --sort-by="~version.createTime" --limit=10 | tail -n +4 | xargs -r gcloud app versions delete --quiet || echo "⚠️ Could not delete old versions"
echo "✅ Atomic deployment completed successfully"
- name: Comprehensive rollback on failure
if: ${{ failure() }}
run: |
set -e
echo "🚨 Deployment failed - initiating rollback procedures..."
# Rollback API if it was deployed but not yet promoted
if [ "${{ steps.deploy-api.outcome }}" == "success" ] && [ "${{ steps.promote-api.outcome }}" != "success" ]; then
echo "🔄 Cleaning up failed API deployment..."
gcloud app versions delete ${{ steps.get-version.outputs.NEW_VERSION }} --quiet || echo "⚠️ Could not delete failed API version"
fi
# Rollback API traffic if promotion happened but client deployment failed
if [ "${{ steps.promote-api.outcome }}" == "success" ] && [ "${{ steps.deploy-client.outcome }}" != "success" ]; then
echo "🔄 Rolling back API traffic to previous version..."
PREV_VERSION="${{ steps.store-versions.outputs.current-api-version }}"
if [ "$PREV_VERSION" != "none" ] && [ -n "$PREV_VERSION" ]; then
gcloud app services set-traffic default --splits="$PREV_VERSION=1" --quiet
gcloud app versions delete ${{ steps.get-version.outputs.NEW_VERSION }} --quiet || echo "⚠️ Could not delete failed API version"
echo "✅ API traffic rolled back to $PREV_VERSION"
else
echo "❌ No previous API version found for rollback"
fi
fi
# Database rollback if migrations were applied
BACKUP_ID="${{ needs.generate-and-apply-migration.outputs.backup-id }}"
if [ -n "$BACKUP_ID" ] && [ "$BACKUP_ID" != "" ]; then
echo "🔄 Rolling back database to backup: $BACKUP_ID"
echo "⚠️ Note: Database rollback from backup requires manual approval due to data loss risk"
echo "📋 Backup ID available for manual restore: $BACKUP_ID"
echo "🔧 To restore: gcloud sql backups restore $BACKUP_ID --restore-instance=${{ env.TEST_DB_INSTANCE }}"
else
echo "ℹ️ No database backup available for rollback"
fi
# TODO: Add Firebase client rollback when available
# Note: Firebase doesn't have built-in version rollback, would need custom implementation
echo "🔄 Rollback procedures completed"
shell: bash
test-env-verified:
name: ✅ Approve Test Env Validation (MANUAL)
environment: Test
runs-on: ubuntu-latest
needs: deploy-test
steps:
- run: echo "Testing in TEST environment has been completed, approved by."