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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +122 -0
- data/bin/sigh +71 -0
- data/lib/sigh.rb +12 -0
- data/lib/sigh/dependency_checker.rb +32 -0
- data/lib/sigh/developer_center.rb +424 -0
- data/lib/sigh/helper.rb +49 -0
- data/lib/sigh/update_checker.rb +44 -0
- data/lib/sigh/version.rb +3 -0
- metadata +266 -0
checksums.yaml
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
<p align="center">
|
2
|
+
<a href="https://github.com/KrauseFx/deliver">Deliver</a> •
|
3
|
+
<a href="https://github.com/KrauseFx/snapshot">Snapshot</a> •
|
4
|
+
<a href="https://github.com/KrauseFx/frameit">FrameIt</a> •
|
5
|
+
<a href="https://github.com/KrauseFx/PEM">PEM</a> •
|
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
|
+
[](https://twitter.com/KrauseFx)
|
18
|
+
[](https://github.com/KrauseFx/sigh/blob/master/LICENSE)
|
19
|
+
[](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) •
|
32
|
+
[Installation](#installation) •
|
33
|
+
[Usage](#usage) •
|
34
|
+
[How does it work?](#how-does-it-work) •
|
35
|
+
[Tips](#tips) •
|
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
|
+

|
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
|
+

|
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
|
data/bin/sigh
ADDED
@@ -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
|
data/lib/sigh.rb
ADDED
@@ -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-....."}}
|
data/lib/sigh/helper.rb
ADDED
@@ -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
|
data/lib/sigh/version.rb
ADDED
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:
|