sigh 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 49b46858da6804a8b0605280e13faa4404bc9645
4
+ data.tar.gz: 63a01fa29ba63ade9eaca8c5cfaaa228952b2179
5
+ SHA512:
6
+ metadata.gz: ab7cd40e038f0450ec49f9d7a26b87183e835e9cc1333398330eee27c84e289fbfd25fb0befd770b790402eba42667ec56e706071cf405393dae64e94b0ead58
7
+ data.tar.gz: 9f9145765ef2ee886b2e5b0b7bce876ef9f804328d6bb0390d6e6c268eb7c3068431fd7a45bd6563269e6a5a61445b506cd4ee2503535007417ef4fc97f6b5a7
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Felix Krause
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,122 @@
1
+ <p align="center">
2
+ <a href="https://github.com/KrauseFx/deliver">Deliver</a> &bull;
3
+ <a href="https://github.com/KrauseFx/snapshot">Snapshot</a> &bull;
4
+ <a href="https://github.com/KrauseFx/frameit">FrameIt</a> &bull;
5
+ <a href="https://github.com/KrauseFx/PEM">PEM</a> &bull;
6
+ <b>sigh</b>
7
+ </p>
8
+ -------
9
+
10
+ <p align="center">
11
+ <img src="assets/sigh.png">
12
+ </p>
13
+
14
+ sigh - Create and maintain provisioning profiles
15
+ ============
16
+
17
+ [![Twitter: @KauseFx](https://img.shields.io/badge/contact-@KrauseFx-blue.svg?style=flat)](https://twitter.com/KrauseFx)
18
+ [![License](http://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://github.com/KrauseFx/sigh/blob/master/LICENSE)
19
+ [![Gem](https://img.shields.io/gem/v/sigh.svg?style=flat)](http://rubygems.org/gems/sigh)
20
+
21
+
22
+ Tired of manually creating and maintaining your provisioning profiles?
23
+
24
+ ```sigh``` handles all that for you. Just run ```sigh``` and it will do the rest.
25
+
26
+ Follow the developer on Twitter: [@KrauseFx](https://twitter.com/KrauseFx)
27
+
28
+
29
+
30
+ -------
31
+ [Features](#features) &bull;
32
+ [Installation](#installation) &bull;
33
+ [Usage](#usage) &bull;
34
+ [How does it work?](#how-does-it-work) &bull;
35
+ [Tips](#tips) &bull;
36
+ [Need help?](#need-help)
37
+
38
+ -------
39
+
40
+ # Features
41
+
42
+ - **Download** the latest provisining profile for your app
43
+ - **Renew** a provisining profile, when it has expired
44
+ - **Create** a new App Store provisioning profile, if it doesn't exist yet
45
+ - Support for both **App Store** and **Ad Hoc** profiles
46
+
47
+
48
+ Check out this gif:
49
+
50
+ ![assets/sighRecording.gif](assets/sighRecording.gif)
51
+
52
+ # Installation
53
+ sudo gem install sigh
54
+
55
+ Make sure, you have the latest version of the Xcode command line tools installed:
56
+
57
+ xcode-select --install
58
+
59
+ # Usage
60
+
61
+ sigh
62
+ Yes, that's the whole command!
63
+
64
+ You can pass parameters like this:
65
+
66
+ sigh -a at.felixkrause.app -u username
67
+
68
+ If you want to generate an Ad Ho profile instead:
69
+
70
+ sigh --development
71
+
72
+ By default, ```sigh``` will install the downloaded profile on your machine. If you just want to generate the profile and skip the installation, use the following flag:
73
+
74
+ sigh --skip_install
75
+
76
+
77
+ ## Environment Variables
78
+ In case you prefer environment variables:
79
+
80
+ - ```SIGH_USERNAME```
81
+ - ```SIGH_APP_IDENTIFIER```
82
+
83
+ # How does it work?
84
+
85
+ ```sigh``` will access the ```iOS Dev Center``` to download, renew or generate the ```.mobileprovision``` file. Check out the full source code: [developer_center.rb](https://github.com/KrauseFx/sigh/blob/master/lib/sigh/developer_center.rb).
86
+
87
+
88
+ ## How is my password stored?
89
+ ```sigh``` uses the password manager from [```Deliver```](https://github.com/KrauseFx/deliver#can-i-trust-deliver). Take a look the [Deliver README](https://github.com/KrauseFx/deliver#can-i-trust-deliver) for more information.
90
+
91
+ # Tips
92
+ ## Other helpful tools
93
+ Check out other tools in this collection to speed up your deployment process:
94
+
95
+ - [```deliver```](https://github.com/KrauseFx/deliver): Deploy screenshots, app metadata and app updates to the App Store using just one command
96
+ - [```snapshot```](https://github.com/KrauseFx/snapshot): Create hundreds of screenshots of your iPhone app... while doing something else
97
+ - [```FrameIt```](https://github.com/KrauseFx/frameit): Want a device frame around your screenshot? Do it in an instant!
98
+ - [```PEM```](https://github.com/KrauseFx/pem): Tired of manually creating and maintaining your push certification profiles?
99
+
100
+
101
+ ## Use the 'Provisioning Quicklook plugin'
102
+ Download and install the [Provisioning Plugin](https://github.com/chockenberry/Provisioning).
103
+
104
+ It will show you the ```mobileprovision``` files like this:
105
+ ![assets/QuickLookScreenshot.png](assets/QuickLookScreenshot.png)
106
+
107
+
108
+ # Need help?
109
+ - If there is a technical problem with ```sigh```, submit an issue. Run ```sigh --trace``` to get the stacktrace.
110
+ - I'm available for contract work - drop me an email: sigh@felixkrause.at
111
+
112
+ # License
113
+ This project is licensed under the terms of the MIT license. See the LICENSE file.
114
+
115
+ # Contributing
116
+
117
+ 1. Create an issue to discuss about your idea
118
+ 2. Fork it (https://github.com/KrauseFx/sigh/fork)
119
+ 3. Create your feature branch (`git checkout -b my-new-feature`)
120
+ 4. Commit your changes (`git commit -am 'Add some feature'`)
121
+ 5. Push to the branch (`git push origin my-new-feature`)
122
+ 6. Create a new Pull Request
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.push File.expand_path("../../lib", __FILE__)
4
+
5
+ require 'sigh'
6
+ require 'commander/import'
7
+ require 'sigh/update_checker'
8
+ require 'deliver/password_manager'
9
+
10
+ HighLine.track_eof = false
11
+
12
+
13
+ # Commander
14
+ program :version, Sigh::VERSION
15
+ program :description, 'CLI for \'sigh\' - Automatic maintaining of iOS certificates'
16
+ program :help, 'Author', 'Felix Krause <krausefx@gmail.com>'
17
+ program :help, 'Website', 'http://felixkrause.at'
18
+ program :help, 'GitHub', 'https://github.com/krausefx/sigh'
19
+ program :help_formatter, :compact
20
+
21
+ global_option('--verbose') { $verbose = true }
22
+
23
+
24
+ command :renew do |c|
25
+ c.syntax = 'sigh renew'
26
+ c.description = 'Renews the certificate (in case it expired) and outputs the path to the generated file'
27
+
28
+ c.option '-a', '--identifier STRING', String, 'The bundle identifier of your app'
29
+ c.option '-u', '--username STRING', String, 'Your Apple ID username'
30
+ c.option '-n', '--cert_name STRING', String, 'The name of the generated certificate file.'
31
+ c.option '--adhoc', 'By default, sigh will create and renew App Store profiles. Setting this flag will generate Adhoc profiles instead.'
32
+ c.option '--skip_install', 'By default, the certificate will be added on your local machine. Setting this flag will skip this action.'
33
+ c.option '--development', 'Renew the development certificate instead of the production one'
34
+
35
+ c.action do |args, options|
36
+ app = app_identifier(options)
37
+ username(options)
38
+
39
+ Sigh::UpdateChecker.verify_latest_version
40
+
41
+ type = Sigh::DeveloperCenter::APPSTORE
42
+ type = Sigh::DeveloperCenter::ADHOC if options.adhoc
43
+ type = Sigh::DeveloperCenter::DEVELOPMENT if options.development
44
+
45
+ path = Sigh::DeveloperCenter.new.run(app, type, options.cert_name)
46
+
47
+ if path
48
+ file_name = File.basename(path)
49
+ output = "./#{file_name}"
50
+ FileUtils.mv(path, output)
51
+ system("open '#{output}'") unless options.skip_install
52
+ puts output.green
53
+ end
54
+ end
55
+ end
56
+
57
+ default_command :renew
58
+
59
+ def username(options)
60
+ user = nil
61
+ user = options.username if options.username
62
+ user = ENV["SIGH_USERNAME"] if ENV["SIGH_USERNAME"]
63
+
64
+ Deliver::PasswordManager.shared_manager(user) if user
65
+ end
66
+
67
+ def app_identifier(options)
68
+ return options.identifier if options.identifier
69
+ return ENV["SIGH_APP_IDENTIFIER"] if ENV["SIGH_APP_IDENTIFIER"]
70
+ return ask("App Identifier (Bundle ID, e.g. at.felixkrause.app): ")
71
+ end
@@ -0,0 +1,12 @@
1
+ require 'json'
2
+ require 'sigh/version'
3
+ require 'sigh/helper'
4
+ require 'sigh/dependency_checker'
5
+ require 'sigh/developer_center'
6
+
7
+ # Third Party code
8
+ require 'colored'
9
+
10
+ module Sigh
11
+ TMP_FOLDER = "/tmp/sigh/"
12
+ end
@@ -0,0 +1,32 @@
1
+ module Sigh
2
+ class DependencyChecker
3
+ def self.check_dependencies
4
+ self.check_phantom_js
5
+ self.check_xcode_select
6
+ end
7
+
8
+ def self.check_phantom_js
9
+ if `which phantomjs`.length == 0
10
+ # Missing brew dependency
11
+ Helper.log.fatal '#############################################################'
12
+ Helper.log.fatal "# You have to install phantomjs to use sigh"
13
+ Helper.log.fatal "# phantomjs is used to control the iTunesConnect frontend"
14
+ Helper.log.fatal "# Install Homebrew using http://brew.sh/" if `which brew`.length == 0
15
+ Helper.log.fatal "# Run 'brew update && brew install phantomjs' and start sigh again"
16
+ Helper.log.fatal '#############################################################'
17
+ raise "Run 'brew update && brew install phantomjs' and start sigh again"
18
+ end
19
+ end
20
+
21
+ def self.check_xcode_select
22
+ unless `xcode-select -v`.include?"xcode-select version "
23
+ Helper.log.fatal '#############################################################'
24
+ Helper.log.fatal "# You have to install the Xcode commdand line tools to use sigh"
25
+ Helper.log.fatal "# Install the latest version of Xcode from the AppStore"
26
+ Helper.log.fatal "# Run xcode-select --install to install the developer tools"
27
+ Helper.log.fatal '#############################################################'
28
+ raise "Run 'xcode-select --install' and start sigh again"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,424 @@
1
+ require 'deliver/password_manager'
2
+ require 'open-uri'
3
+ require 'openssl'
4
+
5
+ require 'capybara'
6
+ require 'capybara/poltergeist'
7
+
8
+ module Sigh
9
+ class DeveloperCenter
10
+ # This error occurs only if there is something wrong with the given login data
11
+ class DeveloperCenterLoginError < StandardError
12
+ end
13
+
14
+ # This error can occur for many reaons. It is
15
+ # usually raised when a UI element could not be found
16
+ class DeveloperCenterGeneralError < StandardError
17
+ end
18
+
19
+ # Types of certificates
20
+ APPSTORE = "AppStore"
21
+ ADHOC = "AdHoc"
22
+ DEVELOPMENT = "Development"
23
+
24
+ include Capybara::DSL
25
+
26
+ DEVELOPER_CENTER_URL = "https://developer.apple.com/devcenter/ios/index.action"
27
+ PROFILES_URL = "https://developer.apple.com/account/ios/profile/profileList.action?type=production"
28
+ PROFILES_URL_DEV = "https://developer.apple.com/account/ios/profile/profileList.action?type=limited"
29
+
30
+
31
+ def initialize
32
+ FileUtils.mkdir_p TMP_FOLDER
33
+
34
+ DependencyChecker.check_dependencies
35
+
36
+ Capybara.run_server = false
37
+ Capybara.default_driver = :poltergeist
38
+ Capybara.javascript_driver = :poltergeist
39
+ Capybara.current_driver = :poltergeist
40
+ Capybara.app_host = DEVELOPER_CENTER_URL
41
+
42
+ # Since Apple has some SSL errors, we have to configure the client properly:
43
+ # https://github.com/ariya/phantomjs/issues/11239
44
+ Capybara.register_driver :poltergeist do |a|
45
+ conf = ['--debug=no', '--ignore-ssl-errors=yes', '--ssl-protocol=TLSv1']
46
+ Capybara::Poltergeist::Driver.new(a, {
47
+ phantomjs_options: conf,
48
+ phantomjs_logger: File.open("#{TMP_FOLDER}/poltergeist_log.txt", "a"),
49
+ js_errors: false
50
+ })
51
+ end
52
+
53
+ page.driver.headers = { "Accept-Language" => "en" }
54
+
55
+ self.login
56
+ end
57
+
58
+ # Loggs in a user with the given login data on the Dev Center Frontend.
59
+ # You don't need to pass a username and password. It will
60
+ # Automatically be fetched using the {Deliver::PasswordManager}.
61
+ # This method will also automatically be called when triggering other
62
+ # actions like {#open_app_page}
63
+ # @param user (String) (optional) The username/email address
64
+ # @param password (String) (optional) The password
65
+ # @return (bool) true if everything worked fine
66
+ # @raise [DeveloperCenterGeneralError] General error while executing
67
+ # this action
68
+ # @raise [DeveloperCenterLoginError] Login data is wrong
69
+ def login(user = nil, password = nil)
70
+ begin
71
+ Helper.log.info "Login into iOS Developer Center"
72
+
73
+ user ||= Deliver::PasswordManager.shared_manager.username
74
+ password ||= Deliver::PasswordManager.shared_manager.password
75
+
76
+ result = visit PROFILES_URL
77
+ raise "Could not open Developer Center" unless result['status'] == 'success'
78
+
79
+ wait_for_elements(".button.blue").first.click
80
+
81
+ (wait_for_elements('#accountpassword') rescue nil) # when the user is already logged in, this will raise an exception
82
+
83
+ if page.has_content?"My Apps"
84
+ # Already logged in
85
+ return true
86
+ end
87
+
88
+ fill_in "accountname", with: user
89
+ fill_in "accountpassword", with: password
90
+
91
+ begin
92
+ all(".button.large.blue.signin-button").first.click
93
+
94
+ wait_for_elements('.ios.profiles.gridList')
95
+ visit PROFILES_URL # again, since after the login, the dev center loses the production GET value
96
+ rescue Exception => ex
97
+ Helper.log.debug ex
98
+ raise DeveloperCenterLoginError.new("Error logging in user #{user} with the given password. Make sure you entered them correctly.")
99
+ end
100
+
101
+ Helper.log.info "Login successful"
102
+
103
+ true
104
+ rescue Exception => ex
105
+ error_occured(ex)
106
+ end
107
+ end
108
+
109
+ def run(app_identifier, type, cert_name = nil)
110
+ cert = maintain_app_certificate(app_identifier, type)
111
+
112
+ cert_name ||= "#{type}_#{app_identifier}.mobileprovision" # default name
113
+ cert_name += '.mobileprovision' unless cert_name.include?'mobileprovision'
114
+
115
+ output_path = TMP_FOLDER + cert_name
116
+ File.write(output_path, cert)
117
+
118
+ return output_path
119
+ end
120
+
121
+ def maintain_app_certificate(app_identifier, type)
122
+ begin
123
+ if type == DEVELOPMENT
124
+ visit PROFILES_URL_DEV
125
+ else
126
+ visit PROFILES_URL
127
+ end
128
+
129
+ @list_certs_url = page.html.match(/var profileDataURL = "(.*)"/)[1]
130
+ # list_certs_url will look like this: "https://developer.apple.com/services-account/..../account/ios/profile/listProvisioningProfiles.action?content-type=application/x-www-form-urlencoded&accept=application/json&requestId=id&userLocale=en_US&teamId=xy&includeInactiveProfiles=true&onlyCountLists=true"
131
+ Helper.log.info "Fetching all available provisioning profiles..."
132
+
133
+ certs = post_ajax(@list_certs_url)
134
+
135
+ Helper.log.info "Checking if profile is available. (#{certs['provisioningProfiles'].count} profiles found)"
136
+ certs['provisioningProfiles'].each do |current_cert|
137
+ next if type == DEVELOPMENT and current_cert['type'] != "iOS Development"
138
+ next if type != DEVELOPMENT and current_cert['type'] != 'iOS Distribution'
139
+
140
+ details = profile_details(current_cert['provisioningProfileId'])
141
+
142
+ if details['provisioningProfile']['appId']['identifier'] == app_identifier
143
+ if type == APPSTORE and details['provisioningProfile']['deviceCount'] > 0
144
+ next # that's an Ad Hoc profile. I didn't find a better way to detect if it's one ... skipping it
145
+ end
146
+ if type != APPSTORE and details['provisioningProfile']['deviceCount'] == 0
147
+ next # that's an App Store profile ... skipping it
148
+ end
149
+
150
+ # We found the correct certificate
151
+ if current_cert['status'] == 'Active'
152
+ return download_profile(details['provisioningProfile']['provisioningProfileId']) # this one is already finished. Just download it.
153
+ elsif ['Expired', 'Invalid'].include?current_cert['status']
154
+ renew_profile(current_cert['provisioningProfileId'], type) # This one needs to be renewed
155
+ return maintain_app_certificate(app_identifier, type) # recursive
156
+ end
157
+
158
+ break
159
+ end
160
+ end
161
+
162
+ Helper.log.info "Could not find existing profile. Trying to create a new one."
163
+ # Certificate does not exist yet, we need to create a new one
164
+ create_profile(app_identifier, type)
165
+ # After creating the profile, we need to download it
166
+ return maintain_app_certificate(app_identifier, type) # recursive
167
+
168
+ rescue Exception => ex
169
+ error_occured(ex)
170
+ end
171
+ end
172
+
173
+ def create_profile(app_identifier, type)
174
+ Helper.log.info "Creating new profile for app '#{app_identifier}' for type '#{type}'.".yellow
175
+ certificate = code_signing_certificate(type)
176
+
177
+ create_url = "https://developer.apple.com/account/ios/profile/profileCreate.action"
178
+ visit create_url
179
+
180
+ # 1) Select the profile type (AppStore, Adhoc)
181
+ wait_for_elements('#type-production')
182
+ value = 'store'
183
+ value = 'limited' if type == DEVELOPMENT
184
+ value = 'adhoc' if type == ADHOC
185
+
186
+ first(:xpath, "//input[@type='radio' and @value='#{value}']").click
187
+ click_next
188
+
189
+ # 2) Select the App ID
190
+ while not page.has_content?"Select App ID" do sleep 1 end
191
+ # example: <option value="RGAWZGXSY4">ABP (5A997XSHK2.net.sunapps.34)</option>
192
+ first(:xpath, "//option[contains(text(), '.#{app_identifier})')]").select_option
193
+ click_next
194
+
195
+ # 3) Select the certificate
196
+ while not page.has_content?"Select certificates" do sleep 1 end
197
+ sleep 3
198
+ Helper.log.info "Using certificate ID '#{certificate['certificateId']}' from '#{certificate['ownerName']}'"
199
+
200
+ # example: <input type="radio" name="certificateIds" class="validate" value="[XC5PH8D47H]"> (production)
201
+ id = certificate["certificateId"]
202
+ certs = all(:xpath, "//input[@type='radio' and @value='[#{id}]']") if type != DEVELOPMENT # production uses radio and has a [] around the value
203
+ certs = all(:xpath, "//input[@type='checkbox' and @value='#{id}']") if type == DEVELOPMENT # development uses a checkbox and has no [] around the value
204
+ if certs.count != 1
205
+ Helper.log.info "Looking for certificate: #{certificate}. Found: #{certs.count}"
206
+ raise "Could not find certificate in the list of available certificates."
207
+ end
208
+ certs.first.click
209
+ click_next
210
+
211
+ if type != APPSTORE
212
+ # 4) Devices selection
213
+ wait_for_elements('.selectAll.column')
214
+ sleep 3
215
+
216
+ first(:xpath, "//div[@class='selectAll column']/input").click # select all the devices
217
+ click_next
218
+ end
219
+
220
+ # 5) Choose a profile name
221
+ wait_for_elements('.distributionType')
222
+ profile_name = [app_identifier, type].join(' ')
223
+ fill_in "provisioningProfileName", with: profile_name
224
+ click_next
225
+ wait_for_elements('.row-details')
226
+ end
227
+
228
+ def renew_profile(profile_id, type)
229
+ certificate = code_signing_certificate type
230
+
231
+ details_url = "https://developer.apple.com/account/ios/profile/profileEdit.action?type=&provisioningProfileId=#{profile_id}"
232
+ Helper.log.info "Renewing provisioning profile '#{profile_id}' using URL '#{details_url}'"
233
+ visit details_url
234
+
235
+ Helper.log.info "Using certificate ID '#{certificate['certificateId']}' from '#{certificate['ownerName']}'"
236
+ wait_for_elements('.selectCertificates')
237
+
238
+ certs = all(:xpath, "//input[@type='radio' and @value='#{certificate["certificateId"]}']")
239
+ if certs.count == 1
240
+ certs.first.click
241
+ click_next
242
+
243
+ wait_for_elements('.row-details')
244
+ click_on "Done"
245
+ else
246
+ Helper.log.info "Looking for certificate: #{certificate}. Found: #{certs}"
247
+ raise "Could not find certificate in the list of available certificates."
248
+ end
249
+ end
250
+
251
+ def download_profile(profile_id)
252
+ download_cert_url = "/account/ios/profile/profileContentDownload.action?displayId=#{profile_id}"
253
+
254
+ return download_file(download_cert_url)
255
+ end
256
+
257
+
258
+ private
259
+ def profile_details(profile_id)
260
+ # We need to build the URL to get the App ID for a specific certificate
261
+ current_profile_url = @list_certs_url.gsub('listProvisioningProfiles', 'getProvisioningProfile')
262
+ current_profile_url += "&provisioningProfileId=#{profile_id}"
263
+ # Helper.log.debug "Fetching URL: '#{current_profile_url}'"
264
+
265
+ result = post_ajax(current_profile_url)
266
+ # Example response, see bottom of file
267
+
268
+ if result['resultCode'] == 0
269
+ return result
270
+ else
271
+ raise "Error fetching details for provisioning profile '#{profile_id}'".red
272
+ end
273
+ end
274
+
275
+ # Returns a hash, that contains information about the iOS certificate
276
+ # @example
277
+ # {"certRequestId"=>"B23Q2P396B",
278
+ # "name"=>"SunApps GmbH",
279
+ # "statusString"=>"Issued",
280
+ # "expirationDate"=>"2015-11-25T22:45:50Z",
281
+ # "expirationDateString"=>"Nov 25, 2015",
282
+ # "ownerType"=>"team",
283
+ # "ownerName"=>"SunApps GmbH",
284
+ # "ownerId"=>"....",
285
+ # "canDownload"=>true,
286
+ # "canRevoke"=>true,
287
+ # "certificateId"=>"....",
288
+ # "certificateStatusCode"=>0,
289
+ # "certRequestStatusCode"=>4,
290
+ # "certificateTypeDisplayId"=>"...",
291
+ # "serialNum"=>"....",
292
+ # "typeString"=>"iOS Distribution"},
293
+ def code_signing_certificate(type)
294
+ certs_url = "https://developer.apple.com/account/ios/certificate/certificateList.action?type="
295
+ certs_url << "distribution" if type != DEVELOPMENT
296
+ certs_url << "development" if type == DEVELOPMENT
297
+ visit certs_url
298
+
299
+ certificateDataURL = page.html.match(/var certificateDataURL = "(.*)"/)[1]
300
+ certificateRequestTypes = page.html.match(/var certificateRequestTypes = "(.*)"/)[1]
301
+ certificateStatuses = page.html.match(/var certificateStatuses = "(.*)"/)[1]
302
+ url = [certificateDataURL, certificateRequestTypes, certificateStatuses].join('')
303
+
304
+ # https://developer.apple.com/services-account/.../account/ios/certificate/listCertRequests.action?content-type=application/x-www-form-urlencoded&accept=application/json&requestId=...&userLocale=en_US&teamId=...&types=...&status=4&certificateStatus=0&type=distribution
305
+
306
+ certs = post_ajax(url)['certRequests']
307
+ certs.each do |current_cert|
308
+ if type != DEVELOPMENT and current_cert['typeString'] == 'iOS Distribution'
309
+ # The other profiles are push profiles
310
+ # We only care about the distribution profile
311
+ return current_cert # mostly we only care about the 'certificateId'
312
+ elsif type == DEVELOPMENT and current_cert['typeString'] == 'iOS Development'
313
+ return current_cert # mostly we only care about the 'certificateId'
314
+ end
315
+ end
316
+
317
+ raise "Could not find a Certificate. Please open #{current_url} and make sure you have a signing profile created.".red
318
+ end
319
+
320
+ # Download a file from the dev center, by using a HTTP client. This will return the content of the file
321
+ def download_file(url)
322
+ Helper.log.info "Downloading profile..."
323
+ host = Capybara.current_session.current_host
324
+ url = [host, url].join('')
325
+
326
+ myacinfo = page.driver.cookies['myacinfo'].value # some Apple magic, which is required for the profile download
327
+ data = open(url, {'Cookie' => "myacinfo=#{myacinfo}"}).read
328
+
329
+ raise "Something went wrong when downloading the file from the Dev Center" unless data
330
+ Helper.log.info "Successfully downloaded provisioning profile"
331
+ return data
332
+ end
333
+
334
+ def post_ajax(url)
335
+ JSON.parse(page.evaluate_script("$.ajax({type: 'POST', url: '#{url}', async: false})")['responseText'])
336
+ end
337
+
338
+ def click_next
339
+ wait_for_elements('.button.small.blue.right.submit').last.click
340
+ end
341
+
342
+ def error_occured(ex)
343
+ snap
344
+ raise ex # re-raise the error after saving the snapshot
345
+ end
346
+
347
+ def snap
348
+ path = "Error#{Time.now.to_i}.png"
349
+ save_screenshot(path, :full => true)
350
+ system("open '#{path}'")
351
+ end
352
+
353
+ def wait_for_elements(name)
354
+ counter = 0
355
+ results = all(name)
356
+ while results.count == 0
357
+ # Helper.log.debug "Waiting for #{name}"
358
+ sleep 0.2
359
+
360
+ results = all(name)
361
+
362
+ counter += 1
363
+ if counter > 100
364
+ Helper.log.debug page.html
365
+ Helper.log.debug caller
366
+ raise DeveloperCenterGeneralError.new("Couldn't find element '#{name}' after waiting for quite some time")
367
+ end
368
+ end
369
+ return results
370
+ end
371
+ end
372
+ end
373
+
374
+
375
+
376
+
377
+ # Example response 1)
378
+ # => {"resultCode"=>0,
379
+ # "protocolVersion"=>"....",
380
+ # "isAdmin"=>true,
381
+ # "isMember"=>false,
382
+ # "isAgent"=>true,
383
+ # "pageNumber"=>nil,
384
+ # "pageSize"=>nil,
385
+ # "totalRecords"=>nil,
386
+ # "provisioningProfile"=>
387
+ # {"provisioningProfileId"=>"....",
388
+ # "name"=>"Gut Altentann Development",
389
+ # "status"=>"Active",
390
+ # "type"=>"iOS Development",
391
+ # "distributionMethod"=>"limited",
392
+ # "proProPlatform"=>"ios",
393
+ # "version"=>"ProvisioningProfilev1",
394
+ # "dateExpire"=>"2015-02-22",
395
+ # "managingApp"=>nil,
396
+ # "appId"=>
397
+ # {"appIdId"=>".....",
398
+ # "name"=>"SunApps",
399
+ # "appIdPlatform"=>"ios",
400
+ # "prefix"=>"....",
401
+ # "identifier"=>"net.sunapps.123",
402
+ # "isWildCard"=>true,
403
+ # "isDuplicate"=>false,
404
+ # "features"=>
405
+ # {"push"=>false,
406
+ # "inAppPurchase"=>false,
407
+ # "gameCenter"=>false,
408
+ # "passbook"=>false,
409
+ # "dataProtection"=>"",
410
+ # "homeKit"=>false,
411
+ # "cloudKitVersion"=>1,
412
+ # "iCloud"=>false,
413
+ # "LPLF93JG7M"=>false,
414
+ # "WC421J6T7P"=>false},
415
+ # "enabledFeatures"=>[],
416
+ # "isDevPushEnabled"=>false,
417
+ # "isProdPushEnabled"=>false,
418
+ # "associatedApplicationGroupsCount"=>nil,
419
+ # "associatedCloudContainersCount"=>nil,
420
+ # "associatedIdentifiersCount"=>nil},
421
+ # "appIdId"=>".....",
422
+ # "deviceCount"=>8,
423
+ # "certificateCount"=>1,
424
+ # "UUID"=>"F670D427-2D0E-4782-8171-....."}}
@@ -0,0 +1,49 @@
1
+ require 'logger'
2
+
3
+ module Sigh
4
+ module Helper
5
+
6
+ # Logging happens using this method
7
+ def self.log
8
+ if is_test?
9
+ @@log ||= Logger.new(nil) # don't show any logs when running tests
10
+ else
11
+ @@log ||= Logger.new(STDOUT)
12
+ end
13
+
14
+ @@log.formatter = proc do |severity, datetime, progname, msg|
15
+ string = "#{severity} [#{datetime.strftime('%Y-%m-%d %H:%M:%S.%2N')}]: "
16
+ second = "#{msg}\n"
17
+
18
+ if severity == "DEBUG"
19
+ string = string.magenta
20
+ elsif severity == "INFO"
21
+ string = string.white
22
+ elsif severity == "WARN"
23
+ string = string.yellow
24
+ elsif severity == "ERROR"
25
+ string = string.red
26
+ elsif severity == "FATAL"
27
+ string = string.red.bold
28
+ end
29
+
30
+
31
+ [string, second].join("")
32
+ end
33
+
34
+ @@log
35
+ end
36
+
37
+ # @return true if the currently running program is a unit test
38
+ def self.is_test?
39
+ defined?SpecHelper
40
+ end
41
+
42
+ # @return the full path to the Xcode developer tools of the currently
43
+ # running system
44
+ def self.xcode_path
45
+ return "" if self.is_test? and not OS.mac?
46
+ `xcode-select -p`.gsub("\n", '') + "/"
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,44 @@
1
+ require 'open-uri'
2
+
3
+ module Sigh
4
+ # Verifies, the user runs the latest version of this gem
5
+ class UpdateChecker
6
+ # This method will check if the latest version is installed and show a warning if that's not the case
7
+ def self.verify_latest_version
8
+ if self.update_available?
9
+ v = fetch_latest
10
+ puts '#######################################################################'.green
11
+ puts "# sigh #{v} is available.".green
12
+ puts "# It is recommended to use the latest version.".green
13
+ puts "# Update using '(sudo) gem update sigh'.".green
14
+ puts "# To see what's new, open https://github.com/KrauseFx/sigh/releases.".green
15
+ puts '#######################################################################'.green
16
+ return true
17
+ end
18
+ false
19
+ end
20
+
21
+ # Is a new official release available (this does not include pre-releases)
22
+ def self.update_available?
23
+ begin
24
+ latest = fetch_latest
25
+ if latest and Gem::Version.new(latest) > Gem::Version.new(current_version)
26
+ return true
27
+ end
28
+ rescue Exception => ex
29
+ Helper.log.error("Could not check if 'sigh' is up to date.")
30
+ end
31
+ return false
32
+ end
33
+
34
+ # The currently used version of this gem
35
+ def self.current_version
36
+ Sigh::VERSION
37
+ end
38
+
39
+ private
40
+ def self.fetch_latest
41
+ JSON.parse(open("http://rubygems.org/api/v1/gems/sigh.json").read)["version"]
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ module Sigh
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,266 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sigh
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Felix Krause
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: security
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 0.1.3
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 0.1.3
41
+ - !ruby/object:Gem::Dependency
42
+ name: highline
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 1.6.21
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.6.21
55
+ - !ruby/object:Gem::Dependency
56
+ name: colored
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: commander
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: 4.2.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: 4.2.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: deliver
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: capybara
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: 2.4.3
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: 2.4.3
111
+ - !ruby/object:Gem::Dependency
112
+ name: poltergeist
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: 1.5.1
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ~>
123
+ - !ruby/object:Gem::Version
124
+ version: 1.5.1
125
+ - !ruby/object:Gem::Dependency
126
+ name: bundler
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rake
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rspec
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ~>
158
+ - !ruby/object:Gem::Version
159
+ version: 3.1.0
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ~>
165
+ - !ruby/object:Gem::Version
166
+ version: 3.1.0
167
+ - !ruby/object:Gem::Dependency
168
+ name: pry
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - '>='
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: yard
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ~>
186
+ - !ruby/object:Gem::Version
187
+ version: 0.8.7.4
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ~>
193
+ - !ruby/object:Gem::Version
194
+ version: 0.8.7.4
195
+ - !ruby/object:Gem::Dependency
196
+ name: webmock
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ~>
200
+ - !ruby/object:Gem::Version
201
+ version: 1.19.0
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ~>
207
+ - !ruby/object:Gem::Version
208
+ version: 1.19.0
209
+ - !ruby/object:Gem::Dependency
210
+ name: codeclimate-test-reporter
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - '>='
214
+ - !ruby/object:Gem::Version
215
+ version: '0'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - '>='
221
+ - !ruby/object:Gem::Version
222
+ version: '0'
223
+ description: Create, Renew and Download your provisioning profiles - using one command
224
+ email:
225
+ - krausefx@gmail.com
226
+ executables:
227
+ - sigh
228
+ extensions: []
229
+ extra_rdoc_files: []
230
+ files:
231
+ - LICENSE
232
+ - README.md
233
+ - bin/sigh
234
+ - lib/sigh.rb
235
+ - lib/sigh/dependency_checker.rb
236
+ - lib/sigh/developer_center.rb
237
+ - lib/sigh/helper.rb
238
+ - lib/sigh/update_checker.rb
239
+ - lib/sigh/version.rb
240
+ homepage: http://felixkrause.at
241
+ licenses:
242
+ - MIT
243
+ metadata: {}
244
+ post_install_message: This gem requires phantomjs. Install it using 'brew update &&
245
+ brew install phantomjs'
246
+ rdoc_options: []
247
+ require_paths:
248
+ - lib
249
+ required_ruby_version: !ruby/object:Gem::Requirement
250
+ requirements:
251
+ - - '>='
252
+ - !ruby/object:Gem::Version
253
+ version: 2.0.0
254
+ required_rubygems_version: !ruby/object:Gem::Requirement
255
+ requirements:
256
+ - - '>='
257
+ - !ruby/object:Gem::Version
258
+ version: '0'
259
+ requirements: []
260
+ rubyforge_project:
261
+ rubygems_version: 2.2.2
262
+ signing_key:
263
+ specification_version: 4
264
+ summary: Create, Renew and Download your provisioning profiles - using one command
265
+ test_files: []
266
+ has_rdoc: