name: Release
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: write
concurrency:
group: release
cancel-in-progress: false
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6.0.2
with:
fetch-depth: 0
- name: Extract version from Cargo.toml
id: version
run: |
version=$(grep '^version' Cargo.toml | head -1 | sed -E 's/version = "(.*)"/\1/')
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "tag=v$version" >> "$GITHUB_OUTPUT"
- name: Check if tag already exists
id: tag-check
run: |
tag="v${{ steps.version.outputs.version }}"
if git rev-parse "$tag" >/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "Tag $tag already exists, skipping release."
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi
- name: Set up Rust
if: steps.tag-check.outputs.exists == 'false'
uses: dtolnay/rust-toolchain@stable
- name: Cache Rust
if: steps.tag-check.outputs.exists == 'false'
uses: Swatinem/rust-cache@v2.9.1
- name: Verify build with dry-run
if: steps.tag-check.outputs.exists == 'false'
run: cargo publish --dry-run --all-features
- name: Extract changelog section
if: steps.tag-check.outputs.exists == 'false'
id: changelog
run: |
version="${{ steps.version.outputs.version }}"
# Find the line number of this version and the next version header
start=$(grep -n "^## \[$version\]" CHANGELOG.md | head -1 | cut -d: -f1)
if [ -z "$start" ]; then
echo "Error: Could not find version $version in CHANGELOG.md"
exit 1
fi
# Find the next "## [" after the start line
end=$(tail -n +$((start + 1)) CHANGELOG.md | grep -n "^## \[" | head -1 | cut -d: -f1)
if [ -z "$end" ]; then
# No next section, extract to end of file
sed -n "${start},\$p" CHANGELOG.md > /tmp/release_notes.txt
else
# Extract from start to before the next section
end=$((start + end - 1))
sed -n "${start},$((end - 1))p" CHANGELOG.md > /tmp/release_notes.txt
fi
# Validate that we got content
if [ ! -s /tmp/release_notes.txt ]; then
echo "Error: Could not extract changelog section for version $version"
exit 1
fi
echo "notes_file=/tmp/release_notes.txt" >> "$GITHUB_OUTPUT"
- name: Create git tag
if: steps.tag-check.outputs.exists == 'false'
run: |
git tag "${{ steps.version.outputs.tag }}"
git push origin "${{ steps.version.outputs.tag }}"
- name: Create GitHub release
if: steps.tag-check.outputs.exists == 'false'
run: |
gh release create \
"${{ steps.version.outputs.tag }}" \
--notes-file "${{ steps.changelog.outputs.notes_file }}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish to crates.io
if: steps.tag-check.outputs.exists == 'false'
run: cargo publish --all-features
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}