fastlane-plugin-wpmreleasetoolkit 5.6.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd2b56e6aa5cf05e5e464b66e8b136e05048c7cdadee95b43bb152ab6d451e9f
4
- data.tar.gz: da440f83d0f87a6d2dd6c74684a149d381f157d0f0ce40157c12cd237ebed373
3
+ metadata.gz: c16be217cbd668e4fbd9da107edcdba4b5be149f0d64d4dadb6a0d5697cde006
4
+ data.tar.gz: e736842ca7df0e1ff50fc0a7114e39555570c03c8e58b41fa265999f267f0ec2
5
5
  SHA512:
6
- metadata.gz: e684c1a705d0fab1f9d3ce82f7feed96be8a4aa09a954a19db1a564c4b958654668a6d2b9c23ee41bdcfd39ec68104f3e44e877a8df6d2bb5773efbe706c1730
7
- data.tar.gz: 9fb910e098bbcc54a8753aebb1c2776e0c6e889d20e042f2848a38a5e3ebbeb95ae7b8def13d4efa0fa0e06c050e24e3d80633642a4281e3ba194e55c148427b
6
+ metadata.gz: 739938930d2cfd9238590f6b58b7031923f5f6b325c53246058f77dc47c7e302180f2d088b8c764106d50e45f7bca265d915a3d671bbf09c6a45e9309fcb1e3e
7
+ data.tar.gz: 0741aeff083cab3060b8b4f46259ad091217eef34fd013370fca26289b5c2279c325632dd7935699772e293e81e87ffcbd71bc7b8905da5fffbb4e04543f61e5
@@ -9,7 +9,8 @@ module Fastlane
9
9
  UI.user_error!("Can't find any reference for key #{params[:import_key]}") if version.nil?
10
10
  UI.message "Downloading #{params[:file_path]} from #{params[:repository]} at version #{version} to #{params[:download_folder]}"
11
11
 
12
- Fastlane::Helper::GithubHelper.download_file_from_tag(
12
+ github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token])
13
+ github_helper.download_file_from_tag(
13
14
  repository: params[:repository],
14
15
  tag: "#{params[:github_release_prefix]}#{version}",
15
16
  file_path: params[:file_path],
@@ -57,6 +58,7 @@ module Fastlane
57
58
  description: 'The prefix which is used in the GitHub release title',
58
59
  type: String,
59
60
  optional: true),
61
+ Fastlane::Helper::GithubHelper.github_token_config_item,
60
62
  ]
61
63
  end
62
64
 
@@ -10,10 +10,12 @@ module Fastlane
10
10
  repository = params[:repository]
11
11
  milestone_title = params[:milestone]
12
12
 
13
- milestone = Fastlane::Helper::GithubHelper.get_milestone(repository, milestone_title)
13
+ github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token])
14
+ milestone = github_helper.get_milestone(repository, milestone_title)
15
+
14
16
  UI.user_error!("Milestone #{milestone_title} not found.") if milestone.nil?
15
17
 
16
- Fastlane::Helper::GithubHelper.github_client().update_milestone(repository, milestone[:number], state: 'closed')
18
+ github_helper.update_milestone(repository: repository, number: milestone[:number], state: 'closed')
17
19
  end
18
20
 
19
21
  def self.description
@@ -45,6 +47,7 @@ module Fastlane
45
47
  description: 'The GitHub milestone',
46
48
  optional: false,
47
49
  type: String),
50
+ Fastlane::Helper::GithubHelper.github_token_config_item,
48
51
  ]
49
52
  end
50
53
 
@@ -10,7 +10,8 @@ module Fastlane
10
10
  def self.run(params)
11
11
  require_relative '../../helper/github_helper'
12
12
 
