Skip to content

Build source code and deploy #2261

Build source code and deploy

Build source code and deploy #2261

name: Build source code and deploy
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
tags:
- "capgo-[0-9]*"
permissions: {}
jobs:
changes:
runs-on: ubuntu-latest
timeout-minutes: 10
name: Resolve deploy scope
permissions:
contents: read
outputs:
api: ${{ steps.scope.outputs.api }}
files: ${{ steps.scope.outputs.files }}
plugins: ${{ steps.scope.outputs.plugins }}
supabase: ${{ steps.scope.outputs.supabase }}
translation: ${{ steps.scope.outputs.translation }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
filter: blob:none
- name: Setup bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Resolve deploy scope
id: scope
env:
REF_NAME: ${{ github.ref_name }}
run: bun scripts/deploy-scope.ts "$REF_NAME" >> "$GITHUB_OUTPUT"
# Tests already passed before tag was created, so just build and deploy
supabase_deploy:
needs: [changes, read_replica_schema]
if: ${{ always() && needs.changes.result == 'success' && needs.changes.outputs.supabase == 'true' && (needs.read_replica_schema.result == 'success' || needs.read_replica_schema.result == 'skipped') }}
runs-on: ubuntu-latest
timeout-minutes: 60
name: Build code and deploy to Supabase
permissions:
contents: read
steps:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24.x
- name: Checkout
uses: actions/checkout@v6
- name: Setup bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Set environment variable
run: |
if [[ ${{ github.ref }} == *-alpha* ]]; then
echo "SUPA_ENV=ALPHA" >> $GITHUB_ENV
else
echo "SUPA_ENV=PROD" >> $GITHUB_ENV
fi
- name: Set Supabase credentials (ALPHA)
if: env.SUPA_ENV == 'ALPHA'
run: |
echo "SUPABASE_DB_PASSWORD=${{ secrets.SUPABASE_DB_PASS_ALPHA }}" >> $GITHUB_ENV
echo "SUPABASE_PROJECT_ID=${{ secrets.SUPABASE_PROJECT_ID_ALPHA }}" >> $GITHUB_ENV
- name: Set Supabase credentials (PROD)
if: env.SUPA_ENV == 'PROD'
run: |
echo "SUPABASE_DB_PASSWORD=${{ secrets.SUPABASE_DB_PASS_PROD }}" >> $GITHUB_ENV
echo "SUPABASE_PROJECT_ID=${{ secrets.SUPABASE_PROJECT_ID_PROD }}" >> $GITHUB_ENV
- name: Install dependencies
run: bun install
- name: Install Supabase CLI
uses: supabase/setup-cli@v2.0.0
with:
version: 2.109.1
- name: Prepare Supabase
run: supabase link --project-ref ${{ env.SUPABASE_PROJECT_ID }}
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_TOKEN }}
- name: Apply Supabase Migrations
run: supabase db push
- name: Update functions
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_TOKEN }}
run: supabase functions deploy
read_replica_schema:
needs: changes
if: ${{ needs.changes.result == 'success' && needs.changes.outputs.supabase == 'true' && !contains(github.ref_name, '-alpha') }}
runs-on: ubuntu-latest
timeout-minutes: 40
name: Reconcile production read-replica schema
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Setup Google Cloud CLI
uses: google-github-actions/setup-gcloud@v2
- name: Install dependencies
run: bun install
- name: Reconcile the Google subscriber through server-side Cloud SQL import
env:
GOOGLE_SERVICE_ACCOUNT_BASE64: ${{ secrets.GOOGLE_SERVICE_ACCOUNT }}
run: |
credentials_file="$(mktemp)"
trap 'rm -f "$credentials_file"' EXIT
printf '%s' "$GOOGLE_SERVICE_ACCOUNT_BASE64" | base64 --decode > "$credentials_file"
gcloud auth activate-service-account --key-file="$credentials_file"
bun scripts/sync-read-replica-schema.ts
deploy_webapp:
needs: [changes, supabase_deploy, read_replica_schema]
if: ${{ always() && needs.changes.result == 'success' && (needs.supabase_deploy.result == 'success' || needs.supabase_deploy.result == 'skipped') && (needs.read_replica_schema.result == 'success' || needs.read_replica_schema.result == 'skipped') }}
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
outputs:
native_build_needed: ${{ steps.release_check.outputs.native_build_needed }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
filter: blob:none
- uses: actions/setup-node@v6
with:
node-version: 24
- name: Setup bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies
run: bun install
- name: Set environment variable
run: |
if [[ ${{ github.ref }} == *-alpha* ]]; then
echo "ENV=dev" >> $GITHUB_ENV
echo "CHANNEL=dev" >> $GITHUB_ENV
else
echo "ENV=prod" >> $GITHUB_ENV
echo "CHANNEL=production" >> $GITHUB_ENV
fi
- name: Check if native build is required
id: release_check
env:
CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }}
run: |
set +e
bunx @capgo/cli@latest build needed --channel "${{ env.CHANNEL }}"
exit_code=$?
set -e
if [ "$exit_code" -eq 0 ]; then
echo "native_build_needed=false" >> "$GITHUB_OUTPUT"
echo "Release type: OTA"
elif [ "$exit_code" -eq 1 ]; then
echo "native_build_needed=true" >> "$GITHUB_OUTPUT"
echo "Release type: native"
else
echo "build needed check failed with exit code $exit_code"
exit "$exit_code"
fi
- name: Build
run: bun ${{ env.ENV == 'prod' && 'build:mobile' || 'build:dev:mobile' }}
env:
VITE_VAPID_KEY: ${{ secrets.VITE_VAPID_KEY }}
VITE_FIREBASE_CONFIG: ${{ secrets.VITE_FIREBASE_CONFIG }}
- name: Generate AI changelog
id: changelog
continue-on-error: true
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: bun run changelog:ai
- name: Publish CF console
run: bun run deploy:cloudflare:console:${{ env.ENV }}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
- name: Deploy OTA bundle to Capgo
if: steps.release_check.outputs.native_build_needed == 'false'
run: bunx @capgo/cli@latest bundle upload --channel ${{ env.CHANNEL }} --delta
env:
CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }}
- name: Create GitHub release
id: create_release
uses: softprops/action-gh-release@v2
with:
body: |
## 🆕 Changelog
${{ steps.changelog.outputs.result || '_Changelog was not generated automatically for this release._' }}
---
🔗 **Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.changelog.outputs.from_tag || github.ref_name }}...${{ steps.changelog.outputs.to_tag || github.ref_name }}
make_latest: true
token: "${{ secrets.PERSONAL_ACCESS_TOKEN }}"
prerelease: ${{ contains(github.ref, '-alpha.') }}
deploy_api:
needs: [changes, supabase_deploy, read_replica_schema]
if: ${{ always() && needs.changes.result == 'success' && needs.changes.outputs.api == 'true' && (needs.supabase_deploy.result == 'success' || needs.supabase_deploy.result == 'skipped') && (needs.read_replica_schema.result == 'success' || needs.read_replica_schema.result == 'skipped') }}
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 24
- name: Setup bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies
run: bun install
- name: Set environment variable
run: |
if [[ ${{ github.ref }} == *-alpha* ]]; then
echo "ENV=dev" >> $GITHUB_ENV
else
echo "ENV=prod" >> $GITHUB_ENV
fi
- name: Deploy CF Worker API
run: bun run deploy:cloudflare:api:${{ env.ENV }}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
deploy_translation_worker:
needs: [changes, supabase_deploy, read_replica_schema]
if: ${{ always() && needs.changes.result == 'success' && needs.changes.outputs.translation == 'true' && (needs.supabase_deploy.result == 'success' || (needs.supabase_deploy.result == 'skipped' && needs.changes.outputs.supabase != 'true')) && (needs.read_replica_schema.result == 'success' || needs.read_replica_schema.result == 'skipped') }}
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 24
- name: Setup bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies
run: bun install
- name: Set environment variable
run: |
if [[ ${{ github.ref }} == *-alpha* ]]; then
echo "ENV=dev" >> $GITHUB_ENV
else
echo "ENV=prod" >> $GITHUB_ENV
fi
- name: Deploy CF Worker Translation
run: bun run deploy:cloudflare:translation:${{ env.ENV }}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
deploy_files:
needs: [changes, supabase_deploy, read_replica_schema]
if: ${{ always() && needs.changes.result == 'success' && needs.changes.outputs.files == 'true' && (needs.supabase_deploy.result == 'success' || needs.supabase_deploy.result == 'skipped') && (needs.read_replica_schema.result == 'success' || needs.read_replica_schema.result == 'skipped') }}
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 24
- name: Setup bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies
run: bun install
- name: Set environment variable
run: |
if [[ ${{ github.ref }} == *-alpha* ]]; then
echo "ENV=dev" >> $GITHUB_ENV
else
echo "ENV=prod" >> $GITHUB_ENV
fi
- name: Deploy CF Worker Files
run: bun run deploy:cloudflare:files:${{ env.ENV }}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
deploy_plugin_regions:
needs: [changes, supabase_deploy, read_replica_schema]
if: ${{ always() && needs.changes.result == 'success' && needs.changes.outputs.plugins == 'true' && (needs.supabase_deploy.result == 'success' || needs.supabase_deploy.result == 'skipped') && (needs.read_replica_schema.result == 'success' || needs.read_replica_schema.result == 'skipped') }}
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
region: [eu, as, us, sa, oc, af, me, hk, jp]
include:
- region: eu
region_label: Europe (EU)
cf_target: plugin_eu
- region: as
region_label: Asia (AS)
cf_target: plugin_as
- region: us
region_label: United States (US)
cf_target: plugin_na
- region: sa
region_label: South America (SA)
cf_target: plugin_sa
- region: oc
region_label: Oceania (OC)
cf_target: plugin_oc
- region: af
region_label: Africa (AF)
cf_target: plugin_af
- region: me
region_label: Middle East (ME)
cf_target: plugin_me
- region: hk
region_label: Hong Kong (HK)
cf_target: plugin_hk
- region: jp
region_label: Japan (JP)
cf_target: plugin_jp
name: Deploy CF Worker Plugin (${{ matrix.region_label }})
steps:
- name: Checkout
uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 24
- name: Setup bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies
run: bun install
- name: Set environment variable
run: |
if [[ ${{ github.ref }} == *-alpha* ]]; then
echo "ENV=dev" >> $GITHUB_ENV
else
echo "ENV=prod" >> $GITHUB_ENV
fi
- name: Deploy CF Worker Plugin
run: bun run deploy:cloudflare:${{ matrix.cf_target }}:${{ env.ENV }}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
deploy_native_ios:
needs: [changes, supabase_deploy, deploy_webapp]
if: ${{ always() && needs.changes.result == 'success' && (needs.supabase_deploy.result == 'success' || needs.supabase_deploy.result == 'skipped') && needs.deploy_webapp.result == 'success' && needs.deploy_webapp.outputs.native_build_needed == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: read
actions: write
steps:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24.x
- name: Checkout
uses: actions/checkout@v6
- name: Setup bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Set environment variable
run: |
if [[ ${{ github.ref }} == *-alpha* ]]; then
echo "BUILD_SCRIPT=build:dev" >> $GITHUB_ENV
else
echo "BUILD_SCRIPT=build" >> $GITHUB_ENV
fi
- name: Install dependencies
run: bun install --frozen-lockfile --linker=hoisted
- name: Prepare mobile project
run: |
bun run ${{ env.BUILD_SCRIPT }}
bunx cap sync ios
- name: Request iOS build through Capgo CLI
env:
CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }}
APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
CAPGO_IOS_PROVISIONING_MAP_BASE64: ${{ secrets.CAPGO_IOS_PROVISIONING_MAP_BASE64 }}
APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
BUILD_OUTPUT_UPLOAD_ENABLED: "true"
BUILD_OUTPUT_RETENTION_SECONDS: "604800"
run: bunx @capgo/cli@latest build request --platform ios --path . --ai-analytics
deploy_native_android:
needs: [changes, supabase_deploy, deploy_webapp]
if: ${{ always() && needs.changes.result == 'success' && (needs.supabase_deploy.result == 'success' || needs.supabase_deploy.result == 'skipped') && needs.deploy_webapp.result == 'success' && needs.deploy_webapp.outputs.native_build_needed == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: read
actions: write
steps:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24.x
- name: Checkout
uses: actions/checkout@v6
- name: Setup bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Set environment variable
run: |
if [[ ${{ github.ref }} == *-alpha* ]]; then
echo "BUILD_SCRIPT=build:dev" >> $GITHUB_ENV
else
echo "BUILD_SCRIPT=build" >> $GITHUB_ENV
fi
- name: Install dependencies
run: bun install --frozen-lockfile --linker=hoisted
- name: Prepare mobile project
run: |
bun run ${{ env.BUILD_SCRIPT }}
bunx cap sync android
- name: Request Android build through Capgo CLI
env:
CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }}
ANDROID_KEYSTORE_FILE: ${{ secrets.ANDROID_KEYSTORE_FILE }}
PLAY_CONFIG_JSON: ${{ secrets.PLAY_CONFIG_JSON }}
KEYSTORE_KEY_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }}
KEYSTORE_KEY_PASSWORD: ${{ secrets.KEYSTORE_KEY_PASSWORD }}
KEYSTORE_STORE_PASSWORD: ${{ secrets.KEYSTORE_STORE_PASSWORD }}
BUILD_OUTPUT_UPLOAD_ENABLED: "true"
BUILD_OUTPUT_RETENTION_SECONDS: "604800"
run: bunx @capgo/cli@latest build request --platform android --path . --ai-analytics