diff --git a/CHANGELOG.md b/CHANGELOG.md index 16a11e574..981c09407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ _None_ ### New Features -_None_ +- Add the possibility to configure in DerivedBuildCodeFormatter a versioning prefix instead of always defaulting to 1. [#656] ### Bug Fixes diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/versioning/formatters/derived_build_code_formatter.rb b/lib/fastlane/plugin/wpmreleasetoolkit/versioning/formatters/derived_build_code_formatter.rb index 2808016bf..4ddc536ce 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/versioning/formatters/derived_build_code_formatter.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/versioning/formatters/derived_build_code_formatter.rb @@ -6,9 +6,19 @@ module Versioning # The `DerivedBuildCodeFormatter` class is a specialized build code formatter for derived build codes. # It takes in an AppVersion object and derives a build code from it. class DerivedBuildCodeFormatter + # Initialize the formatter with a configurable prefix. + # + # @param [String] prefix The prefix to use for the build code. Must be a single digit (0-9), or empty string / nil. + # + def initialize(prefix: nil) + prefix ||= '' + validate_prefix!(prefix) + @prefix = prefix.to_s + end + # Calculate the next derived build code. # - # This method derives a new build code from the given AppVersion object by concatenating the digit 1, + # This method derives a new build code from the given AppVersion object by concatenating the configured prefix, # the major version, the minor version, the patch version, and the build number. # # @param [AppVersion] version The AppVersion object to derive the next build code from. @@ -19,15 +29,43 @@ class DerivedBuildCodeFormatter # @return [String] The formatted build code string. # def build_code(build_code = nil, version:) - format( - # 1 is appended to the beginning of the string in case there needs to be additional platforms or - # extensions that could then use a different digit prefix such as 2, etc. - '1%.2i%.2i%.2i%.2i', + result = format( + # The prefix is configurable to allow for additional platforms or + # extensions that could use a different digit prefix such as 2, etc. + '%s%.2i%.2i%.2i%.2i', + prefix: @prefix, major: version.major, minor: version.minor, patch: version.patch, build_number: version.build_number ) + + result.gsub(/^0+/, '') + end + + private + + # Validates that the prefix is a valid single digit (0-9) or empty string. + # + # @param [String] prefix The prefix to validate + # + # @raise [StandardError] If the prefix is invalid + # + def validate_prefix!(prefix) + prefix_str = prefix.to_s + + # Allow empty string + return if prefix_str.empty? + + # Check if it's longer than 1 character + if prefix_str.length > 1 + UI.user_error!("Prefix must be a single digit or empty string, got: '#{prefix_str}' (length: #{prefix_str.length})") + end + + # Check if it's a valid integer + return if ('0'..'9').include?(prefix_str) + + UI.user_error!("Prefix must be an integer digit (0-9) or empty string, got: '#{prefix_str}'") end end end diff --git a/spec/derived_build_code_formatter_spec.rb b/spec/derived_build_code_formatter_spec.rb index 6eff0c5b5..c49f1b5e1 100644 --- a/spec/derived_build_code_formatter_spec.rb +++ b/spec/derived_build_code_formatter_spec.rb @@ -4,16 +4,118 @@ describe Fastlane::Wpmreleasetoolkit::Versioning::DerivedBuildCodeFormatter do describe 'derives a build code from an AppVersion object' do - it 'derives the build code from version numbers that are single digits' do - version = Fastlane::Models::AppVersion.new(1, 2, 3, 4) - build_code_string = described_class.new.build_code(version: version) - expect(build_code_string.to_s).to eq('101020304') + context 'with default prefix (nil)' do + it 'derives the build code from version numbers that are single digits' do + version = Fastlane::Models::AppVersion.new(1, 2, 3, 4) + build_code_string = described_class.new.build_code(version: version) + expect(build_code_string.to_s).to eq('1020304') + end + + it 'derives the build code from version numbers that are two digits' do + version = Fastlane::Models::AppVersion.new(12, 34, 56, 78) + build_code_string = described_class.new.build_code(version: version) + expect(build_code_string.to_s).to eq('12345678') + end + end + + context 'with explicit prefix' do + it 'derives the build code with prefix "1"' do + version = Fastlane::Models::AppVersion.new(1, 2, 3, 4) + formatter = described_class.new(prefix: '1') + build_code_string = formatter.build_code(version: version) + expect(build_code_string.to_s).to eq('101020304') + end + + it 'derives the build code with prefix "2"' do + version = Fastlane::Models::AppVersion.new(1, 2, 3, 4) + formatter = described_class.new(prefix: '2') + build_code_string = formatter.build_code(version: version) + expect(build_code_string.to_s).to eq('201020304') + end + + it 'derives the build code with prefix "0"' do + version = Fastlane::Models::AppVersion.new(12, 34, 56, 78) + formatter = described_class.new(prefix: '0') + build_code_string = formatter.build_code(version: version) + expect(build_code_string.to_s).to eq('12345678') + end + end + + context 'with empty prefix' do + it 'derives the build code without prefix and trims leading zeros' do + version = Fastlane::Models::AppVersion.new(1, 2, 3, 4) + formatter = described_class.new(prefix: '') + build_code_string = formatter.build_code(version: version) + expect(build_code_string.to_s).to eq('1020304') + end + + it 'derives the build code without prefix for two-digit major version' do + version = Fastlane::Models::AppVersion.new(12, 34, 56, 78) + formatter = described_class.new(prefix: '') + build_code_string = formatter.build_code(version: version) + expect(build_code_string.to_s).to eq('12345678') + end + + it 'handles edge case with all zeros after removing leading zeros' do + version = Fastlane::Models::AppVersion.new(0, 0, 0, 1) + formatter = described_class.new(prefix: '') + build_code_string = formatter.build_code(version: version) + expect(build_code_string.to_s).to eq('1') + end + end + + context 'with nil prefix' do + it 'treats nil prefix as empty string and derives build code without prefix' do + version = Fastlane::Models::AppVersion.new(1, 2, 3, 4) + formatter = described_class.new(prefix: nil) + build_code_string = formatter.build_code(version: version) + expect(build_code_string.to_s).to eq('1020304') + end + + it 'treats nil prefix as empty string for two-digit major version' do + version = Fastlane::Models::AppVersion.new(12, 34, 56, 78) + formatter = described_class.new(prefix: nil) + build_code_string = formatter.build_code(version: version) + expect(build_code_string.to_s).to eq('12345678') + end + end + end + + describe 'prefix validation' do + context 'with valid prefixes' do + it 'accepts single digits 0-9' do + (0..9).each do |digit| + expect { described_class.new(prefix: digit.to_s) }.not_to raise_error + end + end + + it 'accepts empty string' do + expect { described_class.new(prefix: '') }.not_to raise_error + end + + it 'accepts integer inputs' do + expect { described_class.new(prefix: 5) }.not_to raise_error + end end - it 'derives the build code from version numbers that are two digits' do - version = Fastlane::Models::AppVersion.new(12, 34, 56, 78) - build_code_string = described_class.new.build_code(version: version) - expect(build_code_string.to_s).to eq('112345678') + context 'with invalid prefixes' do + it 'rejects multi-character strings' do + expect { described_class.new(prefix: '12') }.to raise_error(/Prefix must be a single digit or empty string/) + end + + it 'rejects non-numeric strings' do + expect { described_class.new(prefix: 'a') }.to raise_error(/Prefix must be an integer digit/) + end + + it 'rejects numbers outside 0-9 range' do + expect { described_class.new(prefix: '10') }.to raise_error(/Prefix must be a single digit or empty string/) + expect { described_class.new(prefix: '-1') }.to raise_error(/Prefix must be a single digit or empty string/) + end + + it 'rejects symbols and special characters' do + expect { described_class.new(prefix: '@') }.to raise_error(/Prefix must be an integer digit/) + expect { described_class.new(prefix: '#') }.to raise_error(/Prefix must be an integer digit/) + end end end end