---
name: Release
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: "Tag to release (e.g. v0.6.7)"
required: true
permissions:
actions: read
contents: write
jobs:
release:
runs-on: ubuntu-latest
permissions:
actions: read
contents: write
id-token: write
steps:
- name: Resolve tag
id: tag
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "name=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT"
else
echo "name=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
fi
- uses: actions/checkout@v5
with:
ref: ${{ steps.tag.outputs.name }}
- name: Wait for CI to pass for this tag
uses: actions/github-script@v9
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const workflowName = "CI";
const tag = "${{ steps.tag.outputs.name }}";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const maxAttempts = 40;
const delayMs = 15000;
for (let i = 1; i <= maxAttempts; i++) {
const runs = await github.paginate(github.rest.actions.listWorkflowRunsForRepo, {
owner,
repo,
event: "push",
per_page: 100,
});
const ciRun = runs.find((r) => r.name === workflowName && r.head_branch === tag);
if (!ciRun) {
core.info(`Attempt ${i}/${maxAttempts}: CI run not found yet for tag ${tag}.`);
await sleep(delayMs);
continue;
}
core.info(
`Attempt ${i}/${maxAttempts}: CI run ${ciRun.id} status=${ciRun.status} conclusion=${ciRun.conclusion}`
);
if (ciRun.status !== "completed") {
await sleep(delayMs);
continue;
}
if (ciRun.conclusion !== "success") {
core.setFailed(`CI workflow concluded with '${ciRun.conclusion}'. Aborting release.`);
return;
}
core.info("CI passed for this tag. Proceeding with release.");
return;
}
core.setFailed("Timed out waiting for CI workflow completion.");
- name: Install Rust
run: rustup toolchain install stable
- name: Authenticate to crates.io with trusted publishing
id: auth
uses: rust-lang/crates-io-auth-action@v1.0.4
- name: Publish to crates.io
run: cargo publish
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
- name: Create GitHub release
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ steps.tag.outputs.name }}
generate_release_notes: true
prerelease: ${{ contains(steps.tag.outputs.name, '-') }}