Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
274 changes: 274 additions & 0 deletions .github/workflows/bootstrap.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
name: Bootstrap GCP Infrastructure

# One-time workflow to create foundational GCP resources that Terraform and
# CI/CD depend on. After successful bootstrap, WIF replaces the service
# account key for all subsequent workflows.
#
# Prerequisites (manual, one-time):
# 1. Create a GCP project and enable billing
# 2. Create a service account with Owner role
# 3. Create a JSON key and add it as GitHub secret: GCP_SERVICE_ACCOUNT
#
# After bootstrap completes, GCP_SERVICE_ACCOUNT is overwritten with the
# WIF service account email — the JSON key is no longer needed.

on:
workflow_dispatch:
inputs:
project_id:
description: "GCP project ID"
required: true
region:
description: "GCP region"
required: true
default: "us-central1"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
google_sign_in_client_id:
description: "Google OAuth client ID (leave empty on first run, set on phase 2)"
required: false
default: ""

permissions:
contents: read

jobs:
bootstrap:
name: Bootstrap GCP
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Authenticate to Google Cloud
uses: google-github-actions/auth@62cf5bd3e4211a0a0b51f2c6d6a37129d828611d # v2
with:
credentials_json: ${{ secrets.GCP_SERVICE_ACCOUNT }}

- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@6189d56e4096ee891640bb02ac264be376592d6a # v2

- name: Enable required APIs
run: |
gcloud services enable \
cloudresourcemanager.googleapis.com \
iam.googleapis.com \
iamcredentials.googleapis.com \
storage.googleapis.com \
artifactregistry.googleapis.com \
run.googleapis.com \
sqladmin.googleapis.com \
secretmanager.googleapis.com \
firebase.googleapis.com \
identitytoolkit.googleapis.com \
pubsub.googleapis.com \
aiplatform.googleapis.com \
--project="${{ inputs.project_id }}"

Check failure on line 64 in .github/workflows/bootstrap.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

inputs.project_id is vulnerable to script injection: values of inputs are provided by whoever triggers the workflow. Change this workflow to not use user-controlled data directly in a run block, for example by assigning this expression to an environment variable.

See more on https://sonarcloud.io/project/issues?id=petry-projects_broodly&issues=AZ1I5NXj9DoLcXMmefpI&open=AZ1I5NXj9DoLcXMmefpI&pullRequest=51

- name: Create Terraform state bucket
run: |
BUCKET="broodly-terraform-state"
if gcloud storage buckets describe "gs://${BUCKET}" --project="${{ inputs.project_id }}" > /dev/null 2>&1; then

Check failure on line 69 in .github/workflows/bootstrap.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

inputs.project_id is vulnerable to script injection: values of inputs are provided by whoever triggers the workflow. Change this workflow to not use user-controlled data directly in a run block, for example by assigning this expression to an environment variable.

See more on https://sonarcloud.io/project/issues?id=petry-projects_broodly&issues=AZ1I5NXj9DoLcXMmefpJ&open=AZ1I5NXj9DoLcXMmefpJ&pullRequest=51
echo "State bucket gs://${BUCKET} already exists"
else
gcloud storage buckets create "gs://${BUCKET}" \
--project="${{ inputs.project_id }}" \

Check failure on line 73 in .github/workflows/bootstrap.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

inputs.project_id is vulnerable to script injection: values of inputs are provided by whoever triggers the workflow. Change this workflow to not use user-controlled data directly in a run block, for example by assigning this expression to an environment variable.

See more on https://sonarcloud.io/project/issues?id=petry-projects_broodly&issues=AZ1I5NXj9DoLcXMmefpK&open=AZ1I5NXj9DoLcXMmefpK&pullRequest=51
--location="${{ inputs.region }}" \

Check failure on line 74 in .github/workflows/bootstrap.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

inputs.region is vulnerable to script injection: values of inputs are provided by whoever triggers the workflow. Change this workflow to not use user-controlled data directly in a run block, for example by assigning this expression to an environment variable.

See more on https://sonarcloud.io/project/issues?id=petry-projects_broodly&issues=AZ1I5NXj9DoLcXMmefpL&open=AZ1I5NXj9DoLcXMmefpL&pullRequest=51
--uniform-bucket-level-access \
--public-access-prevention
echo "Created state bucket gs://${BUCKET}"
fi

# Enable versioning for state file safety
gcloud storage buckets update "gs://${BUCKET}" --versioning

- name: Create Artifact Registry repository
run: |
REPO="broodly"
if gcloud artifacts repositories describe "${REPO}" \
--project="${{ inputs.project_id }}" \

