name: Install Smoke

on:
  push:
    branches: [main]
  pull_request:
    types: [opened, reopened, synchronize, ready_for_review, converted_to_draft]
  workflow_dispatch:

concurrency:
  group: ${{ github.event_name == 'pull_request' && format('install-smoke-pr-{0}', github.event.pull_request.number) || format('install-smoke-push-{0}', github.run_id) }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"

jobs:
  docs-scope:
    if: github.event_name != 'pull_request' || !github.event.pull_request.draft
    runs-on: blacksmith-16vcpu-ubuntu-2404
    outputs:
      docs_only: ${{ steps.check.outputs.docs_only }}
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          fetch-depth: 1
          fetch-tags: false

      - name: Ensure docs-scope base commit
        uses: ./.github/actions/ensure-base-commit
        with:
          base-sha: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }}
          fetch-ref: ${{ github.event_name == 'push' && github.ref_name || github.event.pull_request.base.ref }}

      - name: Detect docs-only changes
        id: check
        uses: ./.github/actions/detect-docs-changes

  changed-smoke:
    needs: [docs-scope]
    if: needs.docs-scope.outputs.docs_only != 'true'
    runs-on: blacksmith-16vcpu-ubuntu-2404
    outputs:
      run_changed_smoke: ${{ steps.scope.outputs.run_changed_smoke }}
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          fetch-depth: 1
          fetch-tags: false

      - name: Ensure changed-smoke base commit
        uses: ./.github/actions/ensure-base-commit
        with:
          base-sha: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }}
          fetch-ref: ${{ github.event_name == 'push' && github.ref_name || github.event.pull_request.base.ref }}

      - name: Detect changed smoke scope
        id: scope
        shell: bash
        run: |
          set -euo pipefail

          if [ "${{ github.event_name }}" = "push" ]; then
            BASE="${{ github.event.before }}"
          else
            BASE="${{ github.event.pull_request.base.sha }}"
          fi

          node scripts/ci-changed-scope.mjs --base "$BASE" --head HEAD

  install-smoke:
    needs: [docs-scope, changed-smoke]
    if: (github.event_name != 'pull_request' || !github.event.pull_request.draft) && needs.docs-scope.outputs.docs_only != 'true' && needs.changed-smoke.outputs.run_changed_smoke == 'true'
    runs-on: blacksmith-16vcpu-ubuntu-2404
    env:
      DOCKER_BUILD_SUMMARY: "false"
      DOCKER_BUILD_RECORD_UPLOAD: "false"
    steps:
      - name: Checkout CLI
        uses: actions/checkout@v6

      - name: Set up Docker Builder
        uses: docker/setup-buildx-action@v4

      # Blacksmith can fall back to the local docker driver, which rejects gha
      # cache export/import. Keep smoke builds driver-agnostic.
      - name: Build root Dockerfile smoke image
        uses: useblacksmith/build-push-action@v2
        with:
          context: .
          file: ./Dockerfile
          build-args: |
            OPENCLAW_DOCKER_APT_UPGRADE=0
          tags: openclaw-dockerfile-smoke:local
          load: true
          push: false
          provenance: false

      - name: Run root Dockerfile CLI smoke
        run: |
          docker run --rm --entrypoint sh openclaw-dockerfile-smoke:local -lc 'which openclaw && openclaw --version'

      # This smoke validates that the build-arg path preinstalls the matrix
      # runtime deps declared by the plugin and that matrix discovery stays
      # healthy in the final runtime image.
      - name: Build extension Dockerfile smoke image
        uses: useblacksmith/build-push-action@v2
        with:
          context: .
          file: ./Dockerfile
          build-args: |
            OPENCLAW_DOCKER_APT_UPGRADE=0
            OPENCLAW_EXTENSIONS=matrix
          tags: openclaw-ext-smoke:local
          load: true
          push: false
          provenance: false

      - name: Smoke test Dockerfile with matrix extension build arg
        run: |
          docker run --rm --entrypoint sh openclaw-ext-smoke:local -lc '
            which openclaw &&
            openclaw --version &&
            node -e "
              const Module = require(\"node:module\");
              const matrixPackage = require(\"/app/extensions/matrix/package.json\");
              const requireFromMatrix = Module.createRequire(\"/app/extensions/matrix/package.json\");
              const runtimeDeps = Object.keys(matrixPackage.dependencies ?? {});
              if (runtimeDeps.length === 0) {
                throw new Error(
                  \"matrix package has no declared runtime dependencies; smoke cannot validate install mirroring\",
                );
              }
              for (const dep of runtimeDeps) {
                requireFromMatrix.resolve(dep);
              }
              const { spawnSync } = require(\"node:child_process\");
              const run = spawnSync(\"openclaw\", [\"plugins\", \"list\", \"--json\"], { encoding: \"utf8\" });
              if (run.status !== 0) {
                process.stderr.write(run.stderr || run.stdout || \"plugins list failed\\n\");
                process.exit(run.status ?? 1);
              }
              const parsed = JSON.parse(run.stdout);
              const matrix = (parsed.plugins || []).find((entry) => entry.id === \"matrix\");
              if (!matrix) {
                throw new Error(\"matrix plugin missing from bundled plugin list\");
              }
              const matrixDiag = (parsed.diagnostics || []).filter(
                (diag) =>
                  typeof diag.source === \"string\" &&
                  diag.source.includes(\"/extensions/matrix\") &&
                  typeof diag.message === \"string\" &&
                  diag.message.includes(\"extension entry escapes package directory\"),
              );
              if (matrixDiag.length > 0) {
                throw new Error(
                  \"unexpected matrix diagnostics: \" +
                    matrixDiag.map((diag) => diag.message).join(\"; \"),
                );
              }
            "
          '

      - name: Build installer smoke image
        uses: useblacksmith/build-push-action@v2
        with:
          context: ./scripts/docker
          file: ./scripts/docker/install-sh-smoke/Dockerfile
          tags: openclaw-install-smoke:local
          load: true
          push: false
          provenance: false

      - name: Build installer non-root image
        if: github.event_name != 'pull_request'
        uses: useblacksmith/build-push-action@v2
        with:
          context: ./scripts/docker
          file: ./scripts/docker/install-sh-nonroot/Dockerfile
          tags: openclaw-install-nonroot:local
          load: true
          push: false
          provenance: false

      - name: Run installer docker tests
        env:
          OPENCLAW_INSTALL_URL: https://openclaw.ai/install.sh
          OPENCLAW_INSTALL_CLI_URL: https://openclaw.ai/install-cli.sh
          OPENCLAW_NO_ONBOARD: "1"
          OPENCLAW_INSTALL_SMOKE_SKIP_CLI: "1"
          OPENCLAW_INSTALL_SMOKE_SKIP_IMAGE_BUILD: "1"
          OPENCLAW_INSTALL_NONROOT_SKIP_IMAGE_BUILD: ${{ github.event_name == 'pull_request' && '0' || '1' }}
          OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT: ${{ github.event_name == 'pull_request' && '1' || '0' }}
          OPENCLAW_INSTALL_SMOKE_SKIP_PREVIOUS: "1"
        run: bash scripts/test-install-sh-docker.sh