13
- reuse_identifier = Fastlane::Helper::GithubHelper.comment_on_pr(
13
+ github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token])
14
+ reuse_identifier = github_helper.comment_on_pr(
14
15
  project_slug: params[:project],
15
16
  pr_number: params[:pr_number],
16
17
  body: params[:body],
@@ -41,12 +42,7 @@ module Fastlane
41
42
 
42
43
  def self.available_options
43
44
  [
44
- FastlaneCore::ConfigItem.new(
45
- key: :access_token,
46
- env_name: 'GITHUB_TOKEN',
47
- description: 'The GitHub token to use for posting the comment',
48
- type: String
49
- ),
45
+ Fastlane::Helper::GithubHelper.github_token_config_item,
50
46
  FastlaneCore::ConfigItem.new(
51
47
  key: :reuse_identifier,
52
48
  description: 'If provided, the reuse identifier can identify an existing comment to overwrite',
@@ -9,15 +9,29 @@ module Fastlane
9
9
  def self.run(params)
10
10
  repository = params[:repository]
11
11
 
12
- last_stone = Fastlane::Helper::GithubHelper.get_last_milestone(repository)
12
+ github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token])
13
+ last_stone = github_helper.get_last_milestone(repository)
14
+
13
15
  UI.message("Last detected milestone: #{last_stone[:title]} due on #{last_stone[:due_on]}.")
16
+
14
17
  milestone_duedate = last_stone[:due_on]
15
18
  milestone_duration = params[:milestone_duration]
16
19
  newmilestone_duedate = (milestone_duedate.to_datetime.next_day(milestone_duration).to_time).utc
17
20
  newmilestone_number = Fastlane::Helper::Ios::VersionHelper.calc_next_release_version(last_stone[:title])
18
21
  number_of_days_from_code_freeze_to_release = params[:number_of_days_from_code_freeze_to_release]
22
+ # Because of the app stores review process, we submit the binary 3 days before the intended release date.
23
+ # Using 3 days is mostly for historical reasons, for a long time, we've been submitting apps on Friday and releasing them on Monday.
24
+ days_until_submission = params[:need_appstore_submission] ? (number_of_days_from_code_freeze_to_release - 3) : milestone_duration
25
+
19
26
  UI.message("Next milestone: #{newmilestone_number} due on #{newmilestone_duedate}.")
20
- Fastlane::Helper::GithubHelper.create_milestone(repository, newmilestone_number, newmilestone_duedate, milestone_duration, number_of_days_from_code_freeze_to_release, params[:need_appstore_submission])
27
+
28
+ github_helper.create_milestone(
29
+ repository: repository,
30
+ title: newmilestone_number,
31
+ due_date: newmilestone_duedate,
32
+ days_until_submission: days_until_submission,
33
+ days_until_release: number_of_days_from_code_freeze_to_release
34
+ )
21
35
  end
22
36
 
23
37
  def self.description
@@ -62,6 +76,7 @@ module Fastlane
62
76
  optional: true,
63
77
  is_string: false,
64
78
  default_value: 14),
79
+ Fastlane::Helper::GithubHelper.github_token_config_item,
65
80
  ]
66
81
  end
67
82
 
@@ -21,7 +21,8 @@ module Fastlane
21
21
  UI.user_error!("Can't find file #{file_path}!") unless File.exist?(file_path)
22
22
  end
23
23
 
24
- Fastlane::Helper::GithubHelper.create_release(
24
+ github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token])
25
+ github_helper.create_release(
25
26
  repository: repository,
26
27
  version: version,
27
28
  target: params[:target],
@@ -82,6 +83,7 @@ module Fastlane
82
83
  optional: true,
83
84
  default_value: false,
84
85
  is_string: false),
86
+ Fastlane::Helper::GithubHelper.github_token_config_item,
85
87
  ]
86
88
  end
87
89
 
@@ -10,7 +10,8 @@ module Fastlane
10
10
  milestone = params[:milestone]
11
11
 
12
12
  # Get commit list
13
- pr_list = Fastlane::Helper::GithubHelper.get_prs_for_milestone(repository, milestone)
13
+ github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token])
14
+ pr_list = github_helper.get_prs_for_milestone(repository, milestone)
14
15
 
15
16
  File.open(report_path, 'w') do |file|
16
17
  pr_list.each do |data|
@@ -53,6 +54,7 @@ module Fastlane
53
54
  description: 'The name of the milestone we want to fetch the list of PRs for (e.g.: `16.9`)',
54
55
  optional: false,
55
56
  is_string: true),
57
+ Fastlane::Helper::GithubHelper.github_token_config_item,
56
58
  ]
57
59
  end
58
60
 
@@ -7,14 +7,19 @@ module Fastlane
7
7
  def self.run(params)
8
8
  repository = params[:repository]
9
9
  branch_name = params[:branch]
10
- branch_prot = {}
11
10
 
12
11
  branch_url = "https://api.github.com/repos/#{repository}/branches/#{branch_name}"
