Skip to content

Commit b3289b8

Browse files
committed
ci: Automatic releases on push to main
Replace manual tag-based releases with automatic version detection and release creation when pushing to main. The workflow: 1. Checks for release-worthy changes using git-cliff (looks for conventional commits like feat:, fix:, etc.) 2. Detects version bump type from commit footers (bump: major/minor) 3. Calculates next semantic version using git-cliff 4. Builds all artifacts with the correct version baked in 5. Creates git tag and GitHub release with changelog Changes: - build-main.yml: New workflow with check-release, create-tag, and release jobs with proper dependency chain - detect-bump.sh: New script to parse commits for bump: footers - build-tag.yml: Removed old manual tag-triggered release workflow - build-branches.yml: Exclude main branch (now handled by build-main) - _release.yml: Removed redundant permissions block (inherited from caller) - cliff.toml: Updated to match mdnx style - emojis in group names, striptags in template, commit hashes in entries, better comments, removed unnecessary defaults - release-notes.sh: New script to preview release notes locally Prompts: - "bring the release pipeline into line with mdnx - https://github.com/timabell/markdown-neuraxis see commits 19b4fb1 1dbd0e9 4e5db24" - "remove unused yml" (re: build-tag.yml) - "i was thinking more _release" (clarified _release.yml is still used) - "never mind then. commit" - "oh hang on, i want the release-notes.sh too" - "cliff.toml needs brining up to standard with the mdnx one" - "./release-notes.sh --preview is showing ci and chores which aren't in the list" - "commit" Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Plus some manual tweaking of the cliff config
1 parent c13c56c commit b3289b8

7 files changed

Lines changed: 175 additions & 47 deletions

File tree

.github/cliff.toml

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,44 @@
11
[changelog]
2-
header = """
3-
# Changelog\n
4-
"""
2+
header = ""
53
body = """
6-
{% if version %}\
7-
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
8-
{% else %}\
9-
## [unreleased]
4+
{% if version %}## {{ version }}
5+
{% else %}## [unreleased]
106
{% endif %}\
117
{% for group, commits in commits | group_by(attribute="group") %}
12-
### {{ group | upper_first }}
13-
{% for commit in commits %}
14-
- {{ commit.message | upper_first }}\
15-
{% endfor %}
16-
{% endfor %}\n
8+
### {{ group | striptags | trim }}
9+
10+
{% for commit in commits %}\
11+
- {{ commit.message | upper_first }} ({{ commit.id | truncate(length=7, end="") }})
12+
{% endfor %}\
13+
{% endfor %}
1714
"""
15+
footer = ""
1816
trim = true
1917

2018
[git]
2119
conventional_commits = true
22-
filter_unconventional = true # ignore commits with "prefix:"
20+
# true = filter out non-conventional commits (those without "type: message" format)
21+
filter_unconventional = true
22+
# false = keep each commit as one entry; true = split multi-paragraph commits
2323
split_commits = false
24+
25+
# Match commit message prefixes to assign groups (change types).
26+
# First matching parser wins.
27+
# HTML comments control sort order, removed by striptags in template.
2428
commit_parsers = [
25-
{ message = "^security", group = "<!--0-->Security"},
26-
{ message = "^feat", group = "<!--1-->Features"},
27-
{ message = "^fix", group = "<!--2-->Bug Fixes"},
28-
{ message = "^perf", group = "<!--3-->Performance"},
29-
{ message = "^chore", group = "<!--4-->Chores"},
30-
{ message = "^refactor", group = "<!--5-->Refactor"},
31-
{ message = "^doc", group = "<!--6-->Documentation"},
29+
{ message = "^feat", group = "<!-- 1 -->✨ Features" },
30+
{ message = "^fix", group = "<!-- 2 -->🐛 Bug Fixes" },
31+
{ message = "^refactor", group = "<!-- 3 -->🔧 Refactor" },
32+
{ message = "^perf", group = "<!-- 4 -->Performance" },
33+
{ message = "^doc", group = "<!-- 5 -->📚 Documentation" },
34+
{ message = "^security", group = "<!-- 6 -->🔒 Security" },
35+
{ message = "^chore", group = "<!-- 6 -->🧹 Chore" },
3236
]
33-
protect_breaking_commits = false
34-
filter_commits = true # ignore prefixes not listed above
37+
38+
# true = exclude commits that don't match any parser (only release-worthy commits)
39+
# false = include commits that don't match any parser (as ungrouped)
40+
filter_commits = true
41+
3542
tag_pattern = "v[0-9]*"
36-
skip_tags = "v[0-9]*-.*" # ignore pre-release semver tags
37-
ignore_tags = ""
38-
topo_order = false
39-
sort_commits = "oldest"
43+
# Skip pre-release tags like v1.0.0-beta.1
44+
skip_tags = "v[0-9]*-.*"

.github/workflows/_release.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ env:
1717
jobs:
1818
release:
1919
runs-on: ubuntu-latest
20-
permissions:
21-
contents: write
2220
steps:
2321
- uses: actions/checkout@v6
2422
with:

.github/workflows/build-branches.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44
push:
55
branches:
66
- "**"
7+
- "!main"
78

89
jobs:
910
ci:

