name: OpenClaw Cross-OS Release Checks (Reusable)

on:
  workflow_dispatch:
    inputs:
      ref:
        description: Public OpenClaw ref to validate (tag, branch, or full commit SHA)
        required: true
        default: main
        type: string
      workflow_ref:
        description: Optional openclaw/openclaw ref that provides the reusable workflow harness
        required: false
        default: ""
        type: string
      provider:
        description: Provider lane to use for onboarding and the end-to-end turn
        required: true
        default: openai
        type: choice
        options:
          - openai
          - anthropic
          - minimax
      mode:
        description: Which release-check lanes to run
        required: true
        default: both
        type: choice
        options:
          - fresh
          - upgrade
          - both
      previous_version:
        description: Optional baseline version for installer/dev-update and packaged upgrade
        required: false
        default: ""
        type: string
      ubuntu_runner:
        description: Optional Linux runner label override
        required: false
        default: ""
        type: string
      windows_runner:
        description: Optional Windows runner label override
        required: false
        default: ""
        type: string
      macos_runner:
        description: Optional macOS runner label override
        required: false
        default: ""
        type: string
  workflow_call:
    inputs:
      ref:
        description: Public OpenClaw ref to validate (tag, branch, or full commit SHA)
        required: true
        type: string
      workflow_ref:
        description: Optional openclaw/openclaw ref that provides the reusable workflow harness
        required: false
        default: ""
        type: string
      provider:
        description: Provider lane to use for onboarding and the end-to-end turn
        required: true
        type: string
      mode:
        description: Which release-check lanes to run
        required: true
        type: string
      previous_version:
        description: Optional baseline version for the upgrade lane (defaults to npm latest)
        required: false
        default: ""
        type: string
      ubuntu_runner:
        description: Optional Linux runner label override
        required: false
        default: ""
        type: string
      windows_runner:
        description: Optional Windows runner label override
        required: false
        default: ""
        type: string
      macos_runner:
        description: Optional macOS runner label override
        required: false
        default: ""
        type: string
    secrets:
      OPENAI_API_KEY:
        required: false
      ANTHROPIC_API_KEY:
        required: false
      MINIMAX_API_KEY:
        required: false
      OPENCLAW_DISCORD_SMOKE_BOT_TOKEN:
        required: false
      OPENCLAW_DISCORD_SMOKE_GUILD_ID:
        required: false
      OPENCLAW_DISCORD_SMOKE_CHANNEL_ID:
        required: false

permissions: read-all

concurrency:
  group: openclaw-cross-os-release-checks-${{ inputs.ref }}-${{ inputs.provider }}-${{ inputs.mode }}
  cancel-in-progress: false

env:
  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
  NODE_VERSION: "24.x"
  PNPM_VERSION: "10.32.1"
  OPENCLAW_REPOSITORY: openclaw/openclaw
  TSX_VERSION: "4.21.0"

