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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
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
# Expose whether a release happened and its tag, so the binary-upload job
# below can run only on a real release and target the right tag.
outputs:
released: ${{ steps.bump.outputs.released }}
tag: ${{ steps.bump.outputs.tag }}
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@v3
with:
tag_name: ${{ steps.bump.outputs.tag }}
body_path: ${{ runner.temp }}/RELEASE_NOTES.md
# Build the holocron CLI for each platform and attach the archives to the
# GitHub Release. Runs only when a release was actually cut.
# (holocron-lsp lives in its own repo: holocron-lang/holocron-lsp.)
upload-binaries:
name: Upload binary (${{ matrix.target }})
needs: release
if: needs.release.outputs.released == 'true'
permissions:
contents: write # to upload release assets
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
- target: aarch64-unknown-linux-gnu
os: ubuntu-latest
- target: x86_64-apple-darwin
os: macos-latest
- target: aarch64-apple-darwin
os: macos-latest
- target: x86_64-pc-windows-msvc
os: windows-latest
runs-on: ${{ matrix.os }}
steps:
# Check out the released tag so the binary matches the published version.
- uses: actions/checkout@v7
with:
ref: refs/tags/${{ needs.release.outputs.tag }}
- name: Build and upload binary
uses: taiki-e/upload-rust-binary-action@v1
with:
bin: holocron
target: ${{ matrix.target }}
# Archive name, e.g. holocron-v0.4.0-x86_64-unknown-linux-gnu.tar.gz
archive: $bin-$tag-$target
# Attach to the release for the tag we just created.
ref: refs/tags/${{ needs.release.outputs.tag }}
# Also bundle these files and publish a SHA-256 checksum per archive.
include: LICENSE,README.md
checksum: sha256
token: ${{ secrets.GITHUB_TOKEN }}