Check failure on line 87 in .github/workflows/bootstrap.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

inputs.project_id is vulnerable to script injection: values of inputs are provided by whoever triggers the workflow. Change this workflow to not use user-controlled data directly in a run block, for example by assigning this expression to an environment variable.

See more on https://sonarcloud.io/project/issues?id=petry-projects_broodly&issues=AZ1I5NXj9DoLcXMmefpM&open=AZ1I5NXj9DoLcXMmefpM&pullRequest=51
--location="${{ inputs.region }}" > /dev/null 2>&1; then

Check failure on line 88 in .github/workflows/bootstrap.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

inputs.region is vulnerable to script injection: values of inputs are provided by whoever triggers the workflow. Change this workflow to not use user-controlled data directly in a run block, for example by assigning this expression to an environment variable.

See more on https://sonarcloud.io/project/issues?id=petry-projects_broodly&issues=AZ1I5NXj9DoLcXMmefpN&open=AZ1I5NXj9DoLcXMmefpN&pullRequest=51
echo "Artifact Registry repo ${REPO} already exists"
else
gcloud artifacts repositories create "${REPO}" \
--project="${{ inputs.project_id }}" \

Check failure on line 92 in .github/workflows/bootstrap.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

inputs.project_id is vulnerable to script injection: values of inputs are provided by whoever triggers the workflow. Change this workflow to not use user-controlled data directly in a run block, for example by assigning this expression to an environment variable.

See more on https://sonarcloud.io/project/issues?id=petry-projects_broodly&issues=AZ1I5NXj9DoLcXMmefpO&open=AZ1I5NXj9DoLcXMmefpO&pullRequest=51
--location="${{ inputs.region }}" \

Check failure on line 93 in .github/workflows/bootstrap.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

inputs.region is vulnerable to script injection: values of inputs are provided by whoever triggers the workflow. Change this workflow to not use user-controlled data directly in a run block, for example by assigning this expression to an environment variable.

See more on https://sonarcloud.io/project/issues?id=petry-projects_broodly&issues=AZ1I5NXj9DoLcXMmefpP&open=AZ1I5NXj9DoLcXMmefpP&pullRequest=51
--repository-format=docker \
--description="Broodly container images"
echo "Created Artifact Registry repo ${REPO}"
fi

- name: Set up Workload Identity Federation
id: wif
run: |
PROJECT_ID="${{ inputs.project_id }}"

Check failure on line 102 in .github/workflows/bootstrap.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

inputs.project_id is vulnerable to script injection: values of inputs are provided by whoever triggers the workflow. Change this workflow to not use user-controlled data directly in a run block, for example by assigning this expression to an environment variable.

See more on https://sonarcloud.io/project/issues?id=petry-projects_broodly&issues=AZ1I5NXj9DoLcXMmefpQ&open=AZ1I5NXj9DoLcXMmefpQ&pullRequest=51
PROJECT_NUMBER=$(gcloud projects describe "${PROJECT_ID}" --format="value(projectNumber)")
POOL_NAME="github-actions"
PROVIDER_NAME="github"
SA_NAME="github-actions-ci"
SA_EMAIL="${SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
REPO="${{ github.repository }}"

# Create CI service account (if not exists)
if gcloud iam service-accounts describe "${SA_EMAIL}" --project="${PROJECT_ID}" > /dev/null 2>&1; then
echo "Service account ${SA_EMAIL} already exists"
else
gcloud iam service-accounts create "${SA_NAME}" \
--project="${PROJECT_ID}" \
--display-name="GitHub Actions CI/CD"
fi

# Grant necessary roles to CI service account
for role in \
roles/run.admin \
roles/artifactregistry.writer \
roles/iam.serviceAccountUser \
roles/storage.admin \
roles/firebase.admin \
roles/secretmanager.admin \
roles/cloudsql.admin \
roles/pubsub.admin; do
gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
--member="serviceAccount:${SA_EMAIL}" \
--role="${role}" \
--condition=None \
--quiet 2>/dev/null || true
done

# Create WIF pool (if not exists)
if gcloud iam workload-identity-pools describe "${POOL_NAME}" \
--project="${PROJECT_ID}" \
--location="global" > /dev/null 2>&1; then
echo "WIF pool ${POOL_NAME} already exists"
else
gcloud iam workload-identity-pools create "${POOL_NAME}" \
--project="${PROJECT_ID}" \
--location="global" \
--display-name="GitHub Actions"
fi

