fastlane-plugin-wpmreleasetoolkit 12.5.0 → 13.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54ad163793f5102f88dd8a4a82408cd73bd5bb5d084354f4ea8d21f90cb994b5
4
- data.tar.gz: 7d627bd253b7b2c9be0b990108caffb1a0b1a9056466afa98628fe4e27b79c2a
3
+ metadata.gz: 812b207092ef802b165390e74ba296566e70a3e406c7e1a50572a6bd7b6da761
4
+ data.tar.gz: 9266eec38e720af58e54413e649b2451e0f88100f4621bfab25daa05912663ee
5
5
  SHA512:
6
- metadata.gz: 545ce35d03805a5deb7c30ed22e01f55106d0a8420a89a355f2ff8047ddddef2b433294da5a86d6a5c1ca460c0d3f1c08215396b637faa50d9aca7be5823c2f2
7
- data.tar.gz: fc4d77d82c952c610f73c823d4b8548a83627ceac6ba79101e8a235e3851e16fefbc4bdd7f1c87cc0f89ee68194b95c4570bb798923006aa1121de66b57ca6ab
6
+ metadata.gz: 45a5b7aceb10ac90eaf7bf81e3c8cf07d1ed67691d558c610d8393db098e699f20f8ed63dcaf97f3af44f70433ee70f278ac0ee7185a1374642c4b4504ac51df
7
+ data.tar.gz: 8a8f7296d5458082034650d8675a17e4144f3d9c5cfc45e7482101c69ab1cdcadc645109b5151a65099167b0b910b5b4f092e92f717dcd210e37c9d5e7780e3a
@@ -9,6 +9,7 @@ module Fastlane
9
9
  DEFAULT_BRANCH = 'trunk'
10
10
 
11
11
  def self.run(params)
12
+ api_url = params[:api_url]
12
13
  token = params[:github_token]
13
14
  repository = params[:repository]
14
15
  source_branch = params[:source_branch]
@@ -41,6 +42,7 @@ module Fastlane
41
42
  Fastlane::Helper::GitHelper.checkout_and_pull(source_branch)
42
43
 
43
44
  create_backmerge_pr(
45
+ api_url: api_url,
44
46
  token: token,
45
47
  repository: repository,
46
48
  title: "Merge #{source_branch} into #{target_branch}",
@@ -76,6 +78,7 @@ module Fastlane
76
78
 
77
79
  # Creates a backmerge pull request using the `create_pull_request` Fastlane Action.
78
80
  #
81
+ # @param api_url [String] the GitHub API URL to use for creating the pull request
79
82
  # @param token [String] the GitHub token for authentication.
80
83
  # @param repository [String] the repository where the pull request will be created.
81
84
  # @param title [String] the title of the pull request.
@@ -90,7 +93,7 @@ module Fastlane
90
93
  #
91
94
  # @return [String] The URL of the created Pull Request, or `nil` if no PR was created.
92
95
  #
93
- def self.create_backmerge_pr(token:, repository:, title:, head_branch:, base_branch:, labels:, milestone:, reviewers:, team_reviewers:, intermediate_branch_created_callback:)
96
+ def self.create_backmerge_pr(api_url:, token:, repository:, title:, head_branch:, base_branch:, labels:, milestone:, reviewers:, team_reviewers:, intermediate_branch_created_callback:) # rubocop:disable Metrics/ParameterLists
94
97
  # Do an early pre-check to see if the PR would be valid, but only if no callback (as a callback might add new commits on intermediate branch)
95
98
  if intermediate_branch_created_callback.nil? && !can_merge?(head_branch, into: base_branch)
96
99
  UI.error("Nothing to merge from #{head_branch} into #{base_branch}. Skipping PR creation.")
@@ -108,7 +111,9 @@ module Fastlane
108
111
 
109
112
  # Call the callback if one was provided to allow the use to add commits on the intermediate branch (e.g. solve conflicts)
110
113
  unless intermediate_branch_created_callback.nil?
111
- intermediate_branch_created_callback.call(base_branch, intermediate_branch)
114
+ Dir.chdir(FastlaneCore::FastlaneFolder.path) do
115
+ intermediate_branch_created_callback.call(base_branch, intermediate_branch)
116
+ end
112
117
  # Make sure the callback block didn't switch branches
113
118
  other_action.ensure_git_branch(branch: "^#{intermediate_branch}$")
114
119
 
@@ -120,7 +125,7 @@ module Fastlane
120
125
  end
121
126
  end
122
127
 
123
- other_action.push_to_git_remote(tags: false)
128
+ other_action.push_to_git_remote(tags: false, remote_branch: intermediate_branch, set_upstream: true)
124
129
 
125
130
  pr_body = <<~BODY
126
131
  Merging `#{head_branch}` into `#{base_branch}`.
@@ -136,6 +141,7 @@ module Fastlane
136
141
  BODY
137
142
 
138
143
  other_action.create_pull_request(
144
+ api_url: api_url,
139
145
  api_token: token,
140
146
  repo: repository,
141
147
  title: title,
@@ -191,50 +197,63 @@ module Fastlane
191
197
  end
192
198
 
193
199
  def self.available_options
200
+ # Parameters we want to forward from Fastlane's create_pull_request action
201
+ forwarded_param_keys = %i[
202
+ api_url
203
+ labels
204
+ assignees
205
+ reviewers
206
+ team_reviewers
207
+ ].freeze
208
+
209
+ forwarded_params = Fastlane::Actions::CreatePullRequestAction.available_options.select do |opt|
210
+ forwarded_param_keys.include?(opt.key)
211
+ end
212
+
194
213
  [
195
- FastlaneCore::ConfigItem.new(key: :repository,
196
- env_name: 'GHHELPER_REPOSITORY',
197
- description: 'The remote path of the GH repository on which we work',
198
- optional: false,
199
- type: String),
200
- FastlaneCore::ConfigItem.new(key: :source_branch,
201
- description: 'The source branch to create a backmerge PR from, in the format `release/x.y.z`',
202
- optional: false,
203
- type: String),
204
- FastlaneCore::ConfigItem.new(key: :default_branch,
205
- description: 'The default branch to target if no newer release branches exist',
206
- optional: true,
207
- default_value: DEFAULT_BRANCH,
208
- type: String),
209
- FastlaneCore::ConfigItem.new(key: :target_branches,
210
- description: 'Array of target branches for the backmerge. If empty, the action will determine target branches by finding all `release/x.y.z` branches with a `x.y.z` version greater than the version in source branch\'s name. If none are found, it will target `default_branch`', # rubocop:disable Layout/LineLength
211
- optional: true,
212
- default_value: [],
213
- type: Array),
214
- FastlaneCore::ConfigItem.new(key: :labels,
215
- description: 'The labels that should be assigned to the backmerge PRs',
216
- optional: true,
217
- default_value: [],
218
- type: Array),
219
- FastlaneCore::ConfigItem.new(key: :milestone_title,
220
- description: 'The title of the milestone to assign to the created PRs',
221
- optional: true,
222
- type: String),
223
- FastlaneCore::ConfigItem.new(key: :reviewers,
224
- description: 'An array of GitHub users that will be assigned to the pull request',
225
- optional: true,
226
- type: Array),
227
- FastlaneCore::ConfigItem.new(key: :team_reviewers,
228
- description: 'An array of GitHub team slugs that will be assigned to the pull request',
229
- optional: true,
230
- type: Array),
231
- FastlaneCore::ConfigItem.new(key: :intermediate_branch_created_callback,
232
- description: 'Callback to allow for the caller to perform operations on the intermediate branch (e.g. pushing new commits to pre-solve conflicts) before creating the PR. ' \
233
- + 'The callback receives two parameters: the base (target) branch for the PR and the intermediate branch name that has been created.' \
234
- + 'Note that if you use the callback to add new commits to the intermediate branch, you are responsible for git-pushing them too',
235
- optional: true,
236
- type: Proc),
237
- Fastlane::Helper::GithubHelper.github_token_config_item,
214
+ *forwarded_params,
215
+ Fastlane::Helper::GithubHelper.github_token_config_item, # we forward `github_token` to `api_token` in the `create_pull_request` action
216
+ FastlaneCore::ConfigItem.new(
217
+ key: :repository,
218
+ env_name: 'GHHELPER_REPOSITORY',
219
+ description: 'The remote path of the GH repository on which we work',
220
+ optional: false,
221
+ type: String
222
+ ),
223
+ FastlaneCore::ConfigItem.new(
224
+ key: :source_branch,
225
+ description: 'The source branch to create a backmerge PR from, in the format `release/x.y.z`',
226
+ optional: false,
227
+ type: String
228
+ ),
229
+ FastlaneCore::ConfigItem.new(
230
+ key: :default_branch,
231
+ description: 'The default branch to target if no newer release branches exist',
232
+ optional: true,
233
+ default_value: DEFAULT_BRANCH,
234
+ type: String
235
+ ),
236
+ FastlaneCore::ConfigItem.new(
237
+ key: :target_branches,
238
+ description: 'Array of target branches for the backmerge. If empty, the action will determine target branches by finding all `release/x.y.z` branches with a `x.y.z` version greater than the version in source branch\'s name. If none are found, it will target `default_branch`',
239
+ optional: true,
240
+ default_value: [],
241
+ type: Array
242
+ ),
243
+ FastlaneCore::ConfigItem.new(
244
+ key: :milestone_title,
245
+ description: 'The title of the milestone to assign to the created PRs',
246
+ optional: true,
247
+ type: String
248
+ ),
249
+ FastlaneCore::ConfigItem.new(
250
+ key: :intermediate_branch_created_callback,
251
+ description: 'Callback to allow for the caller to perform operations on the intermediate branch (e.g. pushing new commits to pre-solve conflicts) before creating the PR. ' \
252
+ + 'The callback receives two parameters: the base (target) branch for the PR and the intermediate branch name that has been created.' \
253
+ + 'Note that if you use the callback to add new commits to the intermediate branch, you are responsible for git-pushing them too',
254
+ optional: true,
255
+ type: Proc
256
+ ),
238
257
  ]
239
258
  end
240
259
 
@@ -1,26 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'cgi'
4
+ require 'uri'
5
+
3
6
  module Fastlane
4
7
  module Actions
5
8
  class PrototypeBuildDetailsCommentAction < Action
6
9
  def self.run(params)
7
10
  app_display_name = params[:app_display_name]
8
- app_center_info = AppCenterInfo.from_params(params)
9
- metadata = consolidate_metadata(params, app_center_info)
11
+ download_url = params[:download_url]
12
+ release_info = FirebaseReleaseInfo.from_lane_context
10
13
 
11
- qr_code_url, extra_metadata = build_install_links(app_center_info, params[:download_url])
14
+ # Merge explicit extra metadata passed from params with ones derived from FirebaseReleaseInfo
15
+ metadata = generate_metadata_hash(params: params, release_info: release_info)
16
+ # Build the installation link, QR code URL and extra metadata for download links from the available info
17
+ qr_code_url, extra_metadata = install_links(release_info: release_info, download_url: download_url)
12
18
  metadata.merge!(extra_metadata)
13
19
 
14
- # Build the comment parts
15
- icon_img_tag = img_tag(params[:app_icon] || app_center_info.icon, alt: app_display_name)
20
+ # Build the comment parts and body
21
+ app_icon = params[:app_icon]
22
+ app_icon ||= ':firebase:' if !release_info.nil? || (download_url && is_firebase_url?(download_url))
23
+ intro = "#{img_tag(app_icon)}📲 You can test the changes from this Pull Request in <b>#{CGI.escape_html(app_display_name)}</b> by scanning the QR code below to install the corresponding build."
16
24
  metadata_rows = metadata.compact.map { |key, value| "<tr><td><b>#{key}</b></td><td>#{value}</td></tr>" }
17
- intro = "#{icon_img_tag}📲 You can test the changes from this Pull Request in <b>#{app_display_name}</b> by scanning the QR code below to install the corresponding build."
18
- footnote = params[:footnote] || (app_center_info.org_name.nil? ? '' : DEFAULT_APP_CENTER_FOOTNOTE)
19
- body = <<~COMMENT_BODY
25
+ footnote = params[:footnote]
26
+ footnote ||= DEFAULT_FOOTNOTE if !release_info.nil? || (download_url && is_firebase_url?(download_url))
27
+
28
+ body = <<~COMMENT_BODY.chomp('')
20
29
  <table>
21
30
  <tr>
22
31
  <td rowspan='#{metadata_rows.count + 1}' width='260px'><img src='#{qr_code_url}' width='250' height='250' /></td>
23
- <td><b>App Name</b></td><td>#{icon_img_tag} #{app_display_name}</td>
32
+ <td><b>App Name</b></td><td>#{CGI.escape_html(app_display_name)}</td>
24
33
  </tr>
25
34
  #{metadata_rows.join("\n")}
26
35
  </table>
@@ -28,9 +37,9 @@ module Fastlane
28
37
  COMMENT_BODY
29
38
 
30
39
  if params[:fold]
31
- "<details><summary>#{intro}</summary>\n#{body}</details>\n"
40
+ "<details><summary>#{intro}</summary>\n#{body}\n</details>\n"
32
41
  else
33
- "<p>#{intro}</p>\n#{body}"
42
+ "<p>#{intro}</p>\n#{body}\n"
34
43
  end
35
44
  end
36
45
 
@@ -40,76 +49,126 @@ module Fastlane
40
49
 
41
50
  NO_INSTALL_URL_ERROR_MESSAGE = <<~NO_URL_ERROR
42
51
  No URL provided to download or install the app.
43
- - Either use this action right after using `appcenter_upload` and provide an `app_center_org_name` (so that this action can use the link to the App Center build)
52
+ - Either use this action right after using `firebase_app_distribution` so this action can extract the download URL from the `lane_context`
44
53
  - Or provide an explicit value for the `download_url` parameter
45
54
  NO_URL_ERROR
46
55
 
47
- DEFAULT_APP_CENTER_FOOTNOTE = '<em>Automatticians: You can use our internal self-serve MC tool to give yourself access to App Center if needed.</em>'
56
+ DEFAULT_FOOTNOTE = '<em>Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.</em>'
48
57
 
49
- # A small model struct to consolidate and pack all the values related to App Center
58
+ # Parse and validate a URL string
59
+ #
60
+ # @param [String] url The URL string to parse and validate
61
+ # @return [URI] The parsed URI object
62
+ # @raise [FastlaneCore::Interface::FastlaneError] if the URL is invalid
50
63
  #
51
- AppCenterInfo = Struct.new(:org_name, :app_name, :display_name, :release_id, :icon, :version, :short_version, :os, :bundle_id) do
52
- # A method to construct an AppCenterInfo instance from the action params, and infer the rest from the `lane_context` if available
53
- def self.from_params(params)
54
- org_name = params[:app_center_org_name]
55
- ctx = if org_name && defined?(SharedValues::APPCENTER_BUILD_INFORMATION)
56
- Fastlane::Actions.lane_context[SharedValues::APPCENTER_BUILD_INFORMATION] || {}
57
- else
58
- {}
59
- end
60
- app_name = params[:app_center_app_name] || ctx['app_name']
64
+ def self.parse_url!(url)
65
+ URI.parse(url).tap do |uri|
66
+ raise URI::InvalidURIError unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
67
+ end
68
+ rescue URI::InvalidURIError
69
+ UI.user_error!("Invalid URL: #{url}")
70
+ end
71
+
72
+ # A small model/struct representing values exposed by Firebase App Distribution for a given release
73
+ #
74
+ FirebaseReleaseInfo = Struct.new(:display_version, :build_version, :testing_url, :os, :bundle_id, :release_id, keyword_init: true) do
75
+ def self.from_lane_context
76
+ return nil unless defined?(SharedValues::FIREBASE_APP_DISTRO_RELEASE)
77
+
78
+ ctx = Fastlane::Actions.lane_context[SharedValues::FIREBASE_APP_DISTRO_RELEASE]
79
+ return nil if ctx.nil?
80
+
81
+ # Extract platform info from Firebase Console URI
82
+ if ctx[:firebaseConsoleUri]
83
+ uri = URI(ctx[:firebaseConsoleUri])
84
+ os, bundle_id, release_id = uri.path.match(%r{project/.*/appdistribution/app/([^:]*):([^/]*)/releases/(.*)})&.captures
85
+ end
86
+
61
87
  new(
62
- org_name,
63
- app_name,
64
- ctx['app_display_name'] || app_name,
65
- params[:app_center_release_id] || ctx['id'],
66
- ctx['app_icon_url'],
67
- ctx['version'],
68
- ctx['short_version'],
69
- ctx['app_os'],
70
- ctx['bundle_identifier']
88
+ display_version: ctx[:displayVersion],
89
+ build_version: ctx[:buildVersion],
90
+ testing_url: ctx[:testingUri],
91
+ os: os,
92
+ bundle_id: bundle_id,
93
+ release_id: release_id
71
94
  )
72
95
  end
73
96
  end
74
97
 
75
- # Builds the installation link, QR code URL and extra metadata for download links from the available info
98
+ # Constructs the Hash of metadata, based on the explicit ones passed by the user as parameter + the implicit ones from `FirebaseReleaseInfo`
99
+ #
100
+ # @param [Hash<Symbol, Any>] params The action's parameters, as received by `self.run`
101
+ # @param [FirebaseReleaseInfo?] release_info The information about the Firebase Release extracted from the `lane_context`
102
+ # @return [Hash<String, String>] A hash of all the metadata, consolidated from both the explicit and the implicit ones
103
+ #
104
+ def self.generate_metadata_hash(params:, release_info:)
105
+ # Add explicit metadata provided by the caller
106
+ metadata = params[:metadata]&.transform_keys(&:to_s) || {}
107
+
108
+ # Add Firebase-specific metadata if available
109
+ unless release_info.nil?
110
+ metadata['Build Number'] ||= "<code>#{release_info.build_version}</code>"
111
+ metadata['Version'] ||= "<code>#{release_info.display_version}</code>"
112
+ metadata[release_info.os == 'ios' ? 'Bundle ID' : 'Application ID'] ||= "<code>#{release_info.bundle_id}</code>"
113
+ end
114
+
115
+ # Add git metadata
116
+ metadata['Commit'] ||= ENV.fetch('BUILDKITE_COMMIT', nil) || other_action.last_git_commit[:abbreviated_commit_hash]
117
+ metadata
118
+ end
119
+
120
+ # Constructs the installation link, QR code URL and extra metadata for download links from the available info
76
121
  #
77
- # @param [AppCenterInfo] app_center_info The struct containing all the values related to App Center info
78
- # @param [String] download_url The `download_url` parameter passed to the action, if one exists
122
+ # @param [FirebaseReleaseInfo?] release_info The information about the Firebase Release extracted from the `lane_context`
123
+ # @param [String] download_url The `download_url` parameter passed to the action, if one was provided
79
124
  # @return [(String, Hash<String,String>)] A tuple containing:
80
125
  # - The URL for the QR Code
81
126
  # - A Hash of the extra metadata key/value pairs to add to the existing metadata, to enrich them with download/install links
127
+ # @raise [FastlaneCore::Interface::FastlaneError] if no valid installation URL could be determined
82
128
  #
83
- def self.build_install_links(app_center_info, download_url)
129
+ def self.install_links(release_info:, download_url:)
84
130
  install_url = nil
85
131
  extra_metadata = {}
132
+ firebase_release_id = nil
133
+
134
+ # Validate and process direct download URL if provided
86
135
  if download_url
136
+ uri = parse_url!(download_url)
87
137
  install_url = download_url
88
- extra_metadata['Direct Download'] = "<a href='#{install_url}'><code>#{File.basename(install_url)}</code></a>"
138
+
139
+ if is_firebase_url?(uri)
140
+ firebase_release_id = File.basename(uri.path)
141
+ else
142
+ filename = File.basename(uri.path)
143
+ extra_metadata['Direct Download'] = "<a href='#{CGI.escape_html(install_url)}'><code>#{CGI.escape_html(filename)}</code></a>"
144
+ end
89
145
  end
90
- if app_center_info.org_name && app_center_info.app_name
91
- install_url = "https://install.appcenter.ms/orgs/#{app_center_info.org_name}/apps/#{app_center_info.app_name}/releases/#{app_center_info.release_id}"
92
- extra_metadata['App Center Build'] = "<a href='#{install_url}'>#{app_center_info.display_name} ##{app_center_info.release_id}</a>"
146
+
147
+ # Process Firebase testing URL if available from release_info
148
+ if release_info&.testing_url
149
+ install_url = release_info.testing_url
150
+ firebase_release_id = release_info.release_id
93
151
  end
152
+
94
153
  UI.user_error!(NO_INSTALL_URL_ERROR_MESSAGE) if install_url.nil?
154
+
155
+ # Add Installation URL metadata if we have a release_id
156
+ extra_metadata['Installation URL'] = "<a href='#{CGI.escape_html(install_url)}'>#{CGI.escape_html(firebase_release_id)}</a>" if firebase_release_id
157
+
158
+ # Generate QR code URL with proper escaping
95
159
  qr_code_url = "https://api.qrserver.com/v1/create-qr-code/?size=500x500&qzone=4&data=#{CGI.escape(install_url)}"
96
160
  [qr_code_url, extra_metadata]
97
161
  end
98
162
 
99
- # A method to build the Hash of metadata, based on the explicit ones passed by the user as parameter + the implicit ones from `AppCenterInfo`
163
+ # Determines if a given URI is a Firebase App Distribution URL
100
164
  #
101
- # @param [Hash<Symbol, Any>] params The action's parameters, as received by `self.run`
102
- # @param [AppCenterInfo] app_center_info The model object containing all the values related to App Center information
103
- # @return [Hash<String, String>] A hash of all the metadata, gathered from both the explicit and the implicit ones
165
+ # @param [String, URI] url The URL to check, either as a String or an already-parsed URI
166
+ # @return [Boolean] true if the URL is a Firebase App Distribution URL
167
+ # @raise [FastlaneCore::Interface::FastlaneError] if the URL is invalid
104
168
  #
105
- def self.consolidate_metadata(params, app_center_info)
106
- metadata = params[:metadata]&.transform_keys(&:to_s) || {}
107
- metadata['Build Number'] ||= app_center_info.version
108
- metadata['Version'] ||= app_center_info.short_version
109
- metadata[app_center_info.os == 'Android' ? 'Application ID' : 'Bundle ID'] ||= app_center_info.bundle_id
110
- # (Feel free to add more CI-specific env vars in the line below to support other CI providers if you need)
111
- metadata['Commit'] ||= ENV.fetch('BUILDKITE_COMMIT', nil) || other_action.last_git_commit[:abbreviated_commit_hash]
112
- metadata
169
+ def self.is_firebase_url?(url)
170
+ uri = url.is_a?(URI) ? url : parse_url!(url)
171
+ uri.host == 'appdistribution.firebase.google.com' && uri.path.start_with?('/testerapps/')
113
172
  end
114
173
 
115
174
  # Creates an HTML `<img>` tag for an icon URL or the image URL to represent a given Buildkite emoji
@@ -117,19 +176,19 @@ module Fastlane
117
176
  # @param [String] url_or_emoji A `String` which can be:
118
177
  # - Either a valid URI to an image
119
178
  # - Or a string formatted like `:emojiname:`, using a valid Buildite emoji name as defined in https://github.com/buildkite/emojis
120
- # @param [String] alt The alt text to use for the `<img>` tag
121
179
  # @return [String] The `<img …>` tag with the proper image and alt tag
180
+ # @raise [FastlaneCore::Interface::FastlaneError] if the URL is invalid
122
181
  #
123
- def self.img_tag(url_or_emoji, alt: '')
182
+ def self.img_tag(url_or_emoji)
124
183
  return nil if url_or_emoji.nil?
125
184
 
126
185
  emoji = url_or_emoji.match(/:(.*):/)&.captures&.first
127
186
  app_icon_url = if emoji
128
187
  "https://raw.githubusercontent.com/buildkite/emojis/main/img-buildkite-64/#{emoji}.png"
129
- elsif URI(url_or_emoji)
130
- url_or_emoji
188
+ else
189
+ url_or_emoji.tap { parse_url!(url_or_emoji) }
131
190
  end
132
- app_icon_url ? "<img alt='#{alt}' align='top' src='#{app_icon_url}' width='20px' />" : ''
191
+ app_icon_url ? "<img align='top' src='#{app_icon_url}' width='20px' alt='App Icon' />" : ''
133
192
  end
134
193
 
135
194
  #####################################################
@@ -145,98 +204,72 @@ module Fastlane
145
204
  Generates a string providing all the details of a prototype build, nicely-formatted as HTML.
146
205
  The returned string will typically be subsequently used by the `comment_on_pr` action to post that HTML as comment on a PR.
147
206
 
148
- If you used the `appcenter_upload` lane (to upload the Prototype build to App Center) before calling this action, and pass
149
- a value to the `app_center_org_name` parameter, then many of the parameters and metadata will be automatically extracted
150
- from the `lane_context` provided by `appcenter_upload`, including:
207
+ If you used the `firebase_app_distribution` action (to upload the Prototype build to Firebase App Distribution) before calling this action,
208
+ then many of the metadata will be automatically extracted from the `lane_context` it exposed:
151
209
 
152
- - The `app_center_app_name`, `app_center_release_id` and installation URL to use for the QR code to point to that release in App Center
153
- - The `app_icon`
154
- - The app's Build Number / versionCode
155
- - The app's Version / versionName
156
- - The app's Bundle ID / Application ID
157
- - A `footnote` mentioning the MC tool for Automatticians to add themselves to App Center
210
+ - "Version" (from `:displayVersion`) and "Build Number" (from `:buildVersion`)
211
+ - "Bundle ID" (extracted from `:firebaseConsoleUri`)
212
+ - "Commit" (from `BUILDKITE_COMMIT` environment variable or last git commit)
213
+ - "Installation URL" (from `:testingUri`)
158
214
 
159
- This means that if you are using App Center to distribute your Prototype Build, the only parameters you *have* to provide
160
- to this action are `app_display_name` and `app_center_org_name`; plus, for `metadata` most of the interesting values will already be pre-filled.
215
+ You can also pass additional metadata to this action via the `metadata` parameter, and they will also be included in the HTML table of the comment.
161
216
 
162
- Any of those implicit default values/metadata can of course be overridden by passing an explicit value to the appropriate parameter(s).
217
+ This means that if you are using Firebase App Distribution to distribute your Prototype Build, the can just provide
218
+ `app_display_name` and optionally `app_icon`, and the rest will be automatically inferred from the `lane_context`.
219
+
220
+ If you are not using Firebase App Distribution, you can pass an explicit value for the `download_url` parameter,
221
+ and the action will use it to generate the installation link and QR code.
163
222
  DESC
164
223
  end
165
224
 
166
225
  def self.available_options
167
- app_center_auto = '(will be automatically extracted from `lane_context if you used `appcenter_upload` to distribute your Prototype build)'
168
226
  [
169
227
  FastlaneCore::ConfigItem.new(
170
228
  key: :app_display_name,
171
- env_name: 'FL_PROTOTYPE_BUILD_DETAILS_COMMENT_APP_DISPLAY_NAME',
172
229
  description: 'The display name to use for the app in the comment message',
173
230
  optional: false,
174
231
  type: String
175
232
  ),
176
- FastlaneCore::ConfigItem.new(
177
- key: :app_center_org_name,
178
- env_name: 'APPCENTER_OWNER_NAME', # Intentionally the same as the one used by the `appcenter_upload` action
179
- description: 'The name of the organization in App Center (if you used `appcenter_upload` to distribute your Prototype build)',
180
- type: String,
181
- optional: true
182
- ),
183
- FastlaneCore::ConfigItem.new(
184
- key: :app_center_app_name,
185
- env_name: 'APPCENTER_APP_NAME', # Intentionally the same as the one used by the `appcenter_upload` action
186
- description: "The name of the app in App Center #{app_center_auto}",
187
- type: String,
188
- optional: true,
189
- default_value_dynamic: true # As it will be extracted from the `lane_context`` if you used `appcenter_upload``
190
- ),
191
- FastlaneCore::ConfigItem.new(
192
- key: :app_center_release_id,
193
- env_name: 'APPCENTER_RELEASE_ID',
194
- description: "The release ID/Number in App Center #{app_center_auto}",
195
- type: String,
196
- optional: true,
197
- default_value_dynamic: true # As it will be extracted from the `lane_context`` if you used `appcenter_upload``
198
- ),
199
233
  FastlaneCore::ConfigItem.new(
200
234
  key: :app_icon,
201
- env_name: 'FL_PROTOTYPE_BUILD_DETAILS_COMMENT_APP_ICON',
202
- description: "The name of an emoji from the https://github.com/buildkite/emojis list or the full image URL to use for the icon of the app in the message. #{app_center_auto}",
235
+ description: 'The name of an emoji from the https://github.com/buildkite/emojis list or the full image URL to use for the icon of the app in the message',
203
236
  type: String,
204
237
  optional: true,
205
- default_value_dynamic: true # As it will be extracted from the `lane_context`` if you used `appcenter_upload``
238
+ default_value_dynamic: true # Defaults to `:firebase:` only if `firebase_app_distribution` was used
206
239
  ),
207
240
  FastlaneCore::ConfigItem.new(
208
241
  key: :download_url,
209
- env_name: 'FL_PROTOTYPE_BUILD_DETAILS_COMMENT_DOWNLOAD_URL',
210
- description: 'The URL to download the build as a direct download. ' \
211
- + 'If you uploaded the build to App Center, we recommend leaving this nil (the comment will use the URL to the App Center build for the QR code)',
242
+ description: <<~DESC,
243
+ The URL to use to download/install the build.
244
+ - If you used `firebase_app_distribution` to upload the build during the same `fastlane` run, you should leave this nil
245
+ - If you used `firebase_app_distribution` during a separate CI job, you can store the `:testingUri` of that call's returned hash (in e.g. Buildkite metadata), then pass that URI to this parameter
246
+ - Otherwise, you can provide a direct download URL for the build (e.g. link to Cloudfront or AppsCDN URL)
247
+ DESC
212
248
  type: String,
213
249
  optional: true,
214
250
  default_value: nil
215
251
  ),
216
252
  FastlaneCore::ConfigItem.new(
217
253
  key: :fold,
218
- env_name: 'FL_PROTOTYPE_BUILD_DETAILS_COMMENT_FOLD',
219
254
  description: 'If true, will wrap the HTML table inside a <details> block (hidden by default)',
220
255
  type: Boolean,
221
256
  default_value: false
222
257
  ),
223
258
  FastlaneCore::ConfigItem.new(
224
259
  key: :metadata,
225
- env_name: 'FL_PROTOTYPE_BUILD_DETAILS_COMMENT_METADATA',
226
260
  description: 'All additional metadata (as key/value pairs) you want to include in the HTML table of the comment. ' \
227
- + 'If you are running this action after `appcenter_upload`, some metadata will automatically be added to this list too',
261
+ + 'If you are running this action after `firebase_app_distribution`, some metadata will automatically be added and merged with this list',
228
262
  type: Hash,
229
263
  optional: true,
230
- default_value_dynamic: true # As some metadata will be auto-filled if you used `appcenter_upload`
264
+ default_value_dynamic: true # As some metadata will be auto-filled if you used `firebase_app_distribution`
231
265
  ),
232
266
  FastlaneCore::ConfigItem.new(
233
267
  key: :footnote,
234
- env_name: 'FL_PROTOTYPE_BUILD_DETAILS_COMMENT_FOOTNOTE',
235
268
  description: 'Optional footnote to add below the HTML table of the comment. ' \
236
- + 'If you are running this action after `appcenter_upload`, a default footnote for Automatticians will be used unless you provide an explicit value',
269
+ + 'If you are running this action after `firebase_app_distribution`, a default footnote for Automatticians will be used unless you provide an explicit value',
237
270
  type: String,
238
271
  optional: true,
239
- default_value_dynamic: true # We have a default footnote for the case when you used App Center
272
+ default_value_dynamic: true # We have a default footnote for the case when you used Firebase App Distribution
240
273
  ),
241
274
  ]
242
275
  end
@@ -0,0 +1,320 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fastlane/action'
4
+ require 'net/http'
5
+ require 'uri'
6
+ require 'json'
7
+
8
+ module Fastlane
9
+ module Actions
10
+ module SharedValues
11
+ APPS_CDN_UPLOADED_FILE_URL = :APPS_CDN_UPLOADED_FILE_URL
12
+ APPS_CDN_UPLOADED_FILE_ID = :APPS_CDN_UPLOADED_FILE_ID
13
+ APPS_CDN_UPLOADED_POST_ID = :APPS_CDN_UPLOADED_POST_ID
14
+ APPS_CDN_UPLOADED_POST_URL = :APPS_CDN_UPLOADED_POST_URL
15
+ end
16
+
17
+ class UploadBuildToAppsCdnAction < Action
18
+ RESOURCE_TYPE = 'Build'
19
+ VALID_POST_STATUS = %w[publish draft].freeze
20
+ VALID_BUILD_TYPES = %w[Alpha Beta Nightly Production Prototype].freeze
21
+ VALID_PLATFORMS = ['Android', 'iOS', 'Mac - Silicon', 'Mac - Intel', 'Mac - Any', 'Windows'].freeze
22
+
23
+ def self.run(params)
24
+ UI.message('Uploading build to Apps CDN...')
25
+
26
+ file_path = params[:file_path]
27
+ UI.user_error!("File not found at path '#{file_path}'") unless File.exist?(file_path)
28
+
29
+ api_endpoint = "https://public-api.wordpress.com/rest/v1.1/sites/#{params[:site_id]}/media/new"
30
+ uri = URI.parse(api_endpoint)
31
+
32
+ # Create the request body and headers
33
+ parameters = {
34
+ product: params[:product],
35
+ build_type: params[:build_type],
36
+ visibility: params[:visibility].to_s.capitalize,
37
+ platform: params[:platform],
38
+ resource_type: RESOURCE_TYPE,
39
+ version: params[:version],
40
+ build_number: params[:build_number], # Optional: may be nil
41
+ minimum_system_version: params[:minimum_system_version], # Optional: may be nil
42
+ post_status: params[:post_status], # Optional: may be nil
43
+ release_notes: params[:release_notes], # Optional: may be nil
44
+ error_on_duplicate: params[:error_on_duplicate] # defaults to false
45
+ }.compact
46
+ request_body, content_type = build_multipart_request(parameters: parameters, file_path: file_path)
47
+
48
+ # Create and send the HTTP request
49
+ request = Net::HTTP::Post.new(uri.request_uri)
50
+ request.body = request_body
51
+ request['Content-Type'] = content_type
52
+ request['Accept'] = 'application/json'
53
+ request['Authorization'] = "Bearer #{params[:api_token]}"
54
+
55
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
56
+ http.request(request)
57
+ end
58
+
59
+ # Handle the response
60
+ case response
61
+ when Net::HTTPSuccess
62
+ result = parse_successful_response(response.body)
63
+
64
+ Actions.lane_context[SharedValues::APPS_CDN_UPLOADED_POST_ID] = result[:post_id]
65
+ Actions.lane_context[SharedValues::APPS_CDN_UPLOADED_POST_URL] = result[:post_url]
66
+ Actions.lane_context[SharedValues::APPS_CDN_UPLOADED_FILE_ID] = result[:media_id]
67
+ Actions.lane_context[SharedValues::APPS_CDN_UPLOADED_FILE_URL] = result[:media_url]
68
+
69
+ UI.success('Build successfully uploaded to Apps CDN')
70
+ UI.message("Post ID: #{result[:post_id]}")
71
+ UI.message("Post URL: #{result[:post_url]}")
72
+
73
+ result
74
+ else
75
+ UI.error("Failed to upload build to Apps CDN: #{response.code} #{response.message}")
76
+ UI.error(response.body)
77
+ UI.user_error!('Upload to Apps CDN failed')
78
+ end
79
+ end
80
+
81
+ # Builds a multipart request body for the WordPress.com Media API
82
+ #
83
+ # @param parameters [Hash] The parameters to include in the request as top-level form fields
84
+ # @param file_path [String] The path to the file to upload
85
+ # @return [Array] An array containing the request body and the content-type header
86
+ #
87
+ def self.build_multipart_request(parameters:, file_path:)
88
+ boundary = "----WebKitFormBoundary#{SecureRandom.hex(10)}"
89
+ content_type = "multipart/form-data; boundary=#{boundary}"
90
+ post_body = []
91
+
92
+ # Add the file first
93
+ post_body << "--#{boundary}"
94
+ post_body << "Content-Disposition: form-data; name=\"media[]\"; filename=\"#{File.basename(file_path)}\""
95
+ post_body << 'Content-Type: application/octet-stream'
96
+ post_body << ''
97
+ post_body << File.binread(file_path)
98
+
99
+ # Add each parameter as a separate form field
100
+ parameters.each do |key, value|
101
+ post_body << "--#{boundary}"
102
+ post_body << "Content-Disposition: form-data; name=\"#{key}\""
103
+ post_body << ''
104
+ post_body << value.to_s
105
+ end
106
+
107
+ # Add the closing boundary
108
+ post_body << "--#{boundary}--"
109
+
110
+ [post_body.join("\r\n"), content_type]
111
+ end
112
+
113
+ # Parse the successful response and return a hash with the upload details
114
+ #
115
+ # @param response_body [String] The raw response body from the API
116
+ # @return [Hash] A hash containing the upload details
117
+ def self.parse_successful_response(response_body)
118
+ json_response = JSON.parse(response_body)
119
+ media = json_response['media'].first
120
+ media_id = media['ID']
121
+ media_url = media['URL']
122
+ post_id = media['post_ID']
123
+
124
+ # Compute the post URL using the same base URL as media_url
125
+ post_url = URI.parse(media_url)
126
+ post_url.path = '/'
127
+ post_url.query = "p=#{post_id}"
128
+ post_url = post_url.to_s
129
+
130
+ {
131
+ post_id: post_id,
132
+ post_url: post_url,
133
+ media_id: media_id,
134
+ media_url: media_url,
135
+ mime_type: media['mime_type']
136
+ }
137
+ end
138
+
139
+ def self.description
140
+ 'Uploads a build binary to the Apps CDN'
141
+ end
142
+
143
+ def self.authors
144
+ ['Automattic']
145
+ end
146
+
147
+ def self.return_value
148
+ 'Returns a Hash containing the upload result: { post_id:, post_url:, media_id:, media_url:, mime_type: }. On error, raises a FastlaneError.'
149
+ end
150
+
151
+ def self.details
152
+ <<~DETAILS
153
+ Uploads a build binary file to a WordPress blog that has the Apps CDN plugin enabled.
154
+ See PCYsg-15tP-p2 internal a8c documentation for details about the Apps CDN plugin.
155
+ DETAILS
156
+ end
157
+
158
+ def self.available_options
159
+ [
160
+ FastlaneCore::ConfigItem.new(
161
+ key: :site_id,
162
+ env_name: 'APPS_CDN_SITE_ID',
163
+ description: 'The WordPress.com CDN site ID to upload the media to',
164
+ optional: false,
165
+ type: String,
166
+ verify_block: proc do |value|
167
+ UI.user_error!('Site ID cannot be empty') if value.to_s.empty?
168
+ end
169
+ ),
170
+ FastlaneCore::ConfigItem.new(
171
+ key: :product,
172
+ env_name: 'APPS_CDN_PRODUCT',
173
+ # Valid values can be found at https://github.a8c.com/Automattic/wpcom/blob/trunk/wp-content/lib/a8c/cdn/src/enums/enum-product.php
174
+ description: 'The product the build belongs to (e.g. \'WordPress.com Studio\')',
175
+ optional: false,
176
+ type: String,
177
+ verify_block: proc do |value|
178
+ UI.user_error!('Product cannot be empty') if value.to_s.empty?
179
+ end
180
+ ),
181
+ FastlaneCore::ConfigItem.new(
182
+ key: :platform,
183
+ env_name: 'APPS_CDN_PLATFORM',
184
+ # Valid values can be found at https://github.a8c.com/Automattic/wpcom/blob/trunk/wp-content/lib/a8c/cdn/src/enums/enum-platform.php
185
+ description: "The platform the build runs on. One of: #{VALID_PLATFORMS.join(', ')}",
186
+ optional: false,
187
+ type: String,
188
+ verify_block: proc do |value|
189
+ UI.user_error!('Platform cannot be empty') if value.to_s.empty?
190
+ UI.user_error!("Platform must be one of: #{VALID_PLATFORMS.join(', ')}") unless VALID_PLATFORMS.include?(value)
191
+ end
192
+ ),
193
+ FastlaneCore::ConfigItem.new(
194
+ key: :file_path,
195
+ description: 'The path to the build file to upload',
196
+ optional: false,
197
+ type: String,
198
+ verify_block: proc do |value|
199
+ UI.user_error!("File not found at path '#{value}'") unless File.exist?(value)
200
+ end
201
+ ),
202
+ FastlaneCore::ConfigItem.new(
203
+ key: :build_type,
204
+ # Valid values can be found at https://github.a8c.com/Automattic/wpcom/blob/trunk/wp-content/lib/a8c/cdn/src/enums/enum-build-type.php
205
+ description: "The type of the build. One of: #{VALID_BUILD_TYPES.join(', ')}",
206
+ optional: false,
207
+ type: String,
208
+ verify_block: proc do |value|
209
+ UI.user_error!('Build type cannot be empty') if value.to_s.empty?
210
+ UI.user_error!("Build type must be one of: #{VALID_BUILD_TYPES.join(', ')}") unless VALID_BUILD_TYPES.include?(value)
211
+ end
212
+ ),
213
+ FastlaneCore::ConfigItem.new(
214
+ key: :visibility,
215
+ description: 'The visibility of the build (:internal or :external)',
216
+ optional: false,
217
+ type: Symbol,
218
+ verify_block: proc do |value|
219
+ UI.user_error!('Visibility must be either :internal or :external') unless %i[internal external].include?(value)
220
+ end
221
+ ),
222
+ FastlaneCore::ConfigItem.new(
223
+ key: :post_status,
224
+ description: 'The post status (defaults to \'publish\')',
225
+ optional: true,
226
+ default_value: 'publish',
227
+ type: String,
228
+ verify_block: proc do |value|
229
+ UI.user_error!("Post status must be one of: #{VALID_POST_STATUS.join(', ')}") unless VALID_POST_STATUS.include?(value)
230
+ end
231
+ ),
232
+ FastlaneCore::ConfigItem.new(
233
+ key: :version,
234
+ description: 'The version string for the build (e.g. \'20.0\', \'17.8.1\')',
235
+ optional: false,
236
+ type: String,
237
+ verify_block: proc do |value|
238
+ UI.user_error!('Version cannot be empty') if value.to_s.empty?
239
+ end
240
+ ),
241
+ FastlaneCore::ConfigItem.new(
242
+ key: :build_number,
243
+ description: 'The build number for the build (e.g. \'42\')',
244
+ optional: true,
245
+ type: String
246
+ ),
247
+ FastlaneCore::ConfigItem.new(
248
+ key: :minimum_system_version,
249
+ description: 'The minimum version for the provided platform (e.g. \'13.0\' for macOS Ventura)',
250
+ optional: true,
251
+ type: String
252
+ ),
253
+ FastlaneCore::ConfigItem.new(
254
+ key: :release_notes,
255
+ description: 'The release notes to show with the build on the blog frontend',
256
+ optional: true,
257
+ type: String
258
+ ),
259
+ FastlaneCore::ConfigItem.new(
260
+ key: :error_on_duplicate,
261
+ description: 'If true, the action will error if a build matching the same metadata already exists. If false, any potential existing build matching the same metadata will be updated to replace the build with the new file',
262
+ default_value: false,
263
+ type: Boolean
264
+ ),
265
+ FastlaneCore::ConfigItem.new(
266
+ key: :api_token,
267
+ env_name: 'WPCOM_API_TOKEN',
268
+ description: 'The WordPress.com API token for authentication',
269
+ optional: false,
270
+ type: String,
271
+ verify_block: proc do |value|
272
+ UI.user_error!('API token cannot be empty') if value.to_s.empty?
273
+ end
274
+ ),
275
+ ]
276
+ end
277
+
278
+ def self.is_supported?(platform)
279
+ true
280
+ end
281
+
282
+ def self.output
283
+ [
284
+ ['APPS_CDN_UPLOADED_FILE_URL', 'The URL of the uploaded file'],
285
+ ['APPS_CDN_UPLOADED_FILE_ID', 'The ID of the uploaded file'],
286
+ ['APPS_CDN_UPLOADED_POST_ID', 'The ID of the post / page created for the uploaded build'],
287
+ ['APPS_CDN_UPLOADED_POST_URL', 'The URL of the post / page created for the uploaded build'],
288
+ ]
289
+ end
290
+
291
+ def self.example_code
292
+ [
293
+ 'upload_build_to_apps_cdn(
294
+ site_id: "12345678",
295
+ api_token: ENV["WPCOM_API_TOKEN"],
296
+ product: "WordPress.com Studio",
297
+ build_type: "Beta",
298
+ visibility: :internal,
299
+ platform: "Mac - Any",
300
+ version: "20.0",
301
+ build_number: "42",
302
+ file_path: "path/to/app.zip"
303
+ )',
304
+ 'upload_build_to_apps_cdn(
305
+ site_id: "12345678",
306
+ api_token: ENV["WPCOM_API_TOKEN"],
307
+ product: "WordPress.com Studio",
308
+ build_type: "Beta",
309
+ visibility: :external,
310
+ platform: "Android",
311
+ version: "20.0",
312
+ build_number: "42",
313
+ file_path: "path/to/app.apk",
314
+ error_on_duplicate: true
315
+ )',
316
+ ]
317
+ end
318
+ end
319
+ end
320
+ end
@@ -342,7 +342,10 @@ module Fastlane
342
342
  #
