diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..09239104e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,20 @@ +name: Release + +on: + workflow_dispatch: + inputs: + version: + description: Optional release version overriding the checked-in version. + type: string + required: false + +permissions: + contents: write + id-token: write + +jobs: + release: + uses: dblock/ruby-gem-release/.github/workflows/release.yml@v0.6.0 + with: + ruby-version: '3.4' + version: ${{ inputs.version }} diff --git a/CHANGELOG.md b/CHANGELOG.md index dabfb17a1..f5301a1d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * [#3056](https://github.com/ruby-grape/grape/pull/3056): Drop support for Rails 7.2 and require ActiveSupport 8.0 - [@ericproulx](https://github.com/ericproulx). * [#3062](https://github.com/ruby-grape/grape/pull/3062): Use the reusable Danger workflow with a separate Gemfile - [@dblock](https://github.com/dblock). * [#3063](https://github.com/ruby-grape/grape/pull/3063): Drop support for Rack 2.2 and 3.0 and require Rack 3.1 - [@ericproulx](https://github.com/ericproulx). +* [#3078](https://github.com/ruby-grape/grape/pull/3078): Automate gem releases with ruby-gem-release - [@dblock](https://github.com/dblock). * Your contribution here. #### Fixes diff --git a/Gemfile b/Gemfile index 41e95ef63..377914fde 100644 --- a/Gemfile +++ b/Gemfile @@ -18,6 +18,7 @@ group :development do gem 'benchmark-ips' gem 'benchmark-memory' gem 'irb' + gem 'ruby-gem-release', '0.6.0', require: false end group :test do diff --git a/RELEASING.md b/RELEASING.md index 9774d9f52..f273724c8 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,112 +1,71 @@ Releasing Grape =============== -There're no particular rules about when to release Grape. Release bug fixes frequently, features not so frequently and breaking API changes rarely. +There are no particular rules about when to release Grape. Release bug fixes frequently, features not so frequently and breaking API changes rarely. -### Release - -Run tests, check that all tests succeed locally. - -``` -bundle install -rake -``` - -Double-check that the [last build succeeded](https://github.com/ruby-grape/grape/actions) for all supported platforms. - -Those with r/w permissions to the [master Grape repository](https://github.com/ruby-grape/grape) generally have large Grape-based projects. Point one to Grape HEAD and run all your API tests to catch any obvious regressions. - -``` -gem grape, github: 'ruby-grape/grape' -``` - -Modify the "Stable Release" section in [README.md](README.md). Change the text to reflect that this is going to be the documentation for a stable release. Remove references to the previous release of Grape. Keep the file open, you'll have to undo this change after the release. - -``` -## Stable Release - -You're reading the documentation for the stable release of Grape, 0.6.0. -``` - -Change "Next Release" in [CHANGELOG.md](CHANGELOG.md) to the new version. - -``` -#### 0.6.0 (2013/9/16) -``` +### One-Time Setup -Remove the line with "Your contribution here.", since there will be no more contributions to this release. +Releases use [ruby-gem-release 0.6.0](https://github.com/dblock/ruby-gem-release/tree/v0.6.0), pinned in both the Gemfile and reusable workflow, and RubyGems Trusted Publishing without a stored RubyGems API key. -Commit your changes. +Create a GitHub environment named `release` in **Settings > Environments**. Configure required release-maintainer reviewers, prevent self-review where appropriate, and restrict deployments to `master`. Creating an unprotected environment alone does not restrict publication: repository writers can dispatch the workflow, and required reviewers gate publication. -``` -git add README.md CHANGELOG.md -git commit -m "Preparing for release, 0.6.0." -git push origin master -``` +In the [grape trusted publishers settings](https://rubygems.org/gems/grape/trusted_publishers), configure these values: -Release. +| Field | Value | +| --- | --- | +| Repository owner | `ruby-grape` | +| Repository name | `grape` | +| Workflow filename | `release.yml` | +| Environment | `release` | +| Workflow repository owner | `dblock` | +| Workflow repository name | `ruby-gem-release` | -``` -$ rake release +The workflow repository identifies the reusable workflow, not Grape's caller workflow. Ensure branch and tag rules permit `GITHUB_TOKEN` to push release commits to `master` and create `vVERSION` tags. If rules require PRs or checks that the release token cannot bypass, a repository administrator must establish an approved release policy before dispatching; the workflow does not change repository protections. -grape 0.6.0 built to pkg/grape-0.6.0.gem. -Tagged v0.6.0. -Pushed git commits and tags. -Pushed grape 0.6.0 to rubygems.org. -``` - -### Prepare for the Next Version +### Release -Modify the "Stable Release" section in [README.md](README.md). Change the text to reflect that this is going to be the next release. +Run tests and check that all tests succeed locally: +```sh +bundle install +bundle exec rake +bundle exec rake build ``` -## Stable Release -You're reading the documentation for the next release of Grape, which should be 0.6.1. -The current stable release is [0.6.0](https://github.com/ruby-grape/grape/blob/v0.6.0/README.md). -``` +The release specs use `ruby-gem-release/rspec` to exercise the production workflow and README hooks in a disposable Git repository, replacing registry and hosted publication with the library's local adapters. They do not publish to RubyGems or GitHub. -Add the next release to [CHANGELOG.md](CHANGELOG.md). +Double-check that the [last build succeeded](https://github.com/ruby-grape/grape/actions) for all supported platforms. Point a Grape-based project at Grape HEAD and run its API tests to catch regressions: +```ruby +gem 'grape', github: 'ruby-grape/grape' ``` -### 0.6.1 (Next) - -#### Features -* Your contribution here. +Review the next section of [CHANGELOG.md](CHANGELOG.md) and the version in [lib/grape/version.rb](lib/grape/version.rb). Keep one `* Your contribution here.` placeholder in each of Features and Fixes; ruby-gem-release preserves this two-section format. Actual release notes are required. -#### Fixes +Dispatch **Release** from `master` in GitHub Actions, or run: -* Your contribution here. +```sh +gh workflow run release.yml --repo ruby-grape/grape --ref master ``` -Bump the minor version in lib/grape/version.rb. +To select a different release version without a separate version-bump commit, pass `-f version=4.2.0`. Dispatching authorizes publication once the `release` environment's approval requirements are satisfied; do not dispatch from PR CI or use it as a dry run. -```ruby -module Grape - VERSION = '0.6.1'.freeze -end -``` +The workflow validates release preconditions, dates the changelog and removes both contribution placeholders, updates the README to describe the stable release, and runs `bundle exec rake`. It builds and preserves the gem and recovery artifacts, publishes that exact gem to RubyGems, pushes the release commit and tag, and creates a GitHub Release. It then increments the patch version and pushes the next development iteration with both changelog sections and the README's stable-release link restored. No manual release edits or `rake release` invocation are needed. -Commit your changes. +### Recovery -``` -git add CHANGELOG.md README.md lib/grape/version.rb -git commit -m "Preparing for next development iteration, 0.6.1." -git push origin master -``` +Inspect the failed run and its `release-gem-*` artifact before retrying. It can contain the built gem, checksum manifest, and Git recovery bundle. RubyGems publication cannot be undone by resetting Git: if a version was uploaded, never publish different bytes under that version or move its tag. Follow the [ruby-gem-release recovery guide](https://github.com/dblock/ruby-gem-release/blob/v0.6.0/RELEASING.md#verify-and-recover) to determine which steps completed and recover deliberately. ### Make an Announcement -Make an announcement on the [ruby-grape@googlegroups.com](mailto:ruby-grape@googlegroups.com) mailing list. The general format is as follows. +Make an announcement on the [ruby-grape@googlegroups.com](mailto:ruby-grape@googlegroups.com) mailing list: -``` -Grape 0.6.0 has been released. +```text +Grape 4.2.0 has been released. -There were 8 contributors to this release, not counting documentation. +There were N contributors to this release, not counting documentation. Please note the breaking API change in ... [copy/paste CHANGELOG here] - ``` diff --git a/Rakefile b/Rakefile index 2d79797f7..64e7ff2dc 100644 --- a/Rakefile +++ b/Rakefile @@ -25,3 +25,6 @@ require('rubocop/rake_task') RuboCop::RakeTask.new task(default: %i[rubocop spec]) + +require_relative 'tasks/release' +GrapeRelease.workflow.install_tasks diff --git a/spec/tasks/release_notes_spec.rb b/spec/tasks/release_notes_spec.rb new file mode 100644 index 000000000..c84636d0a --- /dev/null +++ b/spec/tasks/release_notes_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'ruby-gem-release/rspec' +require_relative '../../tasks/release' + +describe GrapeRelease do + include RubyGemRelease::RSpec + + it 'runs the production README hooks in the release and next development commits' do + with_release_project do |project| + project.copy_files(path: File.expand_path('../..', __dir__), files: %w[tasks/release.rb grape.gemspec lib/grape/version.rb README.md CHANGELOG.md]) + project.configure_workflow(require: 'tasks/release', builder: 'GrapeRelease.workflow') + project.commit! + + project.release!(version: '4.2.9') + + readme = File.read(File.expand_path('../../README.md', __dir__)) + section = /^## Stable Release\n.*?(?=^## |\z)/m + stable = "You're reading the documentation for the stable release of Grape, 4.2.9." + development = "You're reading the documentation for the next release of Grape, which should be 4.2.10.\n" \ + 'The current stable release is [4.2.9](https://github.com/ruby-grape/grape/blob/v4.2.9/README.md).' + expect(project.remote_file('v4.2.9', 'README.md')).to eq(readme.sub(section, "## Stable Release\n\n#{stable}\n\n").strip) + expect(project.remote_file('master', 'README.md')).to eq(readme.sub(section, "## Stable Release\n\n#{development}\n\n").strip) + expect(project.git('status', '--porcelain')).to eq('') + end + end +end diff --git a/tasks/release.rb b/tasks/release.rb new file mode 100644 index 000000000..b156374bd --- /dev/null +++ b/tasks/release.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'ruby-gem-release/workflow/ruby_gem' +require 'ruby-gem-release/changelog' +require 'ruby-gem-release/markdown/section' + +module GrapeRelease + def self.workflow(**) + workflow = RubyGemRelease::Workflow::RubyGem.new( + version: RubyGemRelease::Version::RubyConstant.new(path: 'lib/grape/version.rb', constant: 'VERSION'), + changelog: RubyGemRelease::Changelog::Intridea.new(placeholder: '* Your contribution here.'), + ** + ) + + readme = RubyGemRelease::Markdown::Section.new(path: 'README.md', heading: '## Stable Release') + workflow.after(:prepare_release) do |ctx| + readme.replace!("You're reading the documentation for the stable release of Grape, #{ctx.release.version}.") + end + workflow.after(:prepare_next) do |ctx| + readme.replace!( + "You're reading the documentation for the next release of Grape, which should be #{ctx.release.next_version}.\n" \ + "The current stable release is [#{ctx.release.version}](https://github.com/ruby-grape/grape/blob/v#{ctx.release.version}/README.md)." + ) + end + workflow + end +end