name: Release
on:
push:
tags:
- "v*.*.*"
workflow_dispatch:
inputs:
tag:
description: "Release tag to publish, for example v0.1.0"
required: true
type: string
dry_run:
description: "Only run validation and dry-run publish checks"
required: true
default: true
type: boolean
permissions:
contents: write
id-token: write
jobs:
publish:
runs-on: ubuntu-latest
env:
INPUT_TAG: ${{ inputs.tag }}
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || 'false' }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Select release ref
if: github.event_name == 'workflow_dispatch'
run: git checkout "$INPUT_TAG"
- uses: ./.github/actions/setup-rust
with:
tools: git-cliff@2.12.0
cache-key: publish
- name: Resolve release tag
run: |
TAG="${GITHUB_REF_NAME}"
if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then
TAG="${INPUT_TAG}"
fi
RELEASE_NOTES_PATH="${RUNNER_TEMP}/release-notes.md"
echo "TAG=${TAG}" >> "$GITHUB_ENV"
echo "RELEASE_NOTES_PATH=${RELEASE_NOTES_PATH}" >> "$GITHUB_ENV"
- name: Verify tag matches Cargo.toml version
run: ./scripts/verify-release-tag.sh "$TAG"
- name: Verify CHANGELOG.md is current
run: |
tmpfile=$(mktemp)
git cliff --config cliff.toml --output "$tmpfile"
diff -u CHANGELOG.md "$tmpfile"
- name: Dry run publish validation
run: cargo publish --locked --dry-run
- name: Render release notes
run: git cliff --config cliff.toml --current --strip header --output "$RELEASE_NOTES_PATH"
- name: Upload release notes artifact
uses: actions/upload-artifact@v7
with:
name: release-notes-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
path: ${{ env.RELEASE_NOTES_PATH }}
- name: Authenticate to crates.io with trusted publishing
if: github.event_name == 'push' || env.DRY_RUN != 'true'
uses: rust-lang/crates-io-auth-action@v1
id: crates-io-auth
- name: Publish to crates.io
if: github.event_name == 'push' || env.DRY_RUN != 'true'
run: cargo publish --locked
env:
CARGO_REGISTRY_TOKEN: ${{ steps.crates-io-auth.outputs.token }}
- name: Publish GitHub release
if: github.event_name == 'push' || env.DRY_RUN != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if gh release view "$TAG" >/dev/null 2>&1; then
gh release edit "$TAG" --title "$TAG" --notes-file "$RELEASE_NOTES_PATH"
else
gh release create "$TAG" --verify-tag --title "$TAG" --notes-file "$RELEASE_NOTES_PATH"
fi
- name: Report dry-run mode
if: github.event_name == 'workflow_dispatch' && env.DRY_RUN == 'true'
run: |
echo "Dry-run only: crates.io publish and GitHub release update were skipped."
echo "Push the release tag to run the real CI publish with trusted publishing."