name: Release

on:
  push:
    tags: ['v*']
  workflow_dispatch:
    inputs:
      version:
        description: 'Package/release version to publish to npm, without the leading v'
        required: true
        type: string

env:
  CARGO_TERM_COLOR: always
  RUSTFLAGS: -Dwarnings

jobs:
  parity:
    if: github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy, rustfmt
      - name: Install Linux system dependencies
        if: runner.os == 'Linux'
        run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev pkg-config
      - uses: Swatinem/rust-cache@v2
      - name: Format check
        run: cargo fmt --all -- --check
      - name: Compile check
        run: cargo check --workspace --all-targets --locked
      - name: Clippy
        run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
      - name: Workspace tests
        run: cargo test --workspace --all-features --locked
      - name: TUI snapshot parity
        run: cargo test -p deepseek-tui-core --test snapshot --locked
      - name: Protocol schema parity
        run: cargo test -p deepseek-protocol --test parity_protocol --locked
      - name: State persistence parity
        run: cargo test -p deepseek-state --test parity_state --locked
      - name: Lockfile drift guard
        run: git diff --exit-code -- Cargo.lock

  build:
    needs: parity
    # `parity` is gated to tag-push events. On manual `workflow_dispatch`,
    # parity is skipped, so let `build` proceed when parity either succeeded
    # or was skipped — but never when it actually failed or the run was
    # cancelled. Operators using dispatch are expected to have already run
    # the same gates locally / via ci.yml on `main`.
    if: ${{ !cancelled() && (needs.parity.result == 'success' || needs.parity.result == 'skipped') }}
    strategy:
      matrix:
        include:
          # --- deepseek (cli) ---
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
            target_zig: x86_64-unknown-linux-gnu.2.28
            binary: deepseek
            artifact_name: deepseek-linux-x64
          - os: ubuntu-24.04-arm
            target: aarch64-unknown-linux-gnu
            target_zig: aarch64-unknown-linux-gnu.2.28
            binary: deepseek
            artifact_name: deepseek-linux-arm64
          - os: macos-latest
            target: x86_64-apple-darwin
            binary: deepseek
            artifact_name: deepseek-macos-x64
          - os: macos-latest
            target: aarch64-apple-darwin
            binary: deepseek
            artifact_name: deepseek-macos-arm64
          - os: windows-latest
            target: x86_64-pc-windows-msvc
            binary: deepseek.exe
            artifact_name: deepseek-windows-x64.exe
          # --- deepseek-tui (TUI) ---
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
            target_zig: x86_64-unknown-linux-gnu.2.28
            binary: deepseek-tui
            artifact_name: deepseek-tui-linux-x64
          - os: ubuntu-24.04-arm
            target: aarch64-unknown-linux-gnu
            target_zig: aarch64-unknown-linux-gnu.2.28
            binary: deepseek-tui
            artifact_name: deepseek-tui-linux-arm64
          - os: macos-latest
            target: x86_64-apple-darwin
            binary: deepseek-tui
            artifact_name: deepseek-tui-macos-x64
          - os: macos-latest
            target: aarch64-apple-darwin
            binary: deepseek-tui
            artifact_name: deepseek-tui-macos-arm64
          - os: windows-latest
            target: x86_64-pc-windows-msvc
            binary: deepseek-tui.exe
            artifact_name: deepseek-tui-windows-x64.exe
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}
      - uses: Swatinem/rust-cache@v2
      - name: Install Linux system dependencies
        if: runner.os == 'Linux'
        run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev pkg-config
      - name: Install zig
        if: runner.os == 'Linux'
        uses: goto-bus-stop/setup-zig@v2
        with:
          version: '0.13.0'
      - name: Install cargo-zigbuild
        if: runner.os == 'Linux'
        run: cargo install cargo-zigbuild --locked
      - name: Build
        shell: bash
        run: |
          if [ -n "${{ matrix.target_zig }}" ]; then
            cargo zigbuild --release --locked --target ${{ matrix.target_zig }}
          else
            cargo build --release --locked --target ${{ matrix.target }}
          fi
      - name: Rename binary
        shell: bash
        run: |
          # cargo zigbuild writes binaries to target/<rust-target>/release/ —
          # the .glibc suffix in matrix.target_zig is consumed by zig as a CC
          # flag, not by cargo as a target dir. Always use the rust target.
          BIN_PATH="target/${{ matrix.target }}/release/${{ matrix.binary }}"
          if [ ! -f "${BIN_PATH}" ]; then
            echo "Binary not at ${BIN_PATH}; searching target/ for ${{ matrix.binary }}:"
            find target -name "${{ matrix.binary }}" -type f
            exit 1
          fi
          cp "${BIN_PATH}" "${{ matrix.artifact_name }}"
      - uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.artifact_name }}
          path: ${{ matrix.artifact_name }}
  docker:
    needs: build
    if: ${{ !cancelled() && needs.build.result == 'success' }}
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: |
            ghcr.io/${{ github.repository }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=semver,pattern=v{{major}}
            type=ref,event=tag
      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  release:
    needs: build
    if: ${{ !cancelled() && needs.build.result == 'success' }}
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/download-artifact@v4
        with:
          path: artifacts
      - name: List artifacts
        run: find artifacts -type f
      - name: Generate checksum manifest
        shell: bash
        run: |
          mkdir -p artifacts/checksums
          manifest="artifacts/checksums/deepseek-artifacts-sha256.txt"
          : > "${manifest}"
          while IFS= read -r -d '' file; do
            hash="$(sha256sum "${file}" | awk '{print $1}')"
            base="$(basename "${file}")"
            printf '%s  %s\n' "${hash}" "${base}" >> "${manifest}"
          done < <(find artifacts -type f ! -path 'artifacts/checksums/*' -print0 | sort -z)
          cat "${manifest}"
      - uses: softprops/action-gh-release@v1
        with:
          files: artifacts/*/*
          prerelease: false
          body: |
            ## Install

            ### Recommended — npm (one command, both binaries)

            ```bash
            npm install -g deepseek-tui
            ```

            The wrapper downloads both binaries from this Release and places them in the same directory.

            ### Cargo (Linux / macOS)

            ```bash
            cargo install deepseek-tui-cli deepseek-tui --locked
            ```

            Both crates are required — `deepseek-tui-cli` produces the `deepseek` dispatcher and `deepseek-tui` produces the interactive runtime that the dispatcher delegates to. Installing only one binary will fail at runtime with a `MISSING_COMPANION_BINARY` error.

            ### Manual download

            **Both** binaries below must be downloaded for your platform and dropped into the same directory (e.g. `~/.local/bin/`):

            | Platform | Dispatcher | TUI runtime |
            |---|---|---|
            | Linux x64 | `deepseek-linux-x64` | `deepseek-tui-linux-x64` |
            | Linux ARM64 | `deepseek-linux-arm64` | `deepseek-tui-linux-arm64` |
            | macOS x64 | `deepseek-macos-x64` | `deepseek-tui-macos-x64` |
            | macOS ARM | `deepseek-macos-arm64` | `deepseek-tui-macos-arm64` |
            | Windows x64 | `deepseek-windows-x64.exe` | `deepseek-tui-windows-x64.exe` |

            Then `chmod +x` both (Unix) and run `./deepseek`.

            ### Verify (recommended)

            Download `deepseek-artifacts-sha256.txt` from this Release and verify:

            ```bash
            # Linux
            sha256sum -c deepseek-artifacts-sha256.txt

            # macOS
            shasum -a 256 -c deepseek-artifacts-sha256.txt
            ```

            ## Changelog

            See [CHANGELOG.md](https://github.com/Hmbown/DeepSeek-TUI/blob/main/CHANGELOG.md) for the full notes for this release.

# npm publish is intentionally not automated. The npm account requires 2FA OTP
# on every publish, and a granular automation token that bypasses 2FA has not
# been provisioned. Release the npm wrapper manually from a developer machine
# after the GitHub Release has been created — see CLAUDE.md "Releases" for the
# exact commands.
