cupertino 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,12 +11,12 @@ module Cupertino
11
11
  super
12
12
  self.user_agent_alias = 'Mac Safari'
13
13
 
14
- pw = Security::InternetPassword.find(:server => Cupertino::HOSTNAME)
14
+ pw = Security::InternetPassword.find(:server => Cupertino::ProvisioningPortal::HOST)
15
15
  @username, @password = pw.attributes['acct'], pw.password if pw
16
16
  end
17
17
 
18
18
  def get(uri, parameters = [], referer = nil, headers = {})
19
- uri = ::File.join("https://#{Cupertino::HOSTNAME}", uri) unless /^https?/ === uri
19
+ uri = ::File.join("https://#{Cupertino::ProvisioningPortal::HOST}", uri) unless /^https?/ === uri
20
20
 
21
21
  3.times do
22
22
  super(uri, parameters, referer, headers)
@@ -24,12 +24,12 @@ module Cupertino
24
24
  return page unless page.respond_to?(:title)
25
25
 
26
26
  case page.title
27
- when %r{Sign in with your Apple ID}
28
- login! and redo
29
- when %r{Select Your Team}
30
- select_team! and redo
31
- else
32
- return page
27
+ when /Sign in with your Apple ID/
28
+ login! and redo
29
+ when /Select Team/
30
+ select_team! and redo
31
+ else
32
+ return page
33
33
  end
34
34
  end
35
35
 
@@ -38,87 +38,106 @@ module Cupertino
38
38
 
39
39
  def list_certificates(type = :development)
40
40
  url = case type
41
- when :development
42
- "https://developer.apple.com/ios/manage/certificates/team/index.action"
43
- when :distribution
44
- "https://developer.apple.com/ios/manage/certificates/team/distribute.action"
45
- else
46
- raise ArgumentError, "Certificate type must be :development or :distribution"
41
+ when :development
42
+ "https://developer.apple.com/account/ios/certificate/certificateList.action?type=development"
43
+ when :distribution
44
+ "https://developer.apple.com/account/ios/certificate/certificateList.action?type=distribution"
45
+ else
46
+ raise ArgumentError, "Certificate type must be :development or :distribution"
47
47
  end
48
48
 
49
49
  get(url)
50
50
 
51
+ regex = /certificateDataURL = "([^"]*)"/
52
+ certificate_data_url = (page.body.match regex or raise UnexpectedContentError)[1]
53
+
54
+ regex = /certificateRequestTypes = "([^"]*)"/
55
+ certificate_request_types = (page.body.match regex or raise UnexpectedContentError)[1]
56
+
57
+ regex = /certificateStatuses = "([^"]*)"/
58
+ certificate_statuses = (page.body.match regex or raise UnexpectedContentError)[1]
59
+
60
+ certificate_data_url += certificate_request_types + certificate_statuses
61
+
62
+ get(certificate_data_url)
63
+ certificate_data = page.content
64
+ parsed_certificate_data = JSON.parse(certificate_data)
65
+
51
66
  certificates = []
52
- page.parser.xpath('//div[@class="nt_multi"]/table/tbody/tr').each do |row|
67
+ parsed_certificate_data['certRequests'].each do |row|
53
68
  certificate = Certificate.new
54
- certificate.name = row.at_xpath('td[@class="name"]//p/text()').to_s.strip rescue nil
69
+ certificate.name = row['name']
55
70
  certificate.type = type
56
- certificate.provisioning_profiles = row.at_xpath('td[@class="profiles"]/text()').to_s.strip.split(/\n+/) rescue []
57
- certificate.expiration_date = row.at_xpath('td[@class="date"]/text()').to_s.strip rescue nil
58
- certificate.status = row.at_xpath('td[@class="status"]/text()').to_s.strip rescue nil
71
+ certificate.download_url = "https://developer.apple.com/account/ios/certificate/certificateContentDownload.action?displayId=#{row['certificateId']}&type=#{row['certificateTypeDisplayId']}"
72
+ certificate.expiration_date = row['expirationDateString']
73
+ certificate.status = row['statusString']
59
74
  certificates << certificate