343
343
  def self.post_process_xml!(translated_xml, locale_code:, original_xml:)
344
344
  copy_orig_attributes = lambda do |node, xpath|
345
- orig_attributes = original_xml.xpath(xpath)&.first&.attribute_nodes&.to_h do |attr|
345
+ found_node = original_xml.xpath(xpath)&.first
346
+ return unless found_node
347
+
348
+ orig_attributes = found_node.attribute_nodes&.to_h do |attr|
346
349
  [[attr.namespace&.prefix, attr.name].compact.join(':'), attr.value]
347
350
  end
348
351
  orig_attributes&.each { |k, v| node[k] = v unless k == 'name' }
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fastlane'
4
+
5
+ # This monkey-patch adds Buildkite-aware logs to fastlane so it generates a collapsible log group in Buildkite for each action execution.
6
+ #
7
+ # @env `FASTLANE_DISABLE_ACTIONS_BUILDKITE_LOG_GROUPS`
8
+ # Set this variable to '1' to disable the auto-application of the monkey patch.
9
+
10
+ unless !ENV.key?('BUILDKITE') || FastlaneCore::Env.truthy?('FASTLANE_DISABLE_ACTIONS_BUILDKITE_LOG_GROUPS')
11
+ FastlaneCore::UI.verbose('Monkey-patching fastlane to add Buildkite-aware log groups for each action execution')
12
+
13
+ module Fastlane
14
+ module Actions
15
+ module SharedValues
16
+ # This differs from the existing `SharedValues::LANE_NAME`, which always contains the name of the **top-level** lane
17
+ CURRENTLY_RUNNING_LANE_NAME = :CURRENTLY_RUNNING_LANE_NAME
18
+ end
19
+
20
+ module BuildkiteLogActionsAsCollapsibleGroups
21
+ def execute_action(action_name, &)
22
+ unless %w[is_ci? is_ci].include?(action_name)
23
+ current_lane = lane_context[SharedValues::CURRENTLY_RUNNING_LANE_NAME]
24
+ lane_name_prefix = (current_lane || '').empty? ? '' : "[lane :#{current_lane}]"
25
+ puts "~~~ :fastlane: #{lane_name_prefix} #{action_name}"
26
+ end
27
+ super
28
+ end
29
+ end
30
+
31
+ class << self
32
+ prepend BuildkiteLogActionsAsCollapsibleGroups
33
+ end
34
+ end
35
+
36
+ class Runner
37
+ prepend(Module.new do
38
+ def current_lane=(lane_name)
39
+ super
40
+ Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::CURRENTLY_RUNNING_LANE_NAME] = lane_name
41
+ end
42
+ end)
43
+ end
44
+ end
45
+ end
@@ -54,7 +54,7 @@ module Fastlane
54
54
  csv = "Version\t#{devices.join("\t")}\n"
