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
env:
CRATE_NAME: zipatch-rs
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 git tag
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."
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi
- name: Check GitHub release
id: release-check
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
tag="v${{ steps.version.outputs.version }}"
if gh release view "$tag" >/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "GitHub release $tag already exists."
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi
- name: Check crates.io
id: crates-check
run: |
version="${{ steps.version.outputs.version }}"
# crates.io requires a descriptive User-Agent with contact info,
# otherwise the API returns 403 (data-access policy).
status=$(curl -sS -o /tmp/crate.json -w "%{http_code}" \
-A "${CRATE_NAME}-release-workflow (https://github.com/${{ github.repository }})" \
"https://crates.io/api/v1/crates/${CRATE_NAME}/${version}")
if [ "$status" = "200" ]; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "crates.io ${CRATE_NAME} ${version} already published."
elif [ "$status" = "404" ]; then
echo "exists=false" >> "$GITHUB_OUTPUT"
else
echo "Unexpected status $status from crates.io API:"
cat /tmp/crate.json
exit 1
fi
- name: Summarise plan
run: |
echo "tag v${{ steps.version.outputs.version }}: exists=${{ steps.tag-check.outputs.exists }}"
echo "release v${{ steps.version.outputs.version }}: exists=${{ steps.release-check.outputs.exists }}"
echo "crates.io ${CRATE_NAME} ${{ steps.version.outputs.version }}: exists=${{ steps.crates-check.outputs.exists }}"
- name: Set up Rust
if: steps.crates-check.outputs.exists == 'false'
uses: dtolnay/rust-toolchain@stable
- name: Cache Rust
if: steps.crates-check.outputs.exists == 'false'
uses: Swatinem/rust-cache@v2.9.1
- name: Verify build with dry-run
if: steps.crates-check.outputs.exists == 'false'
run: cargo publish --dry-run --all-features
- name: Extract changelog section
if: steps.release-check.outputs.exists == 'false'
id: changelog
run: |
version="${{ steps.version.outputs.version }}"
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
end=$(tail -n +$((start + 1)) CHANGELOG.md | grep -n "^## \[" | head -1 | cut -d: -f1)
if [ -z "$end" ]; then
sed -n "${start},\$p" CHANGELOG.md > /tmp/release_notes.txt
else
end=$((start + end - 1))
sed -n "${start},$((end - 1))p" CHANGELOG.md > /tmp/release_notes.txt
fi
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.release-check.outputs.exists == 'false'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create \
"${{ steps.version.outputs.tag }}" \
--notes-file "${{ steps.changelog.outputs.notes_file }}"
- name: Publish to crates.io
if: steps.crates-check.outputs.exists == 'false'
run: cargo publish --all-features
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}