From d5d141e3af71fc6586c7c7a0eddfa5a882769ff6 Mon Sep 17 00:00:00 2001 From: wpbonelli Date: Sun, 6 Sep 2026 19:46:26 -0400 Subject: [PATCH 1/3] docs(release): note the trusted-publisher and conda-forge caveats Record two things learned from modflow-devtools and modflowapi releases: PyPI matches a trusted publisher on the org, repo, workflow and environment names, so a rename silently breaks it; and the conda-forge autotick bot only bumps the version and hash, so recipe requirements must be checked against the sdist metadata when dependencies change. Add a Releasing section to DEVELOPER.md pointing at the release guide. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01UVbcrMW2Ax1LwR9bskJdeu --- DEVELOPER.md | 5 +++++ docs/make_release.md | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/DEVELOPER.md b/DEVELOPER.md index 9399fcf0a..de96fbdcd 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -33,6 +33,7 @@ This document describes how to set up a FloPy development environment, run the e - [Snapshot testing](#snapshot-testing) - [Branching model](#branching-model) - [Deprecation policy](#deprecation-policy) + - [Releasing](#releasing) - [Miscellaneous](#miscellaneous) - [Locating the root](#locating-the-root) - [Dependency analysis](#dependency-analysis) @@ -459,6 +460,10 @@ This project loosely follows [NEP 23](https://numpy.org/neps/nep-0023-backwards- See the linked article for more detail. +## Releasing + +The release procedure is documented separately in [docs/make_release.md](docs/make_release.md). FloPy releases are automated from a pushed `v` branch, publish to PyPI with [trusted publishing](https://docs.pypi.org/trusted-publishers/) from a `release` environment, and reach conda-forge through the autotick bot. There are also a few manual steps (updating citations, reviewing deprecations, regenerating the MF6 module); see the release guide for the full sequence and the caveats. + ## Miscellaneous ### Locating the root diff --git a/docs/make_release.md b/docs/make_release.md index a17c8506d..1c7d5ea1c 100644 --- a/docs/make_release.md +++ b/docs/make_release.md @@ -86,8 +86,12 @@ Review the release and publish it. Publishing the release triggers a final job t The release workflow assumes [trusted publishing](https://docs.pypi.org/trusted-publishers/) has been configured in the PyPI admin interface. A GitHub environment called `release` is required (however it needs no secrets or environment variables). +**Note:** PyPI matches a trusted publisher on the organization name, the repository name, the workflow filename and the environment name. Renaming any of them silently invalidates the publisher, and nothing reports it until the next release fails with `invalid-publisher`. This bit `modflowapi` when its organization was renamed and went unnoticed until the next release. After any such rename, update the publisher at to match. + For the Conda distribution, there [is a bot](https://github.com/regro-cf-autotick-bot) which will [automatically detect new package versions uploaded to PyPI and create a PR](https://github.com/conda-forge/flopy-feedstock/pull/50) to update the `conda-forge/flopy-feedstock` repository. This PR can be reviewed, updated if needed, and merged to update the package on the `conda-forge` channel. If it becomes necessary to manually publish an update to conda forge, see below. +**Note:** the bot updates only the version number and the checksum. If the release changed FloPy's dependencies or minimum versions, update the recipe's `host` and `run` requirements to match the `Requires-Dist` metadata of the sdist on PyPI before merging. A maintainer can push the correction to the bot's branch. + ### Reset the develop branch Make a new branch from `master`: From c71402f46915096ea5166c52c4520f83b8dca50e Mon Sep 17 00:00:00 2001 From: wpbonelli Date: Tue, 8 Sep 2026 08:19:35 -0400 Subject: [PATCH 2/3] ci: reject pull request titles the changelog would drop Development pull requests are squash merged, so the title becomes the commit message the release notes are generated from, and git-cliff drops anything that is not a conventional commit header without saying so. The title check runs on every pull request except the bot-opened release and reset ones, which are not squash merged. Ports MODFLOW-ORG/modflowapi#115. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01GAtFFGBtJSR8f1bY2VNEZT (cherry picked from commit b63d26bd4e51daab8048d8d313105332b3951544) --- .github/workflows/pull_request.yml | 43 ++++++++++++++++++++++++++++++ docs/make_release.md | 2 ++ 2 files changed, 45 insertions(+) create mode 100644 .github/workflows/pull_request.yml diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml new file mode 100644 index 000000000..3bf558e17 --- /dev/null +++ b/.github/workflows/pull_request.yml @@ -0,0 +1,43 @@ +name: pull request + +on: + pull_request: + types: [opened, edited, reopened, synchronize] + branches: [develop] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + + title: + name: check title + # the release and reset pull requests are opened by a bot and are not + # squash merged, so their titles do not reach the changelog + if: github.event.pull_request.user.type != 'Bot' + runs-on: ubuntu-latest + defaults: + run: + shell: bash + steps: + + - name: Check conventional commit format + env: + TITLE: ${{ github.event.pull_request.title }} + run: | + # pull requests are squash merged, so the title becomes the commit + # message in develop, and the changelog is generated from those + # messages. a title that is not a conventional commit is dropped + # from the release notes without warning. + types="build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test" + if [[ ! "$TITLE" =~ ^($types)(\([^\)]+\))?!?:\ .+ ]]; then + echo "::error::'$TITLE' is not a conventional commit header." + echo "" + echo "Use '(): ', for example:" + echo " feat(mf6): support adaptive time step retries" + echo "" + echo "Type is one of: ${types//|/, }." + echo "Only feat, fix, perf and refactor reach the release notes;" + echo "see cliff.toml. A user facing change needs one of those." + exit 1 + fi + echo "'$TITLE' is a conventional commit header" diff --git a/docs/make_release.md b/docs/make_release.md index 1c7d5ea1c..487685a68 100644 --- a/docs/make_release.md +++ b/docs/make_release.md @@ -72,6 +72,8 @@ Pushing the release branch to the repository triggers the workflow. If the branch name ends with `rc`, it's a dry run and the workflow stops here. If the branch name does not end with `rc` the workflow creates a draft PR from the release branch into the `master` branch. +**Note:** the changelog is generated from commit messages with git-cliff. Development PRs are squash merged, so the PR title becomes that message, and a title that is not a [conventional commit](https://www.conventionalcommits.org/) header is dropped from the notes without warning. [`.github/workflows/pull_request.yml`](../.github/workflows/pull_request.yml) rejects such titles, but it cannot tell whether the type is the right one: a user facing change titled `chore:` still passes the check and is still dropped. Review the generated changelog on the release PR and add anything missing to the section for the version being cut. + ### Merge release branch to master Review the PR and merging if it passes inspection. From 2fb9a4e711987dcc17f60bffb898a4e583c7b99a Mon Sep 17 00:00:00 2001 From: wpbonelli Date: Tue, 8 Sep 2026 09:12:33 -0400 Subject: [PATCH 3/3] docs(release): drop the cross-repo aside from the trusted-publisher note Keep the caveat generic to FloPy rather than referring to another project's release incident. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01GAtFFGBtJSR8f1bY2VNEZT --- docs/make_release.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make_release.md b/docs/make_release.md index 487685a68..23b9e5ed6 100644 --- a/docs/make_release.md +++ b/docs/make_release.md @@ -88,7 +88,7 @@ Review the release and publish it. Publishing the release triggers a final job t The release workflow assumes [trusted publishing](https://docs.pypi.org/trusted-publishers/) has been configured in the PyPI admin interface. A GitHub environment called `release` is required (however it needs no secrets or environment variables). -**Note:** PyPI matches a trusted publisher on the organization name, the repository name, the workflow filename and the environment name. Renaming any of them silently invalidates the publisher, and nothing reports it until the next release fails with `invalid-publisher`. This bit `modflowapi` when its organization was renamed and went unnoticed until the next release. After any such rename, update the publisher at to match. +**Note:** PyPI matches a trusted publisher on the organization name, the repository name, the workflow filename and the environment name. Renaming any of them silently invalidates the publisher, and nothing reports it until the next release fails with `invalid-publisher` — possibly long after the rename. After any such rename, update the publisher at to match. For the Conda distribution, there [is a bot](https://github.com/regro-cf-autotick-bot) which will [automatically detect new package versions uploaded to PyPI and create a PR](https://github.com/conda-forge/flopy-feedstock/pull/50) to update the `conda-forge/flopy-feedstock` repository. This PR can be reviewed, updated if needed, and merged to update the package on the `conda-forge` channel. If it becomes necessary to manually publish an update to conda forge, see below.