diff --git a/.github/workflows/release-drift.yml b/.github/workflows/release-drift.yml index f1ccc54..15c9fc4 100644 --- a/.github/workflows/release-drift.yml +++ b/.github/workflows/release-drift.yml @@ -29,6 +29,19 @@ # job is the backstop for when somebody forgets, which is the right shape for a # rule no tooling here enforces. # +# ## It also checks which release GitHub calls "Latest" +# +# Because the remedy for the first check broke it. 0.13.0 was re-pushed on its +# own as advised, released the day AFTER 0.16.0, and GitHub — which picks the +# newest-published release, not the highest version, when nobody says otherwise — +# made 0.13.0 "Latest". `mise use github:chussenot/pact@latest` then installed +# 0.13.0 for seven weeks. Both lists agreed on every tag that mattered; the badge +# was the only thing wrong, and nothing compared it to anything. +# +# `release.yml` now passes `--latest` explicitly, but a re-pushed tag runs the +# workflow AS IT WAS AT THAT TAG, so recovering an old tag still runs the old +# pipeline. Hence the remedy below ends with putting the badge back. +# # ## Why it only reports # # It opens no release. Republishing a tag is a decision with a blast radius — @@ -91,19 +104,49 @@ jobs: fi done - if [ -z "$missing" ]; then + # The highest PUBLISHED version: what "Latest" must be, and so what + # `@latest` installs. + highest="$(grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' <<<"$releases" | tail -n1)" + failed=0 + + if [ -n "$missing" ]; then + failed=1 + echo "::error::tags on master with no GitHub release:$missing" + echo + echo "Most likely cause: more than three tags were pushed in one push, so" + echo "GitHub created no tag push events and release.yml never ran. Push" + echo "version tags one at a time." + echo + echo "To release a missed tag, re-push it on its own:" + for tag in $missing; do + echo " git push origin :refs/tags/$tag && git push origin $tag" + done + echo + echo "Each re-push runs release.yml as it was AT THAT TAG, which may not" + echo "set --latest. When they have all finished, put the badge back:" + # The badge's target once the re-pushes land, which is a missing tag + # itself when the newest tag is the one that never released. + target="$(printf '%s\n' "$highest" $missing | sort -V | tail -n1)" + echo " gh release edit $target --latest" + echo + else echo "every version tag on master has a release" - exit 0 fi - echo "::error::tags on master with no GitHub release:$missing" - echo - echo "Most likely cause: more than three tags were pushed in one push, so" - echo "GitHub created no tag push events and release.yml never ran. Push" - echo "version tags one at a time." - echo - echo "To release a missed tag, re-push it on its own:" - for tag in $missing; do - echo " git push origin :refs/tags/$tag && git push origin $tag" - done - exit 1 + # Compared against releases, not tags: a tag still waiting on its + # release is the check above's problem, not this one's. + latest="$(gh release view --json tagName -q .tagName)" + if [ "$latest" != "$highest" ]; then + failed=1 + echo "::error::GitHub's Latest release is $latest, but the highest published version is $highest" + echo + echo "Anything installing @latest is getting $latest. Most likely cause: an" + echo "older tag was released after $highest and GitHub gave it the badge." + echo + echo "To fix:" + echo " gh release edit $highest --latest" + else + echo "Latest is $latest, the highest published version" + fi + + exit "$failed" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4700614..c9b08f0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -513,14 +513,31 @@ jobs: # Neither a draft nor a prerelease: 0.x IS this project's release series, # and a draft that needs a human to press a button is a release that # sometimes does not happen. + # + # `--latest` is ALWAYS passed, true or false, never left to GitHub. Left + # unset, the newest-PUBLISHED release wins, not the highest version: 0.13.0 + # was released late, the day after 0.16.0, took the "Latest" badge, and + # `mise use github:chussenot/pact@latest` installed 0.13.0 for seven weeks + # while every check stayed green. A late release of an old tag is exactly + # what `release-drift.yml` tells you to do, so this is the normal path. - name: Create the release env: GH_TOKEN: ${{ github.token }} TAG: ${{ needs.resolve.outputs.tag }} run: | set -euo pipefail + # Latest iff no PUBLISHED release outranks this tag. Released, not + # tagged: a higher tag whose release never happened must not leave the + # repository with no Latest at all. + highest="$( + { gh release list --limit 200 --json tagName -q '.[].tagName'; echo "$TAG"; } \ + | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n1 + )" + if [ "$highest" = "$TAG" ]; then latest=true; else latest=false; fi + echo "highest published version after this release: $highest (latest=$latest)" gh release create "$TAG" \ --title "$TAG" \ --notes-file /tmp/notes.md \ --verify-tag \ + --latest="$latest" \ dist/*.tar.gz dist/SHA256SUMS diff --git a/cog.toml b/cog.toml index 145600a..a803b27 100644 --- a/cog.toml +++ b/cog.toml @@ -26,5 +26,3 @@ path = "CHANGELOG.md" authors = [] [bump_profiles] - -[packages]