Skip to content

Commit a7225fc

Browse files
ci: add semantic-release workflow and conventional commit enforcement
Replace manual patch version bumps with python-semantic-release and enforce conventional commit messages via commitizen pre-commit hook. Closes #200 Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 8740166 commit a7225fc

6 files changed

Lines changed: 378 additions & 51 deletions

File tree

.github/workflows/release.yml

Lines changed: 59 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,28 @@ name: release
33
on:
44
push:
55
branches: [main]
6+
workflow_dispatch:
67

78
permissions:
8-
contents: write
9+
contents: read
910

1011
jobs:
12+
test:
13+
if: "!contains(github.event.head_commit.message, '[skip ci]')"
14+
uses: ./.github/workflows/test.yml
15+
1116
release:
1217
if: "!contains(github.event.head_commit.message, '[skip ci]')"
1318
runs-on: ubuntu-latest
19+
needs: test
20+
21+
outputs:
22+
released: ${{ steps.release.outputs.released }}
23+
tag: ${{ steps.release.outputs.tag }}
24+
25+
permissions:
26+
contents: write
27+
1428
steps:
1529
- name: Generate token for version incrementer app
1630
id: create_token
@@ -19,64 +33,61 @@ jobs:
1933
app_id: ${{ secrets.APP_ID }}
2034
private_key: ${{ secrets.PRIVATE_KEY }}
2135

22-
- name: Checkout repository
36+
- name: Setup | Checkout Repository on Release Branch
2337
uses: actions/checkout@v4
2438
with:
39+
ref: ${{ github.ref_name }}
2540
fetch-depth: 0
26-
ref: ${{ github.sha }}
2741
token: ${{ steps.create_token.outputs.token }}
2842

29-
- name: Force correct branch on workflow sha
30-
run: git checkout -B ${{ github.ref_name }} ${{ github.sha }}
31-
32-
- name: Bump patch version
33-
id: bump
43+
- name: Sync to latest remote
3444
run: |
35-
CURRENT_VERSION=$(grep -oP '^version = "\K[^"]+' pyproject.toml)
36-
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"
37-
NEW_PATCH=$((PATCH + 1))
38-
NEW_VERSION="$MAJOR.$MINOR.$NEW_PATCH"
39-
sed -i "s/^version = \"$CURRENT_VERSION\"/version = \"$NEW_VERSION\"/" pyproject.toml
40-
echo "version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
41-
echo "tag=v$NEW_VERSION" >> "$GITHUB_OUTPUT"
42-
echo "Bumped version: $CURRENT_VERSION -> $NEW_VERSION"
45+
git fetch --prune origin
46+
git checkout ${{ github.ref_name }}
47+
git merge --ff-only origin/${{ github.ref_name }}
4348
4449
- name: Install uv
4550
uses: astral-sh/setup-uv@v5
4651

47-
- name: Sync uv lockfile
48-
run: uv lock
52+
- name: Set up Python
53+
uses: actions/setup-python@v5
54+
with:
55+
python-version-file: "pyproject.toml"
4956

50-
- name: Get merged PR info
51-
id: pr_info
52-
env:
53-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
57+
- name: Install dependencies
58+
run: uv sync
59+
60+
- name: Action | Semantic Version Release
61+
id: release
62+
uses: python-semantic-release/python-semantic-release@v10.4.1
63+
with:
64+
github_token: ${{ secrets.GITHUB_TOKEN }}
65+
git_committer_name: "github-actions"
66+
git_committer_email: "actions@users.noreply.github.com"
67+
commit: "false"
68+
push: "false"
69+
tag: "false"
70+
changelog: "true"
71+
vcs_release: "false"
72+
73+
- name: Commit and push version changes
74+
if: steps.release.outputs.released == 'true'
5475
run: |
55-
PR_NUMBER=$(gh pr list --state merged --search "${{ github.sha }}" --json number --jq '.[0].number' 2>/dev/null || echo "")
56-
if [ -n "$PR_NUMBER" ]; then
57-
PR_BODY=$(gh pr view "$PR_NUMBER" --json body --jq '.body')
58-
echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
59-
{
60-
echo "notes<<EOF"
61-
echo "$PR_BODY"
62-
echo "EOF"
63-
} >> "$GITHUB_OUTPUT"
64-
else
65-
echo "notes=Release ${{ steps.bump.outputs.tag }}" >> "$GITHUB_OUTPUT"
66-
fi
67-
68-
- name: Create GitHub release
76+
git config user.name "github-actions"
77+
git config user.email "actions@users.noreply.github.com"
78+
uv lock
79+
git add pyproject.toml CHANGELOG.md uv.lock
80+
git commit -m "chore: release ${{ steps.release.outputs.tag }} [skip ci]
81+
82+
Automatically generated by python-semantic-release"
83+
git tag ${{ steps.release.outputs.tag }}
84+
git push origin ${{ github.ref_name }} --tags
85+
86+
- name: Create GitHub Release
87+
if: steps.release.outputs.released == 'true'
88+
run: |
89+
gh release create ${{ steps.release.outputs.tag }} \
90+
--title "${{ steps.release.outputs.tag }}" \
91+
--generate-notes
6992
env:
7093
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
71-
RELEASE_NOTES: ${{ steps.pr_info.outputs.notes }}
72-
run: |
73-
gh release create "${{ steps.bump.outputs.tag }}" \
74-
--title "${{ steps.bump.outputs.tag }}" \
75-
--notes "$RELEASE_NOTES"
76-
77-
- name: Commit version bump
78-
uses: stefanzweifel/git-auto-commit-action@v5
79-
with:
80-
commit_message: "chore: release ${{ steps.bump.outputs.tag }} [skip ci]"
81-
branch: ${{ github.ref_name }}
82-
file_pattern: "pyproject.toml uv.lock"

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
pull_request:
66
branches: [main]
77
workflow_dispatch:
8+
workflow_call:
89