55
55
  app_sizes.each do |details|
56
56
  build_number = details['cfBundleVersion']
57
- sizes = details['sizesInBytes'].select { |name, _| devices.include?(name) }
57
+ sizes = details['sizesInBytes'].slice(*devices)
58
58
  csv += "#{build_number}\t" + devices.map { |d| sz(sizes[d]['compressed']) }.join("\t") + "\n"
59
59
  end
60
60
  csv
@@ -64,7 +64,7 @@ module Fastlane
64
64
  devices = DEFAULT_DEVICES if devices.nil? || devices.empty?
65
65
  app_sizes.map do |details|
66
66
  build_number = details['cfBundleVersion']
67
- sizes = details['sizesInBytes'].select { |name, _| devices.include?(name) }
67
+ sizes = details['sizesInBytes'].slice(*devices)
68
68
  col_size = devices.map(&:length).max
69
69
  table = "| #{build_number.ljust(col_size)} | Download | Install |\n"
70
70
  table += "|:#{'-' * col_size}-|---------:|---------:|\n"
@@ -65,10 +65,10 @@ module Fastlane
65
65
  # Inspects the given `.strings` file for duplicated keys, returning them if any.
66
66
  #
67
67
  # @param [String] file The path to the file to inspect.
68
- # @return [Hash<String, Array<Int>] Hash with the dublipcated keys.
68
+ # @return [Hash<String, Array<Int>] Hash with the duplicated keys.
69
69
  # Each element has the duplicated key (from the `.strings`) as key and an array of line numbers where the key occurs as value.
