doc-scraper-rs 0.1.0

The fastest, cleanest way to export GitBook docs as markdown.
name: Release

# Build pre-built binaries for the platforms we publish to, attach them to
# a GitHub Release. Triggered by pushing a tag that matches `v*` (e.g. `v0.1.0`).
#
# Per-target notes:
# - reqwest 0.13 -> rustls -> aws-lc-sys compiles vendored C via cmake, so
#   every host needs cmake installed upfront. macOS runners ship BSD tar;
#   we pipe through xz explicitly so the same line works on every shell.
# - Windows runners don't ship `zip` reliably and git-bash path translation
#   trips up zip's arg parser; we use PowerShell Compress-Archive directly.
#
# Targets:
#   x86_64-unknown-linux-gnu     (ubuntu-24.04)
#   x86_64-apple-darwin          (macos-14)
#   aarch64-apple-darwin         (macos-14)
#   x86_64-pc-windows-msvc       (windows-2022)
#
# Not in this matrix (yet):
#   aarch64-unknown-linux-gnu    - aws-lc-sys cross-compile is genuinely
#                                  painful (CMake toolchain file, cross
#                                  binutils, sysroot alignment). The
#                                  `cross-rs` setup needed to make this
#                                  reliable is more infrastructure than
#                                  this release warrants. Linux ARM users
#                                  can `cargo install doc-scraper-rs` if
#                                  they have a Rust toolchain, which is
#                                  the vast majority of that segment.
#
# Manual: leave the tag push to the maintainer — this workflow does NOT
# create or push tags on its own. To cut a release:
#   git tag v0.1.0
#   git push origin v0.1.0

on:
  push:
    tags:
      - "v*"
  # Manual retry: lets you re-run the matrix after fixing the workflow
  # without pushing a new tag. The `tag_name` input is required.
  workflow_dispatch:
    inputs:
      tag_name:
        description: "Tag to publish (e.g. v0.1.0)"
        required: true
        type: string

permissions:
  contents: write

# Unify tag/ref source so a tag-push run and a manual `workflow_dispatch`
# run both see the same tag name downstream.
env:
  TAG_NAME: ${{ github.event.inputs.tag_name || github.ref_name }}

jobs:
  build:
    name: Build (${{ matrix.target }})
    strategy:
      fail-fast: false
      matrix:
        include:
          - target: x86_64-unknown-linux-gnu
            os: ubuntu-24.04
          - target: x86_64-apple-darwin
            os: macos-14
          - target: aarch64-apple-darwin
            os: macos-14
          - target: x86_64-pc-windows-msvc
            os: windows-2022

    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v7

      - name: Install Rust toolchain (stable) + target
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      # Tooling for the vendored C in aws-lc-sys (reqwest -> rustls crypto).
      - name: Install build deps
        shell: bash
        run: |
          set -euo pipefail
          case "${{ matrix.os }}" in
            ubuntu-*)
              sudo apt-get update -qq
              sudo apt-get install -y -qq cmake xz-utils
              ;;
            macos-*)
              brew install cmake 2>/dev/null || true
              ;;
            windows-*)
              # No choco install needed — we use PowerShell Compress-Archive
              # below, which is built into the OS image.
              ;;
          esac

      - name: Cache cargo registry and target
        uses: Swatinem/rust-cache@v2
        with:
          cache-on-failure: true

      # `--locked` honours the committed Cargo.lock — no surprise transitive
      # bumps on a release commit. aws-lc-sys is the slow dep; expect ~3 min
      # first build.
      - name: Build (release)
        run: cargo build --release --target ${{ matrix.target }} --locked

      - name: Package artifacts
        shell: bash
        run: |
          set -euo pipefail

          ARTIFACT_DIR="dist"
          # On Windows runners the cwd is something like D:\a\doc-scraper-rs\doc-scraper-rs.
          # Force a POSIX-style relative path so downstream commands work in both
          # git-bash and PowerShell without surprises.
          rm -rf "${ARTIFACT_DIR}"
          mkdir -p "${ARTIFACT_DIR}"

          case "${{ matrix.target }}" in
            *windows*)
              BIN_NAME="doc-scraper.exe"
              ARCHIVE_EXT="zip"
              ;;
            *)
              BIN_NAME="doc-scraper"
              ARCHIVE_EXT="tar.xz"
              ;;
          esac

          SRC="target/${{ matrix.target }}/release/${BIN_NAME}"
          if [[ ! -f "${SRC}" ]]; then
            echo "::error::Expected binary not found at ${SRC}"
            ls -la "$(dirname "${SRC}")" || true
            exit 1
          fi

          VERSION_NO_V="${TAG_NAME#v}"
          PKG_NAME="doc-scraper-${VERSION_NO_V}-${{ matrix.target }}"

          # Stage the directory tree inside the archive.
          STAGING="$(mktemp -d)"
          mkdir -p "${STAGING}/${PKG_NAME}"
          cp "${SRC}" "${STAGING}/${PKG_NAME}/"
          cp README.md "${STAGING}/${PKG_NAME}/"
          cp CHANGELOG.md "${STAGING}/${PKG_NAME}/"

          case "${ARCHIVE_EXT}" in
            zip)
              # PowerShell Compress-Archive is part of every Windows host.
              # Convert the bash paths to Windows paths first because
              # PowerShell on Windows can't read POSIX-style paths reliably.
              WIN_STAGING="$(cygpath -w "${STAGING}")"
              WIN_PKG="$(cygpath -w "${STAGING}/${PKG_NAME}")"
              WIN_OUT="$(cygpath -w "${ARTIFACT_DIR}/${PKG_NAME}.zip")"
              powershell.exe -NoProfile -Command "
                Compress-Archive -Path '${WIN_PKG}' -DestinationPath '${WIN_OUT}' -Force
              "
              ;;
            tar.xz)
              # Avoid `tar -J` — BSD tar (macOS) doesn't support it. Pipe
              # through `xz` so the same line works on every shell.
              tar -cf - -C "${STAGING}" "${PKG_NAME}" \
                | xz > "${ARTIFACT_DIR}/${PKG_NAME}.tar.xz"
              ;;
          esac

          # SHA-256 next to each archive for integrity verification.
          for f in "${ARTIFACT_DIR}"/*.zip "${ARTIFACT_DIR}"/*.tar.xz; do
            [[ -e "${f}" ]] || continue
            if command -v sha256sum >/dev/null 2>&1; then
              sha256sum "${f}" > "${f}.sha256"
            elif command -v shasum >/dev/null 2>&1; then
              # macOS doesn't ship GNU coreutils; shasum is the Perl equivalent.
              HASH="$(shasum -a 256 "${f}" | awk '{print $1}')"
              printf '%s  %s\n' "${HASH}" "${f}" > "${f}.sha256"
            else
              WIN_F="$(cygpath -w "${f}")"
              HASH="$(powershell.exe -NoProfile -Command "(Get-FileHash -Algorithm SHA256 -Path '${WIN_F}').Hash")"
              printf '%s  %s\n' "${HASH}" "${f}" > "${f}.sha256"
            fi
          done
          ls -la "${ARTIFACT_DIR}/"

      - name: Upload build artifact
        uses: actions/upload-artifact@v6
        with:
          name: doc-scraper-${{ matrix.target }}
          path: dist/
          if-no-files-found: error

  release:
    name: Publish GitHub Release
    needs: build
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v7

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

      # Manual dispatch may not yet have a remote tag — softprops/... needs the
      # tag to exist before it can attach the Release. Tag-triggered runs skip
      # this (the tag is the very thing that triggered the run).
      - name: Ensure tag exists
        if: github.event_name == 'workflow_dispatch'
        shell: bash
        run: |
          set -euo pipefail
          if ! git ls-remote --tags origin "refs/tags/${TAG_NAME}" | grep -q "${TAG_NAME}"; then
            git tag "${TAG_NAME}" HEAD
            git push origin "${TAG_NAME}"
            echo "::notice::Created and pushed tag ${TAG_NAME}"
          else
            echo "::notice::Tag ${TAG_NAME} already exists on remote; reusing"
          fi

      - name: Flatten artifact directory
        run: |
          set -euo pipefail
          mkdir -p release-assets
          find artifacts -type f \( -name '*.zip' -o -name '*.tar.xz' -o -name '*.sha256' \) \
            -exec cp -v {} release-assets/ \;
          ls -la release-assets

      - name: Create / update GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ env.TAG_NAME }}
          name: ${{ env.TAG_NAME }}
          generate_release_notes: true
          files: release-assets/*