60
75
  end
76
+
61
77
  certificates
62
78
  end
63
79
 
64
80
  def download_certificate(certificate)
65
81
  list_certificates(certificate.type)
66
82
 
67
- page.parser.xpath('//div[@class="nt_multi"]/table/tbody/tr').each do |row|
68
- name = row.at_xpath('td[@class="name"]//p/text()').to_s.strip rescue nil
69
-
70
- if name == certificate.name
71
- download_url = row.at_xpath('td[@class="last"]/a')['href'].to_s.strip rescue nil
72
-
73
- self.pluggable_parser.default = Mechanize::Download
74
- download = get(download_url)
75
- download.save
76
- return download.filename
77
- end
78
- end
79
-
80
- return nil
83
+ self.pluggable_parser.default = Mechanize::Download
84
+ download = get(certificate.download_url)
85
+ download.save
86
+ download.filename
81
87
  end
82
88
 
83
89
  def list_devices
84
- get("https://developer.apple.com/ios/manage/devices/index.action")
90
+ get('https://developer.apple.com/account/ios/device/deviceList.action')
91
+
92
+ regex = /deviceDataURL = "([^"]*)"/
93
+ device_data_url = (page.body.match regex or raise UnexpectedContentError)[1]
94
+
95
+ get(device_data_url)
96
+ device_data = page.content
97
+ parsed_device_data = JSON.parse(device_data)
85
98
 
86
99
  devices = []
87
- page.parser.xpath('//fieldset[@id="fs-0"]/table/tbody/tr').each do |row|
100
+ parsed_device_data['devices'].each do |row|
88
101
  device = Device.new
89
- device.name = row.at_xpath('td[@class="name"]/span/text()').to_s.strip rescue nil
90
- device.udid = row.at_xpath('td[@class="id"]/text()').to_s.strip rescue nil
102
+ device.name = row['name']
103
+ device.udid = row['deviceNumber'] # Apple doesn't provide the UDID on this page anymore
91
104
  devices << device
92
105
  end
93
106
 
94
- if message = page.parser.at_xpath('//p[@class="devicesannounce"]/strong/text()').to_s.strip rescue nil
95
- number_of_devices_available = message.scan(/\d{1,3}/).first.to_i
96
- number_of_devices_available.times do
97
- devices << nil
98
- end
99
- end
100
-
101
107
  devices
102
108
  end
103
109
 
104
110
  def add_devices(*devices)
105
111
  return if devices.empty?
106
112
 
107
- get("https://developer.apple.com/ios/manage/devices/upload.action")
113
+ get('https://developer.apple.com/account/ios/device/deviceCreate.action')
108
114
 
109
115
  begin
110
- file = Tempfile.new(['devices', '.txt'])
111
- file.write("deviceIdentifier\tdeviceName")
116
+ file = Tempfile.new(%w(devices .txt))
117
+ file.write("Device ID\tDevice Name")
112
118
  devices.each do |device|
113
119
  file.write("\n#{device.udid}\t#{device.name}")
114
120
  end
115
121
  file.rewind
116
122
 
117
- if form = page.form_with(:name => 'saveupload')
118
- upload = form.file_uploads.first
119
- upload.file_name = file.path
123
+ form = page.form_with(:name => 'deviceImport') or raise UnexpectedContentError
124
+
125
+ upload = form.file_uploads.first
126
+ upload.file_name = file.path
127
+ form.radiobuttons.first.check()
128
+ form.submit
129
+
130
+ if form = page.form_with(:name => 'deviceSubmit')
131
+ form.method = 'POST'
132
+ form.field_with(:name => 'deviceNames').name = 'name'
133
+ form.field_with(:name => 'deviceNumbers').name = 'deviceNumber'
120
134
  form.submit
135
+ elsif form = page.form_with(:name => 'deviceImport')
136
+ form.submit
137
+ else
138
+ raise UnexpectedContentError
121
139
  end
140
+
122
141
  ensure
123
142
  file.close!
124
143
  end
@@ -127,22 +146,38 @@ module Cupertino
127
146
  def list_profiles(type = :development)
128
147
  url = case type
129
148
  when :development
130
- "https://developer.apple.com/ios/manage/provisioningprofiles/index.action"
149
+ 'https://developer.apple.com/account/ios/profile/profileList.action?type=limited'
131
150
  when :distribution
132
- "https://developer.apple.com/ios/manage/provisioningprofiles/viewDistributionProfiles.action"
151
+ 'https://developer.apple.com/account/ios/profile/profileList.action?type=production'
133
152
  else
134
- raise ArgumentError, "Provisioning profile type must be :development or :distribution"
153
+ raise ArgumentError, 'Provisioning profile type must be :development or :distribution'
135
154
  end
136
155
 
137
156
  get(url)
138
157
 
158
+ regex = /profileDataURL = "([^"]*)"/
159
+ profile_data_url = (page.body.match regex or raise UnexpectedContentError)[1]
160
+
161
+ profile_data_url += case type
162
+ when :development
163
+ '&type=limited'
164
+ when :distribution
165
+ '&type=production'
166
+ end
167
+
168
+ get(profile_data_url)
169
+ profile_data = page.content
170
+ parsed_profile_data = JSON.parse(profile_data)
171
+
139
172
  profiles = []
140
- page.parser.xpath('//fieldset[@id="fs-0"]/table/tbody/tr').each do |row|
173
+ parsed_profile_data['provisioningProfiles'].each do |row|
141
174
  profile = ProvisioningProfile.new
142
- profile.name = row.at_xpath('td[@class="profile"]//text()').to_s.strip rescue nil
175
+ profile.name = row['name']
143
176
  profile.type = type
144
- profile.app_id = row.at_xpath('td[@class="appid"]/text()').to_s.strip rescue nil
145
- profile.status = row.at_xpath('td[@class="statusXcode"]/text()').to_s.strip rescue nil
177
+ profile.app_id = row['appId']['appIdId']
178
+ profile.status = row['status']
179
+ profile.download_url = "https://developer.apple.com/account/ios/profile/profileContentDownload.action?displayId=#{row['provisioningProfileId']}"
180
+ profile.edit_url = "https://developer.apple.com/account/ios/profile/profileEdit.action?provisioningProfileId=#{row['provisioningProfileId']}"
146
181
  profiles << profile
147
182
  end
148
183
  profiles
@@ -151,20 +186,10 @@ module Cupertino
151
186
  def download_profile(profile)
152
187
  list_profiles(profile.type)
153
188
 
154
- page.parser.xpath('//fieldset[@id="fs-0"]/table/tbody/tr').each do |row|
155
- name = row.at_xpath('td[@class="profile"]//span/text()').to_s.strip rescue nil
156
-
157
- if name == profile.name
158
- download_url = row.at_xpath('td[@class="action"]/a')['href'].to_s.strip rescue nil
159
-
160
- self.pluggable_parser.default = Mechanize::Download
161
- download = get(download_url)
162
- download.save
163
- return download.filename
164
- end
165
- end
166
-
167
- return nil
189
+ self.pluggable_parser.default = Mechanize::Download
190
+ download = get(profile.download_url)
191
+ download.save
192
+ download.filename
168
193
  end
169
194
 
170
195
  def manage_devices_for_profile(profile)
@@ -172,154 +197,74 @@ module Cupertino
172
197
 
173
198
  list_profiles(profile.type)
174
199
 
175
- modify_url = nil
176
- page.parser.xpath('//fieldset[@id="fs-0"]/table/tbody/tr').each do |row|
177
- break if modify_url
178
-
179
- name = row.at_xpath('td[@class="profile"]//text()').to_s.strip rescue nil
180
-
181
- if name == profile.name
182
- row.css('td.action a').each do |a|
183
- if /edit\.action/ === a['href']
184
- modify_url = a['href']
185
- end
186
- end
187
- end
188
- end
189
-
190
- if modify_url
191
- get(modify_url)
200
+ get(profile.edit_url)
192
201
 
