name: Blacksmith Build Artifacts Testbox

on:
  workflow_dispatch:
    inputs:
      testbox_id:
        type: string
        description: "Testbox session ID"
        required: true
  pull_request:
    paths:
      - ".github/workflows/**"

permissions:
  contents: read

env:
  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"

jobs:
  build-artifacts:
    if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
    permissions:
      contents: read
    name: "build-artifacts"
    runs-on: blacksmith-8vcpu-ubuntu-2404
    timeout-minutes: 35
    steps:
      - name: Begin Testbox
        uses: useblacksmith/begin-testbox@233448af4bfdc6fca509a7f0974411ac6d8a8043
        with:
          testbox_id: ${{ inputs.testbox_id }}

      - name: Checkout
        shell: bash
        env:
          CHECKOUT_REPO: ${{ github.repository }}
          CHECKOUT_SHA: ${{ github.sha }}
          CHECKOUT_TOKEN: ${{ github.token }}
        run: |
          set -euo pipefail

          workdir="$GITHUB_WORKSPACE"
          if [[ -z "$CHECKOUT_TOKEN" ]]; then
            echo "checkout token is missing" >&2
            exit 1
          fi
          auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"

          reset_checkout_dir() {
            mkdir -p "$workdir"
            find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
          }

          checkout_attempt() {
            local attempt="$1"

            reset_checkout_dir
            git init "$workdir" >/dev/null
            git config --global --add safe.directory "$workdir"
            git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
            git -C "$workdir" config gc.auto 0

            timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
              -c protocol.version=2 \
              -c "http.extraheader=AUTHORIZATION: basic ${auth_header}" \
              fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
              "+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1

            git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
            test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
            echo "checkout attempt ${attempt}/5 succeeded"
          }

          for attempt in 1 2 3 4 5; do
            if checkout_attempt "$attempt"; then
              exit 0
            fi
            echo "checkout attempt ${attempt}/5 failed"
            sleep $((attempt * 5))
          done

          echo "checkout failed after 5 attempts" >&2
          exit 1

      - name: Setup Node environment
        uses: ./.github/actions/setup-node-env
        with:
          install-bun: "false"

      - name: Resolve release dist cache seeds
        id: dist-cache-seeds
        shell: bash
        run: |
          set -euo pipefail

          cache_prefix="${RUNNER_OS}-dist-build-"
          declare -A seen=()

          resolve_tag_sha() {
            local tag="$1"
            local direct=""
            local peeled=""

            while read -r sha ref; do
              if [[ "$ref" == "refs/tags/${tag}^{}" ]]; then
                peeled="$sha"
              elif [[ "$ref" == "refs/tags/${tag}" ]]; then
                direct="$sha"
              fi
            done < <(git ls-remote --tags origin "refs/tags/${tag}" "refs/tags/${tag}^{}")

            printf '%s\n' "${peeled:-$direct}"
          }

          {
            echo "restore-keys<<EOF"
            for dist_tag in beta latest; do
              version="$(npm view "openclaw@${dist_tag}" version 2>/dev/null || true)"
              if [[ -z "$version" ]]; then
                echo "Could not resolve npm dist-tag ${dist_tag}; skipping cache seed." >&2
                continue
              fi

              sha="$(resolve_tag_sha "v${version}")"
              if [[ -z "$sha" ]]; then
                echo "Could not resolve git tag v${version}; skipping cache seed." >&2
                continue
              fi

              key="${cache_prefix}${sha}"
              if [[ -z "${seen[$key]+x}" ]]; then
                echo "$key"
                seen[$key]=1
              fi
            done
            echo "${cache_prefix}"
            echo "EOF"
          } >> "$GITHUB_OUTPUT"

      - name: Restore dist build cache
        id: dist-cache
        uses: actions/cache/restore@v5
        with:
          path: |
            .artifacts/build-all-cache/
            dist/
            dist-runtime/
          key: ${{ runner.os }}-dist-build-${{ github.sha }}
          restore-keys: ${{ steps.dist-cache-seeds.outputs.restore-keys }}

      - name: Build dist on cache miss
        if: steps.dist-cache.outputs.cache-hit != 'true'
        env:
          NODE_OPTIONS: --max-old-space-size=8192
        run: pnpm build:ci-artifacts

      - name: Build Control UI on cache miss
        if: steps.dist-cache.outputs.cache-hit != 'true'
        run: pnpm ui:build

      - name: Verify build artifacts
        shell: bash
        run: |
          set -euo pipefail

          test -d dist
          test -d dist-runtime
          if [[ ! -f dist/index.js && ! -f dist/index.mjs ]]; then
            echo "Missing dist/index.js or dist/index.mjs" >&2
            exit 1
          fi
          test -f dist/build-info.json
          test -f dist/control-ui/index.html

      - name: Save dist build cache
        if: steps.dist-cache.outputs.cache-hit != 'true'
        uses: actions/cache/save@v5
        with:
          path: |
            .artifacts/build-all-cache/
            dist/
            dist-runtime/
          key: ${{ runner.os }}-dist-build-${{ github.sha }}

      - name: Prepare Testbox shell
        shell: bash
        run: |
          set -euo pipefail

          timeout --signal=TERM --kill-after=10s 30s git \
            -c protocol.version=2 \
            fetch --no-tags --prune --no-recurse-submodules --depth=50 origin \
            "+refs/heads/main:refs/remotes/origin/main"

          node_bin="$(dirname "$(node -p 'process.execPath')")"
          sudo ln -sf "$node_bin/node" /usr/local/bin/node
          sudo ln -sf "$node_bin/npm" /usr/local/bin/npm
          sudo ln -sf "$node_bin/npx" /usr/local/bin/npx
          sudo ln -sf "$node_bin/corepack" /usr/local/bin/corepack
          sudo tee /usr/local/bin/pnpm >/dev/null <<'PNPM'
          #!/usr/bin/env bash
          exec /usr/local/bin/corepack pnpm "$@"
          PNPM
          sudo chmod 0755 /usr/local/bin/pnpm

      - name: Hydrate Testbox provider env helper
        shell: bash
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
          ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
          CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
          DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
          FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
          GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
          GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
          GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
          KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
          MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
          MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
          MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
          OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
          QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
          TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
          XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
          ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
          Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
        run: bash scripts/ci-hydrate-testbox-env.sh

      - name: Run Testbox
        uses: useblacksmith/run-testbox@3f60ff9ceb2c10c3feefa87dc0c6490cffae059d
        if: success()
        env:
          FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