910
jobs:
1011
test:

.pre-commit-config.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1+
default_install_hook_types:
2+
- pre-commit
3+
- commit-msg
4+
15
repos:
6+
- repo: https://github.com/commitizen-tools/commitizen
7+
rev: v4.10.0
8+
hooks:
9+
- id: commitizen
10+
stages: [commit-msg]
11+
212
- repo: local
313
hooks:
414
- id: ruff-format

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
<!-- version list -->

pyproject.toml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ dev = [
3636
"pytest-jinja-check[fastapi]>=1.0.3",
3737
"pytest-playwright>=0.7.2",
3838
"pre-commit>=4.6.0",
39+
"python-semantic-release>=10.5.3",
3940
]
4041

4142
[tool.ty.rules]
@@ -55,3 +56,68 @@ include = ["tests/**"]
5556
invalid-argument-type = "ignore"
5657
unresolved-attribute = "ignore"
5758
missing-argument = "ignore"
59+
60+
[tool.semantic_release]
61+
assets = []
62+
build_command_env = []
63+
build_command = ""
64+
commit_message = "chore: release {version} [skip ci]\n\nAutomatically generated by python-semantic-release"
65+
commit_parser = "conventional"
66+
logging_use_named_masks = false
67+
major_on_zero = true
68+
allow_zero_version = false
69+
no_git_verify = false
70+
tag_format = "v{version}"
71+
version_toml = ["pyproject.toml:project.version:nf"]
72+
73+
[tool.semantic_release.branches.main]
74+
match = "(main|master)"
75+
prerelease_token = "rc"
76+
prerelease = false
77+
78+
[tool.semantic_release.changelog]
79+
exclude_commit_patterns = []
80+
mode = "update"
81+
insertion_flag = "<!-- version list -->"
82+
83+
[tool.semantic_release.changelog.default_templates]
84+
changelog_file = "CHANGELOG.md"
85+
output_format = "md"
86+
mask_initial_release = true
87+
88+
[tool.semantic_release.changelog.environment]
89+
block_start_string = "{%"
90+
block_end_string = "%}"
91+
variable_start_string = "{{"
92+
variable_end_string = "}}"
93+
comment_start_string = "{#"
94+
comment_end_string = "#}"
95+
trim_blocks = false
96+
lstrip_blocks = false
97+
newline_sequence = "\n"
98+
keep_trailing_newline = false
99+
extensions = []
100+
autoescape = false
101+
102+
[tool.semantic_release.commit_author]
103+
env = "GIT_COMMIT_AUTHOR"
104+
default = "semantic-release <semantic-release>"
105+
106+
[tool.semantic_release.commit_parser_options]
107+
minor_tags = ["feat"]
108+
patch_tags = ["fix", "perf"]
109+
other_allowed_tags = ["build", "chore", "ci", "docs", "style", "refactor", "test"]
110+
allowed_tags = ["feat", "fix", "perf", "build", "chore", "ci", "docs", "style", "refactor", "test"]
111+
default_bump_level = 0
112+
parse_squash_commits = true
113+
ignore_merge_commits = true
114+
115+
[tool.semantic_release.remote]
116+
name = "origin"
117+
type = "github"
118+
ignore_token_for_push = false
119+
insecure = false
120+
121+
[tool.semantic_release.publish]
122+
dist_glob_patterns = []
123+
upload_to_vcs_release = false

0 commit comments

Comments
 (0)