sigh 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: