From 6e12ef5ed5d5cda6efc153fbe70962f3543eb5f7 Mon Sep 17 00:00:00 2001 From: Olivier Halligon Date: Thu, 30 Jul 2026 22:29:15 +0200 Subject: [PATCH 1/6] Publish the most recent GitHub Release when several share a name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `publish_release` looked up the release to publish with `releases.find { |r| r.name == name }`, which returns whichever match the GitHub API happened to list first. That ordering is not part of the API contract, and a repository can legitimately host several releases with the same name: each `finalize_release` run creates its own draft, so re-running it for a version — e.g. after a crash is found during smoke-testing — leaves two drafts named `25.1` behind, targeting different commits. That is what caused the WCiOS 25.1 incident: `publish_release` published the draft from the *first* run, so GitHub created the `25.1` tag on the older commit, and the 25.1.1 hotfix branched off that tag and silently dropped the crash fix that had actually shipped. Both matching releases are now collected and the most recently created one is published, with the staler ones left untouched. `find_release`, which looks a release up by tag rather than by name, shares the same new `matching_releases` helper and so gains the same guarantee. The sort key is the release `id` rather than `created_at`: GitHub rewrites `created_at` to the date of the commit a release targets once that release is published, so it is not a reliable creation timestamp — a published release can end up looking older than a draft created before it. Release `id`s are assigned in increasing order as releases are created, drafts included. Two non-fatal warnings are also emitted, to make the situation visible in the CI logs: one when more than one release matches the name, and one when the release being published turns out not to be a draft anymore. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 2 +- .../common/publish_github_release_action.rb | 8 +- .../wpmreleasetoolkit/helper/github_helper.rb | 34 +++- spec/github_helper_spec.rb | 148 ++++++++++++++++++ 4 files changed, 186 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a44445e4..97d683e9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ _None_ ### Bug Fixes -_None_ +- `publish_github_release` now publishes the most recently created GitHub Release when several share the same name—as happens when `finalize_release` is run more than once for the same version, each run creating its own draft—instead of whichever one the GitHub API happened to list first. It also warns when it finds more than one match, or when the release it publishes turns out to have been published already. The tag-based release lookup used by `upload_github_release_assets` picks the most recent match too. [#763] ### Internal Changes diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/publish_github_release_action.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/publish_github_release_action.rb index 1d046d415..6ff26624b 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/publish_github_release_action.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/publish_github_release_action.rb @@ -35,7 +35,13 @@ def self.return_value end def self.details - 'Publish an existing GitHub Release still in draft mode' + <<~DETAILS + Publish an existing GitHub Release still in draft mode. + + If several GitHub Releases share the same `name`—which happens when `finalize_release` is run more than + once for the same version, each run creating its own draft—the most recently created one is published, + as it is the one targeting the latest commit of the release branch. + DETAILS end def self.available_options diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb b/lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb index da4160295..01e401090 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb @@ -244,8 +244,26 @@ def upload_release_assets(repository:, version:, assets:, replace_existing: true release.html_url end + # Returns all the GitHub Releases of a repository matching a given criteria, sorted from the oldest to the most recent one. + # + # @param [String] repository The repository to fetch the GitHub Releases from. Typically a repo slug (/). + # @yield [Sawyer::Resource] Each GitHub Release of the repository, to decide whether it matches the criteria. + # @yieldreturn [TrueClass|FalseClass] `true` to include that GitHub Release in the returned list. + # @return [Array] The matching GitHub Releases, sorted from the oldest to the most recently created one. + # + # @note A repository can legitimately host several GitHub Releases sharing the same name or tag—e.g. when `finalize_release` + # is run more than once for the same version, creating one draft per run—and the GitHub API makes no promise about the + # order in which it returns them. Callers must thus pick explicitly amongst the matches instead of relying on that order. + # @note The sort key is the release `id`, not `created_at`: once a GitHub Release is published, GitHub rewrites its `created_at` + # to the date of the commit it targets, so `created_at` is not a reliable creation timestamp. Release `id`s, on the other + # hand, are assigned by GitHub in increasing order as releases are created, including for drafts. + # + def matching_releases(repository:, &matcher) + client.releases(repository).select(&matcher).sort_by { |release| release[:id] } + end + def find_release(repository:, version:) - release = client.releases(repository).find { |candidate| candidate.tag_name == version } + release = matching_releases(repository: repository) { |candidate| candidate.tag_name == version }.last return release unless release.nil? release_for_tag(repository: repository, version: version) @@ -259,6 +277,7 @@ def release_for_tag(repository:, version:) nil end + private :matching_releases private :find_release private :release_for_tag @@ -306,12 +325,19 @@ def get_release_url(repository:, tag_name:) # @param [Boolean] prerelease Indicates if this should be created as a pre-release (i.e. for alpha/beta) # # @return [String] URL of the corresponding GitHub Release + # @raise [Fastlane::UI::Error] UI.user_error! if no GitHub Release with that name exists. + # + # @note If several GitHub Releases share that same `name`—which happens when `finalize_release` is run more than once + # for the same version, each run creating its own draft—the most recently created one is the one being published, + # as it is the one targeting the latest commit of the release branch. The other, staler ones are left untouched. # def publish_release(repository:, name:, prerelease: nil) - releases = client.releases(repository) - release = releases.find { |r| r.name == name } + releases = matching_releases(repository: repository) { |release| release.name == name } + UI.user_error!("No release found with name #{name}") if releases.empty? - UI.user_error!("No release found with name #{name}") unless release + release = releases.last + UI.important("Found #{releases.count} GitHub Releases named `#{name}`. Publishing the most recently created one, targeting #{release.target_commitish}, and leaving the #{releases.count - 1} older one(s) untouched.") if releases.count > 1 + UI.important("The most recent GitHub Release named `#{name}` (#{release.html_url}) is not a draft, but has already been published. Updating it nonetheless.") unless release.draft client.update_release( release.url, diff --git a/spec/github_helper_spec.rb b/spec/github_helper_spec.rb index 90e4e7ede..8ce57cbe0 100644 --- a/spec/github_helper_spec.rb +++ b/spec/github_helper_spec.rb @@ -733,6 +733,23 @@ def create_release(is_draft:, assets: [], name: nil) end end + it 'uses the most recently created release when several share the same tag' do + stale_release = sawyer_resource_stub(id: 1, url: 'stale-api-url', html_url: 'stale-html-url', tag_name: test_version, draft: true) + latest_release = sawyer_resource_stub(id: 2, url: 'latest-api-url', html_url: 'latest-html-url', tag_name: test_version, draft: true) + + allow(client).to receive(:releases).with(test_repo).and_return([latest_release, stale_release]) + expect(client).not_to receive(:release_for_tag) + allow(client).to receive(:release_assets).with(latest_release.url).and_return([]) + + with_tmp_file(named: 'test-app.zip') do |file_path| + expect(client).to receive(:upload_asset).with(latest_release.url, file_path, { content_type: 'application/octet-stream' }) + + result = upload_release_assets(assets: [file_path]) + + expect(result).to eq(latest_release.html_url) + end + end + it 'falls back to the direct release-by-tag lookup when the release list misses' do with_tmp_file(named: 'test-app.zip') do |file_path| allow(client).to receive(:releases).with(test_repo).and_return([]) @@ -848,6 +865,137 @@ def release_asset(name:, url:) end end + describe '#publish_release' do + let(:test_repo) { 'repo-test/project-test' } + let(:test_name) { '25.1' } + let(:client) do + instance_double( + Octokit::Client, + user: instance_double('User', name: 'test'), + 'auto_paginate=': nil + ) + end + let(:helper) { described_class.new(github_token: 'Fake-GitHubToken-123') } + + # Those two mirror the two GitHub Releases both named `25.1` that caused the WCiOS 25.1 incident: one draft per + # `finalize_release` run, the older one targeting the commit of the first run, the newer one that of the second run. + let(:stale_release) { github_release(id: 351_929_162, target_commitish: 'dbd800a', created_at: '2026-07-10T06:18:29Z') } + let(:latest_release) { github_release(id: 352_002_205, target_commitish: '558354d', created_at: '2026-07-10T09:27:00Z') } + + before do + allow(Octokit::Client).to receive(:new).and_return(client) + allow(client).to receive(:update_release) + end + + it 'fails clearly if no release matches the name' do + allow(client).to receive(:releases).with(test_repo).and_return([github_release(id: 1, name: '25.2')]) + + expect(client).not_to receive(:update_release) + expect { publish_release }.to raise_error(FastlaneCore::Interface::FastlaneError, "No release found with name #{test_name}") + end + + it 'publishes the release matching the name, ignoring the ones named differently' do + allow(client).to receive(:releases).with(test_repo).and_return([github_release(id: 1, name: '25.2'), latest_release]) + + expect(client).to receive(:update_release).with(latest_release.url, { draft: false }) + + expect(publish_release).to eq(latest_release.html_url) + end + + it 'publishes the most recently created release when several share the same name' do + allow(client).to receive(:releases).with(test_repo).and_return([stale_release, latest_release]) + + expect(client).to receive(:update_release).with(latest_release.url, { draft: false }) + + expect(publish_release).to eq(latest_release.html_url) + end + + it 'publishes the most recently created release regardless of the order the API lists them in' do + allow(client).to receive(:releases).with(test_repo).and_return([latest_release, stale_release]) + + expect(client).to receive(:update_release).with(latest_release.url, { draft: false }) + + expect(publish_release).to eq(latest_release.html_url) + end + + it 'does not rely on `created_at`, which GitHub rewrites to the target commit date on publish' do + # A published release reports the date of the commit it targets as its `created_at`, which can make it look more + # recent than a draft created after it. Only the release `id` reflects the order in which releases were created. + published_release = github_release(id: 351_929_162, draft: false, created_at: '2026-07-10T23:00:00Z') + newer_draft = github_release(id: 352_002_205, created_at: '2026-07-10T09:27:00Z') + allow(client).to receive(:releases).with(test_repo).and_return([published_release, newer_draft]) + + expect(client).to receive(:update_release).with(newer_draft.url, { draft: false }) + + expect(publish_release).to eq(newer_draft.html_url) + end + + it 'warns when several releases share the same name' do + allow(client).to receive(:releases).with(test_repo).and_return([stale_release, latest_release]) + + expect(Fastlane::UI).to receive(:important).with(/Found 2 GitHub Releases named `25\.1`.*558354d.*1 older one/) + + publish_release + end + + it 'warns when the release it publishes has already been published' do + allow(client).to receive(:releases).with(test_repo).and_return([github_release(id: 1, draft: false)]) + + expect(Fastlane::UI).to receive(:important).with(/is not a draft, but has already been published/) + + publish_release + end + + it 'does not warn when publishing a single draft release' do + allow(client).to receive(:releases).with(test_repo).and_return([latest_release]) + + expect(Fastlane::UI).not_to receive(:important) + + publish_release + end + + it 'keeps the prerelease status of the draft when no prerelease value is provided' do + allow(client).to receive(:releases).with(test_repo).and_return([latest_release]) + + expect(client).to receive(:update_release).with(latest_release.url, { draft: false }) + + publish_release + end + + it 'publishes as a prerelease when requested' do + allow(client).to receive(:releases).with(test_repo).and_return([latest_release]) + + expect(client).to receive(:update_release).with(latest_release.url, { draft: false, prerelease: true }) + + publish_release(prerelease: true) + end + + it 'publishes as a final release when prerelease is explicitly false' do + allow(client).to receive(:releases).with(test_repo).and_return([latest_release]) + + expect(client).to receive(:update_release).with(latest_release.url, { draft: false, prerelease: false }) + + publish_release(prerelease: false) + end + + def github_release(id:, name: test_name, draft: true, target_commitish: 'deadbeef', created_at: '2026-07-10T09:27:00Z') + sawyer_resource_stub( + id: id, + name: name, + draft: draft, + target_commitish: target_commitish, + created_at: created_at, + url: "https://api.github.com/repos/#{test_repo}/releases/#{id}", + # GitHub only assigns a tag-based URL to a release once it is published; drafts get an `untagged-*` one + html_url: draft ? "https://github.com/#{test_repo}/releases/tag/untagged-#{id}" : "https://github.com/#{test_repo}/releases/tag/#{name}" + ) + end + + def publish_release(prerelease: nil) + helper.publish_release(repository: test_repo, name: test_name, prerelease: prerelease) + end + end + describe '#github_token_config_item' do it 'has the correct key' do expect(described_class.github_token_config_item.key).to eq(:github_token) From 39d348e9ff4a277b137585637ddc19f34d13e9ce Mon Sep 17 00:00:00 2001 From: Olivier Halligon Date: Fri, 31 Jul 2026 19:45:32 +0200 Subject: [PATCH 2/6] Apply suggestions from code review Co-authored-by: Gio Lodi Co-authored-by: Olivier Halligon --- CHANGELOG.md | 2 +- .../actions/common/publish_github_release_action.rb | 10 ++++++---- .../plugin/wpmreleasetoolkit/helper/github_helper.rb | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97d683e9f..8aa20cc51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ _None_ ### Bug Fixes -- `publish_github_release` now publishes the most recently created GitHub Release when several share the same name—as happens when `finalize_release` is run more than once for the same version, each run creating its own draft—instead of whichever one the GitHub API happened to list first. It also warns when it finds more than one match, or when the release it publishes turns out to have been published already. The tag-based release lookup used by `upload_github_release_assets` picks the most recent match too. [#763] +- `publish_github_release` now publishes the most recently created GitHub Release when several share the same name, instead of whichever one the GitHub API happened to list first. It also warns when it finds more than one match, or when the release it publishes turns out to have been published already. The tag-based release lookup used by `upload_github_release_assets` picks the most recent match too. [#763] ### Internal Changes diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/publish_github_release_action.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/publish_github_release_action.rb index 6ff26624b..e35aed632 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/publish_github_release_action.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/publish_github_release_action.rb @@ -36,11 +36,13 @@ def self.return_value def self.details <<~DETAILS - Publish an existing GitHub Release still in draft mode. - - If several GitHub Releases share the same `name`—which happens when `finalize_release` is run more than - once for the same version, each run creating its own draft—the most recently created one is published, + Publish an existing draft GitHub Release. + + If several GitHub Releases share the same `name`, the most recently created one is published, as it is the one targeting the latest commit of the release branch. + + (Multiple releases can exist with the same `name` when the release finalization automation + is run more than once for the same version, each run creating its own draft.) DETAILS end diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb b/lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb index 01e401090..00378088d 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb @@ -337,7 +337,7 @@ def publish_release(repository:, name:, prerelease: nil) release = releases.last UI.important("Found #{releases.count} GitHub Releases named `#{name}`. Publishing the most recently created one, targeting #{release.target_commitish}, and leaving the #{releases.count - 1} older one(s) untouched.") if releases.count > 1 - UI.important("The most recent GitHub Release named `#{name}` (#{release.html_url}) is not a draft, but has already been published. Updating it nonetheless.") unless release.draft + UI.important("The most recent GitHub Release named `#{name}` (#{release.html_url}) has already been published. Updating it nonetheless.") unless release.draft client.update_release( release.url, From dd43d630ad3309d6bc81a3f4d717ce282c426d59 Mon Sep 17 00:00:00 2001 From: Olivier Halligon Date: Fri, 31 Jul 2026 21:22:59 +0200 Subject: [PATCH 3/6] Fix lint and spec fallout from the review suggestions The applied suggestions left trailing whitespace on two blank lines inside the `details` heredoc, which `Layout/TrailingWhitespace` rejects, and reworded the already-published warning, which the spec was still matching on its old phrasing. Co-Authored-By: Claude Opus 5 (1M context) --- .../actions/common/publish_github_release_action.rb | 4 ++-- spec/github_helper_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/publish_github_release_action.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/publish_github_release_action.rb index e35aed632..ee5e793cc 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/publish_github_release_action.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/publish_github_release_action.rb @@ -37,10 +37,10 @@ def self.return_value def self.details <<~DETAILS Publish an existing draft GitHub Release. - + If several GitHub Releases share the same `name`, the most recently created one is published, as it is the one targeting the latest commit of the release branch. - + #{' '} (Multiple releases can exist with the same `name` when the release finalization automation is run more than once for the same version, each run creating its own draft.) DETAILS diff --git a/spec/github_helper_spec.rb b/spec/github_helper_spec.rb index 8ce57cbe0..c75ac2648 100644 --- a/spec/github_helper_spec.rb +++ b/spec/github_helper_spec.rb @@ -941,7 +941,7 @@ def release_asset(name:, url:) it 'warns when the release it publishes has already been published' do allow(client).to receive(:releases).with(test_repo).and_return([github_release(id: 1, draft: false)]) - expect(Fastlane::UI).to receive(:important).with(/is not a draft, but has already been published/) + expect(Fastlane::UI).to receive(:important).with(/has already been published/) publish_release end From c9cacaae82494af28d4cb579f82d26998e2a9910 Mon Sep 17 00:00:00 2001 From: Olivier Halligon Date: Fri, 31 Jul 2026 21:29:50 +0200 Subject: [PATCH 4/6] Prefer the release owning the tag over a more recent draft MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Making the tag-based lookup deterministic by taking the highest id was the wrong tie-break for that question. A git tag can only ever back a single published release, so when one of the matches is not a draft it *is* the release for that tag; a more recent draft sharing the same `tag_name`—typically left behind by a re-run of the release finalization—is a leftover, not a successor. woocommerce-ios is in exactly that state after the 25.1 incident: published release 351929162 owns the `25.1` tag, and draft 352002205 still carries the same `tag_name` with a higher id. Under the previous tie-break, `upload_github_release_assets(version: '25.1')` resolved to the draft every time, so assets would land on a release nobody can see, `existing_assets` would be computed against the wrong release—silently defeating `replace_existing`—and the caller would get an `untagged-…` URL back. The lookup now prefers a published match and only falls back to the most recent draft when no published release claims the tag, which is the case when uploading assets to a release that has not been published yet. `matching_releases` stays a plain "filter, order by creation" primitive, with each caller stating its own tie-break: `publish_release` still wants the newest match whether draft or not. Reported by @mokagio in review. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 2 +- .../wpmreleasetoolkit/helper/github_helper.rb | 12 ++++++++++- spec/github_helper_spec.rb | 20 +++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8aa20cc51..c82d29e85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ _None_ ### Bug Fixes -- `publish_github_release` now publishes the most recently created GitHub Release when several share the same name, instead of whichever one the GitHub API happened to list first. It also warns when it finds more than one match, or when the release it publishes turns out to have been published already. The tag-based release lookup used by `upload_github_release_assets` picks the most recent match too. [#763] +- `publish_github_release` now publishes the most recently created GitHub Release when several share the same name, instead of whichever one the GitHub API happened to list first. It also warns when it finds more than one match, or when the release it publishes turns out to have been published already. The tag-based release lookup used by `upload_github_release_assets` is deterministic too, preferring the published release that owns the tag, and falling back to the most recently created draft when no published release claims it. [#763] ### Internal Changes diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb b/lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb index 00378088d..9402794db 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb @@ -262,8 +262,18 @@ def matching_releases(repository:, &matcher) client.releases(repository).select(&matcher).sort_by { |release| release[:id] } end + # Returns the GitHub Release associated with a given tag, if any, including draft ones. + # + # @note Unlike a lookup by name, "the most recently created match" is not the right answer here: a git tag can + # only ever back a single *published* release, so if one of the matches is not a draft then it is the + # release owning that tag, and a more recent draft sharing the same `tag_name` is a leftover—typically + # from a re-run of the release finalization—rather than a successor. Only when no published release + # claims the tag yet do we fall back to the most recent draft, which is the case when uploading assets + # to a release that has not been published yet. + # def find_release(repository:, version:) - release = matching_releases(repository: repository) { |candidate| candidate.tag_name == version }.last + matches = matching_releases(repository: repository) { |candidate| candidate.tag_name == version } + release = matches.reject { |candidate| candidate[:draft] }.last || matches.last return release unless release.nil? release_for_tag(repository: repository, version: version) diff --git a/spec/github_helper_spec.rb b/spec/github_helper_spec.rb index c75ac2648..e6f30b519 100644 --- a/spec/github_helper_spec.rb +++ b/spec/github_helper_spec.rb @@ -750,6 +750,26 @@ def create_release(is_draft:, assets: [], name: nil) end end + it 'uses the release owning the tag rather than a more recent draft sharing it' do + # The state woocommerce-ios is left in after the 25.1 incident: the release that was published and owns the + # `25.1` tag, plus the never-cleaned-up draft created by the second `finalize_release` run, which has a higher + # id. A tag can only back one published release, so the published one wins despite being the older of the two. + published_release = sawyer_resource_stub(id: 351_929_162, url: 'published-api-url', html_url: 'published-html-url', tag_name: test_version, draft: false) + leftover_draft = sawyer_resource_stub(id: 352_002_205, url: 'draft-api-url', html_url: 'draft-html-url', tag_name: test_version, draft: true) + + allow(client).to receive(:releases).with(test_repo).and_return([published_release, leftover_draft]) + allow(client).to receive(:release_assets).with(published_release.url).and_return([]) + + with_tmp_file(named: 'test-app.zip') do |file_path| + expect(client).to receive(:upload_asset).with(published_release.url, file_path, { content_type: 'application/octet-stream' }) + expect(client).not_to receive(:upload_asset).with(leftover_draft.url, any_args) + + result = upload_release_assets(assets: [file_path]) + + expect(result).to eq(published_release.html_url) + end + end + it 'falls back to the direct release-by-tag lookup when the release list misses' do with_tmp_file(named: 'test-app.zip') do |file_path| allow(client).to receive(:releases).with(test_repo).and_return([]) From 26e2cc147569faf950b123bd3bba8476c3364471 Mon Sep 17 00:00:00 2001 From: Olivier Halligon Date: Mon, 3 Aug 2026 08:09:37 +0200 Subject: [PATCH 5/6] Remove stray interpolation of space Co-authored-by: Gio Lodi --- .../actions/common/publish_github_release_action.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/publish_github_release_action.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/publish_github_release_action.rb index ee5e793cc..59b3236fc 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/publish_github_release_action.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/publish_github_release_action.rb @@ -40,7 +40,7 @@ def self.details If several GitHub Releases share the same `name`, the most recently created one is published, as it is the one targeting the latest commit of the release branch. - #{' '} + (Multiple releases can exist with the same `name` when the release finalization automation is run more than once for the same version, each run creating its own draft.) DETAILS From 3b81bb1b59752829560bcde7d281938056792e04 Mon Sep 17 00:00:00 2001 From: Olivier Halligon Date: Mon, 3 Aug 2026 08:19:42 +0200 Subject: [PATCH 6/6] Fix rubocop violation (trailing whitespace) --- .../actions/common/publish_github_release_action.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/publish_github_release_action.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/publish_github_release_action.rb index 59b3236fc..3f76bedc7 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/publish_github_release_action.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/publish_github_release_action.rb @@ -40,7 +40,6 @@ def self.details If several GitHub Releases share the same `name`, the most recently created one is published, as it is the one targeting the latest commit of the release branch. - (Multiple releases can exist with the same `name` when the release finalization automation is run more than once for the same version, each run creating its own draft.) DETAILS