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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72fc2ae05e3630efd3ed2a5d278e712e62c48d03
|
4
|
+
data.tar.gz: 8db0cf1bdfb90d66b8404009eb76898ff4a9f9a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5bbfe725c73e9e7ba2920347f566bd06051e73d30c9a9e8e1fc1864f9a9c84a7c2bc3e8bc59c56fadb3d88d6c9cd2d0e8924a94f97789c02a52f69156f67064c
|
7
|
+
data.tar.gz: 529280001dc37f94b8713749c0517f94e46484af030e1a089b54dd95d2b15f490bc39ade83ac295e5507c1f2c7465f5908c9e9a76b31bf181cbbdc0fb01d0f07
|
@@ -88,6 +88,11 @@ module Fastlane
|
|
88
88
|
app_id = options.delete(:public_identifier)
|
89
89
|
|
90
90
|
ipaio = Faraday::UploadIO.new(ipa, 'application/octet-stream') if ipa and File.exist?(ipa)
|
91
|
+
dsym = options.delete(:dsym)
|
92
|
+
|
93
|
+
if dsym
|
94
|
+
dsym_io = Faraday::UploadIO.new(dsym, 'application/octet-stream') if dsym and File.exist?(dsym)
|
95
|
+
end
|
91
96
|
|
92
97
|
response = connection.get do |req|
|
93
98
|
req.url("/api/2/apps/#{app_id}/app_versions/new")
|
@@ -105,6 +110,10 @@ module Fastlane
|
|
105
110
|
|
106
111
|
options[:ipa] = ipaio
|
107
112
|
|
113
|
+
if dsym
|
114
|
+
options[:dsym] = dsym_io
|
115
|
+
end
|
116
|
+
|
108
117
|
connection.put do |req|
|
109
118
|
req.url("/api/2/apps/#{app_id}/app_versions/#{app_version_id}")
|
110
119
|
req.headers['X-HockeyAppToken'] = api_token
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module FastlaneCore
|
2
|
+
class BuildWatcher
|
3
|
+
# @return The build we waited for. This method will always return a build
|
4
|
+
def self.wait_for_build_processing_to_be_complete(app_id: nil, platform: nil)
|
5
|
+
# First, find the train and build version we want to watch for
|
6
|
+
processing_builds = Spaceship::TestFlight::Build.all_processing_builds(app_id: app_id, platform: platform)
|
7
|
+
|
8
|
+
watching_build = processing_builds.sort_by(&:upload_date).last # either it's still processing
|
9
|
+
watching_build ||= Spaceship::TestFlight::Build.latest(app_id: app_id, platform: platform) # or we fallback to the most recent uplaod
|
10
|
+
|
11
|
+
loop do
|
12
|
+
UI.message("Waiting for iTunes Connect to finish processing the new build (#{watching_build.train_version} - #{watching_build.build_version})")
|
13
|
+
|
14
|
+
# Due to iTunes Connect, builds disappear from the build list alltogether
|
15
|
+
# after they finished processing. Before returning this build, we have to
|
16
|
+
# wait for the build to appear in the build list again
|
17
|
+
# As this method is very often used to wait for a build, and then do something
|
18
|
+
# 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?
|
24
|
+
UI.message("Build doesn't show up in the build list any more, waiting for it to appear again")
|
25
|
+
elsif matching_build.active?
|
26
|
+
UI.success("Build #{matching_build.train_version} - #{matching_build.build_version} is already being tested")
|
27
|
+
return matching_build
|
28
|
+
elsif matching_build.ready_to_submit?
|
29
|
+
UI.success("Successfully finished processing the build #{matching_build.train_version} - #{matching_build.build_version}")
|
30
|
+
return matching_build
|
31
|
+
end
|
32
|
+
|
33
|
+
sleep 10
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -35,40 +35,17 @@ module Pilot
|
|
35
35
|
end
|
36
36
|
|
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,
|
40
|
+
distribute(options, latest_build)
|
41
41
|
end
|
42
42
|
|
43
|
-
def distribute(options, build
|
43
|
+
def distribute(options, build)
|
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
|
-
if build.nil?
|
50
|
-
platform = fetch_app_platform(required: false)
|
51
|
-
builds = app.all_processing_builds(platform: platform) + app.builds(platform: platform)
|
52
|
-
# sort by upload_date
|
53
|
-
builds.sort! { |a, b| a.upload_date <=> b.upload_date }
|
54
|
-
build = builds.last
|
55
|
-
if build.nil?
|
56
|
-
UI.user_error!("No builds found.")
|
57
|
-
return
|
58
|
-
end
|
59
|
-
if build.processing
|
60
|
-
UI.user_error!("Build #{build.train_version}(#{build.build_version}) is still processing.")
|
61
|
-
return
|
62
|
-
end
|
63
|
-
if build.testing_status == "External"
|
64
|
-
UI.user_error!("Build #{build.train_version}(#{build.build_version}) has already been distributed.")
|
65
|
-
return
|
66
|
-
end
|
67
|
-
|
68
|
-
type = options[:distribute_external] ? 'External' : 'Internal'
|
69
|
-
UI.message("Distributing build #{build.train_version}(#{build.build_version}) from #{build.testing_status} -> #{type}")
|
70
|
-
end
|
71
|
-
|
72
49
|
unless config[:update_build_info_on_upload]
|
73
50
|
if should_update_build_information(options)
|
74
51
|
build.update_build_information!(whats_new: options[:changelog], description: options[:beta_app_description], feedback_email: options[:beta_app_feedback_email])
|
@@ -128,88 +105,34 @@ module Pilot
|
|
128
105
|
options[:changelog].to_s.length > 0 or options[:beta_app_description].to_s.length > 0 or options[:beta_app_feedback_email].to_s.length > 0
|
129
106
|
end
|
130
107
|
|
131
|
-
|
132
|
-
|
133
|
-
def wait_for_processing_build(options, platform)
|
134
|
-
# the upload date of the new buid
|
135
|
-
# we use it to identify the build
|
136
|
-
start = Time.now
|
137
|
-
wait_processing_interval = config[:wait_processing_interval].to_i
|
138
|
-
latest_build = nil
|
139
|
-
UI.message("Waiting for iTunes Connect to process the new build")
|
140
|
-
must_update_build_info = config[:update_build_info_on_upload]
|
141
|
-
loop do
|
142
|
-
sleep(wait_processing_interval)
|
143
|
-
|
144
|
-
# before we look for processing builds, we need to ensure that there
|
145
|
-
# is a build train for this application; new applications don't
|
146
|
-
# build trains right away, and if we don't do this check, we will
|
147
|
-
# get break out of this loop and then generate an error later when we
|
148
|
-
# have a nil build
|
149
|
-
if app.build_trains(platform: platform).count == 0
|
150
|
-
UI.message("New application; waiting for build train to appear on iTunes Connect")
|
151
|
-
else
|
152
|
-
builds = app.all_processing_builds(platform: platform)
|
153
|
-
latest_build = builds.last unless latest_build
|
154
|
-
break unless builds.include?(latest_build)
|
155
|
-
|
156
|
-
if latest_build.valid and must_update_build_info
|
157
|
-
# Set the changelog and/or description if necessary
|
158
|
-
if should_update_build_information(options)
|
159
|
-
latest_build.update_build_information!(whats_new: options[:changelog], description: options[:beta_app_description], feedback_email: options[:beta_app_feedback_email])
|
160
|
-
UI.success "Successfully set the changelog and/or description for build"
|
161
|
-
end
|
162
|
-
must_update_build_info = false
|
163
|
-
end
|
164
|
-
|
165
|
-
UI.message("Waiting for iTunes Connect to finish processing the new build (#{latest_build.train_version} - #{latest_build.build_version})")
|
166
|
-
end
|
167
|
-
end
|
108
|
+
def distribute_build(uploaded_build, options)
|
109
|
+
UI.message("Distributing new build to testers: #{uploaded_build.train_version} - #{uploaded_build.build_version}")
|
168
110
|
|
169
|
-
|
170
|
-
|
111
|
+
# This is where we could add a check to see if encryption is required and has been updated
|
112
|
+
uploaded_build.export_compliance.encryption_updated = false
|
113
|
+
uploaded_build.beta_review_info.demo_account_required = false
|
114
|
+
uploaded_build.submit_for_testflight_review!
|
171
115
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
# and ensure it's not processing before proceeding - it had to have already been false before, to get out of the
|
176
|
-
# previous loop.
|
177
|
-
full_build = app.build_trains(platform: platform)[latest_build.train_version].builds.find do |b|
|
178
|
-
b.build_version == latest_build.build_version
|
179
|
-
end
|
116
|
+
if options[:distribute_external]
|
117
|
+
external_group = Spaceship::TestFlight::Group.default_external_group(app_id: uploaded_build.app_id)
|
118
|
+
uploaded_build.add_group!(external_group) unless external_group.nil?
|
180
119
|
|
181
|
-
|
182
|
-
|
183
|
-
|
120
|
+
if external_group.nil? && options[:groups].nil?
|
121
|
+
UI.user_error!("You must specify at least one group using the `:groups` option to distribute externally")
|
122
|
+
end
|
184
123
|
|
185
|
-
if full_build && !full_build.processing && full_build.valid
|
186
|
-
minutes = ((Time.now - start) / 60).round
|
187
|
-
UI.success("Successfully finished processing the build")
|
188
|
-
UI.message("You can now tweet: ")
|
189
|
-
UI.important("iTunes Connect #iosprocessingtime #{minutes} minutes")
|
190
|
-
return full_build
|
191
|
-
else
|
192
|
-
UI.user_error!("Error: Seems like iTunes Connect didn't properly pre-process the binary")
|
193
124
|
end
|
194
|
-
end
|
195
125
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
train: uploaded_build.build_train.version_string,
|
204
|
-
build_number: uploaded_build.build_version,
|
205
|
-
platform: uploaded_build.platform
|
206
|
-
)
|
126
|
+
if options[:groups]
|
127
|
+
groups = Group.filter_groups(app_id: uploaded_build.app_id) do |group|
|
128
|
+
options[:groups].include?(group.name)
|
129
|
+
end
|
130
|
+
groups.each do |group|
|
131
|
+
uploaded_build.add_group!(group)
|
132
|
+
end
|
207
133
|
end
|
208
134
|
|
209
|
-
|
210
|
-
type = options[:distribute_external] ? 'external' : 'internal'
|
211
|
-
uploaded_build.build_train.update_testing_status!(true, type, uploaded_build)
|
212
|
-
return true
|
135
|
+
true
|
213
136
|
end
|
214
137
|
end
|
215
138
|
end
|
@@ -8,14 +8,8 @@ module Pilot
|
|
8
8
|
start(options)
|
9
9
|
|
10
10
|
if config[:groups]
|
11
|
-
groups
|
12
|
-
|
13
|
-
config[:groups].each do |group|
|
14
|
-
group_id = groups.find { |k, v| v == group || k == group }
|
15
|
-
raise "Group '#{group}' not found for #{config[:email]}" unless group_id
|
16
|
-
selected_groups.push(group_id[0])
|
17
|
-
end
|
18
|
-
config[:groups] = selected_groups
|
11
|
+
UI.important("Currently pilot doesn't support groups yet, we're working on restoring that functionality")
|
12
|
+
config[:groups] = nil
|
19
13
|
end
|
20
14
|
|
21
15
|
begin
|
@@ -27,8 +21,7 @@ module Pilot
|
|
27
21
|
else
|
28
22
|
tester = Spaceship::Tunes::Tester::External.create!(email: config[:email],
|
29
23
|
first_name: config[:first_name],
|
30
|
-
last_name: config[:last_name]
|
31
|
-
groups: config[:groups])
|
24
|
+
last_name: config[:last_name])
|
32
25
|
UI.success("Successfully invited tester: #{tester.email}")
|
33
26
|
end
|
34
27
|
|
@@ -37,7 +30,7 @@ module Pilot
|
|
37
30
|
begin
|
38
31
|
app = Spaceship::Application.find(app_filter)
|
39
32
|
UI.user_error!("Couldn't find app with '#{app_filter}'") unless app
|
40
|
-
|
33
|
+
app.default_external_group.add_tester!(tester)
|
41
34
|
UI.success("Successfully added tester to app #{app_filter}")
|
42
35
|
rescue => ex
|
43
36
|
UI.error("Could not add #{tester.email} to app: #{ex}")
|
@@ -74,7 +67,7 @@ module Pilot
|
|
74
67
|
begin
|
75
68
|
app = Spaceship::Application.find(app_filter)
|
76
69
|
UI.user_error!("Couldn't find app with '#{app_filter}'") unless app
|
77
|
-
|
70
|
+
app.default_external_group.remove_tester!(tester)
|
78
71
|
UI.success("Successfully removed tester #{tester.email} from app #{app_filter}")
|
79
72
|
rescue => ex
|
80
73
|
UI.error("Could not remove #{tester.email} from app: #{ex}")
|
@@ -10,10 +10,8 @@ module Snapshot
|
|
10
10
|
|
11
11
|
File.write(snapfile_path, File.read("#{Snapshot::ROOT}/lib/assets/SnapfileTemplate"))
|
12
12
|
File.write(File.join(path, 'SnapshotHelper.swift'), File.read("#{Snapshot::ROOT}/lib/assets/SnapshotHelper.swift"))
|
13
|
-
File.write(File.join(path, 'SnapshotHelper2-3.swift'), File.read("#{Snapshot::ROOT}/lib/assets/SnapshotHelper2-3.swift"))
|
14
13
|
|
15
14
|
puts "✅ Successfully created SnapshotHelper.swift '#{File.join(path, 'SnapshotHelper.swift')}'".green
|
16
|
-
puts "✅ Successfully created SnapshotHelper2-3.swift '#{File.join(path, 'SnapshotHelper2-3.swift')} (if your UI tests are written in Swift 2.3)'".green
|
17
15
|
puts "✅ Successfully created new Snapfile at '#{snapfile_path}'".green
|
18
16
|
|
19
17
|
puts "-------------------------------------------------------".yellow
|
data/spaceship/lib/spaceship.rb
CHANGED
@@ -20,6 +20,8 @@ module Spaceship
|
|
20
20
|
# When you want to instantiate a model pass in the parsed response: `Widget.new(widget_json)`
|
21
21
|
class Base
|
22
22
|
class DataHash
|
23
|
+
include Enumerable
|
24
|
+
|
23
25
|
def initialize(hash)
|
24
26
|
@hash = hash || {}
|
25
27
|
end
|
@@ -50,11 +52,19 @@ module Spaceship
|
|
50
52
|
end
|
51
53
|
end
|
52
54
|
|
55
|
+
def each(&block)
|
56
|
+
@hash.each(&block)
|
57
|
+
end
|
58
|
+
|
53
59
|
def to_json(*a)
|
54
60
|
h = @hash.dup
|
55
61
|
h.delete(:application)
|
56
62
|
h.to_json(*a)
|
57
63
|
end
|
64
|
+
|
65
|
+
def to_h
|
66
|
+
@hash.dup
|
67
|
+
end
|
58
68
|
end
|
59
69
|
|
60
70
|
class << self
|
@@ -19,6 +19,7 @@ if ENV["SPACESHIP_DEBUG"]
|
|
19
19
|
end
|
20
20
|
|
21
21
|
module Spaceship
|
22
|
+
# rubocop:disable Metrics/ClassLength
|
22
23
|
class Client
|
23
24
|
PROTOCOL_VERSION = "QH65B2"
|
24
25
|
USER_AGENT = "Spaceship #{Fastlane::VERSION}"
|
@@ -126,14 +127,81 @@ module Spaceship
|
|
126
127
|
raise "You must implement self.hostname"
|
127
128
|
end
|
128
129
|
|
129
|
-
|
130
|
+
# @return (Array) A list of all available teams
|
131
|
+
def teams
|
132
|
+
user_details_data['associatedAccounts'].sort_by do |team|
|
133
|
+
[
|
134
|
+
team['contentProvider']['name'],
|
135
|
+
team['contentProvider']['contentProviderId']
|
136
|
+
]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def user_details_data
|
141
|
+
return @_cached_user_details if @_cached_user_details
|
142
|
+
r = request(:get, '/WebObjects/iTunesConnect.woa/ra/user/detail')
|
143
|
+
@_cached_user_details = parse_response(r, 'data')
|
144
|
+
end
|
145
|
+
|
146
|
+
# @return (String) The currently selected Team ID
|
147
|
+
def team_id
|
148
|
+
return @current_team_id if @current_team_id
|
149
|
+
|
150
|
+
if teams.count > 1
|
151
|
+
puts "The current user is in #{teams.count} teams. Pass a team ID or call `select_team` to choose a team. Using the first one for now."
|
152
|
+
end
|
153
|
+
@current_team_id ||= teams[0]['contentProvider']['contentProviderId']
|
154
|
+
end
|
155
|
+
|
156
|
+
# Set a new team ID which will be used from now on
|
157
|
+
def team_id=(team_id)
|
158
|
+
# First, we verify the team actually exists, because otherwise iTC would return the
|
159
|
+
# following confusing error message
|
160
|
+
#
|
161
|
+
# invalid content provider id
|
162
|
+
#
|
163
|
+
available_teams = teams.collect do |team|
|
164
|
+
(team["contentProvider"] || {})["contentProviderId"]
|
165
|
+
end
|
166
|
+
|
167
|
+
result = available_teams.find do |available_team_id|
|
168
|
+
team_id.to_s == available_team_id.to_s
|
169
|
+
end
|
170
|
+
|
171
|
+
unless result
|
172
|
+
raise ITunesConnectError.new, "Could not set team ID to '#{team_id}', only found the following available teams: #{available_teams.join(', ')}"
|
173
|
+
end
|
174
|
+
|
175
|
+
response = request(:post) do |req|
|
176
|
+
req.url "ra/v1/session/webSession"
|
177
|
+
req.body = {
|
178
|
+
contentProviderId: team_id,
|
179
|
+
dsId: user_detail_data.ds_id # https://github.com/fastlane/fastlane/issues/6711
|
180
|
+
}.to_json
|
181
|
+
req.headers['Content-Type'] = 'application/json'
|
182
|
+
end
|
183
|
+
|
184
|
+
handle_itc_response(response.body)
|
185
|
+
|
186
|
+
@current_team_id = team_id
|
187
|
+
end
|
188
|
+
|
189
|
+
# Instantiates a client but with a cookie derived from another client.
|
190
|
+
#
|
191
|
+
# HACK: since the `@cookie` is not exposed, we use this hacky way of sharing the instance.
|
192
|
+
def self.client_with_authorization_from(another_client)
|
193
|
+
self.new(cookie: another_client.instance_variable_get(:@cookie), current_team_id: another_client.team_id)
|
194
|
+
end
|
195
|
+
|
196
|
+
def initialize(cookie: nil, current_team_id: nil)
|
130
197
|
options = {
|
131
198
|
request: {
|
132
199
|
timeout: (ENV["SPACESHIP_TIMEOUT"] || 300).to_i,
|
133
200
|
open_timeout: (ENV["SPACESHIP_TIMEOUT"] || 300).to_i
|
134
201
|
}
|
135
202
|
}
|
136
|
-
@
|
203
|
+
@current_team_id = current_team_id
|
204
|
+
@cookie = cookie || HTTP::CookieJar.new
|
137
205
|
@client = Faraday.new(self.class.hostname, options) do |c|
|
138
206
|
c.response :json, content_type: /\bjson$/
|
139
207
|
c.response :xml, content_type: /\bxml$/
|
@@ -569,6 +637,7 @@ module Spaceship
|
|
569
637
|
return params, headers
|
570
638
|
end
|
571
639
|
end
|
640
|
+
# rubocop:enable Metrics/ClassLength
|
572
641
|
end
|
573
642
|
|
574
643
|
require 'spaceship/two_step_client'
|
@@ -0,0 +1,9 @@
|
|
1
|
+
|
2
|
+
require 'spaceship/test_flight/client'
|
3
|
+
require 'spaceship/test_flight/base'
|
4
|
+
require 'spaceship/test_flight/build'
|
5
|
+
require 'spaceship/test_flight/build_trains'
|
6
|
+
require 'spaceship/test_flight/beta_review_info'
|
7
|
+
require 'spaceship/test_flight/export_compliance'
|
8
|
+
require 'spaceship/test_flight/test_info'
|
9
|
+
require 'spaceship/test_flight/group'
|