13
- branch_prot[:restrictions] = { url: "#{branch_url}/protection/restrictions", users_url: "#{branch_url}/protection/restrictions/users", teams_url: "#{branch_url}/protection/restrictions/teams", users: [], teams: [] }
14
- branch_prot[:enforce_admins] = nil
15
- branch_prot[:required_pull_request_reviews] = { url: "#{branch_url}/protection/required_pull_request_reviews", dismiss_stale_reviews: false, require_code_owner_reviews: false }
16
-
17
- Fastlane::Helper::GithubHelper.github_client().unprotect_branch(repository, branch_name, branch_prot)
12
+ restrictions = { url: "#{branch_url}/protection/restrictions", users_url: "#{branch_url}/protection/restrictions/users", teams_url: "#{branch_url}/protection/restrictions/teams", users: [], teams: [] }
13
+ required_pull_request_reviews = { url: "#{branch_url}/protection/required_pull_request_reviews", dismiss_stale_reviews: false, require_code_owner_reviews: false }
14
+
15
+ github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token])
16
+ github_helper.remove_branch_protection(
17
+ repository: repository,
18
+ branch: branch_name,
19
+ restrictions: restrictions,
20
+ enforce_admins: nil,
21
+ required_pull_request_reviews: required_pull_request_reviews
22
+ )
18
23
  end
19
24
 
20
25
  def self.description
@@ -46,6 +51,7 @@ module Fastlane
46
51
  description: 'The branch to unprotect',
47
52
  optional: false,
48
53
  type: String),
54
+ Fastlane::Helper::GithubHelper.github_token_config_item,
49
55
  ]
50
56
  end
51
57
 
@@ -7,13 +7,19 @@ module Fastlane
7
7
  def self.run(params)
8
8
  repository = params[:repository]
9
9
  branch_name = params[:branch]
10
- branch_prot = {}
11
10
 
12
11
  branch_url = "https://api.github.com/repos/#{repository}/branches/#{branch_name}"
13
- branch_prot[:restrictions] = { url: "#{branch_url}/protection/restrictions", users_url: "#{branch_url}/protection/restrictions/users", teams_url: "#{branch_url}/protection/restrictions/teams", users: [], teams: [] }
14
- branch_prot[:enforce_admins] = nil
15
- branch_prot[:required_pull_request_reviews] = { url: "#{branch_url}/protection/required_pull_request_reviews", dismiss_stale_reviews: false, require_code_owner_reviews: false }
16
- Fastlane::Helper::GithubHelper.github_client().protect_branch(repository, branch_name, branch_prot)
12
+ restrictions = { url: "#{branch_url}/protection/restrictions", users_url: "#{branch_url}/protection/restrictions/users", teams_url: "#{branch_url}/protection/restrictions/teams", users: [], teams: [] }
13
+ required_pull_request_reviews = { url: "#{branch_url}/protection/required_pull_request_reviews", dismiss_stale_reviews: false, require_code_owner_reviews: false }
14
+
15
+ github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token])
16
+ github_helper.set_branch_protection(
17
+ repository: repository,
18
+ branch: branch_name,
19
+ restrictions: restrictions,
20
+ enforce_admins: nil,
21
+ required_pull_request_reviews: required_pull_request_reviews
22
+ )
17
23
  end
18
24
 
19
25
  def self.description
@@ -45,6 +51,7 @@ module Fastlane
45
51
  description: 'The branch to protect',
46
52
  optional: false,
47
53
  type: String),
54
+ Fastlane::Helper::GithubHelper.github_token_config_item,
48
55
  ]
49
56
  end
50
57
 
@@ -9,7 +9,9 @@ module Fastlane
9
9
  milestone_title = params[:milestone]
10
10
  freeze = params[:freeze]
11
11
 
12
- milestone = Fastlane::Helper::GithubHelper.get_milestone(repository, milestone_title)
12
+ github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token])
13
+ milestone = github_helper.get_milestone(repository, milestone_title)
14
+
13
15
  UI.user_error!("Milestone #{milestone_title} not found.") if milestone.nil?
14
16
 
15
17
  mile_title = milestone[:title]
@@ -27,7 +29,7 @@ module Fastlane
27
29
  end
28
30
 
29
31
  UI.message("New milestone: #{mile_title}")
30
- Fastlane::Helper::GithubHelper.github_client().update_milestone(repository, milestone[:number], title: mile_title)
32
+ github_helper.update_milestone(repository: repository, number: milestone[:number], title: mile_title)
31
33
  end
32
34
 
33
35
  def self.is_frozen(milestone)
@@ -70,6 +72,7 @@ module Fastlane
70
72
  optional: false,
71
73
  default_value: true,
