diff --git a/.github/workflows/semantic-release.yml b/.github/workflows/semantic-release.yml index b2e3aab9..b6c2c920 100644 --- a/.github/workflows/semantic-release.yml +++ b/.github/workflows/semantic-release.yml @@ -25,6 +25,31 @@ jobs: uses: actions/checkout@v7 with: fetch-depth: 0 + - name: Mirror prerelease tags as semver + run: | + # Prerelease tags use PEP 440 (v3.0.0b1) to match the published + # package version, but semantic-release only understands semver and + # silently ignores tags it cannot parse. Without this, every run + # reports the first prerelease of the channel as the next version. + # Mirror each PEP 440 prerelease tag onto its semver equivalent + # (v3.0.0-beta.1). These are local to the run and are never pushed; + # the PEP 440 tags remain the only real ones. The channel notes the + # Version workflow records against the tagged commit apply to the + # mirrored tag too, since notes attach to commits, not tags. + for tag in $(git tag --list 'v*'); do + semver="$( + printf '%s' "${tag#v}" | sed -E \ + -e 's/^([0-9]+\.[0-9]+\.[0-9]+)a([0-9]+)$/\1-alpha.\2/' \ + -e 's/^([0-9]+\.[0-9]+\.[0-9]+)b([0-9]+)$/\1-beta.\2/' \ + -e 's/^([0-9]+\.[0-9]+\.[0-9]+)rc([0-9]+)$/\1-rc.\2/' + )" + if [ "$semver" = "${tag#v}" ]; then + continue + fi + + git tag --force "v$semver" "$tag^{commit}" + echo "Mirrored $tag as v$semver." + done - name: Semantic release id: release uses: cycjimmy/semantic-release-action@v6 diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index afb09b8c..723815d3 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -31,20 +31,28 @@ jobs: passphrase: ${{ secrets.GPG_PASSPHRASE }} - name: Setup uses: ./.github/actions/setup + # uv normalizes the semver version semantic-release computes + # (3.0.0-beta.1) to PEP 440 (3.0.0b1), which is what gets committed, + # tagged and published. The Semantic Release workflow mirrors that tag + # back to semver so it can pick up where the last prerelease left off. - name: Cut ${{ github.event.inputs.version }} version run: | uv version "${{ github.event.inputs.version }}" just version + # semantic-release only treats a prerelease tag as released when a note + # records the channel it went out on, so record it here, once the release + # exists. The note is keyed to the commit rather than the tag, which is + # what lets the mirrored semver tag pick it up. - name: Record prerelease channel - env: - VERSION: ${{ github.event.inputs.version }} run: | - case "$VERSION" in - *-*) channel="${VERSION#*-}"; channel="${channel%%.*}" ;; + tag="v$(uv version --short)" + case "$(uv version --short)" in + *.*.*a[0-9]*) channel=alpha ;; + *.*.*b[0-9]*) channel=beta ;; + *.*.*rc[0-9]*) channel=rc ;; *) echo "Stable release, no channel note required."; exit 0 ;; esac - git fetch origin "+refs/notes/semantic-release:refs/notes/semantic-release" || true - git notes --ref semantic-release add --force \ - --message "{\"channels\":[\"$channel\"]}" "v$VERSION^{commit}" - git push origin refs/notes/semantic-release - echo "Recorded v$VERSION on the '$channel' channel." + git notes --ref "semantic-release-$tag" add --force \ + --message "{\"channels\":[\"$channel\"]}" "$tag^{commit}" + git push origin "refs/notes/semantic-release-$tag" + echo "Recorded $tag on the '$channel' channel."