.github/workflows/build-main.yml

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
name: Build Main
2+
3+
on:
4+
push:
5+
branches: [main]
6+
7+
# Prevent concurrent releases racing to create the same version tag
8+
concurrency:
9+
group: release
10+
cancel-in-progress: false
11+
12+
permissions:
13+
contents: write
14+
15+
jobs:
16+
check-release:
17+
runs-on: ubuntu-latest
18+
outputs:
19+
should_release: ${{ steps.check_changes.outputs.should_release }}
20+
release_tag: ${{ steps.version_outputs.outputs.tag }}
21+
steps:
22+
- uses: actions/checkout@v6
23+
with:
24+
fetch-depth: 0
25+
26+
- name: Generate changelog
27+
id: changelog
28+
uses: orhun/git-cliff-action@v4
29+
with:
30+
config: .github/cliff.toml
31+
args: --unreleased --strip header
32+
33+
- name: Check for release-worthy changes
34+
id: check_changes
35+
# git-cliff outputs ### section headers only when there are matching commits
36+
# (## [unreleased] header is always output, so we check for ### specifically)
37+
run: |
38+
if echo "${{ steps.changelog.outputs.content }}" | grep -q '###'; then
39+
echo "Release-worthy changes found"
40+
echo "should_release=true" >> $GITHUB_OUTPUT
41+
else
42+
echo "No release-worthy changes found"
43+
echo "should_release=false" >> $GITHUB_OUTPUT
44+
fi
45+
46+
- name: Detect bump type
47+
if: steps.check_changes.outputs.should_release == 'true'
48+
id: bump
49+
run: |
50+
BUMP_TYPE=$(.github/workflows/detect-bump.sh)
51+
echo "type=$BUMP_TYPE" >> $GITHUB_OUTPUT
52+
echo "Bump type: $BUMP_TYPE"
53+
54+
- name: Calculate next version
55+
if: steps.check_changes.outputs.should_release == 'true'
56+
id: version
57+
uses: orhun/git-cliff-action@v4
58+
with:
59+
config: .github/cliff.toml
60+
args: --bump ${{ steps.bump.outputs.type }} --bumped-version
61+
env:
62+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
63+
64+
- name: Set version outputs
65+
if: steps.check_changes.outputs.should_release == 'true'
66+
id: version_outputs
67+
run: |
68+
VERSION="${{ steps.version.outputs.content }}"
69+
VERSION="${VERSION#v}" # Remove v prefix if present
70+
VERSION="${VERSION%%$'\n'*}" # Remove any trailing newlines
71+
echo "version=$VERSION" >> $GITHUB_OUTPUT
72+
echo "tag=v$VERSION" >> $GITHUB_OUTPUT
73+
echo "Version: $VERSION, Tag: v$VERSION"
74+
75+
ci:
76+
needs: check-release
77+
uses: ./.github/workflows/_ci.yml
78+
with:
79+
release_tag: ${{ needs.check-release.outputs.release_tag }}
80+
81+
create-tag:
82+
needs: [check-release, ci]
83+
if: needs.check-release.outputs.should_release == 'true'
84+
runs-on: ubuntu-latest
85+
steps:
86+
- uses: actions/checkout@v6
87+
with:
88+
ref: ${{ github.sha }}
89+
90+
- name: Create git tag
91+
run: |
92+
git config user.name "github-actions[bot]"
93+
git config user.email "github-actions[bot]@users.noreply.github.com"
94+
git tag ${{ needs.check-release.outputs.release_tag }}
95+
git push origin ${{ needs.check-release.outputs.release_tag }}
96+
97+
release:
98+
needs: [check-release, ci, create-tag]
99+
if: needs.check-release.outputs.should_release == 'true'
100+
uses: ./.github/workflows/_release.yml
101+
with:
102+
release_tag: ${{ needs.check-release.outputs.release_tag }}
103+
secrets:
104+
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

.github/workflows/build-tag.yml

Lines changed: 0 additions & 19 deletions
This file was deleted.

.github/workflows/detect-bump.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/bin/sh
2+
set -e
3+
# Detect version bump type from commit footers since last tag
4+
# Outputs: major, minor, or patch
5+
#
6+
# Usage: Add a footer line "bump: major" or "bump: minor" to any commit
7+
# to trigger that bump type. Otherwise defaults to patch.
8+
9+
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
10+
11+
if [ -z "$LAST_TAG" ]; then
12+
COMMITS=$(git log --format=%B)
13+
else
14+
COMMITS=$(git log "$LAST_TAG"..HEAD --format=%B)
15+
fi
16+
17+
if echo "$COMMITS" | grep -qE '^bump: major$'; then
18+
echo "major"
19+
elif echo "$COMMITS" | grep -qE '^bump: minor$'; then
20+
echo "minor"
21+
else
22+
echo "patch"
23+
fi

release-notes.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/sh
2+
# Preview git-cliff output
3+
4+
case "$1" in
5+
--preview)
6+
git cliff --config .github/cliff.toml --bump --unreleased
7+
;;
8+
"")
9+
git cliff --config .github/cliff.toml --latest
10+
;;
11+
*)
12+
echo "Unknown flag: $1" >&2
13+
echo "Usage: $0 [--preview]" >&2
14+
exit 1
15+
;;
16+
esac

0 commit comments

Comments
 (0)