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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 714e463263371b9066074b3d7dc867ffbfaa0cc3
4
- data.tar.gz: 7787b53d623db2f5ef85a2f73aecd542e412d98e
3
+ metadata.gz: 72fc2ae05e3630efd3ed2a5d278e712e62c48d03
4
+ data.tar.gz: 8db0cf1bdfb90d66b8404009eb76898ff4a9f9a8
5
5
  SHA512:
6
- metadata.gz: b87c7e3880a9eee91a0e9ce581657356ac15f544afef9100bc2ee72c1753f392569188a99f6c36e4fd07f517b5a6b0f821f659c6ac0191eeb282194b422b0f23
7
- data.tar.gz: 477d8acdcbc946c5a2d7af8965d823e75d1234a7d31c90bfb1d93ff0712d6e3968fcdf573efad2d95285a9a2da7aa56793570ac8d2c82b951692e478b5b96782
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
@@ -1,4 +1,4 @@
1
1
  module Fastlane
2
- VERSION = '2.28.0.beta.20170420010017'.freeze
2
+ VERSION = '2.28.0'.freeze
3
3
  DESCRIPTION = "The easiest way to automate beta deployments and releases for your iOS and Android apps".freeze
4
4
  end
@@ -30,6 +30,7 @@ require 'fastlane_core/tool_collector'
30
30
  require 'fastlane_core/fastlane_folder'
31
31
  require 'fastlane_core/keychain_importer'
32
32
  require 'fastlane_core/swag'
33
+ require 'fastlane_core/build_watcher'
33
34
 
34
35
  # Third Party code
35
36
  require 'colored'
@@ -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
- uploaded_build = wait_for_processing_build(options, platform) # this might take a while
38
+ latest_build = FastlaneCore::BuildWatcher.wait_for_build_processing_to_be_complete(app_id: app.apple_id, platform: platform)
39
39
 
40
- distribute(options, uploaded_build)
40
+ distribute(options, latest_build)
41
41
  end
42
42
 
43
- def distribute(options, build = nil)
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
- # This method will takes care of checking for the processing builds every few seconds
132
- # @return [Build] The build that we just uploaded
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
- UI.user_error!("Error receiving the newly uploaded binary, please check iTunes Connect") if latest_build.nil?
170
- full_build = nil
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
- while full_build.nil? || full_build.processing
173
- # The build's processing state should go from true to false, and be done. But sometimes it goes true -> false ->
174
- # true -> false, where the second true is transient. This causes a spurious failure. Find build by build_version
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
- UI.message("Waiting for iTunes Connect to finish processing the new build (#{latest_build.train_version} - #{latest_build.build_version})")
182
- sleep(wait_processing_interval)
183
- end
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
- def distribute_build(uploaded_build, options)
197
- UI.message("Distributing new build to testers")
198
-
199
- # Submit for review before external testflight is available
200
- if options[:distribute_external]
201
- uploaded_build.client.submit_testflight_build_for_review!(
202
- app_id: uploaded_build.build_train.application.apple_id,
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
- # Submit for beta testing
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 = Spaceship::Tunes::Tester::External.groups
12
- selected_groups = []
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
- tester.add_to_app!(app.apple_id)
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
- tester.remove_from_app!(app.apple_id)
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
@@ -10,6 +10,7 @@ require 'spaceship/portal/spaceship'
10
10
  # iTunes Connect
11
11
  require 'spaceship/tunes/tunes'
12
12
  require 'spaceship/tunes/spaceship'
13
+ require 'spaceship/test_flight'
13
14
 
14
15
  # To support legacy code
15
16
  module Spaceship
@@ -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
- def initialize
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
- @cookie = HTTP::CookieJar.new
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'