# Create WIF provider (if not exists)
if gcloud iam workload-identity-pools providers describe "${PROVIDER_NAME}" \
--project="${PROJECT_ID}" \
--location="global" \
--workload-identity-pool="${POOL_NAME}" > /dev/null 2>&1; then
echo "WIF provider ${PROVIDER_NAME} already exists"
else
gcloud iam workload-identity-pools providers create-oidc "${PROVIDER_NAME}" \
--project="${PROJECT_ID}" \
--location="global" \
--workload-identity-pool="${POOL_NAME}" \
--display-name="GitHub" \
--attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
--issuer-uri="https://token.actions.githubusercontent.com"
fi

# Allow GitHub repo to impersonate the CI service account
WIF_PROVIDER="projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_NAME}/providers/${PROVIDER_NAME}"
gcloud iam service-accounts add-iam-policy-binding "${SA_EMAIL}" \
--project="${PROJECT_ID}" \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_NAME}/attribute.repository/${REPO}" \
--quiet

# Output values for GitHub secrets
echo "wif_provider=${WIF_PROVIDER}" >> "$GITHUB_OUTPUT"
echo "service_account=${SA_EMAIL}" >> "$GITHUB_OUTPUT"
echo ""
echo "============================================"
echo " Workload Identity Federation configured"
echo "============================================"
echo " Provider: ${WIF_PROVIDER}"
echo " Service Account: ${SA_EMAIL}"
echo ""

- name: Set GitHub secrets via gh CLI
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Set the WIF secrets that deploy-api and terraform jobs need.
# GCP_SERVICE_ACCOUNT is overwritten: JSON key → WIF SA email.
gh secret set GCP_PROJECT_ID --body "${{ inputs.project_id }}"

Check failure on line 189 in .github/workflows/bootstrap.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

inputs.project_id is vulnerable to script injection: values of inputs are provided by whoever triggers the workflow. Change this workflow to not use user-controlled data directly in a run block, for example by assigning this expression to an environment variable.

See more on https://sonarcloud.io/project/issues?id=petry-projects_broodly&issues=AZ1I5NXj9DoLcXMmefpR&open=AZ1I5NXj9DoLcXMmefpR&pullRequest=51
gh secret set GCP_WORKLOAD_IDENTITY_PROVIDER --body "${{ steps.wif.outputs.wif_provider }}"
gh secret set GCP_SERVICE_ACCOUNT --body "${{ steps.wif.outputs.service_account }}"

echo ""
echo "============================================"
echo " GitHub secrets configured"
echo "============================================"
echo " GCP_PROJECT_ID"
echo " GCP_WORKLOAD_IDENTITY_PROVIDER"
echo " GCP_SERVICE_ACCOUNT (overwritten with WIF SA email)"
echo ""
echo "All future workflows use Workload Identity Federation."
echo "The original JSON key in GCP_SERVICE_ACCOUNT has been replaced."

- name: Run Terraform init and apply
run: |
cd infra/terraform/environments/dev

# Write tfvars — includes google_sign_in_client_id when provided
{
printf 'project_id = "%s"\n' "${{ inputs.project_id }}"

Check failure on line 210 in .github/workflows/bootstrap.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

inputs.project_id is vulnerable to script injection: values of inputs are provided by whoever triggers the workflow. Change this workflow to not use user-controlled data directly in a run block, for example by assigning this expression to an environment variable.

See more on https://sonarcloud.io/project/issues?id=petry-projects_broodly&issues=AZ1I5NXj9DoLcXMmefpS&open=AZ1I5NXj9DoLcXMmefpS&pullRequest=51
printf 'region = "%s"\n' "${{ inputs.region }}"

Check failure on line 211 in .github/workflows/bootstrap.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

inputs.region is vulnerable to script injection: values of inputs are provided by whoever triggers the workflow. Change this workflow to not use user-controlled data directly in a run block, for example by assigning this expression to an environment variable.

See more on https://sonarcloud.io/project/issues?id=petry-projects_broodly&issues=AZ1I5NXj9DoLcXMmefpT&open=AZ1I5NXj9DoLcXMmefpT&pullRequest=51
printf 'environment = "dev"\n'
if [ -n "${{ inputs.google_sign_in_client_id }}" ]; then

Check failure on line 213 in .github/workflows/bootstrap.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

inputs.google_sign_in_client_id is vulnerable to script injection: values of inputs are provided by whoever triggers the workflow. Change this workflow to not use user-controlled data directly in a run block, for example by assigning this expression to an environment variable.

See more on https://sonarcloud.io/project/issues?id=petry-projects_broodly&issues=AZ1I5NXj9DoLcXMmefpU&open=AZ1I5NXj9DoLcXMmefpU&pullRequest=51
printf 'google_sign_in_client_id = "%s"\n' "${{ inputs.google_sign_in_client_id }}"

Check failure on line 214 in .github/workflows/bootstrap.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

inputs.google_sign_in_client_id is vulnerable to script injection: values of inputs are provided by whoever triggers the workflow. Change this workflow to not use user-controlled data directly in a run block, for example by assigning this expression to an environment variable.

See more on https://sonarcloud.io/project/issues?id=petry-projects_broodly&issues=AZ1I5NXj9DoLcXMmefpV&open=AZ1I5NXj9DoLcXMmefpV&pullRequest=51
fi
} > terraform.tfvars

