fastlane 2.28.0.beta.20170420010017 → 2.28.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/fastlane/lib/fastlane/actions/hockey.rb +9 -0
- data/fastlane/lib/fastlane/version.rb +1 -1
- data/fastlane_core/lib/fastlane_core.rb +1 -0
- data/fastlane_core/lib/fastlane_core/build_watcher.rb +37 -0
- data/pilot/lib/pilot/build_manager.rb +23 -100
- data/pilot/lib/pilot/tester_manager.rb +5 -12
- data/snapshot/lib/snapshot/setup.rb +0 -2
- data/spaceship/lib/spaceship.rb +1 -0
- data/spaceship/lib/spaceship/base.rb +10 -0
- data/spaceship/lib/spaceship/client.rb +71 -2
- data/spaceship/lib/spaceship/test_flight.rb +9 -0
- data/spaceship/lib/spaceship/test_flight/base.rb +11 -0
- data/spaceship/lib/spaceship/test_flight/beta_review_info.rb +16 -0
- data/spaceship/lib/spaceship/test_flight/build.rb +172 -0
- data/spaceship/lib/spaceship/test_flight/build_trains.rb +42 -0
- data/spaceship/lib/spaceship/test_flight/client.rb +145 -0
- data/spaceship/lib/spaceship/test_flight/export_compliance.rb +10 -0
- data/spaceship/lib/spaceship/test_flight/group.rb +51 -0
- data/spaceship/lib/spaceship/test_flight/test_info.rb +34 -0
- data/spaceship/lib/spaceship/tunes/application.rb +2 -52
- data/spaceship/lib/spaceship/tunes/tester.rb +9 -8
- metadata +55 -17
- data/snapshot/lib/assets/SnapshotHelper2-3.swift +0 -164
@@ -0,0 +1,16 @@
|
|
1
|
+
module Spaceship::TestFlight
|
2
|
+
class BetaReviewInfo < Base
|
3
|
+
attr_accessor :contact_first_name, :contact_last_name, :contact_phone, :contact_email
|
4
|
+
attr_accessor :demo_account_name, :demo_account_password, :demo_account_required
|
5
|
+
|
6
|
+
attr_mapping({
|
7
|
+
'contactFirstName' => :contact_first_name,
|
8
|
+
'contactLastName' => :contact_last_name,
|
9
|
+
'contactPhone' => :contact_phone,
|
10
|
+
'contactEmail' => :contact_email,
|
11
|
+
'demoAccountName' => :demo_account_name,
|
12
|
+
'demoAccountPassword' => :demo_account_password,
|
13
|
+
'demoAccountRequired' => :demo_account_required
|
14
|
+
})
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module Spaceship::TestFlight
|
4
|
+
class Build < Base
|
5
|
+
# @example
|
6
|
+
# "com.sample.app"
|
7
|
+
attr_accessor :bundle_id
|
8
|
+
|
9
|
+
# @example
|
10
|
+
# "testflight.build.state.testing.active"
|
11
|
+
# @example
|
12
|
+
# "testflight.build.state.processing"
|
13
|
+
attr_accessor :internal_state
|
14
|
+
|
15
|
+
# @example
|
16
|
+
# "testflight.build.state.submit.ready"
|
17
|
+
# @example
|
18
|
+
# "testflight.build.state.processing"
|
19
|
+
attr_accessor :external_state
|
20
|
+
|
21
|
+
# Internal build ID (int)
|
22
|
+
# @example
|
23
|
+
# 19285309
|
24
|
+
attr_accessor :id
|
25
|
+
|
26
|
+
# @example
|
27
|
+
# "1.0"
|
28
|
+
attr_accessor :train_version
|
29
|
+
|
30
|
+
# @example
|
31
|
+
# "152"
|
32
|
+
attr_accessor :build_version
|
33
|
+
|
34
|
+
attr_accessor :beta_review_info
|
35
|
+
|
36
|
+
attr_accessor :export_compliance
|
37
|
+
|
38
|
+
attr_accessor :test_info
|
39
|
+
|
40
|
+
attr_accessor :install_count
|
41
|
+
attr_accessor :invite_count
|
42
|
+
attr_accessor :crash_count
|
43
|
+
|
44
|
+
attr_accessor :did_notify
|
45
|
+
|
46
|
+
attr_accessor :upload_date
|
47
|
+
|
48
|
+
attr_mapping({
|
49
|
+
'appAdamId' => :app_id,
|
50
|
+
'providerId' => :provider_id,
|
51
|
+
'bundleId' => :bundle_id,
|
52
|
+
'trainVersion' => :train_version,
|
53
|
+
'buildVersion' => :build_version,
|
54
|
+
'betaReviewInfo' => :beta_review_info,
|
55
|
+
'exportCompliance' => :export_compliance,
|
56
|
+
'internalState' => :internal_state,
|
57
|
+
'externalState' => :external_state,
|
58
|
+
'testInfo' => :test_info,
|
59
|
+
'installCount' => :install_count,
|
60
|
+
'inviteCount' => :invite_count,
|
61
|
+
'crashCount' => :crash_count,
|
62
|
+
'didNotify' => :did_notify,
|
63
|
+
'uploadDate' => :upload_date,
|
64
|
+
'id' => :id
|
65
|
+
})
|
66
|
+
|
67
|
+
BUILD_STATES = {
|
68
|
+
processing: 'testflight.build.state.processing',
|
69
|
+
active: 'testflight.build.state.testing.active',
|
70
|
+
ready: 'testflight.build.state.submit.ready',
|
71
|
+
export_compliance_missing: 'testflight.build.state.export.compliance.missing'
|
72
|
+
}
|
73
|
+
|
74
|
+
# Find a Build by `build_id`. Returns `nil` if can't find it.
|
75
|
+
#
|
76
|
+
# @return (Spaceship::TestFlight::Build)
|
77
|
+
def self.find(app_id: nil, build_id: nil)
|
78
|
+
attrs = client.get_build(app_id: app_id, build_id: build_id)
|
79
|
+
self.new(attrs) if attrs
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.all(app_id: nil, platform: nil)
|
83
|
+
trains = BuildTrains.all(app_id: app_id, platform: platform)
|
84
|
+
trains.values.flatten
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.builds_for_train(app_id: nil, platform: nil, train_version: nil)
|
88
|
+
builds_data = client.get_builds_for_train(app_id: app_id, platform: platform, train_version: train_version)
|
89
|
+
builds_data.map { |data| self.new(data) }
|
90
|
+
end
|
91
|
+
|
92
|
+
# Just the builds, as a flat array, that are still processing
|
93
|
+
def self.all_processing_builds(app_id: nil, platform: nil)
|
94
|
+
all(app_id: app_id, platform: platform).find_all(&:processing?)
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.latest(app_id: nil, platform: nil)
|
98
|
+
all(app_id: app_id, platform: platform).sort_by(&:upload_date).last
|
99
|
+
end
|
100
|
+
|
101
|
+
# reload the raw_data resource for this build.
|
102
|
+
# This is useful when we start with a partial build response as returned by the BuildTrains,
|
103
|
+
# but then need to look up some attributes on the full build representation.
|
104
|
+
#
|
105
|
+
# Note: this will overwrite any non-saved changes to the object
|
106
|
+
#
|
107
|
+
# @return (Spaceceship::Base::DataHash) the raw_data of the build.
|
108
|
+
def reload
|
109
|
+
self.raw_data = self.class.find(app_id: app_id, build_id: id).raw_data
|
110
|
+
end
|
111
|
+
|
112
|
+
def ready_to_submit?
|
113
|
+
external_state == BUILD_STATES[:ready]
|
114
|
+
end
|
115
|
+
|
116
|
+
def active?
|
117
|
+
external_state == BUILD_STATES[:active]
|
118
|
+
end
|
119
|
+
|
120
|
+
def processing?
|
121
|
+
external_state == BUILD_STATES[:processing]
|
122
|
+
end
|
123
|
+
|
124
|
+
# Getting builds from BuildTrains only gets a partial Build object
|
125
|
+
# We are then requesting the full build from iTC when we need to access
|
126
|
+
# any of the variables below, because they are not inlcuded in the partial Build objects
|
127
|
+
#
|
128
|
+
# `super` here calls `beta_review_info` as defined by the `attr_mapping` above.
|
129
|
+
# @return (Spaceship::TestFlight::BetaReviewInfo)
|
130
|
+
def beta_review_info
|
131
|
+
super || reload
|
132
|
+
BetaReviewInfo.new(super)
|
133
|
+
end
|
134
|
+
|
135
|
+
# @return (Spaceship::TestFlight::ExportCompliance)
|
136
|
+
def export_compliance
|
137
|
+
super || reload
|
138
|
+
ExportCompliance.new(super)
|
139
|
+
end
|
140
|
+
|
141
|
+
# @return (Spaceship::TestFlight::TestInfo)
|
142
|
+
def test_info
|
143
|
+
super || reload
|
144
|
+
TestInfo.new(super)
|
145
|
+
end
|
146
|
+
|
147
|
+
# @return (Time) an parsed Time value for the upload_date
|
148
|
+
def upload_date
|
149
|
+
Time.parse(super)
|
150
|
+
end
|
151
|
+
|
152
|
+
# saves the changes to the Build object to TestFlight
|
153
|
+
def save!
|
154
|
+
client.put_build(app_id: app_id, build_id: id, build: self)
|
155
|
+
end
|
156
|
+
|
157
|
+
def update_build_information!(description: nil, feedback_email: nil, whats_new: nil)
|
158
|
+
test_info.description = description
|
159
|
+
test_info.feedback_email = feedback_email
|
160
|
+
test_info.whats_new = whats_new
|
161
|
+
save!
|
162
|
+
end
|
163
|
+
|
164
|
+
def submit_for_testflight_review!
|
165
|
+
client.post_for_testflight_review(app_id: app_id, build_id: id, build: self)
|
166
|
+
end
|
167
|
+
|
168
|
+
def add_group!(group)
|
169
|
+
client.add_group_to_build(app_id: app_id, group_id: group.id, build_id: id)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Spaceship::TestFlight
|
2
|
+
class BuildTrains < Base
|
3
|
+
##
|
4
|
+
# BuildTrains represent the collection of builds for a `train_version`
|
5
|
+
#
|
6
|
+
# Note: builds returned by BuildTrains are _partially_ complete. Properties
|
7
|
+
# such as `exportCompliance`, `testInfo` and many others are not provided.
|
8
|
+
# It is the responsibility of Build to lazy-load the necessary properties.
|
9
|
+
#
|
10
|
+
# See `Spaceship::TestFlight::Build#reload`
|
11
|
+
|
12
|
+
include Enumerable
|
13
|
+
|
14
|
+
def self.all(app_id: nil, platform: nil)
|
15
|
+
data = client.get_build_trains(app_id: app_id, platform: platform)
|
16
|
+
trains = {}
|
17
|
+
data.each do |train_version|
|
18
|
+
builds_data = client.get_builds_for_train(app_id: app_id, platform: platform, train_version: train_version)
|
19
|
+
trains[train_version] = builds_data.map { |attrs| Build.new(attrs) }
|
20
|
+
end
|
21
|
+
|
22
|
+
self.new(trains)
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(trains = {})
|
26
|
+
@trains = trains
|
27
|
+
end
|
28
|
+
|
29
|
+
def get(key)
|
30
|
+
@trains[key]
|
31
|
+
end
|
32
|
+
alias [] get
|
33
|
+
|
34
|
+
def values
|
35
|
+
@trains.values
|
36
|
+
end
|
37
|
+
|
38
|
+
def each(&bock)
|
39
|
+
@tains.each(&block)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module Spaceship::TestFlight
|
2
|
+
class Client < Spaceship::Client
|
3
|
+
def self.hostname
|
4
|
+
'https://itunesconnect.apple.com/testflight/v2/'
|
5
|
+
end
|
6
|
+
|
7
|
+
# Returns an array of all available build trains (not the builds they include)
|
8
|
+
def get_build_trains(app_id: nil, platform: nil)
|
9
|
+
assert_required_params(__method__, binding)
|
10
|
+
platform ||= "ios"
|
11
|
+
response = request(:get, "providers/#{team_id}/apps/#{app_id}/platforms/#{platform}/trains")
|
12
|
+
handle_response(response)
|
13
|
+
end
|
14
|
+
|
15
|
+
def get_builds_for_train(app_id: nil, platform: nil, train_version: nil)
|
16
|
+
assert_required_params(__method__, binding)
|
17
|
+
platform ||= "ios"
|
18
|
+
|
19
|
+
response = request(:get, "providers/#{team_id}/apps/#{app_id}/platforms/#{platform}/trains/#{train_version}/builds")
|
20
|
+
handle_response(response)
|
21
|
+
end
|
22
|
+
|
23
|
+
def post_tester(app_id: nil, tester: nil)
|
24
|
+
assert_required_params(__method__, binding)
|
25
|
+
# First we need to add the tester to the app
|
26
|
+
# It's ok if the tester already exists, we just have to do this... don't ask
|
27
|
+
# This will enable testing for the tester for a given app, as just creating the tester on an account-level
|
28
|
+
# is not enough to add the tester to a group. If this isn't done the next request would fail.
|
29
|
+
# This is a bug we reported to the iTunes Connect team, as it also happens on the iTunes Connect UI on 18. April 2017
|
30
|
+
url = "providers/#{team_id}/apps/#{app_id}/testers"
|
31
|
+
response = request(:post) do |req|
|
32
|
+
req.url url
|
33
|
+
req.body = {
|
34
|
+
"email" => tester.email,
|
35
|
+
"firstName" => tester.last_name,
|
36
|
+
"lastName" => tester.first_name
|
37
|
+
}.to_json
|
38
|
+
req.headers['Content-Type'] = 'application/json'
|
39
|
+
end
|
40
|
+
handle_response(response)
|
41
|
+
end
|
42
|
+
|
43
|
+
def put_test_to_group(app_id: nil, tester_id: nil, group_id: nil)
|
44
|
+
assert_required_params(__method__, binding)
|
45
|
+
# Then we can add the tester to the group that allows the app to test
|
46
|
+
# This is easy enough, we already have all this data. We don't need any response from the previous request
|
47
|
+
url = "providers/#{team_id}/apps/#{app_id}/groups/#{group_id}/testers/#{tester_id}"
|
48
|
+
response = request(:put) do |req|
|
49
|
+
req.url url
|
50
|
+
req.body = {
|
51
|
+
"groupId" => group_id,
|
52
|
+
"testerId" => tester_id
|
53
|
+
}.to_json
|
54
|
+
req.headers['Content-Type'] = 'application/json'
|
55
|
+
end
|
56
|
+
handle_response(response)
|
57
|
+
end
|
58
|
+
|
59
|
+
# def remove_tester_from_group!(group: nil, tester: nil, app_id: nil)
|
60
|
+
def delete_tester_from_group(group_id: nil, tester_id: nil, app_id: nil)
|
61
|
+
assert_required_params(__method__, binding)
|
62
|
+
url = "providers/#{team_id}/apps/#{app_id}/groups/#{group_id}/testers/#{tester_id}"
|
63
|
+
response = request(:delete) do |req|
|
64
|
+
req.url url
|
65
|
+
req.headers['Content-Type'] = 'application/json'
|
66
|
+
end
|
67
|
+
handle_response(response)
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_build(app_id: nil, build_id: nil)
|
71
|
+
assert_required_params(__method__, binding)
|
72
|
+
response = request(:get, "providers/#{team_id}/apps/#{app_id}/builds/#{build_id}")
|
73
|
+
handle_response(response)
|
74
|
+
end
|
75
|
+
|
76
|
+
def put_build(app_id: nil, build_id: nil, build: nil)
|
77
|
+
assert_required_params(__method__, binding)
|
78
|
+
response = request(:put) do |req|
|
79
|
+
req.url "providers/#{team_id}/apps/#{app_id}/builds/#{build_id}"
|
80
|
+
req.body = build.to_json
|
81
|
+
req.headers['Content-Type'] = 'application/json'
|
82
|
+
end
|
83
|
+
handle_response(response)
|
84
|
+
end
|
85
|
+
|
86
|
+
def post_for_testflight_review(app_id: nil, build_id: nil, build: nil)
|
87
|
+
assert_required_params(__method__, binding)
|
88
|
+
response = request(:post) do |req|
|
89
|
+
req.url "providers/#{team_id}/apps/#{app_id}/builds/#{build_id}/review"
|
90
|
+
req.body = build.to_json
|
91
|
+
req.headers['Content-Type'] = 'application/json'
|
92
|
+
end
|
93
|
+
handle_response(response)
|
94
|
+
end
|
95
|
+
|
96
|
+
def get_groups(app_id: nil)
|
97
|
+
assert_required_params(__method__, binding)
|
98
|
+
response = request(:get, "/testflight/v2/providers/#{team_id}/apps/#{app_id}/groups")
|
99
|
+
handle_response(response)
|
100
|
+
end
|
101
|
+
|
102
|
+
def add_group_to_build(app_id: nil, group_id: nil, build_id: nil)
|
103
|
+
body = {
|
104
|
+
'groupId' => group_id,
|
105
|
+
'buildId' => build_id
|
106
|
+
}
|
107
|
+
response = request(:put) do |req|
|
108
|
+
req.url "providers/#{team_id}/apps/#{app_id}/groups/#{group_id}/builds/#{build_id}"
|
109
|
+
req.body = body.to_json
|
110
|
+
req.headers['Content-Type'] = 'application/json'
|
111
|
+
end
|
112
|
+
handle_response(response)
|
113
|
+
end
|
114
|
+
|
115
|
+
def handle_response(response)
|
116
|
+
if (200..300).cover?(response.status) && response.body.empty?
|
117
|
+
return
|
118
|
+
end
|
119
|
+
|
120
|
+
unless response.body.kind_of?(Hash)
|
121
|
+
raise UnexpectedResponse, response.body
|
122
|
+
end
|
123
|
+
|
124
|
+
raise UnexpectedResponse, response.body['error'] if response.body['error']
|
125
|
+
|
126
|
+
return response.body['data'] if response.body['data']
|
127
|
+
|
128
|
+
return response.body
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
# used to assert all of the named parameters are supplied values
|
134
|
+
#
|
135
|
+
# @raises NameError if the values are nil
|
136
|
+
def assert_required_params(method_name, binding)
|
137
|
+
parameter_names = Hash[method(method_name).parameters].values
|
138
|
+
parameter_names.each do |name|
|
139
|
+
if binding.local_variable_get(name).nil?
|
140
|
+
raise NameError, "`#{name}' is a required parameter"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Spaceship::TestFlight
|
2
|
+
class Group < Base
|
3
|
+
attr_accessor :id
|
4
|
+
attr_accessor :name
|
5
|
+
attr_accessor :is_default_external_group
|
6
|
+
|
7
|
+
attr_accessor :app_id
|
8
|
+
|
9
|
+
attr_mapping({
|
10
|
+
'id' => :id,
|
11
|
+
'name' => :name,
|
12
|
+
'isDefaultExternalGroup' => :is_default_external_group
|
13
|
+
})
|
14
|
+
|
15
|
+
def self.all(app_id: nil)
|
16
|
+
groups = client.get_groups(app_id: app_id)
|
17
|
+
groups.map do |g|
|
18
|
+
current_element = self.new(g)
|
19
|
+
current_element.app_id = app_id
|
20
|
+
current_element
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.find(app_id: nil, group_name: nil)
|
25
|
+
groups = self.all(app_id: app_id)
|
26
|
+
groups.find { |g| g.name == group_name }
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.default_external_group(app_id: nil)
|
30
|
+
groups = self.all(app_id: app_id)
|
31
|
+
groups.find(&:default_external_group?)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.filter_groups(app_id: nil, &block)
|
35
|
+
groups = self.all(app_id: app_id)
|
36
|
+
groups.select(&block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_tester!(tester)
|
40
|
+
client.add_tester_to_group!(group: self, tester: tester, app_id: self.app_id)
|
41
|
+
end
|
42
|
+
|
43
|
+
def remove_tester!(tester)
|
44
|
+
client.remove_tester_from_group!(group: self, tester: tester, app_id: self.app_id)
|
45
|
+
end
|
46
|
+
|
47
|
+
def default_external_group?
|
48
|
+
is_default_external_group
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|