sigh 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![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) •
|
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
|
+
![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
|
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:
|