name: CI · Build

# Reusable stage 3 - build: produces the deployable artifacts. The server is built once as a
# Docker image tagged by commit SHA and pushed to GHCR on a push to main (build-once,
# deploy-many - the deploy then pulls that exact tag). PRs build the image to validate it
# without pushing. The web bundle is also built here. Called by ci.yml only for main-bound work;
# the caller's `if` disables it on feature branches (see ci.yml).

on:
  workflow_call:
    inputs:
      ui:
        description: "'true' to run the Storybook static build (skipped on PRs that cannot affect it, #788)"
        type: string
        default: 'true'

permissions:
  contents: read
  packages: write

jobs:
  build:
    name: Build
    runs-on: bloom-arc
    steps:
      - uses: actions/checkout@v4

      # Build-once: push the SHA-tagged server image to GHCR only on a push to main - the
      # deployable event; PRs build it to validate without pushing. The deploy pulls this exact
      # tag instead of rebuilding from source.
      - name: Compute image reference (short commit SHA)
        id: img
        run: |
          echo "server_image=ghcr.io/${GITHUB_REPOSITORY,,}-server" >> "$GITHUB_OUTPUT"
          echo "tag=sha-${GITHUB_SHA::12}" >> "$GITHUB_OUTPUT"

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GHCR
        if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # The TypeScript backend (apps/server, M29). Root context: the image is built from the
      # pnpm workspace; the per-Dockerfile dockerignore keeps the context to just its inputs.
      - name: Build the server image (push on main)
        uses: docker/build-push-action@v6
        with:
          context: .
          file: apps/server/Dockerfile
          push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
          tags: |
            ${{ steps.img.outputs.server_image }}:${{ steps.img.outputs.tag }}
            ${{ steps.img.outputs.server_image }}:latest
          cache-from: type=gha,scope=server
          cache-to: type=gha,mode=max,scope=server

      - uses: pnpm/action-setup@v4
        with:
          version: 10.6.3

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
          cache-dependency-path: pnpm-lock.yaml

      # Workspace-root install (single lockfile, #481); each SPA builds in place so the output
      # paths (apps/*/dist) are unchanged.
      - name: Install web workspace dependencies
        run: pnpm install --frozen-lockfile

      - name: Build web
        working-directory: apps/web
        run: pnpm run build

      - name: Build admin
        working-directory: apps/admin
        run: pnpm run build

      - name: Build server
        working-directory: apps/server
        run: pnpm run build

      # Non-deploying check (M28-1): the @bloom/ui Storybook static build must stay green.
      # Publishing it (Cloudflare Pages) is a later M28 slice - nothing here deploys.
      - name: Build Storybook (packages/ui)
        if: ${{ inputs.ui == 'true' }}
        working-directory: packages/ui
        run: pnpm run build-storybook
