spaceship 0.0.15 → 0.1.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/assets/languageMapping.json +224 -0
- data/lib/spaceship.rb +20 -63
- data/lib/spaceship/base.rb +71 -14
- data/lib/spaceship/client.rb +9 -274
- data/lib/spaceship/launcher.rb +1 -1
- data/lib/spaceship/portal/app.rb +125 -0
- data/lib/spaceship/portal/certificate.rb +273 -0
- data/lib/spaceship/portal/device.rb +102 -0
- data/lib/spaceship/portal/portal.rb +6 -0
- data/lib/spaceship/portal/portal_base.rb +13 -0
- data/lib/spaceship/portal/portal_client.rb +289 -0
- data/lib/spaceship/portal/provisioning_profile.rb +369 -0
- data/lib/spaceship/portal/spaceship.rb +94 -0
- data/lib/spaceship/{ui → portal/ui}/select_team.rb +0 -0
- data/lib/spaceship/tunes/app_screenshot.rb +28 -0
- data/lib/spaceship/tunes/app_status.rb +63 -0
- data/lib/spaceship/tunes/app_submission.rb +149 -0
- data/lib/spaceship/tunes/app_version.rb +337 -0
- data/lib/spaceship/tunes/application.rb +253 -0
- data/lib/spaceship/tunes/build.rb +128 -0
- data/lib/spaceship/tunes/build_train.rb +79 -0
- data/lib/spaceship/tunes/language_converter.rb +44 -0
- data/lib/spaceship/tunes/language_item.rb +54 -0
- data/lib/spaceship/tunes/processing_build.rb +30 -0
- data/lib/spaceship/tunes/spaceship.rb +26 -0
- data/lib/spaceship/tunes/tester.rb +177 -0
- data/lib/spaceship/tunes/tunes.rb +12 -0
- data/lib/spaceship/tunes/tunes_base.rb +15 -0
- data/lib/spaceship/tunes/tunes_client.rb +360 -0
- data/lib/spaceship/version.rb +1 -1
- metadata +27 -7
- data/lib/spaceship/app.rb +0 -125
- data/lib/spaceship/certificate.rb +0 -271
- data/lib/spaceship/device.rb +0 -100
- data/lib/spaceship/provisioning_profile.rb +0 -367
data/lib/spaceship/client.rb
CHANGED
@@ -5,6 +5,7 @@ require 'spaceship/ui'
|
|
5
5
|
require 'spaceship/helper/plist_middleware'
|
6
6
|
require 'spaceship/helper/net_http_generic_request'
|
7
7
|
|
8
|
+
|
8
9
|
if ENV["DEBUG"]
|
9
10
|
require 'openssl'
|
10
11
|
# this has to be on top of this file, since the value can't be changed later
|
@@ -49,8 +50,12 @@ module Spaceship
|
|
49
50
|
end
|
50
51
|
end
|
51
52
|
|
53
|
+
def self.hostname
|
54
|
+
raise "You must implemented self.hostname"
|
55
|
+
end
|
56
|
+
|
52
57
|
def initialize
|
53
|
-
@client = Faraday.new(
|
58
|
+
@client = Faraday.new(self.class.hostname) do |c|
|
54
59
|
c.response :json, content_type: /\bjson$/
|
55
60
|
c.response :xml, content_type: /\bxml$/
|
56
61
|
c.response :plist, content_type: /\bplist$/
|
@@ -65,28 +70,6 @@ module Spaceship
|
|
65
70
|
end
|
66
71
|
end
|
67
72
|
|
68
|
-
# Fetches the latest API Key from the Apple Dev Portal
|
69
|
-
def api_key
|
70
|
-
cache_path = "/tmp/spaceship_api_key.txt"
|
71
|
-
begin
|
72
|
-
cached = File.read(cache_path)
|
73
|
-
rescue Errno::ENOENT
|
74
|
-
end
|
75
|
-
return cached if cached
|
76
|
-
|
77
|
-
landing_url = "https://developer.apple.com/membercenter/index.action"
|
78
|
-
logger.info("GET: " + landing_url)
|
79
|
-
headers = @client.get(landing_url).headers
|
80
|
-
results = headers['location'].match(/.*appIdKey=(\h+)/)
|
81
|
-
if results.length > 1
|
82
|
-
api_key = results[1]
|
83
|
-
File.write(cache_path, api_key)
|
84
|
-
return api_key
|
85
|
-
else
|
86
|
-
raise "Could not find latest API Key from the Dev Portal"
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
73
|
# The logger in which all requests are logged
|
91
74
|
# /tmp/spaceship.log by default
|
92
75
|
def logger
|
@@ -162,19 +145,7 @@ module Spaceship
|
|
162
145
|
raise InvalidUserCredentialsError.new("No login data provided")
|
163
146
|
end
|
164
147
|
|
165
|
-
|
166
|
-
appleId: user,
|
167
|
-
accountPassword: password,
|
168
|
-
appIdKey: api_key
|
169
|
-
})
|
170
|
-
|
171
|
-
if response['Set-Cookie'] =~ /myacinfo=(\w+);/
|
172
|
-
@cookie = "myacinfo=#{$1};"
|
173
|
-
return @client
|
174
|
-
else
|
175
|
-
# User Credentials are wrong
|
176
|
-
raise InvalidUserCredentialsError.new(response)
|
177
|
-
end
|
148
|
+
send_login_request(user, password) # different in subclasses
|
178
149
|
end
|
179
150
|
|
180
151
|
# @return (Bool) Do we have a valid session?
|
@@ -182,243 +153,6 @@ module Spaceship
|
|
182
153
|
!!@cookie
|
183
154
|
end
|
184
155
|
|
185
|
-
# @return (Array) A list of all available teams
|
186
|
-
def teams
|
187
|
-
r = request(:post, 'account/listTeams.action')
|
188
|
-
parse_response(r, 'teams')
|
189
|
-
end
|
190
|
-
|
191
|
-
# @return (String) The currently selected Team ID
|
192
|
-
def team_id
|
193
|
-
return @current_team_id if @current_team_id
|
194
|
-
|
195
|
-
if teams.count > 1
|
196
|
-
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."
|
197
|
-
end
|
198
|
-
@current_team_id ||= teams[0]['teamId']
|
199
|
-
end
|
200
|
-
|
201
|
-
# Shows a team selection for the user in the terminal. This should not be
|
202
|
-
# called on CI systems
|
203
|
-
def select_team
|
204
|
-
@current_team_id = self.UI.select_team
|
205
|
-
end
|
206
|
-
|
207
|
-
# Set a new team ID which will be used from now on
|
208
|
-
def team_id=(team_id)
|
209
|
-
@current_team_id = team_id
|
210
|
-
end
|
211
|
-
|
212
|
-
# @return (Hash) Fetches all information of the currently used team
|
213
|
-
def team_information
|
214
|
-
teams.find do |t|
|
215
|
-
t['teamId'] == team_id
|
216
|
-
end
|
217
|
-
end
|
218
|
-
|
219
|
-
# Is the current session from an Enterprise In House account?
|
220
|
-
def in_house?
|
221
|
-
return @in_house unless @in_house.nil?
|
222
|
-
@in_house = (team_information['type'] == 'In-House')
|
223
|
-
end
|
224
|
-
|
225
|
-
#####################################################
|
226
|
-
# @!group Apps
|
227
|
-
#####################################################
|
228
|
-
|
229
|
-
def apps
|
230
|
-
paging do |page_number|
|
231
|
-
r = request(:post, 'account/ios/identifiers/listAppIds.action', {
|
232
|
-
teamId: team_id,
|
233
|
-
pageNumber: page_number,
|
234
|
-
pageSize: page_size,
|
235
|
-
sort: 'name=asc'
|
236
|
-
})
|
237
|
-
parse_response(r, 'appIds')
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
def details_for_app(app)
|
242
|
-
r = request(:post, 'account/ios/identifiers/getAppIdDetail.action', {
|
243
|
-
teamId: team_id,
|
244
|
-
appIdId: app.app_id
|
245
|
-
})
|
246
|
-
parse_response(r, 'appId')
|
247
|
-
end
|
248
|
-
|
249
|
-
def create_app!(type, name, bundle_id)
|
250
|
-
ident_params = case type.to_sym
|
251
|
-
when :explicit
|
252
|
-
{
|
253
|
-
type: 'explicit',
|
254
|
-
explicitIdentifier: bundle_id,
|
255
|
-
appIdentifierString: bundle_id,
|
256
|
-
push: 'on',
|
257
|
-
inAppPurchase: 'on',
|
258
|
-
gameCenter: 'on'
|
259
|
-
}
|
260
|
-
when :wildcard
|
261
|
-
{
|
262
|
-
type: 'wildcard',
|
263
|
-
wildcardIdentifier: bundle_id,
|
264
|
-
appIdentifierString: bundle_id
|
265
|
-
}
|
266
|
-
end
|
267
|
-
|
268
|
-
params = {
|
269
|
-
appIdName: name,
|
270
|
-
teamId: team_id
|
271
|
-
}
|
272
|
-
|
273
|
-
params.merge!(ident_params)
|
274
|
-
|
275
|
-
r = request(:post, 'account/ios/identifiers/addAppId.action', params)
|
276
|
-
parse_response(r, 'appId')
|
277
|
-
end
|
278
|
-
|
279
|
-
def delete_app!(app_id)
|
280
|
-
r = request(:post, 'account/ios/identifiers/deleteAppId.action', {
|
281
|
-
teamId: team_id,
|
282
|
-
appIdId: app_id
|
283
|
-
})
|
284
|
-
parse_response(r)
|
285
|
-
end
|
286
|
-
|
287
|
-
#####################################################
|
288
|
-
# @!group Devices
|
289
|
-
#####################################################
|
290
|
-
|
291
|
-
def devices
|
292
|
-
paging do |page_number|
|
293
|
-
r = request(:post, 'account/ios/device/listDevices.action', {
|
294
|
-
teamId: team_id,
|
295
|
-
pageNumber: page_number,
|
296
|
-
pageSize: page_size,
|
297
|
-
sort: 'name=asc'
|
298
|
-
})
|
299
|
-
parse_response(r, 'devices')
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
def create_device!(device_name, device_id)
|
304
|
-
r = request(:post) do |r|
|
305
|
-
r.url "https://developerservices2.apple.com/services/#{PROTOCOL_VERSION}/ios/addDevice.action"
|
306
|
-
r.params = {
|
307
|
-
teamId: team_id,
|
308
|
-
deviceNumber: device_id,
|
309
|
-
name: device_name
|
310
|
-
}
|
311
|
-
end
|
312
|
-
|
313
|
-
parse_response(r, 'device')
|
314
|
-
end
|
315
|
-
|
316
|
-
#####################################################
|
317
|
-
# @!group Certificates
|
318
|
-
#####################################################
|
319
|
-
|
320
|
-
def certificates(types)
|
321
|
-
paging do |page_number|
|
322
|
-
r = request(:post, 'account/ios/certificate/listCertRequests.action', {
|
323
|
-
teamId: team_id,
|
324
|
-
types: types.join(','),
|
325
|
-
pageNumber: page_number,
|
326
|
-
pageSize: page_size,
|
327
|
-
sort: 'certRequestStatusCode=asc'
|
328
|
-
})
|
329
|
-
parse_response(r, 'certRequests')
|
330
|
-
end
|
331
|
-
end
|
332
|
-
|
333
|
-
def create_certificate!(type, csr, app_id = nil)
|
334
|
-
r = request(:post, 'account/ios/certificate/submitCertificateRequest.action', {
|
335
|
-
teamId: team_id,
|
336
|
-
type: type,
|
337
|
-
csrContent: csr,
|
338
|
-
appIdId: app_id #optional
|
339
|
-
})
|
340
|
-
parse_response(r, 'certRequest')
|
341
|
-
end
|
342
|
-
|
343
|
-
def download_certificate(certificate_id, type)
|
344
|
-
{type: type, certificate_id: certificate_id}.each { |k, v| raise "#{k} must not be nil" if v.nil? }
|
345
|
-
|
346
|
-
r = request(:post, 'https://developer.apple.com/account/ios/certificate/certificateContentDownload.action', {
|
347
|
-
teamId: team_id,
|
348
|
-
displayId: certificate_id,
|
349
|
-
type: type
|
350
|
-
})
|
351
|
-
parse_response(r)
|
352
|
-
end
|
353
|
-
|
354
|
-
def revoke_certificate!(certificate_id, type)
|
355
|
-
r = request(:post, 'account/ios/certificate/revokeCertificate.action', {
|
356
|
-
teamId: team_id,
|
357
|
-
certificateId: certificate_id,
|
358
|
-
type: type
|
359
|
-
})
|
360
|
-
parse_response(r, 'certRequests')
|
361
|
-
end
|
362
|
-
|
363
|
-
#####################################################
|
364
|
-
# @!group Provisioning Profiles
|
365
|
-
#####################################################
|
366
|
-
|
367
|
-
def provisioning_profiles
|
368
|
-
r = request(:post) do |r|
|
369
|
-
r.url "https://developerservices2.apple.com/services/#{PROTOCOL_VERSION}/ios/listProvisioningProfiles.action"
|
370
|
-
r.params = {
|
371
|
-
teamId: team_id,
|
372
|
-
includeInactiveProfiles: true,
|
373
|
-
onlyCountLists: true,
|
374
|
-
}
|
375
|
-
end
|
376
|
-
|
377
|
-
parse_response(r, 'provisioningProfiles')
|
378
|
-
end
|
379
|
-
|
380
|
-
def create_provisioning_profile!(name, distribution_method, app_id, certificate_ids, device_ids)
|
381
|
-
r = request(:post, 'account/ios/profile/createProvisioningProfile.action', {
|
382
|
-
teamId: team_id,
|
383
|
-
provisioningProfileName: name,
|
384
|
-
appIdId: app_id,
|
385
|
-
distributionType: distribution_method,
|
386
|
-
certificateIds: certificate_ids,
|
387
|
-
deviceIds: device_ids
|
388
|
-
})
|
389
|
-
parse_response(r, 'provisioningProfile')
|
390
|
-
end
|
391
|
-
|
392
|
-
def download_provisioning_profile(profile_id)
|
393
|
-
r = request(:get, 'https://developer.apple.com/account/ios/profile/profileContentDownload.action', {
|
394
|
-
teamId: team_id,
|
395
|
-
displayId: profile_id
|
396
|
-
})
|
397
|
-
parse_response(r)
|
398
|
-
end
|
399
|
-
|
400
|
-
def delete_provisioning_profile!(profile_id)
|
401
|
-
r = request(:post, 'account/ios/profile/deleteProvisioningProfile.action', {
|
402
|
-
teamId: team_id,
|
403
|
-
provisioningProfileId: profile_id
|
404
|
-
})
|
405
|
-
parse_response(r)
|
406
|
-
end
|
407
|
-
|
408
|
-
def repair_provisioning_profile!(profile_id, name, distribution_method, app_id, certificate_ids, device_ids)
|
409
|
-
r = request(:post, 'account/ios/profile/regenProvisioningProfile.action', {
|
410
|
-
teamId: team_id,
|
411
|
-
provisioningProfileId: profile_id,
|
412
|
-
provisioningProfileName: name,
|
413
|
-
appIdId: app_id,
|
414
|
-
distributionType: distribution_method,
|
415
|
-
certificateIds: certificate_ids.first, # we are most of the times only allowed to pass one
|
416
|
-
deviceIds: device_ids
|
417
|
-
})
|
418
|
-
|
419
|
-
parse_response(r, 'provisioningProfile')
|
420
|
-
end
|
421
|
-
|
422
156
|
private
|
423
157
|
# Is called from `parse_response` to store the latest csrf_token (if available)
|
424
158
|
def store_csrf_tokens(response)
|
@@ -460,7 +194,8 @@ module Spaceship
|
|
460
194
|
|
461
195
|
def log_request(method, url, params)
|
462
196
|
params_to_log = Hash(params).dup # to also work with nil
|
463
|
-
params_to_log.delete(:accountPassword)
|
197
|
+
params_to_log.delete(:accountPassword) # Dev Portal
|
198
|
+
params_to_log.delete(:theAccountPW) # iTC
|
464
199
|
params_to_log = params_to_log.collect do |key, value|
|
465
200
|
"{#{key}: #{value}}"
|
466
201
|
end
|
data/lib/spaceship/launcher.rb
CHANGED
@@ -0,0 +1,125 @@
|
|
1
|
+
module Spaceship
|
2
|
+
module Portal
|
3
|
+
# Represents an App ID from the Developer Portal
|
4
|
+
class App < PortalBase
|
5
|
+
|
6
|
+
# @return (String) The identifier of this app, provided by the Dev Portal
|
7
|
+
# @example
|
8
|
+
# "RGAWZGXSAA"
|
9
|
+
attr_accessor :app_id
|
10
|
+
|
11
|
+
# @return (String) The name you provided for this app
|
12
|
+
# @example
|
13
|
+
# "Spaceship"
|
14
|
+
attr_accessor :name
|
15
|
+
|
16
|
+
# @return (String) the supported platform of this app
|
17
|
+
# @example
|
18
|
+
# "ios"
|
19
|
+
attr_accessor :platform
|
20
|
+
|
21
|
+
# Prefix provided by the Dev Portal
|
22
|
+
# @example
|
23
|
+
# "5A997XSHK2"
|
24
|
+
attr_accessor :prefix
|
25
|
+
|
26
|
+
# @return (String) The bundle_id (app identifier) of your app
|
27
|
+
# @example
|
28
|
+
# "com.krausefx.app"
|
29
|
+
attr_accessor :bundle_id
|
30
|
+
|
31
|
+
# @return (Bool) Is this app a wildcard app (e.g. com.krausefx.*)
|
32
|
+
attr_accessor :is_wildcard
|
33
|
+
|
34
|
+
# @return (Hash) Feature details
|
35
|
+
attr_accessor :features
|
36
|
+
|
37
|
+
# @return (Array) List of enabled features
|
38
|
+
attr_accessor :enabled_features
|
39
|
+
|
40
|
+
# @return (Bool) Development Push Enabled?
|
41
|
+
attr_accessor :dev_push_enabled
|
42
|
+
|
43
|
+
# @return (Bool) Production Push Enabled?
|
44
|
+
attr_accessor :prod_push_enabled
|
45
|
+
|
46
|
+
# @return (Fixnum) Number of associated app groups
|
47
|
+
attr_accessor :app_groups_count
|
48
|
+
|
49
|
+
# @return (Fixnum) Number of associated cloud containers
|
50
|
+
attr_accessor :cloud_containers_count
|
51
|
+
|
52
|
+
# @return (Fixnum) Number of associated identifiers
|
53
|
+
attr_accessor :identifiers_count
|
54
|
+
|
55
|
+
attr_mapping(
|
56
|
+
'appIdId' => :app_id,
|
57
|
+
'name' => :name,
|
58
|
+
'appIdPlatform' => :platform,
|
59
|
+
'prefix' => :prefix,
|
60
|
+
'identifier' => :bundle_id,
|
61
|
+
'isWildCard' => :is_wildcard,
|
62
|
+
'features' => :features,
|
63
|
+
'enabledFeatures' => :enabled_features,
|
64
|
+
'isDevPushEnabled' => :dev_push_enabled,
|
65
|
+
'isProdPushEnabled' => :prod_push_enabled,
|
66
|
+
'associatedApplicationGroupsCount' => :app_groups_count,
|
67
|
+
'associatedCloudContainersCount' => :cloud_containers_count,
|
68
|
+
'associatedIdentifiersCount' => :identifiers_count
|
69
|
+
)
|
70
|
+
|
71
|
+
class << self
|
72
|
+
# Create a new object based on a hash.
|
73
|
+
# This is used to create a new object based on the server response.
|
74
|
+
def factory(attrs)
|
75
|
+
self.new(attrs)
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return (Array) Returns all apps available for this account
|
79
|
+
def all
|
80
|
+
client.apps.map { |app| self.factory(app) }
|
81
|
+
end
|
82
|
+
|
83
|
+
# Creates a new App ID on the Apple Dev Portal
|
84
|
+
#
|
85
|
+
# if bundle_id ends with '*' then it is a wildcard id otherwise, it is an explicit id
|
86
|
+
# @param bundle_id [String] the bundle id (app_identifier) of the app associated with this provisioning profile
|
87
|
+
# @param name [String] the name of the App
|
88
|
+
# @return (App) The app you just created
|
89
|
+
def create!(bundle_id: nil, name: nil)
|
90
|
+
if bundle_id.end_with?('*')
|
91
|
+
type = :wildcard
|
92
|
+
else
|
93
|
+
type = :explicit
|
94
|
+
end
|
95
|
+
|
96
|
+
new_app = client.create_app!(type, name, bundle_id)
|
97
|
+
self.new(new_app)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Find a specific App ID based on the bundle_id
|
101
|
+
# @return (App) The app you're looking for. This is nil if the app can't be found.
|
102
|
+
def find(bundle_id)
|
103
|
+
all.find do |app|
|
104
|
+
app.bundle_id == bundle_id
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Delete this App ID. This action will most likely fail if the App ID is already in the store
|
110
|
+
# or there are active profiles
|
111
|
+
# @return (App) The app you just deletd
|
112
|
+
def delete!
|
113
|
+
client.delete_app!(app_id)
|
114
|
+
self
|
115
|
+
end
|
116
|
+
|
117
|
+
# Fetch a specific App ID details based on the bundle_id
|
118
|
+
# @return (App) The app you're looking for. This is nil if the app can't be found.
|
119
|
+
def details
|
120
|
+
app = client.details_for_app(self)
|
121
|
+
self.class.factory(app)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|