omudid 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'cupertino'#, :git => 'git://github.com/davidlawson/cupertino.git', :branch => 'omudid'
4
+ gem 'spinning_cursor'
5
+ gem 'rainbow'
6
+
7
+ # Specify your gem's dependencies in OneMoreUDID.gemspec
8
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 David Lawson
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'OneMoreUDID'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "omudid"
9
+ spec.version = OneMoreUDID::VERSION
10
+ spec.authors = ["David Lawson"]
11
+ spec.email = ["tech.lawson@gmail.com"]
12
+ spec.description = 'Conveniently add a UDID to the iOS Developer Portal, refresh a provisioning profile and download it. Uploading a new provisioning profile to TestFlight is also supported.'
13
+ spec.summary = 'Add one UDID to iOS Dev Portal & TestFlight'
14
+ spec.homepage = 'https://github.com/Papercloud/OneMoreUDID'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files`.split($/)
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.3"
23
+ spec.add_development_dependency "rake"
24
+
25
+ spec.add_dependency "cupertino"
26
+
27
+ spec.add_dependency "spinning_cursor"
28
+ spec.add_dependency "rainbow"
29
+
30
+ spec.add_dependency "commander", "~> 4.1.2"
31
+ end
data/README.md ADDED
@@ -0,0 +1,19 @@
1
+ OneMoreUDID
2
+ ===========
3
+
4
+ Conveniently add a UDID to the iOS Developer Portal, refresh a provisioning profile and download it.
5
+
6
+ Uploading a new provisioning profile to TestFlight is also supported.
7
+
8
+ Installation
9
+ -
10
+ ```
11
+ $ gem install OneMoreUDID
12
+ ```
13
+
14
+ Usage
15
+ -
16
+ ```
17
+ $ omudid add
18
+ $ omudid testflight upload
19
+ ```
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/omudid ADDED
@@ -0,0 +1,297 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'commander/import'
5
+
6
+ require 'OneMoreUDID'
7
+
8
+ require 'terminal-table'
9
+ require 'term/ansicolor'
10
+
11
+ require 'cupertino_compatibility'
12
+
13
+ require 'shellwords'
14
+
15
+ program :name, 'OneMoreUDID'
16
+ program :version, OneMoreUDID::VERSION
17
+ program :description, '''Uses the developer portal to add one device to a provisioning profile,
18
+ then downloads and installs the new profile.
19
+ Also uploads provisioning profiles to TestFlight.'''
20
+
21
+ HighLine.track_eof = false # Fix for built-in Ruby
22
+
23
+ default_command :help
24
+
25
+ command :'list-teams' do |c|
26
+ c.syntax = 'omudid list-teams [username] [password]'
27
+ c.summary = 'Lists teams on the iOS developer portal'
28
+ c.description = ''
29
+
30
+ c.action do |args, options|
31
+ username = (args[0] or ask 'Apple Username:')
32
+ password = (args[1] or pw 'Apple Password:')
33
+
34
+ if !args[1]
35
+ puts
36
+ say_warning 'Note: you can repeat this process by running:'
37
+ say_warning ' omudid list-teams ' + Shellwords.escape(username) + ' [password]'
38
+ puts
39
+ end
40
+
41
+ agent = OneMoreUDID::PortalAgent.new
42
+
43
+ teams = agent.get_teams(username, password)
44
+
45
+ table = Terminal::Table.new do |t|
46
+ t << ['Team ID', 'Team Name']
47
+ t << :separator
48
+
49
+ teams.each do |id, name|
50
+ t << [id, name]
51
+ end
52
+
53
+ end
54
+
55
+ puts table
56
+ end
57
+ end
58
+
59
+ command :'list-profiles' do |c|
60
+ c.syntax = 'omudid list-profiles [username] [password] ([team ID])'
61
+ c.summary = 'Lists provisioning profiles on the iOS developer portal'
62
+ c.description = ''
63
+
64
+ c.action do |args, options|
65
+
66
+ username = (args[0] or ask 'Apple Username:')
67
+ password = (args[1] or pw 'Apple Password:')
68
+ team_name = args[2] rescue ''
69
+
70
+ if !args[1]
71
+ puts
72
+ end
73
+
74
+ agent = OneMoreUDID::PortalAgent.new
75
+
76
+ agent.setup_cupertino(username, password, team_name)
77
+ profiles = agent.list_profiles()
78
+
79
+ if !args[1]
80
+ puts
81
+ say_warning 'Note: you can repeat this process by running:'
82
+
83
+ if agent.agent.instance_variable_get('@team')
84
+ say_warning ' omudid list-profiles ' + Shellwords.escape(username) + ' [password] ' + agent.agent.instance_variable_get('@team')
85
+ else
86
+ say_warning ' omudid list-profiles ' + Shellwords.escape(username) + ' [password]'
87
+ end
88
+
89
+ puts
90
+ end
91
+
92
+ table = Terminal::Table.new do |t|
93
+ t << ['Name', 'Type', 'App ID', 'Status']
94
+ t << :separator
95
+
96
+ profiles.each do |profile|
97
+ t << [profile.name, profile.type, profile.app_id, profile.status]
98
+ end
99
+ end
100
+
101
+ puts table
102
+ end
103
+ end
104
+
105
+ command :add do |c|
106
+ c.syntax = 'omudid add [username] [password] [profile name] [device name] [UDID] ([team ID])'
107
+ c.summary = 'Adds a device to the provisioning portal, then downloads and installs the new profile'
108
+ c.description = ''
109
+
110
+ c.action do |args, options|
111
+
112
+ username = (args[0] or ask 'Apple Username:')
113
+ password = (args[1] or pw 'Apple Password:')
114
+
115
+ profile_name = (args[2] or ask 'Profile name (leave blank to select from available profiles):')
116
+ device_name = (args[3] or ask 'Device name:')
117
+ udid = (args[4] or ask 'UDID:')
118
+ team_name = args[5] rescue ''
119
+
120
+ show_hint = false
121
+
122
+ if !args[4]
123
+ show_hint = true
124
+ puts
125
+ end
126
+
127
+ agent = OneMoreUDID::PortalAgent.new
128
+ agent.setup_cupertino(username, password, team_name)
129
+
130
+ agent.add_device(device_name, udid)
131
+
132
+ if profile_name == ''
133
+ puts
134
+ profile_name = choose "\nSelect a profile:", *agent.list_profiles().collect { |profile| profile.name }
135
+ puts
136
+ end
137
+
138
+ if show_hint
139
+ say_warning 'Note: you can repeat this process by running:'
140
+
141
+ if agent.agent.instance_variable_get('@team')
142
+ say_warning ' omudid add ' + Shellwords.escape(username) + ' [password] ' + Shellwords.escape(profile_name) + ' ' + Shellwords.escape(device_name) + ' ' + Shellwords.escape(udid) + ' ' + agent.agent.instance_variable_get('@team')
143
+ else
144
+ say_warning ' omudid add ' + Shellwords.escape(username) + ' [password] ' + Shellwords.escape(profile_name) + ' ' + Shellwords.escape(device_name) + ' ' + Shellwords.escape(udid)
145
+ end
146
+
147
+ puts
148
+ end
149
+
150
+ agent.update_profile(profile_name)
151
+
152
+ filename = agent.download_new_profile(profile_name)
153
+
154
+ local_agent = OneMoreUDID::LocalAgent.new
155
+ local_agent.install_profile(profile_name, filename)
156
+ end
157
+ end
158
+
159
+ command :'testflight list-apps' do |c|
160
+ c.syntax = 'omudid testflight list-apps [username] [password]'
161
+ c.summary = 'Lists apps on TestFlight'
162
+ c.description = ''
163
+
164
+ c.action do |args, options|
165
+
166
+ username = (args[0] or ask 'TestFlight Username:')
167
+ password = (args[1] or pw 'TestFlight Password:')
168
+
169
+ if !args[1]
170
+ puts
171
+ say_warning 'Note: you can repeat this process by running:'
172
+ say_warning ' omudid testflight list-apps ' + Shellwords.escape(username) + ' [password]'
173
+ puts
174
+ end
175
+
176
+ agent = OneMoreUDID::TestFlightAgent.new(username, password)
177
+ apps = agent.get_apps()
178
+
179
+ table = Terminal::Table.new do |t|
180
+ t << ['App ID', 'App Name']
181
+ t << :separator
182
+
183
+ apps.each do |id, name|
184
+ t << [id, name]
185
+ end
186
+
187
+ end
188
+
189
+ puts table
190
+ end
191
+ end
192
+
193
+ command :'testflight list-builds' do |c|
194
+ c.syntax = 'omudid testflight list-builds [username] [password] [app ID]'
195
+ c.summary = 'Lists builds of an app on TestFlight'
196
+ c.description = ''
197
+
198
+ c.action do |args, options|
199
+
200
+ username = (args[0] or ask 'TestFlight Username:')
201
+ password = (args[1] or pw 'TestFlight Password:')
202
+ app_id = (args[2] or ask 'App ID (leave blank to select from available IDs):')
203
+
204
+ show_hint = false
205
+
206
+ if !args[2]
207
+ show_hint = true
208
+ end
209
+
210
+ agent = OneMoreUDID::TestFlightAgent.new(username, password)
211
+
212
+ if app_id == ''
213
+ puts
214
+ choice = choose 'Select an app ID:', *agent.get_apps().collect{ |id, name| id + ' ('+name+')' }
215
+ puts
216
+ regex = /^[^ ]*/
217
+ app_id = (choice.match regex)[0]
218
+ end
219
+
220
+ say_warning 'Note: you can repeat this process by running:'
221
+ say_warning ' omudid testflight list-builds ' + Shellwords.escape(username) + ' [password] ' + Shellwords.escape(app_id)
222
+ puts
223
+
224
+ builds = agent.get_builds(app_id)
225
+
226
+ table = Terminal::Table.new do |t|
227
+ t << ['Build ID', 'Build Name']
228
+ t << :separator
229
+
230
+ builds.each do |id, name|
231
+ t << [id, name]
232
+ end
233
+
234
+ end
235
+
236
+ puts table
237
+ end
238
+ end
239
+
240
+ command :'testflight upload' do |c|
241
+ c.syntax = 'omudid testflight upload [username] [password] [build ID] [profile name]'
242
+ c.summary = 'Uploads a provisioning profile to TestFlight'
243
+ c.description = ''
244
+
245
+ c.action do |args, options|
246
+
247
+ username = (args[0] or ask 'TestFlight Username:')
248
+ password = (args[1] or pw 'TestFlight Password:')
249
+ build_id = (args[2] or ask 'Build ID (leave blank to select from available IDs):')
250
+ profile_name = (args[3] or ask 'Profile name (leave blank to select from available local profiles):')
251
+
252
+ show_hint = false
253
+
254
+ if !args[3]
255
+ show_hint = true
256
+ end
257
+
258
+ agent = OneMoreUDID::TestFlightAgent.new(username, password)
259
+
260
+ if build_id == ''
261
+ app_id = ask 'Enter an App ID to list builds (or leave blank to list App IDs):'
262
+
263
+ if app_id == ''
264
+ puts
265
+ choice = choose 'Select an app ID:', *agent.get_apps().collect{ |id, name| id + ' ('+name+')' }
266
+ puts
267
+ regex = /^[^ ]*/
268
+ app_id = (choice.match regex)[0]
269
+ end
270
+
271
+ puts
272
+ choice = choose 'Select a build ID:', *agent.get_builds(app_id).collect{ |id, name| id + ' ('+name+')' }
273
+ puts
274
+ regex = /^[^ ]*/
275
+ build_id = (choice.match regex)[0]
276
+
277
+ show_hint = true
278
+ end
279
+
280
+ if profile_name == ''
281
+ local_agent = OneMoreUDID::LocalAgent.new
282
+ profile_name = choose 'Select a profile:', *local_agent.get_profiles()
283
+ puts
284
+
285
+ show_hint = true
286
+ end
287
+
288
+ if show_hint
289
+ say_warning 'Note: you can repeat this process by running:'
290
+ say_warning ' omudid testflight list-builds ' + Shellwords.escape(username) + ' [password] ' + Shellwords.escape(build_id) + ' ' + Shellwords.escape(profile_name)
291
+ puts
292
+ end
293
+
294
+ agent.upload(build_id, profile_name)
295
+ end
296
+ end
297
+
@@ -0,0 +1,345 @@
1
+ require 'mechanize'
2
+ require 'spinning_cursor'
3
+ require 'rainbow'
4
+ require 'iconv' unless String.method_defined?(:encode)
5
+
6
+ module OneMoreUDID
7
+ VERSION = "1.0.3"
8
+
9
+ class PortalAgent
10
+
11
+ attr_accessor :agent
12
+
13
+ def get_teams(username, password)
14
+ agent = Cupertino::ProvisioningPortal::Agent.new
15
+
16
+ agent.instance_eval do
17
+ def get(uri, parameters = [], referer = nil, headers = {})
18
+ uri = ::File.join("https://#{Cupertino::HOSTNAME}", uri) unless /^https?/ === uri
19
+
20
+ 3.times do
21
+ SpinningCursor.start do
22
+ banner 'Loading page...'
23
+ action do
24
+ ::Mechanize.instance_method(:get).bind(self).call(uri, parameters, referer, headers)
25
+ end
26
+ message 'Loading page... '+'done'.color(:green)
27
+ end
28
+
29
+ return page unless page.respond_to?(:title)
30
+
31
+ case page.title
32
+ when /Sign in with your Apple ID/
33
+ login! and next
34
+ else
35
+ return page
36
+ end
37
+ end
38
+
39
+ raise UnsuccessfulAuthenticationError
40
+ end
41
+ end
42
+
43
+ agent.username = username
44
+ agent.password = password
45
+
46
+ agent.get('https://developer.apple.com/account/selectTeam.action')
47
+
48
+ teams = agent.page.form_with(:name => 'saveTeamSelection').radiobuttons
49
+
50
+ formatted_teams = {}
51
+ teams.each do |team|
52
+ labels = agent.page.search("label[for=\"#{team.dom_id}\"]")
53
+ formatted_teams[team.value] = labels[0].text.strip
54
+ formatted_teams[team.value] += ' - ' + labels[1].text.strip if labels[1].text.strip != ''
55
+ end
56
+
57
+ formatted_teams
58
+ end
59
+
60
+ def setup_cupertino(username, password, team_name = '')
61
+ agent = Cupertino::ProvisioningPortal::Agent.new
62
+
63
+ agent.instance_eval do
64
+ def get(uri, parameters = [], referer = nil, headers = {})
65
+ uri = ::File.join("https://#{Cupertino::HOSTNAME}", uri) unless /^https?/ === uri
66
+
67
+ 3.times do
68
+ SpinningCursor.start do
69
+ banner 'Loading page...'
70
+ action do
71
+ ::Mechanize.instance_method(:get).bind(self).call(uri, parameters, referer, headers)
72
+ end
73
+ message 'Loading page... '+'done'.color(:green)
74
+ end
75
+
76
+ return page unless page.respond_to?(:title)
77
+
78
+ case page.title
79
+ when /Sign in with your Apple ID/
80
+ login! and next
81
+ when /Select Team/
82
+ select_team! and next
83
+ else
84
+ return page
85
+ end
86
+ end
87
+
88
+ raise UnsuccessfulAuthenticationError
89
+ end
90
+
91
+ def team
92
+ teams = page.form_with(:name => 'saveTeamSelection').radiobuttons
93
+
94
+ formatted_teams = {}
95
+ teams.each do |team|
96
+ labels = page.search("label[for=\"#{team.dom_id}\"]")
97
+ formatted_teams[team.value] = labels[0].text.strip
98
+ formatted_teams[team.value] += ' - ' + labels[1].text.strip if labels[1].text.strip != ''
99
+ end
100
+
101
+ if formatted_teams[@teamName]
102
+ @team = @teamName
103
+ else
104
+ puts
105
+ say_warning 'Note: you can specify the team with a command-line argument'
106
+ puts
107
+ chosen_team = choose 'Please select one of the following teams:', *formatted_teams.collect { |id, name| id + ': ' + name }
108
+ puts
109
+ regex = /^[^:]*/
110
+ @team = (chosen_team.match regex)[0]
111
+ end
112
+
113
+ @team
114
+ end
115
+ end
116
+
117
+ agent.username = username
118
+ agent.password = password
119
+ agent.instance_variable_set(:@teamName, team_name)
120
+
121
+ @agent = agent
122
+
123
+ self
124
+ end
125
+
126
+ def add_device(device_name, udid)
127
+ device = Device.new
128
+ device.name = device_name
129
+ device.udid = udid
130
+
131
+ try {@agent.add_devices(*[device])}
132
+
133
+ say_ok 'Device ' + device.name + ' (' + device.udid + ') added'
134
+ end
135
+
136
+ def list_profiles()
137
+ try{@agent.list_profiles(:distribution)}
138
+ end
139
+
140
+ def update_profile(profile_name)
141
+ profiles = try{@agent.list_profiles(:distribution)}
142
+ profile = (profiles.select { |profile| profile.name == profile_name }).first
143
+
144
+ if !profile
145
+ say_error 'Provisioning profile not found, profiles available:'
146
+
147
+ #modularise this?
148
+ table = Terminal::Table.new do |t|
149
+ t << ['Name', 'Type', 'App ID', 'Status']
150
+ t << :separator
151
+
152
+ profiles.each do |profile|
153
+ t << [profile.name, profile.type, profile.app_id, profile.status]
154
+ end
155
+ end
156
+
157
+ puts table
158
+
159
+ abort
160
+ end
161
+
162
+ @agent.manage_devices_for_profile(profile) do |on, off|
163
+ #enable all devices
164
+ on + off
165
+ end
166
+ end
167
+
168
+ def download_new_profile(profile_name)
169
+ profiles = try{@agent.list_profiles(:distribution)}
170
+ profile = (profiles.select { |profile| profile.name == profile_name }).first
171
+
172
+ if !profile
173
+ say_error 'New provisioning profile not found, profiles available:'
174
+ puts profiles
175
+ abort
176
+ end
177
+
178
+ filename = ''
179
+ 5.times do
180
+ begin
181
+ sleep 5
182
+ if filename = @agent.download_profile(profile)
183
+ say_ok 'Downloaded new profile (' + Dir.pwd + '/' + filename + ')'
184
+ break
185
+ else
186
+ say_error 'Could not download profile'
187
+ end
188
+ rescue
189
+ say_error 'Could not download profile'
190
+ end
191
+ end
192
+
193
+ if filename == ''
194
+ abort
195
+ end
196
+
197
+ filename
198
+ end
199
+ end
200
+
201
+ class LocalAgent
202
+ def install_profile(profile_name, filename)
203
+
204
+ Dir.glob(File.expand_path('~') + '/Library/MobileDevice/Provisioning Profiles/*.mobileprovision') do |file|
205
+
206
+ delete_file = false
207
+
208
+ File.open(file, "r") do |_file|
209
+
210
+ file_contents = _file.read
211
+ if String.method_defined?(:encode)
212
+ file_contents.encode!('UTF-8', 'UTF-8', :invalid => :replace)
213
+ end
214
+ matches = /<key>Name<\/key>\s+<string>([^<]+)<\/string>/.match file_contents
215
+
216
+ if matches[1] == profile_name
217
+ delete_file = true
218
+ end
219
+ end
220
+
221
+ if delete_file
222
+ say_warning 'Old profile deleted ('+ file +')'
223
+ File.delete(file)
224
+ break
225
+ end
226
+
227
+ end
228
+
229
+ new_path = File.expand_path('~') + '/Library/MobileDevice/Provisioning Profiles/' + filename
230
+ File.rename(Dir.pwd + '/' + filename, new_path)
231
+
232
+ say_ok 'New profile installed ('+new_path+')'
233
+ end
234
+
235
+ def get_profiles
236
+
237
+ profiles = []
238
+
239
+ Dir.glob(File.expand_path('~') + '/Library/MobileDevice/Provisioning Profiles/*.mobileprovision') do |file|
240
+
241
+ File.open(file, "r") do |_file|
242
+
243
+ file_contents = _file.read
244
+ if String.method_defined?(:encode)
245
+ file_contents.encode!('UTF-8', 'UTF-8', :invalid => :replace)
246
+ end
247
+
248
+ matches = /<key>Name<\/key>\s+<string>([^<]+)<\/string>/.match file_contents
249
+ profiles << matches[1]
250
+ end
251
+
252
+ end
253
+
254
+ profiles
255
+
256
+ end
257
+ end
258
+
259
+ class TestFlightAgent < ::Mechanize
260
+
261
+ attr_accessor :username, :password
262
+
263
+ def initialize(username, password)
264
+ super()
265
+ @username = username
266
+ @password = password
267
+ self
268
+ end
269
+
270
+ def get(uri, parameters = [], referer = nil, headers = {})
271
+ 3.times do
272
+ SpinningCursor.start do
273
+ banner 'Loading page...'
274
+ action do
275
+ super(uri, parameters, referer, headers)
276
+ end
277
+ message 'Loading page... '+'done'.color(:green)
278
+ end
279
+
280
+ return page unless page.respond_to?(:body)
281
+
282
+ case page.body
283
+ when /Login to TestFlight and FlightPath/
284
+ login! and next
285
+ else
286
+ return page
287
+ end
288
+ end
289
+
290
+ raise UnsuccessfulAuthenticationError
291
+ end
292
+
293
+ def get_apps
294
+ get('https://testflightapp.com/dashboard/applications/')
295
+
296
+ apps = page.search('tr.goapp')
297
+ regex = /([0-9]+)/
298
+ apps.collect { |app| [(app['id'].match regex)[1], app.search('h2 small').text.strip] }
299
+ end
300
+
301
+ def get_builds(app_id)
302
+ get("https://testflightapp.com/dashboard/applications/#{app_id}/builds/")
303
+
304
+ builds = page.search('tr.goversion')
305
+ regex = /([0-9]+)/
306
+ builds.collect { |build| [(build['id'].match regex)[1], build.search('td:first')[0].text] }
307
+ end
308
+
309
+ def upload(build_id, profile_name)
310
+ get("https://testflightapp.com/dashboard/builds/update/#{build_id}/")
311
+
312
+ profile_file = get_profile_file(profile_name)
313
+
314
+ form = page.form_with(:id => 'provision-form')
315
+ upload = form.file_uploads.first
316
+ upload.file_name = profile_file.path
317
+ form.submit
318
+
319
+ say_ok 'Submitted '+profile_file.path
320
+ end
321
+
322
+ def get_profile_file(profile_name)
323
+ Dir.glob(::File.expand_path('~') + '/Library/MobileDevice/Provisioning Profiles/*.mobileprovision') do |file|
324
+
325
+ ::File.open(file, "r") do |_file|
326
+ matches = /<key>Name<\/key>\s+<string>([^<]+)<\/string>/.match _file.read
327
+ if matches[1] == profile_name
328
+ return _file
329
+ end
330
+ end
331
+
332
+ end
333
+ end
334
+
335
+ private
336
+
337
+ def login!
338
+ form = page.forms[0]
339
+ form.username = self.username
340
+ form.password = self.password
341
+ form.submit
342
+ end
343
+ end
344
+
345
+ end
@@ -0,0 +1,54 @@
1
+ require 'cupertino'
2
+ require 'cupertino/provisioning_portal/helpers'
3
+ require 'cupertino/provisioning_portal/agent'
4
+
5
+ require 'mechanize'
6
+ require 'certified'
7
+
8
+ include Cupertino::ProvisioningPortal
9
+ include Cupertino::ProvisioningPortal::Helpers
10
+
11
+ module Cupertino
12
+ module ProvisioningPortal
13
+ HOST = 'developer.apple.com'
14
+
15
+ class UnsuccessfulAuthenticationError < RuntimeError; end
16
+ class UnexpectedContentError < RuntimeError; end
17
+
18
+ class Device < Struct.new(:name, :udid)
19
+ def to_s
20
+ "#{self.name} #{self.udid}"
21
+ end
22
+ end
23
+
24
+ class Certificate < Struct.new(:name, :type, :expiration_date, :status, :download_url) #:provisioning_profiles,
25
+ def to_s
26
+ "#{self.name}"
27
+ end
28
+ end
29
+
30
+ class AppID < Struct.new(:bundle_seed_id, :description, :development_properties, :distribution_properties)
31
+ def to_s
32
+ "#{self.bundle_seed_id}"
33
+ end
34
+ end
35
+
36
+ class ProvisioningProfile < Struct.new(:name, :type, :app_id, :status, :download_url, :edit_url)
37
+ def to_s
38
+ "#{self.name}"
39
+ end
40
+ end
41
+
42
+ class PassTypeID < Struct.new(:description, :id, :pass_certificates, :card_id)
43
+ def to_s
44
+ "#{self.id} #{self.description}"
45
+ end
46
+ end
47
+
48
+ class PassCertificate < Struct.new(:name, :status, :expiration_date, :certificate_id)
49
+ def to_s
50
+ "#{self.certificate_id}"
51
+ end
52
+ end
53
+ end
54
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: omudid
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 3
9
+ version: 1.0.3
10
+ platform: ruby
11
+ authors:
12
+ - David Lawson
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2013-05-10 00:00:00 +10:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: bundler
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 1
29
+ - 3
30
+ version: "1.3"
31
+ type: :development
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: rake
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ segments:
41
+ - 0
42
+ version: "0"
43
+ type: :development
44
+ version_requirements: *id002
45
+ - !ruby/object:Gem::Dependency
46
+ name: cupertino
47
+ prerelease: false
48
+ requirement: &id003 !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ segments:
53
+ - 0
54
+ version: "0"
55
+ type: :runtime
56
+ version_requirements: *id003
57
+ - !ruby/object:Gem::Dependency
58
+ name: spinning_cursor
59
+ prerelease: false
60
+ requirement: &id004 !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ type: :runtime
68
+ version_requirements: *id004
69
+ - !ruby/object:Gem::Dependency
70
+ name: rainbow
71
+ prerelease: false
72
+ requirement: &id005 !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ type: :runtime
80
+ version_requirements: *id005
81
+ - !ruby/object:Gem::Dependency
82
+ name: commander
83
+ prerelease: false
84
+ requirement: &id006 !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ~>
87
+ - !ruby/object:Gem::Version
88
+ segments:
89
+ - 4
90
+ - 1
91
+ - 2
92
+ version: 4.1.2
93
+ type: :runtime
94
+ version_requirements: *id006
95
+ description: Conveniently add a UDID to the iOS Developer Portal, refresh a provisioning profile and download it. Uploading a new provisioning profile to TestFlight is also supported.
96
+ email:
97
+ - tech.lawson@gmail.com
98
+ executables:
99
+ - omudid
100
+ extensions: []
101
+
102
+ extra_rdoc_files: []
103
+
104
+ files:
105
+ - .gitignore
106
+ - Gemfile
107
+ - LICENSE.txt
108
+ - OneMoreUDID.gemspec
109
+ - README.md
110
+ - Rakefile
111
+ - bin/omudid
112
+ - lib/OneMoreUDID.rb
113
+ - lib/cupertino_compatibility.rb
114
+ has_rdoc: true
115
+ homepage: https://github.com/Papercloud/OneMoreUDID
116
+ licenses:
117
+ - MIT
118
+ post_install_message:
119
+ rdoc_options: []
120
+
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ segments:
128
+ - 0
129
+ version: "0"
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ segments:
135
+ - 0
136
+ version: "0"
137
+ requirements: []
138
+
139
+ rubyforge_project:
140
+ rubygems_version: 1.3.6
141
+ signing_key:
142
+ specification_version: 3
143
+ summary: Add one UDID to iOS Dev Portal & TestFlight
144
+ test_files: []
145
+