fastlane-plugin-wpmreleasetoolkit 11.0.2 → 11.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: 1a01dbbf909c6d6ae5a683e4d4261e44ef7dda1f9b896e0f49529de45ffb8c58
4
- data.tar.gz: 5b6343174100d8167d85b7a31fe2484988877eb249d4ee527469fc025d201a26
3
+ metadata.gz: 1f9d86b44058a8ff2f28f2c56bdb81230395e36eac41f6e4d0761140f9694177
4
+ data.tar.gz: cc234c6029919a63d4d9ed2114d390bb58ad79e67f7ae1650e94b23cfac92bc9
5
5
  SHA512:
6
- metadata.gz: 10035929825ca4d666f3a75d184feba49720233b0a57a0e00ac7f9cf92097f332f2554a3e9f811ebdb5dda83a9d6b750dc22568bf3cddd992509f12ca551ee2b
7
- data.tar.gz: 0a67262b02fbf58244a5c779a6e517a27aa869c2e872c10f298a4c2621837e46206a78e4ec4c292df5f2086eb02054ea86470062435c7683f44a1a97bf278aec
6
+ metadata.gz: e2e86d40c624300445ddc600e19b8b16a560e86725c82344719bbbadb69ad0c1552fff4f951cdb5411d892973fa37b524b47bcc99b0d14a23bff56607d44f676
7
+ data.tar.gz: faf08e93483cced02d83d705b9cff2843e07244c708312bdf8dfb86131b2990d4e5ba94397aa71aadd8a3cb6d485da2007b5743950e15486ff81243f8a956b35
@@ -0,0 +1,215 @@
1
+ require 'fastlane/action'
2
+ require_relative '../../helper/github_helper'
3
+
4
+ module Fastlane
5
+ module Actions
6
+ class CreateReleaseBackmergePullRequestAction < Action
7
+ DEFAULT_BRANCH = 'trunk'.freeze
8
+
9
+ def self.run(params)
10
+ token = params[:github_token]
11
+ repository = params[:repository]
12
+ source_branch = params[:source_branch]
13
+ default_branch = params[:default_branch]
14
+ target_branches = params[:target_branches]
15
+ labels = params[:labels]
16
+ milestone_title = params[:milestone_title]
17
+ reviewers = params[:reviewers]
18
+ team_reviewers = params[:team_reviewers]
19
+ intermediate_branch_created_callback = params[:intermediate_branch_created_callback]
20
+
21
+ if target_branches.include?(source_branch)
22
+ UI.user_error!('`target_branches` must not contain `source_branch`')
23
+ end
24
+
25
+ github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token])
26
+ target_milestone = milestone_title.nil? ? nil : github_helper.get_milestone(repository, milestone_title)
27
+
28
+ final_target_branches = if target_branches.empty?
29
+ unless source_branch.start_with?('release/')
30
+ UI.user_error!('`source_branch` must start with `release/`')
31
+ end
32
+
33
+ determine_target_branches(source_release_version: source_branch.delete('release/'), default_branch: default_branch)
34
+ else
35
+ target_branches
36
+ end
37
+
38
+ final_target_branches.map do |target_branch|
39
+ Fastlane::Helper::GitHelper.checkout_and_pull(source_branch)
40
+
41
+ create_backmerge_pr(
42
+ token: token,
43
+ repository: repository,
44
+ title: "Merge #{source_branch} into #{target_branch}",
45
+ head_branch: source_branch,
46
+ base_branch: target_branch,
47
+ labels: labels,
48
+ milestone: target_milestone&.number,
49
+ reviewers: reviewers,
50
+ team_reviewers: team_reviewers,
51
+ intermediate_branch_created_callback: intermediate_branch_created_callback
52
+ )
53
+ end.compact
54
+ end
55
+
56
+ # Determines the target branches for a release version.
57
+ #
58
+ # @param source_release_version [String] the source release version to compare against other release branches.
59
+ # @param default_branch [String] the default branch to use if no target branches are found.
60
+ # @return [Array<String>] the list of target branches greater than the release version.
61
+ def self.determine_target_branches(source_release_version:, default_branch:)
62
+ release_branches = Actions.sh('git', 'branch', '-r', '-l', 'origin/release/*').strip.split("\n")
63
+
64
+ all_release_branches_versions = release_branches
65
+ .map { |branch| branch.match(%r{origin/release/([0-9.]*)})&.captures&.first }
66
+ .compact
67
+
68
+ target_branches = all_release_branches_versions.select { |branch| Gem::Version.new(branch) > Gem::Version.new(source_release_version) }
69
+ .map { |v| "release/#{v}" }
70
+ target_branches = [default_branch] if target_branches.empty?
71
+
72
+ target_branches
73
+ end
74
+
75
+ # Creates a backmerge pull request using the `create_pull_request` Fastlane Action.
76
+ #
77
+ # @param token [String] the GitHub token for authentication.
78
+ # @param repository [String] the repository where the pull request will be created.
79
+ # @param title [String] the title of the pull request.
80
+ # @param head_branch [String] the source branch for the pull request.
81
+ # @param base_branch [String] the target branch for the pull request.
82
+ # @param labels [Array<String>] the labels to add to the pull request.
83
+ # @param milestone [String] the milestone to associate with the pull request.
84
+ # @param reviewers [Array<String>] the individual reviewers for the pull request.
85
+ # @param team_reviewers [Array<String>] the team reviewers for the pull request.
86
+ # @param intermediate_branch_created_callback [Proc] A callback to call after having created the intermediate branch
87
+ # to allow the caller to e.g. add new commits on it before the PR is created. The callback takes two parameters: the base branch and the intermediate branch
88
+ #
89
+ # @return [String] The URL of the created Pull Request, or `nil` if no PR was created.
90
+ #
91
+ def self.create_backmerge_pr(token:, repository:, title:, head_branch:, base_branch:, labels:, milestone:, reviewers:, team_reviewers:, intermediate_branch_created_callback:)
92
+ intermediate_branch = "merge/#{head_branch.gsub('/', '-')}-into-#{base_branch.gsub('/', '-')}"
93
+
94
+ if Fastlane::Helper::GitHelper.branch_exists_on_remote?(branch_name: intermediate_branch)
95
+ UI.user_error!("The intermediate branch `#{intermediate_branch}` already exists. Please check if there is an existing Pull Request that needs to be merged or closed first, or delete the branch.")
96
+ return nil
97
+ end
98
+
99
+ Fastlane::Helper::GitHelper.create_branch(intermediate_branch)
100
+
101
+ intermediate_branch_created_callback&.call(base_branch, intermediate_branch)
102
+
103
+ # if there's a callback, make sure it didn't switch branches
104
+ other_action.ensure_git_branch(branch: "^#{intermediate_branch}/") unless intermediate_branch_created_callback.nil?
105
+
106
+ if Fastlane::Helper::GitHelper.point_to_same_commit?(base_branch, head_branch)
107
+ UI.error("No differences between #{head_branch} and #{base_branch}. Skipping PR creation.")
108
+ return nil
109
+ end
110
+
111
+ other_action.push_to_git_remote(tags: false)
112
+
113
+ pr_body = <<~BODY
114
+ Merging `#{head_branch}` into `#{base_branch}`.
115
+
116
+ Via intermediate branch `#{intermediate_branch}`, to help fix conflicts if any:
117
+ ```
118
+ #{head_branch.rjust(40)} ----o-- - - -
119
+ #{' ' * 40} \\
120
+ #{intermediate_branch.rjust(40)} `---.
121
+ #{' ' * 40} \\
122
+ #{base_branch.rjust(40)} ------------x- - -
123
+ ```
124
+ BODY
125
+
126
+ other_action.create_pull_request(
127
+ api_token: token,
128
+ repo: repository,
129
+ title: title,
130
+ body: pr_body,
131
+ head: intermediate_branch,
132
+ base: base_branch,
133
+ labels: labels,
134
+ milestone: milestone,
135
+ reviewers: reviewers,
136
+ team_reviewers: team_reviewers
137
+ )
138
+ end
139
+
140
+ def self.description
141
+ 'Creates backmerge PRs for a release branch into target branches'
142
+ end
143
+
144
+ def self.authors
145
+ ['Automattic']
146
+ end
147
+
148
+ def self.return_type
149
+ :array_of_strings
150
+ end
151
+
152
+ def self.return_value
153
+ 'The list of the created backmerge Pull Request URLs'
154
+ end
155
+
156
+ def self.details
157
+ <<~DETAILS
158
+ This action creates backmerge Pull Requests from a release branch into one or more target branches.
159
+
160
+ It can be used to ensure that changes from a release branch are merged back into other branches, such as newer release branches or the main development branch (e.g., `trunk`).
161
+ DETAILS
162
+ end
163
+
164
+ def self.available_options
165
+ [
166
+ FastlaneCore::ConfigItem.new(key: :repository,
167
+ env_name: 'GHHELPER_REPOSITORY',
168
+ description: 'The remote path of the GH repository on which we work',
169
+ optional: false,
170
+ type: String),
171
+ FastlaneCore::ConfigItem.new(key: :source_branch,
172
+ description: 'The source branch to create a backmerge PR from, in the format `release/x.y.z`',
173
+ optional: false,
174
+ type: String),
175
+ FastlaneCore::ConfigItem.new(key: :default_branch,
176
+ description: 'The default branch to target if no newer release branches exist',
177
+ optional: true,
178
+ default_value: DEFAULT_BRANCH,
179
+ type: String),
180
+ FastlaneCore::ConfigItem.new(key: :target_branches,
181
+ 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
182
+ optional: true,
183
+ default_value: [],
184
+ type: Array),
185
+ FastlaneCore::ConfigItem.new(key: :labels,
186
+ description: 'The labels that should be assigned to the backmerge PRs',
187
+ optional: true,
188
+ default_value: [],
189
+ type: Array),
190
+ FastlaneCore::ConfigItem.new(key: :milestone_title,
191
+ description: 'The title of the milestone to assign to the created PRs',
192
+ optional: true,
193
+ type: String),
194
+ FastlaneCore::ConfigItem.new(key: :reviewers,
195
+ description: 'An array of GitHub users that will be assigned to the pull request',
196
+ optional: true,
197
+ type: Array),
198
+ FastlaneCore::ConfigItem.new(key: :team_reviewers,
199
+ description: 'An array of GitHub team slugs that will be assigned to the pull request',
200
+ optional: true,
201
+ type: Array),
202
+ FastlaneCore::ConfigItem.new(key: :intermediate_branch_created_callback,
203
+ description: 'Callback to allow for the caller to perform operations on the intermediate branch before pushing. The call back receives two parameters: the base (target) branch for the PR and the intermediate branch name',
204
+ optional: true,
205
+ type: Proc),
206
+ Fastlane::Helper::GithubHelper.github_token_config_item,
207
+ ]
208
+ end
209
+
210
+ def self.is_supported?(platform)
211
+ true
212
+ end
213
+ end
214
+ end
215
+ end
@@ -152,6 +152,13 @@ module Fastlane
152
152
  (added_count + updated_count) != 0
153
153
  end
154
154
 
155
+ ########
156
+ # @!group Verify diff of library vs main strings matches
157
+ #
158
+ # @note This set of methods is used by `an_validate_lib_strings_action`
159
+ # (which doesn't seem to be used by any of our Android projects nowadays?)
160
+ ########
161
+
155
162
  def self.verify_diff(diff_string, main_strings, lib_strings, library)
156
163
  return unless diff_string.start_with?('name=')
157
164
 
@@ -198,6 +205,8 @@ module Fastlane
198
205
  end
199
206
  end
200
207
 
208
+ # @!endgroup
209
+
201
210
  ########
202
211
  # @!group Downloading translations from GlotPress
203
212
  ########
@@ -223,7 +232,7 @@ module Fastlane
223
232
  #
224
233
  # @param [String] res_dir The relative path to the `…/src/main/res` directory.
225
234
  # @param [String] glotpress_project_url The base URL to the glotpress project to download the strings from.
226
- # @param [Hash{String=>String}, Array] glotpress_filters
235
+ # @param [Hash{Symbol=>String}, Array] glotpress_filters
227
236
  # The filters to apply when exporting strings from GlotPress.
228
237
  # Typical examples include `{ status: 'current' }` or `{ status: 'review' }`.
229
238
  # If an array of Hashes is provided instead of a single Hash, this method will perform as many
@@ -235,10 +244,8 @@ module Fastlane
235
244
  def self.download_from_glotpress(res_dir:, glotpress_project_url:, locales_map:, glotpress_filters: { status: 'current' })
236
245
  glotpress_filters = [glotpress_filters] unless glotpress_filters.is_a?(Array)
237
246
 
238
- attributes_to_copy = %w[formatted] # Attributes that we want to replicate into translated `string.xml` files
239
247
  orig_file = File.join(res_dir, 'values', 'strings.xml')
240
248
  orig_xml = File.open(orig_file) { |f| Nokogiri::XML(f, nil, Encoding::UTF_8.to_s) }
241
- orig_attributes = orig_xml.xpath('//string').to_h { |tag| [tag['name'], tag.attributes.select { |k, _| attributes_to_copy.include?(k) }] }
242
249
 
243
250
  locales_map.each do |lang_codes|
244
251
  all_xml_documents = glotpress_filters.map do |filters|
@@ -250,13 +257,7 @@ module Fastlane
250
257
  # Merge all XMLs together
251
258
  merged_xml = merge_xml_documents(all_xml_documents)
252
259
 
253
- # Process XML (text substitutions, replicate attributes, quick-lint string)
254
- merged_xml.xpath('//string').each do |string_tag|
255
- apply_substitutions(string_tag)
256
- orig_attributes[string_tag['name']]&.each { |k, v| string_tag[k] = v }
257
- quick_lint(string_tag, lang_codes[:android])
258
- end
259
- merged_xml.xpath('//string-array/item').each { |item_tag| apply_substitutions(item_tag) }
260
+ post_process_xml!(merged_xml, locale_code: lang_codes[:android], original_xml: orig_xml)
260
261
 
261
262
  # Save
262
263
  lang_dir = File.join(res_dir, "values-#{lang_codes[:android]}")
@@ -266,8 +267,10 @@ module Fastlane
266
267
  end
267
268
  end
268
269
 
270
+ # @!endgroup
271
+
269
272
  #####################
270
- # Private Helpers
273
+ # @!group Private Helpers
271
274
  #####################
272
275
 
273
276
  # Downloads the export from GlotPress for a given locale and given filters
@@ -276,7 +279,7 @@ module Fastlane
276
279
  # @param [String] locale The GlotPress locale code to download strings for.
277
280
  # @param [Hash{Symbol=>String}] filters The hash of filters to apply when exporting from GlotPress.
278
281
  # Typical examples include `{ status: 'current' }` or `{ status: 'review' }`.
279
- # @return [Nokogiri::XML] the download XML document, parsed as a Nokogiri::XML object
282
+ # @return [Nokogiri::XML::Document] the download XML document, parsed as a Nokogiri::XML object
280
283
  #
281
284
  def self.download_glotpress_export_file(project_url:, locale:, filters:)
282
285
  query_params = filters.transform_keys { |k| "filters[#{k}]" }.merge(format: 'android')
@@ -297,6 +300,9 @@ module Fastlane
297
300
 
298
301
  # Merge multiple Nokogiri::XML `strings.xml` documents together
299
302
  #
303
+ # Used especially when we provided multiple GlotPress filters to `download_from_glotpress`,
304
+ # as in this case we'd trigger one export per filter, then merge the result in a single XML
305
+ #
300
306
  # @param [Array<Nokogiri::XML::Document>] all_xmls Array of the Nokogiri XML documents to merge together
301
307
  # @return [Nokogiri::XML::Document] The merged document.
302
308
  #
@@ -323,11 +329,53 @@ module Fastlane
323
329
  end
324
330
  private_class_method :merge_xml_documents
325
331
 
326
- # Apply some common text substitutions to tag contents
332
+ # Process a downloaded XML (in-place), to apply the following
333
+ # - replicate attributes from the nodes of the original XML (`translatable`, `tools:ignore`, …) to the translated XML
334
+ # - text substitutions for common special characters
335
+ # - quick-lint string by searching for common issue patterns (using `%%` in a `formatted=false` string, etc)
336
+ #
337
+ # @param [Nokogiri::XML::Document] translated_xml The downloaded XML to post-process
338
+ # @param [String] locale_code The android locale code associated with the translated_xml
339
+ # @param [Nokogiri::XML::Document] original_xml The original `values/strings.xml` to use as reference
340
+ #
341
+ def self.post_process_xml!(translated_xml, locale_code:, original_xml:)
342
+ copy_orig_attributes = lambda do |node, xpath|
343
+ orig_attributes = original_xml.xpath(xpath)&.first&.attribute_nodes&.to_h do |attr|
344
+ [[attr.namespace&.prefix, attr.name].compact.join(':'), attr.value]
345
+ end
346
+ orig_attributes&.each { |k, v| node[k] = v unless k == 'name' }
347
+ end
348
+
349
+ # 1. Replicate namespaces on the document (especially `xmlns:tools` if present)
350
+ original_xml.namespaces.each { |k, v| translated_xml.root&.add_namespace(k.delete_prefix('xmlns:'), v) }
351
+ # 2. Replicate attributes on any node with `@name` attribute (`string`, `string-array`, `plurals`)
352
+ translated_xml.xpath('//*[@name]').each do |node|
353
+ copy_orig_attributes.call(node, "//#{node.name}[@name = '#{node['name']}']")
354
+ end
355
+ # 3. Process copies for `string` nodes
356
+ translated_xml.xpath('//string[@name]').each do |string_node|
357
+ apply_substitutions!(string_node)
358
+ quick_lint(string_node, locale_code)
359
+ end
360
+ # 4. Process copies for `string-array/item` nodes
361
+ translated_xml.xpath('//string-array[@name]/item').each do |item_node|
362
+ apply_substitutions!(item_node)
363
+ quick_lint(item_node, locale_code)
364
+ end
365
+ # 5. Replicate attributes + Process copies for `plurals/item` nodes
366
+ translated_xml.xpath('//plurals[@name]/item[@quantity]').each do |item_node|
367
+ copy_orig_attributes.call(item_node, "//plurals[@name = '#{item_node.parent['name']}']/item[@quantity = '#{item_node['quantity']}']")
368
+ apply_substitutions!(item_node)
369
+ quick_lint(item_node, locale_code)
370
+ end
371
+ end
372
+ private_class_method :post_process_xml!
373
+
374
+ # Apply some common text substitutions to tag contents, like `... => …` or en-dash instead of regular dash for ranges of numbers
327
375
  #
328
376
  # @param [Nokogiri::XML::Node] tag The XML tag/node to apply substitutions to
329
377
  #
330
- def self.apply_substitutions(tag)
378
+ def self.apply_substitutions!(tag)
331
379
  tag.content = tag.content.gsub('...', '…')
332
380
 
333
381
  # Typography en-dash
@@ -339,17 +387,28 @@ module Fastlane
339
387
  is_negative_number ? str : "#{match[1]}\u{2013}#{match[2]}"
340
388
  end
341
389
  end
342
- private_class_method :apply_substitutions
390
+ private_class_method :apply_substitutions!
343
391
 
344
- # Perform some quick basic checks about an individual `<string>` tag and print warnings accordingly
392
+ # Perform some quick basic checks about an individual `<string>` tag and print warnings accordingly:
393
+ # - detect the use of `%%` in the string even if `formatted=false` is set
394
+ # - detect the presence of `\@string/` in translated XML, which suggests the original key that referenced `@string/…` did not set `translatable=false`
395
+ # and thus that `@string/…` copy was sent to GlotPress for translation, then escaped during exporting it back.
345
396
  #
346
- # @param [Nokogiri::XML::Node] string_tag The XML tag/node to check
397
+ # @param [Nokogiri::XML::Node] node The XML tag/node to check the content of
347
398
  # @param [String] lang The language we are currently processing. Used for providing context during logging / warning message
348
399
  #
349
- def self.quick_lint(string_tag, lang)
350
- return unless string_tag['formatted'] == 'false' && string_tag.content.include?('%%')
351
-
352
- UI.important "Warning: [#{lang}] translation for '#{string_tag['name']}' has attribute formatted=false, but still contains escaped '%%' in translation."
400
+ def self.quick_lint(node, lang)
401
+ named_node = node.has_attribute?('name') ? node : node.parent
402
+ if named_node['formatted'] == 'false' && node.content.include?('%%')
403
+ UI.important "Warning: [#{lang}] translation for '#{named_node['name']}' has attribute formatted=false, but still contains escaped '%%' in translation."
404
+ end
405
+ # rubocop:disable Style/GuardClause
406
+ if node.content.include?('\\@string/')
407
+ UI.important "Warning: [#{lang}] exported translation for '#{named_node['name']}' contains `\\@string/`. This is a sign that this entry was not marked as `translatable=false` " \
408
+ + 'in the original `values/strings.xml`, and was thus sent to GlotPress, which added the backslash when exporting it back.'
409
+ node.content = node.content.gsub('\\@string/', '@string/')
410
+ end
411
+ # rubocop:enable Style/GuardClause
353
412
  end
354
413
  private_class_method :quick_lint
355
414
 
@@ -179,6 +179,21 @@ module Fastlane
179
179
  Action.sh('git', 'fetch', '--tags')
180
180
  end
181
181
 
182
+ # Checks if two git references point to the same commit.
183
+ #
184
+ # @param ref1 [String] the first git reference to check.
185
+ # @param ref2 [String] the second git reference to check.
186
+ #
187
+ # @return [Boolean] true if the two references point to the same commit, false otherwise.
188
+ #
189
+ def self.point_to_same_commit?(ref1, ref2)
190
+ git_repo = Git.open(Dir.pwd)
191
+ ref1_commit = git_repo.gcommit(ref1)
192
+ ref2_commit = git_repo.gcommit(ref2)
193
+
194
+ ref1_commit.sha == ref2_commit.sha
195
+ end
196
+
182
197
  # Returns the current git branch, or "HEAD" if it's not checked out to any branch
183
198
  # Can NOT be replaced using the environment variables such as `GIT_BRANCH` or `BUILDKITE_BRANCH`
184
199
  #
@@ -206,6 +221,17 @@ module Fastlane
206
221
  !Action.sh('git', 'branch', '--list', branch_name).empty?
207
222
  end
208
223
 
224
+ # Checks if a branch exists on the repository's remote.
225
+ #
226
+ # @param branch_name [String] the name of the branch to check.
227
+ # @param remote_name [String] the name of the remote repository (default is 'origin').
228
+ #
229
+ # @return [Boolean] true if the branch exists on remote, false otherwise.
230
+ #
231
+ def self.branch_exists_on_remote?(branch_name:, remote_name: 'origin')
232
+ !Action.sh('git', 'ls-remote', '--heads', remote_name, branch_name).empty?
233
+ end
234
+
209
235
  # Ensure that we are on the expected branch, and abort if not.
210
236
  #
211
237
  # @param [String] branch_name The name of the branch we expect to be on
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Fastlane
4
4
  module Wpmreleasetoolkit
5
- VERSION = '11.0.2'
5
+ VERSION = '11.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: 11.0.2
4
+ version: 11.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: 2024-06-04 00:00:00.000000000 Z
11
+ date: 2024-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -449,6 +449,7 @@ files:
449
449
  - lib/fastlane/plugin/wpmreleasetoolkit/actions/common/copy_branch_protection_action.rb
450
450
  - lib/fastlane/plugin/wpmreleasetoolkit/actions/common/create_new_milestone_action.rb
451
451
  - lib/fastlane/plugin/wpmreleasetoolkit/actions/common/create_release_action.rb
452
+ - lib/fastlane/plugin/wpmreleasetoolkit/actions/common/create_release_backmerge_pull_request_action.rb
452
453
  - lib/fastlane/plugin/wpmreleasetoolkit/actions/common/extract_release_notes_for_version_action.rb
453
454
  - lib/fastlane/plugin/wpmreleasetoolkit/actions/common/find_previous_tag.rb
454
455
  - lib/fastlane/plugin/wpmreleasetoolkit/actions/common/firebase_login.rb