name: GitHub-hosted static site build and deploy

# Primary build+deploy path for the HashPass development target (replaces the
# AWS `hashpass-dev-site` CodePipeline as the day-to-day trigger -- see
# .agents/active/task-build-cost-containment-and-cicd-migration.md). The AWS
# pipeline is manual-only after the 2026-09-21 cost-containment cutover.
# Use start-web-pipeline-disaster-recovery.sh for an explicit recovery build.
on:
  push:
    branches: [develop]
    # Mirrors local.site_trigger_includes in
    # packages/infra/terraform/stacks/hashpass-web/main.tf (the AWS pipeline's
    # own trigger include list) so this workflow watches the same inputs.
    # GitHub Actions cannot combine `paths` with `paths-ignore` on one
    # trigger, so the AWS side's narrower excludes aren't mirrored here --
    # GitHub-hosted runners are free for this public repo, so an occasional
    # extra build on an excluded path costs nothing.
    paths:
      - 'apps/mobile-app/**'
      - 'apps/web-app/**'
      - 'packages/**'
      - 'package.json'
      - 'pnpm-lock.yaml'
      - 'pnpm-workspace.yaml'
      - 'db/migrations/**'
      - 'db/migrations-mobile-bootstrap/**'
      - 'packages/tools/scripts/config/database-profiles.json'
      - 'packages/tools/scripts/migrate-tenant-db.mjs'
      - 'packages/tools/scripts/db-backup/**'
      - '.github/actions/apply-core-db-migrations/**'
  workflow_dispatch:
    inputs:
      deploy:
        description: Deploy the development artifact (build-only by default)
        required: true
        default: false
        type: boolean

permissions:
  contents: read