jobs:
  prepare:
    runs-on: ubuntu-latest
    outputs:
      baseline_file_name: ${{ steps.baseline_metadata.outputs.file_name }}
      baseline_spec: ${{ steps.baseline.outputs.value }}
      candidate_file_name: ${{ steps.candidate_metadata.outputs.file_name }}
      candidate_version: ${{ steps.candidate_metadata.outputs.version }}
      matrix: ${{ steps.matrix.outputs.value }}
      source_sha: ${{ steps.candidate_metadata.outputs.source_sha }}
      workflow_ref: ${{ steps.workflow_ref.outputs.value }}
    steps:
      - name: Validate provider secret availability
        env:
          PROVIDER: ${{ inputs.provider }}
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
        run: |
          set -euo pipefail
          case "${PROVIDER}" in
            openai)
              [[ -n "${OPENAI_API_KEY}" ]] || { echo "Missing OPENAI_API_KEY secret." >&2; exit 1; }
              ;;
            anthropic)
              [[ -n "${ANTHROPIC_API_KEY}" ]] || { echo "Missing ANTHROPIC_API_KEY secret." >&2; exit 1; }
              ;;
            minimax)
              [[ -n "${MINIMAX_API_KEY}" ]] || { echo "Missing MINIMAX_API_KEY secret." >&2; exit 1; }
              ;;
            *)
              echo "Unsupported provider: ${PROVIDER}" >&2
              exit 1
              ;;
          esac

      - name: Resolve workflow ref
        id: workflow_ref
        env:
          INPUT_WORKFLOW_REF: ${{ inputs.workflow_ref }}
          CALLER_REPOSITORY: ${{ github.repository }}
          CURRENT_SHA: ${{ github.sha }}
          WORKFLOW_CONTEXT_REF: ${{ github.workflow_ref }}
          WORKFLOW_REPOSITORY: ${{ env.OPENCLAW_REPOSITORY }}
        run: |
          set -euo pipefail
          resolve_unique_remote_ref() {
            local remote_url="$1"
            shift
            local -a refs=("$@")
            local -a matches=()
            local ref=""

            for ref in "${refs[@]}"; do
              [[ -n "${ref}" ]] || continue
              mapfile -t matches < <(
                git ls-remote "${remote_url}" "${ref}" | awk '{print $1}' | awk '!seen[$0]++'
              )
              if [[ "${#matches[@]}" -eq 0 ]]; then
                continue
              fi
              if [[ "${#matches[@]}" -ne 1 ]]; then
                return 2
              fi

              printf '%s\n' "${matches[0]}"
              return 0
            done
            return 1
          }

          if [[ -n "${INPUT_WORKFLOW_REF}" ]]; then
            TARGET_REF="${INPUT_WORKFLOW_REF}"
          elif [[ "${CALLER_REPOSITORY}" == "${WORKFLOW_REPOSITORY}" ]]; then
            TARGET_REF="${CURRENT_SHA}"
          elif [[ "${WORKFLOW_CONTEXT_REF}" == "${WORKFLOW_REPOSITORY}/"* ]] && [[ "${WORKFLOW_CONTEXT_REF}" == *"@"* ]]; then
            TARGET_REF="${WORKFLOW_CONTEXT_REF##*@}"
          else
            echo "Failed to infer workflow ref from github.workflow_ref=${WORKFLOW_CONTEXT_REF}" >&2
            exit 1
          fi

          if [[ "${TARGET_REF}" =~ ^[0-9a-fA-F]{40}$ ]]; then
            echo "value=${TARGET_REF}" >> "$GITHUB_OUTPUT"
            exit 0
          fi

          REMOTE_URL="https://github.com/${WORKFLOW_REPOSITORY}.git"
          if [[ "${TARGET_REF}" == refs/* ]]; then
            if [[ "${TARGET_REF}" == refs/tags/* ]]; then
              mapfile -t MATCHES < <(
                resolve_unique_remote_ref "${REMOTE_URL}" "${TARGET_REF}^{}" "${TARGET_REF}" || true
              )
            else
              mapfile -t MATCHES < <(resolve_unique_remote_ref "${REMOTE_URL}" "${TARGET_REF}" || true)
            fi
          else
            mapfile -t BRANCH_MATCHES < <(
              resolve_unique_remote_ref "${REMOTE_URL}" "refs/heads/${TARGET_REF}" || true
            )
            mapfile -t TAG_MATCHES < <(
              resolve_unique_remote_ref "${REMOTE_URL}" "refs/tags/${TARGET_REF}^{}" "refs/tags/${TARGET_REF}" || true
            )

            MATCH_COUNT=$(( ${#BRANCH_MATCHES[@]} + ${#TAG_MATCHES[@]} ))
            if [[ "${MATCH_COUNT}" -eq 1 ]]; then
              if [[ "${#BRANCH_MATCHES[@]}" -eq 1 ]]; then
                MATCHES=("${BRANCH_MATCHES[0]}")
              else
                MATCHES=("${TAG_MATCHES[0]}")
              fi
            elif [[ "${MATCH_COUNT}" -eq 0 ]]; then
              MATCHES=()
            else
              echo "Workflow ref resolved ambiguously: ${TARGET_REF}" >&2
              exit 1
            fi
          fi

          case "${#MATCHES[@]}" in
            1)
              echo "value=${MATCHES[0]}" >> "$GITHUB_OUTPUT"
              ;;
            0)
              echo "Failed to resolve workflow ref: ${TARGET_REF}" >&2
              exit 1
              ;;
            *)
              echo "Workflow ref resolved ambiguously: ${TARGET_REF}" >&2
              exit 1
              ;;
          esac

      - name: Checkout workflow repo
        uses: actions/checkout@v6
        with:
          repository: ${{ env.OPENCLAW_REPOSITORY }}
          ref: ${{ steps.workflow_ref.outputs.value }}
          path: workflow
          fetch-depth: 1
          persist-credentials: false

      - name: Checkout public source ref
        uses: actions/checkout@v6
        with:
          repository: ${{ env.OPENCLAW_REPOSITORY }}
          ref: ${{ inputs.ref }}
          path: source
          fetch-depth: 0
          persist-credentials: false
          submodules: recursive

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: ${{ env.PNPM_VERSION }}
          run_install: false

      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: pnpm
          cache-dependency-path: source/pnpm-lock.yaml

      - name: Build candidate artifact once
        env:
          OUTPUT_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare
        run: |
          pnpm dlx "tsx@${TSX_VERSION}" workflow/scripts/openclaw-cross-os-release-checks.ts \
            --prepare-only \
            --source-dir source \
            --output-dir "${OUTPUT_DIR}"

      - name: Resolve baseline package spec
        if: ${{ inputs.mode != 'fresh' }}
        id: baseline
        env:
          INPUT_PREVIOUS_VERSION: ${{ inputs.previous_version }}
        run: |
          set -euo pipefail
          if [[ -n "${INPUT_PREVIOUS_VERSION}" ]]; then
            echo "value=openclaw@${INPUT_PREVIOUS_VERSION}" >> "$GITHUB_OUTPUT"
            exit 0
          fi
          BASELINE_VERSION="$(npm view openclaw@latest version)"
          echo "value=openclaw@${BASELINE_VERSION}" >> "$GITHUB_OUTPUT"

      - name: Pack baseline artifact
        if: ${{ inputs.mode != 'fresh' }}
        env:
          BASELINE_SPEC: ${{ steps.baseline.outputs.value }}
          OUTPUT_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/baseline
        run: |
          mkdir -p "${OUTPUT_DIR}"
          npm pack --ignore-scripts --json "${BASELINE_SPEC}" --pack-destination "${OUTPUT_DIR}" > "${OUTPUT_DIR}/pack.json"

      - name: Capture candidate metadata
        id: candidate_metadata
        env:
          CANDIDATE_JSON: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/candidate.json
        run: |
          node <<'NODE' >>"$GITHUB_OUTPUT"
          const fs = require("node:fs");
          const payload = JSON.parse(fs.readFileSync(process.env.CANDIDATE_JSON, "utf8"));
          process.stdout.write(`file_name=${payload.candidateFileName}\n`);
          process.stdout.write(`version=${payload.candidateVersion}\n`);
          process.stdout.write(`source_sha=${payload.sourceSha}\n`);
          NODE

      - name: Capture baseline metadata
        if: ${{ inputs.mode != 'fresh' }}
        id: baseline_metadata
        env:
          BASELINE_PACK_JSON: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/baseline/pack.json
        run: |
          node <<'NODE' >>"$GITHUB_OUTPUT"
          const fs = require("node:fs");
          const payload = JSON.parse(fs.readFileSync(process.env.BASELINE_PACK_JSON, "utf8"));
          const entry = Array.isArray(payload) ? payload.at(-1) : null;
          if (!entry?.filename) {
            throw new Error("Baseline npm pack did not produce a filename.");
          }
          process.stdout.write(`file_name=${entry.filename}\n`);
          NODE

      - name: Upload candidate artifact
        uses: actions/upload-artifact@v7
        with:
          name: openclaw-cross-os-release-checks-candidate-${{ github.run_id }}
          path: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/package/${{ steps.candidate_metadata.outputs.file_name }}
          if-no-files-found: error

      - name: Upload baseline artifact
        if: ${{ inputs.mode != 'fresh' }}
        uses: actions/upload-artifact@v7
        with:
          name: openclaw-cross-os-release-checks-baseline-${{ github.run_id }}
          path: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/baseline/${{ steps.baseline_metadata.outputs.file_name }}
          if-no-files-found: error

      - name: Resolve runner matrix
        id: matrix
        env:
          INPUT_REF: ${{ inputs.ref }}
          INPUT_MODE: ${{ inputs.mode }}
          INPUT_UBUNTU_RUNNER: ${{ inputs.ubuntu_runner }}
          INPUT_WINDOWS_RUNNER: ${{ inputs.windows_runner }}
          INPUT_MACOS_RUNNER: ${{ inputs.macos_runner }}
          VAR_UBUNTU_RUNNER: ${{ vars.OPENCLAW_RELEASE_CHECKS_UBUNTU_RUNNER }}
          VAR_WINDOWS_RUNNER: ${{ vars.OPENCLAW_RELEASE_CHECKS_WINDOWS_RUNNER }}
          VAR_MACOS_RUNNER: ${{ vars.OPENCLAW_RELEASE_CHECKS_MACOS_RUNNER }}
        run: |
          MATRIX_JSON="$(pnpm dlx "tsx@${TSX_VERSION}" workflow/scripts/openclaw-cross-os-release-checks.ts \
            --resolve-matrix \
            --ref "${INPUT_REF}" \
            --mode "${INPUT_MODE}" \
            --ubuntu-runner "${INPUT_UBUNTU_RUNNER}" \
            --windows-runner "${INPUT_WINDOWS_RUNNER}" \
            --macos-runner "${INPUT_MACOS_RUNNER}")"
          echo "value=${MATRIX_JSON}" >> "$GITHUB_OUTPUT"

  cross_os_release_checks:
    name: "${{ matrix.display_name }} / ${{ matrix.suite_label }}"
    needs: prepare
    strategy:
      fail-fast: false
      matrix: ${{ fromJson(needs.prepare.outputs.matrix) }}
    runs-on: ${{ matrix.runner }}
    timeout-minutes: 120
    steps:
      - name: Checkout workflow repo
        uses: actions/checkout@v6
        with:
          repository: ${{ env.OPENCLAW_REPOSITORY }}
          ref: ${{ needs.prepare.outputs.workflow_ref }}
          path: workflow
          fetch-depth: 1
          persist-credentials: false

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: ${{ env.PNPM_VERSION }}
          run_install: false

      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: ${{ env.NODE_VERSION }}

      - name: Download candidate artifact
        uses: actions/download-artifact@v8
        with:
          name: openclaw-cross-os-release-checks-candidate-${{ github.run_id }}
          path: ${{ runner.temp }}/openclaw-cross-os-release-checks/candidate

      - name: Download baseline artifact
        if: ${{ matrix.suite == 'packaged-upgrade' }}
        uses: actions/download-artifact@v8
        with:
          name: openclaw-cross-os-release-checks-baseline-${{ github.run_id }}
          path: ${{ runner.temp }}/openclaw-cross-os-release-checks/baseline

      - name: Run cross-OS release checks
        shell: bash
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
          OPENCLAW_DISCORD_SMOKE_BOT_TOKEN: ${{ secrets.OPENCLAW_DISCORD_SMOKE_BOT_TOKEN }}
          OPENCLAW_DISCORD_SMOKE_GUILD_ID: ${{ secrets.OPENCLAW_DISCORD_SMOKE_GUILD_ID }}
          OPENCLAW_DISCORD_SMOKE_CHANNEL_ID: ${{ secrets.OPENCLAW_DISCORD_SMOKE_CHANNEL_ID }}
          OPENCLAW_RELEASE_CHECK_OS: ${{ matrix.os_id }}
          OPENCLAW_RELEASE_CHECK_RUNNER: ${{ matrix.runner }}
        run: |
          DISCORD_ARGS=()
          if [[ -n "${OPENCLAW_DISCORD_SMOKE_BOT_TOKEN}" ]] && [[ -n "${OPENCLAW_DISCORD_SMOKE_GUILD_ID}" ]] && [[ -n "${OPENCLAW_DISCORD_SMOKE_CHANNEL_ID}" ]]; then
            DISCORD_ARGS+=(--run-discord-roundtrip true)
          fi
          pnpm dlx "tsx@${TSX_VERSION}" workflow/scripts/openclaw-cross-os-release-checks.ts \
            --candidate-tgz "$RUNNER_TEMP/openclaw-cross-os-release-checks/candidate/${{ needs.prepare.outputs.candidate_file_name }}" \
            --candidate-version "${{ needs.prepare.outputs.candidate_version }}" \
            --source-sha "${{ needs.prepare.outputs.source_sha }}" \
            --baseline-spec "${{ needs.prepare.outputs.baseline_spec }}" \
            --previous-version "${{ inputs.previous_version }}" \
            --baseline-tgz "$RUNNER_TEMP/openclaw-cross-os-release-checks/baseline/${{ needs.prepare.outputs.baseline_file_name }}" \
            --provider "${{ inputs.provider }}" \
            --mode "${{ matrix.lane }}" \
            --suite "${{ matrix.suite }}" \
            --ref "${{ inputs.ref }}" \
            "${DISCORD_ARGS[@]}" \
            --output-dir "$RUNNER_TEMP/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }}"

      - name: Summarize release checks
        if: always()
        shell: bash
        env:
          SUMMARY_PATH: ${{ runner.temp }}/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }}/summary.md
        run: |
          if [[ -f "${SUMMARY_PATH}" ]]; then
            cat "${SUMMARY_PATH}" >> "$GITHUB_STEP_SUMMARY"
          else
            echo "No summary generated." >> "$GITHUB_STEP_SUMMARY"
          fi

      - name: Upload release-check artifacts
        if: always()
        uses: actions/upload-artifact@v7
        with:
          name: openclaw-cross-os-release-checks-${{ matrix.artifact_name }}-${{ matrix.suite }}-${{ github.run_id }}
          path: ${{ runner.temp }}/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }}
          if-no-files-found: error