193
- on, off = [], []
194
- page.parser.css('.checkboxlist.last td').each do |td|
195
- checkbox = td.at_xpath('input[@type="checkbox"]')
202
+ on, off = [], []
203
+ page.search('dd.selectDevices div.rows div').each do |row|
204
+ checkbox = row.search('input[type="checkbox"]').first
196
205
 
197
- device = Device.new
198
- device.name = td.at_xpath('label/text()').to_s.strip rescue nil
199
- device.udid = checkbox['value'].to_s.strip rescue nil
206
+ device = Device.new
207
+ device.name = row.search('span.title').text rescue nil
208
+ device.udid = checkbox['value'] rescue nil
200
209
 
201
- if checkbox['checked']
202
- on << device
203
- else
204
- off << device
205
- end
210
+ if checkbox['checked']
211
+ on << device
212
+ else
213
+ off << device
206
214
  end
215
+ end
207
216
 
208
- devices = yield on, off
217
+ devices = yield on, off
209
218
 
210
- form = page.form_with(:name => 'save')
211
- form.checkboxes_with(:name => "selectedDevices").each do |checkbox|
219
+ form = page.form_with(:name => 'profileEdit') or raise UnexpectedContentError
220
+ form.checkboxes_with(:name => 'deviceIds').each do |checkbox|
221
+ checkbox.check
222
+ if devices.detect{|device| device.udid == checkbox['value']}
212
223
  checkbox.check
213
- if devices.detect{|device| device.udid == checkbox['value']}
214
- checkbox.check
215
- else
216
- checkbox.uncheck
217
- end
224
+ else
225
+ checkbox.uncheck
218
226
  end
219
-
220
- button = form.button_with(:name => 'submit')
221
- form.click_button(button)
222
227
  end
223
228
 
224
- return nil
229
+ form.method = 'POST'
230
+ form.submit
225
231
  end
226
232
 
227
233
  def list_app_ids
228
- get("https://developer.apple.com/ios/manage/bundles/index.action")
234
+ get('https://developer.apple.com/account/ios/identifiers/bundle/bundleList.action')
235
+
236
+ regex = /bundleDataURL = "([^"]*)"/
237
+ bundle_data_url = (page.body.match regex or raise UnexpectedContentError)[1]
238
+
239
+ get(bundle_data_url)
240
+ bundle_data = page.content
241
+ parsed_bundle_data = JSON.parse(bundle_data)
229
242
 
230
243
  app_ids = []
231
- page.parser.xpath('//div[@class="nt_multi"]/table/tbody/tr').each do |row|
244
+ parsed_bundle_data['appIds'].each do |row|
232
245
  app_id = AppID.new
233
- app_id.bundle_seed_id = row.at_xpath('td[@class="name"]/strong/text()').to_s.strip rescue nil
234
- app_id.description = row.at_xpath('td[@class="name"]/text()').to_s.strip rescue nil
235
-
236
- keys = row.xpath('td[@class="name"]/p/text()').collect(&:to_s).collect(&:strip)
237
- app_id.development_properties, app_id.distribution_properties = row.xpath('td')[1..2].collect do |td|
238
- values = td.xpath('p//text()').collect(&:to_s).collect(&:strip).reject{|text| text.empty?}
239
- keys.zip(values)
240
- keys.zip(values)
246
+ app_id.bundle_seed_id = [row['prefix'], row['identifier']].join(".")
247
+ app_id.description = row['name']
248
+
249
+ app_id.development_properties, app_id.distribution_properties = [], []
250
+ row['features'].each do |feature, value|
251
+ if value == true
252
+ app_id.development_properties << feature
253
+ elsif value.kind_of?(String) && !value.empty?
254
+ app_id.development_properties << "#{feature}: #{value}"
255
+ end
256
+ end
257
+
258
+ row['enabledFeatures'].each do |feature|
259
+ app_id.distribution_properties << feature
241
260
  end
242
261
 
243
262
  app_ids << app_id
244
263
  end
264
+
245
265
  app_ids
246
266
  end