72
74
  is_string: false),
75
+ Fastlane::Helper::GithubHelper.github_token_config_item,
73
76
  ]
74
77
  end
75
78
 
@@ -20,7 +20,15 @@ module Fastlane
20
20
  key = [file_name_hash, key].join('/')
21
21
  end
22
22
 
23
- UI.user_error!("File already exists in S3 bucket #{bucket} at #{key}") if file_is_already_uploaded?(bucket, key)
23
+ if file_is_already_uploaded?(bucket, key)
24
+ message = "File already exists in S3 bucket #{bucket} at #{key}"
25
+ if params[:skip_if_exists]
26
+ UI.important("#{message}. Skipping upload.")
27
+ return key
28
+ else
29
+ UI.user_error!(message)
30
+ end
31
+ end
24
32
 
25
33
  UI.message("Uploading #{file_path} to: #{key}")
26
34
 
@@ -101,6 +109,13 @@ module Fastlane
101
109
  default_value: true,
102
110
  type: Boolean
103
111
  ),
112
+ FastlaneCore::ConfigItem.new(
113
+ key: :skip_if_exists,
114
+ description: 'If the file already exists in the S3 bucket, skip the upload (and report it in the logs), instead of failing with `user_error!`',
115
+ optional: true,
116
+ default_value: false,
117
+ type: Boolean
118
+ ),
104
119
  ]
105
120
  end
106
121
 
@@ -2,12 +2,11 @@ module Fastlane
2
2
  module Actions
3
3
  class IosLintLocalizationsAction < Action
4
4
  def self.run(params)
5
- violations = Hash.new([])
5
+ violations = nil
6
6
 
7
7
  loop do
8
- # If we did `violations = self.run...` we'd lose the default value for missing key being `[]` that we set above with `Hash.new`.
9
- # We want that default value so that we can use `+=` when adding the duplicate keys violations below.
10
- violations = violations.merge(self.run_linter(params))
8
+ violations = self.run_linter(params)
9
+ violations.default = [] # Set the default value for when querying a missing key
11
10
 
12
11
  if params[:check_duplicate_keys]
13
12
  find_duplicated_keys(params).each do |language, duplicates|
@@ -49,18 +48,21 @@ module Fastlane
49
48
  def self.find_duplicated_keys(params)
50
49
  duplicate_keys = {}
51
50
 
52
- files_to_lint = Dir.chdir(params[:input_dir]) do
53
- Dir.glob('*.lproj/Localizable.strings').map do |file|
54
- {
55
- language: File.basename(File.dirname(file), '.lproj'),
56
- path: File.join(params[:input_dir], file)
57
- }
58
- end
59
- end
60
-
51
+ files_to_lint = Dir.glob('*.lproj/Localizable.strings', base: params[:input_dir])
61
52
  files_to_lint.each do |file|
62
- duplicates = Fastlane::Helper::Ios::StringsFileValidationHelper.find_duplicated_keys(file: file[:path])
63
- duplicate_keys[file[:language]] = duplicates.map { |key, value| "`#{key}` was found at multiple lines: #{value.join(', ')}" } unless duplicates.empty?
53
+ language = File.basename(File.dirname(file), '.lproj')
54
+ path = File.join(params[:input_dir], file)
55
+
56
+ file_type = Fastlane::Helper::Ios::L10nHelper.strings_file_type(path: path)
57
+ if file_type == :text
58
+ duplicates = Fastlane::Helper::Ios::StringsFileValidationHelper.find_duplicated_keys(file: path)
59
+ duplicate_keys[language] = duplicates.map { |key, value| "`#{key}` was found at multiple lines: #{value.join(', ')}" } unless duplicates.empty?
60
+ else
61
+ UI.important <<~WRONG_FORMAT
62
+ File `#{path}` is in #{file_type} format, while finding duplicate keys only make sense on files that are in ASCII-plist format.
63
+ Since your files are in #{file_type} format, you should probably disable the `check_duplicate_keys` option from this `#{self.action_name}` call.
64
+ WRONG_FORMAT
65
+ end
64
66
  end
65
67
 
66
68
  duplicate_keys
@@ -8,34 +8,25 @@ module Fastlane
8
8
 
9
9
  module Helper
10
10
  class GithubHelper
