From c07f0cdbe175626649ecbdb8e36fc8581e2a3ab6 Mon Sep 17 00:00:00 2001 From: Dylan Verheul Date: Sun, 11 Oct 2026 10:37:53 +0200 Subject: [PATCH] Sync the release preflight from django-marina Propagation of zostera/django-marina#694, where the reasoning lives. just release-check listed only commits touching src/, then closed with "Every commit above must be covered by an entry", which reads as a complete list and is not one. Anything tooling-only was invisible to it. It now prints a second group for commits touching nothing in src/, under a heading that says to read them rather than a rule, because no rule fits: a Dependabot bump of a dev dependency is nothing to a user, while a lint rule or a contributor policy belongs in the notes. just build now fails when uv.lock does not name the version in pyproject.toml, and just relock regenerates the lock without upgrading anything. uv sync --frozen does not check the project's own version, so a stale lock previously passed twine, check-manifest, pyroma and both smoke tests unnoticed. That is not theoretical: it is how a version bump shipped here with the previous version still in the lock. MAINTAINING.md release steps 1 and 2 updated to match. The justfile is byte-identical to django-marina again. Verified: just lint, just build and just test-cov at 100%. just relock was a no-op, so this lock was already current. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 2 ++ MAINTAINING.md | 17 +++++++++++++---- justfile | 29 +++++++++++++++++++++++++---- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cd1af90..475172c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ - Link the README's maintenance-mode section to the django-bootstrap5 guide for migrating from django-bootstrap3 (#1160). - Fix README: restore the canonical `##` headings for the last four sections, and correct the example-app command from the long-gone `make example` to `just example`. +- Show commits that touch nothing in `src/` in `just release-check` as a second group, instead of hiding them. Tooling, CI and documentation changes belong in the release notes too, and the old output read as a complete list while being a filtered one. +- Fail `just build` when `uv.lock` does not name the version in `pyproject.toml`, and add `just relock` to fix it. `uv sync --frozen` does not check the project's own version, so a stale lock passed every packaging check. ## 26.2 (2026-07-30) - Note in MAINTAINING.md that the pinned Bootstrap 3.4.1 CDN version is confirmed final (no 3.x releases expected), matching the note already in django-bootstrap4. diff --git a/MAINTAINING.md b/MAINTAINING.md index 7e44fadd..fb2542b0 100644 --- a/MAINTAINING.md +++ b/MAINTAINING.md @@ -97,10 +97,19 @@ it half-updated. Dependabot reopens it on the next run. ## Release process -1. `just release-check` — lists the commits touching `src/` since the last tag next to the current - `Unreleased` entries. Every one of those commits must be covered by an entry, or be a no-op for - users (a docstring typo, say). A code change that reaches users without an entry ships invisible. -2. On a release branch, update `CHANGELOG.md` and bump `version` in `pyproject.toml`; open a PR and merge it +1. `just release-check` — lists the commits since the last tag next to the current `Unreleased` + entries, in two groups. Every commit touching `src/` must be covered by an entry, or be a + no-op for users (a docstring typo, say). A code change that reaches users without an entry + ships invisible. The second group is everything else, and it needs reading rather than a rule: + a Dependabot bump of a dev dependency is genuinely nothing to a user, while a lint rule, a CI + gate or a contributor policy belongs in the notes under tooling. django-marina 26.3 shipped + with nine such changes unrecorded, and django-bootstrap4 and django-bootstrap5 each found + four or five more of their own, because the recipe only showed the first group. +2. On a release branch, update `CHANGELOG.md`, bump `version` in `pyproject.toml`, and run + `just relock` so `uv.lock` names the new version; open a PR and merge it. `uv sync --frozen` + does not check the project's own version, so a stale lock otherwise survives every packaging + check unnoticed. `just build` now fails on the mismatch, which is what catches it if the + relock is forgotten. 3. Check out and pull `main` 4. `just build` — builds wheel + tarball, runs packaging checks, and smoke-tests both against an isolated env 5. `just release-tag` — creates and pushes the version tag; GitHub Actions publishes to PyPI diff --git a/justfile b/justfile index c465f5db..62ea792d 100644 --- a/justfile +++ b/justfile @@ -20,6 +20,10 @@ VERSION := `sed -n 's/^ *version.*=.*"\([^"]*\)".*/\1/p' pyproject.toml` @upgrade: uv sync --all-groups --upgrade +# Regenerate uv.lock without upgrading anything (use after bumping version) +@relock: + uv lock + # Install for development (uses uv.lock) install: uv sync --frozen --all-groups @@ -60,8 +64,20 @@ install: @tests *ARGS: uvx --with tox-uv tox {{ARGS}} +# Fail if uv.lock does not name the version in pyproject.toml. +# `uv sync --frozen` does not check the project's own version, so without this a build +# passes with a stale lock: django-bootstrap4 shipped a bump with the previous version +# still in the lock, and `just build` reported everything green. Fix with `just relock`. +[private] +@_lockfile-matches-version: + LOCKED=`awk -F'"' '/^version = /{v=$2} /editable = "\."/{print v; exit}' uv.lock`; \ + if [ "$LOCKED" != "{{ VERSION }}" ]; then \ + echo "Error - pyproject.toml says {{ VERSION }}, uv.lock says $LOCKED. Run 'just relock'."; \ + exit 1; \ + fi + # Build artefacts + packaging checks (local preflight) -@build: clean-build install +@build: clean-build install _lockfile-matches-version uv build uvx twine check dist/* uvx check-manifest @@ -82,16 +98,21 @@ install: echo "Example not found."; \ fi -# Show src changes and changelog entries since the last tag (release preflight) +# Show changes and changelog entries since the last tag (release preflight) @release-check: TAG=$(git describe --tags --abbrev=0); \ - echo "Commits touching src/ since $TAG:"; \ + echo "Commits touching src/ since $TAG. Each needs an entry, or is a no-op for users:"; \ git log --format=' %h %s' "$TAG"..HEAD -- src/ | grep . || echo " (none)"; \ echo ""; \ + echo "Commits touching nothing in src/. Tooling, CI and docs still ship, so read these too:"; \ + git log --format=' %h %s' "$TAG"..HEAD -- . ':(exclude)src/' | grep . || echo " (none)"; \ + echo ""; \ echo "Entries in the Unreleased section of CHANGELOG.md:"; \ awk '/^## Unreleased/{f=1;next} /^## /{f=0} f' CHANGELOG.md | grep '^- ' || echo " (none)"; \ echo ""; \ - echo "Every commit above must be covered by an entry, or be a no-op for users." + echo "Version: pyproject.toml {{ VERSION }}, uv.lock `awk -F'\"' '/^version = /{v=$2} /editable = \"\\.\"/{print v; exit}' uv.lock`"; \ + echo ""; \ + echo "A commit touching src/ and other paths appears in both lists." # Create and push a release tag (publishing happens in GitHub Actions) @release-tag: porcelain branch build