name: Release
on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
tag:
description: "Release tag (e.g., v0.3.0)"
required: true
type: string
env:
RUST_BACKTRACE: 1
CARGO_TERM_COLOR: always
RUSTFLAGS: -Dwarnings
jobs:
prepare:
name: Prepare Release
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.tag.outputs.tag }}
version: ${{ steps.tag.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Determine tag
id: tag
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
TAG="${{ inputs.tag }}"
else
TAG="${{ github.ref_name }}"
fi
# Validate tag format
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+.*$ ]]; then
echo "::error::Invalid tag format: $TAG (expected vX.Y.Z)"
exit 1
fi
VERSION="${TAG#v}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Verify Cargo.toml version matches tag
run: |
CARGO_VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
if [ "$CARGO_VERSION" != "${{ steps.tag.outputs.version }}" ]; then
echo "::error::Cargo.toml version ($CARGO_VERSION) does not match tag (${{ steps.tag.outputs.version }})"
exit 1
fi
echo "Version verified: $CARGO_VERSION"
validate:
name: Validate Release
runs-on: ubuntu-latest
needs: prepare
steps:
- uses: actions/checkout@v5
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- name: Check formatting
run: cargo fmt -- --check
- name: Run clippy
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Run all tests
run: cargo test --verbose
- name: Build release
run: cargo build --release --verbose
- name: Verify WASM compatibility
run: |
rustup target add wasm32-unknown-unknown
cargo build --target wasm32-unknown-unknown --release
- name: Check documentation
run: cargo doc --no-deps --all-features
publish-check:
name: Cargo Publish (Dry Run)
runs-on: ubuntu-latest
needs: [prepare, validate]
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Cargo publish dry run
run: cargo publish --dry-run
github-release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs: [prepare, publish-check]
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create ${{ needs.prepare.outputs.tag }} \
--title "${{ needs.prepare.outputs.tag }}" \
--generate-notes
publish:
name: Publish to crates.io
runs-on: ubuntu-latest
needs: [prepare, github-release]
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Publish to crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish