Skip to content

Commit a47fce4

Browse files
committed
ci(release): add release workflow with changelog and social notifications
Add comprehensive release workflow that: - Validates version format and ensures it's greater than latest release - Verifies build workflow passed for current commit - Builds release binaries for all 5 platforms - Updates version in Cargo.toml for release builds - Creates archives (tar.gz/zip) and uploads raw binaries - Generates changelog using reusable workflow - Creates GitHub Release with detailed release notes - Creates GitHub Discussion announcement - Posts to Bluesky and LinkedIn Also updates CLAUDE.md with CI/CD documentation. Closes #16
1 parent 02eb6b2 commit a47fce4

2 files changed

Lines changed: 396 additions & 3 deletions

File tree

.github/workflows/release.yml

Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
1+
name: Release
2+
3+
run-name: Release v${{ inputs.version }}
4+
5+
on:
6+
workflow_dispatch:
7+
inputs:
8+
version:
9+
description: 'Version number (e.g., 1.0.0, 0.2.1)'
10+
required: true
11+
type: string
12+
13+
permissions:
14+
contents: write
15+
discussions: write
16+
17+
env:
18+
CARGO_TERM_COLOR: always
19+
20+
jobs:
21+
validate:
22+
name: Validate Release
23+
runs-on: ubuntu-latest
24+
25+
steps:
26+
- name: Checkout code
27+
uses: actions/checkout@v4
28+
with:
29+
fetch-depth: 0
30+
31+
- name: Verify build workflow passed
32+
run: |
33+
echo "Checking if build workflow passed for commit ${{ github.sha }}..."
34+
35+
BUILD_RUNS=$(gh run list \
36+
--workflow=build.yml \
37+
--commit=${{ github.sha }} \
38+
--status=success \
39+
--json databaseId,conclusion \
40+
--jq 'length')
41+
42+
if [ "$BUILD_RUNS" -eq 0 ]; then
43+
echo "::error::No successful build workflow run found for commit ${{ github.sha }}"
44+
echo ""
45+
echo "The build workflow must pass before creating a release."
46+
echo "Please ensure the build workflow has completed successfully for this commit."
47+
echo ""
48+
echo "View workflows: https://github.com/${{ github.repository }}/actions"
49+
exit 1
50+
fi
51+
52+
echo "Build workflow has passed for this commit"
53+
shell: bash
54+
env:
55+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
56+
57+
- name: Validate version format
58+
run: |
59+
VERSION="${{ github.event.inputs.version }}"
60+
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
61+
echo "::error::Version must be in format X.Y.Z (e.g., 1.0.0)"
62+
exit 1
63+
fi
64+
echo "Version format is valid: $VERSION"
65+
shell: bash
66+
67+
- name: Validate version is greater than latest release
68+
run: |
69+
NEW_VERSION="${{ github.event.inputs.version }}"
70+
71+
LATEST_TAG=$(git describe --tags --abbrev=0 --match "v*" 2>/dev/null || echo "")
72+
73+
if [ -z "$LATEST_TAG" ]; then
74+
echo "No previous release tags found"
75+
echo "This will be the first release: v$NEW_VERSION"
76+
exit 0
77+
fi
78+
79+
CURRENT_VERSION="${LATEST_TAG#v}"
80+
echo "Latest release: $CURRENT_VERSION (tag: $LATEST_TAG)"
81+
echo "New version: $NEW_VERSION"
82+
83+
IFS='.' read -r curr_major curr_minor curr_patch <<< "$CURRENT_VERSION"
84+
IFS='.' read -r new_major new_minor new_patch <<< "$NEW_VERSION"
85+
86+
curr_major=$((10#$curr_major))
87+
curr_minor=$((10#$curr_minor))
88+
curr_patch=$((10#$curr_patch))
89+
new_major=$((10#$new_major))
90+
new_minor=$((10#$new_minor))
91+
new_patch=$((10#$new_patch))
92+
93+
if [ $new_major -gt $curr_major ]; then
94+
echo "Version is valid (major version increased: $curr_major -> $new_major)"
95+
elif [ $new_major -eq $curr_major ] && [ $new_minor -gt $curr_minor ]; then
96+
echo "Version is valid (minor version increased: $curr_minor -> $new_minor)"
97+
elif [ $new_major -eq $curr_major ] && [ $new_minor -eq $curr_minor ] && [ $new_patch -gt $curr_patch ]; then
98+
echo "Version is valid (patch version increased: $curr_patch -> $new_patch)"
99+
else
100+
echo "::error::New version ($NEW_VERSION) must be greater than latest release ($CURRENT_VERSION)"
101+
exit 1
102+
fi
103+
shell: bash
104+
105+
build:
106+
name: Build ${{ matrix.platform }}
107+
runs-on: ${{ matrix.os }}
108+
needs: validate
109+
strategy:
110+
matrix:
111+
include:
112+
- os: ubuntu-latest
113+
target: x86_64-unknown-linux-gnu
114+
platform: linux-amd64
115+
binary: rnr
116+
archive_ext: tar.gz
117+
118+
- os: macos-latest
119+
target: x86_64-apple-darwin
120+
platform: macos-amd64
121+
binary: rnr
122+
archive_ext: tar.gz
123+
124+
- os: macos-latest
125+
target: aarch64-apple-darwin
126+
platform: macos-arm64
127+
binary: rnr
128+
archive_ext: tar.gz
129+
130+
- os: windows-latest
131+
target: x86_64-pc-windows-msvc
132+
platform: windows-amd64
133+
binary: rnr.exe
134+
archive_ext: zip
135+
136+
- os: windows-latest
137+
target: aarch64-pc-windows-msvc
138+
platform: windows-arm64
139+
binary: rnr.exe
140+
archive_ext: zip
141+
142+
steps:
143+
- name: Checkout code
144+
uses: actions/checkout@v4
145+
146+
- name: Install Rust toolchain
147+
uses: dtolnay/rust-toolchain@stable
148+
with:
149+
targets: ${{ matrix.target }}
150+
151+
- name: Cache cargo registry
152+
uses: actions/cache@v4
153+
with:
154+
path: |
155+
~/.cargo/registry
156+
~/.cargo/git
157+
target
158+
key: ${{ runner.os }}-${{ matrix.target }}-cargo-release-${{ hashFiles('**/Cargo.lock') }}
159+
restore-keys: |
160+
${{ runner.os }}-${{ matrix.target }}-cargo-release-
161+
162+
- name: Update version in Cargo.toml
163+
run: |
164+
VERSION="${{ github.event.inputs.version }}"
165+
sed -i.bak 's/^version = ".*"/version = "'"$VERSION"'"/' Cargo.toml
166+
rm -f Cargo.toml.bak
167+
echo "Updated Cargo.toml to version $VERSION"
168+
grep "^version" Cargo.toml
169+
shell: bash
170+
171+
- name: Build release binary
172+
run: cargo build --release --target ${{ matrix.target }}
173+
174+
- name: Prepare binary for release
175+
shell: bash
176+
run: |
177+
mkdir -p dist
178+
cp target/${{ matrix.target }}/release/${{ matrix.binary }} dist/rnr-${{ matrix.platform }}${{ matrix.binary == 'rnr.exe' && '.exe' || '' }}
179+
180+
- name: Create archive (Unix)
181+
if: matrix.archive_ext == 'tar.gz'
182+
run: |
183+
cd dist
184+
tar -czf rnr-${{ github.event.inputs.version }}-${{ matrix.platform }}.${{ matrix.archive_ext }} rnr-${{ matrix.platform }}
185+
shell: bash
186+
187+
- name: Create archive (Windows)
188+
if: matrix.archive_ext == 'zip'
189+
run: |
190+
cd dist
191+
7z a rnr-${{ github.event.inputs.version }}-${{ matrix.platform }}.${{ matrix.archive_ext }} rnr-${{ matrix.platform }}.exe
192+
shell: bash
193+
194+
- name: Upload build artifact (archive)
195+
uses: actions/upload-artifact@v4
196+
with:
197+
name: archive-${{ matrix.platform }}
198+
path: dist/rnr-${{ github.event.inputs.version }}-${{ matrix.platform }}.${{ matrix.archive_ext }}
199+
retention-days: 1
200+
201+
- name: Upload build artifact (raw binary)
202+
uses: actions/upload-artifact@v4
203+
with:
204+
name: binary-${{ matrix.platform }}
205+
path: dist/rnr-${{ matrix.platform }}${{ matrix.binary == 'rnr.exe' && '.exe' || '' }}
206+
retention-days: 1
207+
208+
changelog:
209+
name: Generate Changelog
210+
needs: build
211+
uses: CodingWithCalvin/.github/.github/workflows/generate-changelog.yml@main
212+
secrets: inherit
213+
214+
release:
215+
name: Create GitHub Release
216+
runs-on: ubuntu-latest
217+
needs: [build, changelog]
218+
219+
steps:
220+
- name: Checkout code
221+
uses: actions/checkout@v4
222+
223+
- name: Download all artifacts
224+
uses: actions/download-artifact@v4
225+
with:
226+
path: artifacts
227+
228+
- name: List artifacts
229+
run: find artifacts -type f | head -50
230+
shell: bash
231+
232+
- name: Create and push release tag
233+
run: |
234+
VERSION="${{ github.event.inputs.version }}"
235+
git config user.name "github-actions[bot]"
236+
git config user.email "github-actions[bot]@users.noreply.github.com"
237+
238+
git tag -a "v$VERSION" -m "Release v$VERSION"
239+
git push origin "v$VERSION"
240+
echo "Created and pushed tag v$VERSION"
241+
shell: bash
242+
243+
- name: Create GitHub Release
244+
uses: softprops/action-gh-release@v1
245+
with:
246+
tag_name: v${{ github.event.inputs.version }}
247+
name: v${{ github.event.inputs.version }}
248+
files: |
249+
artifacts/archive-linux-amd64/rnr-${{ github.event.inputs.version }}-linux-amd64.tar.gz
250+
artifacts/archive-macos-amd64/rnr-${{ github.event.inputs.version }}-macos-amd64.tar.gz
251+
artifacts/archive-macos-arm64/rnr-${{ github.event.inputs.version }}-macos-arm64.tar.gz
252+
artifacts/archive-windows-amd64/rnr-${{ github.event.inputs.version }}-windows-amd64.zip
253+
artifacts/archive-windows-arm64/rnr-${{ github.event.inputs.version }}-windows-arm64.zip
254+
artifacts/binary-linux-amd64/rnr-linux-amd64
255+
artifacts/binary-macos-amd64/rnr-macos-amd64
256+
artifacts/binary-macos-arm64/rnr-macos-arm64
257+
artifacts/binary-windows-amd64/rnr-windows-amd64.exe
258+
artifacts/binary-windows-arm64/rnr-windows-arm64.exe
259+
body: |
260+
## What's New in v${{ github.event.inputs.version }}
261+
262+
${{ needs.changelog.outputs.changelog }}
263+
264+
## Installation
265+
266+
### Initialize a Project (Recommended)
267+
268+
Download the binary for your platform, then run `init` to set up your project:
269+
270+
```bash
271+
# Example for macOS ARM64
272+
curl -LO https://github.com/${{ github.repository }}/releases/download/v${{ github.event.inputs.version }}/rnr-macos-arm64
273+
chmod +x rnr-macos-arm64
274+
./rnr-macos-arm64 init
275+
```
276+
277+
This creates the `.rnr/` directory with binaries for your selected platforms, wrapper scripts, and a starter `rnr.yaml`.
278+
279+
### Manual Download
280+
281+
Download the appropriate binary or archive for your platform from the assets below.
282+
283+
## Binary Assets
284+
285+
| Platform | Binary | Archive |
286+
|----------|--------|---------|
287+
| Linux x86_64 | `rnr-linux-amd64` | `rnr-${{ github.event.inputs.version }}-linux-amd64.tar.gz` |
288+
| macOS x86_64 | `rnr-macos-amd64` | `rnr-${{ github.event.inputs.version }}-macos-amd64.tar.gz` |
289+
| macOS ARM64 | `rnr-macos-arm64` | `rnr-${{ github.event.inputs.version }}-macos-arm64.tar.gz` |
290+
| Windows x86_64 | `rnr-windows-amd64.exe` | `rnr-${{ github.event.inputs.version }}-windows-amd64.zip` |
291+
| Windows ARM64 | `rnr-windows-arm64.exe` | `rnr-${{ github.event.inputs.version }}-windows-arm64.zip` |
292+
293+
## Supported Platforms
294+
295+
- Linux (x86_64)
296+
- macOS (x86_64, ARM64/Apple Silicon)
297+
- Windows (x86_64, ARM64)
298+
draft: false
299+
prerelease: false
300+
generate_release_notes: false
301+
env:
302+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
303+
304+
notify-discussion:
305+
name: Create GitHub Discussion
306+
needs: release
307+
uses: CodingWithCalvin/.github/.github/workflows/github-discussion.yml@main
308+
with:
309+
title: "rnr v${{ github.event.inputs.version }} has been released!"
310+
body: |
311+
## Changes in this release
312+
313+
See the [full changelog](https://github.com/${{ github.repository }}/releases/tag/v${{ github.event.inputs.version }}) for details on what's new in this release.
314+
315+
## What is rnr?
316+
317+
**rnr** (pronounced "runner") is a cross-platform task runner that works instantly on any machine. No Node.js. No Python. No global installs. Just clone and go.
318+
319+
## Quick Start
320+
321+
```bash
322+
git clone your-repo
323+
./rnr build # It just works!
324+
```
325+
326+
## Installation
327+
328+
Download the binary for your platform from the [release page](https://github.com/${{ github.repository }}/releases/tag/v${{ github.event.inputs.version }}), then run `./rnr init` to set up your project.
329+
330+
---
331+
332+
[View Release](https://github.com/${{ github.repository }}/releases/tag/v${{ github.event.inputs.version }}) | [Documentation](https://github.com/${{ github.repository }})
333+
334+
notify-bluesky:
335+
name: Post to Bluesky
336+
needs: notify-discussion
337+
uses: CodingWithCalvin/.github/.github/workflows/bluesky-post.yml@main
338+
with:
339+
post_text: |
340+
rnr v${{ github.event.inputs.version }} is now available!
341+
342+
Cross-platform task runner with zero setup - clone a repo and tasks just work!
343+
344+
#rnr #taskrunner #devtools #rust #opensource
345+
346+
[Release Notes](https://github.com/${{ github.repository }}/releases/tag/v${{ github.event.inputs.version }})
347+
[Discussion](${{ needs.notify-discussion.outputs.discussion_url }})
348+
embed_url: https://github.com/${{ github.repository }}/releases/tag/v${{ github.event.inputs.version }}
349+
embed_title: rnr v${{ github.event.inputs.version }}
350+
embed_description: Cross-platform task runner with zero setup
351+
secrets:
352+
BLUESKY_USERNAME: ${{ secrets.BLUESKY_USERNAME }}
353+
BLUESKY_APP_PASSWORD: ${{ secrets.BLUESKY_APP_PASSWORD }}
354+
355+
notify-linkedin:
356+
name: Post to LinkedIn
357+
needs: notify-discussion
358+
uses: CodingWithCalvin/.github/.github/workflows/linkedin-post.yml@main
359+
with:
360+
post_text: |
361+
rnr v${{ github.event.inputs.version }} is now available!
362+
363+
Cross-platform task runner with zero setup - clone a repo and tasks just work!
364+
365+
#rnr #taskrunner #devtools #rust #opensource
366+
367+
Release Notes: https://github.com/${{ github.repository }}/releases/tag/v${{ github.event.inputs.version }}
368+
Discussion: ${{ needs.notify-discussion.outputs.discussion_url }}
369+
article_url: https://github.com/${{ github.repository }}/releases/tag/v${{ github.event.inputs.version }}
370+
article_title: rnr v${{ github.event.inputs.version }}
371+
article_description: Cross-platform task runner with zero setup
372+
secrets:
373+
LINKEDIN_ACCESS_TOKEN: ${{ secrets.LINKEDIN_ACCESS_TOKEN }}
374+
LINKEDIN_CLIENT_ID: ${{ secrets.LINKEDIN_CLIENT_ID }}

0 commit comments

Comments
 (0)