jobs:
  build:
    name: Build static site (development)
    runs-on: ubuntu-latest
    # A rapid follow-up push should cancel a stale in-flight build rather than
    # waste runner time on both -- this is the free build-only path, unlike
    # the deploy job below which must never be cancelled mid-flight.
    concurrency:
      group: static-site-build-development-${{ github.ref }}
      cancel-in-progress: true
    env:
      BUILD_ENV: dev
      PNPM_STORE_DIR: ${{ github.workspace }}/.pnpm-store
      EXPO_PUBLIC_SUPABASE_URL: ${{ vars.EXPO_PUBLIC_SUPABASE_URL_DEV }}
      EXPO_PUBLIC_SUPABASE_ANON_KEY: ${{ vars.EXPO_PUBLIC_SUPABASE_ANON_KEY_DEV }}
      EXPO_PUBLIC_SUPABASE_KEY: ${{ vars.EXPO_PUBLIC_SUPABASE_ANON_KEY_DEV }}
      NEXT_PUBLIC_SUPABASE_URL: ${{ vars.NEXT_PUBLIC_SUPABASE_URL_DEV }}
      NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ vars.NEXT_PUBLIC_SUPABASE_ANON_KEY_DEV }}
      NEXT_PUBLIC_LINKS_API_BASE_URL: ${{ vars.NEXT_PUBLIC_LINKS_API_BASE_URL_DEV }}
      EXPO_PUBLIC_BSL_SUPABASE_URL: ${{ vars.EXPO_PUBLIC_BSL_SUPABASE_URL_DEV }}
      EXPO_PUBLIC_BSL_SUPABASE_KEY: ${{ vars.EXPO_PUBLIC_BSL_SUPABASE_KEY_DEV }}
      # Same site id everywhere (see apps/mobile-app/app/+html.tsx) -- no _DEV
      # suffix needed, unlike the Supabase vars above.
      EXPO_PUBLIC_CIG_ANALYTICS_SITE_ID: ${{ vars.EXPO_PUBLIC_CIG_ANALYTICS_SITE_ID }}
      NODE_MAX_OLD_SPACE_SIZE: '12288'
      EXPO_EXPORT_MAX_WORKERS: '1'
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Record build start
        id: build-start
        run: echo "epoch=$(date +%s)" >> "$GITHUB_OUTPUT"

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9.15.5

      # setup-node must cache the same store used by build-static-site.sh.
      - name: Configure pnpm store
        run: pnpm config set store-dir "$PNPM_STORE_DIR"

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version-file: '.nvmrc'
          cache: pnpm

      - name: Build the static site
        id: build
        run: bash packages/tools/scripts/build-static-site.sh

      - name: Record build evidence
        if: always()
        env:
          BUILD_OUTCOME: ${{ steps.build.outcome }}
          BUILD_STARTED_AT: ${{ steps.build-start.outputs.epoch }}
        run: |
          set -euo pipefail
          finished_at=$(date +%s)
          duration_seconds=$((finished_at - BUILD_STARTED_AT))
          artifact_bytes=0
          artifact_sha256=""

          if [[ -d dist/client ]]; then
            artifact_bytes=$(du -sb dist/client | awk '{print $1}')
            artifact_sha256=$(find dist/client -type f -print0 | sort -z | xargs -0 sha256sum | sha256sum | awk '{print $1}')
          fi

          jq -n \
            --arg workflow "${{ github.workflow }}" \
            --arg run_url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
            --arg commit "${{ github.sha }}" \
            --arg trigger "${{ github.event_name }}" \
            --arg build_outcome "$BUILD_OUTCOME" \
            --argjson duration_seconds "$duration_seconds" \
            --argjson artifact_bytes "$artifact_bytes" \
            --arg artifact_sha256 "$artifact_sha256" \
            '{workflow: $workflow, runUrl: $run_url, commit: $commit, trigger: $trigger, buildOutcome: $build_outcome, durationSeconds: $duration_seconds, artifactBytes: $artifact_bytes, artifactSha256: $artifact_sha256}' \
            > static-site-build-evidence.json

          {
            echo "## GitHub-hosted static-site build"
            echo
            echo "- Run: [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
            echo "- Commit: \`${{ github.sha }}\`"
            echo "- Trigger: \`${{ github.event_name }}\`"
            echo "- Build: \`$BUILD_OUTCOME\` in \`${duration_seconds}s\`"
            echo "- Artifact: \`${artifact_bytes} bytes\`"
            echo "- Artifact SHA-256: \`${artifact_sha256:-not-created}\`"
            echo "- Deployment requested: \`${{ inputs.deploy || (github.event_name == 'push') }}\`"
          } >> "$GITHUB_STEP_SUMMARY"

      - name: Upload static site artifact
        uses: actions/upload-artifact@v4
        with:
          name: static-site-development
          # Must include dist/server, not just dist/client: package-lambda.sh
          # prefers dist/server (the expo export server output, which is
          # where Better Auth's [...auth]+api route and other API routes
          # live) and only falls back to dist/client if it's absent. An
          # earlier version of this workflow uploaded dist/client only,
          # which made the Lambda packaging step silently fall back to a
          # client-only export missing the API routes entirely (caught in
          # the first real observed deploy, run 33899558488, 2026-09-04).
          path: dist
          # upload-artifact@v4 skips dot-directories by default, which silently dropped
          # server/_expo/functions/.well-known/ (RFC 8414 OAuth discovery) from the Lambda.
          include-hidden-files: true
          if-no-files-found: error
          retention-days: 1

      - name: Upload build evidence
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: static-site-development-build-evidence
          path: static-site-build-evidence.json
          if-no-files-found: error
          retention-days: 14

  migrate-database:
    name: Migrate core database (development)
    if: github.event_name == 'push' || inputs.deploy == true
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4
      - name: Apply core development migrations
        uses: ./.github/actions/apply-core-db-migrations
        with:
          profile: core-development
          database-url: ${{ secrets.SUPABASE_DB_URL_DEV }}

  deploy:
    name: Deploy static site (development)
    needs: [build, migrate-database]
    # Automatic push runs are the real replacement for the AWS pipeline and
    # must deploy, not just build. Manual dispatches keep the opt-in checkbox.
    if: github.event_name == 'push' || inputs.deploy == true
    # Build-only trials must not interrupt an in-progress deployment. Queue
    # deploy-capable runs instead of cancelling an active S3/Lambda update.
    concurrency:
      group: static-site-deploy-development
      cancel-in-progress: false
    runs-on: ubuntu-latest
    environment: development
    permissions:
      contents: read
      id-token: write
    env:
      AWS_REGION: us-east-2
      SITE_BUILD_DIR: dist/client
      SITE_BUCKET_NAME: dev.hashpass.tech
      SITE_CLOUDFRONT_DISTRIBUTION_ID: E1Y06KG5U6MKCS
      SITE_LAMBDA_FUNCTION_NAME: hashpass-dev-expo-router-api
      SITE_LAMBDA_REGION: us-east-1
      SITE_LAMBDA_DEPLOYMENT_ENVIRONMENT: dev
      SITE_LAMBDA_DEPLOYMENT_BUCKET_REGION: us-east-1
      SITE_API_VERSION_URL: https://api-dev.hashpass.tech/api/config/versions
    steps:
      - name: Checkout deployment scripts
        uses: actions/checkout@v4

      - name: Download verified static site artifact
        uses: actions/download-artifact@v4
        with:
          name: static-site-development
          path: dist

      - name: Validate deployment role configuration
        env:
          ROLE_ARN: ${{ vars.AWS_STATIC_SITE_DEPLOY_ROLE_ARN }}
        run: |
          set -euo pipefail
          test -n "$ROLE_ARN" || {
            echo "AWS_STATIC_SITE_DEPLOY_ROLE_ARN is not configured for this repository."
            exit 1
          }

      - name: Configure AWS credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ vars.AWS_STATIC_SITE_DEPLOY_ROLE_ARN }}
          aws-region: ${{ env.AWS_REGION }}
          role-session-name: hashpass-static-site-development

      - name: Deploy the verified static site
        id: deploy
        run: bash packages/tools/scripts/deploy-static-site.sh

      - name: Record deployment evidence
        if: always()
        env:
          BUILD_RESULT: ${{ needs.build.result }}
          DEPLOY_OUTCOME: ${{ steps.deploy.outcome }}
        run: |
          set -euo pipefail
          jq -n \
            --arg run_url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
            --arg commit "${{ github.sha }}" \
            --arg trigger "${{ github.event_name }}" \
            --arg build_result "$BUILD_RESULT" \
            --arg deploy_outcome "$DEPLOY_OUTCOME" \
            '{runUrl: $run_url, commit: $commit, trigger: $trigger, buildResult: $build_result, deployOutcome: $deploy_outcome}' \
            > static-site-deployment-evidence.json

          {
            echo "## GitHub-hosted static-site deployment"
            echo
            echo "- Run: [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
            echo "- Commit: \`${{ github.sha }}\`"
            echo "- Trigger: \`${{ github.event_name }}\`"
            echo "- Build result: \`$BUILD_RESULT\`"
            echo "- Deploy result: \`$DEPLOY_OUTCOME\`"
            echo "- Target: \`development\`"
          } >> "$GITHUB_STEP_SUMMARY"

      - name: Upload deployment evidence
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: static-site-development-deployment-evidence
          path: static-site-deployment-evidence.json
          if-no-files-found: error
          retention-days: 14
