1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
name: Release
# Automated semantic release. On every push to main (i.e. when a PR merges),
# cocogitto reads the Conventional Commits since the last tag, computes the next
# version, bumps Cargo.toml, updates CHANGELOG.md, and creates the version commit
# and tag. The tag then drives the GitHub Release and (when configured) the
# crates.io publish. No release PR — the tag is created automatically.
on:
push:
branches:
workflow_dispatch:
# Needed to push the bump commit + tag and to create the GitHub Release.
permissions:
contents: write
# Never run two releases at once, and never cancel one mid-publish.
concurrency:
group: release
cancel-in-progress: false
env:
CARGO_TERM_COLOR: always
jobs:
release:
name: Semantic release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # full history so cocogitto can analyse every commit
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
# Prebuilt binaries — fast, no compilation.
# --force: rust-cache restores ~/.cargo/.crates.toml (the "installed" registry)
# but not the binaries themselves, so without --force binstall sees cocogitto as
# "already installed" and skips it, leaving `cog` missing on PATH.
- uses: cargo-bins/cargo-binstall@main
- name: Install cocogitto and cargo-edit
run: cargo binstall -y --force cocogitto cargo-edit
- name: Configure git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Bump version, update changelog, and tag
id: bump
run: |
# Fail loudly if the tool is missing, rather than mistaking it for
# "no releasable changes" below and exiting green without releasing.
command -v cog >/dev/null || { echo "::error::cocogitto (cog) is not installed"; exit 1; }
before=$(git describe --tags --abbrev=0 2>/dev/null || echo "none")
# `cog bump --auto` exits non-zero when there are no releasable commits.
if cog bump --auto; then
tag=$(git describe --tags --abbrev=0)
echo "released=true" >> "$GITHUB_OUTPUT"
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo "Released $tag (previous: $before)"
else
echo "released=false" >> "$GITHUB_OUTPUT"
echo "No releasable changes since $before — skipping release."
fi
- name: Push commit and tag
if: steps.bump.outputs.released == 'true'
run: git push --follow-tags origin HEAD:main
# Publishing only runs once a crates.io token is configured, so a pre-release
# stub is never published by accident. Add the CARGO_REGISTRY_TOKEN secret to
# enable it (Settings → Secrets and variables → Actions).
- name: Check for crates.io token
if: steps.bump.outputs.released == 'true'
id: cratesio
env:
TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
if [ -n "$TOKEN" ]; then
echo "enabled=true" >> "$GITHUB_OUTPUT"
else
echo "enabled=false" >> "$GITHUB_OUTPUT"
echo "CARGO_REGISTRY_TOKEN not set — skipping crates.io publish."
fi
# Publish before creating the GitHub Release, so the crates.io link added to
# the release notes is already valid when the release goes live.
- name: Publish to crates.io
if: steps.bump.outputs.released == 'true' && steps.cratesio.outputs.enabled == 'true'
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish
- name: Extract release notes
if: steps.bump.outputs.released == 'true'
# Take the newest section of the changelog cocogitto just wrote (from the
# first "## " heading up to the next one). Avoids re-parsing old, possibly
# non-conventional history, which `cog changelog --at` would choke on.
# Write outside the repo so the working tree stays clean for cargo publish.
# When the crate was published, append a link to the crates.io release.
run: |
notes="$RUNNER_TEMP/RELEASE_NOTES.md"
awk '/^## /{n++} n==1{print} n==2{exit}' CHANGELOG.md > "$notes"
if [ "${{ steps.cratesio.outputs.enabled }}" = "true" ]; then
version="${{ steps.bump.outputs.tag }}"
version="${version#v}"
printf '\n📦 **crates.io:** [holocron %s](https://crates.io/crates/holocron/%s)\n' "$version" "$version" >> "$notes"
fi
- name: Create GitHub Release
if: steps.bump.outputs.released == 'true'
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.bump.outputs.tag }}
body_path: ${{ runner.temp }}/RELEASE_NOTES.md