From db56bb9618b9bb1af66d5de518b8263ff9fbb2ea Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Mon, 30 Mar 2026 14:17:58 +0200 Subject: [PATCH 1/4] Add `next_version` parameter to release notes update actions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `android_update_release_notes` and `ios_update_release_notes` actions used a built-in calculator that assumes minor version caps at 9 (WordPress/ Jetpack convention). This caused apps using semantic versioning (e.g. Pocket Casts) to get incorrect version bumps (8.9 → 9.0 instead of 8.10). Add an optional `next_version` parameter so callers can provide the correct next version directly. Deprecate the `new_version` parameter in favor of it. Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 2 +- .../android/android_update_release_notes.rb | 13 +++- .../actions/ios/ios_update_release_notes.rb | 13 +++- spec/android_update_release_notes_spec.rb | 62 +++++++++++++++++++ spec/ios_update_release_notes_spec.rb | 62 +++++++++++++++++++ 5 files changed, 149 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c567330f8..17e475371 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ _None_ ### Bug Fixes -_None_ +- Added optional `next_version` parameter to `android_update_release_notes` and `ios_update_release_notes` actions, allowing callers to provide the next version directly instead of relying on the built-in calculator that assumes minor version caps at 9. This fixes incorrect version bumps (e.g., 8.9 → 9.0 instead of 8.10) for apps using semantic versioning. [#xxx] ### Internal Changes diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_update_release_notes.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_update_release_notes.rb index 9b20e8762..a61536360 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_update_release_notes.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_update_release_notes.rb @@ -10,8 +10,10 @@ def self.run(params) require_relative '../../helper/release_notes_helper' require_relative '../../helper/git_helper' + UI.user_error!('You must provide either `next_version` or `new_version`') if params[:next_version].nil? && params[:new_version].nil? + path = params[:release_notes_file_path] - next_version = Fastlane::Helper::Android::VersionHelper.calc_next_release_short_version(params[:new_version]) + next_version = params[:next_version] || Fastlane::Helper::Android::VersionHelper.calc_next_release_short_version(params[:new_version]) Fastlane::Helper::ReleaseNotesHelper.add_new_section(path: path, section_title: next_version) Fastlane::Helper::GitHelper.commit(message: "Release Notes: add new section for next version (#{next_version})", files: path) @@ -36,6 +38,15 @@ def self.available_options FastlaneCore::ConfigItem.new(key: :new_version, env_name: 'FL_ANDROID_UPDATE_RELEASE_NOTES_VERSION', description: 'The version we are currently freezing; An empty entry for the _next_ version after this one will be added to the release notes', + deprecated: 'Use `next_version` instead. This parameter computes the next version using a built-in calculator ' \ + 'that assumes the minor version rolls over to the next major at 9, which is incorrect for apps using semantic versioning', + optional: true, + type: String), + FastlaneCore::ConfigItem.new(key: :next_version, + description: 'The next version to use as the section title in the release notes. ' \ + 'When provided, this value is used directly instead of computing it from `new_version`. ' \ + 'Use this if your app does not follow the default versioning convention (minor capped at 9)', + optional: true, type: String), FastlaneCore::ConfigItem.new(key: :release_notes_file_path, env_name: 'FL_ANDROID_UPDATE_RELEASE_NOTES_FILE_PATH', diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_update_release_notes.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_update_release_notes.rb index c64c95707..774b2a0e8 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_update_release_notes.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_update_release_notes.rb @@ -10,8 +10,10 @@ def self.run(params) require_relative '../../helper/release_notes_helper' require_relative '../../helper/git_helper' + UI.user_error!('You must provide either `next_version` or `new_version`') if params[:next_version].nil? && params[:new_version].nil? + path = params[:release_notes_file_path] - next_version = Fastlane::Helper::Ios::VersionHelper.calc_next_release_version(params[:new_version]) + next_version = params[:next_version] || Fastlane::Helper::Ios::VersionHelper.calc_next_release_version(params[:new_version]) Fastlane::Helper::ReleaseNotesHelper.add_new_section(path: path, section_title: next_version) Fastlane::Helper::GitHelper.commit(message: "Release Notes: add new section for next version (#{next_version})", files: path) @@ -36,6 +38,15 @@ def self.available_options FastlaneCore::ConfigItem.new(key: :new_version, env_name: 'FL_IOS_UPDATE_RELEASE_NOTES_VERSION', description: 'The version we are currently freezing; An empty entry for the _next_ version after this one will be added to the release notes', + deprecated: 'Use `next_version` instead. This parameter computes the next version using a built-in calculator ' \ + 'that assumes the minor version rolls over to the next major at 9, which is incorrect for apps using semantic versioning', + optional: true, + type: String), + FastlaneCore::ConfigItem.new(key: :next_version, + description: 'The next version to use as the section title in the release notes. ' \ + 'When provided, this value is used directly instead of computing it from `new_version`. ' \ + 'Use this if your app does not follow the default versioning convention (minor capped at 9)', + optional: true, type: String), FastlaneCore::ConfigItem.new(key: :release_notes_file_path, env_name: 'FL_IOS_UPDATE_RELEASE_NOTES_FILE_PATH', diff --git a/spec/android_update_release_notes_spec.rb b/spec/android_update_release_notes_spec.rb index 7967c181f..a907f240e 100644 --- a/spec/android_update_release_notes_spec.rb +++ b/spec/android_update_release_notes_spec.rb @@ -61,5 +61,67 @@ expect(File.read(changelog_md)).to eq(new_section + content) end end + + it 'uses next_version directly when provided' do + in_tmp_dir do |tmp_dir| + # Arrange + release_notes_txt = File.join(tmp_dir, 'RELEASE-NOTES.txt') + File.write(release_notes_txt, content) + + expected_section = <<~CONTENT + 8.10 + ----- + + + CONTENT + + # Act + run_described_fastlane_action( + next_version: '8.10' + ) + + # Assert + expect(File.read(release_notes_txt)).to eq(expected_section + content) + end + end + + it 'prefers next_version over new_version when both are provided' do + in_tmp_dir do |tmp_dir| + # Arrange + release_notes_txt = File.join(tmp_dir, 'RELEASE-NOTES.txt') + File.write(release_notes_txt, content) + + expected_section = <<~CONTENT + 8.10 + ----- + + + CONTENT + + # Act + run_described_fastlane_action( + new_version: '8.9', + next_version: '8.10' + ) + + # Assert — next_version wins, not the computed 9.0 from new_version + expect(File.read(release_notes_txt)).to eq(expected_section + content) + end + end + + it 'raises an error when neither new_version nor next_version is provided' do + in_tmp_dir do |tmp_dir| + # Arrange + release_notes_txt = File.join(tmp_dir, 'RELEASE-NOTES.txt') + File.write(release_notes_txt, content) + + # Act & Assert + expect do + run_described_fastlane_action( + release_notes_file_path: release_notes_txt + ) + end.to raise_error(FastlaneCore::Interface::FastlaneError, 'You must provide either `next_version` or `new_version`') + end + end end end diff --git a/spec/ios_update_release_notes_spec.rb b/spec/ios_update_release_notes_spec.rb index 6c5734f0c..f76188e8f 100644 --- a/spec/ios_update_release_notes_spec.rb +++ b/spec/ios_update_release_notes_spec.rb @@ -61,5 +61,67 @@ expect(File.read(changelog_md)).to eq(new_section + content) end end + + it 'uses next_version directly when provided' do + in_tmp_dir do |tmp_dir| + # Arrange + release_notes_txt = File.join(tmp_dir, 'RELEASE-NOTES.txt') + File.write(release_notes_txt, content) + + expected_section = <<~CONTENT + 8.10 + ----- + + + CONTENT + + # Act + run_described_fastlane_action( + next_version: '8.10' + ) + + # Assert + expect(File.read(release_notes_txt)).to eq(expected_section + content) + end + end + + it 'prefers next_version over new_version when both are provided' do + in_tmp_dir do |tmp_dir| + # Arrange + release_notes_txt = File.join(tmp_dir, 'RELEASE-NOTES.txt') + File.write(release_notes_txt, content) + + expected_section = <<~CONTENT + 8.10 + ----- + + + CONTENT + + # Act + run_described_fastlane_action( + new_version: '8.9', + next_version: '8.10' + ) + + # Assert — next_version wins, not the computed 9.0 from new_version + expect(File.read(release_notes_txt)).to eq(expected_section + content) + end + end + + it 'raises an error when neither new_version nor next_version is provided' do + in_tmp_dir do |tmp_dir| + # Arrange + release_notes_txt = File.join(tmp_dir, 'RELEASE-NOTES.txt') + File.write(release_notes_txt, content) + + # Act & Assert + expect do + run_described_fastlane_action( + release_notes_file_path: release_notes_txt + ) + end.to raise_error(FastlaneCore::Interface::FastlaneError, 'You must provide either `next_version` or `new_version`') + end + end end end From 7db5dd4407cfd7a3943c51e749e138fd6687be5f Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Mon, 30 Mar 2026 14:20:26 +0200 Subject: [PATCH 2/4] Update CHANGELOG with PR number Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17e475371..b0c8f3e67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ _None_ ### Bug Fixes -- Added optional `next_version` parameter to `android_update_release_notes` and `ios_update_release_notes` actions, allowing callers to provide the next version directly instead of relying on the built-in calculator that assumes minor version caps at 9. This fixes incorrect version bumps (e.g., 8.9 → 9.0 instead of 8.10) for apps using semantic versioning. [#xxx] +- Added optional `next_version` parameter to `android_update_release_notes` and `ios_update_release_notes` actions, allowing callers to provide the next version directly instead of relying on the built-in calculator that assumes minor version caps at 9. This fixes incorrect version bumps (e.g., 8.9 → 9.0 instead of 8.10) for apps using semantic versioning. [#706] ### Internal Changes From 965a0373d1661dbceced148b63487d15f36eaa28 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Mon, 30 Mar 2026 14:57:25 +0200 Subject: [PATCH 3/4] Validate that version parameters are non-empty strings Co-Authored-By: Claude Opus 4.6 (1M context) --- .../android/android_update_release_notes.rb | 5 +++-- .../actions/ios/ios_update_release_notes.rb | 5 +++-- spec/android_update_release_notes_spec.rb | 18 +++++++++++++++++- spec/ios_update_release_notes_spec.rb | 18 +++++++++++++++++- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_update_release_notes.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_update_release_notes.rb index a61536360..b66e151f4 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_update_release_notes.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_update_release_notes.rb @@ -10,10 +10,11 @@ def self.run(params) require_relative '../../helper/release_notes_helper' require_relative '../../helper/git_helper' - UI.user_error!('You must provide either `next_version` or `new_version`') if params[:next_version].nil? && params[:new_version].nil? + UI.user_error!('You must provide a non-empty value for either `next_version` or `new_version`') if params[:next_version].to_s.strip.empty? && params[:new_version].to_s.strip.empty? path = params[:release_notes_file_path] - next_version = params[:next_version] || Fastlane::Helper::Android::VersionHelper.calc_next_release_short_version(params[:new_version]) + next_version = params[:next_version]&.strip + next_version = Fastlane::Helper::Android::VersionHelper.calc_next_release_short_version(params[:new_version]) if next_version.nil? || next_version.empty? Fastlane::Helper::ReleaseNotesHelper.add_new_section(path: path, section_title: next_version) Fastlane::Helper::GitHelper.commit(message: "Release Notes: add new section for next version (#{next_version})", files: path) diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_update_release_notes.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_update_release_notes.rb index 774b2a0e8..b6879b89c 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_update_release_notes.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_update_release_notes.rb @@ -10,10 +10,11 @@ def self.run(params) require_relative '../../helper/release_notes_helper' require_relative '../../helper/git_helper' - UI.user_error!('You must provide either `next_version` or `new_version`') if params[:next_version].nil? && params[:new_version].nil? + UI.user_error!('You must provide a non-empty value for either `next_version` or `new_version`') if params[:next_version].to_s.strip.empty? && params[:new_version].to_s.strip.empty? path = params[:release_notes_file_path] - next_version = params[:next_version] || Fastlane::Helper::Ios::VersionHelper.calc_next_release_version(params[:new_version]) + next_version = params[:next_version]&.strip + next_version = Fastlane::Helper::Ios::VersionHelper.calc_next_release_version(params[:new_version]) if next_version.nil? || next_version.empty? Fastlane::Helper::ReleaseNotesHelper.add_new_section(path: path, section_title: next_version) Fastlane::Helper::GitHelper.commit(message: "Release Notes: add new section for next version (#{next_version})", files: path) diff --git a/spec/android_update_release_notes_spec.rb b/spec/android_update_release_notes_spec.rb index a907f240e..66f9e3666 100644 --- a/spec/android_update_release_notes_spec.rb +++ b/spec/android_update_release_notes_spec.rb @@ -120,7 +120,23 @@ run_described_fastlane_action( release_notes_file_path: release_notes_txt ) - end.to raise_error(FastlaneCore::Interface::FastlaneError, 'You must provide either `next_version` or `new_version`') + end.to raise_error(FastlaneCore::Interface::FastlaneError, 'You must provide a non-empty value for either `next_version` or `new_version`') + end + end + + it 'raises an error when next_version is an empty string' do + in_tmp_dir do |tmp_dir| + # Arrange + release_notes_txt = File.join(tmp_dir, 'RELEASE-NOTES.txt') + File.write(release_notes_txt, content) + + # Act & Assert + expect do + run_described_fastlane_action( + next_version: '', + release_notes_file_path: release_notes_txt + ) + end.to raise_error(FastlaneCore::Interface::FastlaneError, 'You must provide a non-empty value for either `next_version` or `new_version`') end end end diff --git a/spec/ios_update_release_notes_spec.rb b/spec/ios_update_release_notes_spec.rb index f76188e8f..ba617e9b9 100644 --- a/spec/ios_update_release_notes_spec.rb +++ b/spec/ios_update_release_notes_spec.rb @@ -120,7 +120,23 @@ run_described_fastlane_action( release_notes_file_path: release_notes_txt ) - end.to raise_error(FastlaneCore::Interface::FastlaneError, 'You must provide either `next_version` or `new_version`') + end.to raise_error(FastlaneCore::Interface::FastlaneError, 'You must provide a non-empty value for either `next_version` or `new_version`') + end + end + + it 'raises an error when next_version is an empty string' do + in_tmp_dir do |tmp_dir| + # Arrange + release_notes_txt = File.join(tmp_dir, 'RELEASE-NOTES.txt') + File.write(release_notes_txt, content) + + # Act & Assert + expect do + run_described_fastlane_action( + next_version: '', + release_notes_file_path: release_notes_txt + ) + end.to raise_error(FastlaneCore::Interface::FastlaneError, 'You must provide a non-empty value for either `next_version` or `new_version`') end end end From 169bb36b3b04cd882c6c4f97a0c71163c90ef5a8 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Mon, 30 Mar 2026 19:18:58 +0200 Subject: [PATCH 4/4] Add conflicting_options and clarify deprecation wording --- .../android/android_update_release_notes.rb | 7 ++++-- .../actions/ios/ios_update_release_notes.rb | 7 ++++-- spec/android_update_release_notes_spec.rb | 24 +++++++------------ spec/ios_update_release_notes_spec.rb | 24 +++++++------------ 4 files changed, 26 insertions(+), 36 deletions(-) diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_update_release_notes.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_update_release_notes.rb index b66e151f4..177736242 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_update_release_notes.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_update_release_notes.rb @@ -39,15 +39,18 @@ def self.available_options FastlaneCore::ConfigItem.new(key: :new_version, env_name: 'FL_ANDROID_UPDATE_RELEASE_NOTES_VERSION', description: 'The version we are currently freezing; An empty entry for the _next_ version after this one will be added to the release notes', - deprecated: 'Use `next_version` instead. This parameter computes the next version using a built-in calculator ' \ + deprecated: 'Deprecated in favor of `next_version`. ' \ + 'The `new_version` parameter computes the next version using a built-in calculator ' \ 'that assumes the minor version rolls over to the next major at 9, which is incorrect for apps using semantic versioning', optional: true, + conflicting_options: [:next_version], type: String), FastlaneCore::ConfigItem.new(key: :next_version, description: 'The next version to use as the section title in the release notes. ' \ - 'When provided, this value is used directly instead of computing it from `new_version`. ' \ + 'This value is used directly as the section title. ' \ 'Use this if your app does not follow the default versioning convention (minor capped at 9)', optional: true, + conflicting_options: [:new_version], type: String), FastlaneCore::ConfigItem.new(key: :release_notes_file_path, env_name: 'FL_ANDROID_UPDATE_RELEASE_NOTES_FILE_PATH', diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_update_release_notes.rb b/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_update_release_notes.rb index b6879b89c..670175b66 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_update_release_notes.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_update_release_notes.rb @@ -39,15 +39,18 @@ def self.available_options FastlaneCore::ConfigItem.new(key: :new_version, env_name: 'FL_IOS_UPDATE_RELEASE_NOTES_VERSION', description: 'The version we are currently freezing; An empty entry for the _next_ version after this one will be added to the release notes', - deprecated: 'Use `next_version` instead. This parameter computes the next version using a built-in calculator ' \ + deprecated: 'Deprecated in favor of `next_version`. ' \ + 'The `new_version` parameter computes the next version using a built-in calculator ' \ 'that assumes the minor version rolls over to the next major at 9, which is incorrect for apps using semantic versioning', optional: true, + conflicting_options: [:next_version], type: String), FastlaneCore::ConfigItem.new(key: :next_version, description: 'The next version to use as the section title in the release notes. ' \ - 'When provided, this value is used directly instead of computing it from `new_version`. ' \ + 'This value is used directly as the section title. ' \ 'Use this if your app does not follow the default versioning convention (minor capped at 9)', optional: true, + conflicting_options: [:new_version], type: String), FastlaneCore::ConfigItem.new(key: :release_notes_file_path, env_name: 'FL_IOS_UPDATE_RELEASE_NOTES_FILE_PATH', diff --git a/spec/android_update_release_notes_spec.rb b/spec/android_update_release_notes_spec.rb index 66f9e3666..da02e64e8 100644 --- a/spec/android_update_release_notes_spec.rb +++ b/spec/android_update_release_notes_spec.rb @@ -85,27 +85,19 @@ end end - it 'prefers next_version over new_version when both are provided' do + it 'raises an error when both new_version and next_version are provided' do in_tmp_dir do |tmp_dir| # Arrange release_notes_txt = File.join(tmp_dir, 'RELEASE-NOTES.txt') File.write(release_notes_txt, content) - expected_section = <<~CONTENT - 8.10 - ----- - - - CONTENT - - # Act - run_described_fastlane_action( - new_version: '8.9', - next_version: '8.10' - ) - - # Assert — next_version wins, not the computed 9.0 from new_version - expect(File.read(release_notes_txt)).to eq(expected_section + content) + # Act & Assert + expect do + run_described_fastlane_action( + new_version: '8.9', + next_version: '8.10' + ) + end.to raise_error(FastlaneCore::Interface::FastlaneError, /Unresolved conflict between options/) end end diff --git a/spec/ios_update_release_notes_spec.rb b/spec/ios_update_release_notes_spec.rb index ba617e9b9..1e7a70608 100644 --- a/spec/ios_update_release_notes_spec.rb +++ b/spec/ios_update_release_notes_spec.rb @@ -85,27 +85,19 @@ end end - it 'prefers next_version over new_version when both are provided' do + it 'raises an error when both new_version and next_version are provided' do in_tmp_dir do |tmp_dir| # Arrange release_notes_txt = File.join(tmp_dir, 'RELEASE-NOTES.txt') File.write(release_notes_txt, content) - expected_section = <<~CONTENT - 8.10 - ----- - - - CONTENT - - # Act - run_described_fastlane_action( - new_version: '8.9', - next_version: '8.10' - ) - - # Assert — next_version wins, not the computed 9.0 from new_version - expect(File.read(release_notes_txt)).to eq(expected_section + content) + # Act & Assert + expect do + run_described_fastlane_action( + new_version: '8.9', + next_version: '8.10' + ) + end.to raise_error(FastlaneCore::Interface::FastlaneError, /Unresolved conflict between options/) end end