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 +4 -4
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/create_release_backmerge_pull_request_action.rb +65 -46
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/prototype_build_details_comment_action.rb +141 -108
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/upload_build_to_apps_cdn.rb +320 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/android/android_localize_helper.rb +4 -1
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/buildkite_aware_log_groups.rb +45 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_adc_app_sizes_helper.rb +2 -2
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_strings_file_validation_helper.rb +2 -2
- data/lib/fastlane/plugin/wpmreleasetoolkit/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 812b207092ef802b165390e74ba296566e70a3e406c7e1a50572a6bd7b6da761
|
4
|
+
data.tar.gz: 9266eec38e720af58e54413e649b2451e0f88100f4621bfab25daa05912663ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
FastlaneCore::ConfigItem.new(
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
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
|
|
data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/prototype_build_details_comment_action.rb
CHANGED
@@ -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
|
-
|
9
|
-
|
11
|
+
download_url = params[:download_url]
|
12
|
+
release_info = FirebaseReleaseInfo.from_lane_context
|
10
13
|
|
11
|
-
|
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
|
-
|
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
|
-
|
18
|
-
footnote
|
19
|
-
|
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>#{
|
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 `
|
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
|
-
|
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
|
-
#
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
63
|
-
|
64
|
-
ctx[
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
#
|
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 [
|
78
|
-
# @param [String] download_url The `download_url` parameter passed to the action, if one
|
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.
|
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
|
-
|
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
|
-
|
91
|
-
|
92
|
-
|
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
|
-
#
|
163
|
+
# Determines if a given URI is a Firebase App Distribution URL
|
100
164
|
#
|
101
|
-
# @param [
|
102
|
-
# @
|
103
|
-
# @
|
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.
|
106
|
-
|
107
|
-
|
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
|
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
|
-
|
130
|
-
url_or_emoji
|
188
|
+
else
|
189
|
+
url_or_emoji.tap { parse_url!(url_or_emoji) }
|
131
190
|
end
|
132
|
-
app_icon_url ? "<img
|
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 `
|
149
|
-
|
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
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 #
|
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
|
-
|
210
|
-
|
211
|
-
|
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 `
|
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 `
|
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 `
|
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
|
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
|
-
|
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'].
|
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'].
|
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
|
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
|
|
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:
|
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-
|
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
|