11
- def self.github_token!
12
- token = [
13
- 'GHHELPER_ACCESS', # For historical reasons / backward compatibility
14
- 'GITHUB_TOKEN', # Used by the `gh` CLI tool
15
- ].map { |key| ENV[key] }
16
- .compact
17
- .first
18
-
19
- token || UI.user_error!('Please provide a GitHub authentication token via the `GITHUB_TOKEN` environment variable')
20
- end
21
-
22
- def self.github_client
23
- @@client ||= begin
24
- client = Octokit::Client.new(access_token: github_token!)
11
+ attr_reader :client
25
12
 
26
- # Fetch the current user
27
- user = client.user
28
- UI.message("Logged in as: #{user.name}")
13
+ # Helper for GitHub Actions
14
+ #
15
+ # @param [String?] github_token GitHub OAuth access token
16
+ #
17
+ def initialize(github_token:)
18
+ @client = Octokit::Client.new(access_token: github_token)
29
19
 
30
- # Auto-paginate to ensure we're not missing data
31
- client.auto_paginate = true
20
+ # Fetch the current user
21
+ user = @client.user
22
+ UI.message("Logged in as: #{user.name}")
32
23
 
33
- client
34
- end
24
+ # Auto-paginate to ensure we're not missing data
25
+ @client.auto_paginate = true
35
26
  end
36
27
 
37
- def self.get_milestone(repository, release)
38
- miles = github_client().list_milestones(repository)
28
+ def get_milestone(repository, release)
29
+ miles = client.list_milestones(repository)
39
30
  mile = nil
40
31
 
41
32
  miles&.each do |mm|
@@ -51,15 +42,15 @@ module Fastlane
51
42
  # @param [String] milestone The name of the milestone we want to fetch the list of PRs for (e.g.: `16.9`)
52
43
  # @return [<Sawyer::Resource>] A list of the PRs for the given milestone, sorted by number
53
44
  #
