fastlane-plugin-wpmreleasetoolkit 5.5.0 → 6.0.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/android/android_create_avd_action.rb +90 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_download_file_by_version.rb +3 -1
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_launch_emulator_action.rb +61 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_send_app_size_metrics.rb +6 -11
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_shutdown_emulator_action.rb +48 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/close_milestone_action.rb +5 -2
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/comment_on_pr.rb +3 -7
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/create_new_milestone_action.rb +17 -2
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/create_release_action.rb +3 -1
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/get_prs_list_action.rb +3 -1
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/removebranchprotection_action.rb +12 -6
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/setbranchprotection_action.rb +12 -5
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/setfrozentag_action.rb +5 -2
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/upload_to_s3.rb +16 -1
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_lint_localizations.rb +17 -15
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/android/android_emulator_helper.rb +198 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/android/android_tools_path_helper.rb +87 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb +110 -46
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_l10n_helper.rb +24 -4
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_strings_file_validation_helper.rb +14 -11
- data/lib/fastlane/plugin/wpmreleasetoolkit/version.rb +1 -1
- metadata +9 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c16be217cbd668e4fbd9da107edcdba4b5be149f0d64d4dadb6a0d5697cde006
|
4
|
+
data.tar.gz: e736842ca7df0e1ff50fc0a7114e39555570c03c8e58b41fa265999f267f0ec2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 739938930d2cfd9238590f6b58b7031923f5f6b325c53246058f77dc47c7e302180f2d088b8c764106d50e45f7bca265d915a3d671bbf09c6a45e9309fcb1e3e
|
7
|
+
data.tar.gz: 0741aeff083cab3060b8b4f46259ad091217eef34fd013370fca26289b5c2279c325632dd7935699772e293e81e87ffcbd71bc7b8905da5fffbb4e04543f61e5
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Actions
|
3
|
+
class AndroidCreateAvdAction < Action
|
4
|
+
def self.run(params)
|
5
|
+
device_model = params[:device_model]
|
6
|
+
api_level = params[:api_level]
|
7
|
+
avd_name = params[:avd_name]
|
8
|
+
sdcard = params[:sdcard]
|
9
|
+
|
10
|
+
helper = Fastlane::Helper::Android::EmulatorHelper.new
|
11
|
+
|
12
|
+
# Ensure we have the system image needed for creating the AVD with this API level
|
13
|
+
system_image = params[:system_image] || helper.install_system_image(api: api_level)
|
14
|
+
|
15
|
+
# Create the AVD for device, API and system image we need
|
16
|
+
helper.create_avd(
|
17
|
+
api: api_level,
|
18
|
+
device: device_model,
|
19
|
+
system_image: system_image,
|
20
|
+
name: avd_name,
|
21
|
+
sdcard: sdcard
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
#####################################################
|
26
|
+
# @!group Documentation
|
27
|
+
#####################################################
|
28
|
+
|
29
|
+
def self.description
|
30
|
+
'Creates a new Android Virtual Device (AVD) for a specific device model and API level'
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.details
|
34
|
+
<<~DESC
|
35
|
+
Creates a new Android Virtual Device (AVD) for a specific device model and API level.
|
36
|
+
By default, it also installs the necessary system image (using `sdkmanager`) if needed before creating the AVD
|
37
|
+
DESC
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.available_options
|
41
|
+
[
|
42
|
+
FastlaneCore::ConfigItem.new(key: :device_model,
|
43
|
+
env_name: 'FL_ANDROID_CREATE_AVD_DEVICE_MODEL',
|
44
|
+
description: 'The device model code to use to create the AVD. Valid values can be found using `avdmanager list devices`',
|
45
|
+
type: String,
|
46
|
+
optional: false),
|
47
|
+
FastlaneCore::ConfigItem.new(key: :api_level,
|
48
|
+
env_name: 'FL_ANDROID_CREATE_AVD_API_LEVEL',
|
49
|
+
description: 'The API level to use to install the necessary system-image and create the AVD',
|
50
|
+
type: Integer,
|
51
|
+
optional: false),
|
52
|
+
FastlaneCore::ConfigItem.new(key: :avd_name,
|
53
|
+
env_name: 'FL_ANDROID_CREATE_AVD_AVD_NAME',
|
54
|
+
description: 'The name to give to the created AVD. If not provided, will be derived from device model and API level',
|
55
|
+
type: String,
|
56
|
+
optional: true,
|
57
|
+
default_value: nil),
|
58
|
+
FastlaneCore::ConfigItem.new(key: :sdcard,
|
59
|
+
env_name: 'FL_ANDROID_CREATE_AVD_SDCARD',
|
60
|
+
description: 'The size of the SD card to use for the AVD',
|
61
|
+
type: String,
|
62
|
+
optional: true,
|
63
|
+
default_value: '512M'),
|
64
|
+
FastlaneCore::ConfigItem.new(key: :system_image,
|
65
|
+
env_name: 'FL_ANDROID_CREATE_AVD_SYSTEM_IMAGE',
|
66
|
+
description: 'The system image to use (as used/listed by `sdkmanager`). Defaults to the appropriate system image given the API level requested and the current machine\'s architecture',
|
67
|
+
type: String,
|
68
|
+
optional: true,
|
69
|
+
default_value_dynamic: true,
|
70
|
+
default_value: nil),
|
71
|
+
]
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.output
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.return_value
|
78
|
+
'Returns the name of the created AVD'
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.authors
|
82
|
+
['Automattic']
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.is_supported?(platform)
|
86
|
+
platform == :android
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_download_file_by_version.rb
CHANGED
@@ -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.
|
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
|
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Actions
|
3
|
+
class AndroidLaunchEmulatorAction < Action
|
4
|
+
def self.run(params)
|
5
|
+
helper = Fastlane::Helper::Android::EmulatorHelper.new
|
6
|
+
helper.launch_avd(
|
7
|
+
name: params[:avd_name],
|
8
|
+
cold_boot: params[:cold_boot],
|
9
|
+
wipe_data: params[:wipe_data]
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
#####################################################
|
14
|
+
# @!group Documentation
|
15
|
+
#####################################################
|
16
|
+
|
17
|
+
def self.description
|
18
|
+
'Boots an Android emulator using the given AVD name'
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.details
|
22
|
+
description
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.available_options
|
26
|
+
[
|
27
|
+
FastlaneCore::ConfigItem.new(key: :avd_name,
|
28
|
+
env_name: 'FL_ANDROID_LAUNCH_EMULATOR_AVD_NAME',
|
29
|
+
description: 'The name of the AVD to boot',
|
30
|
+
type: String,
|
31
|
+
optional: false),
|
32
|
+
FastlaneCore::ConfigItem.new(key: :cold_boot,
|
33
|
+
env_name: 'FL_ANDROID_LAUNCH_EMULATOR_COLD_BOOT',
|
34
|
+
description: 'Indicate if we want a cold boot (true) of if we prefer booting from a snapshot (false)',
|
35
|
+
type: Fastlane::Boolean,
|
36
|
+
default_value: true),
|
37
|
+
FastlaneCore::ConfigItem.new(key: :wipe_data,
|
38
|
+
env_name: 'FL_ANDROID_LAUNCH_EMULATOR_WIPE_DATA',
|
39
|
+
description: 'Indicate if we want to wipe the device data before booting the AVD, so it is like it were a brand new device',
|
40
|
+
type: Fastlane::Boolean,
|
41
|
+
default_value: true),
|
42
|
+
]
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.output
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.return_value
|
49
|
+
'The serial of the emulator that was created after booting the AVD (e.g. `emulator-5554`)'
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.authors
|
53
|
+
['Automattic']
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.is_supported?(platform)
|
57
|
+
platform == :android
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -71,31 +71,26 @@ module Fastlane
|
|
71
71
|
end
|
72
72
|
|
73
73
|
# The path where the `apkanalyzer` binary was found, after searching it:
|
74
|
-
# - in priority in `$
|
74
|
+
# - in priority in `$ANDROID_HOME` (or `$ANDROID_SDK_ROOT` for legacy setups), under `cmdline-tools/latest/bin/` or `cmdline-tools/tools/bin`
|
75
75
|
# - and falling back by trying to find it in `$PATH`
|
76
76
|
#
|
77
77
|
# @return [String,Nil] The path to `apkanalyzer`, or `nil` if it wasn't found in any of the above tested paths.
|
78
78
|
#
|
79
79
|
def find_apkanalyzer_binary
|
80
|
-
|
81
|
-
|
82
|
-
pattern = File.join(sdk_root, 'cmdline-tools', '{latest,tools}', 'bin', 'apkanalyzer')
|
83
|
-
apkanalyzer_bin = Dir.glob(pattern).find { |path| File.executable?(path) }
|
84
|
-
end
|
85
|
-
apkanalyzer_bin || Action.sh('command', '-v', 'apkanalyzer', print_command_output: false) { |_| nil }
|
80
|
+
@tools ||= Fastlane::Helper::Android::ToolsPathHelper.new
|
81
|
+
@tools.find_tool_path(binary: 'apkanalyzer', search_paths: @tools.cmdline_tools_search_paths)
|
86
82
|
end
|
87
83
|
|
88
84
|
# The path where the `apkanalyzer` binary was found, after searching it:
|
89
|
-
# - in priority in `$
|
85
|
+
# - in priority in `$ANDROID_HOME` (or `$ANDROID_SDK_ROOT` for legacy setups), under `cmdline-tools/latest/bin/` or `cmdline-tools/tools/bin`
|
90
86
|
# - and falling back by trying to find it in `$PATH`
|
91
87
|
#
|
92
88
|
# @return [String] The path to `apkanalyzer`
|
93
89
|
# @raise [FastlaneCore::Interface::FastlaneError] if it wasn't found in any of the above tested paths.
|
94
90
|
#
|
95
91
|
def find_apkanalyzer_binary!
|
96
|
-
|
97
|
-
|
98
|
-
apkanalyzer_bin
|
92
|
+
@tools ||= Fastlane::Helper::Android::ToolsPathHelper.new
|
93
|
+
@tools.find_tool_path!(binary: 'apkanalyzer', search_paths: @tools.cmdline_tools_search_paths)
|
99
94
|
end
|
100
95
|
|
101
96
|
# Add the `file-size` and `download-size` values of an APK to the helper, as reported by the corresponding `apkanalyzer apk …` commands
|
data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_shutdown_emulator_action.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Actions
|
3
|
+
class AndroidShutdownEmulatorAction < Action
|
4
|
+
def self.run(params)
|
5
|
+
helper = Fastlane::Helper::Android::EmulatorHelper.new
|
6
|
+
helper.shut_down_emulators!(serials: params[:serials])
|
7
|
+
end
|
8
|
+
|
9
|
+
#####################################################
|
10
|
+
# @!group Documentation
|
11
|
+
#####################################################
|
12
|
+
|
13
|
+
def self.description
|
14
|
+
'Shuts down Android emulators'
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.details
|
18
|
+
description
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.available_options
|
22
|
+
[
|
23
|
+
FastlaneCore::ConfigItem.new(key: :serials,
|
24
|
+
env_name: 'FL_ANDROID_SHUTDOWN_EMULATOR_SERIALS',
|
25
|
+
description: 'The serial(s) of the emulators to shut down. If not provided (nil), will shut them all down',
|
26
|
+
type: Array,
|
27
|
+
optional: true,
|
28
|
+
default_value: nil),
|
29
|
+
]
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.output
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.return_value
|
36
|
+
# If you method provides a return value, you can describe here what it does
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.authors
|
40
|
+
['Automattic']
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.is_supported?(platform)
|
44
|
+
platform == :android
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -10,10 +10,12 @@ module Fastlane
|
|
10
10
|
repository = params[:repository]
|
11
11
|
milestone_title = params[:milestone]
|
12
12
|
|
13
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
Fastlane::Helper::GithubHelper.
|
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
|
-
|
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
|
-
|
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
|
-
|
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 =
|
5
|
+
violations = nil
|
6
6
|
|
7
7
|
loop do
|
8
|
-
|
9
|
-
#
|
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.
|
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
|
-
|
63
|
-
|
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
|
@@ -0,0 +1,198 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Helper
|
3
|
+
module Android
|
4
|
+
# Helper methods to manipulate System Images, AVDs and Android Emulators
|
5
|
+
#
|
6
|
+
class EmulatorHelper
|
7
|
+
BOOT_WAIT = 2
|
8
|
+
BOOT_TIMEOUT = 60
|
9
|
+
|
10
|
+
SHUTDOWN_WAIT = 2
|
11
|
+
SHUTDOWN_TIMEOUT = 60
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@tools = Fastlane::Helper::Android::ToolsPathHelper.new
|
15
|
+
end
|
16
|
+
|
17
|
+
# Installs the system-image suitable for a given Android `api`, with Google APIs, and for the current machine's architecture
|
18
|
+
#
|
19
|
+
# @param [Integer] api The Android API level to use
|
20
|
+
#
|
21
|
+
# @return [String] The `sdkmanager` package specifier that has been installed
|
22
|
+
#
|
23
|
+
def install_system_image(api:)
|
24
|
+
package = system_image_package(api: api)
|
25
|
+
|
26
|
+
UI.message("Installing System Image for Android #{api} (#{package})")
|
27
|
+
Actions.sh(@tools.sdkmanager, '--install', package)
|
28
|
+
UI.success("System Image #{package} successfully installed.")
|
29
|
+
package
|
30
|
+
end
|
31
|
+
|
32
|
+
# Create an emulator (AVD) for a given `api` number and `device` model
|
33
|
+
#
|
34
|
+
# @param [Integer] api The Android API level to use for this AVD
|
35
|
+
# @param [String] device The Device Model to use for this AVD. Valid values can be found using `avdmanager list devices`
|
36
|
+
# @param [String] name The name to give for the created AVD. Defaults to `<device>_API_<api>`.
|
37
|
+
# @param [String] sdcard The size of the SD card for this device. Defaults to `512M`.
|
38
|
+
#
|
39
|
+
# @return [String] The device name (i.e. either `name` if provided, or the derived `<device>_API_<api>` if provided `name` was `nil``)
|
40
|
+
#
|
41
|
+
def create_avd(api:, device:, system_image: nil, name: nil, sdcard: '512M')
|
42
|
+
package = system_image || system_image_package(api: api)
|
43
|
+
device_name = name || "#{device.gsub(' ', '_').capitalize}_API_#{api}"
|
44
|
+
|
45
|
+
UI.message("Creating AVD `#{device_name}` (#{device}, API #{api})")
|
46
|
+
|
47
|
+
Actions.sh(
|
48
|
+
@tools.avdmanager, 'create', 'avd',
|
49
|
+
'--force',
|
50
|
+
'--package', package,
|
51
|
+
'--device', device,
|
52
|
+
'--sdcard', sdcard,
|
53
|
+
'--name', device_name
|
54
|
+
)
|
55
|
+
|
56
|
+
UI.success("AVD `#{device_name}` successfully created.")
|
57
|
+
|
58
|
+
device_name
|
59
|
+
end
|
60
|
+
|
61
|
+
# Launch the emulator for the given AVD, then return the emulator serial
|
62
|
+
#
|
63
|
+
# @param [String] name name of the AVD to launch
|
64
|
+
# @param [Int] port the TCP port to use to connect to the emulator via adb. If nil (default), will let `emulator` pick the first free one.
|
65
|
+
# @param [Boolean] cold_boot if true, will do a cold boot, if false will try to use a previous snapshot of the device
|
66
|
+
# @param [Boolean] wipe_data if true, will wipe the emulator (i.e. reset the user data image)
|
67
|
+
#
|
68
|
+
# @return [String] emulator serial number corresponding to the launched AVD
|
69
|
+
#
|
70
|
+
def launch_avd(name:, port: nil, cold_boot: true, wipe_data: true)
|
71
|
+
UI.message("Launching emulator for #{name}")
|
72
|
+
|
73
|
+
params = ['-avd', name]
|
74
|
+
params << ['-port', port.to_s] unless port.nil?
|
75
|
+
params << '-no-snapshot' if cold_boot
|
76
|
+
params << '-wipe-data' if wipe_data
|
77
|
+
|
78
|
+
UI.command([@tools.emulator, *params].shelljoin)
|
79
|
+
# We want to launch emulator in the background to not block the rest of the code, so we can't use `Actions.sh` here
|
80
|
+
# We also want to filter the `stdout`+`stderr` emitted by the `emulator` process in the background,
|
81
|
+
# to limit verbosity and only print error lines, and also prefix those clearly (because they might happen
|
82
|
+
# at any moment in the background, so in parallel/the middle of other fastlane logs).
|
83
|
+
t = Thread.new do
|
84
|
+
Open3.popen2e(@tools.emulator, *params) do |i, oe, wait_thr|
|
85
|
+
i.close
|
86
|
+
until oe.eof?
|
87
|
+
line = oe.readline
|
88
|
+
UI.error("📱 [emulator]: #{line}") if line.start_with?(/ERROR|PANIC/)
|
89
|
+
next unless line.include?('PANIC: Broken AVD system path')
|
90
|
+
|
91
|
+
UI.user_error! <<~HINT
|
92
|
+
#{line}
|
93
|
+
Verify that your `sdkmanager/avdmanager` tools are not installed in a different SDK root than your `emulator` tool
|
94
|
+
(which can happen if you installed Android's command-line tools via `brew`, but the `emulator` via Android Studio, or vice-versa)
|
95
|
+
HINT
|
96
|
+
end
|
97
|
+
UI.error("📱 [emulator]: exited with non-zero status code: #{wait_thr.value.exitstatus}") unless wait_thr.value.success?
|
98
|
+
end
|
99
|
+
end
|
100
|
+
t.abort_on_exception = true # To bubble up any exception like `UI.user_error!` back to the main thread here
|
101
|
+
|
102
|
+
UI.message('Waiting for emulator to start...')
|
103
|
+
# Loop until the emulator has started and shows up in `adb devices -l` so we can find its serial
|
104
|
+
serial = nil
|
105
|
+
retry_loop(time_between_retries: BOOT_WAIT, timeout: BOOT_TIMEOUT, description: 'waiting for emulator to start') do
|
106
|
+
serial = find_serial(avd_name: name)
|
107
|
+
!serial.nil?
|
108
|
+
end
|
109
|
+
UI.message("Found device `#{name}` with serial `#{serial}`")
|
110
|
+
|
111
|
+
# Once the emulator has started, wait for the device in the emulator to finish booting
|
112
|
+
UI.message('Waiting for device to finish booting...')
|
113
|
+
retry_loop(time_between_retries: BOOT_WAIT, timeout: BOOT_TIMEOUT, description: 'waiting for device to finish booting') do
|
114
|
+
Actions.sh(@tools.adb, '-s', serial, 'shell', 'getprop', 'sys.boot_completed').chomp == '1'
|
115
|
+
end
|
116
|
+
|
117
|
+
UI.success("Emulator #{name} successfully booted as `#{serial}`.")
|
118
|
+
|
119
|
+
serial
|
120
|
+
end
|
121
|
+
|
122
|
+
# @return [Array<Fastlane::Helper::AdbDevice>] List of currently booted emulators
|
123
|
+
#
|
124
|
+
def running_emulators
|
125
|
+
helper = Fastlane::Helper::AdbHelper.new(adb_path: @tools.adb)
|
126
|
+
helper.load_all_devices.select { |device| device.serial.include?('emulator') }
|
127
|
+
end
|
128
|
+
|
129
|
+
def find_serial(avd_name:)
|
130
|
+
running_emulators.find do |candidate|
|
131
|
+
command = [@tools.adb, '-s', candidate.serial, 'emu', 'avd', 'name']
|
132
|
+
UI.command(command.shelljoin)
|
133
|
+
candidate_name = Actions.sh(*command, log: false).split("\n").first.chomp
|
134
|
+
candidate_name == avd_name
|
135
|
+
end&.serial
|
136
|
+
end
|
137
|
+
|
138
|
+
# Trigger a shutdown for all running emulators, and wait until there is no more emulators running.
|
139
|
+
#
|
140
|
+
# @param [Array<String>] serials List of emulator serials to shut down. Will shut down all of them if `nil`.
|
141
|
+
#
|
142
|
+
def shut_down_emulators!(serials: nil)
|
143
|
+
UI.message("Shutting down #{serials || 'all'} emulator(s)...")
|
144
|
+
|
145
|
+
emulators_list = running_emulators.map(&:serial)
|
146
|
+
# Get the intersection of the set of running emulators with the ones we want to shut down
|
147
|
+
emulators_list &= serials unless serials.nil?
|
148
|
+
emulators_list.each do |e|
|
149
|
+
Actions.sh(@tools.adb, '-s', e, 'emu', 'kill') { |_| } # ignore error if no emulator with specified serial is running
|
150
|
+
|
151
|
+
# NOTE: Alternative way of shutting down emulator would be to call the following command instead, which shuts down the emulator more gracefully:
|
152
|
+
# `adb -s #{e} shell reboot -p` # In case you're wondering, `-p` is for "power-off"
|
153
|
+
# But this alternate command:
|
154
|
+
# - Requires that `-no-snapshot` was used on boot (to avoid being prompted to save current state on shutdown)
|
155
|
+
# - Disconnects the emulator from `adb` (and thus disappear from `adb devices -l`) for a short amount of time,
|
156
|
+
# before reconnecting to it but in an `offline` state, until `emulator` finally completely quits and it disappears
|
157
|
+
# again (for good) from `adb devices --list`.
|
158
|
+
# This means that so if we used alternative, we couldn't really retry_loop until emulator disappears from `running_emulators` to detect
|
159
|
+
# that the shutdown was really complete, as we might as well accidentally detect the intermediate disconnect instead.
|
160
|
+
end
|
161
|
+
|
162
|
+
# Wait until all emulators are killed
|
163
|
+
retry_loop(time_between_retries: SHUTDOWN_WAIT, timeout: SHUTDOWN_TIMEOUT, description: 'waiting for devices to shutdown') do
|
164
|
+
(emulators_list & running_emulators.map(&:serial)).empty?
|
165
|
+
end
|
166
|
+
|
167
|
+
UI.success('All emulators are now shut down.')
|
168
|
+
end
|
169
|
+
|
170
|
+
# Find the system-images package for the provided `api`, with Google APIs, and matching the current platform/architecture this lane is called from.
|
171
|
+
#
|
172
|
+
# @param [Integer] api The Android API level to use for this AVD
|
173
|
+
# @return [String] The `system-images;android-<N>;google_apis;<platform>` package specifier for `sdkmanager` to use in its install command
|
174
|
+
#
|
175
|
+
# @note Results from this method are memoized, to avoid repeating calls to `sdkmanager` when querying for the same api level multiple times.
|
176
|
+
#
|
177
|
+
def system_image_package(api:)
|
178
|
+
@system_image_packages ||= {}
|
179
|
+
@system_image_packages[api] ||= begin
|
180
|
+
platform = `uname -m`.chomp
|
181
|
+
all_packages = `#{@tools.sdkmanager} --list`
|
182
|
+
package = all_packages.match(/^ *(system-images;android-#{api};google_apis;#{platform}(-[^ ]*)?)/)&.captures&.first
|
183
|
+
UI.user_error!("Could not find system-image for API `#{api}` and your platform `#{platform}` in `sdkmanager --list`. Maybe Google removed it for download and it's time to update to a newer API?") if package.nil?
|
184
|
+
package
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def retry_loop(time_between_retries:, timeout:, description:)
|
189
|
+
Timeout.timeout(timeout) do
|
190
|
+
sleep(time_between_retries) until yield
|
191
|
+
end
|
192
|
+
rescue Timeout::Error
|
193
|
+
UI.user_error!("Timed out #{description}")
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'fastlane_core/ui/ui'
|
2
|
+
|
3
|
+
module Fastlane
|
4
|
+
module Helper
|
5
|
+
module Android
|
6
|
+
# Helper to find the paths of common Android build and SDK tools on the current machine
|
7
|
+
# Based on `$ANDROID_HOME` and the common relative paths those tools are installed in.
|
8
|
+
#
|
9
|
+
class ToolsPathHelper
|
10
|
+
attr_reader :android_home
|
11
|
+
|
12
|
+
def initialize(sdk_root: nil)
|
13
|
+
@android_home = sdk_root || ENV['ANDROID_HOME'] || ENV['ANDROID_SDK_ROOT'] || ENV['ANDROID_SDK']
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param [String] binary The name of the binary to search for
|
17
|
+
# @param [Array<String>] search_paths The search paths, relative to `@android_home`, in which to search for the tools.
|
18
|
+
# If `android_home` is `nil` or the binary wasn't found in any of the `search_paths`, will fallback to searching in `$PATH`.
|
19
|
+
# @return [String] The absolute path of the tool if found, `nil` if not found.
|
20
|
+
def find_tool_path(binary:, search_paths:)
|
21
|
+
bin_path = unless android_home.nil?
|
22
|
+
search_paths
|
23
|
+
.map { |path| File.join(android_home, path, binary) }
|
24
|
+
.find { |path| File.executable?(path) }
|
25
|
+
end
|
26
|
+
|
27
|
+
# If not found in any of the `search_paths`, try to look for it in $PATH
|
28
|
+
bin_path ||= Actions.sh('command', '-v', binary) { |err, res, _| res if err&.success? }&.chomp
|
29
|
+
|
30
|
+
# Normalize return value to `nil` if it was not found, empty, or is not an executable
|
31
|
+
bin_path = nil if !bin_path.nil? && (bin_path.empty? || !File.executable?(bin_path))
|
32
|
+
|
33
|
+
bin_path
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param [String] binary The name of the binary to search for
|
37
|
+
# @param [Array<String>] search_paths The search paths, relative to `@android_home`, in which to search for the tools.
|
38
|
+
# If `android_home` is `nil` or the binary wasn't found in any of the `search_paths`, will fallback to searching in `$PATH`.
|
39
|
+
# @return [String] The absolute path of the tool if found.
|
40
|
+
# @raise [FastlaneCore::Interface::FastlaneError] If the tool couldn't be found.
|
41
|
+
def find_tool_path!(binary:, search_paths:)
|
42
|
+
bin_path = find_tool_path(binary: binary, search_paths: search_paths)
|
43
|
+
UI.user_error!("Unable to find path for #{binary} in #{search_paths.inspect}. Verify you installed the proper Android tools.") if bin_path.nil?
|
44
|
+
bin_path
|
45
|
+
end
|
46
|
+
|
47
|
+
def cmdline_tools_search_paths
|
48
|
+
# It appears that depending on the machines and versions of Android SDK, some versions
|
49
|
+
# installed the command line tools in `tools` and not `latest` subdirectory, hence why
|
50
|
+
# we search both (`latest` first, `tools` as fallback) to cover all our bases.
|
51
|
+
[
|
52
|
+
File.join('cmdline-tools', 'latest', 'bin'),
|
53
|
+
File.join('cmdline-tools', 'tools', 'bin'),
|
54
|
+
]
|
55
|
+
end
|
56
|
+
|
57
|
+
def sdkmanager
|
58
|
+
@sdkmanager ||= find_tool_path!(
|
59
|
+
binary: 'sdkmanager',
|
60
|
+
search_paths: cmdline_tools_search_paths
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def avdmanager
|
65
|
+
@avdmanager ||= find_tool_path!(
|
66
|
+
binary: 'avdmanager',
|
67
|
+
search_paths: cmdline_tools_search_paths
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
def emulator
|
72
|
+
@emulator ||= find_tool_path!(
|
73
|
+
binary: 'emulator',
|
74
|
+
search_paths: [File.join('emulator')]
|
75
|
+
)
|
76
|
+
end
|
77
|
+
|
78
|
+
def adb
|
79
|
+
@adb ||= find_tool_path!(
|
80
|
+
binary: 'adb',
|
81
|
+
search_paths: [File.join('platform-tools')]
|
82
|
+
)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -8,34 +8,25 @@ module Fastlane
|
|
8
8
|
|
9
9
|
module Helper
|
10
10
|
class GithubHelper
|
11
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
31
|
-
|
20
|
+
# Fetch the current user
|
21
|
+
user = @client.user
|
22
|
+
UI.message("Logged in as: #{user.name}")
|
32
23
|
|
33
|
-
|
34
|
-
|
24
|
+
# Auto-paginate to ensure we're not missing data
|
25
|
+
@client.auto_paginate = true
|
35
26
|
end
|
36
27
|
|
37
|
-
def
|
38
|
-
miles =
|
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
|
55
|
-
|
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
|
49
|
+
def get_last_milestone(repository)
|
59
50
|
options = {}
|
60
51
|
options[:state] = 'open'
|
61
52
|
|
62
|
-
milestones =
|
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
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
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
|
-
|
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
|
110
|
-
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
|
-
|
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
|
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 =
|
139
|
-
|
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
|
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
|
72
|
-
|
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
|
-
/./
|
28
|
+
/./mu => :in_block_comment
|
29
29
|
},
|
30
30
|
maybe_block_comment_end: {
|
31
31
|
'/' => :root,
|
32
|
-
/./
|
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
|
-
/./
|
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
|
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.
|
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: 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-
|
11
|
+
date: 2022-11-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -260,14 +260,14 @@ dependencies:
|
|
260
260
|
requirements:
|
261
261
|
- - "~>"
|
262
262
|
- !ruby/object:Gem::Version
|
263
|
-
version: '2'
|
263
|
+
version: '2.210'
|
264
264
|
type: :development
|
265
265
|
prerelease: false
|
266
266
|
version_requirements: !ruby/object:Gem::Requirement
|
267
267
|
requirements:
|
268
268
|
- - "~>"
|
269
269
|
- !ruby/object:Gem::Version
|
270
|
-
version: '2'
|
270
|
+
version: '2.210'
|
271
271
|
- !ruby/object:Gem::Dependency
|
272
272
|
name: pry
|
273
273
|
requirement: !ruby/object:Gem::Requirement
|
@@ -402,6 +402,7 @@ files:
|
|
402
402
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_bump_version_release.rb
|
403
403
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_codefreeze_prechecks.rb
|
404
404
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_completecodefreeze_prechecks.rb
|
405
|
+
- lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_create_avd_action.rb
|
405
406
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_create_xml_release_notes.rb
|
406
407
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_current_branch_is_hotfix.rb
|
407
408
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_download_file_by_version.rb
|
@@ -412,7 +413,9 @@ files:
|
|
412
413
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_get_app_version.rb
|
413
414
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_get_release_version.rb
|
414
415
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_hotfix_prechecks.rb
|
416
|
+
- lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_launch_emulator_action.rb
|
415
417
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_send_app_size_metrics.rb
|
418
|
+
- lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_shutdown_emulator_action.rb
|
416
419
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_tag_build.rb
|
417
420
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_update_release_notes.rb
|
418
421
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/common/buildkite_trigger_build_action.rb
|
@@ -471,8 +474,10 @@ files:
|
|
471
474
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_update_release_notes.rb
|
472
475
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_validate_ci_build.rb
|
473
476
|
- lib/fastlane/plugin/wpmreleasetoolkit/helper/an_metadata_update_helper.rb
|
477
|
+
- lib/fastlane/plugin/wpmreleasetoolkit/helper/android/android_emulator_helper.rb
|
474
478
|
- lib/fastlane/plugin/wpmreleasetoolkit/helper/android/android_git_helper.rb
|
475
479
|
- lib/fastlane/plugin/wpmreleasetoolkit/helper/android/android_localize_helper.rb
|
480
|
+
- lib/fastlane/plugin/wpmreleasetoolkit/helper/android/android_tools_path_helper.rb
|
476
481
|
- lib/fastlane/plugin/wpmreleasetoolkit/helper/android/android_version_helper.rb
|
477
482
|
- lib/fastlane/plugin/wpmreleasetoolkit/helper/app_size_metrics_helper.rb
|
478
483
|
- lib/fastlane/plugin/wpmreleasetoolkit/helper/ci_helper.rb
|