semvertag 0.6.0 — dry-run on the composite action
Minor release exposing --dry-run through modern-python/semvertag@v0 and removing the action-smoke side-effect smell. Pure action + CI change — the CLI is unchanged from 0.5.0 (no Python source touched). Consumers who don't need the new input can keep using @v0 exactly as before; the input is opt-in.
If you don't use the composite action and only consume semvertag directly via uvx, you can skip this release — there's no CLI change.
What landed
dry-runinput onaction.yml(default'false'). Whentrue, the composite passes--dry-runtosemvertag tag; semvertag short-circuits beforeprovider.create_tagand the existing case block normalizesstatus="dry_run"to the publicstatus="no-bump". The action'sbump/tagoutputs reflect what would have happened. (PR #16)action-smoke(this repo's CI) becomes structurally side-effect-free. Two layers of protection:- Layer 1:
with: { dry-run: 'true' }onuses: ./— semvertag never attempts a push. - Layer 2:
permissions: contents: writeremoved — even a future regression that bypassed dry-run would 403 on the API.
- Layer 1:
- Assertion reduced from two lines (
status == no-bumpANDbump == none) to one (status == no-bump). Under dry-run,bumpis unstable (reflects the would-be value) — the old check would fail on any PR where main HEAD was an untaggedfeat//bugfix/merge. Status normalization is the real wiring contract; the single check catches a regression loudly (a unwired dry-run would surface ascreated). - CLI version floor in
action.ymlbumps from>=0.3.1,<1to>=0.5.0,<1(the release that ships--dry-run). docs/providers/github.mdgets a## Preview the next bumpsection with both the action-input example and the equivalent localuvxinvocation.
Usage
- id: semvertag
uses: modern-python/semvertag@v0
with:
dry-run: true
- if: steps.semvertag.outputs.bump != 'none'
run: echo "Next release would be ${{ steps.semvertag.outputs.tag }} (${{ steps.semvertag.outputs.bump }} bump)"Under dry-run: true, the action's status output is no-bump (no real tag was pushed). The bump and tag outputs reflect what would have happened, so a downstream conditional like the one above can branch on the planned bump.
Why
PR #14 (in 0.5.0) surfaced the smell: action-smoke ran semvertag against the real main with contents: write, and when main HEAD wasn't already tagged, the smoke test pushed a real release tag from a PR's CI run. 0.5.0 shipped the CLI half (--dry-run); 0.6.0 wires it through the composite and switches the smoke job to use it. After this release:
action-smokeis provably side-effect-free regardless of main HEAD's tag state.- The brittle "main HEAD is always already-tagged" assumption is gone — the test asserts the dry-run wiring, not an external invariant.
- Any consumer who wants to preview the next bump from a workflow can opt in via the same input.
Breaking changes
None. dry-run defaults to 'false'; existing uses: modern-python/semvertag@v0 invocations behave identically to 0.5.x.
The version floor bump (>=0.3.1,<1 → >=0.5.0,<1) is internal to the action — consumers who pin @v0 don't see it directly. If anyone was somehow consuming action.yml outside the @v0 pin and pinning an old semvertag themselves, they'd need to upgrade to 0.5.0+. No known consumer pattern matches this.
Release procedure
tag-major.yml automatically updates the floating v0 tag on release-published, so the steps from 0.4.0's manual bootstrap are no longer needed.
gh release create 0.6.0 \
--title '0.6.0 — dry-run on the composite action' \
--notes-file planning/releases/0.6.0.mdpublish.yml fires on release creation and pushes 0.6.0 to PyPI. tag-major.yml fires in parallel and force-updates v0 to point at the 0.6.0 commit.