undoc 0.1.20

High-performance Microsoft Office document extraction to Markdown
Documentation
name: Release

on:
  workflow_run:
    workflows: ["CI"]
    types: [completed]
    branches: [main]
  workflow_dispatch:
    inputs:
      version:
        description: 'Version to release (leave empty to use Cargo.toml version)'
        required: false
        type: string

env:
  CARGO_TERM_COLOR: always

jobs:
  check-version:
    name: Check Version
    runs-on: ubuntu-latest
    if: |
      github.event_name == 'workflow_dispatch' ||
      (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
    outputs:
      version: ${{ steps.get_version.outputs.version }}
      version_num: ${{ steps.get_version.outputs.version_num }}
      should_release: ${{ steps.check_release.outputs.should_release }}
    steps:
      - uses: actions/checkout@v4

      - name: Get version from Cargo.toml
        id: get_version
        run: |
          if [ -n "${{ inputs.version }}" ]; then
            VERSION_NUM="${{ inputs.version }}"
          else
            VERSION_NUM=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)".*/\1/')
          fi
          echo "version=v${VERSION_NUM}" >> $GITHUB_OUTPUT
          echo "version_num=${VERSION_NUM}" >> $GITHUB_OUTPUT
          echo "Version: v${VERSION_NUM}"

      - name: Check if release exists
        id: check_release
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          VERSION=${{ steps.get_version.outputs.version }}
          if gh release view "$VERSION" > /dev/null 2>&1; then
            echo "Release $VERSION already exists, skipping"
            echo "should_release=false" >> $GITHUB_OUTPUT
          else
            echo "Release $VERSION does not exist, proceeding"
            echo "should_release=true" >> $GITHUB_OUTPUT
          fi

  build-release:
    name: Build Release
    needs: check-version
    if: needs.check-version.outputs.should_release == 'true'
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
            artifact_name: undoc
            archive_name: undoc-linux-x86_64
            archive_ext: tar.gz
          - os: windows-latest
            target: x86_64-pc-windows-msvc
            artifact_name: undoc.exe
            archive_name: undoc-windows-x86_64
            archive_ext: zip
          - os: macos-latest
            target: x86_64-apple-darwin
            artifact_name: undoc
            archive_name: undoc-macos-x86_64
            archive_ext: tar.gz
          - os: macos-latest
            target: aarch64-apple-darwin
            artifact_name: undoc
            archive_name: undoc-macos-aarch64
            archive_ext: tar.gz

    steps:
      - uses: actions/checkout@v4

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

      - name: Build
        run: cargo build --release --target ${{ matrix.target }} -p undoc-cli

      - name: Create archive (Unix)
        if: matrix.archive_ext == 'tar.gz'
        run: |
          cd target/${{ matrix.target }}/release
          tar -czvf ${{ matrix.archive_name }}-${{ needs.check-version.outputs.version }}.tar.gz ${{ matrix.artifact_name }}

      - name: Create archive (Windows)
        if: matrix.archive_ext == 'zip'
        shell: pwsh
        run: |
          cd target/${{ matrix.target }}/release
          Compress-Archive -Path ${{ matrix.artifact_name }} -DestinationPath "${{ matrix.archive_name }}-${{ needs.check-version.outputs.version }}.zip"

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.archive_name }}
          path: target/${{ matrix.target }}/release/${{ matrix.archive_name }}-${{ needs.check-version.outputs.version }}.${{ matrix.archive_ext }}
          retention-days: 1

  create-release:
    name: Create Release
    needs: [check-version, build-release]
    if: needs.check-version.outputs.should_release == 'true'
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      - uses: actions/checkout@v4

      - name: Download all artifacts
        uses: actions/download-artifact@v4
        with:
          path: artifacts

      - name: List artifacts
        run: find artifacts -type f

      - name: Create Release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ needs.check-version.outputs.version }}
          name: Release ${{ needs.check-version.outputs.version }}
          files: |
            artifacts/**/*
          generate_release_notes: true
          draft: false
          prerelease: ${{ contains(needs.check-version.outputs.version, 'alpha') || contains(needs.check-version.outputs.version, 'beta') || contains(needs.check-version.outputs.version, 'rc') }}

      - name: Delete artifacts after release
        uses: geekyeggo/delete-artifact@v5
        with:
          name: |
            undoc-*

  publish-crates:
    name: Publish to crates.io
    needs: [check-version, create-release]
    if: needs.check-version.outputs.should_release == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

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

      - name: Publish library (undoc)
        run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
        continue-on-error: true

      - name: Wait for crates.io index update
        run: sleep 30

      - name: Publish CLI (undoc-cli)
        run: cargo publish -p undoc-cli --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
        continue-on-error: true

  cleanup-old-releases:
    name: Cleanup Old Releases
    # Run after the new release is published. We only need create-release
    # to have succeeded — cleanup is independent of crates.io publish.
    needs: [check-version, create-release]
    if: needs.check-version.outputs.should_release == 'true'
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: Delete releases and tags beyond the 10 most recent
        env:
          GH_TOKEN: ${{ github.token }}
          REPO: ${{ github.repository }}
        shell: bash
        run: |
          set -euo pipefail

          # Fetch up to 100 recent releases. Sort by creation time desc and
          # take everything AFTER index 10 — keep 10 newest, delete the rest.
          # --cleanup-tag removes the underlying git tag too, so the repo's
          # release list AND tag list stay bounded. Aligns with CLAUDE.md's
          # GitHub Actions 리소스 관리 정책 (reduce storage use).
          mapfile -t OLD < <(
            gh release list -R "$REPO" --limit 100 --json tagName,createdAt \
              | jq -r 'sort_by(.createdAt) | reverse | .[10:] | .[].tagName'
          )

          if [ ${#OLD[@]} -eq 0 ]; then
            echo "Nothing to clean up (<=10 releases exist)."
            exit 0
          fi

          echo "Deleting ${#OLD[@]} old release(s):"
          printf '  %s\n' "${OLD[@]}"

          for tag in "${OLD[@]}"; do
            # --cleanup-tag deletes the git tag alongside the release.
            # --yes skips the interactive confirmation.
            gh release delete "$tag" --yes --cleanup-tag -R "$REPO"
          done