fastlane_core 0.1.0
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 +53 -0
- data/lib/fastlane_core.rb +12 -0
- data/lib/fastlane_core/developer_center/developer_center.rb +163 -0
- data/lib/fastlane_core/developer_center/developer_center_helper.rb +73 -0
- data/lib/fastlane_core/developer_center/developer_center_login.rb +5 -0
- data/lib/fastlane_core/developer_center/developer_center_signing_certificates.rb +76 -0
- data/lib/fastlane_core/helper.rb +72 -0
- data/lib/fastlane_core/itunes_connect/itunes_connect.rb +63 -0
- data/lib/fastlane_core/itunes_connect/itunes_connect_helper.rb +82 -0
- data/lib/fastlane_core/itunes_connect/itunes_connect_login.rb +61 -0
- data/lib/fastlane_core/languages.rb +6 -0
- data/lib/fastlane_core/update_checker.rb +38 -0
- data/lib/fastlane_core/version.rb +3 -0
- metadata +283 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bba431883f062fc0b42eca6e525b4a299214401a
|
4
|
+
data.tar.gz: a01c9ca046143ca50a9c8574c2d85cfcfa41af1c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7ee079587326a09667ca0fdb355a63fa808063492ad8c0f58b7453eba5a57e6d719d9f1e685a2fef031d2a602ee12e948bac22a03b5c597dbd9fe46d7da506f8
|
7
|
+
data.tar.gz: ab0561256f15a2906e4de8652ab8b080565e6b93c068fa98c9ac63d34e15c9da2b43d4afda11a6e71afa26d444022121532cb106a6708d8eb4d1bfe921fabf62
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 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,53 @@
|
|
1
|
+
<h3 align="center">
|
2
|
+
<a href="https://github.com/KrauseFx/fastlane">
|
3
|
+
<img src="assets/fastlane.png" width="150" />
|
4
|
+
<br />
|
5
|
+
fastlane
|
6
|
+
</a>
|
7
|
+
</h3>
|
8
|
+
<p align="center">
|
9
|
+
<a href="https://github.com/KrauseFx/deliver">deliver</a> •
|
10
|
+
<a href="https://github.com/KrauseFx/snapshot">snapshot</a> •
|
11
|
+
<a href="https://github.com/KrauseFx/frameit">frameit</a> •
|
12
|
+
<a href="https://github.com/KrauseFx/PEM">PEM</a> •
|
13
|
+
<a href="https://github.com/KrauseFx/sigh">sigh</a> •
|
14
|
+
<a href="https://github.com/KrauseFx/produce">produce</a>
|
15
|
+
</p>
|
16
|
+
-------
|
17
|
+
|
18
|
+
Fastlane Core
|
19
|
+
============
|
20
|
+
|
21
|
+
[](https://twitter.com/KrauseFx)
|
22
|
+
[](https://github.com/KrauseFx/fastlane_core/blob/master/LICENSE)
|
23
|
+
[](http://rubygems.org/gems/fastlane_core)
|
24
|
+
[](https://travis-ci.org/KrauseFx/fastlane_core)
|
25
|
+
|
26
|
+
All shared code of the fastlane tools is stored in this repository.
|
27
|
+
|
28
|
+
Get in contact with the developer on Twitter: [@KrauseFx](https://twitter.com/KrauseFx)
|
29
|
+
|
30
|
+
# Features
|
31
|
+
|
32
|
+
This gem contains all shared classes and code:
|
33
|
+
|
34
|
+
- Setting up a headless JavaScript browser using [poltergeist](https://github.com/teampoltergeist/poltergeist)
|
35
|
+
- Login and basic navigation on iTunes Connect
|
36
|
+
- Login and basic navigation on the Apple Developer Portal
|
37
|
+
- Checking for updates for a specific gem and showing an update message
|
38
|
+
- All output of all tools with different logging levels
|
39
|
+
- Finding of the current Xcode and iTunes Transporter path
|
40
|
+
- A list of available languages of iTunes Connect
|
41
|
+
|
42
|
+
|
43
|
+
# License
|
44
|
+
This project is licensed under the terms of the MIT license. See the LICENSE file.
|
45
|
+
|
46
|
+
# Contributing
|
47
|
+
|
48
|
+
1. Create an issue to start a discussion about your idea
|
49
|
+
2. Fork it (https://github.com/KrauseFx/fastlane_core/fork)
|
50
|
+
3. Create your feature branch (`git checkout -b my-new-feature`)
|
51
|
+
4. Commit your changes (`git commit -am 'Add some feature'`)
|
52
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
53
|
+
6. Create a new Pull Request
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'credentials_manager/password_manager'
|
2
|
+
require 'open-uri'
|
3
|
+
|
4
|
+
require 'capybara'
|
5
|
+
require 'capybara/poltergeist'
|
6
|
+
require 'phantomjs/poltergeist'
|
7
|
+
|
8
|
+
require 'fastlane_core/developer_center/developer_center_login'
|
9
|
+
require 'fastlane_core/developer_center/developer_center_helper'
|
10
|
+
require 'fastlane_core/developer_center/developer_center_signing_certificates'
|
11
|
+
|
12
|
+
module FastlaneCore
|
13
|
+
class DeveloperCenter
|
14
|
+
# This error occurs only if there is something wrong with the given login data
|
15
|
+
class DeveloperCenterLoginError < StandardError
|
16
|
+
end
|
17
|
+
|
18
|
+
# This error can occur for many reaons. It is
|
19
|
+
# usually raised when a UI element could not be found
|
20
|
+
class DeveloperCenterGeneralError < StandardError
|
21
|
+
end
|
22
|
+
|
23
|
+
include Capybara::DSL
|
24
|
+
|
25
|
+
DEVELOPER_CENTER_URL = "https://developer.apple.com/devcenter/ios/index.action"
|
26
|
+
PROFILES_URL = "https://developer.apple.com/account/ios/profile/profileList.action?type=production"
|
27
|
+
TMP_FOLDER = "/tmp/fastlane_core/"
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
FileUtils.mkdir_p TMP_FOLDER
|
31
|
+
|
32
|
+
Capybara.run_server = false
|
33
|
+
Capybara.default_driver = :poltergeist
|
34
|
+
Capybara.javascript_driver = :poltergeist
|
35
|
+
Capybara.current_driver = :poltergeist
|
36
|
+
Capybara.app_host = DEVELOPER_CENTER_URL
|
37
|
+
|
38
|
+
# Since Apple has some SSL errors, we have to configure the client properly:
|
39
|
+
# https://github.com/ariya/phantomjs/issues/11239
|
40
|
+
Capybara.register_driver :poltergeist do |a|
|
41
|
+
conf = ['--debug=no', '--ignore-ssl-errors=yes', '--ssl-protocol=TLSv1']
|
42
|
+
Capybara::Poltergeist::Driver.new(a, {
|
43
|
+
phantomjs: Phantomjs.path,
|
44
|
+
phantomjs_options: conf,
|
45
|
+
phantomjs_logger: File.open("#{TMP_FOLDER}/poltergeist_log.txt", "a"),
|
46
|
+
js_errors: false
|
47
|
+
})
|
48
|
+
end
|
49
|
+
|
50
|
+
page.driver.headers = { "Accept-Language" => "en" }
|
51
|
+
|
52
|
+
self.login
|
53
|
+
end
|
54
|
+
|
55
|
+
# Loggs in a user with the given login data on the Dev Center Frontend.
|
56
|
+
# You don't need to pass a username and password. It will
|
57
|
+
# Automatically be fetched using the {CredentialsManager::PasswordManager}.
|
58
|
+
# This method will also automatically be called when triggering other
|
59
|
+
# actions like {#open_app_page}
|
60
|
+
# @param user (String) (optional) The username/email address
|
61
|
+
# @param password (String) (optional) The password
|
62
|
+
# @return (bool) true if everything worked fine
|
63
|
+
# @raise [DeveloperCenterGeneralError] General error while executing
|
64
|
+
# this action
|
65
|
+
# @raise [DeveloperCenterLoginError] Login data is wrong
|
66
|
+
def login(user = nil, password = nil)
|
67
|
+
begin
|
68
|
+
Helper.log.info "Login into iOS Developer Center"
|
69
|
+
|
70
|
+
user ||= CredentialsManager::PasswordManager.shared_manager.username
|
71
|
+
password ||= CredentialsManager::PasswordManager.shared_manager.password
|
72
|
+
|
73
|
+
result = visit PROFILES_URL
|
74
|
+
raise "Could not open Developer Center" unless result['status'] == 'success'
|
75
|
+
|
76
|
+
# Already logged in
|
77
|
+
return true if page.has_content? "Member Center"
|
78
|
+
|
79
|
+
(wait_for_elements(".button.blue").first.click rescue nil) # maybe already logged in
|
80
|
+
|
81
|
+
(wait_for_elements('#accountpassword') rescue nil) # when the user is already logged in, this will raise an exception
|
82
|
+
|
83
|
+
# Already logged in
|
84
|
+
return true if page.has_content? "Member Center"
|
85
|
+
|
86
|
+
fill_in "accountname", with: user
|
87
|
+
fill_in "accountpassword", with: password
|
88
|
+
|
89
|
+
all(".button.large.blue.signin-button").first.click
|
90
|
+
|
91
|
+
begin
|
92
|
+
# If the user is not on multiple teams
|
93
|
+
select_team if page.has_content? "Select Team"
|
94
|
+
rescue => ex
|
95
|
+
Helper.log.debug ex
|
96
|
+
raise DeveloperCenterLoginError.new("Error loggin in user #{user}. User is on multiple teams and we were unable to correctly retrieve them.")
|
97
|
+
end
|
98
|
+
|
99
|
+
begin
|
100
|
+
wait_for_elements('.ios.profiles.gridList')
|
101
|
+
visit PROFILES_URL # again, since after the login, the dev center loses the production GET value
|
102
|
+
rescue => ex
|
103
|
+
Helper.log.debug ex
|
104
|
+
if page.has_content?"Getting Started"
|
105
|
+
raise "There was no valid signing certificate found. Please log in and follow the 'Getting Started guide' on '#{current_url}'".red
|
106
|
+
else
|
107
|
+
raise DeveloperCenterLoginError.new("Error logging in user #{user} with the given password. Make sure you entered them correctly.")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
Helper.log.info "Login successful"
|
112
|
+
|
113
|
+
true
|
114
|
+
rescue => ex
|
115
|
+
error_occured(ex)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
def select_team
|
121
|
+
team_id = ENV["FASTLANE_TEAM_ID"]
|
122
|
+
team_id = nil if team_id.to_s.length == 0
|
123
|
+
|
124
|
+
unless team_id
|
125
|
+
Helper.log.info "You can store you preferred team using the environment variable `FASTLANE_TEAM_ID`".green
|
126
|
+
Helper.log.info "Your ID belongs to the following teams:".green
|
127
|
+
end
|
128
|
+
|
129
|
+
available_options = []
|
130
|
+
|
131
|
+
teams = find("div.input").all('.team-value') # Grab all the teams data
|
132
|
+
teams.each_with_index do |val, index|
|
133
|
+
current_team_id = '"' + val.find("input").value + '"'
|
134
|
+
team_text = val.find(".label-primary").text
|
135
|
+
description_text = val.find(".label-secondary").text
|
136
|
+
description_text = "(#{description_text})" unless description_text.empty? # Include the team description if any
|
137
|
+
index_text = (index + 1).to_s + "."
|
138
|
+
|
139
|
+
available_options << [index_text, current_team_id, team_text, description_text].join(" ")
|
140
|
+
end
|
141
|
+
|
142
|
+
unless team_id
|
143
|
+
puts available_options.join("\n").green
|
144
|
+
team_index = ask("Please select the team number you would like to access: ".green)
|
145
|
+
team_id = teams[team_index.to_i - 1].find(".radio").value
|
146
|
+
end
|
147
|
+
|
148
|
+
team_button = first(:xpath, "//input[@type='radio' and @value='#{team_id}']") # Select the desired team
|
149
|
+
if team_button
|
150
|
+
team_button.click
|
151
|
+
else
|
152
|
+
Helper.log.fatal "Could not find given Team. Available options: ".red
|
153
|
+
puts available_options.join("\n").yellow
|
154
|
+
raise DeveloperCenterLoginError.new("Error finding given team #{team_id}.".red)
|
155
|
+
end
|
156
|
+
|
157
|
+
all(".button.large.blue.submit").first.click
|
158
|
+
|
159
|
+
result = visit PROFILES_URL
|
160
|
+
raise "Could not open Developer Center" unless result['status'] == 'success'
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module FastlaneCore
|
2
|
+
class DeveloperCenter
|
3
|
+
# Download a file from the dev center, by using a HTTP client. This will return the content of the file
|
4
|
+
def download_file(url)
|
5
|
+
Helper.log.info "Downloading profile..."
|
6
|
+
host = Capybara.current_session.current_host
|
7
|
+
url = [host, url].join('')
|
8
|
+
|
9
|
+
cookieString = ""
|
10
|
+
|
11
|
+
page.driver.cookies.each do |key, cookie|
|
12
|
+
cookieString << "#{cookie.name}=#{cookie.value};" # append all known cookies
|
13
|
+
end
|
14
|
+
|
15
|
+
data = open(url, {'Cookie' => cookieString}).read
|
16
|
+
|
17
|
+
raise "Something went wrong when downloading the file from the Dev Center" unless data
|
18
|
+
Helper.log.info "Successfully downloaded provisioning profile"
|
19
|
+
return data
|
20
|
+
end
|
21
|
+
|
22
|
+
def post_ajax(url)
|
23
|
+
JSON.parse(page.evaluate_script("$.ajax({type: 'POST', url: '#{url}', async: false})")['responseText'])
|
24
|
+
end
|
25
|
+
|
26
|
+
def click_next
|
27
|
+
wait_for_elements('.button.small.blue.right.submit').last.click
|
28
|
+
end
|
29
|
+
|
30
|
+
def error_occured(ex)
|
31
|
+
snap
|
32
|
+
raise ex # re-raise the error after saving the snapshot
|
33
|
+
end
|
34
|
+
|
35
|
+
def snap
|
36
|
+
path = "Error#{Time.now.to_i}.png"
|
37
|
+
save_screenshot(path, :full => true)
|
38
|
+
system("open '#{path}'") unless ENV['SIGH_DISABLE_OPEN_ERROR']
|
39
|
+
end
|
40
|
+
|
41
|
+
def wait_for(method, parameter, success)
|
42
|
+
counter = 0
|
43
|
+
result = method.call(parameter)
|
44
|
+
while !success.call(result)
|
45
|
+
sleep 0.2
|
46
|
+
|
47
|
+
result = method.call(parameter)
|
48
|
+
|
49
|
+
counter += 1
|
50
|
+
if counter > 100
|
51
|
+
Helper.log.debug caller
|
52
|
+
raise DeveloperCenterGeneralError.new("Couldn't find '#{parameter}' after waiting for quite some time")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
return result
|
56
|
+
end
|
57
|
+
|
58
|
+
def wait_for_elements(name)
|
59
|
+
method = Proc.new { |n| all(name) }
|
60
|
+
success = Proc.new { |r| r.count > 0 }
|
61
|
+
return wait_for(method, name, success)
|
62
|
+
end
|
63
|
+
|
64
|
+
def wait_for_variable(name)
|
65
|
+
method = Proc.new { |n|
|
66
|
+
retval = page.html.match(/var #{n} = "(.*)"/)
|
67
|
+
retval[1] unless retval == nil
|
68
|
+
}
|
69
|
+
success = Proc.new { |r| r != nil }
|
70
|
+
return wait_for(method, name, success)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module FastlaneCore
|
2
|
+
class DeveloperPortal
|
3
|
+
# Returns a array of hashes, that contains information about the iOS certificate
|
4
|
+
# @example
|
5
|
+
# [{"certRequestId"=>"B23Q2P396B",
|
6
|
+
# "name"=>"SunApps GmbH",
|
7
|
+
# "statusString"=>"Issued",
|
8
|
+
# "expirationDate"=>"2015-11-25T22:45:50Z",
|
9
|
+
# "expirationDateString"=>"Nov 25, 2015",
|
10
|
+
# "ownerType"=>"team",
|
11
|
+
# "ownerName"=>"SunApps GmbH",
|
12
|
+
# "ownerId"=>"....",
|
13
|
+
# "canDownload"=>true,
|
14
|
+
# "canRevoke"=>true,
|
15
|
+
# "certificateId"=>"....",
|
16
|
+
# "certificateStatusCode"=>0,
|
17
|
+
# "certRequestStatusCode"=>4,
|
18
|
+
# "certificateTypeDisplayId"=>"...",
|
19
|
+
# "serialNum"=>"....",
|
20
|
+
# "typeString"=>"iOS Distribution"},
|
21
|
+
# {another sertificate...}]
|
22
|
+
def code_signing_certificates(type, cert_date)
|
23
|
+
certs_url = "https://developer.apple.com/account/ios/certificate/certificateList.action?type="
|
24
|
+
certs_url << (type == DEVELOPMENT ? 'development' : 'distribution')
|
25
|
+
visit certs_url
|
26
|
+
|
27
|
+
certificateDataURL = wait_for_variable('certificateDataURL')
|
28
|
+
certificateRequestTypes = wait_for_variable('certificateRequestTypes')
|
29
|
+
certificateStatuses = wait_for_variable('certificateStatuses')
|
30
|
+
|
31
|
+
url = [certificateDataURL, certificateRequestTypes, certificateStatuses].join('')
|
32
|
+
|
33
|
+
# 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
|
34
|
+
|
35
|
+
certs = post_ajax(url)['certRequests']
|
36
|
+
|
37
|
+
ret_certs = []
|
38
|
+
certificate_name = ENV['SIGH_CERTIFICATE']
|
39
|
+
|
40
|
+
# The other profiles are push profiles
|
41
|
+
certificate_type = type == DEVELOPMENT ? 'iOS Development' : 'iOS Distribution'
|
42
|
+
|
43
|
+
# New profiles first
|
44
|
+
certs.sort! do |a, b|
|
45
|
+
Time.parse(b['expirationDate']) <=> Time.parse(a['expirationDate'])
|
46
|
+
end
|
47
|
+
|
48
|
+
certs.each do |current_cert|
|
49
|
+
next unless current_cert['typeString'] == certificate_type
|
50
|
+
|
51
|
+
if cert_date || certificate_name
|
52
|
+
if current_cert['expirationDateString'] == cert_date
|
53
|
+
Helper.log.info "Certificate ID '#{current_cert['certificateId']}' with expiry date '#{current_cert['expirationDateString']}' located"
|
54
|
+
ret_certs << current_cert
|
55
|
+
end
|
56
|
+
if current_cert['name'] == certificate_name
|
57
|
+
Helper.log.info "Certificate ID '#{current_cert['certificateId']}' with name '#{certificate_name}' located"
|
58
|
+
ret_certs << current_cert
|
59
|
+
end
|
60
|
+
else
|
61
|
+
ret_certs << current_cert
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
return ret_certs unless ret_certs.empty?
|
66
|
+
|
67
|
+
predicates = []
|
68
|
+
predicates << "name: #{certificate_name}" if certificate_name
|
69
|
+
predicates << "expiry date: #{cert_date}" if cert_date
|
70
|
+
|
71
|
+
predicates_str = " with #{predicates.join(' or ')}"
|
72
|
+
|
73
|
+
raise "Could not find a Certificate#{predicates_str}. Please open #{current_url} and make sure you have a signing profile created.".red
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module FastlaneCore
|
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
|
+
[string, second].join("")
|
31
|
+
end
|
32
|
+
|
33
|
+
@@log
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return true if the currently running program is a unit test
|
37
|
+
def self.test?
|
38
|
+
defined?SpecHelper
|
39
|
+
end
|
40
|
+
|
41
|
+
# Use Helper.test? instead
|
42
|
+
def self.is_test?
|
43
|
+
self.test?
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return the full path to the Xcode developer tools of the currently
|
47
|
+
# running system
|
48
|
+
def self.xcode_path
|
49
|
+
return "" if self.is_test? and not OS.mac?
|
50
|
+
`xcode-select -p`.gsub("\n", '') + "/"
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return the full path to the iTMSTransporter executable
|
54
|
+
def self.transporter_path
|
55
|
+
self.xcode_path + '../Applications/Application\ Loader.app/Contents/MacOS/itms/bin/iTMSTransporter'
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.fastlane_enabled?
|
59
|
+
# This is called from the root context on the first start
|
60
|
+
@@enabled ||= File.directory?"./fastlane"
|
61
|
+
end
|
62
|
+
|
63
|
+
# Path to the installed gem to load resources (e.g. resign.sh)
|
64
|
+
def self.gem_path(gem_name)
|
65
|
+
if not Helper.is_test? and Gem::Specification::find_all_by_name(gem_name).any?
|
66
|
+
return Gem::Specification.find_by_name(gem_name).gem_dir
|
67
|
+
else
|
68
|
+
return './'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'capybara'
|
2
|
+
require 'capybara/poltergeist'
|
3
|
+
require 'credentials_manager/password_manager'
|
4
|
+
require 'phantomjs/poltergeist' # this will download and store phantomjs
|
5
|
+
|
6
|
+
|
7
|
+
require 'fastlane_core/itunes_connect/itunes_connect_helper.rb'
|
8
|
+
require 'fastlane_core/itunes_connect/itunes_connect_login.rb'
|
9
|
+
|
10
|
+
module FastlaneCore
|
11
|
+
# Everything that can't be achived using the {FastlaneCore::ItunesTransporter}
|
12
|
+
# will be scripted using the iTunesConnect frontend.
|
13
|
+
#
|
14
|
+
# Every method you call here, might take a time
|
15
|
+
class ItunesConnect
|
16
|
+
# This error occurs only if there is something wrong with the given login data
|
17
|
+
class ItunesConnectLoginError < StandardError
|
18
|
+
end
|
19
|
+
|
20
|
+
# This error can occur for many reaons. It is
|
21
|
+
# usually raised when an UI element could not be found
|
22
|
+
class ItunesConnectGeneralError < StandardError
|
23
|
+
end
|
24
|
+
|
25
|
+
include Capybara::DSL
|
26
|
+
|
27
|
+
ITUNESCONNECT_URL = "https://itunesconnect.apple.com/"
|
28
|
+
APP_DETAILS_URL = "https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/ra/ng/app/[[app_id]]"
|
29
|
+
|
30
|
+
BUTTON_STRING_NEW_VERSION = "New Version"
|
31
|
+
BUTTON_STRING_SUBMIT_FOR_REVIEW = "Submit for Review"
|
32
|
+
|
33
|
+
WAITING_FOR_REVIEW = "Waiting For Review"
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
super
|
37
|
+
|
38
|
+
return if Helper.is_test?
|
39
|
+
|
40
|
+
Capybara.run_server = false
|
41
|
+
Capybara.default_driver = :poltergeist
|
42
|
+
Capybara.javascript_driver = :poltergeist
|
43
|
+
Capybara.current_driver = :poltergeist
|
44
|
+
Capybara.app_host = ITUNESCONNECT_URL
|
45
|
+
|
46
|
+
# Since Apple has some SSL errors, we have to configure the client properly:
|
47
|
+
# https://github.com/ariya/phantomjs/issues/11239
|
48
|
+
Capybara.register_driver :poltergeist do |a|
|
49
|
+
conf = ['--debug=no', '--ignore-ssl-errors=yes', '--ssl-protocol=TLSv1']
|
50
|
+
Capybara::Poltergeist::Driver.new(a, {
|
51
|
+
phantomjs: Phantomjs.path,
|
52
|
+
phantomjs_options: conf,
|
53
|
+
phantomjs_logger: File.open("/tmp/poltergeist_log.txt", "a"),
|
54
|
+
js_errors: false
|
55
|
+
})
|
56
|
+
end
|
57
|
+
|
58
|
+
page.driver.headers = { "Accept-Language" => "en" }
|
59
|
+
|
60
|
+
login
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module FastlaneCore
|
2
|
+
class ItunesConnect
|
3
|
+
# All the private helpers
|
4
|
+
private
|
5
|
+
# Opens the app details page of the given app.
|
6
|
+
# @param app (Deliver::App) the app that should be opened
|
7
|
+
# @return (bool) true if everything worked fine
|
8
|
+
# @raise [ItunesConnectGeneralError] General error while executing
|
9
|
+
# this action
|
10
|
+
# @raise [ItunesConnectLoginError] Login data is wrong
|
11
|
+
def open_app_page(app)
|
12
|
+
verify_app(app)
|
13
|
+
|
14
|
+
Helper.log.info "Opening detail page for app #{app}"
|
15
|
+
|
16
|
+
visit APP_DETAILS_URL.gsub("[[app_id]]", app.apple_id.to_s)
|
17
|
+
|
18
|
+
wait_for_elements('.page-subnav')
|
19
|
+
sleep 5
|
20
|
+
|
21
|
+
if current_url.include?"wa/defaultError" # app could not be found
|
22
|
+
raise "Could not open app details for app '#{app}'. Make sure you're using the correct Apple ID and the correct Apple developer account (#{CredentialsManager::PasswordManager.shared_manager.username}).".red
|
23
|
+
end
|
24
|
+
|
25
|
+
true
|
26
|
+
rescue => ex
|
27
|
+
error_occured(ex)
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def verify_app(app)
|
32
|
+
raise ItunesConnectGeneralError.new("No valid Deliver::App given") unless app.kind_of?Deliver::App
|
33
|
+
raise ItunesConnectGeneralError.new("App is missing information (apple_id not given)") unless (app.apple_id || '').to_s.length > 5
|
34
|
+
end
|
35
|
+
|
36
|
+
def error_occured(ex)
|
37
|
+
snap
|
38
|
+
raise ex # re-raise the error after saving the snapshot
|
39
|
+
end
|
40
|
+
|
41
|
+
def snap
|
42
|
+
path = "Error#{Time.now.to_i}.png"
|
43
|
+
save_screenshot(path, :full => true)
|
44
|
+
system("open '#{path}'")
|
45
|
+
end
|
46
|
+
|
47
|
+
# Since Apple takes for ages, after the upload is properly processed, we have to wait here
|
48
|
+
def wait_for_preprocessing
|
49
|
+
started = Time.now
|
50
|
+
|
51
|
+
# Wait, while iTunesConnect is processing the uploaded file
|
52
|
+
while (page.has_content?"Uploaded")
|
53
|
+
# iTunesConnect is super slow... so we have to wait...
|
54
|
+
Helper.log.info("Sorry, we have to wait for iTunesConnect, since it's still processing the uploaded ipa file\n" +
|
55
|
+
"If this takes longer than 45 minutes, you have to re-upload the ipa file again.\n" +
|
56
|
+
"You can always open the browser page yourself: '#{current_url}'\n" +
|
57
|
+
"Passed time: ~#{((Time.now - started) / 60.0).to_i} minute(s)")
|
58
|
+
sleep 30
|
59
|
+
visit current_url
|
60
|
+
sleep 30
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def wait_for_elements(name)
|
65
|
+
counter = 0
|
66
|
+
results = all(name)
|
67
|
+
while results.count == 0
|
68
|
+
# Helper.log.debug "Waiting for #{name}"
|
69
|
+
sleep 0.2
|
70
|
+
|
71
|
+
results = all(name)
|
72
|
+
|
73
|
+
counter += 1
|
74
|
+
if counter > 100
|
75
|
+
Helper.log.debug caller
|
76
|
+
raise ItunesConnectGeneralError.new("Couldn't find element '#{name}' after waiting for quite some time")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
return results
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module FastlaneCore
|
2
|
+
# Login code
|
3
|
+
class ItunesConnect
|
4
|
+
# Loggs in a user with the given login data on the iTC Frontend.
|
5
|
+
# You don't need to pass a username and password. It will
|
6
|
+
# Automatically be fetched using the {CredentialsManager::PasswordManager}.
|
7
|
+
# This method will also automatically be called when triggering other
|
8
|
+
# actions like {#open_app_page}
|
9
|
+
# @param user (String) (optional) The username/email address
|
10
|
+
# @param password (String) (optional) The password
|
11
|
+
# @return (bool) true if everything worked fine
|
12
|
+
# @raise [ItunesConnectGeneralError] General error while executing
|
13
|
+
# this action
|
14
|
+
# @raise [ItunesConnectLoginError] Login data is wrong
|
15
|
+
def login(user = nil, password = nil)
|
16
|
+
Helper.log.info "Logging into iTunesConnect"
|
17
|
+
|
18
|
+
user ||= CredentialsManager::PasswordManager.shared_manager.username
|
19
|
+
password ||= CredentialsManager::PasswordManager.shared_manager.password
|
20
|
+
|
21
|
+
result = visit ITUNESCONNECT_URL
|
22
|
+
raise "Could not open iTunesConnect" unless result['status'] == 'success'
|
23
|
+
|
24
|
+
sleep 3
|
25
|
+
|
26
|
+
if page.has_content?"My Apps"
|
27
|
+
# Already logged in
|
28
|
+
return true
|
29
|
+
end
|
30
|
+
|
31
|
+
begin
|
32
|
+
wait_for_elements('#accountpassword')
|
33
|
+
rescue => ex
|
34
|
+
# when the user is already logged in, this will raise an exception
|
35
|
+
end
|
36
|
+
|
37
|
+
fill_in "accountname", with: user
|
38
|
+
fill_in "accountpassword", with: password
|
39
|
+
|
40
|
+
begin
|
41
|
+
(wait_for_elements(".enabled").first.click rescue nil) # Login Button
|
42
|
+
wait_for_elements('.homepageWrapper.ng-scope')
|
43
|
+
|
44
|
+
if page.has_content?"My Apps"
|
45
|
+
# Everything looks good
|
46
|
+
else
|
47
|
+
raise ItunesConnectLoginError.new("Looks like your login data was correct, but you do not have access to the apps.")
|
48
|
+
end
|
49
|
+
rescue => ex
|
50
|
+
Helper.log.debug(ex)
|
51
|
+
raise ItunesConnectLoginError.new("Error logging in user #{user} with the given password. Make sure you entered them correctly.")
|
52
|
+
end
|
53
|
+
|
54
|
+
Helper.log.info "Successfully logged into iTunesConnect"
|
55
|
+
|
56
|
+
true
|
57
|
+
rescue => ex
|
58
|
+
error_occured(ex)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
module FastlaneCore
|
2
|
+
module Languages
|
3
|
+
# These are all the languages which are available to use to upload app metadata and screenshots
|
4
|
+
ALL_LANGUAGES = ["da-DK", "de-DE", "el-GR", "en-AU", "en-CA", "en-GB", "en-US", "es-ES", "es-MX", "fi-FI", "fr-CA", "fr-FR", "id-ID", "it-IT", "ja-JP", "ko-KR", "ms-MY", "nl-NL", "no-NO", "pt-BR", "pt-PT", "ru-RU", "sv-SE", "th-TH", "tr-TR", "vi-VI", "cmn-Hans", "zh_CN", "cmn-Hant"]
|
5
|
+
end
|
6
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
module FastlaneCore
|
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(gem_name, current_version)
|
8
|
+
return true unless self.update_available?(gem_name, current_version)
|
9
|
+
|
10
|
+
v = fetch_latest(gem_name)
|
11
|
+
puts '#######################################################################'.green
|
12
|
+
puts "# #{gem_name} #{v} is available. You are on #{current_version}.".green
|
13
|
+
puts "# It is recommended to use the latest version.".green
|
14
|
+
puts "# Update using 'sudo gem update #{gem_name}'.".green
|
15
|
+
puts "# To see what's new, open https://github.com/KrauseFx/#{gem_name}/releases.".green
|
16
|
+
puts '#######################################################################'.green
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
# Is a new official release available (this does not include pre-releases)
|
21
|
+
def self.update_available?(gem_name, current_version)
|
22
|
+
begin
|
23
|
+
latest = fetch_latest(gem_name)
|
24
|
+
if latest and Gem::Version.new(latest) > Gem::Version.new(current_version)
|
25
|
+
return true
|
26
|
+
end
|
27
|
+
rescue => ex
|
28
|
+
Helper.log.error("Could not check if '#{gem_name}' is up to date.")
|
29
|
+
end
|
30
|
+
return false
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def self.fetch_latest(gem_name)
|
35
|
+
JSON.parse(open("http://rubygems.org/api/v1/gems/#{gem_name}.json").read)["version"]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,283 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fastlane_core
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Felix Krause
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-02-18 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: multi_json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
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.3.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.3.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: credentials_manager
|
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: phantomjs
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.9.8
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.9.8
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: capybara
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 2.4.3
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ~>
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 2.4.3
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: poltergeist
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ~>
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 1.5.1
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ~>
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 1.5.1
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: bundler
|
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: rake
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - '>='
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - '>='
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rspec
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ~>
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 3.1.0
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ~>
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: 3.1.0
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: pry
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - '>='
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - '>='
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: yard
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ~>
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: 0.8.7.4
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ~>
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: 0.8.7.4
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: webmock
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ~>
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: 1.19.0
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ~>
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: 1.19.0
|
223
|
+
- !ruby/object:Gem::Dependency
|
224
|
+
name: codeclimate-test-reporter
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
226
|
+
requirements:
|
227
|
+
- - '>='
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: '0'
|
230
|
+
type: :development
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
requirements:
|
234
|
+
- - '>='
|
235
|
+
- !ruby/object:Gem::Version
|
236
|
+
version: '0'
|
237
|
+
description: Contains all shared code/dependencies of the fastlane.tools
|
238
|
+
email:
|
239
|
+
- fastlanecore@krausefx.com
|
240
|
+
executables: []
|
241
|
+
extensions: []
|
242
|
+
extra_rdoc_files: []
|
243
|
+
files:
|
244
|
+
- LICENSE
|
245
|
+
- README.md
|
246
|
+
- lib/fastlane_core.rb
|
247
|
+
- lib/fastlane_core/developer_center/developer_center.rb
|
248
|
+
- lib/fastlane_core/developer_center/developer_center_helper.rb
|
249
|
+
- lib/fastlane_core/developer_center/developer_center_login.rb
|
250
|
+
- lib/fastlane_core/developer_center/developer_center_signing_certificates.rb
|
251
|
+
- lib/fastlane_core/helper.rb
|
252
|
+
- lib/fastlane_core/itunes_connect/itunes_connect.rb
|
253
|
+
- lib/fastlane_core/itunes_connect/itunes_connect_helper.rb
|
254
|
+
- lib/fastlane_core/itunes_connect/itunes_connect_login.rb
|
255
|
+
- lib/fastlane_core/languages.rb
|
256
|
+
- lib/fastlane_core/update_checker.rb
|
257
|
+
- lib/fastlane_core/version.rb
|
258
|
+
homepage: http://fastlane.tools
|
259
|
+
licenses:
|
260
|
+
- MIT
|
261
|
+
metadata: {}
|
262
|
+
post_install_message:
|
263
|
+
rdoc_options: []
|
264
|
+
require_paths:
|
265
|
+
- lib
|
266
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
267
|
+
requirements:
|
268
|
+
- - '>='
|
269
|
+
- !ruby/object:Gem::Version
|
270
|
+
version: 2.0.0
|
271
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
272
|
+
requirements:
|
273
|
+
- - '>='
|
274
|
+
- !ruby/object:Gem::Version
|
275
|
+
version: '0'
|
276
|
+
requirements: []
|
277
|
+
rubyforge_project:
|
278
|
+
rubygems_version: 2.2.2
|
279
|
+
signing_key:
|
280
|
+
specification_version: 4
|
281
|
+
summary: Contains all shared code/dependencies of the fastlane.tools
|
282
|
+
test_files: []
|
283
|
+
has_rdoc:
|