name: Release
on:
push:
tags: ['v*.*.*']
workflow_dispatch:
inputs:
dry_run:
description: 'Dry run — run the full pipeline without publishing'
required: false
default: 'true'
type: choice
options:
- 'true'
- 'false'
env:
CARGO_TERM_COLOR: always
NEXTEST_PROFILE: ci
RUSTFLAGS: -D warnings
permissions:
contents: write
defaults:
run:
shell: bash
jobs:
gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt, llvm-tools-preview
- uses: Swatinem/rust-cache@v2
- name: Test
run: cargo test
- name: Clippy
run: cargo clippy --all-targets -- -D warnings
- name: Format
run: cargo fmt -- --check
- name: Doc
env:
RUSTDOCFLAGS: -D warnings
run: cargo doc --no-deps
- name: Deny
uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check
publish:
needs: gate
runs-on: ubuntu-latest
if: >-
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v'))
|| (github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'false')
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Verify tag matches Cargo.toml version
run: |
tag="${GITHUB_REF_NAME#v}"
cargo_version=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
if [ "$tag" != "$cargo_version" ]; then
echo "::error::Tag v$tag does not match Cargo.toml version $cargo_version"
exit 1
fi
- name: Publish to crates.io
run: cargo publish --token "${{ secrets.CRATES_IO_TOKEN }}"
github-release:
needs: publish
runs-on: ubuntu-latest
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate release notes from commits
id: notes
run: |
prev_tag=$(git tag --sort=-v:refname | grep '^v' | sed -n '2p')
if [ -z "$prev_tag" ]; then
log=$(git log --oneline)
else
log=$(git log --oneline "$prev_tag"..HEAD)
fi
{
echo "body<<RELEASE_EOF"
echo "$log"
echo "RELEASE_EOF"
} >> "$GITHUB_OUTPUT"
- uses: softprops/action-gh-release@v2
with:
body: ${{ steps.notes.outputs.body }}
generate_release_notes: true