From 4fa69083fecec5ab2f3f73854fd56f9a66894127 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 18:30:11 +0000 Subject: [PATCH] Release automatically when the version changes Add a release job to the CI workflow: after the tests pass on a push to the default branch it reads lib/ipdata/version.rb and, when tag v does not exist, creates the tag and the GitHub release. A tag created with the workflow token starts no other workflow, so a publish job in the same run calls the release workflow, which gains a workflow_call trigger with a tag input and checks that tag out. The manual bump run and a pushed tag publish as before. Document the process in the README. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/ci.yml | 62 +++++++++++++++++++++++++++++++++++ .github/workflows/release.yml | 13 ++++++-- README.md | 6 ++++ 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5441ed8..a5e0abf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,3 +27,65 @@ jobs: - name: Verify gem builds run: gem build ipdata.gemspec + + # After a push to the default branch, tag the commit and create the GitHub + # release when lib/ipdata/version.rb holds a version that is not tagged yet. + release: + if: github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + needs: test + runs-on: ubuntu-latest + permissions: + contents: write + concurrency: + group: release + cancel-in-progress: false + outputs: + tag: ${{ steps.release.outputs.tag }} + created: ${{ steps.release.outputs.created }} + + steps: + - uses: actions/checkout@v5 + + - name: Tag and release a new version + id: release + env: + GH_TOKEN: ${{ github.token }} + run: | + version=$(sed -nE 's/.*VERSION *= *"([^"]+)".*/\1/p' lib/ipdata/version.rb | head -1) + if [ -z "$version" ]; then + echo "Could not read the version from lib/ipdata/version.rb" >&2 + exit 1 + fi + tag="v${version}" + echo "tag=${tag}" >> "$GITHUB_OUTPUT" + + status=0 + git ls-remote --exit-code --tags origin "refs/tags/${tag}" > /dev/null || status=$? + case "$status" in + 0) + echo "Tag ${tag} already exists; nothing to release." + echo "created=false" >> "$GITHUB_OUTPUT" + ;; + 2) + gh release create "${tag}" --target "$GITHUB_SHA" --title "${tag}" --generate-notes + echo "Created tag and release ${tag} from ${GITHUB_SHA}." + echo "created=true" >> "$GITHUB_OUTPUT" + ;; + *) + echo "Could not check whether tag ${tag} exists (git ls-remote exited with ${status})." >&2 + exit "$status" + ;; + esac + + # A tag created with the workflow token does not start other workflows, so + # the gem is published from this run through the reusable release workflow. + publish: + needs: release + if: needs.release.outputs.created == 'true' + permissions: + contents: write + id-token: write + uses: ./.github/workflows/release.yml + with: + tag: ${{ needs.release.outputs.tag }} + secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7817705..091b289 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,6 +14,13 @@ on: - patch - minor - major + # Called by the CI workflow after it tags a new version on main. + workflow_call: + inputs: + tag: + description: "Existing tag to publish, for example v1.2.3" + required: true + type: string jobs: bump: @@ -57,7 +64,7 @@ jobs: git push origin main --tags release: - if: always() && (github.event_name == 'push' || needs.bump.result == 'success') + if: always() && (inputs.tag != '' || github.event_name == 'push' || needs.bump.result == 'success') needs: [bump] runs-on: ubuntu-latest permissions: @@ -67,7 +74,9 @@ jobs: steps: - uses: actions/checkout@v5 with: - ref: ${{ github.event_name == 'workflow_dispatch' && 'main' || github.ref }} + # The tag passed by the calling workflow, main after a manual bump, + # or the pushed tag. + ref: ${{ inputs.tag != '' && format('refs/tags/{0}', inputs.tag) || (github.event_name == 'workflow_dispatch' && 'main' || github.ref) }} - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/README.md b/README.md index 145a87c..6b3bdbb 100644 --- a/README.md +++ b/README.md @@ -260,6 +260,12 @@ bundle install bundle exec rake test ``` +## Releasing + +Bump `VERSION` in `lib/ipdata/version.rb` in a pull request and merge it. CI then tags the merge commit `vx.y.z`, creates the GitHub release and publishes the gem to RubyGems. A merge whose version is already tagged publishes nothing. + +The *Publish to RubyGems* workflow can also be run manually with a `bump` input (`patch`, `minor` or `major`): it commits the bump to `main`, tags it and publishes the gem in the same run. + ## License [MIT](LICENSE)