247
-
248
- def list_pass_type_ids
249
- get("https://developer.apple.com/ios/manage/passtypeids/index.action")
250
-
251
- pass_type_ids = []
252
- page.parser.xpath('//fieldset[@id="fs-0"]/table/tbody/tr').each do |row|
253
- pass_type_id = PassTypeID.new
254
- pass_type_id.card_id = row.at_xpath('td[@class="checkbox"]/input[@name="selectedValues"]')['value'].to_s.strip rescue nil
255
- pass_type_id.id = row.at_xpath('td[@class="name"]/strong/text()').to_s.strip rescue nil
256
- pass_type_id.description = row.at_xpath('td[@class="name"]/text()').to_s.strip rescue nil
257
- pass_type_id.pass_certificates = row.at_xpath('td[@class="profile"]').inner_text.strip rescue nil
258
-
259
- pass_type_ids << pass_type_id
260
- end
261
- pass_type_ids
262
- end
263
-
264
- def list_pass_certificates(pass_type_id)
265
- pass_type_id = list_pass_type_ids().delete_if{ |item| item.id != pass_type_id }.shift rescue nil
266
- return [] if pass_type_id.nil?
267
-
268
- get("https://developer.apple.com/ios/manage/passtypeids/configure.action?displayId=#{pass_type_id.card_id}")
269
-
270
- pass_certificates = []
271
- page.parser.xpath('//form[@name="form_logginMemberCert"]/table/tr[position()>1]').each do |row|
272
- pass_certificate = PassCertificate.new
273
- pass_certificate.name = row.at_xpath('td[1]').inner_text.strip rescue nil
274
- pass_certificate.status = row.at_xpath('td[2]/span/text()').to_s.strip rescue nil
275
- pass_certificate.expiration_date = row.at_xpath('td[3]/text()').to_s.strip rescue nil
276
- pass_certificate.certificate_id = row.at_xpath('td[4]//a[@id="form_logginMemberCert_"]')['href'].to_s.strip.match(/certDisplayId=(.+?)$/)[1] rescue nil
277
-
278
- pass_certificates << pass_certificate unless pass_certificate.certificate_id.nil?
279
- end
280
- pass_certificates
281
- end
282
-
283
- def add_pass_type_id(pass_type_id, description)
284
- get("https://developer.apple.com/ios/manage/passtypeids/add.action")
285
-
286
- if form = page.form_with(:name => 'save')
287
- form['cardName'] = description
288
- form['cardIdentifier'] = pass_type_id
289
-
290
- button = form.button_with(:name => 'submit')
291
- form.click_button(button)
292
- end
293
- end
294
-
295
- def add_pass_certificate(pass_type_id, csr_path, aps_cert_type = 'development')
296
- pass_type_id = list_pass_type_ids().delete_if{ |item| item.id != pass_type_id }.shift rescue nil
297
- return if pass_type_id.nil?
298
-
299
- csr_contents = ::File.open(csr_path, "rb").read
300
-
301
- post("https://developer.apple.com/ios/assistant/passtypecommit.action", { 'cardIdValue' => pass_type_id.card_id, 'csrValue' => csr_contents, 'apsCertType' => aps_cert_type })
302
-
303
- JSON.parse(page.content)
304
- end
305
-
306
- def pass_type_generate(aps_cert_type = 'development')
307
- post("https://developer.apple.com/ios/assistant/passtypegenerate.action", { 'apsCertType' => aps_cert_type })
308
-
309
- ::JSON.parse(page.content)
310
- end
311
-
312
- def download_pass_certificate(pass_type_id, certificate_id = nil)
313
- pass_certificate = (certificate_id.nil? ? list_pass_certificates(pass_type_id).last : list_pass_certificates(pass_type_id).delete_if{ |item| item.certificate_id != certificate_id }.shift) rescue nil
314
- return nil if pass_certificate.nil?
315
267
 
316
- self.pluggable_parser.default = Mechanize::Download
317
- download = get("/ios/manage/passtypeids/downloadCert.action?certDisplayId=#{pass_certificate.certificate_id}")
318
- download.filename = "#{pass_certificate.certificate_id}.cer"
319
- download.save
320
- return download.filename
321
- end
322
-
323
268
  private