terraform init
terraform apply -auto-approve

- name: Export Firebase config to GitHub secrets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
cd infra/terraform/environments/dev

set_secret() {
local tf_output="$1"
local secret_name="$2"
local value
value=$(terraform output -raw "${tf_output}" 2>/dev/null) || value=""
if [ -n "${value}" ]; then
gh secret set "${secret_name}" --body "${value}"
echo " SET ${secret_name}"
else
echo " SKIP ${secret_name} (empty)"
fi
}

echo "Setting Firebase secrets..."
set_secret "firebase_api_key" "EXPO_PUBLIC_FIREBASE_API_KEY"
set_secret "firebase_auth_domain" "EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN"
set_secret "firebase_project_id" "EXPO_PUBLIC_FIREBASE_PROJECT_ID"
set_secret "firebase_storage_bucket" "EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET"
set_secret "firebase_messaging_sender_id" "EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID"
set_secret "firebase_app_id" "EXPO_PUBLIC_FIREBASE_APP_ID"

# Cloud Run URL for API
set_secret "cloud_run_url" "EXPO_PUBLIC_API_URL"

echo ""
echo "Bootstrap complete! All secrets are configured."

- name: Summary
run: |
echo "## Bootstrap Complete" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "### Resources Created" >> "$GITHUB_STEP_SUMMARY"
echo "- GCS state bucket: \`broodly-terraform-state\`" >> "$GITHUB_STEP_SUMMARY"
echo "- Artifact Registry: \`broodly\`" >> "$GITHUB_STEP_SUMMARY"
echo "- Workload Identity Federation pool + provider" >> "$GITHUB_STEP_SUMMARY"
echo "- CI service account: \`github-actions-ci@${{ inputs.project_id }}.iam.gserviceaccount.com\`" >> "$GITHUB_STEP_SUMMARY"

Check failure on line 262 in .github/workflows/bootstrap.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

inputs.project_id is vulnerable to script injection: values of inputs are provided by whoever triggers the workflow. Change this workflow to not use user-controlled data directly in a run block, for example by assigning this expression to an environment variable.

See more on https://sonarcloud.io/project/issues?id=petry-projects_broodly&issues=AZ1I5NXj9DoLcXMmefpW&open=AZ1I5NXj9DoLcXMmefpW&pullRequest=51
echo "- All Terraform-managed resources (Cloud SQL, Firebase, Cloud Run, etc.)" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "### GitHub Secrets Set" >> "$GITHUB_STEP_SUMMARY"
echo "- \`GCP_PROJECT_ID\`, \`GCP_WORKLOAD_IDENTITY_PROVIDER\`, \`GCP_SERVICE_ACCOUNT\`" >> "$GITHUB_STEP_SUMMARY"
echo "- \`EXPO_PUBLIC_FIREBASE_*\` (6 secrets)" >> "$GITHUB_STEP_SUMMARY"
echo "- \`EXPO_PUBLIC_API_URL\`" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "### Next Steps" >> "$GITHUB_STEP_SUMMARY"
echo "1. **Delete the bootstrap service account** in GCP Console (no longer needed)" >> "$GITHUB_STEP_SUMMARY"
echo "2. Enable Google Sign-In in Firebase Console → Authentication → Sign-in method" >> "$GITHUB_STEP_SUMMARY"
echo "3. Retrieve the auto-created OAuth client ID from GCP Console → Credentials" >> "$GITHUB_STEP_SUMMARY"
echo "4. Re-run this workflow with \`google_sign_in_client_id\` to complete auth setup" >> "$GITHUB_STEP_SUMMARY"
103 changes: 86 additions & 17 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,26 +156,17 @@ jobs:
run: docker build -f apps/api/Dockerfile -t broodly-api:ci apps/api/

