markdown-tui-explorer 1.22.1

A terminal-based markdown file browser and viewer with search, syntax highlighting, and live reload
name: Release

# Triggered by `v*` tags (the parent crate `markdown-tui-explorer`'s
# version line). Builds cross-platform binaries of `markdown-reader`,
# attaches them to a GitHub Release, publishes the parent crate to
# crates.io, and updates the Homebrew tap formula.
#
# The library crate `mermaid-text` has its own tag pattern
# (`mermaid-text-*`) handled by `release-mermaid-text.yml` — that one
# only publishes to crates.io, no binary release.

on:
  push:
    tags:
      - "v*"
  workflow_dispatch:
    inputs:
      publish_crate:
        description: "Publish to crates.io"
        type: boolean
        default: true

permissions:
  contents: write

env:
  CARGO_TERM_COLOR: always
  CARGO_INCREMENTAL: 0

jobs:
  build-linux:
    name: Build Linux (${{ matrix.target }})
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        target:
          - x86_64-unknown-linux-gnu
          - aarch64-unknown-linux-gnu
          - x86_64-unknown-linux-musl
    steps:
      - uses: actions/checkout@v6

      - uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2
        with:
          key: linux-${{ matrix.target }}

      - name: Install cross
        run: cargo install cross --locked

      - name: Build binary
        # Build the parent crate's `markdown-reader` binary.
        # `--package markdown-tui-explorer` ensures we don't try to build
        # mermaid-text's binary (which is a separate workspace member
        # released on its own tag pattern).
        run: cross build --release --target ${{ matrix.target }} --package markdown-tui-explorer

      - name: Strip binary
        run: |
          TARGET_STRIP=""
          case "${{ matrix.target }}" in
            aarch64-unknown-linux-gnu)
              TARGET_STRIP="aarch64-linux-gnu-strip"
              sudo apt-get install -y binutils-aarch64-linux-gnu
              ;;
            *)
              TARGET_STRIP="strip"
              ;;
          esac
          $TARGET_STRIP target/${{ matrix.target }}/release/markdown-reader

      - name: Package tarball
        run: |
          VERSION=${GITHUB_REF_NAME#v}
          TARBALL="markdown-reader-${VERSION}-${{ matrix.target }}.tar.gz"
          tar -czf "$TARBALL" -C target/${{ matrix.target }}/release markdown-reader
          echo "TARBALL=$TARBALL" >> "$GITHUB_ENV"

      - name: Upload artifact
        uses: actions/upload-artifact@v7
        with:
          name: markdown-reader-${{ matrix.target }}
          path: ${{ env.TARBALL }}
          retention-days: 1

  build-macos:
    name: Build macOS (${{ matrix.target }})
    # Both targets build on macos-latest (Apple Silicon). The x86_64
    # target cross-compiles via `rustup target add x86_64-apple-darwin`
    # — the host toolchain and `strip` both handle it natively.
    runs-on: macos-latest
    strategy:
      fail-fast: false
      matrix:
        target:
          - x86_64-apple-darwin
          - aarch64-apple-darwin
    steps:
      - uses: actions/checkout@v6

      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      - uses: Swatinem/rust-cache@v2
        with:
          key: macos-${{ matrix.target }}

      - name: Build binary
        run: cargo build --release --target ${{ matrix.target }} --package markdown-tui-explorer

      - name: Strip binary
        run: strip target/${{ matrix.target }}/release/markdown-reader

      - name: Package tarball
        run: |
          VERSION=${GITHUB_REF_NAME#v}
          TARBALL="markdown-reader-${VERSION}-${{ matrix.target }}.tar.gz"
          tar -czf "$TARBALL" -C target/${{ matrix.target }}/release markdown-reader
          echo "TARBALL=$TARBALL" >> "$GITHUB_ENV"

      - name: Upload artifact
        uses: actions/upload-artifact@v7
        with:
          name: markdown-reader-${{ matrix.target }}
          path: ${{ env.TARBALL }}
          retention-days: 1

  release:
    name: Create GitHub Release
    needs: [build-linux, build-macos]
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Download all artifacts
        uses: actions/download-artifact@v8
        with:
          path: artifacts
          merge-multiple: true

      - name: Generate SHA-256 checksums
        working-directory: artifacts
        run: sha256sum *.tar.gz > SHA256SUMS

      - name: Extract changelog section
        id: changelog
        run: |
          VERSION=${GITHUB_REF_NAME#v}
          if [ -f CHANGELOG.md ]; then
            # Extract the block between ## [version] and the next ## [
            BODY=$(awk "/^## \[${VERSION}\]/{found=1; next} found && /^## \[/{exit} found{print}" CHANGELOG.md)
          else
            BODY="Release ${GITHUB_REF_NAME}"
          fi
          printf '%s' "$BODY" > release_notes.md

      - name: Publish GitHub Release
        uses: softprops/action-gh-release@v3
        with:
          body_path: release_notes.md
          files: |
            artifacts/*.tar.gz
            artifacts/SHA256SUMS
          token: ${{ secrets.GITHUB_TOKEN }}

  publish-crate:
    name: Publish parent crate to crates.io
    needs: [release]
    runs-on: ubuntu-latest
    if: >
      (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) ||
      (github.event_name == 'workflow_dispatch' && inputs.publish_crate == true)
    steps:
      - uses: actions/checkout@v6

      - uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2

      - name: Publish parent crate
        # Only the parent (markdown-tui-explorer) ships on `v*` tags.
        # The library (mermaid-text) is released independently via
        # `mermaid-text-*` tags; if a `v*` release depends on a new
        # mermaid-text version, tag mermaid-text first.
        run: cargo publish --package markdown-tui-explorer --token ${{ secrets.CARGO_REGISTRY_TOKEN }}

  publish-homebrew:
    name: Update Homebrew tap
    needs: [release]
    runs-on: ubuntu-latest
    # Note: GitHub Actions does not allow `secrets.*` in job-level `if:`
    # (https://github.com/orgs/community/discussions/17245). Instead,
    # every step guards on a `HAS_TOKEN` job env that evaluates the
    # secret into a plain string. When the token is absent the job
    # "runs" but all steps no-op, leaving a clean summary.
    env:
      HAS_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN != '' && 'true' || 'false' }}
    steps:
      - name: Report skip reason
        if: env.HAS_TOKEN != 'true'
        run: echo "HOMEBREW_TAP_TOKEN is not set on this repo — skipping Homebrew formula publish."

      - name: Checkout source repo
        if: env.HAS_TOKEN == 'true'
        uses: actions/checkout@v6

      - name: Download release checksums
        if: env.HAS_TOKEN == 'true'
        # SHA256SUMS is an asset on the GitHub Release, not an inter-job
        # build artifact — it's generated inside the `release` job and
        # attached via softprops/action-gh-release. Fetch it directly.
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          mkdir -p artifacts
          gh release download "$GITHUB_REF_NAME" \
            -R "$GITHUB_REPOSITORY" \
            -p SHA256SUMS \
            -D artifacts

      - name: Checkout tap repo
        if: env.HAS_TOKEN == 'true'
        uses: actions/checkout@v6
        with:
          repository: leboiko/homebrew-tap
          token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
          path: homebrew-tap

      - name: Render formula
        if: env.HAS_TOKEN == 'true'
        run: |
          VERSION=${GITHUB_REF_NAME#v}
          mkdir -p homebrew-tap/Formula
          ./scripts/render-homebrew-formula.sh "$VERSION" artifacts/SHA256SUMS \
            > homebrew-tap/Formula/markdown-reader.rb

      - name: Commit and push
        if: env.HAS_TOKEN == 'true'
        working-directory: homebrew-tap
        run: |
          VERSION=${GITHUB_REF_NAME#v}
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          # Stage first, then check the index. Plain `git diff --quiet`
          # misses brand-new files (Formula/markdown-reader.rb on a
          # first-ever release wouldn't exist in the tap yet — the file
          # would be untracked, so unstaged-diff returns clean and the
          # workflow would exit thinking it was a no-op).
          git add Formula/markdown-reader.rb
          if git diff --cached --quiet; then
            echo "Formula already up to date for v${VERSION}"
            exit 0
          fi
          git commit -m "markdown-reader ${VERSION}"
          git push

  publish-aur:
    name: Publish AUR -bin package
    needs: [release]
    runs-on: ubuntu-latest
    # Same `HAS_KEY` gate pattern as `publish-homebrew` — the job runs
    # on every release, but every step is no-op'd when the secret is
    # missing so an unconfigured fork stays green.
    env:
      HAS_KEY: ${{ secrets.AUR_SSH_KEY != '' && 'true' || 'false' }}
    steps:
      - name: Report skip reason
        if: env.HAS_KEY != 'true'
        run: echo "AUR_SSH_KEY is not set on this repo — skipping AUR publish."

      - name: Checkout source repo
        if: env.HAS_KEY == 'true'
        uses: actions/checkout@v6

      - name: Download release checksums
        if: env.HAS_KEY == 'true'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          mkdir -p artifacts
          gh release download "$GITHUB_REF_NAME" \
            -R "$GITHUB_REPOSITORY" \
            -p SHA256SUMS \
            -D artifacts

      - name: Set up SSH for AUR
        if: env.HAS_KEY == 'true'
        env:
          AUR_SSH_KEY: ${{ secrets.AUR_SSH_KEY }}
        run: |
          mkdir -p ~/.ssh
          echo "$AUR_SSH_KEY" > ~/.ssh/aur
          chmod 600 ~/.ssh/aur
          # Pin the AUR host key fingerprint to prevent MITM. Pulled
          # from `ssh-keyscan -H aur.archlinux.org` and verified
          # against the published fingerprint at
          # https://wiki.archlinux.org/title/Aurweb_RPC_interface.
          ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts 2>/dev/null
          {
            echo "Host aur.archlinux.org"
            echo "  IdentityFile ~/.ssh/aur"
            echo "  User aur"
            echo "  StrictHostKeyChecking yes"
          } > ~/.ssh/config
          chmod 600 ~/.ssh/config

      - name: Clone AUR repo
        if: env.HAS_KEY == 'true'
        run: |
          # Clone (creates a local copy of the AUR-side repo for this
          # package). On first publish the AUR-side repo must exist —
          # see docs/RELEASING-AUR.md for the manual one-time setup.
          git clone ssh://aur@aur.archlinux.org/markdown-reader-bin.git aur-pkg

      - name: Render PKGBUILD + .SRCINFO
        if: env.HAS_KEY == 'true'
        run: |
          VERSION=${GITHUB_REF_NAME#v}
          ./scripts/render-aur-pkgbuild.sh "$VERSION" artifacts/SHA256SUMS aur-pkg

      - name: Commit and push
        if: env.HAS_KEY == 'true'
        working-directory: aur-pkg
        run: |
          VERSION=${GITHUB_REF_NAME#v}
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git add PKGBUILD .SRCINFO
          if git diff --cached --quiet; then
            echo "AUR PKGBUILD already up to date for v${VERSION}"
            exit 0
          fi
          git commit -m "markdown-reader ${VERSION}"
          git push