diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3dd52ea..ca5c62a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,10 +4,10 @@ name: Release on: push: - tags: ['[0-9]+.[0-9]+.[0-9]+'] + tags: ['v[0-9]+.[0-9]+.[0-9]+'] permissions: - contents: read + contents: write # create the GitHub release id-token: write # OIDC for RubyGems trusted publishing (FR-021a) jobs: @@ -23,7 +23,9 @@ jobs: - name: The tag must match the packaged version run: | - TAG="${GITHUB_REF_NAME}" + # Every tag in this repository, and in api-reference, carries the v. The version + # inside the package never does, so the tag is compared with it stripped. + TAG="${GITHUB_REF_NAME#v}" PKG="$(ruby -Ilib -e "require 'flat_api/version'; print FlatApi::VERSION")" [ "$TAG" = "$PKG" ] || { echo "tag $TAG != VERSION $PKG"; exit 1; } @@ -37,7 +39,20 @@ jobs: - uses: rubygems/configure-rubygems-credentials@dc5a8d8553e6ee01fc26761a49e99e733d17954a # v2.1.0 # Attestations are generated by rubygems itself when it publishes over trusted publishing. - - run: gem push "flat_api-$(cat VERSION).gem" + - name: Skip the publish if this version is already on RubyGems + id: published + run: | + VERSION="${GITHUB_REF_NAME#v}" + if ( cd "$(mktemp -d)" && gem fetch flat_api --version "$VERSION" >/dev/null 2>&1 ); then + echo "flat_api $VERSION is already on RubyGems; nothing to publish." + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + + - name: Publish to RubyGems (trusted publishing) + if: steps.published.outputs.skip != 'true' + run: gem push "flat_api-$(cat VERSION).gem" # The push is accepted asynchronously, so confirm the version is actually fetchable rather # than reporting a green release for a gem nobody can install yet. @@ -60,3 +75,25 @@ jobs: echo "flat_api $VERSION did not appear on RubyGems within five minutes. Last attempt:" ( cd "$WORK" && gem fetch flat_api --version "$VERSION" ) || true exit 1 + + # The tag alone left the repository's Releases page showing a version from years ago as + # "Latest", which is what anyone browsing the repository sees first. The notes come from the + # CHANGELOG section for this version, so the release says what changed rather than nothing. + - name: Publish the GitHub release + env: + GH_TOKEN: ${{ github.token }} + run: | + VERSION="${GITHUB_REF_NAME#v}" + NOTES="$(awk -v v="$VERSION" ' + $0 ~ "^## \\[?" v "\\]?" { found = 1; next } + found && /^## / { exit } + found { print } + ' CHANGELOG.md)" + [ -n "$NOTES" ] || NOTES="See CHANGELOG.md for $VERSION." + # Creating one that exists is an error, and a re-run of a release that already happened + # should be a no-op rather than a red build. + if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then + echo "Release $GITHUB_REF_NAME already exists; nothing to do." + else + gh release create "$GITHUB_REF_NAME" --title "$GITHUB_REF_NAME" --notes "$NOTES" + fi diff --git a/.github/workflows/tag-on-merge.yml b/.github/workflows/tag-on-merge.yml index 477d6d6..f1bc04e 100644 --- a/.github/workflows/tag-on-merge.yml +++ b/.github/workflows/tag-on-merge.yml @@ -36,7 +36,9 @@ jobs: token: ${{ secrets.SDK_RELEASE_TOKEN }} - name: Tag the version if it is new run: | - VERSION="$(cat VERSION)" + # v-prefixed, matching every tag this repository and api-reference already carry. + # The version inside the package stays bare; only the git tag is prefixed. + VERSION="v$(cat VERSION)" if git rev-parse "$VERSION" >/dev/null 2>&1; then echo "Tag $VERSION already exists, nothing to do." exit 0