# ---------------------------------------------------------------------------
# Push API image to Artifact Registry (main branch only)
# Deploy API to Cloud Run (main branch only)
# ---------------------------------------------------------------------------
# The Cloud Run service is managed by Terraform (infra/terraform/modules/cloud-run/).
# This job builds and pushes the image; Terraform references the :latest tag.
# To deploy a new version: push to main (image pushed here), then run terraform apply.
#
# Required GitHub Secrets:
# GCP_PROJECT_ID — GCP project ID (e.g. broodly-491920)
# GCP_WORKLOAD_IDENTITY_PROVIDER — Full provider resource name:
# projects/<number>/locations/global/workloadIdentityPools/<pool>/providers/<provider>
# GCP_SERVICE_ACCOUNT — SA email for WIF (e.g. broodly-ci@broodly-491920.iam.gserviceaccount.com)
push-api-image:
name: Push API Image
deploy-api:
name: Deploy API
runs-on: ubuntu-latest
timeout-minutes: 15
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: [go]
permissions:
contents: read
id-token: write
id-token: write # Required for Workload Identity Federation
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

Expand All @@ -191,7 +182,85 @@ jobs:
- name: Build and push container image
working-directory: .
run: |
IMAGE_BASE="us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/broodly/api"
docker build -f apps/api/Dockerfile -t "${IMAGE_BASE}:${{ github.sha }}" -t "${IMAGE_BASE}:latest" apps/api/
docker push "${IMAGE_BASE}:${{ github.sha }}"
docker push "${IMAGE_BASE}:latest"
IMAGE="us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/broodly/api"
docker build -f apps/api/Dockerfile \
-t "${IMAGE}:${{ github.sha }}" \
-t "${IMAGE}:latest" \
apps/api/
docker push "${IMAGE}:${{ github.sha }}"
docker push "${IMAGE}:latest"

# Service name and region must match Terraform cloud-run module config
# in infra/terraform/environments/dev/main.tf
- name: Deploy to Cloud Run
env:
CLOUD_RUN_SERVICE: broodly-api-dev
CLOUD_RUN_REGION: us-central1
run: |
gcloud run deploy "${CLOUD_RUN_SERVICE}" \
--image "us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/broodly/api:${{ github.sha }}" \
--region "${CLOUD_RUN_REGION}" \
--project "${{ secrets.GCP_PROJECT_ID }}" \
--quiet

# ---------------------------------------------------------------------------
# Mobile Web Build (main branch only)
# ---------------------------------------------------------------------------
mobile-build:
name: Mobile Build
runs-on: ubuntu-latest
timeout-minutes: 20
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: [typescript]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4

- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 20
cache: pnpm

- run: pnpm install --frozen-lockfile

- name: Validate required secrets
env:
FIREBASE_API_KEY: ${{ secrets.EXPO_PUBLIC_FIREBASE_API_KEY }}
FIREBASE_PROJECT_ID: ${{ secrets.EXPO_PUBLIC_FIREBASE_PROJECT_ID }}
API_URL: ${{ secrets.EXPO_PUBLIC_API_URL }}
run: |
missing=()
[ -z "${FIREBASE_API_KEY}" ] && missing+=("EXPO_PUBLIC_FIREBASE_API_KEY")
[ -z "${FIREBASE_PROJECT_ID}" ] && missing+=("EXPO_PUBLIC_FIREBASE_PROJECT_ID")
[ -z "${API_URL}" ] && missing+=("EXPO_PUBLIC_API_URL")
if [ ${#missing[@]} -gt 0 ]; then
echo "::error::Missing required secrets: ${missing[*]}"
echo "Run the Bootstrap workflow or scripts/bootstrap-secrets.sh first."
exit 1
fi

- name: Generate .env from secrets
working-directory: apps/mobile
run: |
{
echo "EXPO_PUBLIC_FIREBASE_USE_EMULATOR=false"
echo "EXPO_PUBLIC_FIREBASE_API_KEY=${{ secrets.EXPO_PUBLIC_FIREBASE_API_KEY }}"
echo "EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN=${{ secrets.EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN }}"
echo "EXPO_PUBLIC_FIREBASE_PROJECT_ID=${{ secrets.EXPO_PUBLIC_FIREBASE_PROJECT_ID }}"
echo "EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET=${{ secrets.EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET }}"
echo "EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=${{ secrets.EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }}"
echo "EXPO_PUBLIC_FIREBASE_APP_ID=${{ secrets.EXPO_PUBLIC_FIREBASE_APP_ID }}"
echo "EXPO_PUBLIC_API_URL=${{ secrets.EXPO_PUBLIC_API_URL }}"
} > .env

- name: Expo web export
working-directory: apps/mobile
run: npx expo export --platform web

- name: Upload web build artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: web-build
path: apps/mobile/dist/
retention-days: 14
Loading
Loading