Skip to content

Automate the post-release version bump - #12

Merged
menjoo merged 1 commit into
mainfrom
automate-post-release
Aug 2, 2026
Merged

Automate the post-release version bump#12
menjoo merged 1 commit into
mainfrom
automate-post-release

Conversation

@menjoo

@menjoo menjoo commented Aug 2, 2026

Copy link
Copy Markdown
Owner

The README still says 0.1.1 now that 0.1.1 has shipped, because step 3 of RELEASING.md was a list
of commands someone had to remember. This makes it a workflow.

What it does

Actions → Post-releaseRun workflow, entering the version just released. It:

  1. checks the tag exists — pointing the docs at an unreleased version would send people to a 404
  2. runs set-docs-version.py <version> over the README's install snippets
  3. runs set-version.sh <next>-SNAPSHOT
  4. pushes post-<version> and links to the Open a pull request page in its summary

Simulated end to end against a clone: produces README.md | 8 ++-- and gradle.properties | 2 +-,
which is what #7 did by hand.

Why it anchors on coordinates, not version numbers

set-docs-version.py matches only id("io.github.menjoo.cucumberkmp") version "X" and
io.github.menjoo.cucumberkmp:<module>:X.

Of the fifteen version references in this repository, eight are historical and must never be
rewritten:

Where Text
release.yml "0.1.0 was lost to precisely this"
verify-publishable.py "The 0.1.0 release failed on exactly these rules"
RELEASING.md "Step 5 exists because 0.1.0 was lost without it"
ARCHITECTURE.md "0.1.0 does not exist on Central", "the v0.1.0 tag remains as a record"
get-version.sh, RELEASING.md doc-comment examples

A substitution blunt enough to catch the install snippets would erase the record of the incident
that step 5 of the release workflow exists to prevent. Anchoring on the coordinate cannot reach
prose at all. Verified: the KSP plugin's version "2.3.10" on the line above the plugin id is
untouched, and ARCHITECTURE.md comes out byte-identical.

It also fails loudly if the anchors match nothing, rather than reporting success — otherwise
"documentation is current" and "the patterns broke in a docs restructure" look the same, which is
how a script like this rots unnoticed.

ARCHITECTURE.md is deliberately not rewritten. Its status lines want a human sentence.

Why it doesn't open the pull request

A pull request opened with GITHUB_TOKEN does not trigger workflow runs — GitHub blocks that to
prevent recursion. With CI now required on main, a bot-opened pull request would sit permanently
unable to satisfy its own checks. Opening it yourself is one click and makes CI run normally.

A PAT would buy that click for a long-lived credential, which is the trade RELEASING.md already
declines under "Why not one button".

Notes

  • Python rather than bash, matching verify-publishable.py: anchored regex with capture groups and
    per-line reporting is awkward in shell.
  • Hardening carried over from Escape feature-file text in generated code, harden CI #10: contents: write only, inputs reach bash through env, actions
    SHA-pinned.
  • Also retargets the Release workflow's "Next:" summary at this workflow.

Ordering

Merge after #11. The first real use is 0.1.2's post-release step.

README's install snippets still said 0.1.1 after 0.1.1 shipped, because
step 3 of RELEASING.md was a list of commands someone had to remember.

set-docs-version.py rewrites them, and a Post-release workflow runs it
alongside set-version.sh and pushes a branch.

It anchors on dependency coordinates -- id("io.github.menjoo.cucumberkmp")
version "X" and io.github.menjoo.cucumberkmp:<module>:X -- rather than on
version numbers. That distinction is the point. Of the fifteen version
references in this repository, eight are historical: "0.1.0 was lost to
precisely this", "0.1.0 does not exist on Central", "the v0.1.0 tag
remains as a record", and doc-comment examples in two scripts. A
substitution blunt enough to catch the install snippets would erase the
record of the incident that step 5 of the release workflow exists to
prevent. Anchoring on the coordinate cannot reach prose at all.

It also refuses to be a silent no-op. If the anchors match nothing it
fails loudly rather than reporting success, because "documentation is
current" and "the patterns broke in a docs restructure" otherwise look
identical -- which is how a script like this rots unnoticed.

ARCHITECTURE.md is left alone. Its version references are prose, and its
status lines want a human sentence rather than a substitution.

The workflow pushes a branch and stops. It does not open the pull
request: one opened with GITHUB_TOKEN does not trigger workflow runs, so
with CI now required on main a bot-opened pull request would sit unable
to satisfy its own checks. A personal access token would buy the last
click for a long-lived credential, which is the trade RELEASING.md
already declines under "Why not one button". The summary links to the
compare page instead.

Python rather than bash, matching verify-publishable.py: anchored regex
with capture groups and per-line reporting is awkward in shell.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@menjoo
menjoo merged commit 59d8868 into main Aug 2, 2026
3 checks passed
@menjoo
menjoo deleted the automate-post-release branch August 2, 2026 09:30
menjoo added a commit that referenced this pull request Aug 2, 2026
`0.0.9` after `0.1.1` currently releases cleanly: it is valid semver, is
not a `-SNAPSHOT`, and
`v0.0.9` does not exist, so all four checks in "Check the version" pass
it. Publishing to Central
is the irreversible half, so that is worth closing.

## The comparison

Two deliberate looseness choices, both to avoid half-implementing semver
precedence in bash:

- **Numeric core only** — `1.0.0-rc1` is compared as `1.0.0`.
- **"Not older" rather than "strictly newer"** — this is what lets
`1.0.0` follow `1.0.0-rc1`,
which a strict-greater check would wrongly block. An exact repeat is
already refused by the
  tag-exists check immediately above.

Residual gap: `1.0.0-rc2` after `1.0.0` is allowed. Rare and ambiguous
enough not to be worth the
machinery.

Field-wise numeric sort (`sort -t. -k1,1n -k2,2n -k3,3n`) rather than
`sort -V`, which is not
portable across GNU and BSD and orders pre-release versions wrongly
regardless.

## Tested

| Tags | Version | Result |
|---|---|---|
| `v0.1.0 v0.1.1` | `0.1.2`, `0.2.0`, `1.0.0` | allow |
| `v0.1.0 v0.1.1` | `0.0.9`, `0.1.0` | **refuse** |
| `v0.1.0 v0.1.1` | `0.1.11`, `10.0.0` | allow — lexicographic
comparison gets both backwards |
| `v0.1.0 v0.1.1` | `0.1.1` | allow — correctly deferred to the
tag-exists check |
| `… v1.0.0-rc1` | `1.0.0` | allow — the case a strict-greater check
breaks |
| none | anything | allow |

## Why the input stays

It guards a different failure mode, so the new check does not subsume
it:

| Guard | Catches |
|---|---|
| `requested == committed` | main isn't where you think — **in either
direction** |
| monotonic vs latest tag | main went backwards |

If you believe main is on 0.1.2 and someone merged 0.2.0, monotonicity
passes and 0.2.0 ships.

It is also what `run-name` reads. `run-name` is evaluated before any job
starts and can only use
the `github`, `inputs` and `vars` contexts, so it cannot read
`gradle.properties` — dropping the
input would put the Actions list back to a column of identical "Release"
entries, which is what #8
was merged to fix.

## Ordering

Independent of #11 and #12; touches a different region of `release.yml`
than #12 does.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant