tui-checkbox 0.4.4

A customizable checkbox widget for Ratatui TUI applications
Documentation
name: Release

on:
  push:
    tags:
      - "v*"
  # Manual fallback — trigger from the GitHub Actions UI if the tag push
  # ever silently skips or a re-run is needed without re-pushing the tag.
  workflow_dispatch:
    inputs:
      tag:
        description: "Tag to release (e.g. v0.5.0) — must already exist on the repo"
        required: true
        default: ""

permissions:
  contents: write
  packages: write

# One release run at a time per tag.  Re-pushing the same tag cancels the
# previous run instead of queuing a duplicate.
concurrency:
  group: release-${{ github.ref }}
  cancel-in-progress: true

env:
  CARGO_TERM_COLOR: always

jobs:
  # ── 0. Validate trigger ──────────────────────────────────────────────────────
  # Resolve the effective tag from either a tag push or a manual dispatch, then
  # validate it matches vX.Y.Z before spending time on any build work.
  validate:
    name: Validate tag
    runs-on: ubuntu-latest
    outputs:
      tag: ${{ steps.resolve.outputs.tag }}
      version: ${{ steps.resolve.outputs.version }}
    steps:
      - name: Resolve and validate tag
        id: resolve
        run: |
          # For a tag push  → github.ref_name is the tag (e.g. "v0.5.0")
          # For manual runs → the user supplies it via the `tag` input
          if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
            TAG="${{ github.event.inputs.tag }}"
          else
            TAG="${{ github.ref_name }}"
          fi

          echo "Resolved tag: ${TAG}"

          if [[ ! "${TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
            echo "❌ Tag '${TAG}' does not match vX.Y.Z — aborting."
            exit 1
          fi

          VERSION="${TAG#v}"
          echo "tag=${TAG}"         >> "$GITHUB_OUTPUT"
          echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
          echo "✅ Tag ${TAG} (version ${VERSION}) is valid."

  # ── 1. Quality gate ──────────────────────────────────────────────────────────
  test:
    name: Test & lint
    needs: validate
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.validate.outputs.tag }}

      - name: Install Rust stable (rustfmt + clippy)
        uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy

      - name: Cache Cargo dependencies
        uses: Swatinem/rust-cache@v2

      - name: Check formatting
        run: cargo fmt -- --check

      - name: Run clippy
        run: cargo clippy -- -D warnings -A deprecated

      - name: Run tests
        run: cargo test --locked --all-features --all-targets

  # ── 2. Build release binary ──────────────────────────────────────────────────
  build:
    name: Build release
    needs: [validate, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.validate.outputs.tag }}

      - name: Install Rust stable
        uses: dtolnay/rust-toolchain@stable

      - name: Cache Cargo dependencies
        uses: Swatinem/rust-cache@v2

      - name: Build release
        run: cargo build --release --all-features

  # ── 3. GitHub Release ────────────────────────────────────────────────────────
  release:
    name: Create GitHub Release
    needs: [validate, build]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.validate.outputs.tag }}
          fetch-depth: 0

      - name: Install Rust stable
        uses: dtolnay/rust-toolchain@stable

      - name: Cache Cargo dependencies
        uses: Swatinem/rust-cache@v2

      - name: Install git-cliff
        run: cargo install git-cliff --locked

      - name: Install Nushell
        run: cargo install nu --locked

      - name: Prepare release (RELEASE_NOTES.md + CHANGELOG.md)
        run: nu scripts/release_prepare.nu ${{ needs.validate.outputs.tag }}

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ needs.validate.outputs.tag }}
          body_path: RELEASE_NOTES.md
          files: |
            LICENSE
            README.md
            CHANGELOG.md
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  # ── 4. Publish to crates.io ──────────────────────────────────────────────────
  publish:
    name: Publish to crates.io
    needs: [validate, release]
    runs-on: ubuntu-latest
    # Expose the secret as an env var and gate every publish step with it.
    # `secrets.*` cannot be used in a job-level `if:` on GitHub Actions.
    env:
      HAS_TOKEN: ${{ secrets.CRATES_IO_TOKEN != '' }}
    steps:
      - name: Skip notice (no token)
        if: env.HAS_TOKEN != 'true'
        run: echo "⏭️  CRATES_IO_TOKEN is not configured — skipping crates.io publish."

      - uses: actions/checkout@v4
        if: env.HAS_TOKEN == 'true'
        with:
          ref: ${{ needs.validate.outputs.tag }}

      - name: Install Rust stable
        if: env.HAS_TOKEN == 'true'
        uses: dtolnay/rust-toolchain@stable

      - name: Cache Cargo dependencies
        if: env.HAS_TOKEN == 'true'
        uses: Swatinem/rust-cache@v2

      - name: Publish tui-checkbox
        if: env.HAS_TOKEN == 'true'
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
        run: |
          VERSION="${{ needs.validate.outputs.version }}"
          if cargo info tui-checkbox@${VERSION} 2>/dev/null | grep -q "^tui-checkbox"; then
            echo "⏭️  tui-checkbox@${VERSION} already on crates.io — skipping."
          else
            echo "📦 Publishing tui-checkbox…"
            output=$(cargo publish --allow-dirty 2>&1) && echo "$output" || {
              echo "$output"
              echo "$output" | grep -q "already exists" && echo "⏭️  Already published — skipping." || exit 1
            }
          fi
          echo "✅ Publish step complete."