324
269
 
325
270
  def login!
@@ -332,9 +277,9 @@ module Cupertino
332
277
 
333
278
  def select_team!
334
279
  if form = page.form_with(:name => 'saveTeamSelection')
335
- team_list = form.field_with(:name => 'memberDisplayId')
336
- team_option = team_list.option_with(:text => self.team)
337
- team_option.select
280
+ # self.team now stores team ID, not name
281
+ team_option = form.radiobutton_with(:value => self.team)
282
+ team_option.check
338
283
 
339
284
  button = form.button_with(:name => 'action:saveTeamSelection!save')
340
285
  form.click_button(button)
@@ -10,17 +10,17 @@ command :'certificates:list' do |c|
10
10
  say_warning "No #{type} certificates found." and abort if certificates.empty?
11
11
 
12
12
  table = Terminal::Table.new do |t|
13
- t << ["Name", "Provisioning Profiles", "Expiration Date", "Status"]
13
+ t << ["Name", "Type", "Expiration Date", "Status"]
14
14
  t.add_separator
15
15
  certificates.each do |certificate|
16
16
  status = case certificate.status
17
- when "Issued"
18
- certificate.status.green
19
- else
20
- certificate.status.red
17
+ when "Issued"
18
+ certificate.status.green
19
+ else
20
+ certificate.status.red
21
21
  end
22
22
 
23
- t << [certificate.name, certificate.provisioning_profiles.join("\n"), certificate.expiration_date, status]
23
+ t << [certificate.name, certificate.type, certificate.expiration_date, status]
24
24
  end
25
25
  end
26
26
 
@@ -36,7 +36,7 @@ command :'devices:add' do |c|
36
36
 
37
37
  devices = []
38
38
  args.each do |arg|
