name: Publish
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
env:
CARGO_TERM_COLOR: always
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
check-branch:
name: Check tag is on main
runs-on: ubuntu-latest
outputs:
on_main: ${{ steps.check.outputs.on_main }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check tag is on main
id: check
run: |
git fetch origin main
if git merge-base --is-ancestor ${{ github.sha }} origin/main; then
echo "on_main=true" >> "$GITHUB_OUTPUT"
else
echo "Tag is not on main — skipping publish."
echo "on_main=false" >> "$GITHUB_OUTPUT"
fi
publish:
name: Publish to crates.io
needs: check-branch
if: needs.check-branch.outputs.on_main == 'true'
runs-on: ubuntu-latest
permissions:
id-token: write attestations: write contents: write steps:
- uses: actions/checkout@v4
- name: Install stable toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Cache
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-
- name: Format check
run: cargo fmt --check
- name: Clippy
run: cargo clippy --locked -- -D warnings
- name: Docs
run: cargo rustdoc --locked -- -D missing_docs
- name: Test (std)
run: cargo test --locked
- name: Test (std + serde)
run: cargo test --locked --features serde
- name: Build (alloc only)
run: cargo build --locked --no-default-features --features alloc
- name: Build (no_std)
run: cargo build --locked --no-default-features
- name: Package crate
run: cargo package
- name: Attest crate provenance
uses: actions/attest-build-provenance@v2
with:
subject-path: target/package/*.crate
- name: Publish to crates.io
run: |
NAME=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].name')
VERSION=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
"https://crates.io/api/v1/crates/$NAME/$VERSION")
if [ "$STATUS" = "200" ]; then
echo "$NAME $VERSION is already on crates.io — skipping (first release or manual publish)."
exit 0
fi
cargo publish --no-verify # quality gates already ran above
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Create GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ github.ref_name }}"
VER="${TAG#v}"
NAME=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].name')
CRATE_FILE=$(ls target/package/*.crate)
CRATE_BASENAME=$(basename "$CRATE_FILE")
cat > /tmp/release-notes.md <<NOTES
## Provenance verification
The \`.crate\` package below was built and attested with
[SLSA Build Level 2](https://slsa.dev) provenance during this
GitHub Actions run. Verify with the
[GitHub CLI](https://cli.github.com/):
\`\`\`sh
gh attestation verify ${CRATE_BASENAME} --repo ${{ github.repository }}
\`\`\`
The same package is published to
[crates.io/crates/${NAME}/${VER}](https://crates.io/crates/${NAME}/${VER}).
NOTES
gh release create "$TAG" \
--title "$TAG" \
--notes-file /tmp/release-notes.md \
"$CRATE_FILE"