name: Release
on:
push:
tags:
- 'v*'
env:
CARGO_TERM_COLOR: always
jobs:
release:
name: Create Release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 with:
fetch-depth: 0
- name: Verify tag is reachable from master
env:
TAG_SHA: ${{ github.sha }}
TAG_REF: ${{ github.ref_name }}
run: |
# Fail closed if the tag points at a commit that is not an ancestor
# of master. This ensures every release has been through the master
# branch (and therefore the PR / CI gate), even if an attacker with
# tag-push rights tries to release off a side branch.
git fetch --no-tags origin "+refs/heads/master:refs/remotes/origin/master"
if ! git merge-base --is-ancestor "$TAG_SHA" origin/master; then
echo "::error::tag ${TAG_REF} (${TAG_SHA}) is not reachable from origin/master; refusing to release"
exit 1
fi
- name: Extract changelog for this version
env:
TAG: ${{ github.ref_name }}
run: |
VERSION="${TAG#v}"
# `awk -v` binds `target` as data, never program text; `index(...) == 1`
# is a literal prefix match (no regex interpretation of VERSION).
awk -v target="## [${VERSION}]" '
index($0, target) == 1 { found = 1; next }
/^## \[/ { if (found) exit }
found
' CHANGELOG.md > RELEASE_NOTES.md
if [ ! -s RELEASE_NOTES.md ]; then
echo "No CHANGELOG entry for ${VERSION}, falling back to auto-generated notes." > RELEASE_NOTES.md
fi
- name: Create Release
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 with:
body_path: RELEASE_NOTES.md
publish-crate:
name: Publish to crates.io
needs: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - name: Install Rust
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 - name: Publish
run: cargo publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}