From cf1f9229ea91ac872f157b8c1c6e41af99d611e5 Mon Sep 17 00:00:00 2001 From: Neil Carvalho Date: Fri, 17 Apr 2026 17:13:47 -0300 Subject: [PATCH 1/3] Add workflow to prepare a release PR Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/prepare-release.yml | 104 ++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 .github/workflows/prepare-release.yml diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml new file mode 100644 index 000000000..f63e80a71 --- /dev/null +++ b/.github/workflows/prepare-release.yml @@ -0,0 +1,104 @@ +name: Prepare release + +on: + workflow_dispatch: + inputs: + bump: + description: Version bump type + required: true + type: choice + options: + - patch + - minor + - major + +jobs: + prepare: + name: Bump version and open release PR + runs-on: ubuntu-latest + permissions: + contents: write # push release branch + pull-requests: write # open draft PR + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 # required to reach tag history + + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.3" + + - name: Bump version + id: version + run: | + gem install gem-release --no-document + gem bump --version ${{ inputs.bump }} --no-commit + NEW=$(ruby -e "require_relative 'lib/factory_bot/version'; puts FactoryBot::VERSION") + echo "new=$NEW" >> "$GITHUB_OUTPUT" + + - name: Collect merged PRs since last release + id: changelog + env: + GH_TOKEN: ${{ github.token }} + run: | + LAST_TAG=$(git tag --sort=-version:refname | grep -E '^v?[0-9]' | head -1) + SINCE=$(git log -1 --format=%as "$LAST_TAG") + ENTRIES=$(gh pr list \ + --state merged \ + --base main \ + --search "merged:>$SINCE" \ + --json number,title,author,url \ + --limit 200 \ + | jq -r '.[] | "* \(.title) by @\(.author.login) in [#\(.number)](\(.url))"') + echo "entries<> "$GITHUB_OUTPUT" + echo "$ENTRIES" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + - name: Update NEWS.md + env: + NEW_VERSION: ${{ steps.version.outputs.new }} + PR_ENTRIES: ${{ steps.changelog.outputs.entries }} + run: | + ruby - <<~RUBY + require 'date' + + news = File.read('NEWS.md') + version = ENV.fetch('NEW_VERSION') + entries = ENV.fetch('PR_ENTRIES') + today = Date.today.strftime('%B %-d, %Y') + + # Extract and remove the Unreleased section (if present) + unreleased = '' + if news =~ /^## Unreleased\n(.*?)(?=^## )/m + unreleased = $1.strip + news = news.sub(/^## Unreleased\n.*?(?=^## )/m, '') + end + + # Combine unreleased entries and merged PR entries + body = [unreleased, entries].reject(&:empty?).join("\n") + new_section = "## #{version} (#{today})\n\n#{body}\n" + + # Insert the new section right after the title + news.sub!(/^(# News\n+)/, "\\1#{new_section}\n") + + File.write('NEWS.md', news) + RUBY + + - name: Push branch and open draft PR + env: + GH_TOKEN: ${{ github.token }} + NEW_VERSION: ${{ steps.version.outputs.new }} + run: | + BRANCH="release/v$NEW_VERSION" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git checkout -b "$BRANCH" + git add lib/factory_bot/version.rb NEWS.md + git commit -m "Release v$NEW_VERSION" + git push origin "$BRANCH" + gh pr create \ + --draft \ + --base main \ + --head "$BRANCH" \ + --title "Release v$NEW_VERSION" \ + --body "Bumps version to \`$NEW_VERSION\` and updates the changelog with merged PRs since the last release." From ba7d8caef04dcf2b7aac2c3f2f3581f7d67ba2c8 Mon Sep 17 00:00:00 2001 From: Neil Carvalho Date: Fri, 17 Apr 2026 17:28:19 -0300 Subject: [PATCH 2/3] Extract changelog update logic into a Rake task Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/prepare-release.yml | 47 ++------------------------- Rakefile | 2 ++ tasks/release.rake | 39 ++++++++++++++++++++++ 3 files changed, 43 insertions(+), 45 deletions(-) create mode 100644 tasks/release.rake diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index f63e80a71..389a7f62e 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -36,53 +36,10 @@ jobs: NEW=$(ruby -e "require_relative 'lib/factory_bot/version'; puts FactoryBot::VERSION") echo "new=$NEW" >> "$GITHUB_OUTPUT" - - name: Collect merged PRs since last release - id: changelog - env: - GH_TOKEN: ${{ github.token }} - run: | - LAST_TAG=$(git tag --sort=-version:refname | grep -E '^v?[0-9]' | head -1) - SINCE=$(git log -1 --format=%as "$LAST_TAG") - ENTRIES=$(gh pr list \ - --state merged \ - --base main \ - --search "merged:>$SINCE" \ - --json number,title,author,url \ - --limit 200 \ - | jq -r '.[] | "* \(.title) by @\(.author.login) in [#\(.number)](\(.url))"') - echo "entries<> "$GITHUB_OUTPUT" - echo "$ENTRIES" >> "$GITHUB_OUTPUT" - echo "EOF" >> "$GITHUB_OUTPUT" - - name: Update NEWS.md env: - NEW_VERSION: ${{ steps.version.outputs.new }} - PR_ENTRIES: ${{ steps.changelog.outputs.entries }} - run: | - ruby - <<~RUBY - require 'date' - - news = File.read('NEWS.md') - version = ENV.fetch('NEW_VERSION') - entries = ENV.fetch('PR_ENTRIES') - today = Date.today.strftime('%B %-d, %Y') - - # Extract and remove the Unreleased section (if present) - unreleased = '' - if news =~ /^## Unreleased\n(.*?)(?=^## )/m - unreleased = $1.strip - news = news.sub(/^## Unreleased\n.*?(?=^## )/m, '') - end - - # Combine unreleased entries and merged PR entries - body = [unreleased, entries].reject(&:empty?).join("\n") - new_section = "## #{version} (#{today})\n\n#{body}\n" - - # Insert the new section right after the title - news.sub!(/^(# News\n+)/, "\\1#{new_section}\n") - - File.write('NEWS.md', news) - RUBY + GH_TOKEN: ${{ github.token }} + run: bundle exec rake "release:update_changelog[${{ steps.version.outputs.new }}]" - name: Push branch and open draft PR env: diff --git a/Rakefile b/Rakefile index a5077b70f..f885eaf9d 100644 --- a/Rakefile +++ b/Rakefile @@ -8,6 +8,8 @@ require "standard/rake" Bundler::GemHelper.install_tasks(name: "factory_bot") +Dir.glob("tasks/*.rake").each { |f| load f } + desc "Default: run all specs and standard" task default: %w[all_specs standard] diff --git a/tasks/release.rake b/tasks/release.rake new file mode 100644 index 000000000..cf8cae1ae --- /dev/null +++ b/tasks/release.rake @@ -0,0 +1,39 @@ +require "date" +require "json" + +namespace :release do + desc "Update NEWS.md with merged PRs since the last release tag. Usage: rake release:update_changelog[VERSION]" + task :update_changelog, [:version] do |_, args| + version = args.fetch(:version) { abort "Usage: rake release:update_changelog[VERSION]" } + + # Find the last release tag and when it was made + last_tag = `git tag --sort=-version:refname`.split.grep(/^v?[0-9]/).first + since_date = `git log -1 --format=%as #{last_tag}`.strip + + # Collect merged PRs since that date via the GitHub CLI + raw = `gh pr list --state merged --base main --search "merged:>#{since_date}" --json number,title,author,url --limit 200` + prs = JSON.parse(raw) + pr_entries = prs + .map { |pr| "* #{pr["title"]} by @#{pr.dig("author", "login")} in [##{pr["number"]}](#{pr["url"]})" } + .join("\n") + + news = File.read("NEWS.md") + today = Date.today.strftime("%B %-d, %Y") + + # Extract and remove the Unreleased section (if present) + unreleased = "" + if news =~ /^## Unreleased\n(.*?)(?=^## )/m + unreleased = $1.strip + news = news.sub(/^## Unreleased\n.*?(?=^## )/m, "") + end + + # Combine unreleased entries and merged PR entries + body = [unreleased, pr_entries].reject(&:empty?).join("\n") + new_section = "## #{version} (#{today})\n\n#{body}\n" + + news.sub!(/^(# News\n+)/, "\\1#{new_section}\n") + File.write("NEWS.md", news) + + puts "Updated NEWS.md for v#{version}" + end +end From 5d61d823583affbf4d5463a774527445cff791be Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 20:40:44 +0000 Subject: [PATCH 3/3] Add bundle install step before bundle exec rake in prepare-release workflow Agent-Logs-Url: https://github.com/thoughtbot/factory_bot/sessions/51e4ccb7-1eef-4798-b80e-f87f1f5f7850 Co-authored-by: neilvcarvalho <761956+neilvcarvalho@users.noreply.github.com> --- .github/workflows/prepare-release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 389a7f62e..22e92a405 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -36,6 +36,9 @@ jobs: NEW=$(ruby -e "require_relative 'lib/factory_bot/version'; puts FactoryBot::VERSION") echo "new=$NEW" >> "$GITHUB_OUTPUT" + - name: Install dependencies + run: bundle install + - name: Update NEWS.md env: GH_TOKEN: ${{ github.token }}