54
- def self.get_prs_for_milestone(repository, milestone)
55
- github_client.search_issues(%(type:pr milestone:"#{milestone}" repo:#{repository}))[:items].sort_by(&:number)
45
+ def get_prs_for_milestone(repository, milestone)
46
+ client.search_issues(%(type:pr milestone:"#{milestone}" repo:#{repository}))[:items].sort_by(&:number)
56
47
  end
57
48
 
58
- def self.get_last_milestone(repository)
49
+ def get_last_milestone(repository)
59
50
  options = {}
60
51
  options[:state] = 'open'
61
52
 
62
- milestones = github_client().list_milestones(repository, options)
53
+ milestones = client.list_milestones(repository, options)
63
54
  return nil if milestones.nil?
64
55
 
65
56
  last_stone = nil
@@ -80,19 +71,42 @@ module Fastlane
80
71
  last_stone
81
72
  end
82
73
 
83
- def self.create_milestone(repository, newmilestone_number, newmilestone_duedate, newmilestone_duration, number_of_days_from_code_freeze_to_release, need_submission)
84
- # If there is a review process, we want to submit the binary 3 days before its release
85
- #
86
- # Using 3 days is mostly for historical reasons where we release the apps on Monday and submit them on Friday.
87
- days_until_submission = need_submission ? (number_of_days_from_code_freeze_to_release - 3) : newmilestone_duration
88
- submission_date = newmilestone_duedate.to_datetime.next_day(days_until_submission)
89
- release_date = newmilestone_duedate.to_datetime.next_day(number_of_days_from_code_freeze_to_release)
90
- comment = "Code freeze: #{newmilestone_duedate.to_datetime.strftime('%B %d, %Y')} App Store submission: #{submission_date.strftime('%B %d, %Y')} Release: #{release_date.strftime('%B %d, %Y')}"
74
+ # Creates a new milestone
75
+ #
76
+ # @param [String] repository The repository name, including the organization (e.g. `wordpress-mobile/wordpress-ios`)
77
+ # @param [String] title The name of the milestone we want to create (e.g.: `16.9`)
78
+ # @param [Time] due_date Milestone due date—which will also correspond to the code freeze date
79
+ # @param [Integer] days_until_submission Number of days from code freeze to submission to the App Store / Play Store
80
+ # @param [Integer] days_until_release Number of days from code freeze to release
81
+ #
82
+ def create_milestone(repository:, title:, due_date:, days_until_submission:, days_until_release:)
83
+ UI.user_error!('days_until_release must be greater than zero.') unless days_until_release.positive?
84
+ UI.user_error!('days_until_submission must be greater than zero.') unless days_until_submission.positive?
85
+ UI.user_error!('days_until_release must be greater or equal to days_until_submission.') unless days_until_release >= days_until_submission
86
+
87
+ submission_date = due_date.to_datetime.next_day(days_until_submission)
88
+ release_date = due_date.to_datetime.next_day(days_until_release)
89
+ comment = <<~MILESTONE_DESCRIPTION
90
+ Code freeze: #{due_date.to_datetime.strftime('%B %d, %Y')}
91
+ App Store submission: #{submission_date.strftime('%B %d, %Y')}
92
+ Release: #{release_date.strftime('%B %d, %Y')}
93
+ MILESTONE_DESCRIPTION
91
94
 
92
95
  options = {}
93
- options[:due_on] = newmilestone_duedate
96
+ # == Workaround for GitHub API bug ==
97
+ #
98
+ # It seems that whatever date we send to the API, GitHub will 'floor' it to the date that seems to be at
99
+ # 00:00 PST/PDT and then discard the time component of the date we sent.
100
+ # This means that, when we cross the November DST change date, where the due date of the previous milestone
101
+ # was e.g. `2022-10-31T07:00:00Z` and `.next_day(14)` returns `2022-11-14T07:00:00Z` and we send that value
102
+ # for the `due_on` field via the API, GitHub ends up creating a milestone with a due of `2022-11-13T08:00:00Z`
103
+ # instead, introducing an off-by-one error on that due date.
104
+ #
105
+ # This is a bug in the GitHub API, not in our date computation logic.
106
+ # To solve this, we trick it by forcing the time component of the ISO date we send to be `12:00:00Z`.
107
+ options[:due_on] = due_date.strftime('%Y-%m-%dT12:00:00Z')
94
108
  options[:description] = comment
95
- github_client().create_milestone(repository, newmilestone_number, options)
109
+ client.create_milestone(repository, title, options)
96
110
  end
97
111
 
98
112
  # Creates a Release on GitHub as a Draft
@@ -106,8 +120,8 @@ module Fastlane
106
120
  # @param [Array<String>] assets List of file paths to attach as assets to the release
107
121
  # @param [TrueClass|FalseClass] prerelease Indicates if this should be created as a pre-release (i.e. for alpha/beta)
108
122
  #
109
- def self.create_release(repository:, version:, target: nil, description:, assets:, prerelease:)
110
- release = github_client().create_release(
123
+ def create_release(repository:, version:, target: nil, description:, assets:, prerelease:)
124
+ release = client.create_release(
111
125
  repository,
112
126
  version, # tag name
113
127
  name: version, # release name
@@ -117,7 +131,7 @@ module Fastlane
117
131
  body: description
118
132
  )
119
133
  assets.each do |file_path|
120
- github_client().upload_asset(release[:url], file_path, content_type: 'application/octet-stream')
134
+ client.upload_asset(release[:url], file_path, content_type: 'application/octet-stream')
121
135
  end
122
136
  end
123
137
 
@@ -129,15 +143,14 @@ module Fastlane
129
143
  # @param [String] download_folder The folder which the file should be downloaded into
130
144
  # @return [String] The path of the downloaded file, or nil if something went wrong
131
145
  #
132
- def self.download_file_from_tag(repository:, tag:, file_path:, download_folder:)
146
+ def download_file_from_tag(repository:, tag:, file_path:, download_folder:)
133
147
  repository = repository.delete_prefix('/').chomp('/')
134
148
  file_path = file_path.delete_prefix('/').chomp('/')
135
149
  file_name = File.basename(file_path)
136
150
  download_path = File.join(download_folder, file_name)
137
151
 
138
- download_url = github_client.contents(repository,
139
- path: file_path,
140
- ref: tag).download_url
152
+ download_url = client.contents(repository, path: file_path, ref: tag).download_url
153
+
141
154
  begin
142
155
  uri = URI.parse(download_url)
143
156
  uri.open do |remote_file|
@@ -151,8 +164,7 @@ module Fastlane
151
164
  end
152
165
 
153
166
  # Creates (or updates an existing) GitHub PR Comment
154
- def self.comment_on_pr(project_slug:, pr_number:, body:, reuse_identifier: SecureRandom.uuid)
155
- client = github_client
167
+ def comment_on_pr(project_slug:, pr_number:, body:, reuse_identifier: SecureRandom.uuid)
156
168
  comments = client.issue_comments(project_slug, pr_number)
157
169
 
158
170
  reuse_marker = "<!-- REUSE_ID: #{reuse_identifier} -->"
@@ -172,6 +184,58 @@ module Fastlane
172
184
 
173
185
  reuse_identifier
174
186
  end
187
+
188
+ # Update a milestone for a repository
189
+ #
190
+ # @param [String] repository The repository name (including the organization)
191
+ # @param [String] number The number of the milestone we want to fetch
192
+ # @param options [Hash] A customizable set of options.
193
+ # @option options [String] :title A unique title.
194
+ # @option options [String] :state
195
+ # @option options [String] :description A meaningful description
196
+ # @option options [Time] :due_on Set if the milestone has a due date
197
+ # @return [Milestone] A single milestone object
198
+ # @see http://developer.github.com/v3/issues/milestones/#update-a-milestone
199
+ #
200
+ def update_milestone(repository:, number:, **options)
201
+ client.update_milestone(repository, number, options)
202
+ end
203
+
204
+ # Remove the protection of a single branch from a repository
205
+ #
206
+ # @param [String] repository The repository name (including the organization)
207
+ # @param [String] branch The branch name
208
+ # @param [Hash] options A customizable set of options.
209
+ # @see https://docs.github.com/en/rest/branches/branch-protection#update-branch-protection
210
+ #
211
+ def remove_branch_protection(repository:, branch:, **options)
212
+ client.unprotect_branch(repository, branch, options)
213
+ end
214
+
215
+ # Protects a single branch from a repository
216
+ #
217
+ # @param [String] repository The repository name (including the organization)
218
+ # @param [String] branch The branch name
219
+ # @param options [Hash] A customizable set of options.
220
+ # @see https://docs.github.com/en/rest/branches/branch-protection#update-branch-protection
221
+ #
222
+ def set_branch_protection(repository:, branch:, **options)
223
+ client.protect_branch(repository, branch, options)
224
+ end
225
+
226
+ # Creates a GithubToken Fastlane ConfigItem
227
+ #
228
+ # @return [FastlaneCore::ConfigItem] The Fastlane ConfigItem for GitHub OAuth access token
229
+ #
230
+ def self.github_token_config_item
231
+ FastlaneCore::ConfigItem.new(
232
+ key: :github_token,
233
+ env_name: 'GITHUB_TOKEN',
234
+ description: 'The GitHub OAuth access token',
235
+ optional: false,
236
+ type: String
237
+ )
238
+ end
175
239
  end
176
240
  end
177
241
  end
@@ -36,6 +36,28 @@ module Fastlane
36
36
  end
37
37
  end
38
38
 
39
+ # Read a file line by line and iterate over it (just like `File.readlines` does),
40
+ # except that it also detects the encoding used by the file (using the BOM if present) when reading it,
41
+ # and then convert each line to UTF-8 before yielding it
42
+ #
43
+ # This is particularly useful if you need to then use a `RegExp` to match part of the lines you're iterating over,
44
+ # as the `RegExp` (which will typically be UTF-8) and the string you're matching with it have to use the same encoding
45
+ # (otherwise we would get a `Encoding::CompatibilityError`)
46
+ #
47
+ # @important If you are then using a `RegExp` to match the UTF-8 lines you iterate on,
48
+ # remember to use the `u` flag on it (`/…/u`) to make it UTF-8-aware too.
49
+ #
50
+ # @param [String] file The path to the file to read
51
+ # @yield each line read from the file, after converting it to the UTF-8 encoding
52
+ #
53
+ def self.read_utf8_lines(file)
54
+ # Be sure to guess file encoding using the Byte-Order-Mark, and fallback to UTF-8 if there's no BOM.
55
+ File.readlines(file, mode: 'rb:BOM|UTF-8').map do |line|
56
+ # Ensure the line is re-encoded to UTF-8 regardless of the encoding that was used in the input file
57
+ line.encode(Encoding::UTF_8)
58
+ end
59
+ end
60
+
39
61
  # Merge the content of multiple `.strings` files into a new `.strings` text file.
40
62
  #
41
63
  # @param [Hash<String, String>] paths The paths of the `.strings` files to merge together, associated with the prefix to prepend to each of their respective keys
@@ -68,11 +90,9 @@ module Fastlane
68
90
  all_keys_found += string_keys
69
91
 
70
92
  tmp_file.write("/* MARK: - #{File.basename(input_file)} */\n\n")
71
- # Read line-by-line to reduce memory footprint during content copy; Be sure to guess file encoding using the Byte-Order-Mark.
72
- File.readlines(input_file, mode: 'rb:BOM|UTF-8').each do |line|
93
+ # Read line-by-line to reduce memory footprint during content copy
94
+ read_utf8_lines(input_file).each do |line|
73
95
  unless prefix.nil? || prefix.empty?
74
- # We need to ensure the line and RegExp are using the same encoding, so we transcode everything to UTF-8.
75
- line.encode!(Encoding::UTF_8)
76
96
  # The `/u` modifier on the RegExps is to make them UTF-8
77
97
  line.gsub!(/^(\s*")/u, "\\1#{prefix}") # Lines starting with a quote are considered to be start of a key; add prefix right after the quote
78
98
  line.gsub!(/^(\s*)([A-Z0-9_]+)(\s*=\s*")/ui, "\\1\"#{prefix}\\2\"\\3") # Lines starting with an identifier followed by a '=' are considered to be an unquoted key (typical in InfoPlist.strings files for example)
@@ -11,25 +11,25 @@ module Fastlane
11
11
 
12
12
  TRANSITIONS = {
13
13
  root: {
14
- /\s/ => :root,
14
+ /\s/u => :root,
15
15
  '/' => :maybe_comment_start,
16
16
  '"' => :in_quoted_key
17
17
  },
18
18
  maybe_comment_start: {
19
19
  '/' => :in_line_comment,
20
- /\*/ => :in_block_comment
20
+ /\*/u => :in_block_comment
21
21
  },
22
22
  in_line_comment: {
23
23
  "\n" => :root,
24
- /./ => :in_line_comment
24
+ /./u => :in_line_comment
25
25
  },
26
26
  in_block_comment: {
27
27
  /\*/ => :maybe_block_comment_end,
28
- /./m => :in_block_comment
28
+ /./mu => :in_block_comment
29
29
  },
30
30
  maybe_block_comment_end: {
31
31
  '/' => :root,
32
- /./m => :in_block_comment
32
+ /./mu => :in_block_comment
33
33
  },
34
34
  in_quoted_key: {
35
35
  '"' => lambda do |state, _|
@@ -37,25 +37,25 @@ module Fastlane
37
37
  state.buffer.string = ''
38
38
  :after_quoted_key_before_eq
39
39
  end,
40
- /./ => lambda do |state, c|
40
+ /./u => lambda do |state, c|
41
41
  state.buffer.write(c)
42
42
  :in_quoted_key
43
43
  end
44
44
  },
45
45
  after_quoted_key_before_eq: {
46
- /\s/ => :after_quoted_key_before_eq,
46
+ /\s/u => :after_quoted_key_before_eq,
47
47
  '=' => :after_quoted_key_and_eq
48
48
  },
49
49
  after_quoted_key_and_eq: {
50
- /\s/ => :after_quoted_key_and_eq,
50
+ /\s/u => :after_quoted_key_and_eq,
51
51
  '"' => :in_quoted_value
52
52
  },
53
53
  in_quoted_value: {
54
54
  '"' => :after_quoted_value,
55
- /./m => :in_quoted_value
55
+ /./mu => :in_quoted_value
56
56
  },
57
57
  after_quoted_value: {
58
- /\s/ => :after_quoted_value,
58
+ /\s/u => :after_quoted_value,
59
59
  ';' => :root
60
60
  }
61
61
  }.freeze
@@ -70,7 +70,10 @@ module Fastlane
70
70
 
71
71
  state = State.new(context: :root, buffer: StringIO.new, in_escaped_ctx: false, found_key: nil)
72
72
 
73
- File.readlines(file).each_with_index do |line, line_no|
73
+ # Using our `each_utf8_line` helper instead of `File.readlines` ensures we can also read files that are
74
+ # encoded in UTF-16, yet process each of their lines as a UTF-8 string, so that `RegExp#match?` don't throw
75
+ # an `Encoding::CompatibilityError` exception. (Note how all our `RegExp`s in `TRANSITIONS` have the `u` flag)
76
+ Fastlane::Helper::Ios::L10nHelper.read_utf8_lines(file).each_with_index do |line, line_no|
74
77
  line.chars.each_with_index do |c, col_no|
75
78
  # Handle escaped characters at a global level.
76
79
  # This is more straightforward than having to account for it in the `TRANSITIONS` table.
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module Wpmreleasetoolkit
3
- VERSION = '5.6.0'
3
+ VERSION = '6.0.0'
4
4
  end
5
5
  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: 5.6.0
4
+ version: 6.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Automattic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-27 00:00:00.000000000 Z
11
+ date: 2022-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport