From 30963a1ee3a04004203df88654d9f7ce0ac9e4e9 Mon Sep 17 00:00:00 2001 From: allen0099 Date: Fri, 25 Sep 2026 16:45:57 +0000 Subject: [PATCH] release: list one line per change in the GitHub release notes The release body used to be the promoted changelog section verbatim. Our entries explain each change in full, so the 0.3.7 release page was about 8 KB of long bullets. GitHub also renders every hard wrap as a line break, which made them longer still. Each changelog entry now opens with a bold one-line summary. The new release_notes() in scripts/changelog_release.py turns the promoted section into those summaries plus the issue links found in each entry, grouped under the same ### headings. It ends with a link to the version's section on the documentation site's changelog, which keeps the full entries. An entry without a summary, or a line that is neither a ### heading nor a list entry, is an error that names the entries. The release therefore stops before publishing anything, and so does a dry run. The test that promotes the real CHANGELOG.md runs the same check, so the pull request that adds an entry without a summary fails CI instead of the release. CHANGELOG.md, DEVELOPMENT.md and CONTRIBUTING.md describe the format. Released versions are left as they are. --- .github/workflows/release.yml | 3 +- CHANGELOG.md | 18 +++-- docs/CONTRIBUTING.md | 9 +-- docs/DEVELOPMENT.md | 35 +++++++-- scripts/changelog_release.py | 95 ++++++++++++++++++++++-- tests/test_changelog_release.py | 123 +++++++++++++++++++++++++++++--- 6 files changed, 251 insertions(+), 32 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8ab9e26..48a8540 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -150,7 +150,8 @@ jobs: # Promoting the changelog is part of cutting the release, not a chore to # remember afterwards: the script fails when `## [Unreleased]` is empty, # so a release with nothing written down stops here instead of shipping - # release notes that say nothing. + # release notes that say nothing. It also fails when an entry has no bold + # one-line summary, because the release notes are those summaries. - name: Promote the changelog env: VERSION: ${{ steps.version.outputs.version }} diff --git a/CHANGELOG.md b/CHANGELOG.md index a960843..afeadfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,15 +6,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). This file records what changed for users of the library, in particular -behaviour that changed under an unchanged API. The `## [Unreleased]` section is -what the Release workflow publishes as the GitHub release notes, and a release -with an empty one fails — so entries are added by hand, in the pull request -that earns them. See [Releasing](https://fastapi-cachex.readthedocs.io/en/latest/DEVELOPMENT/#releasing). +behaviour that changed under an unchanged API. Entries are added by hand, in +the pull request that earns them, and each one opens with a bold one-line +summary: `- **What changed.** The details...`. The GitHub release notes list +those summaries and link back here, and a release fails if `## [Unreleased]` +is empty or an entry has no summary. Entries before 0.3.8 predate this format. +See [Releasing](https://fastapi-cachex.readthedocs.io/en/latest/DEVELOPMENT/#releasing). Note that 0.3.3 was never released; 0.3.4 follows 0.3.2. ## [Unreleased] +### Changed + +- **GitHub release notes list one line per change.** Each changelog entry now + opens with a bold one-line summary. The release page shows only those + summaries with their issue links, grouped as in the changelog, and links to + the full entries on the documentation site. The changelog itself keeps the + details. + ## [0.3.7] - 2026-09-25 ### Added diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 1e7da2d..cefe2f6 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -34,10 +34,11 @@ Please refer to our [Development Guide](DEVELOPMENT.md) for detailed instruction is allowed to lag behind them 2. Add an entry to the `## [Unreleased]` section of [CHANGELOG.md](https://github.com/allen0099/FastAPI-CacheX/blob/master/CHANGELOG.md) if your change alters behaviour, adds public - API, or fixes something a user could have hit. That section is what the - release notes are built from, and a release refuses to run on an empty one, - so an omission surfaces — but only at release time, and only as "somebody - forgot", never as which PR it was + API, or fixes something a user could have hit. Open the entry with a bold + one-line summary, `- **What changed.** The details...`: the release notes + list only those summaries, and CI fails on an entry without one. A release + also refuses to run on an empty section, so an omission surfaces — but only + at release time, and only as "somebody forgot", never as which PR it was 3. Update the documentation with any new dependencies, features, or changes. New public API needs a docstring and, if it lives in a module not yet covered, an entry under `docs/api/`; check the site with diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index f3c45df..0d43f68 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -262,8 +262,9 @@ The workflow runs in this order: `vX.Y.Z` is already tagged, locally or on the remote, the run stops here. 3. **The changelog.** `scripts/changelog_release.py` renames `## [Unreleased]` to `## [X.Y.Z] - YYYY-MM-DD`, opens a fresh empty `## [Unreleased]` above - it, rewrites the compare links at the bottom, and writes the promoted - section out to be used as the release body. + it, rewrites the compare links at the bottom, and writes the release body: + each entry's bold summary and issue links, and a link to the full entries + on the documentation site. 4. **The permanent part**, kept together at the end: commit the version bump and the promoted changelog, push it, tag, push the tag by refspec, create the GitHub release from the promoted section, publish to PyPI. @@ -295,10 +296,32 @@ different path, not of this one. `CHANGELOG.md` used to be maintained entirely by hand and nothing enforced it: the release notes came from `git log --pretty=format:"- %s (%h)"`, so a release happened whether or not anyone had written down what it meant. That is no -longer true. The release body **is** the `## [Unreleased]` section, and an -empty one fails the run — for a hand-maintained file, "nobody wrote it down" is -far more likely than "nothing changed". The commit list has not been lost: the -release body ends with a compare link against the previous tag. +longer true. The release body is built from the `## [Unreleased]` section, and +an empty one fails the run — for a hand-maintained file, "nobody wrote it down" +is far more likely than "nothing changed". The commit list has not been lost: +the release body ends with a compare link against the previous tag. + +The changelog and the release page serve different readers. The changelog +explains each change in full: what behaviour moved, why, and how to adapt. The +release page is scanned, so it gets one line per change. Every entry therefore +opens with a bold summary, and the release body is just those summaries: + +```markdown +- **Add `CacheManager.add()` for store-if-absent writes.** It uses the same + key prefix, JSON encoding and `default_ttl` as `set()`, and runs on the + backend's atomic `set_if_absent` ... ([#65](https://github.com/allen0099/FastAPI-CacheX/issues/65)) +``` + +becomes ``- Add `CacheManager.add()` for store-if-absent writes. ([#65](...))`` +under the same `### Added` heading. The issue links are carried over from +anywhere in the entry, and the body ends with a link to the version's section +on the documentation site's changelog page. Write the summary for someone +deciding whether this release matters to them: what changed, in the imperative +or as a plain statement, not how. An entry without one fails the run and is +named in the error, and so does a line in the section that is neither a `###` +heading nor a `- ` entry. `tests/test_changelog_release.py` runs the same check +on the real `CHANGELOG.md`, so the pull request that adds an entry without a +summary fails CI instead of the release. Two details of the promotion are worth knowing, because both have bitten this project: diff --git a/scripts/changelog_release.py b/scripts/changelog_release.py index e182267..9e80795 100644 --- a/scripts/changelog_release.py +++ b/scripts/changelog_release.py @@ -2,10 +2,20 @@ `release.yml` calls this so that one manual dispatch does the whole cut: the heading is renamed, a fresh empty `## [Unreleased]` is opened above it, the -compare links at the bottom are rewritten, and the promoted section is written -out to be used verbatim as the GitHub release body. +compare links at the bottom are rewritten, and a short version of the promoted +section is written out as the GitHub release body. -Two rules drive the implementation: +The changelog keeps the full story of each change; the release page lists one +line per change. Every entry therefore opens with a bold one-line summary:: + + - **Add `CacheManager.add()` for store-if-absent writes.** It uses the + same key prefix ... ([#65](https://github.com/.../issues/65)) + +and the release body is those summaries with their issue links, grouped under +the same `###` headings, followed by a link to the full entries on the +documentation site. + +Three rules drive the implementation: * The previous version is **read from the existing headings**, never derived from the new one. 0.3.3 was never released, so `[0.3.4]` has to compare @@ -14,6 +24,8 @@ * An empty `## [Unreleased]` is an error, not an empty release note. The changelog is maintained by hand, so "nothing was written down" and "nothing changed" look identical from here, and only the former is likely. +* An entry without a bold summary is an error too, for the same reason: the + release would otherwise publish a line nobody chose. Run it directly to see what a release would produce:: @@ -36,6 +48,12 @@ ) _LINK = re.compile(r"^\[(?P