Skip to content

Commit 3fdb8f0

Browse files
committed
ci: automate releases via release-please + PR title lint
Replaces the manual "remember to run make release" flow with two GitHub Actions that together guarantee releases can't be forgotten or mislabeled: - release-please.yml watches main; whenever there are unreleased commits with conventional-commit prefixes, it opens (or updates) a "Release PR" with auto-computed version + auto-generated CHANGELOG. Merging that PR creates the v* tag, which publish.yml already consumes to ship to PyPI. The version bump is deterministic from semver rules on the commit prefixes (feat = minor, fix = patch, BREAKING CHANGE = major), so it can't be picked wrong. - lint-pr-title.yml runs amannn/action-semantic-pull-request against every PR title. Titles without a conventional prefix get a red check; combined with branch protection (enable later in repo settings → Branches), this blocks merging until the title is fixed. Important specifically because release-please reads PR titles under squash-merge, and a missing prefix would silently miss a release. The setuptools-scm migration from PR #56 was the prerequisite — with the version derived from tags rather than a source field, release-please doesn't need to edit any source files (uses release-type: simple). The manifest file tracks the current released version (0.3.5) so the bot's first run picks up from where we are. `make release` and friends stay as a manual escape hatch for the rare case where the release-please flow can't be used (e.g. urgent security fix that shouldn't wait for the normal Release PR cycle). AGENTS.md updated to describe the new flow. After merge: - The next PR with a `feat:` or `fix:` will trigger a Release PR - Enable branch protection on main → require lint-pr-title check - Stop running make release; let the bot do it
1 parent 4eac2eb commit 3fdb8f0

5 files changed

Lines changed: 124 additions & 21 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Lint PR title
2+
3+
# Rejects PR titles that don't follow Conventional Commits format. The
4+
# title is what release-please reads (under squash-merge) to compute the
5+
# next version bump, so a wrong/missing prefix would silently miss a
6+
# release. This check turns that silent failure into a visible block.
7+
8+
on:
9+
pull_request:
10+
types: [opened, edited, synchronize, reopened]
11+
12+
permissions:
13+
pull-requests: read
14+
15+
jobs:
16+
lint:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: amannn/action-semantic-pull-request@v5
20+
env:
21+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22+
with:
23+
types: |
24+
feat
25+
fix
26+
docs
27+
chore
28+
refactor
29+
test
30+
build
31+
ci
32+
perf
33+
style
34+
revert
35+
requireScope: false
36+
# Subject can be lowercase (matches existing repo convention).
37+
# No regex constraint on subject content.
38+
validateSingleCommit: false
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: Release Please
2+
3+
# Watches main and opens (or updates) a "Release PR" whenever there are
4+
# unreleased commits with conventional-commit prefixes (feat:, fix:, etc.).
5+
# Merging that Release PR creates a vX.Y.Z tag, which the existing
6+
# publish.yml workflow consumes to build and upload to PyPI.
7+
8+
on:
9+
push:
10+
branches: [main]
11+
12+
permissions:
13+
contents: write
14+
pull-requests: write
15+
16+
jobs:
17+
release-please:
18+
runs-on: ubuntu-latest
19+
steps:
20+
- uses: googleapis/release-please-action@v4
21+
with:
22+
config-file: release-please-config.json
23+
manifest-file: .release-please-manifest.json

.release-please-manifest.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
".": "0.3.5"
3+
}

AGENTS.md

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,36 @@ Update both files:
2525

2626
## Releasing
2727

28-
The version lives in git tags. `pyproject.toml` has no `version` field —
29-
`setuptools-scm` derives it from the latest `v*` tag at build time.
30-
31-
After merging a PR with user-visible changes, publish to PyPI from main:
32-
33-
```bash
34-
git checkout main && git pull
35-
make release # patch bump (0.3.4 → 0.3.5) — bug fixes only
36-
make release-minor # minor bump (0.3.4 → 0.4.0) — new features, backwards-compat
37-
make release-major # major bump (0.3.4 → 1.0.0) — breaking changes
38-
```
39-
40-
Each target reads the latest tag, computes the next, tags it, and pushes
41-
the tag. No source file is bumped; no "Release vX.Y.Z" commits land on
42-
main. The `publish.yml` workflow runs on tag push and publishes to PyPI
43-
via trusted publisher (setuptools-scm bakes the tag's version into the
44-
built artifacts).
45-
46-
Pick the bump from semver: new public API = minor, only bug fixes =
47-
patch, backwards-incompatible = major. Don't tag manually outside the
48-
Makefile — the targets enforce clean tree + on main + up-to-date.
28+
Releases are automated by [release-please](https://github.com/googleapis/release-please).
29+
You do not run `make release` in normal flow — the bot does it for you.
30+
31+
**Normal flow:**
32+
33+
1. Open a PR with a Conventional Commits title (`feat: ...`, `fix: ...`, `docs: ...`).
34+
The PR title lint blocks merge if the prefix is missing.
35+
2. Merge the PR to main.
36+
3. `release-please` reads commits since the last release tag and, if any
37+
`feat:` / `fix:` are present, opens (or updates) a "Release PR"
38+
titled `chore(main): release X.Y.Z` with an auto-generated
39+
`CHANGELOG.md` entry and a new version computed via semver:
40+
- `feat:` → minor bump
41+
- `fix:` / `perf:` → patch bump
42+
- any `BREAKING CHANGE:` footer → major bump
43+
4. Merge the Release PR when you're ready to ship. The bot creates the
44+
`vX.Y.Z` tag, which triggers `publish.yml` → build → PyPI upload.
45+
46+
The version itself lives in git tags. `pyproject.toml` has no `version`
47+
field — `setuptools-scm` derives it from the tag at build time. The
48+
`.release-please-manifest.json` file tracks the current released
49+
version for release-please's own bookkeeping; you don't edit it by hand.
50+
51+
**Manual escape hatch (rare):**
52+
53+
If you ever need to release out of band — e.g. a security fix that
54+
shouldn't wait for the next Release PR cycle — `make release` /
55+
`make release-minor` / `make release-major` still work. They read the
56+
latest tag, compute the next version, tag, and push. Use only when the
57+
release-please flow can't be used.
58+
59+
Don't tag manually outside the Makefile — the targets enforce clean
60+
tree + on main + up-to-date.

release-please-config.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
3+
"packages": {
4+
".": {
5+
"release-type": "simple",
6+
"bump-minor-pre-major": false,
7+
"bump-patch-for-minor-pre-major": false,
8+
"draft": false,
9+
"prerelease": false,
10+
"include-component-in-tag": false,
11+
"include-v-in-tag": true,
12+
"changelog-sections": [
13+
{ "type": "feat", "section": "Features" },
14+
{ "type": "fix", "section": "Bug Fixes" },
15+
{ "type": "perf", "section": "Performance" },
16+
{ "type": "revert", "section": "Reverts" },
17+
{ "type": "docs", "section": "Documentation", "hidden": false },
18+
{ "type": "refactor", "section": "Refactoring" },
19+
{ "type": "build", "section": "Build System", "hidden": true },
20+
{ "type": "ci", "section": "CI", "hidden": true },
21+
{ "type": "chore", "section": "Chores", "hidden": true },
22+
{ "type": "test", "section": "Tests", "hidden": true },
23+
{ "type": "style", "section": "Style", "hidden": true }
24+
]
25+
}
26+
}
27+
}

0 commit comments

Comments
 (0)