fastlane 2.28.5 → 2.28.6
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/deliver/lib/deliver/options.rb +2 -2
- data/fastlane/lib/fastlane/actions/crashlytics.rb +1 -1
- data/fastlane/lib/fastlane/version.rb +1 -1
- data/fastlane_core/lib/fastlane_core/build_watcher.rb +39 -21
- data/fastlane_core/lib/fastlane_core/helper.rb +0 -5
- data/pilot/lib/pilot/build_manager.rb +7 -2
- data/pilot/lib/pilot/options.rb +1 -1
- data/pilot/lib/pilot/tester_manager.rb +2 -36
- data/spaceship/lib/spaceship/test_flight/base.rb +12 -0
- data/spaceship/lib/spaceship/test_flight/build.rb +18 -8
- data/spaceship/lib/spaceship/test_flight/client.rb +93 -41
- data/spaceship/lib/spaceship/test_flight/group.rb +36 -5
- data/spaceship/lib/spaceship/test_flight/test_info.rb +4 -0
- metadata +16 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bf87006e4674fdd128d4cae3634a07ba4d506f1c
|
|
4
|
+
data.tar.gz: 2e806c93d18de7344cd44df0c92ca162749bf55b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1d273bba01302f14006f1a308c605bd01ca3a20636c2f0b5e1ac7fa8c003df9f286e44ce4119409426c828f74827c0510c3a934abed1c0167155fb0f4aaadac2
|
|
7
|
+
data.tar.gz: 505e1862730380c1f0144969163e9de58deb212a5f6ab179f61e432572de39f137784c63c7714dec7ba9132ba4d51b2e8ad6f888f5367115ab42ff101e5c3b4b
|
|
@@ -37,7 +37,7 @@ module Deliver
|
|
|
37
37
|
optional: true,
|
|
38
38
|
env_name: "DELIVER_IPA_PATH",
|
|
39
39
|
description: "Path to your ipa file",
|
|
40
|
-
default_value: Dir["*.ipa"].
|
|
40
|
+
default_value: Dir["*.ipa"].sort_by { |x| File.mtime(x) }.last,
|
|
41
41
|
verify_block: proc do |value|
|
|
42
42
|
UI.user_error!("Could not find ipa file at path '#{File.expand_path(value)}'") unless File.exist?(value)
|
|
43
43
|
UI.user_error!("'#{value}' doesn't seem to be an ipa file") unless value.end_with?(".ipa")
|
|
@@ -51,7 +51,7 @@ module Deliver
|
|
|
51
51
|
optional: true,
|
|
52
52
|
env_name: "DELIVER_PKG_PATH",
|
|
53
53
|
description: "Path to your pkg file",
|
|
54
|
-
default_value: Dir["*.pkg"].
|
|
54
|
+
default_value: Dir["*.pkg"].sort_by { |x| File.mtime(x) }.last,
|
|
55
55
|
verify_block: proc do |value|
|
|
56
56
|
UI.user_error!("Could not find pkg file at path '#{File.expand_path(value)}'") unless File.exist?(value)
|
|
57
57
|
UI.user_error!("'#{value}' doesn't seem to be a pkg file") unless value.end_with?(".pkg")
|
|
@@ -65,7 +65,7 @@ module Fastlane
|
|
|
65
65
|
platform = Actions.lane_context[Actions::SharedValues::PLATFORM_NAME]
|
|
66
66
|
|
|
67
67
|
if platform == :ios or platform.nil?
|
|
68
|
-
ipa_path_default = Dir["*.ipa"].last
|
|
68
|
+
ipa_path_default = Dir["*.ipa"].sort_by { |x| File.mtime(x) }.last
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
if platform == :android
|
|
@@ -1,36 +1,54 @@
|
|
|
1
1
|
module FastlaneCore
|
|
2
2
|
class BuildWatcher
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
class << self
|
|
4
|
+
# @return The build we waited for. This method will always return a build
|
|
5
|
+
def wait_for_build_processing_to_be_complete(app_id: nil, platform: nil)
|
|
6
|
+
# First, find the train and build version we want to watch for
|
|
7
|
+
watched_build = watching_build(app_id: app_id, platform: platform)
|
|
8
|
+
UI.crash!("Could not find a build for app: #{app_id} on platform: #{platform}") if watched_build.nil?
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
loop do
|
|
11
|
+
matched_build = matching_build(watched_build: watched_build, app_id: app_id, platform: platform)
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
UI.message("Waiting for iTunes Connect to finish processing the new build (#{watching_build.train_version} - #{watching_build.build_version})")
|
|
13
|
+
report_status(build: matched_build)
|
|
13
14
|
|
|
15
|
+
if matched_build && matched_build.processed?
|
|
16
|
+
return matched_build
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
sleep 10
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def watching_build(app_id: nil, platform: nil)
|
|
26
|
+
processing_builds = Spaceship::TestFlight::Build.all_processing_builds(app_id: app_id, platform: platform)
|
|
27
|
+
|
|
28
|
+
watched_build = processing_builds.sort_by(&:upload_date).last
|
|
29
|
+
watched_build || Spaceship::TestFlight::Build.latest(app_id: app_id, platform: platform)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def matching_build(watched_build: nil, app_id: nil, platform: nil)
|
|
33
|
+
matched_builds = Spaceship::TestFlight::Build.builds_for_train(app_id: app_id, platform: platform, train_version: watched_build.train_version)
|
|
34
|
+
matched_builds.find { |build| build.build_version == watched_build.build_version }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def report_status(build: nil)
|
|
14
38
|
# Due to iTunes Connect, builds disappear from the build list alltogether
|
|
15
39
|
# after they finished processing. Before returning this build, we have to
|
|
16
40
|
# wait for the build to appear in the build list again
|
|
17
41
|
# As this method is very often used to wait for a build, and then do something
|
|
18
42
|
# with it, we have to be sure that the build actually is ready
|
|
19
|
-
|
|
20
|
-
matching_builds = Spaceship::TestFlight::Build.builds_for_train(app_id: app_id, platform: platform, train_version: watching_build.train_version)
|
|
21
|
-
matching_build = matching_builds.find { |build| build.build_version == watching_build.build_version }
|
|
22
|
-
|
|
23
|
-
if matching_build.nil?
|
|
43
|
+
if build.nil?
|
|
24
44
|
UI.message("Build doesn't show up in the build list any more, waiting for it to appear again")
|
|
25
|
-
elsif
|
|
26
|
-
UI.success("Build #{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
elsif build.active?
|
|
46
|
+
UI.success("Build #{build.train_version} - #{build.build_version} is already being tested")
|
|
47
|
+
elsif build.ready_to_submit? || build.export_compliance_missing?
|
|
48
|
+
UI.success("Successfully finished processing the build #{build.train_version} - #{build.build_version}")
|
|
49
|
+
else
|
|
50
|
+
UI.message("Waiting for iTunes Connect to finish processing the new build (#{build.train_version} - #{build.build_version})")
|
|
31
51
|
end
|
|
32
|
-
|
|
33
|
-
sleep 10
|
|
34
52
|
end
|
|
35
53
|
end
|
|
36
54
|
end
|
|
@@ -118,11 +118,6 @@ module FastlaneCore
|
|
|
118
118
|
FastlaneCore::Env.truthy?("TERM_PROGRAM_VERSION")
|
|
119
119
|
end
|
|
120
120
|
|
|
121
|
-
# Does the user use iTerm?
|
|
122
|
-
def self.iterm?
|
|
123
|
-
FastlaneCore::Env.truthy?("ITERM_SESSION_ID")
|
|
124
|
-
end
|
|
125
|
-
|
|
126
121
|
# Logs base directory
|
|
127
122
|
def self.buildlog_path
|
|
128
123
|
return ENV["FL_BUILDLOG_PATH"] || "~/Library/Logs"
|
|
@@ -37,15 +37,20 @@ module Pilot
|
|
|
37
37
|
UI.message("If you want to skip waiting for the processing to be finished, use the `skip_waiting_for_build_processing` option")
|
|
38
38
|
latest_build = FastlaneCore::BuildWatcher.wait_for_build_processing_to_be_complete(app_id: app.apple_id, platform: platform)
|
|
39
39
|
|
|
40
|
-
distribute(options, latest_build)
|
|
40
|
+
distribute(options, build: latest_build)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
def distribute(options, build)
|
|
43
|
+
def distribute(options, build: nil)
|
|
44
44
|
start(options)
|
|
45
45
|
if config[:apple_id].to_s.length == 0 and config[:app_identifier].to_s.length == 0
|
|
46
46
|
config[:app_identifier] = UI.input("App Identifier: ")
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
build ||= Spaceship::TestFlight::Build.latest(app_id: app.apple_id, platform: fetch_app_platform)
|
|
50
|
+
if build.nil?
|
|
51
|
+
UI.user_error!("No build to distribute!")
|
|
52
|
+
end
|
|
53
|
+
|
|
49
54
|
if should_update_build_information(options)
|
|
50
55
|
build.update_build_information!(whats_new: options[:changelog], description: options[:beta_app_description], feedback_email: options[:beta_app_feedback_email])
|
|
51
56
|
UI.success "Successfully set the changelog and/or description for build"
|
data/pilot/lib/pilot/options.rb
CHANGED
|
@@ -32,7 +32,7 @@ module Pilot
|
|
|
32
32
|
optional: true,
|
|
33
33
|
env_name: "PILOT_IPA",
|
|
34
34
|
description: "Path to the ipa file to upload",
|
|
35
|
-
default_value: Dir["*.ipa"].
|
|
35
|
+
default_value: Dir["*.ipa"].sort_by { |x| File.mtime(x) }.last,
|
|
36
36
|
verify_block: proc do |value|
|
|
37
37
|
UI.user_error!("Could not find ipa file at path '#{value}'") unless File.exist? value
|
|
38
38
|
UI.user_error!("'#{value}' doesn't seem to be an ipa file") unless value.end_with? ".ipa"
|
|
@@ -13,7 +13,7 @@ module Pilot
|
|
|
13
13
|
tester = find_or_create_tester(email: config[:email], first_name: config[:first_name], last_name: config[:last_name])
|
|
14
14
|
|
|
15
15
|
begin
|
|
16
|
-
groups = add_tester_to_groups!(tester: tester, app: app, groups: config[:groups])
|
|
16
|
+
groups = Spaceship::TestFlight::Group.add_tester_to_groups!(tester: tester, app: app, groups: config[:groups])
|
|
17
17
|
if tester.kind_of?(Spaceship::Tunes::Tester::Internal)
|
|
18
18
|
UI.success("Successfully added tester to app #{app.name}")
|
|
19
19
|
else
|
|
@@ -59,7 +59,7 @@ module Pilot
|
|
|
59
59
|
test_flight_tester.remove_from_app!(app_id: app.apple_id)
|
|
60
60
|
UI.success("Successfully removed tester, #{test_flight_tester.email}, from app: #{app.name}")
|
|
61
61
|
else
|
|
62
|
-
groups = remove_tester_from_groups!(tester: tester, app: app, groups: config[:groups])
|
|
62
|
+
groups = Spaceship::TestFlight::Group.remove_tester_from_groups!(tester: tester, app: app, groups: config[:groups])
|
|
63
63
|
group_names = groups.map(&:name).join(", ")
|
|
64
64
|
UI.success("Successfully removed tester #{tester.email} from app #{app.name} in group(s) #{group_names}")
|
|
65
65
|
end
|
|
@@ -109,40 +109,6 @@ module Pilot
|
|
|
109
109
|
raise ex
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
-
def perform_for_groups_in_app(app: nil, groups: nil, &block)
|
|
113
|
-
if groups.nil?
|
|
114
|
-
default_external_group = app.default_external_group
|
|
115
|
-
if default_external_group.nil?
|
|
116
|
-
UI.user_error!("The app #{app.name} does not have a default external group. Please make sure to pass group names to the `:groups` option.")
|
|
117
|
-
end
|
|
118
|
-
test_flight_groups = [default_external_group]
|
|
119
|
-
else
|
|
120
|
-
test_flight_groups = Spaceship::TestFlight::Group.filter_groups(app_id: app.apple_id) do |group|
|
|
121
|
-
groups.include?(group.name)
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
UI.user_error!("There are no groups available matching the names passed to the `:groups` option.") if test_flight_groups.empty?
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
test_flight_groups.each(&block)
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def add_tester_to_groups!(tester: nil, app: nil, groups: nil)
|
|
131
|
-
if tester.kind_of?(Spaceship::Tunes::Tester::Internal)
|
|
132
|
-
Spaceship::TestFlight::Group.internal_group(app_id: app.apple_id).add_tester!(tester)
|
|
133
|
-
else
|
|
134
|
-
perform_for_groups_in_app(app: app, groups: groups) { |group| group.add_tester!(tester) }
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
def remove_tester_from_groups!(tester: nil, app: nil, groups: nil)
|
|
139
|
-
if tester.kind_of?(Spaceship::Tunes::Tester::Internal)
|
|
140
|
-
Spaceship::TestFlight::Group.internal_group(app_id: app.apple_id).remove_tester!(tester)
|
|
141
|
-
else
|
|
142
|
-
perform_for_groups_in_app(app: app, groups: groups) { |group| group.remove_tester!(tester) }
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
112
|
def list_testers_by_app(app_filter)
|
|
147
113
|
app = Spaceship::Application.find(app_filter)
|
|
148
114
|
UI.user_error!("Couldn't find app with '#{app_filter}'") unless app
|
|
@@ -4,6 +4,18 @@ module Spaceship::TestFlight
|
|
|
4
4
|
@client ||= Client.client_with_authorization_from(Spaceship::Tunes.client)
|
|
5
5
|
end
|
|
6
6
|
|
|
7
|
+
##
|
|
8
|
+
# Have subclasses inherit the client from their superclass
|
|
9
|
+
#
|
|
10
|
+
# Essentially, we are making a class-inheritable-accessor as described here:
|
|
11
|
+
# https://apidock.com/rails/v4.2.7/Class/class_attribute
|
|
12
|
+
def self.inherited(subclass)
|
|
13
|
+
this_class = self
|
|
14
|
+
subclass.define_singleton_method(:client) do
|
|
15
|
+
this_class.client
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
7
19
|
def to_json
|
|
8
20
|
raw_data.to_json
|
|
9
21
|
end
|
|
@@ -67,16 +67,17 @@ module Spaceship::TestFlight
|
|
|
67
67
|
BUILD_STATES = {
|
|
68
68
|
processing: 'testflight.build.state.processing',
|
|
69
69
|
active: 'testflight.build.state.testing.active',
|
|
70
|
-
|
|
70
|
+
ready_to_submit: 'testflight.build.state.submit.ready',
|
|
71
|
+
ready_to_test: 'testflight.build.state.testing.ready',
|
|
71
72
|
export_compliance_missing: 'testflight.build.state.export.compliance.missing'
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
# Find a Build by `build_id`.
|
|
75
|
+
# Find a Build by `build_id`.
|
|
75
76
|
#
|
|
76
77
|
# @return (Spaceship::TestFlight::Build)
|
|
77
78
|
def self.find(app_id: nil, build_id: nil)
|
|
78
79
|
attrs = client.get_build(app_id: app_id, build_id: build_id)
|
|
79
|
-
self.new(attrs)
|
|
80
|
+
self.new(attrs)
|
|
80
81
|
end
|
|
81
82
|
|
|
82
83
|
def self.all(app_id: nil, platform: nil)
|
|
@@ -104,13 +105,17 @@ module Spaceship::TestFlight
|
|
|
104
105
|
#
|
|
105
106
|
# Note: this will overwrite any non-saved changes to the object
|
|
106
107
|
#
|
|
107
|
-
# @return (
|
|
108
|
+
# @return (Spaceship::Base::DataHash) the raw_data of the build.
|
|
108
109
|
def reload
|
|
109
110
|
self.raw_data = self.class.find(app_id: app_id, build_id: id).raw_data
|
|
110
111
|
end
|
|
111
112
|
|
|
112
113
|
def ready_to_submit?
|
|
113
|
-
external_state == BUILD_STATES[:
|
|
114
|
+
external_state == BUILD_STATES[:ready_to_submit]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def ready_to_test?
|
|
118
|
+
external_state == BUILD_STATES[:ready_to_test]
|
|
114
119
|
end
|
|
115
120
|
|
|
116
121
|
def active?
|
|
@@ -125,6 +130,10 @@ module Spaceship::TestFlight
|
|
|
125
130
|
external_state == BUILD_STATES[:export_compliance_missing]
|
|
126
131
|
end
|
|
127
132
|
|
|
133
|
+
def self.processed?
|
|
134
|
+
active? || ready_to_submit? || export_compliance_missing?
|
|
135
|
+
end
|
|
136
|
+
|
|
128
137
|
# Getting builds from BuildTrains only gets a partial Build object
|
|
129
138
|
# We are then requesting the full build from iTC when we need to access
|
|
130
139
|
# any of the variables below, because they are not inlcuded in the partial Build objects
|
|
@@ -159,13 +168,14 @@ module Spaceship::TestFlight
|
|
|
159
168
|
end
|
|
160
169
|
|
|
161
170
|
def update_build_information!(description: nil, feedback_email: nil, whats_new: nil)
|
|
162
|
-
test_info.description = description
|
|
163
|
-
test_info.feedback_email = feedback_email
|
|
164
|
-
test_info.whats_new = whats_new
|
|
171
|
+
test_info.description = description if description
|
|
172
|
+
test_info.feedback_email = feedback_email if feedback_email
|
|
173
|
+
test_info.whats_new = whats_new if whats_new
|
|
165
174
|
save!
|
|
166
175
|
end
|
|
167
176
|
|
|
168
177
|
def submit_for_testflight_review!
|
|
178
|
+
return if ready_to_test?
|
|
169
179
|
client.post_for_testflight_review(app_id: app_id, build_id: id, build: self)
|
|
170
180
|
end
|
|
171
181
|
|
|
@@ -1,20 +1,34 @@
|
|
|
1
1
|
module Spaceship::TestFlight
|
|
2
2
|
class Client < Spaceship::Client
|
|
3
|
+
##
|
|
4
|
+
# Spaceship HTTP client for the testflight API.
|
|
5
|
+
#
|
|
6
|
+
# This client is solely responsible for the making HTTP requests and
|
|
7
|
+
# parsing their responses. Parameters should be either named parameters, or
|
|
8
|
+
# for large request data bodies, pass in anything that can resond to
|
|
9
|
+
# `to_json`.
|
|
10
|
+
#
|
|
11
|
+
# Each request method should validate the required parameters. A required parameter is one that would result in 400-range response if it is not supplied.
|
|
12
|
+
# Each request method should make only one request. For more high-level logic, put code in the data models.
|
|
13
|
+
|
|
3
14
|
def self.hostname
|
|
4
15
|
'https://itunesconnect.apple.com/testflight/v2/'
|
|
5
16
|
end
|
|
6
17
|
|
|
18
|
+
##
|
|
19
|
+
# @!group Build trains API
|
|
20
|
+
##
|
|
21
|
+
|
|
7
22
|
# Returns an array of all available build trains (not the builds they include)
|
|
8
|
-
def get_build_trains(app_id: nil, platform:
|
|
23
|
+
def get_build_trains(app_id: nil, platform: "ios")
|
|
9
24
|
assert_required_params(__method__, binding)
|
|
10
|
-
|
|
25
|
+
|
|
11
26
|
response = request(:get, "providers/#{team_id}/apps/#{app_id}/platforms/#{platform}/trains")
|
|
12
27
|
handle_response(response)
|
|
13
28
|
end
|
|
14
29
|
|
|
15
|
-
def get_builds_for_train(app_id: nil, platform:
|
|
30
|
+
def get_builds_for_train(app_id: nil, platform: "ios", train_version: nil)
|
|
16
31
|
assert_required_params(__method__, binding)
|
|
17
|
-
platform ||= "ios"
|
|
18
32
|
|
|
19
33
|
response = request(:get, "providers/#{team_id}/apps/#{app_id}/platforms/#{platform}/trains/#{train_version}/builds")
|
|
20
34
|
handle_response(response)
|
|
@@ -34,8 +48,72 @@ module Spaceship::TestFlight
|
|
|
34
48
|
handle_response(response)
|
|
35
49
|
end
|
|
36
50
|
|
|
51
|
+
##
|
|
52
|
+
# @!group Builds API
|
|
53
|
+
##
|
|
54
|
+
|
|
55
|
+
def get_build(app_id: nil, build_id: nil)
|
|
56
|
+
assert_required_params(__method__, binding)
|
|
57
|
+
|
|
58
|
+
response = request(:get, "providers/#{team_id}/apps/#{app_id}/builds/#{build_id}")
|
|
59
|
+
handle_response(response)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def put_build(app_id: nil, build_id: nil, build: nil)
|
|
63
|
+
assert_required_params(__method__, binding)
|
|
64
|
+
|
|
65
|
+
response = request(:put) do |req|
|
|
66
|
+
req.url "providers/#{team_id}/apps/#{app_id}/builds/#{build_id}"
|
|
67
|
+
req.body = build.to_json
|
|
68
|
+
req.headers['Content-Type'] = 'application/json'
|
|
69
|
+
end
|
|
70
|
+
handle_response(response)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def post_for_testflight_review(app_id: nil, build_id: nil, build: nil)
|
|
74
|
+
assert_required_params(__method__, binding)
|
|
75
|
+
|
|
76
|
+
response = request(:post) do |req|
|
|
77
|
+
req.url "providers/#{team_id}/apps/#{app_id}/builds/#{build_id}/review"
|
|
78
|
+
req.body = build.to_json
|
|
79
|
+
req.headers['Content-Type'] = 'application/json'
|
|
80
|
+
end
|
|
81
|
+
handle_response(response)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
##
|
|
85
|
+
# @!group Groups API
|
|
86
|
+
##
|
|
87
|
+
|
|
88
|
+
def get_groups(app_id: nil)
|
|
89
|
+
assert_required_params(__method__, binding)
|
|
90
|
+
|
|
91
|
+
response = request(:get, "/testflight/v2/providers/#{team_id}/apps/#{app_id}/groups")
|
|
92
|
+
handle_response(response)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def add_group_to_build(app_id: nil, group_id: nil, build_id: nil)
|
|
96
|
+
assert_required_params(__method__, binding)
|
|
97
|
+
|
|
98
|
+
body = {
|
|
99
|
+
'groupId' => group_id,
|
|
100
|
+
'buildId' => build_id
|
|
101
|
+
}
|
|
102
|
+
response = request(:put) do |req|
|
|
103
|
+
req.url "providers/#{team_id}/apps/#{app_id}/groups/#{group_id}/builds/#{build_id}"
|
|
104
|
+
req.body = body.to_json
|
|
105
|
+
req.headers['Content-Type'] = 'application/json'
|
|
106
|
+
end
|
|
107
|
+
handle_response(response)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
##
|
|
111
|
+
# @!group Testers API
|
|
112
|
+
##
|
|
113
|
+
|
|
37
114
|
def post_tester(app_id: nil, tester: nil)
|
|
38
115
|
assert_required_params(__method__, binding)
|
|
116
|
+
|
|
39
117
|
url = "providers/#{team_id}/apps/#{app_id}/testers"
|
|
40
118
|
response = request(:post) do |req|
|
|
41
119
|
req.url url
|
|
@@ -51,6 +129,7 @@ module Spaceship::TestFlight
|
|
|
51
129
|
|
|
52
130
|
def put_tester_to_group(app_id: nil, tester_id: nil, group_id: nil)
|
|
53
131
|
assert_required_params(__method__, binding)
|
|
132
|
+
|
|
54
133
|
# Then we can add the tester to the group that allows the app to test
|
|
55
134
|
# This is easy enough, we already have all this data. We don't need any response from the previous request
|
|
56
135
|
url = "providers/#{team_id}/apps/#{app_id}/groups/#{group_id}/testers/#{tester_id}"
|
|
@@ -67,6 +146,7 @@ module Spaceship::TestFlight
|
|
|
67
146
|
|
|
68
147
|
def delete_tester_from_group(group_id: nil, tester_id: nil, app_id: nil)
|
|
69
148
|
assert_required_params(__method__, binding)
|
|
149
|
+
|
|
70
150
|
url = "providers/#{team_id}/apps/#{app_id}/groups/#{group_id}/testers/#{tester_id}"
|
|
71
151
|
response = request(:delete) do |req|
|
|
72
152
|
req.url url
|
|
@@ -75,53 +155,25 @@ module Spaceship::TestFlight
|
|
|
75
155
|
handle_response(response)
|
|
76
156
|
end
|
|
77
157
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
handle_response(response)
|
|
82
|
-
end
|
|
158
|
+
##
|
|
159
|
+
# @!group TestInfo
|
|
160
|
+
##
|
|
83
161
|
|
|
84
|
-
def
|
|
162
|
+
def put_testinfo(app_id: nil, testinfo: nil)
|
|
85
163
|
assert_required_params(__method__, binding)
|
|
86
|
-
response = request(:put) do |req|
|
|
87
|
-
req.url "providers/#{team_id}/apps/#{app_id}/builds/#{build_id}"
|
|
88
|
-
req.body = build.to_json
|
|
89
|
-
req.headers['Content-Type'] = 'application/json'
|
|
90
|
-
end
|
|
91
|
-
handle_response(response)
|
|
92
|
-
end
|
|
93
164
|
|
|
94
|
-
def post_for_testflight_review(app_id: nil, build_id: nil, build: nil)
|
|
95
|
-
assert_required_params(__method__, binding)
|
|
96
|
-
response = request(:post) do |req|
|
|
97
|
-
req.url "providers/#{team_id}/apps/#{app_id}/builds/#{build_id}/review"
|
|
98
|
-
req.body = build.to_json
|
|
99
|
-
req.headers['Content-Type'] = 'application/json'
|
|
100
|
-
end
|
|
101
|
-
handle_response(response)
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def get_groups(app_id: nil)
|
|
105
|
-
assert_required_params(__method__, binding)
|
|
106
|
-
response = request(:get, "/testflight/v2/providers/#{team_id}/apps/#{app_id}/groups")
|
|
107
|
-
handle_response(response)
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def add_group_to_build(app_id: nil, group_id: nil, build_id: nil)
|
|
111
|
-
body = {
|
|
112
|
-
'groupId' => group_id,
|
|
113
|
-
'buildId' => build_id
|
|
114
|
-
}
|
|
115
165
|
response = request(:put) do |req|
|
|
116
|
-
req.url "providers/#{team_id}/apps/#{app_id}/
|
|
117
|
-
req.body =
|
|
166
|
+
req.url "providers/#{team_id}/apps/#{app_id}/testInfo"
|
|
167
|
+
req.body = testinfo.to_json
|
|
118
168
|
req.headers['Content-Type'] = 'application/json'
|
|
119
169
|
end
|
|
120
170
|
handle_response(response)
|
|
121
171
|
end
|
|
122
172
|
|
|
173
|
+
protected
|
|
174
|
+
|
|
123
175
|
def handle_response(response)
|
|
124
|
-
if (200
|
|
176
|
+
if (200...300).cover?(response.status) && (response.body.nil? || response.body.empty?)
|
|
125
177
|
return
|
|
126
178
|
end
|
|
127
179
|
|
|
@@ -11,16 +11,13 @@ module Spaceship::TestFlight
|
|
|
11
11
|
'id' => :id,
|
|
12
12
|
'name' => :name,
|
|
13
13
|
'isInternalGroup' => :is_internal_group,
|
|
14
|
+
'appAdamId' => :app_id,
|
|
14
15
|
'isDefaultExternalGroup' => :is_default_external_group
|
|
15
16
|
})
|
|
16
17
|
|
|
17
18
|
def self.all(app_id: nil)
|
|
18
19
|
groups = client.get_groups(app_id: app_id)
|
|
19
|
-
groups.map
|
|
20
|
-
current_element = self.new(g)
|
|
21
|
-
current_element.app_id = app_id
|
|
22
|
-
current_element
|
|
23
|
-
end
|
|
20
|
+
groups.map { |g| self.new(g) }
|
|
24
21
|
end
|
|
25
22
|
|
|
26
23
|
def self.find(app_id: nil, group_name: nil)
|
|
@@ -59,6 +56,22 @@ module Spaceship::TestFlight
|
|
|
59
56
|
client.delete_tester_from_group(group_id: self.id, tester_id: tester.tester_id, app_id: self.app_id)
|
|
60
57
|
end
|
|
61
58
|
|
|
59
|
+
def self.add_tester_to_groups!(tester: nil, app: nil, groups: nil)
|
|
60
|
+
if tester.kind_of?(Spaceship::Tunes::Tester::Internal)
|
|
61
|
+
self.internal_group(app_id: app.apple_id).add_tester!(tester)
|
|
62
|
+
else
|
|
63
|
+
self.perform_for_groups_in_app(app: app, groups: groups) { |group| group.add_tester!(tester) }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.remove_tester_from_groups!(tester: nil, app: nil, groups: nil)
|
|
68
|
+
if tester.kind_of?(Spaceship::Tunes::Tester::Internal)
|
|
69
|
+
self.internal_group(app_id: app.apple_id).remove_tester!(tester)
|
|
70
|
+
else
|
|
71
|
+
self.perform_for_groups_in_app(app: app, groups: groups) { |group| group.remove_tester!(tester) }
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
62
75
|
def default_external_group?
|
|
63
76
|
is_default_external_group
|
|
64
77
|
end
|
|
@@ -66,5 +79,23 @@ module Spaceship::TestFlight
|
|
|
66
79
|
def internal_group?
|
|
67
80
|
is_internal_group
|
|
68
81
|
end
|
|
82
|
+
|
|
83
|
+
def self.perform_for_groups_in_app(app: nil, groups: nil, &block)
|
|
84
|
+
if groups.nil?
|
|
85
|
+
default_external_group = app.default_external_group
|
|
86
|
+
if default_external_group.nil?
|
|
87
|
+
UI.user_error!("The app #{app.name} does not have a default external group. Please make sure to pass group names to the `:groups` option.")
|
|
88
|
+
end
|
|
89
|
+
test_flight_groups = [default_external_group]
|
|
90
|
+
else
|
|
91
|
+
test_flight_groups = self.filter_groups(app_id: app.apple_id) do |group|
|
|
92
|
+
groups.include?(group.name)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
UI.user_error!("There are no groups available matching the names passed to the `:groups` option.") if test_flight_groups.empty?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
test_flight_groups.each(&block)
|
|
99
|
+
end
|
|
69
100
|
end
|
|
70
101
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fastlane
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.28.
|
|
4
|
+
version: 2.28.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Felix Krause
|
|
@@ -15,7 +15,7 @@ authors:
|
|
|
15
15
|
autorequire:
|
|
16
16
|
bindir: bin
|
|
17
17
|
cert_chain: []
|
|
18
|
-
date: 2017-04-
|
|
18
|
+
date: 2017-04-28 00:00:00.000000000 Z
|
|
19
19
|
dependencies:
|
|
20
20
|
- !ruby/object:Gem::Dependency
|
|
21
21
|
name: slack-notifier
|
|
@@ -743,6 +743,20 @@ dependencies:
|
|
|
743
743
|
- - "~>"
|
|
744
744
|
- !ruby/object:Gem::Version
|
|
745
745
|
version: 0.8.1
|
|
746
|
+
- !ruby/object:Gem::Dependency
|
|
747
|
+
name: sinatra
|
|
748
|
+
requirement: !ruby/object:Gem::Requirement
|
|
749
|
+
requirements:
|
|
750
|
+
- - "~>"
|
|
751
|
+
- !ruby/object:Gem::Version
|
|
752
|
+
version: 1.4.8
|
|
753
|
+
type: :development
|
|
754
|
+
prerelease: false
|
|
755
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
756
|
+
requirements:
|
|
757
|
+
- - "~>"
|
|
758
|
+
- !ruby/object:Gem::Version
|
|
759
|
+
version: 1.4.8
|
|
746
760
|
description: The easiest way to automate beta deployments and releases for your iOS
|
|
747
761
|
and Android apps
|
|
748
762
|
email:
|