Skip to content

Commit 6fa277e

Browse files
authored
Add version components digits configuration to DerivedBuildCodeFormatter (#657)
2 parents 6486869 + cef6996 commit 6fa277e

File tree

4 files changed

+283
-35
lines changed

4 files changed

+283
-35
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ _None_
1010

1111
### New Features
1212

13-
- Add the possibility to configure in DerivedBuildCodeFormatter a versioning prefix instead of always defaulting to 1. [#656]
13+
- Add the possibility to configure in `DerivedBuildCodeFormatter` a versioning prefix instead of always defaulting to 1. [#656]
14+
- Add configurable number of digits in version components in `DerivedBuildCodeFormatter`. [#657]
1415

1516
### Bug Fixes
1617

lib/fastlane/plugin/wpmreleasetoolkit/models/app_version.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ def initialize(major, minor, patch = 0, build_number = 0)
3333
def to_s
3434
"#{@major}.#{@minor}.#{@patch}.#{@build_number}"
3535
end
36+
37+
# Returns an array of the version components.
38+
#
39+
# @return [Array<Integer>] an array of the version components.
40+
#
41+
def components
42+
[@major, @minor, @patch, @build_number]
43+
end
3644
end
3745
end
3846
end

lib/fastlane/plugin/wpmreleasetoolkit/versioning/formatters/derived_build_code_formatter.rb

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,35 @@
33
module Fastlane
44
module Wpmreleasetoolkit
55
module Versioning
6+
# Max total for `*_digits` params, not counting prefix
7+
MAX_TOTAL_DIGITS = 8
8+
MIN_DIGIT_COUNT = 1
9+
MAX_DIGIT_COUNT = 3
10+
611
# The `DerivedBuildCodeFormatter` class is a specialized build code formatter for derived build codes.
712
# It takes in an AppVersion object and derives a build code from it.
813
class DerivedBuildCodeFormatter
9-
# Initialize the formatter with a configurable prefix.
14+
# Initialize the formatter with configurable prefix and digit counts.
1015
#
1116
# @param [String] prefix The prefix to use for the build code. Must be a single digit (0-9), or empty string / nil.
17+
# @param [Integer] major_digits Number of digits for major version. Must be between 1–3. Defaults to 2.
18+
# @param [Integer] minor_digits Number of digits for minor version. Must be between 1–3. Defaults to 2.
19+
# @param [Integer] patch_digits Number of digits for patch version. Must be between 1–3. Defaults to 2.
20+
# @param [Integer] build_digits Number of digits for build number. Must be between 1–3. Defaults to 2.
1221
#
13-
def initialize(prefix: nil)
14-
prefix ||= ''
22+
def initialize(prefix: nil, major_digits: 2, minor_digits: 2, patch_digits: 2, build_digits: 2)
1523
validate_prefix!(prefix)
1624
@prefix = prefix.to_s
25+
26+
@digit_counts = [major_digits, minor_digits, patch_digits, build_digits]
27+
@digit_counts.each { |d| validate_digit_count!(d) }
28+
validate_total_digits!(@digit_counts)
1729
end
1830

1931
# Calculate the next derived build code.
2032
#
2133
# This method derives a new build code from the given AppVersion object by concatenating the configured prefix,
22-
# the major version, the minor version, the patch version, and the build number.
34+
# the major version, the minor version, the patch version, and the build number with configurable digit counts.
2335
#
2436
# @param [AppVersion] version The AppVersion object to derive the next build code from.
2537
#
@@ -28,19 +40,16 @@ def initialize(prefix: nil)
2840
#
2941
# @return [String] The formatted build code string.
3042
#
31-
def build_code(build_code = nil, version:)
32-
result = format(
33-
# The prefix is configurable to allow for additional platforms or
34-
# extensions that could use a different digit prefix such as 2, etc.
35-
'%<prefix>s%<major>.2i%<minor>.2i%<patch>.2i%<build_number>.2i',
36-
prefix: @prefix,
37-
major: version.major,
38-
minor: version.minor,
39-
patch: version.patch,
40-
build_number: version.build_number
41-
)
42-
43-
result.gsub(/^0+/, '')
43+
def build_code(_build_code = nil, version:)
44+
formatted_components = version.components.zip(@digit_counts).map do |value, width|
45+
comp = value.to_s.rjust(width, '0')
46+
if comp.length > width
47+
UI.user_error!("Version component value (#{value}) exceeds maximum allowed width of #{width} characters. " \
48+
"Consider increasing the corresponding `*_digits` parameter of your `#{self.class.name}`")
49+
end
50+
comp
51+
end
52+
[@prefix, *formatted_components].join.gsub(/^0+/, '')
4453
end
4554

4655
private
@@ -52,6 +61,10 @@ def build_code(build_code = nil, version:)
5261
# @raise [StandardError] If the prefix is invalid
5362
#
5463
def validate_prefix!(prefix)
64+
unless prefix.nil? || prefix.is_a?(String) || prefix.is_a?(Integer)
65+
UI.user_error!("Prefix must be a string or integer, got: #{prefix.class}")
66+
end
67+
5568
prefix_str = prefix.to_s
5669

5770
# Allow empty string
@@ -67,6 +80,37 @@ def validate_prefix!(prefix)
6780

6881
UI.user_error!("Prefix must be an integer digit (0-9) or empty string, got: '#{prefix_str}'")
6982
end
83+
84+
# Validates that the digit count is a valid positive integer within reasonable limits.
85+
#
86+
# @param [Integer] digit_count The digit count to validate
87+
#
88+
# @raise [StandardError] If the digit count is invalid
89+
#
90+
def validate_digit_count!(digit_count)
91+
# Check if it's an integer
92+
unless digit_count.is_a?(Integer)
93+
UI.user_error!("Digit count must be an integer, got: #{digit_count.class}")
94+
end
95+
96+
return if digit_count.between?(MIN_DIGIT_COUNT, MAX_DIGIT_COUNT)
97+
98+
UI.user_error!("Digit count must be between #{MIN_DIGIT_COUNT} and #{MAX_DIGIT_COUNT} digits, got: #{digit_count}")
99+
end
100+
101+
# Validates that the total number of digits (excluding prefix) doesn't exceed the maximum for multiplatform compatibility.
102+
#
103+
# Since Google Play's max versionCode is ≈ 2_000_000_000, we want to avoid being too close to the limit
104+
# as this would then block us from submitting any updates for that app if we reached it.
105+
def validate_total_digits!(digits_list)
106+
total_digits = digits_list.sum
107+
108+
# Limit total digits to 8 (excluding prefix)
109+
return if total_digits <= MAX_TOTAL_DIGITS
110+
111+
UI.user_error!("Total digit count (#{total_digits}) exceeds maximum allowed (#{MAX_TOTAL_DIGITS}). " \
112+
"Current config: major(#{digits_list[0]}) + minor(#{digits_list[1]}) + patch(#{digits_list[2]}) + build(#{digits_list[3]}) digits")
113+
end
70114
end
71115
end
72116
end

0 commit comments

Comments
 (0)