cupertinopro 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 125d0575cf1aff5322d6896069f2f855dd7dd714
4
+ data.tar.gz: b68575108f6659ac4f2a40258805a7f8bec9a18a
5
+ SHA512:
6
+ metadata.gz: 78551d611468315678e0f2eb532e116aadda7cf7b47e5b5b6297d06992a7c344ff7fb5af7cffe4ca8b829d1e6b4bd6e1e0171cdc9191f382ac2b0bc86476f4e6
7
+ data.tar.gz: 84e418d022c43d29fc944fd72f583dc2af31fd6cdf94215d7a3f9ad5d8c17232fb78aae55a2e57c5ab8a8d0263eca1a02b91593a688afbae66ee3b42ba8c2c3c
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 arvystate.net
4
+
5
+ Copyright (c) 2012 Mattt Thompson (http://mattt.me/)
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in
15
+ all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,137 @@
1
+ ![Cupertino](https://raw.github.com/mattt/nomad-cli.com/assets/cupertino-banner.png)
2
+
3
+ Automate administrative tasks that you would normally have to do through the Apple Dev Center websites. Life's too short to manage device identifiers by hand!
4
+
5
+ > Cupertino is named after [Cupertino, CA](http://en.wikipedia.org/wiki/Cupertino,_California): home to Apple, Inc.'s world headquarters.
6
+ > It's part of a series of world-class command-line utilities for iOS development, which includes [Shenzhen](https://github.com/mattt/shenzhen) (Building & Distribution), [Houston](https://github.com/mattt/houston) (Push Notifications), [Venice](https://github.com/mattt/venice) (In-App Purchase Receipt Verification), and [Dubai](https://github.com/mattt/dubai) (Passbook pass generation).
7
+
8
+ ## Requirements
9
+
10
+ Cupertino requires the [Xcode Command Line Tools](https://developer.apple.com/xcode/), which can be installed with the following command:
11
+
12
+ $ xcode-select --install
13
+
14
+ ## Installation
15
+
16
+ $ gem install cupertino
17
+
18
+ ## Usage
19
+
20
+ ### Authentication
21
+
22
+ $ ios login
23
+
24
+
25
+ _Credentials are saved in the Keychain. You will not be prompted for your username or password by commands while you are logged in. (Mac only)_
26
+
27
+ ### Devices
28
+
29
+ $ ios devices:list
30
+
31
+ +------------------------------+---------------------------------------+
32
+ | Listing 2 devices. You can register 98 additional devices. |
33
+ +---------------------------+------------------------------------------+
34
+ | Device Name | Device Identifier |
35
+ +---------------------------+------------------------------------------+
36
+ | Johnny Appleseed iPad | 0123456789012345678901234567890123abcdef |
37
+ | Johnny Appleseed iPhone | abcdef0123456789012345678901234567890123 |
38
+ +---------------------------+------------------------------------------+
39
+
40
+ $ ios devices:add "iPad 1"=abc123
41
+ $ ios devices:add "iPad 2"=def456 "iPad 3"=ghi789 ...
42
+
43
+ ### Provisioning Profiles
44
+
45
+ $ ios profiles:list
46
+
47
+ +----------------------------------+--------------+---------+
48
+ | Profile | App ID | Status |
49
+ +----------------------------------+--------------+---------+
50
+ | iOS Team Provisioning Profile: * | ABCDEFG123.* | Valid |
51
+ +----------------------------------+--------------+---------+
52
+
53
+ ---
54
+
55
+ $ ios profiles:manage:devices
56
+
57
+ _Opens an editor with a list of devices, each of which can be commented / uncommented to turn them off / on for that provisioning profile._
58
+
59
+ # Comment / Uncomment Devices to Turn Off / On for Provisioning Profile
60
+ Johnny Appleseed iPad 0123456789012345678901234567890123abcdef
61
+ # Johnny Appleseed iPhone abcdef0123456789012345678901234567890123
62
+
63
+
64
+ $ ios profiles:devices:add MyApp_Development_Profile "Johnny Appleseed iPad"=0123456789012345678901234567890123abcdef "Johnny Appleseed iPhone"=abcdef0123456789012345678901234567890123
65
+
66
+ _Adds (without an editor) a list of devices to a provisioning profile_
67
+
68
+ $ ios profiles:devices:remove MyApp_Development_Profile "Johnny Old iPad"=0123456789012345678901234567890123abcdef "Johnny Old iPhone"=abcdef0123456789012345678901234567890123
69
+
70
+ _Removes (without an editor) a list of devices from a provisioning profile_
71
+
72
+ ### App IDs
73
+
74
+ $ ios app_ids:list
75
+
76
+ +-----------------------------+------------------------+-------------------+-------------------+
77
+ | Bundle Seed ID | Description | Development | Distribution |
78
+ +-----------------------------+------------------------+-------------------+-------------------+
79
+ | 123ABCDEFG.com.mattt.bundle | App Bundle Description | Passes | Passes |
80
+ | | | Data Protection | Data Protection |
81
+ | | | iCloud | iCloud |
82
+ | | | In-App Purchase | In-App Purchase |
83
+ | | | Game Center | Game Center |
84
+ | | | Push Notification | Push Notification |
85
+ +-----------------------------+------------------------+-------------------+-------------------+
86
+
87
+ ### Certificates
88
+
89
+ $ ios certificates:list
90
+
91
+ +------------------+----------------------------------+-----------------+--------+
92
+ | Name | Provisioning Profiles | Expiration Date | Status |
93
+ +------------------+----------------------------------+-----------------+--------+
94
+ | Johnny Appleseed | iOS Team Provisioning Profile: * | Dec 23, 2012 | Issued |
95
+ +------------------+----------------------------------+-----------------+--------+
96
+
97
+ ## Commands
98
+
99
+ - `login`
100
+ - `logout`
101
+ - `devices:add`
102
+ - `devices:list`
103
+ - `profiles:list`
104
+ - `profiles:manage:devices`
105
+ - `profiles:manage:devices:add`
106
+ - `profiles:manage:devices:remove`
107
+ - `profiles:download`
108
+ - `profiles:download:all`
109
+ - `certificates:list`
110
+ - `certificates:download`
111
+ - `app_ids:list`
112
+
113
+ ### Disabled Commands
114
+
115
+ > With the latest updates to the Apple Developer Portal, the following functionality has been removed.
116
+
117
+ - `pass_type_ids:list`
118
+ - `pass_type_ids:add`
119
+ - `pass_type_ids:certificates:list`
120
+ - `pass_type_ids:certificates:add`
121
+ - `pass_type_ids:certificates:download`
122
+
123
+ ## Proxies
124
+
125
+ Cupertino will access the provisioning portal through a proxy if the `HTTP_PROXY` environment variable is set, with optional credentials `HTTP_PROXY_USER` and `HTTP_PROXY_PASSWORD`.
126
+
127
+ ## Contact
128
+
129
+ Mattt Thompson
130
+
131
+ - http://github.com/mattt
132
+ - http://twitter.com/mattt
133
+ - m@mattt.me
134
+
135
+ ## License
136
+
137
+ Cupertino is available under the MIT license. See the LICENSE file for more info.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler"
2
+ Bundler.setup
3
+
4
+ gemspec = eval(File.read("cupertinopro.gemspec"))
5
+
6
+ task :build => "#{gemspec.full_name}.gem"
7
+
8
+ file "#{gemspec.full_name}.gem" => gemspec.files + ["cupertinopro.gemspec"] do
9
+ system "gem build cupertinopro.gemspec"
10
+ end
data/bin/iospro ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'commander/import'
4
+ require 'terminal-table'
5
+ require 'term/ansicolor'
6
+ require 'csv'
7
+
8
+ #$:.unshift(File.join(File.dirname(__FILE__), "/../lib"))
9
+
10
+ require 'cupertinopro'
11
+
12
+ HighLine.track_eof = false # Fix for built-in Ruby
13
+ Signal.trap("INT") {} # Suppress backtrace when exiting command
14
+
15
+ program :version, CupertinoPro::VERSION
16
+ program :description, 'A supercharged command-line interface for the iOS Provisioning Portal'
17
+
18
+ program :help, 'Author', 'Mattt Thompson <m@mattt.me>'
19
+ program :help, 'Website', 'https://github.com/mattt'
20
+ program :help_formatter, :compact
21
+
22
+ default_command :help
23
+
24
+ require 'cupertinopro/provisioning_portal'
25
+ require 'cupertinopro/provisioning_portal/commands'
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ require "cupertinopro/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "cupertinopro"
8
+ s.license = "MIT"
9
+ s.authors = ["Mattt Thompson", "Dal Rupnik"]
10
+ s.email = "legoless@arvystate.net"
11
+ s.homepage = "http://nomad-cli.com"
12
+ s.version = CupertinoPro::VERSION
13
+ s.platform = Gem::Platform::RUBY
14
+ s.summary = "CupertinoPro"
15
+ s.description = "A supercharged command-line interface for the iOS Provisioning Portal"
16
+
17
+ s.add_dependency "commander", "~> 4.1.2"
18
+ s.add_dependency "terminal-table", "~> 1.4.5"
19
+ s.add_dependency "term-ansicolor", "~> 1.0.7"
20
+ s.add_dependency "mechanize", "~> 2.5.1"
21
+ s.add_dependency "nokogiri", "~> 1.5.9"
22
+ s.add_dependency "security", "~> 0.1.2"
23
+ s.add_dependency "shenzhen", ">= 0.0.1"
24
+ s.add_dependency "certified", ">= 0.1.0"
25
+
26
+ s.add_development_dependency "rspec"
27
+ s.add_development_dependency "rake"
28
+
29
+ s.files = Dir["./**/*"].reject { |file| file =~ /\.\/(bin|log|pkg|script|spec|test|vendor)/ }
30
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
31
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
32
+ s.require_paths = ["lib"]
33
+ end
@@ -0,0 +1,325 @@
1
+ require 'mechanize'
2
+ require 'security'
3
+ require 'uri'
4
+ require 'json'
5
+ require 'logger'
6
+
7
+ module CupertinoPro
8
+ module ProvisioningPortal
9
+ class Agent < ::Mechanize
10
+ attr_accessor :username, :password, :team, :format
11
+
12
+ def initialize
13
+ super
14
+
15
+ self.user_agent_alias = 'Mac Safari'
16
+
17
+ self.log ||= Logger.new(STDOUT)
18
+ self.log.level = Logger::ERROR
19
+
20
+ if ENV['HTTP_PROXY']
21
+ uri = URI.parse(ENV['HTTP_PROXY'])
22
+ user = ENV['HTTP_PROXY_USER'] if ENV['HTTP_PROXY_USER']
23
+ password = ENV['HTTP_PROXY_PASSWORD'] if ENV['HTTP_PROXY_PASSWORD']
24
+
25
+ set_proxy(uri.host, uri.port, user || uri.user, password || uri.password)
26
+ end
27
+
28
+ pw = Security::InternetPassword.find(:server => CupertinoPro::ProvisioningPortal::HOST)
29
+ @username, @password = pw.attributes['acct'], pw.password if pw
30
+ end
31
+
32
+ def username=(value)
33
+ @username = value
34
+
35
+ pw = Security::InternetPassword.find(:a => self.username, :server => CupertinoPro::ProvisioningPortal::HOST)
36
+ @password = pw.password if pw
37
+ end
38
+
39
+ def get(uri, parameters = [], referer = nil, headers = {})
40
+ uri = ::File.join("https://#{CupertinoPro::ProvisioningPortal::HOST}", uri) unless /^https?/ === uri
41
+
42
+ 3.times do
43
+ super(uri, parameters, referer, headers)
44
+
45
+ return page unless page.respond_to?(:title)
46
+
47
+ case page.title
48
+ when /Sign in with your Apple ID/
49
+ login! and redo
50
+ when /Select Team/
51
+ select_team! and redo
52
+ else
53
+ return page
54
+ end
55
+ end
56
+
57
+ raise UnsuccessfulAuthenticationError
58
+ end
59
+
60
+ def list_certificates(type = :development)
61
+ url = case type
62
+ when :development
63
+ "https://developer.apple.com/account/iospro/certificate/certificateList.action?type=development"
64
+ when :distribution
65
+ "https://developer.apple.com/account/iospro/certificate/certificateList.action?type=distribution"
66
+ else
67
+ raise ArgumentError, "Certificate type must be :development or :distribution"
68
+ end
69
+
70
+ get(url)
71
+
72
+ regex = /certificateDataURL = "([^"]*)"/
73
+ certificate_data_url = (page.body.match regex or raise UnexpectedContentError)[1]
74
+
75
+ regex = /certificateRequestTypes = "([^"]*)"/
76
+ certificate_request_types = (page.body.match regex or raise UnexpectedContentError)[1]
77
+
78
+ regex = /certificateStatuses = "([^"]*)"/
79
+ certificate_statuses = (page.body.match regex or raise UnexpectedContentError)[1]
80
+
81
+ certificate_data_url += certificate_request_types + certificate_statuses
82
+
83
+ post(certificate_data_url)
84
+ certificate_data = page.content
85
+ parsed_certificate_data = JSON.parse(certificate_data)
86
+
87
+ certificates = []
88
+ parsed_certificate_data['certRequests'].each do |row|
89
+ certificate = Certificate.new
90
+ certificate.name = row['name']
91
+ certificate.type = type
92
+ certificate.download_url = "https://developer.apple.com/account/iospro/certificate/certificateContentDownload.action?displayId=#{row['certificateId']}&type=#{row['certificateTypeDisplayId']}"
93
+ certificate.expiration_date = row['expirationDateString']
94
+ certificate.status = row['statusString']
95
+ certificates << certificate
96
+ end
97
+
98
+ certificates
99
+ end
100
+
101
+ def download_certificate(certificate)
102
+ list_certificates(certificate.type)
103
+
104
+ self.pluggable_parser.default = Mechanize::Download
105
+ download = post(certificate.download_url)
106
+ download.save
107
+ download.filename
108
+ end
109
+
110
+ def list_devices
111
+ get('https://developer.apple.com/account/iospro/device/deviceList.action')
112
+
113
+ regex = /deviceDataURL = "([^"]*)"/
114
+ device_data_url = (page.body.match regex or raise UnexpectedContentError)[1]
115
+
116
+ post(device_data_url)
117
+
118
+ device_data = page.content
119
+ parsed_device_data = JSON.parse(device_data)
120
+
121
+ devices = []
122
+ parsed_device_data['devices'].each do |row|
123
+ #puts row
124
+
125
+ device = Device.new
126
+ device.name = row['name']
127
+ device.enabled = (row['status'] == 'c' ? 'Y' : 'N')
128
+ device.device_id = row['deviceId']
129
+ device.udid = row['deviceNumber']
130
+ devices << device
131
+ end
132
+
133
+ devices
134
+ end
135
+
136
+ def add_devices(*devices)
137
+ return if devices.empty?
138
+
139
+ get('https://developer.apple.com/account/iospro/device/deviceCreate.action')
140
+
141
+ begin
142
+ file = Tempfile.new(%w(devices .txt))
143
+ file.write("Device ID\tDevice Name")
144
+ devices.each do |device|
145
+ file.write("\n#{device.udid}\t#{device.name}")
146
+ end
147
+ file.rewind
148
+
149
+ form = page.form_with(:name => 'deviceImport') or raise UnexpectedContentError
150
+
151
+ upload = form.file_uploads.first
152
+ upload.file_name = file.path
153
+ form.radiobuttons.first.check()
154
+ form.submit
155
+
156
+ if form = page.form_with(:name => 'deviceSubmit')
157
+ form.method = 'POST'
158
+ form.field_with(:name => 'deviceNames').name = 'name'
159
+ form.field_with(:name => 'deviceNumbers').name = 'deviceNumber'
160
+ form.submit
161
+ elsif form = page.form_with(:name => 'deviceImport')
162
+ form.submit
163
+ else
164
+ raise UnexpectedContentError
165
+ end
166
+
167
+ #puts page.body
168
+
169
+ ensure
170
+ file.close!
171
+ end
172
+ end
173
+
174
+ def list_profiles(type = :development)
175
+ url = case type
176
+ when :development
177
+ 'https://developer.apple.com/account/iospro/profile/profileList.action?type=limited'
178
+ when :distribution
179
+ 'https://developer.apple.com/account/iospro/profile/profileList.action?type=production'
180
+ else
181
+ raise ArgumentError, 'Provisioning profile type must be :development or :distribution'
182
+ end
183
+
184
+ self.pluggable_parser.default = Mechanize::File
185
+ get(url)
186
+
187
+ regex = /profileDataURL = "([^"]*)"/
188
+ profile_data_url = (page.body.match regex or raise UnexpectedContentError)[1]
189
+
190
+ profile_data_url += case type
191
+ when :development
192
+ '&type=limited'
193
+ when :distribution
194
+ '&type=production'
195
+ end
196
+
197
+ post(profile_data_url)
198
+
199
+ profile_data = page.content
200
+ parsed_profile_data = JSON.parse(profile_data)
201
+
202
+ profiles = []
203
+ parsed_profile_data['provisioningProfiles'].each do |row|
204
+ #puts row
205
+
206
+ profile = ProvisioningProfile.new
207
+ profile.name = row['name']
208
+ profile.type = type
209
+ profile.app_id = row['appId']['appIdId']
210
+ profile.status = row['status']
211
+ profile.download_url = "https://developer.apple.com/account/iospro/profile/profileContentDownload.action?displayId=#{row['provisioningProfileId']}"
212
+ profile.edit_url = "https://developer.apple.com/account/iospro/profile/profileEdit.action?provisioningProfileId=#{row['provisioningProfileId']}"
213
+ profile.uuid = row['UUID']
214
+ profiles << profile
215
+ end
216
+
217
+ profiles
218
+ end
219
+
220
+ def download_profile(profile)
221
+ self.pluggable_parser.default = Mechanize::Download
222
+ download = get(profile.download_url)
223
+ download.save
224
+ download.filename
225
+ end
226
+
227
+ def manage_devices_for_profile(profile)
228
+ raise ArgumentError unless block_given?
229
+
230
+ devices = list_devices
231
+
232
+ begin
233
+ get(profile.edit_url)
234
+ rescue Mechanize::ResponseCodeError
235
+ say_error "Cannot manage devices for #{profile}" and abort
236
+ end
237
+
238
+ on, off = [], []
239
+ page.search('dd.selectDevices div.rows div').each do |row|
240
+ checkbox = row.search('input[type="checkbox"]').first
241
+ device = devices.detect{|device| device.device_id == checkbox['value']}
242
+
243
+ if checkbox['checked']
244
+ on << device
245
+ else
246
+ off << device
247
+ end
248
+ end
249
+
250
+ devices = yield on, off
251
+
252
+ form = page.form_with(:name => 'profileEdit') or raise UnexpectedContentError
253
+ form.checkboxes_with(:name => 'deviceIds').each do |checkbox|
254
+ if devices.detect{|device| device.device_id == checkbox['value']}
255
+ checkbox.check
256
+ else
257
+ checkbox.uncheck
258
+ end
259
+ end
260
+
261
+ adssuv = cookies.find{|cookie| cookie.name == 'adssuv'}
262
+ form.add_field!('adssuv-value', Mechanize::Util::uri_unescape(adssuv.value))
263
+
264
+ form.method = 'POST'
265
+ form.submit
266
+ end
267
+
268
+ def list_app_ids
269
+ get('https://developer.apple.com/account/iospro/identifiers/bundle/bundleList.action')
270
+
271
+ regex = /bundleDataURL = "([^"]*)"/
272
+ bundle_data_url = (page.body.match regex or raise UnexpectedContentError)[1]
273
+
274
+ post(bundle_data_url)
275
+ bundle_data = page.content
276
+ parsed_bundle_data = JSON.parse(bundle_data)
277
+
278
+ app_ids = []
279
+ parsed_bundle_data['appIds'].each do |row|
280
+ app_id = AppID.new
281
+ app_id.bundle_seed_id = [row['prefix'], row['identifier']].join(".")
282
+ app_id.description = row['name']
283
+
284
+ app_id.development_properties, app_id.distribution_properties = [], []
285
+ row['features'].each do |feature, value|
286
+ if value == true
287
+ app_id.development_properties << feature
288
+ elsif value.kind_of?(String) && !value.empty?
289
+ app_id.development_properties << "#{feature}: #{value}"
290
+ end
291
+ end
292
+
293
+ row['enabledFeatures'].each do |feature|
294
+ app_id.distribution_properties << feature
295
+ end
296
+
297
+ app_ids << app_id
298
+ end
299
+
300
+ app_ids
301
+ end
302
+
303
+ private
304
+
305
+ def login!
306
+ if form = page.forms.first
307
+ form.fields_with(type: 'text').first.value = self.username
308
+ form.fields_with(type: 'password').first.value = self.password
309
+
310
+ form.submit
311
+ end
312
+ end
313
+
314
+ def select_team!
315
+ if form = page.form_with(:name => 'saveTeamSelection')
316
+ team_option = form.radiobutton_with(:value => self.team_id)
317
+ team_option.check
318
+
319
+ button = form.button_with(:name => 'action:saveTeamSelection!save')
320
+ form.click_button(button)
321
+ end
322
+ end
323
+ end
324
+ end
325
+ end
@@ -0,0 +1,38 @@
1
+ COLORS_BY_PROPERTY_VALUES = {
2
+ "Enabled" => :green,
3
+ "Configurable" => :yellow,
4
+ "Unavailable" => :underline
5
+ }
6
+
7
+ command :'app_ids:list' do |c|
8
+ c.syntax = 'iospro app_ids:list'
9
+ c.summary = 'Lists the App IDs'
10
+ c.description = ''
11
+
12
+ c.action do |args, options|
13
+ app_ids = try{agent.list_app_ids}
14
+
15
+ title = "Legend: #{COLORS_BY_PROPERTY_VALUES.collect{|k, v| k.send(v)}.join(', ')}"
16
+ table = Terminal::Table.new :title => title do |t|
17
+ t << ["Bundle Seed ID", "Description", "Development", "Distribution"]
18
+ app_ids.each do |app_id|
19
+ t << :separator
20
+
21
+ row = [app_id.bundle_seed_id, app_id.description]
22
+ [app_id.development_properties, app_id.distribution_properties].each do |properties|
23
+ values = []
24
+ properties.each do |key, value|
25
+ color = COLORS_BY_PROPERTY_VALUES[value] || :reset
26
+ values << key.sub(/\:$/, "").send(color)
27
+ end
28
+ row << values.join("\n")
29
+ end
30
+ t << row
31
+ end
32
+ end
33
+
34
+ puts table
35
+ end
36
+ end
37
+
38
+ alias_command :app_ids, :'app_ids:list'
@@ -0,0 +1,51 @@
1
+ command :'certificates:list' do |c|
2
+ c.syntax = 'iospro certificates:list [development|distribution]'
3
+ c.summary = 'Lists the Certificates'
4
+ c.description = ''
5
+
6
+ c.action do |args, options|
7
+ type = args.first.downcase.to_sym rescue nil
8
+ certificates = try{agent.list_certificates(type ||= :development)}
9
+
10
+ say_warning "No #{type} certificates found." and abort if certificates.empty?
11
+
12
+ table = Terminal::Table.new do |t|
13
+ t << ["Name", "Type", "Expiration Date", "Status"]
14
+ t.add_separator
15
+ certificates.each do |certificate|
16
+ status = case certificate.status
17
+ when "Issued"
18
+ certificate.status.green
19
+ else
20
+ certificate.status.red
21
+ end
22
+
23
+ t << [certificate.name, certificate.type, certificate.expiration_date, status]
24
+ end
25
+ end
26
+
27
+ puts table
28
+ end
29
+ end
30
+
31
+ alias_command :certificates, :'certificates:list'
32
+
33
+ command :'certificates:download' do |c|
34
+ c.syntax = 'iospro certificates:download [development|distribution]'
35
+ c.summary = 'Downloads the Certificates'
36
+ c.description = ''
37
+
38
+ c.action do |args, options|
39
+ type = args.first.downcase.to_sym rescue nil
40
+ certificates = try{agent.list_certificates(type ||= :development)}
41
+
42
+ say_warning "No #{type} certificates found." and abort if certificates.empty?
43
+
44
+ certificate = choose "Select a certificate to download:", *certificates
45
+ if filename = agent.download_certificate(certificate)
46
+ say_ok "Successfully downloaded: '#{filename}'"
47
+ else
48
+ say_error "Could not download certificate"
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,65 @@
1
+ command :'devices:list' do |c|
2
+ c.syntax = 'iospro devices:list'
3
+ c.summary = 'Lists the Name and ID of Devices in the Provisioning Portal'
4
+ c.description = ''
5
+
6
+ c.action do |args, options|
7
+ devices = try{agent.list_devices}
8
+
9
+ if (agent.format == "csv")
10
+ csv_string = CSV.generate do |csv|
11
+ csv << ["Device Name", "Device Identifier", "Enabled"]
12
+
13
+ devices.compact.each do |device|
14
+ csv << [device.name, device.udid, device.enabled]
15
+ end
16
+ end
17
+
18
+ puts csv_string
19
+ else
20
+ number_of_devices = devices.compact.length
21
+ number_of_additional_devices = devices.length - number_of_devices
22
+
23
+ title = "Listing #{pluralize(number_of_devices, 'device')} "
24
+ title += "(You can register #{pluralize(number_of_additional_devices, 'additional device')})" if number_of_additional_devices > 0
25
+
26
+ table = Terminal::Table.new :title => title do |t|
27
+ t << ["Device Name", "Device Identifier", "Enabled"]
28
+ t.add_separator
29
+ devices.compact.each do |device|
30
+ t << [device.name, device.udid, device.enabled]
31
+ end
32
+ end
33
+
34
+ table.align_column 2, :center
35
+
36
+ puts table
37
+ end
38
+ end
39
+ end
40
+
41
+ alias_command :devices, :'devices:list'
42
+
43
+ command :'devices:add' do |c|
44
+ c.syntax = 'iospro devices:add DEVICE_NAME=DEVICE_ID [...]'
45
+ c.summary = 'Adds a device to the Provisioning Portal'
46
+ c.description = ''
47
+
48
+ c.action do |args, options|
49
+ say_error "Missing arguments, expected DEVICE_NAME=DEVICE_ID" and abort if args.nil? or args.empty?
50
+
51
+ devices = []
52
+ args.each do |arg|
53
+ components = arg.strip.gsub(/"/, '').split(/\=/)
54
+ device = Device.new
55
+ device.name = components.first
56
+ device.udid = components.last
57
+ say_warning "Invalid UDID: #{device.udid}" and next unless /\h{40}/ === device.udid
58
+ devices << device
59
+ end
60
+
61
+ agent.add_devices(*devices)
62
+
63
+ say_ok "Added #{pluralize(devices.length, 'device')}"
64
+ end
65
+ end
@@ -0,0 +1,16 @@
1
+ command :login do |c|
2
+ c.syntax = 'iospro login'
3
+ c.summary = 'Save account credentials'
4
+ c.description = ''
5
+
6
+ c.action do |args, options|
7
+ say_warning "You are already authenticated" if Security::InternetPassword.find(:server => CupertinoPro::ProvisioningPortal::HOST)
8
+
9
+ user = ask "Username:"
10
+ pass = password "Password:"
11
+
12
+ Security::InternetPassword.add(CupertinoPro::ProvisioningPortal::HOST, user, pass)
13
+
14
+ say_ok "Account credentials saved"
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ command :logout do |c|
2
+ c.syntax = 'iospro logout'
3
+ c.summary = 'Remove account credentials'
4
+ c.description = ''
5
+
6
+ c.action do |args, options|
7
+ say_error "You are not authenticated" and abort unless Security::InternetPassword.find(:server => CupertinoPro::ProvisioningPortal::HOST)
8
+
9
+ Security::InternetPassword.delete(:server => CupertinoPro::ProvisioningPortal::HOST)
10
+
11
+ say_ok "Account credentials removed"
12
+ end
13
+ end
@@ -0,0 +1,200 @@
1
+ command :'profiles:list' do |c|
2
+ c.syntax = 'iospro profiles:list'
3
+ c.summary = 'Lists the Provisioning Profiles'
4
+ c.description = ''
5
+
6
+ c.option '--type [TYPE]', [:development, :distribution], "Type of profile (development or distribution; defaults to development)"
7
+
8
+ c.action do |args, options|
9
+ type = (options.type.downcase.to_sym if options.type) || :development
10
+ profiles = try{agent.list_profiles(type)}
11
+
12
+ if (agent.format == "csv")
13
+ csv_string = CSV.generate do |csv|
14
+ csv << ["Profile", "App ID", "UUID", "Status"]
15
+
16
+ profiles.each do |profile|
17
+ csv << [profile.name, profile.app_id, profile.uuid, profile.status]
18
+ end
19
+ end
20
+
21
+ puts csv_string
22
+ else
23
+ say_warning "No #{type} provisioning profiles found." and abort if profiles.empty?
24
+
25
+ table = Terminal::Table.new do |t|
26
+ t << ["Profile", "App ID", "UUID", "Status"]
27
+ t.add_separator
28
+ profiles.each do |profile|
29
+ status = case profile.status
30
+ when "Invalid"
31
+ profile.status.red
32
+ else
33
+ profile.status.green
34
+ end
35
+
36
+ t << [profile.name, profile.app_id, profile.uuid, status]
37
+ end
38
+ end
39
+
40
+ puts table
41
+ end
42
+ end
43
+ end
44
+
45
+ alias_command :profiles, :'profiles:list'
46
+
47
+ command :'profiles:download' do |c|
48
+ c.syntax = 'iospro profiles:download [PROFILE_NAME]'
49
+ c.summary = 'Downloads the Provisioning Profiles'
50
+ c.description = ''
51
+
52
+ c.option '--type [TYPE]', [:development, :distribution], "Type of profile (development or distribution; defaults to development)"
53
+
54
+ c.action do |args, options|
55
+ type = (options.type.downcase.to_sym if options.type) || :development
56
+ profiles = try{agent.list_profiles(type)}
57
+ profiles = profiles.select{|profile| profile.status == 'Active'}
58
+
59
+ say_warning "No active #{type} profiles found." and abort if profiles.empty?
60
+
61
+ profile = profiles.find{|p| p.name == args.join(" ")} || choose("Select a profile:", *profiles)
62
+
63
+ if filename = agent.download_profile(profile)
64
+ say_ok "Successfully downloaded: '#{filename}'"
65
+ else
66
+ say_error "Could not download profile"
67
+ end
68
+ end
69
+ end
70
+
71
+ command :'profiles:download:all' do |c|
72
+ c.syntax = 'iospro profiles:download:all'
73
+ c.summary = 'Downloads all the active Provisioning Profiles'
74
+ c.description = ''
75
+
76
+ c.option '--type [TYPE]', [:development, :distribution], "Type of profile (development or distribution; defaults to development)"
77
+
78
+ c.action do |args, options|
79
+ type = (options.type.downcase.to_sym if options.type) || :development
80
+ profiles = try{agent.list_profiles(type)}
81
+ profiles = profiles.select{|profile| profile.status == 'Active'}
82
+
83
+ say_warning "No active #{type} profiles found." and abort if profiles.empty?
84
+ profiles.each do |profile|
85
+ if filename = agent.download_profile(profile)
86
+ say_ok "Successfully downloaded: '#{filename}'"
87
+ else
88
+ say_error "Could not download profile: '#{profile.name}'"
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ command :'profiles:manage:devices' do |c|
95
+ c.syntax = 'iospro profiles:manage:devices [PROFILE_NAME]'
96
+ c.summary = 'Manage active devices for a development provisioning profile'
97
+ c.description = ''
98
+
99
+ c.option '--type [TYPE]', [:development, :distribution], "Type of profile (development or distribution; defaults to development)"
100
+
101
+ c.action do |args, options|
102
+ type = (options.type.downcase.to_sym if options.type) || :development
103
+ profiles = try{agent.list_profiles(type)}
104
+ profiles.delete_if{|profile| profile.status == "Invalid"}
105
+
106
+ say_warning "No valid #{type} provisioning profiles found." and abort if profiles.empty?
107
+
108
+ profile = profiles.find{|p| p.name == args.first} || choose("Select a profile:", *profiles)
109
+
110
+ agent.manage_devices_for_profile(profile) do |on, off|
111
+ lines = ["# Comment / Uncomment Devices to Turn Off / On for Provisioning Profile"]
112
+ lines += on.collect{|device| "#{device.name} #{device.device_id}"}
113
+ lines += off.collect{|device| "# #{device.name} #{device.device_id}"}
114
+ (result = ask_editor lines.join("\n")) or abort("EDITOR undefined. Try run 'export EDITOR=vi'")
115
+
116
+ devices = []
117
+ result.split(/\n+/).each do |line|
118
+ next if /^#/ === line
119
+ components = line.split(/\s+/)
120
+ device = Device.new
121
+ device.device_id = components.pop
122
+ device.name = components.join(" ")
123
+ devices << device
124
+ end
125
+
126
+ devices
127
+ end
128
+
129
+ say_ok "Successfully managed devices"
130
+ end
131
+ end
132
+
133
+ alias_command :'profiles:devices', :'profiles:manage:devices'
134
+
135
+ command :'profiles:manage:devices:add' do |c|
136
+ c.syntax = 'iospro profiles:manage:devices:add [PROFILE_NAME] DEVICE_NAME=DEVICE_ID [...]'
137
+ c.summary = 'Add active devices to a Provisioning Profile'
138
+ c.description = ''
139
+
140
+ c.action do |args, options|
141
+ profiles = try{agent.list_profiles(:development) + agent.list_profiles(:distribution)}
142
+ profile = profiles.find{|p| p.name == args.first} || choose("Select a profile:", *profiles)
143
+
144
+ names = args[1..-1].select{|arg| /\=/ === arg}.collect{|arg| arg.sub /\=.*/, ''}
145
+ devices = []
146
+
147
+ agent.manage_devices_for_profile(profile) do |on, off|
148
+ names.each_with_index do |name, idx|
149
+ next if idx == 0 and name == profile.name
150
+
151
+ device = (on + off).detect{|d| d.name === name}
152
+
153
+ if device
154
+ devices << Device.new(name, device.udid, "Y", device.device_id)
155
+ end
156
+
157
+ end
158
+
159
+ on + devices
160
+ end
161
+
162
+ case devices.length
163
+ when 0
164
+ say_warning "No devices were added"
165
+ else
166
+ say_ok "Successfully added #{pluralize(devices.length, 'device', 'devices')} to #{profile}."
167
+ end
168
+ end
169
+ end
170
+
171
+ alias_command :'profiles:devices:add', :'profiles:manage:devices:add'
172
+
173
+ command :'profiles:manage:devices:remove' do |c|
174
+ c.syntax = 'iospro profiles:manage:devices:remove PROFILE_NAME DEVICE_NAME=DEVICE_ID [...]'
175
+ c.summary = 'Remove active devices from a Provisioning Profile.'
176
+ c.description = ''
177
+
178
+ c.action do |args, options|
179
+ profiles = try{agent.list_profiles(:development) + agent.list_profiles(:distribution)}
180
+ profile = profiles.find{|p| p.name == args.first} || choose("Select a profile:", *profiles)
181
+
182
+ say_warning "No provisioning profiles named #{args.first} were found." and abort unless profile
183
+
184
+ names = args.collect{|arg| arg.gsub /\=.*/, ''}
185
+
186
+ devices = []
187
+ agent.manage_devices_for_profile(profile) do |on, off|
188
+ devices = on.delete_if{|device| names.include?(device.name)}
189
+ end
190
+
191
+ case devices.length
192
+ when 0
193
+ say_warning "No devices were removed"
194
+ else
195
+ say_ok "Successfully removed #{pluralize(devices.length, 'device', 'devices')} from #{profile}."
196
+ end
197
+ end
198
+ end
199
+
200
+ alias_command :'profiles:devices:remove', :'profiles:manage:devices:remove'
@@ -0,0 +1,18 @@
1
+ include CupertinoPro::ProvisioningPortal
2
+
3
+ require 'cupertinopro/provisioning_portal/helpers'
4
+ include CupertinoPro::ProvisioningPortal::Helpers
5
+
6
+ global_option('-u', '--username USER', 'Username') { |arg| agent.username = arg unless arg.nil? }
7
+ global_option('-p', '--password PASSWORD', 'Password') { |arg| agent.password = arg unless arg.nil? }
8
+ global_option('--team TEAM', 'Team') { |arg| agent.team = arg if arg }
9
+ global_option('--info', 'Set log level to INFO') { agent.log.level = Logger::INFO }
10
+ global_option('--debug', 'Set log level to DEBUG') { agent.log.level = Logger::DEBUG }
11
+ global_option('--format [table|csv]', 'Set output format (default: table)') { |arg| agent.format = arg if arg }
12
+
13
+ require 'cupertinopro/provisioning_portal/commands/certificates'
14
+ require 'cupertinopro/provisioning_portal/commands/devices'
15
+ require 'cupertinopro/provisioning_portal/commands/profiles'
16
+ require 'cupertinopro/provisioning_portal/commands/app_ids'
17
+ require 'cupertinopro/provisioning_portal/commands/login'
18
+ require 'cupertinopro/provisioning_portal/commands/logout'
@@ -0,0 +1,66 @@
1
+ # Monkey Patch Commander::UI to alias password to avoid conflicts
2
+ module Commander::UI
3
+ alias :pw :password
4
+ end
5
+
6
+ class String
7
+ include Term::ANSIColor
8
+ end
9
+
10
+ module CupertinoPro
11
+ module ProvisioningPortal
12
+ module Helpers
13
+ def agent
14
+ unless @agent
15
+ @agent = CupertinoPro::ProvisioningPortal::Agent.new
16
+
17
+ @agent.instance_eval do
18
+ def username
19
+ @username ||= ask "Username:"
20
+ end
21
+
22
+ def password
23
+ @password ||= pw "Password:"
24
+ end
25
+
26
+ def team_id
27
+ unless @team_id
28
+ teams = []
29
+ page.form_with(:name => 'saveTeamSelection').radiobuttons.each do |radio|
30
+ name = page.search(".label-primary[for=\"#{radio.dom_id}\"]").first.text.strip
31
+ programs = page.search(".label-secondary[for=\"#{radio.dom_id}\"]").first.text.strip.split(/\,\s+/)
32
+ team_id = radio.value
33
+ teams << Team.new(name, programs, radio.value)
34
+ end
35
+
36
+ unless team = teams.detect{|t| t.name == @team || t.identifier == @team}
37
+ team = choose "Select a team:", *teams
38
+ end
39
+
40
+ @team_id = team.identifier
41
+ end
42
+
43
+ @team_id
44
+ end
45
+ end
46
+ end
47
+
48
+ @agent
49
+ end
50
+
51
+ def pluralize(n, singular, plural = nil)
52
+ n.to_i == 1 ? "1 #{singular}" : "#{n} #{plural || singular + 's'}"
53
+ end
54
+
55
+ def try
56
+ return unless block_given?
57
+
58
+ begin
59
+ yield
60
+ rescue UnsuccessfulAuthenticationError
61
+ say_error "Could not authenticate with Apple Developer Center. Check that your username & password are correct, and that your membership is valid and all pending Terms of Service & agreements are accepted. If this problem continues, try logging into https://developer.apple.com/membercenter/ from a browser to see what's going on." and abort
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,55 @@
1
+ require 'mechanize'
2
+ require 'certified'
3
+
4
+ module CupertinoPro
5
+ module ProvisioningPortal
6
+ HOST = "developer.apple.com"
7
+
8
+ class UnsuccessfulAuthenticationError < RuntimeError; end
9
+ class UnexpectedContentError < RuntimeError; end
10
+
11
+ class Device < Struct.new(:name, :udid, :enabled, :device_id)
12
+ def to_s
13
+ "#{self.name} #{self.udid} #{self.enabled}"
14
+ end
15
+ end
16
+
17
+ class Certificate < Struct.new(:name, :type, :expiration_date, :status, :download_url)
18
+ def to_s
19
+ "#{self.name}"
20
+ end
21
+ end
22
+
23
+ class AppID < Struct.new(:bundle_seed_id, :description, :development_properties, :distribution_properties)
24
+ def to_s
25
+ "#{self.bundle_seed_id}"
26
+ end
27
+ end
28
+
29
+ class ProvisioningProfile < Struct.new(:name, :type, :app_id, :status, :download_url, :edit_url, :uuid)
30
+ def to_s
31
+ "#{self.name}"
32
+ end
33
+ end
34
+
35
+ class PassTypeID < Struct.new(:description, :id, :pass_certificates, :card_id)
36
+ def to_s
37
+ "#{self.id} #{self.description}"
38
+ end
39
+ end
40
+
41
+ class PassCertificate < Struct.new(:name, :status, :expiration_date, :certificate_id)
42
+ def to_s
43
+ "#{self.certificate_id}"
44
+ end
45
+ end
46
+
47
+ class Team < Struct.new(:name, :programs, :identifier)
48
+ def to_s
49
+ "#{self.name} (#{self.identifier})" + (" [#{self.programs.join(', ')}]" unless self.programs.empty?).to_s
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ require 'cupertinopro/provisioning_portal/agent'
@@ -0,0 +1,3 @@
1
+ module CupertinoPro
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1 @@
1
+ require 'cupertinopro/version'
metadata ADDED
@@ -0,0 +1,203 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cupertinopro
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mattt Thompson
8
+ - Dal Rupnik
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-06-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: commander
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 4.1.2
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 4.1.2
28
+ - !ruby/object:Gem::Dependency
29
+ name: terminal-table
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: 1.4.5
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 1.4.5
42
+ - !ruby/object:Gem::Dependency
43
+ name: term-ansicolor
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: 1.0.7
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: 1.0.7
56
+ - !ruby/object:Gem::Dependency
57
+ name: mechanize
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: 2.5.1
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 2.5.1
70
+ - !ruby/object:Gem::Dependency
71
+ name: nokogiri
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: 1.5.9
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: 1.5.9
84
+ - !ruby/object:Gem::Dependency
85
+ name: security
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: 0.1.2
91
+ type: :runtime
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: 0.1.2
98
+ - !ruby/object:Gem::Dependency
99
+ name: shenzhen
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: 0.0.1
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: 0.0.1
112
+ - !ruby/object:Gem::Dependency
113
+ name: certified
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: 0.1.0
119
+ type: :runtime
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: 0.1.0
126
+ - !ruby/object:Gem::Dependency
127
+ name: rspec
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ - !ruby/object:Gem::Dependency
141
+ name: rake
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ type: :development
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ description: A supercharged command-line interface for the iOS Provisioning Portal
155
+ email: legoless@arvystate.net
156
+ executables:
157
+ - iospro
158
+ extensions: []
159
+ extra_rdoc_files: []
160
+ files:
161
+ - "./cupertinopro.gemspec"
162
+ - "./Gemfile"
163
+ - "./lib/cupertinopro/provisioning_portal/agent.rb"
164
+ - "./lib/cupertinopro/provisioning_portal/commands/app_ids.rb"
165
+ - "./lib/cupertinopro/provisioning_portal/commands/certificates.rb"
166
+ - "./lib/cupertinopro/provisioning_portal/commands/devices.rb"
167
+ - "./lib/cupertinopro/provisioning_portal/commands/login.rb"
168
+ - "./lib/cupertinopro/provisioning_portal/commands/logout.rb"
169
+ - "./lib/cupertinopro/provisioning_portal/commands/profiles.rb"
170
+ - "./lib/cupertinopro/provisioning_portal/commands.rb"
171
+ - "./lib/cupertinopro/provisioning_portal/helpers.rb"
172
+ - "./lib/cupertinopro/provisioning_portal.rb"
173
+ - "./lib/cupertinopro/version.rb"
174
+ - "./lib/cupertinopro.rb"
175
+ - "./LICENSE"
176
+ - "./Rakefile"
177
+ - "./README.md"
178
+ - bin/iospro
179
+ homepage: http://nomad-cli.com
180
+ licenses:
181
+ - MIT
182
+ metadata: {}
183
+ post_install_message:
184
+ rdoc_options: []
185
+ require_paths:
186
+ - lib
187
+ required_ruby_version: !ruby/object:Gem::Requirement
188
+ requirements:
189
+ - - ">="
190
+ - !ruby/object:Gem::Version
191
+ version: '0'
192
+ required_rubygems_version: !ruby/object:Gem::Requirement
193
+ requirements:
194
+ - - ">="
195
+ - !ruby/object:Gem::Version
196
+ version: '0'
197
+ requirements: []
198
+ rubyforge_project:
199
+ rubygems_version: 2.0.14
200
+ signing_key:
201
+ specification_version: 4
202
+ summary: CupertinoPro
203
+ test_files: []