39
- components = arg.strip.gsub(/\"/, '').split(/\=/)
39
+ components = arg.strip.gsub(/"/, '').split(/\=/)
40
40
  device = Device.new
41
41
  device.name = components.first
42
42
  device.udid = components.last
@@ -4,12 +4,12 @@ command :login do |c|
4
4
  c.description = ''
5
5
 
6
6
  c.action do |args, options|
7
- say_warning "You are already authenticated" if Security::InternetPassword.find(:server => Cupertino::HOSTNAME)
7
+ say_warning "You are already authenticated" if Security::InternetPassword.find(:server => Cupertino::ProvisioningPortal::HOST)
8
8
 
9
9
  user = ask "Username:"
10
10
  pass = password "Password:"
11
11
 
12
- Security::InternetPassword.add(Cupertino::HOSTNAME, user, pass)
12
+ Security::InternetPassword.add(Cupertino::ProvisioningPortal::HOST, user, pass)
13
13
 
14
14
  say_ok "Account credentials saved"
15
15
  end
@@ -4,9 +4,9 @@ command :logout do |c|
4
4
  c.description = ''
5
5
 
6
6
  c.action do |args, options|
7
- say_error "You are not authenticated" and abort unless Security::InternetPassword.find(:server => Cupertino::HOSTNAME)
7
+ say_error "You are not authenticated" and abort unless Security::InternetPassword.find(:server => Cupertino::ProvisioningPortal::HOST)
8
8
 
9
- Security::InternetPassword.delete(:server => Cupertino::HOSTNAME)
9
+ Security::InternetPassword.delete(:server => Cupertino::ProvisioningPortal::HOST)
10
10
 
11
11
  say_ok "Account credentials removed"
12
12
  end
@@ -47,11 +47,11 @@ command :'profiles:manage:devices' do |c|
47
47
  lines = ["# Comment / Uncomment Devices to Turn Off / On for Provisioning Profile"]
48
48
  lines += on.collect{|device| "#{device}"}
49
49
  lines += off.collect{|device| "# #{device}"}
50
- result = ask_editor lines.join("\n")
50
+ (result = ask_editor lines.join("\n")) or abort("EDITOR undefined. Try run 'export EDITOR=vi'")
51
51
 
52
52
  devices = []
53
53
  result.split(/\n+/).each do |line|
54
- next if /^\#/ === line
54
+ next if /^#/ === line
55
55
  components = line.split(/\s+/)
56
56
  device = Device.new
57
57
  device.udid = components.pop
@@ -5,6 +5,5 @@ require 'cupertino/provisioning_portal/commands/certificates'
5
5
  require 'cupertino/provisioning_portal/commands/devices'
6
6
  require 'cupertino/provisioning_portal/commands/profiles'
7
7
  require 'cupertino/provisioning_portal/commands/app_ids'
8
- require 'cupertino/provisioning_portal/commands/pass_type_ids'
9
8
  require 'cupertino/provisioning_portal/commands/login'
10
9
  require 'cupertino/provisioning_portal/commands/logout'
@@ -24,19 +24,28 @@ module Cupertino
24
24
  end
25
25
 
26
26
  def team
27
- teams = page.form_with(:name => 'saveTeamSelection').field_with(:name => 'memberDisplayId').options.collect(&:text)
28
- @team ||= choose "Select a team:", *teams
27
+ # we're working with radio buttons instead of a drop down menu
28
+ teams = page.form_with(:name => 'saveTeamSelection').radiobuttons
29
+ # create a dictionary of team.value -> team name
30
+ formatted_teams = {}
31
+ teams.each do |team|
32
+ # we can't use team.label as this only returns the last label
33
+ # Apple use two labels with the same for="", we want the first
34
+ formatted_teams[team.value] = page.search("label[for=\"#{team.dom_id}\"]").first.text.strip
35
+ end
36
+ teamname = choose "Select a team:", *formatted_teams.values
37
+ @team ||= formatted_teams.key(teamname)
29
38
  end
30
39
  end
31
40
  end
32
41
 
33
42
  @agent
34
43
  end
35
-
44
+
36
45
  def pluralize(n, singular, plural = nil)
37
46
  n.to_i == 1 ? "1 #{singular}" : "#{n} #{plural || singular + 's'}"
38
47
  end
39
-
48
+
40
49
  def try
41
50
  return unless block_given?
42
51
 
@@ -3,7 +3,10 @@ require 'certified'
3
3
 
4
4
  module Cupertino
5
5
  module ProvisioningPortal
6
+ HOST = "developer.apple.com"
7
+
6
8
  class UnsuccessfulAuthenticationError < RuntimeError; end
9
+ class UnexpectedContentError < RuntimeError; end
7
10
 
8
11
  class Device < Struct.new(:name, :udid)
9
12
  def to_s
@@ -11,7 +14,7 @@ module Cupertino
11
14
  end
12
15
  end
13
16
 
14
- class Certificate < Struct.new(:name, :type, :provisioning_profiles, :expiration_date, :status)
17
+ class Certificate < Struct.new(:name, :type, :expiration_date, :status, :download_url) #:provisioning_profiles,
15
18
  def to_s
16
19
  "#{self.name}"
17
20
  end
@@ -23,18 +26,18 @@ module Cupertino
23
26
  end
24
27
  end
25
28
 
26
- class ProvisioningProfile < Struct.new(:name, :type, :app_id, :status)
29
+ class ProvisioningProfile < Struct.new(:name, :type, :app_id, :status, :download_url, :edit_url)
27
30
  def to_s
28
31
  "#{self.name}"
29
32
  end
30
33
  end
31
-
34
+
32
35
  class PassTypeID < Struct.new(:description, :id, :pass_certificates, :card_id)
33
36
  def to_s
34
37
  "#{self.id} #{self.description}"
35
38
  end
36
39
  end
37
-
40
+
38
41
  class PassCertificate < Struct.new(:name, :status, :expiration_date, :certificate_id)
39
42
  def to_s
40
43
  "#{self.certificate_id}"
data/lib/cupertino.rb CHANGED
@@ -1,4 +1,3 @@
1
1
  module Cupertino
2
- VERSION = '0.7.1'
3
- HOSTNAME = "developer.apple.com"
2
+ VERSION = '0.8.0'
4
3
  end