produce 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/produce +0 -1
- data/lib/produce.rb +4 -8
- data/lib/produce/config.rb +42 -34
- data/lib/produce/developer_center.rb +17 -235
- data/lib/produce/itunes_connect.rb +18 -140
- data/lib/produce/manager.rb +6 -3
- data/lib/produce/version.rb +1 -1
- metadata +3 -103
- data/lib/produce/helper.rb +0 -49
- data/lib/produce/update_checker.rb +0 -44
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 943588dab52ad39c6de765497b027db1de2e10eb
|
4
|
+
data.tar.gz: e4682e29209151d0a9b55881632e56f005b75640
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eabb9f04201e1e47927b7b48e2892235feb68363d0ff89297ab1c12d689e2c30c55f72d71eba4ccb7867bd904c0f7bb9f9f08f9200edd93234b96c13224d5b2c
|
7
|
+
data.tar.gz: 6650261476f7584cf3b94f810d6971a586cb08875dee1d60661d1553eaaefe28f49f670a43ded93765755b112e8dbd38ce36180ffa69e8b4714a188cc5ad30ad
|
data/bin/produce
CHANGED
data/lib/produce.rb
CHANGED
@@ -1,21 +1,17 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'produce/version'
|
3
|
-
require 'produce/helper'
|
4
3
|
require 'produce/config'
|
5
4
|
require 'produce/manager'
|
6
5
|
require 'produce/dependency_checker'
|
7
6
|
require 'produce/developer_center'
|
8
7
|
require 'produce/itunes_connect'
|
9
|
-
require 'produce/update_checker'
|
10
8
|
require 'produce/available_default_languages'
|
11
9
|
|
12
|
-
# Third Party code
|
13
|
-
require 'phantomjs/poltergeist'
|
14
|
-
require 'colored'
|
15
|
-
|
16
10
|
module Produce
|
17
|
-
|
11
|
+
Helper = FastlaneCore::Helper # you gotta love Ruby: Helper.* should use the Helper class contained in FastlaneCore
|
12
|
+
|
13
|
+
ENV['FASTLANE_TEAM_ID'] ||= ENV["PRODUCE_TEAM_ID"]
|
18
14
|
|
19
|
-
|
15
|
+
FastlaneCore::UpdateChecker.verify_latest_version('produce', Produce::VERSION)
|
20
16
|
DependencyChecker.check_dependencies
|
21
17
|
end
|
data/lib/produce/config.rb
CHANGED
@@ -8,17 +8,51 @@ module Produce
|
|
8
8
|
primary_language: "Primary Language (e.g. 'English', 'German'): "
|
9
9
|
}
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
# Left to prevent fastlane from crashing. Should be removed upon version bump.
|
13
12
|
def self.shared_config
|
14
|
-
@@shared ||= self.new
|
15
13
|
end
|
16
14
|
|
17
|
-
|
18
|
-
|
15
|
+
# Creates new Config instance using ENV variables.
|
16
|
+
# @param options (Hash) (optional) config options hash. If duplicates keys
|
17
|
+
# specified by ENV variable, `options` has value will be used.
|
18
|
+
# @return (Config) created Config instance
|
19
|
+
def initialize(options = {})
|
20
|
+
@config = env_options.merge(options)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Retrieves the value for given `key`. If not found, will promt user with
|
24
|
+
# `ASK_MESSAGES[key]` till gets valid response. Thus, always returns value.
|
25
|
+
# Raises exception if given `key` is not Symbol or unknown.
|
26
|
+
def val(key)
|
27
|
+
raise "Please only pass symbols, no Strings to this method".red unless key.kind_of? Symbol
|
28
|
+
|
29
|
+
unless @config.has_key? key
|
30
|
+
@config[key] = ask(ASK_MESSAGES[key]) do |q|
|
31
|
+
case key
|
32
|
+
when :primary_language
|
33
|
+
q.validate = lambda { |val| is_valid_language?(val) }
|
34
|
+
q.responses[:not_valid] = "Please enter one of available languages: #{AvailableDefaultLanguages.all_langauges}"
|
35
|
+
else
|
36
|
+
q.validate = lambda { |val| !val.empty? }
|
37
|
+
q.responses[:not_valid] = "#{key.to_s.gsub('_', ' ').capitalize} can't be blank"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
return @config[key]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Aliases `[key]` to `val(key)` because Ruby can do it.
|
46
|
+
alias_method :[], :val
|
47
|
+
|
48
|
+
# Returns true if option for the given key is present.
|
49
|
+
def has_key?(key)
|
50
|
+
@config.has_key? key
|
19
51
|
end
|
20
52
|
|
21
|
-
|
53
|
+
private
|
54
|
+
|
55
|
+
def env_options
|
22
56
|
hash = {
|
23
57
|
bundle_identifier: ENV['PRODUCE_APP_IDENTIFIER'],
|
24
58
|
app_name: ENV['PRODUCE_APP_NAME'],
|
@@ -42,38 +76,12 @@ module Produce
|
|
42
76
|
hash
|
43
77
|
end
|
44
78
|
|
45
|
-
def initialize(options = {})
|
46
|
-
@config = Config.env_options.merge(options)
|
47
|
-
end
|
48
|
-
|
49
|
-
def self.val(key)
|
50
|
-
raise "Please only pass symbols, no Strings to this method".red unless key.kind_of? Symbol
|
51
|
-
|
52
|
-
unless shared_config.config.has_key? key
|
53
|
-
shared_config.config[key] = ask(ASK_MESSAGES[key]) do |q|
|
54
|
-
case key
|
55
|
-
when :primary_language
|
56
|
-
q.validate = lambda { |val| is_valid_language?(val) }
|
57
|
-
q.responses[:not_valid] = "Please enter one of available languages: #{AvailableDefaultLanguages.all_langauges}"
|
58
|
-
else
|
59
|
-
q.validate = lambda { |val| !val.empty? }
|
60
|
-
q.responses[:not_valid] = "#{key.to_s.gsub('_', ' ').capitalize} can't be blank"
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
return self.shared_config.config[key]
|
66
|
-
end
|
67
|
-
|
68
|
-
def self.has_key?(key)
|
69
|
-
shared_config.config.has_key? key
|
70
|
-
end
|
71
79
|
|
72
|
-
def
|
80
|
+
def is_valid_language? language
|
73
81
|
AvailableDefaultLanguages.all_langauges.include? language
|
74
82
|
end
|
75
83
|
|
76
|
-
def
|
84
|
+
def skip_itc? value
|
77
85
|
%w( true t 1 yes y ).include? value.to_s.downcase
|
78
86
|
end
|
79
87
|
end
|
@@ -1,192 +1,12 @@
|
|
1
|
-
require '
|
2
|
-
require 'open-uri'
|
3
|
-
require 'openssl'
|
1
|
+
require 'fastlane_core/developer_center/developer_center'
|
4
2
|
|
5
|
-
|
6
|
-
require 'capybara/poltergeist'
|
7
|
-
require 'phantomjs/poltergeist'
|
8
|
-
|
9
|
-
module Produce
|
3
|
+
module FastlaneCore
|
10
4
|
class DeveloperCenter
|
11
|
-
# This error occurs only if there is something wrong with the given login data
|
12
|
-
class DeveloperCenterLoginError < StandardError
|
13
|
-
end
|
14
|
-
|
15
|
-
# This error can occur for many reaons. It is
|
16
|
-
# usually raised when a UI element could not be found
|
17
|
-
class DeveloperCenterGeneralError < StandardError
|
18
|
-
end
|
19
|
-
|
20
|
-
# Types of certificates
|
21
|
-
APPSTORE = "AppStore"
|
22
|
-
ADHOC = "AdHoc"
|
23
|
-
DEVELOPMENT = "Development"
|
24
|
-
|
25
|
-
include Capybara::DSL
|
26
|
-
|
27
|
-
DEVELOPER_CENTER_URL = "https://developer.apple.com/devcenter/ios/index.action"
|
28
5
|
APPS_URL = "https://developer.apple.com/account/ios/identifiers/bundle/bundleList.action"
|
29
6
|
CREATE_APP_URL = "https://developer.apple.com/account/ios/identifiers/bundle/bundleCreate.action"
|
30
7
|
|
31
|
-
|
32
|
-
|
33
|
-
def initialize
|
34
|
-
FileUtils.mkdir_p TMP_FOLDER
|
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: Phantomjs.path,
|
48
|
-
phantomjs_options: conf,
|
49
|
-
phantomjs_logger: File.open("#{TMP_FOLDER}/poltergeist_log.txt", "a"),
|
50
|
-
js_errors: false
|
51
|
-
})
|
52
|
-
end
|
53
|
-
|
54
|
-
page.driver.headers = { "Accept-Language" => "en" }
|
55
|
-
|
56
|
-
self.login
|
57
|
-
end
|
58
|
-
|
59
|
-
# Loggs in a user with the given login data on the Dev Center Frontend.
|
60
|
-
# You don't need to pass a username and password. It will
|
61
|
-
# Automatically be fetched using the {CredentialsManager::PasswordManager}.
|
62
|
-
# This method will also automatically be called when triggering other
|
63
|
-
# actions like {#open_app_page}
|
64
|
-
# @param user (String) (optional) The username/email address
|
65
|
-
# @param password (String) (optional) The password
|
66
|
-
# @return (bool) true if everything worked fine
|
67
|
-
# @raise [DeveloperCenterGeneralError] General error while executing
|
68
|
-
# this action
|
69
|
-
# @raise [DeveloperCenterLoginError] Login data is wrong
|
70
|
-
def login(user = nil, password = nil)
|
71
|
-
begin
|
72
|
-
Helper.log.info "Login into iOS Developer Center"
|
73
|
-
|
74
|
-
user ||= CredentialsManager::PasswordManager.shared_manager.username
|
75
|
-
password ||= CredentialsManager::PasswordManager.shared_manager.password
|
76
|
-
|
77
|
-
result = visit APPS_URL
|
78
|
-
raise "Could not open Developer Center" unless result['status'] == 'success'
|
79
|
-
|
80
|
-
if page.has_content?"Member Center"
|
81
|
-
# Already logged in
|
82
|
-
return true
|
83
|
-
end
|
84
|
-
|
85
|
-
(wait_for_elements(".button.blue").first.click rescue nil) # maybe already logged in
|
86
|
-
|
87
|
-
(wait_for_elements('#accountpassword') rescue nil) # when the user is already logged in, this will raise an exception
|
88
|
-
|
89
|
-
if page.has_content?"Member Center"
|
90
|
-
# Already logged in
|
91
|
-
return true
|
92
|
-
end
|
93
|
-
|
94
|
-
fill_in "accountname", with: user
|
95
|
-
fill_in "accountpassword", with: password
|
96
|
-
|
97
|
-
all(".button.large.blue.signin-button").first.click
|
98
|
-
|
99
|
-
begin
|
100
|
-
if page.has_content?"Select Team" # If the user is not on multiple teams
|
101
|
-
select_team
|
102
|
-
end
|
103
|
-
rescue => ex
|
104
|
-
Helper.log.debug ex
|
105
|
-
raise DeveloperCenterLoginError.new("Error loggin in user #{user}. User is on multiple teams and we couldn't select the one specified.")
|
106
|
-
end
|
107
|
-
|
108
|
-
begin
|
109
|
-
wait_for_elements('.toolbar-button.add.navLink')
|
110
|
-
visit APPS_URL # again, since after the login, the dev center loses the GET value
|
111
|
-
rescue => ex
|
112
|
-
Helper.log.debug ex
|
113
|
-
if page.has_content?"Getting Started"
|
114
|
-
raise "There was no valid signing certificate found. Please log in and follow the 'Getting Started guide' on '#{current_url}'".red
|
115
|
-
else
|
116
|
-
raise DeveloperCenterLoginError.new("Error logging in user #{user} with the given password. Make sure you entered them correctly.")
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
|
121
|
-
Helper.log.info "Login successful"
|
122
|
-
|
123
|
-
true
|
124
|
-
rescue => ex
|
125
|
-
error_occured(ex)
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
def select_team
|
130
|
-
team_id = Config.val(:team_id) if Config.has_key?(:team_id)
|
131
|
-
team_name = Config.val(:team_name) if Config.has_key?(:team_name)
|
132
|
-
|
133
|
-
if team_id == nil and team_name == nil
|
134
|
-
Helper.log.info "You can store you preferred team using the environment variable `PRODUCE_TEAM_ID` or `PRODUCE_TEAM_NAME`".green
|
135
|
-
Helper.log.info "Your ID belongs to the following teams:".green
|
136
|
-
end
|
137
|
-
|
138
|
-
available_options = []
|
139
|
-
|
140
|
-
teams = find("div.input").all('.team-value') # Grab all the teams data
|
141
|
-
teams.each_with_index do |val, index|
|
142
|
-
current_team_id = '"' + val.find("input").value + '"'
|
143
|
-
team_text = val.find(".label-primary").text
|
144
|
-
description_text = val.find(".label-secondary").text
|
145
|
-
description_text = "(#{description_text})" unless description_text.empty? # Include the team description if any
|
146
|
-
index_text = (index + 1).to_s + "."
|
147
|
-
|
148
|
-
available_options << [index_text, current_team_id, team_text, description_text].join(" ")
|
149
|
-
end
|
150
|
-
|
151
|
-
if team_name
|
152
|
-
# Search for name
|
153
|
-
found_it = false
|
154
|
-
all("label.label-primary").each do |current|
|
155
|
-
if current.text.downcase.gsub(/\s+/, "") == team_name.downcase.gsub(/\s+/, "")
|
156
|
-
current.click # select the team by name
|
157
|
-
found_it = true
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
unless found_it
|
162
|
-
available_teams = all("label.label-primary").collect { |a| a.text }
|
163
|
-
raise DeveloperCenterLoginError.new("Could not find Team with name '#{team_name}'. Available Teams: #{available_teams}".red)
|
164
|
-
end
|
165
|
-
else
|
166
|
-
# Search by ID/Index
|
167
|
-
unless team_id
|
168
|
-
puts available_options.join("\n").green
|
169
|
-
team_index = ask("Please select the team number you would like to access: ".green)
|
170
|
-
team_id = teams[team_index.to_i - 1].find(".radio").value
|
171
|
-
end
|
172
|
-
|
173
|
-
team_button = first(:xpath, "//input[@type='radio' and @value='#{team_id}']") # Select the desired team
|
174
|
-
if team_button
|
175
|
-
team_button.click
|
176
|
-
else
|
177
|
-
Helper.log.fatal "Could not find given Team. Available options: ".red
|
178
|
-
puts available_options.join("\n").yellow
|
179
|
-
raise DeveloperCenterLoginError.new("Error finding given team #{team_id}.".red)
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
all(".button.large.blue.submit").first.click
|
184
|
-
|
185
|
-
result = visit APPS_URL
|
186
|
-
raise "Could not open Developer Center" unless result['status'] == 'success'
|
187
|
-
end
|
188
|
-
|
189
|
-
def run
|
8
|
+
def run(config)
|
9
|
+
@config = config
|
190
10
|
create_new_app
|
191
11
|
rescue => ex
|
192
12
|
error_occured(ex)
|
@@ -194,22 +14,27 @@ module Produce
|
|
194
14
|
|
195
15
|
def create_new_app
|
196
16
|
if app_exists?
|
197
|
-
Helper.log.info "App '#{
|
17
|
+
Helper.log.info "App '#{@config[:app_name]}' already exists, nothing to do on the Dev Center".green
|
198
18
|
ENV["CREATED_NEW_APP_ID"] = nil
|
199
19
|
# Nothing to do here
|
200
20
|
else
|
201
|
-
Helper.log.info "Creating new app '#{
|
21
|
+
Helper.log.info "Creating new app '#{@config[:app_name]}' on the Apple Dev Center".green
|
202
22
|
visit CREATE_APP_URL
|
203
|
-
wait_for_elements("*[name='appIdName']").first.set
|
204
|
-
wait_for_elements("*[name='explicitIdentifier']").first.set
|
23
|
+
wait_for_elements("*[name='appIdName']").first.set @config[:app_name]
|
24
|
+
wait_for_elements("*[name='explicitIdentifier']").first.set @config[:bundle_identifier]
|
205
25
|
click_next
|
206
26
|
|
207
|
-
sleep
|
27
|
+
sleep 5 # sometimes this takes a while and we don't want to timeout
|
28
|
+
|
29
|
+
if all(".form-error").count > 0
|
30
|
+
error = all(".form-error").collect { |a| a.text }.join("\n")
|
31
|
+
raise error.red
|
32
|
+
end
|
208
33
|
|
209
34
|
wait_for_elements("form[name='bundleSubmit']") # this will show the summary of the given information
|
210
35
|
click_next
|
211
36
|
|
212
|
-
sleep
|
37
|
+
sleep 5 # sometimes this takes a while and we don't want to timeout
|
213
38
|
|
214
39
|
wait_for_elements(".ios.bundles.confirmForm.complete")
|
215
40
|
click_on "Done"
|
@@ -218,7 +43,7 @@ module Produce
|
|
218
43
|
|
219
44
|
ENV["CREATED_NEW_APP_ID"] = Time.now.to_s
|
220
45
|
|
221
|
-
Helper.log.info "Finished creating new app '#{
|
46
|
+
Helper.log.info "Finished creating new app '#{@config[:app_name]}' on the Dev Center".green
|
222
47
|
end
|
223
48
|
|
224
49
|
return true
|
@@ -232,7 +57,7 @@ module Produce
|
|
232
57
|
wait_for_elements("td[aria-describedby='grid-table_identifier']").each do |app|
|
233
58
|
identifier = app['title']
|
234
59
|
|
235
|
-
return true if identifier.to_s ==
|
60
|
+
return true if identifier.to_s == @config[:bundle_identifier].to_s
|
236
61
|
end
|
237
62
|
|
238
63
|
false
|
@@ -241,48 +66,5 @@ module Produce
|
|
241
66
|
def click_next
|
242
67
|
wait_for_elements('.button.small.blue.right.submit').last.click
|
243
68
|
end
|
244
|
-
|
245
|
-
def error_occured(ex)
|
246
|
-
snap
|
247
|
-
raise ex # re-raise the error after saving the snapshot
|
248
|
-
end
|
249
|
-
|
250
|
-
def snap
|
251
|
-
path = "Error#{Time.now.to_i}.png"
|
252
|
-
save_screenshot(path, :full => true)
|
253
|
-
system("open '#{path}'")
|
254
|
-
end
|
255
|
-
|
256
|
-
def wait_for(method, parameter, success)
|
257
|
-
counter = 0
|
258
|
-
result = method.call(parameter)
|
259
|
-
while !success.call(result)
|
260
|
-
sleep 0.2
|
261
|
-
|
262
|
-
result = method.call(parameter)
|
263
|
-
|
264
|
-
counter += 1
|
265
|
-
if counter > 100
|
266
|
-
Helper.log.debug caller
|
267
|
-
raise DeveloperCenterGeneralError.new("Couldn't find '#{parameter}' after waiting for quite some time")
|
268
|
-
end
|
269
|
-
end
|
270
|
-
return result
|
271
|
-
end
|
272
|
-
|
273
|
-
def wait_for_elements(name)
|
274
|
-
method = Proc.new { |n| all(name) }
|
275
|
-
success = Proc.new { |r| r.count > 0 }
|
276
|
-
return wait_for(method, name, success)
|
277
|
-
end
|
278
|
-
|
279
|
-
def wait_for_variable(name)
|
280
|
-
method = Proc.new { |n|
|
281
|
-
retval = page.html.match(/var #{n} = "(.*)"/)
|
282
|
-
retval[1] unless retval == nil
|
283
|
-
}
|
284
|
-
success = Proc.new { |r| r != nil }
|
285
|
-
return wait_for(method, name, success)
|
286
|
-
end
|
287
69
|
end
|
288
70
|
end
|
@@ -1,109 +1,16 @@
|
|
1
|
-
require '
|
2
|
-
require 'capybara/poltergeist'
|
3
|
-
require 'credentials_manager/password_manager'
|
4
|
-
require 'phantomjs/poltergeist'
|
1
|
+
require 'fastlane_core/itunes_connect/itunes_connect'
|
5
2
|
|
6
|
-
module
|
3
|
+
module FastlaneCore
|
7
4
|
# Every method you call here, might take a time
|
8
5
|
class ItunesConnect
|
9
|
-
|
10
|
-
class ItunesConnectLoginError < StandardError
|
11
|
-
end
|
12
|
-
|
13
|
-
# This error can occur for many reaons. It is
|
14
|
-
# usually raised when a UI element could not be found
|
15
|
-
class ItunesConnectGeneralError < StandardError
|
16
|
-
end
|
17
|
-
|
18
|
-
include Capybara::DSL
|
19
|
-
|
20
|
-
ITUNESCONNECT_URL = "https://itunesconnect.apple.com/"
|
6
|
+
|
21
7
|
APPS_URL = "https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/ra/ng/app"
|
22
8
|
|
23
9
|
NEW_APP_CLASS = ".new-button.ng-isolate-scope"
|
24
|
-
|
25
|
-
def initialize
|
26
|
-
super
|
27
10
|
|
28
|
-
|
29
|
-
|
30
|
-
Capybara.run_server = false
|
31
|
-
Capybara.default_driver = :poltergeist
|
32
|
-
Capybara.javascript_driver = :poltergeist
|
33
|
-
Capybara.current_driver = :poltergeist
|
34
|
-
Capybara.app_host = ITUNESCONNECT_URL
|
35
|
-
|
36
|
-
# Since Apple has some SSL errors, we have to configure the client properly:
|
37
|
-
# https://github.com/ariya/phantomjs/issues/11239
|
38
|
-
Capybara.register_driver :poltergeist do |a|
|
39
|
-
conf = ['--debug=no', '--ignore-ssl-errors=yes', '--ssl-protocol=TLSv1']
|
40
|
-
Capybara::Poltergeist::Driver.new(a, {
|
41
|
-
phantomjs: Phantomjs.path,
|
42
|
-
phantomjs_options: conf,
|
43
|
-
phantomjs_logger: File.open("/tmp/poltergeist_log.txt", "a"),
|
44
|
-
js_errors: false
|
45
|
-
})
|
46
|
-
end
|
47
|
-
|
48
|
-
page.driver.headers = { "Accept-Language" => "en" }
|
49
|
-
|
50
|
-
self.login
|
51
|
-
end
|
11
|
+
def run(config)
|
12
|
+
@config = config
|
52
13
|
|
53
|
-
# Loggs in a user with the given login data on the iTC Frontend.
|
54
|
-
# You don't need to pass a username and password. It will
|
55
|
-
# Automatically be fetched using the {CredentialsManager::PasswordManager}.
|
56
|
-
# This method will also automatically be called when triggering other
|
57
|
-
# actions like {#open_app_page}
|
58
|
-
# @param user (String) (optional) The username/email address
|
59
|
-
# @param password (String) (optional) The password
|
60
|
-
# @return (bool) true if everything worked fine
|
61
|
-
# @raise [ItunesConnectGeneralError] General error while executing
|
62
|
-
# this action
|
63
|
-
# @raise [ItunesConnectLoginError] Login data is wrong
|
64
|
-
def login(user = nil, password = nil)
|
65
|
-
begin
|
66
|
-
Helper.log.info "Logging into iTunesConnect"
|
67
|
-
|
68
|
-
user ||= CredentialsManager::PasswordManager.shared_manager.username
|
69
|
-
password ||= CredentialsManager::PasswordManager.shared_manager.password
|
70
|
-
|
71
|
-
result = visit ITUNESCONNECT_URL
|
72
|
-
raise "Could not open iTunesConnect" unless result['status'] == 'success'
|
73
|
-
|
74
|
-
(wait_for_elements('#accountpassword') rescue nil) # when the user is already logged in, this will raise an exception
|
75
|
-
|
76
|
-
if page.has_content?"My Apps"
|
77
|
-
# Already logged in
|
78
|
-
return true
|
79
|
-
end
|
80
|
-
|
81
|
-
fill_in "accountname", with: user
|
82
|
-
fill_in "accountpassword", with: password
|
83
|
-
|
84
|
-
begin
|
85
|
-
(wait_for_elements(".enabled").first.click rescue nil) # Login Button
|
86
|
-
wait_for_elements('.homepageWrapper.ng-scope')
|
87
|
-
|
88
|
-
if page.has_content?"My Apps"
|
89
|
-
# Everything looks good
|
90
|
-
else
|
91
|
-
raise ItunesConnectLoginError.new("Looks like your login data was correct, but you do not have access to the apps.")
|
92
|
-
end
|
93
|
-
rescue => ex
|
94
|
-
Helper.log.debug(ex)
|
95
|
-
raise ItunesConnectLoginError.new("Error logging in user #{user} with the given password. Make sure you entered them correctly.")
|
96
|
-
end
|
97
|
-
|
98
|
-
Helper.log.info "Successfully logged into iTunesConnect"
|
99
|
-
|
100
|
-
true
|
101
|
-
rescue => ex
|
102
|
-
error_occured(ex)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def run
|
107
14
|
if ENV["CREATED_NEW_APP_ID"].to_i > 0
|
108
15
|
# We just created this App ID, this takes about 3 minutes to show up on iTunes Connect
|
109
16
|
Helper.log.info "Waiting for 3 minutes to make sure, the App ID is synced to iTunes Connect".yellow
|
@@ -121,10 +28,10 @@ module Produce
|
|
121
28
|
|
122
29
|
def create_new_app
|
123
30
|
if app_exists?
|
124
|
-
Helper.log.info "App '#{
|
31
|
+
Helper.log.info "App '#{@config[:app_name]}' exists already, nothing to do on iTunes Connect".green
|
125
32
|
# Nothing to do here
|
126
33
|
else
|
127
|
-
Helper.log.info "Creating new app '#{
|
34
|
+
Helper.log.info "Creating new app '#{@config[:app_name]}' on iTunes Connect".green
|
128
35
|
|
129
36
|
initial_create
|
130
37
|
|
@@ -132,7 +39,7 @@ module Produce
|
|
132
39
|
|
133
40
|
raise "Something went wrong when creating the new app - it's not listed in the App's list" unless app_exists?
|
134
41
|
|
135
|
-
Helper.log.info "Finished creating new app '#{
|
42
|
+
Helper.log.info "Finished creating new app '#{@config[:app_name]}' on iTunes Connect".green
|
136
43
|
end
|
137
44
|
|
138
45
|
return fetch_apple_id
|
@@ -140,12 +47,12 @@ module Produce
|
|
140
47
|
|
141
48
|
def fetch_apple_id
|
142
49
|
# First try it using the Apple API
|
143
|
-
data = JSON.parse(open("https://itunes.apple.com/lookup?bundleId=#{
|
50
|
+
data = JSON.parse(open("https://itunes.apple.com/lookup?bundleId=#{@config[:bundle_identifier]}").read)
|
144
51
|
|
145
52
|
if data['resultCount'] == 0 or true
|
146
53
|
visit current_url
|
147
54
|
sleep 10
|
148
|
-
first("input[ng-model='searchModel']").set
|
55
|
+
first("input[ng-model='searchModel']").set @config[:bundle_identifier]
|
149
56
|
|
150
57
|
if all("div[bo-bind='app.name']").count == 2
|
151
58
|
raise "There were multiple results when looking for the new app. This might be due to having same app identifiers included in each other (see generated screenshots)".red
|
@@ -165,12 +72,12 @@ module Produce
|
|
165
72
|
open_new_app_popup
|
166
73
|
|
167
74
|
# Fill out the initial information
|
168
|
-
wait_for_elements("input[ng-model='createAppDetails.newApp.name.value']").first.set
|
169
|
-
wait_for_elements("input[ng-model='createAppDetails.versionString.value']").first.set
|
170
|
-
wait_for_elements("input[ng-model='createAppDetails.newApp.vendorId.value']").first.set
|
75
|
+
wait_for_elements("input[ng-model='createAppDetails.newApp.name.value']").first.set @config[:app_name]
|
76
|
+
wait_for_elements("input[ng-model='createAppDetails.versionString.value']").first.set @config[:version]
|
77
|
+
wait_for_elements("input[ng-model='createAppDetails.newApp.vendorId.value']").first.set @config[:sku]
|
171
78
|
|
172
|
-
wait_for_elements("option[value='#{
|
173
|
-
all(:xpath, "//option[text()='#{
|
79
|
+
wait_for_elements("option[value='#{@config[:bundle_identifier]}']").first.select_option
|
80
|
+
all(:xpath, "//option[text()='#{@config[:primary_language]}']").first.select_option
|
174
81
|
|
175
82
|
click_on "Create"
|
176
83
|
sleep 5 # this usually takes some time
|
@@ -181,7 +88,7 @@ module Produce
|
|
181
88
|
|
182
89
|
wait_for_elements(".language.hasPopOver") # looking good
|
183
90
|
|
184
|
-
Helper.log.info "Successfully created new app '#{
|
91
|
+
Helper.log.info "Successfully created new app '#{@config[:app_name]}' on iTC. Setting up the initial information now.".green
|
185
92
|
end
|
186
93
|
|
187
94
|
def initial_pricing
|
@@ -197,12 +104,12 @@ module Produce
|
|
197
104
|
|
198
105
|
sleep 4
|
199
106
|
|
200
|
-
return (all("option[value='#{
|
107
|
+
return (all("option[value='#{@config[:bundle_identifier]}']").count == 0)
|
201
108
|
end
|
202
109
|
|
203
110
|
def open_new_app_popup
|
204
111
|
visit APPS_URL
|
205
|
-
sleep
|
112
|
+
sleep 8 # this usually takes some time
|
206
113
|
|
207
114
|
wait_for_elements(NEW_APP_CLASS).first.click
|
208
115
|
wait_for_elements('#new-menu > * > a').first.click # Create a new App
|
@@ -210,34 +117,5 @@ module Produce
|
|
210
117
|
sleep 5 # this usually takes some time - this is important
|
211
118
|
wait_for_elements("input[ng-model='createAppDetails.newApp.name.value']") # finish loading
|
212
119
|
end
|
213
|
-
|
214
|
-
def error_occured(ex)
|
215
|
-
snap
|
216
|
-
raise ex # re-raise the error after saving the snapshot
|
217
|
-
end
|
218
|
-
|
219
|
-
def snap
|
220
|
-
path = "Error#{Time.now.to_i}.png"
|
221
|
-
save_screenshot(path, :full => true)
|
222
|
-
system("open '#{path}'")
|
223
|
-
end
|
224
|
-
|
225
|
-
def wait_for_elements(name)
|
226
|
-
counter = 0
|
227
|
-
results = all(name)
|
228
|
-
while results.count == 0
|
229
|
-
# Helper.log.debug "Waiting for #{name}"
|
230
|
-
sleep 0.2
|
231
|
-
|
232
|
-
results = all(name)
|
233
|
-
|
234
|
-
counter += 1
|
235
|
-
if counter > 100
|
236
|
-
Helper.log.debug caller
|
237
|
-
raise ItunesConnectGeneralError.new("Couldn't find element '#{name}' after waiting for quite some time")
|
238
|
-
end
|
239
|
-
end
|
240
|
-
return results
|
241
|
-
end
|
242
120
|
end
|
243
121
|
end
|
data/lib/produce/manager.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
module Produce
|
2
2
|
class Manager
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
# Produces app at DeveloperCenter and ItunesConnect
|
4
|
+
# @param config (Config) (optional) config to use. Will fallback to
|
5
|
+
# config with ENV values if not specified.
|
6
|
+
def self.start_producing(config = Config.new)
|
7
|
+
FastlaneCore::DeveloperCenter.new.run(config)
|
8
|
+
return FastlaneCore::ItunesConnect.new.run(config) unless config[:skip_itc]
|
6
9
|
end
|
7
10
|
end
|
8
11
|
end
|
data/lib/produce/version.rb
CHANGED
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: produce
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Felix Krause
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-02-
|
11
|
+
date: 2015-02-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: fastlane_core
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - '>='
|
@@ -24,104 +24,6 @@ dependencies:
|
|
24
24
|
- - '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: highline
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ~>
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 1.6.21
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ~>
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: 1.6.21
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: colored
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - '>='
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - '>='
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: commander
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ~>
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: 4.2.0
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ~>
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: 4.2.0
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: credentials_manager
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - '>='
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :runtime
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - '>='
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: phantomjs
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - ~>
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: 1.9.8
|
90
|
-
type: :runtime
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - ~>
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: 1.9.8
|
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
27
|
- !ruby/object:Gem::Dependency
|
126
28
|
name: bundler
|
127
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -237,10 +139,8 @@ files:
|
|
237
139
|
- lib/produce/config.rb
|
238
140
|
- lib/produce/dependency_checker.rb
|
239
141
|
- lib/produce/developer_center.rb
|
240
|
-
- lib/produce/helper.rb
|
241
142
|
- lib/produce/itunes_connect.rb
|
242
143
|
- lib/produce/manager.rb
|
243
|
-
- lib/produce/update_checker.rb
|
244
144
|
- lib/produce/version.rb
|
245
145
|
homepage: http://fastlane.tools
|
246
146
|
licenses:
|
data/lib/produce/helper.rb
DELETED
@@ -1,49 +0,0 @@
|
|
1
|
-
require 'logger'
|
2
|
-
|
3
|
-
module Produce
|
4
|
-
module Helper
|
5
|
-
|
6
|
-
# Logging happens using this method
|
7
|
-
def self.log
|
8
|
-
if is_test?
|
9
|
-
@@log ||= Logger.new(STDOUT) # 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
|
@@ -1,44 +0,0 @@
|
|
1
|
-
require 'open-uri'
|
2
|
-
|
3
|
-
module Produce
|
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 "# produce #{v} is available.".green
|
12
|
-
puts "# It is recommended to use the latest version.".green
|
13
|
-
puts "# Update using '(sudo) gem update produce'.".green
|
14
|
-
puts "# To see what's new, open https://github.com/KrauseFx/produce/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 => ex
|
29
|
-
Helper.log.error("Could not check if 'produce' 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
|
-
Produce::VERSION
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
def self.fetch_latest
|
41
|
-
JSON.parse(open("http://rubygems.org/api/v1/gems/produce.json").read)["version"]
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|