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
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