70
70
  def self.find_duplicated_keys(file:)
71
- keys_with_lines = Hash.new([])
71
+ keys_with_lines = Hash.new { |h, k| h[k] = [] }
72
72
 
73
73
  state = State.new(context: :root, buffer: StringIO.new, in_escaped_ctx: false, found_key: nil)
74
74
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Fastlane
4
4
  module Wpmreleasetoolkit
5
- VERSION = '12.5.0'
5
+ VERSION = '13.1.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane-plugin-wpmreleasetoolkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 12.5.0
4
+ version: 13.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Automattic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-02-19 00:00:00.000000000 Z
11
+ date: 2025-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -431,6 +431,7 @@ files:
431
431
  - lib/fastlane/plugin/wpmreleasetoolkit/actions/common/set_branch_protection_action.rb
432
432
  - lib/fastlane/plugin/wpmreleasetoolkit/actions/common/set_milestone_frozen_marker_action.rb
433
433
  - lib/fastlane/plugin/wpmreleasetoolkit/actions/common/update_assigned_milestone_action.rb
434
+ - lib/fastlane/plugin/wpmreleasetoolkit/actions/common/upload_build_to_apps_cdn.rb
434
435
  - lib/fastlane/plugin/wpmreleasetoolkit/actions/common/upload_to_s3.rb
435
436
  - lib/fastlane/plugin/wpmreleasetoolkit/actions/configure/configure_add_files_to_copy_action.rb
436
437
  - lib/fastlane/plugin/wpmreleasetoolkit/actions/configure/configure_apply_action.rb
@@ -456,6 +457,7 @@ files:
456
457
  - lib/fastlane/plugin/wpmreleasetoolkit/helper/android/android_tools_path_helper.rb
457
458
  - lib/fastlane/plugin/wpmreleasetoolkit/helper/android/android_version_helper.rb
458
459
  - lib/fastlane/plugin/wpmreleasetoolkit/helper/app_size_metrics_helper.rb
460
+ - lib/fastlane/plugin/wpmreleasetoolkit/helper/buildkite_aware_log_groups.rb
459
461
  - lib/fastlane/plugin/wpmreleasetoolkit/helper/ci_helper.rb
460
462
  - lib/fastlane/plugin/wpmreleasetoolkit/helper/configure_helper.rb
461
463
  - lib/fastlane/plugin/wpmreleasetoolkit/helper/encryption_helper.rb