name: Release
on:
push:
tags:
- "v*"
env:
CARGO_TERM_COLOR: always
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
validate-version:
name: Validate version matches tag
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Check version matches tag
run: |
CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | cut -d'"' -f2)
TAG_VERSION=${GITHUB_REF_NAME#v} # strips the 'v' prefix
if [ "$CARGO_VERSION" != "$TAG_VERSION" ]; then
echo "::error::Version mismatch: Cargo.toml has '$CARGO_VERSION' but tag is '$TAG_VERSION'"
exit 1
fi
echo "Version validated: $CARGO_VERSION"
test:
name: Test before release
runs-on: macos-26
timeout-minutes: 10
needs: validate-version
steps:
- uses: actions/checkout@v5
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
cache-targets: true
cache-all-crates: true
- name: Check formatting
run: cargo fmt --all -- --check
- name: Run clippy
run: cargo clippy --all-targets --all-features -- -D warnings
publish:
name: Publish to crates.io
runs-on: macos-26
timeout-minutes: 10
environment: release permissions:
id-token: write needs: test
steps:
- uses: actions/checkout@v5
- uses: rust-lang/crates-io-auth-action@v1
id: auth
- run: cargo publish
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
create-release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs: publish
permissions:
contents: write
steps:
- uses: actions/checkout@v5
- name: Create Release
run: |
gh release create ${{ github.ref_name }} \
--title "${{ github.ref_name }}" \
--generate-notes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}