Skip to content

Commit 647b481

Browse files
committed
feat: add automated releases with python-semantic-release
Analyze conventional commits on every push to main: when a release is due, semantic-release updates the changelog (insertion flag, history preserved), commits, tags vX.Y.Z and creates the GitHub release; the same workflow run then builds, publishes to PyPI via trusted publishing and deploys the versioned docs. hatch-vcs derives the package version from the tag, so no version file is bumped. Replaces the manual tag-push release flow.
1 parent 9df297f commit 647b481

7 files changed

Lines changed: 319 additions & 49 deletions

File tree

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,89 @@
1-
# Release workflow: publishes to PyPI via OIDC trusted publishing.
1+
# Automated release: python-semantic-release analyzes the conventional
2+
# commits on main, and when a release is due it updates CHANGELOG.md,
3+
# commits, tags vX.Y.Z, pushes and creates the GitHub release. hatch-vcs
4+
# derives the package version from that tag, so no version file is bumped.
5+
# Publishing happens in this same run: pushes made with GITHUB_TOKEN do
6+
# not retrigger workflows, so a tag-triggered publish would never fire.
27
#
3-
# Filename and environment name are load-bearing: the trusted publisher
4-
# configured on PyPI references workflow "on-release-main.yml" and
5-
# environment "pypi" (no token/secret involved).
6-
#
7-
# Docs: dev builds are deployed from main by docs.yml; deploy-docs below
8-
# publishes the versioned per-release docs (mike -> gh-pages branch).
8+
# Filename and environment name are load-bearing: the PyPI trusted
9+
# publisher (OIDC) references workflow "on-release-main.yml" and
10+
# environment "pypi".
911

1012
name: release-main
1113

1214
on:
1315
push:
14-
tags: ['v[0-9]*']
16+
branches: [main]
17+
workflow_dispatch:
18+
19+
# One release run at a time: python-semantic-release pushes commits/tags
20+
# to main, so overlapping runs would race each other.
21+
concurrency:
22+
group: release
23+
cancel-in-progress: false
1524

1625
jobs:
17-
publish:
26+
release:
1827
runs-on: ubuntu-latest
1928
environment:
2029
name: pypi
2130
permissions:
22-
contents: read
2331
# required for PyPI trusted publishing (OIDC)
2432
id-token: write
33+
# push the release commit/tag and create the GitHub release (PSR)
34+
contents: write
35+
outputs:
36+
released: ${{ steps.psr.outputs.released }}
37+
version: ${{ steps.psr.outputs.version }}
2538
steps:
26-
- uses: actions/checkout@v4
39+
- name: Check out
40+
uses: actions/checkout@v4
2741
with:
28-
# deep clone incl. tags so hatch-vcs derives the version from the tag
42+
# full history: PSR analyzes commits, hatch-vcs needs the tags
2943
fetch-depth: 0
44+
# latest main, not the triggering SHA: runs queued behind the
45+
# concurrency group must include release commits pushed by the
46+
# previous run, or their own push is rejected
47+
ref: main
3048

3149
- name: Set up the environment
3250
uses: ./.github/actions/setup-python-env
3351

52+
- name: Version, tag and changelog with python-semantic-release
53+
id: psr
54+
env:
55+
GH_TOKEN: ${{ github.token }}
56+
run: |
57+
git config user.name "github-actions[bot]"
58+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
59+
before=$(git rev-parse HEAD)
60+
uv run semantic-release version
61+
after=$(git rev-parse HEAD)
62+
if [ "$before" != "$after" ]; then
63+
ver=$(git tag --points-at HEAD | grep -E '^v[0-9]' | head -1)
64+
echo "released=true" >> "$GITHUB_OUTPUT"
65+
echo "version=${ver#v}" >> "$GITHUB_OUTPUT"
66+
echo "released $ver"
67+
else
68+
echo "released=" >> "$GITHUB_OUTPUT"
69+
echo "no release necessary"
70+
fi
71+
3472
- name: Build package distribution files
73+
if: steps.psr.outputs.released != ''
3574
run: uv build
3675

3776
- name: Check package metadata
77+
if: steps.psr.outputs.released != ''
3878
run: uvx twine check dist/*
3979

4080
- name: Publish package
81+
if: steps.psr.outputs.released != ''
4182
run: uv publish --trusted-publishing always
4283

4384
deploy-docs:
44-
needs: publish
85+
needs: release
86+
if: needs.release.outputs.released != ''
4587
runs-on: ubuntu-latest
4688
permissions:
4789
# mike commits the built site to the gh-pages branch
@@ -52,23 +94,23 @@ jobs:
5294
steps:
5395
- uses: actions/checkout@v4
5496
with:
55-
# deep clone incl. tags: hatch-vcs needs them and mike needs gh-pages
97+
# deep clone incl. the release commit and tags pushed by PSR
5698
fetch-depth: 0
99+
ref: main
57100

58101
- name: Set up the environment
59102
uses: ./.github/actions/setup-python-env
60103

61104
- name: Configure git identity
62105
run: |
63106
git config user.name 'github-actions[bot]'
64-
git config user.email 'github-actions[bot]@users.noreply.github.com'
107+
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
65108
66109
- name: Deploy versioned documentation
67110
env:
68-
TAG: ${{ github.ref_name }}
111+
VERSION: ${{ needs.release.outputs.version }}
69112
run: |
70-
ver="${TAG#v}"
71113
# --alias-type=copy keeps /latest/ a real directory instead of a
72114
# redirect, so machine-readable files stay fetchable there
73-
uv run mike deploy --push --alias-type=copy --update-aliases "$ver" latest
115+
uv run mike deploy --push --alias-type=copy --update-aliases "$VERSION" latest
74116
uv run mike set-default --push latest

CHANGELOG.md

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,36 @@
11
# Changelog
22

3-
> **Note:** This file is no longer updated manually. Changes since
4-
> v0.12.0 are tracked in the
3+
> **Note:** From here on this file is generated by
4+
> [python-semantic-release](https://python-semantic-release.readthedocs.io/)
5+
> from the conventional commit history; do not edit release sections
6+
> manually. Changes between v0.12.0 and the first generated entry are
7+
> tracked in the
58
> [GitHub Releases](https://github.com/OpenSemanticLab/osw-python/releases).
6-
> Automated changelog generation (python-semantic-release) is planned;
7-
> the historical entries below are preserved for reference.
9+
> The historical entries at the bottom are preserved for reference.
10+
11+
<!-- version list -->
812

913
## Version v0.12.0
10-
- rework ontology import (BREAKING, see examples/ontology_import.py)
11-
- add in-memory credentials in CredentialManager
12-
- fix: 'value is not a valid enumeration member' errors
13-
- improve namespace detection
14-
- parallelize get_page
14+
15+
- rework ontology import (BREAKING, see examples/ontology_import.py)
16+
- add in-memory credentials in CredentialManager
17+
- fix: 'value is not a valid enumeration member' errors
18+
- improve namespace detection
19+
- parallelize get_page
1520

1621
## Version v0.11.0
17-
- fix code generator for Statement
18-
- parallelize page operations
19-
- add legacy file page migration script
20-
- refactor search function in wiki_tools
21-
- add file info to utils
22-
- refactor WtSite to work directly with CredentialManager
22+
23+
- fix code generator for Statement
24+
- parallelize page operations
25+
- add legacy file page migration script
26+
- refactor search function in wiki_tools
27+
- add file info to utils
28+
- refactor WtSite to work directly with CredentialManager
2329

2430
## Version v0.10.0
25-
- refactor PagePackage creation
26-
- add requiredExtensions, requiredPackages
31+
32+
- refactor PagePackage creation
33+
- add requiredExtensions, requiredPackages
2734

2835
## Version v0.9.0
2936

@@ -79,13 +86,11 @@
7986

8087
- add local slot edit feature
8188

82-
8389
## Version v0.2.0
8490

8591
- rename OSL to OSW
8692
- add page package feature
8793

88-
8994
## Version v0.1.0
9095

9196
- create package

CONTRIBUTING.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,15 @@ locally.
2525

2626
## Releasing
2727

28-
Maintainers release by pushing a version tag; CI does the rest (build,
29-
publish to PyPI via trusted publishing, deploy versioned docs):
30-
31-
```bash
32-
git tag v1.2.0 && git push origin v1.2.0
33-
```
28+
Releases are fully automated with
29+
[python-semantic-release](https://python-semantic-release.readthedocs.io/):
30+
every push to `main` is analyzed, and conventional commits decide the
31+
outcome. `fix:` commits trigger a patch release, `feat:` a minor release
32+
and a `BREAKING CHANGE:` footer (or `!` after the type) a major release;
33+
other types (`chore:`, `docs:`, `ci:`, ...) release nothing. CI then
34+
updates the changelog, tags, builds, publishes to PyPI via trusted
35+
publishing and deploys the versioned docs. No manual version bumping or
36+
tagging.
3437

3538
## AI Guidelines
3639

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ clean-build: ## Remove build artifacts
4545
.PHONY: publish
4646
publish: ## Publish a release to PyPI (CI-only via OIDC trusted publishing)
4747
@echo "Publishing happens in CI via OIDC trusted publishing (on-release-main.yml)."
48-
@echo "Push a version tag (v*) to trigger a release."
48+
@echo "Releases are cut automatically by python-semantic-release from the"
49+
@echo "conventional commits on main; no manual tagging."
4950

5051
.PHONY: docs
5152
docs: ## Build and serve the documentation locally

docs/dev.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,16 @@ strict build that fails on any warning:
116116

117117
## Releasing
118118

119-
Maintainers release by pushing a version tag; CI builds the package,
120-
publishes it to PyPI via trusted publishing and deploys the versioned
121-
docs. The version is derived from the tag by `hatch-vcs`, no manual
122-
bumping needed:
119+
Releases are fully automated
120+
([python-semantic-release](https://python-semantic-release.readthedocs.io/)):
121+
conventional commits on `main` decide the version bump (`fix:` patch,
122+
`feat:` minor, `BREAKING CHANGE:`/`!` major), CI updates the changelog,
123+
tags `vX.Y.Z`, builds, publishes to PyPI via trusted publishing and
124+
deploys the versioned docs. The package version is derived from the tag
125+
by `hatch-vcs`; nothing is bumped manually.
126+
127+
To preview what the next release would be:
123128

124129
```bash
125-
git tag v1.2.0 && git push origin v1.2.0
130+
uv run semantic-release --noop version
126131
```

pyproject.toml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ dev = [
122122
"mkdocstrings-python>=1.0.3",
123123
# docs versioning: zensical-compatible mike fork (same pin as OO-LD/oold-schema)
124124
"mike @ git+https://github.com/squidfunk/mike.git@2.2.0+zensical-0.1.0",
125+
# automated releases + changelog from conventional commits
126+
"python-semantic-release>=10.0.0",
125127
]
126128

127129
[tool.hatch.build.targets.wheel]
@@ -265,6 +267,28 @@ ignore = [
265267
[tool.ruff.format]
266268
preview = true
267269

270+
[tool.semantic_release]
271+
# No version_toml/version_variables: the package version is derived from
272+
# the git tag by hatch-vcs, so semantic-release only computes the next
273+
# version from the conventional commits, updates the changelog, commits,
274+
# tags (vX.Y.Z) and creates the GitHub release. Publishing to PyPI happens
275+
# in the same workflow run (on-release-main.yml).
276+
commit_parser = "conventional"
277+
tag_format = "v{version}"
278+
commit_message = "chore(release): v{version} [skip ci]"
279+
# our history starts at v0.x; without this, PSR would jump a 0.x baseline
280+
# straight to 1.0.0 (moot at >=1.0.0, kept for copy-paste safety)
281+
allow_zero_version = true
282+
exclude_commit_patterns = ['''chore\(release\):.*''']
283+
284+
[tool.semantic_release.changelog]
285+
# prepend new versions at the insertion flag, keep the manual history below
286+
mode = "update"
287+
insertion_flag = "<!-- version list -->"
288+
289+
[tool.semantic_release.changelog.default_templates]
290+
changelog_file = "CHANGELOG.md"
291+
268292
[tool.ty.environment]
269293
python = "./.venv"
270294
python-version = "3.10"

0 commit comments

Comments
 (0)