deliver 0.13.5 → 1.0.0.beta1
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 +4 -4
- data/README.md +9 -208
- data/bin/deliver +1 -1
- data/lib/assets/DeliverfileDefault +2 -22
- data/lib/assets/summary.html.erb +27 -39
- data/lib/deliver.rb +16 -14
- data/lib/deliver/app_screenshot.rb +43 -33
- data/lib/deliver/commands_generator.rb +30 -108
- data/lib/deliver/detect_values.rb +23 -0
- data/lib/deliver/download_screenshots.rb +30 -9
- data/lib/deliver/html_generator.rb +7 -8
- data/lib/deliver/options.rb +126 -0
- data/lib/deliver/runner.rb +75 -0
- data/lib/deliver/setup.rb +84 -0
- data/lib/deliver/submit_for_review.rb +60 -0
- data/lib/deliver/upload_assets.rb +22 -0
- data/lib/deliver/upload_metadata.rb +100 -0
- data/lib/deliver/upload_price_tier.rb +21 -0
- data/lib/deliver/upload_screenshots.rb +63 -0
- data/lib/deliver/version.rb +2 -1
- metadata +37 -62
- data/lib/assets/DeliverLanguageMapping.json +0 -187
- data/lib/assets/DeliverfileExample +0 -38
- data/lib/deliver/app.rb +0 -167
- data/lib/deliver/app_metadata.rb +0 -419
- data/lib/deliver/app_metadata_screenshots.rb +0 -189
- data/lib/deliver/deliver_process.rb +0 -426
- data/lib/deliver/deliverer.rb +0 -138
- data/lib/deliver/deliverfile/deliverfile.rb +0 -35
- data/lib/deliver/deliverfile/deliverfile_creator.rb +0 -135
- data/lib/deliver/deliverfile/dsl.rb +0 -142
- data/lib/deliver/dependency_checker.rb +0 -19
- data/lib/deliver/ipa_file_analyser.rb +0 -44
- data/lib/deliver/ipa_uploader.rb +0 -148
- data/lib/deliver/itunes_connect/itunes_connect.rb +0 -12
- data/lib/deliver/itunes_connect/itunes_connect_additional.rb +0 -105
- data/lib/deliver/itunes_connect/itunes_connect_app_icon.rb +0 -41
- data/lib/deliver/itunes_connect/itunes_connect_app_rating.rb +0 -90
- data/lib/deliver/itunes_connect/itunes_connect_apple_watch_app_icon.rb +0 -42
- data/lib/deliver/itunes_connect/itunes_connect_information.rb +0 -34
- data/lib/deliver/itunes_connect/itunes_connect_new_version.rb +0 -67
- data/lib/deliver/itunes_connect/itunes_connect_reader.rb +0 -46
- data/lib/deliver/itunes_connect/itunes_connect_screenshot_fetcher.rb +0 -54
- data/lib/deliver/itunes_connect/itunes_connect_submission.rb +0 -282
- data/lib/deliver/itunes_transporter.rb +0 -221
- data/lib/deliver/metadata_item.rb +0 -94
- data/lib/deliver/testflight.rb +0 -27
@@ -1,187 +0,0 @@
|
|
1
|
-
[
|
2
|
-
{
|
3
|
-
"locale": "cmn-Hans",
|
4
|
-
"name": "Simplified Chinese",
|
5
|
-
"game-center": true
|
6
|
-
|
7
|
-
},
|
8
|
-
{
|
9
|
-
"locale": "cmn-Hant",
|
10
|
-
"name": "Traditional Chinese",
|
11
|
-
"game-center": true
|
12
|
-
},
|
13
|
-
{
|
14
|
-
"locale": "da-DK",
|
15
|
-
"name": "Danish",
|
16
|
-
"game-center": true,
|
17
|
-
"alternatives": ["da"]
|
18
|
-
},
|
19
|
-
{
|
20
|
-
"locale": "nl-NL",
|
21
|
-
"name": "Dutch",
|
22
|
-
"game-center": true,
|
23
|
-
"alternatives": ["nl"]
|
24
|
-
},
|
25
|
-
{
|
26
|
-
"locale": "en-AU",
|
27
|
-
"name": "Australian English",
|
28
|
-
"game-center": false
|
29
|
-
},
|
30
|
-
{
|
31
|
-
"locale": "en-AU",
|
32
|
-
"name": "English_Australian",
|
33
|
-
"game-center": false
|
34
|
-
},
|
35
|
-
{
|
36
|
-
"locale": "en-CA",
|
37
|
-
"name": "Canadian English",
|
38
|
-
"game-center": false
|
39
|
-
},
|
40
|
-
{
|
41
|
-
"locale": "en-CA",
|
42
|
-
"name": "English_CA",
|
43
|
-
"game-center": false
|
44
|
-
},
|
45
|
-
{
|
46
|
-
"locale": "en-GB",
|
47
|
-
"name": "UK English",
|
48
|
-
"game-center": true
|
49
|
-
},
|
50
|
-
{
|
51
|
-
"locale": "en-GB",
|
52
|
-
"name": "English_UK",
|
53
|
-
"game-center": true
|
54
|
-
},
|
55
|
-
{
|
56
|
-
"locale": "en-US",
|
57
|
-
"name": "English",
|
58
|
-
"game-center": true
|
59
|
-
},
|
60
|
-
{
|
61
|
-
"locale": "fi-FI",
|
62
|
-
"name": "Finnish",
|
63
|
-
"game-center": true,
|
64
|
-
"alternatives": ["fi"]
|
65
|
-
},
|
66
|
-
{
|
67
|
-
"locale": "fr-CA",
|
68
|
-
"name": "Canadian French",
|
69
|
-
"game-center": false
|
70
|
-
},
|
71
|
-
{
|
72
|
-
"locale": "fr-CA",
|
73
|
-
"name": "French_CA",
|
74
|
-
"game-center": false
|
75
|
-
},
|
76
|
-
{
|
77
|
-
"locale": "fr-FR",
|
78
|
-
"name": "French",
|
79
|
-
"game-center": true,
|
80
|
-
"alternatives": ["fr"]
|
81
|
-
},
|
82
|
-
{
|
83
|
-
"locale": "de-DE",
|
84
|
-
"name": "German",
|
85
|
-
"game-center": true,
|
86
|
-
"alternatives": ["de"]
|
87
|
-
},
|
88
|
-
{
|
89
|
-
"locale": "el-GR",
|
90
|
-
"name": "Greek",
|
91
|
-
"game-center": true,
|
92
|
-
"alternatives": ["el"]
|
93
|
-
},
|
94
|
-
{
|
95
|
-
"locale": "id-ID",
|
96
|
-
"name": "Indonesian",
|
97
|
-
"game-center": true,
|
98
|
-
"alternatives": ["id"]
|
99
|
-
},
|
100
|
-
{
|
101
|
-
"locale": "it-IT",
|
102
|
-
"name": "Italian",
|
103
|
-
"game-center": true,
|
104
|
-
"alternatives": ["it"]
|
105
|
-
},
|
106
|
-
{
|
107
|
-
"locale": "ja-JP",
|
108
|
-
"name": "Japanese",
|
109
|
-
"game-center": true,
|
110
|
-
"alternatives": ["ja"]
|
111
|
-
},
|
112
|
-
{
|
113
|
-
"locale": "ko-KR",
|
114
|
-
"name": "Korean",
|
115
|
-
"game-center": true,
|
116
|
-
"alternatives": ["ko"]
|
117
|
-
},
|
118
|
-
{
|
119
|
-
"locale": "ms-MY",
|
120
|
-
"name": "Malay",
|
121
|
-
"game-center": true,
|
122
|
-
"alternatives": ["ms"]
|
123
|
-
},
|
124
|
-
{
|
125
|
-
"locale": "no-NO",
|
126
|
-
"name": "Norwegian",
|
127
|
-
"game-center": true,
|
128
|
-
"alternatives": ["no"]
|
129
|
-
},
|
130
|
-
{
|
131
|
-
"locale": "pt-BR",
|
132
|
-
"name": "Brazilian Portuguese",
|
133
|
-
"game-center": true
|
134
|
-
},
|
135
|
-
{
|
136
|
-
"locale": "pt-PT",
|
137
|
-
"name": "Portuguese",
|
138
|
-
"game-center": true,
|
139
|
-
"alternatives": ["pt"]
|
140
|
-
},
|
141
|
-
{
|
142
|
-
"locale": "ru-RU",
|
143
|
-
"name": "Russian",
|
144
|
-
"game-center": true,
|
145
|
-
"alternatives": ["ru"]
|
146
|
-
},
|
147
|
-
{
|
148
|
-
"locale": "es-MX",
|
149
|
-
"name": "Mexican Spanish",
|
150
|
-
"game-center": false
|
151
|
-
},
|
152
|
-
{
|
153
|
-
"locale": "es-MX",
|
154
|
-
"name": "Spanish_MX",
|
155
|
-
"game-center": false
|
156
|
-
},
|
157
|
-
{
|
158
|
-
"locale": "es-ES",
|
159
|
-
"name": "Spanish",
|
160
|
-
"game-center": true,
|
161
|
-
"alternatives": ["es"]
|
162
|
-
},
|
163
|
-
{
|
164
|
-
"locale": "sv-SE",
|
165
|
-
"name": "Swedish",
|
166
|
-
"game-center": true,
|
167
|
-
"alternatives": ["sv"]
|
168
|
-
},
|
169
|
-
{
|
170
|
-
"locale": "th-TH",
|
171
|
-
"name": "Thai",
|
172
|
-
"game-center": true,
|
173
|
-
"alternatives": ["th"]
|
174
|
-
},
|
175
|
-
{
|
176
|
-
"locale": "tr-TR",
|
177
|
-
"name": "Turkish",
|
178
|
-
"game-center": true,
|
179
|
-
"alternatives": ["tr"]
|
180
|
-
},
|
181
|
-
{
|
182
|
-
"locale": "vi-VI",
|
183
|
-
"name" : "Vietnamese",
|
184
|
-
"game-center": true,
|
185
|
-
"alternatives": ["vi"]
|
186
|
-
}
|
187
|
-
]
|
@@ -1,38 +0,0 @@
|
|
1
|
-
###################### App Metadata ######################
|
2
|
-
#
|
3
|
-
# title(
|
4
|
-
# "en-US" => "Your App Name"
|
5
|
-
# )
|
6
|
-
#
|
7
|
-
# changelog(
|
8
|
-
# "en-US" => "iPhone 6 (Plus) Support"
|
9
|
-
# )
|
10
|
-
# More options: https://github.com/KrauseFx/deliver/blob/master/Deliverfile.md
|
11
|
-
|
12
|
-
###################### Screenshots ######################
|
13
|
-
# Store all screenshots in the ./screenshots folder separated
|
14
|
-
# by language. If you use snapshot, this happens automatically
|
15
|
-
|
16
|
-
###################### IPA File ######################
|
17
|
-
# This part is only relevant, if you want to submit a new binary
|
18
|
-
# If you don't use fastlane (https://github.com/KrauseFx/fastlane)
|
19
|
-
# you can set the path to your ipa file using:
|
20
|
-
# ipa "./app.ipa"
|
21
|
-
|
22
|
-
# to provide an ipa file for TestFlight distribution, use beta_ipa
|
23
|
-
# beta_ipa "./app.ipa"
|
24
|
-
|
25
|
-
# The version of your app - remove this if you provide an ipa file
|
26
|
-
version "[[APP_VERSION]]"
|
27
|
-
|
28
|
-
###################### More Options ######################
|
29
|
-
# If you want to have even more control, check out the documentation
|
30
|
-
# https://github.com/KrauseFx/deliver/blob/master/Deliverfile.md
|
31
|
-
|
32
|
-
|
33
|
-
###################### Automatically generated ######################
|
34
|
-
# Feel free to remove the following lines if you use fastlane
|
35
|
-
|
36
|
-
app_identifier "[[APP_IDENTIFIER]]" # The bundle identifier of your app
|
37
|
-
apple_id "[[APPLE_ID]]" # This is NOT your Apple login ID, but the App ID of your app
|
38
|
-
email "[[EMAIL]]" # the login email address
|
data/lib/deliver/app.rb
DELETED
@@ -1,167 +0,0 @@
|
|
1
|
-
module Deliver
|
2
|
-
class App
|
3
|
-
attr_accessor :apple_id, :app_identifier, :metadata
|
4
|
-
|
5
|
-
|
6
|
-
# Defines the different states of the app
|
7
|
-
#
|
8
|
-
# As specified by Apple: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/ChangingAppStatus.html
|
9
|
-
module AppStatus
|
10
|
-
PREPARE_FOR_SUBMISSION = "Prepare for Submission"
|
11
|
-
WAITING_FOR_REVIEW = "Waiting For Review"
|
12
|
-
IN_REVIEW = "In Review"
|
13
|
-
UPLOAD_RECEIVED = "Upload Received"
|
14
|
-
PENDING_DEVELOPER_RELEASE = "Pending Developer Release"
|
15
|
-
PROCESSING_FOR_APP_STORE = "Processing for App Store"
|
16
|
-
READY_FOR_SALE = "Ready for Sale"
|
17
|
-
REJECTED = "Rejected"
|
18
|
-
|
19
|
-
|
20
|
-
# Unused app states
|
21
|
-
# PENDING_APPLE_RELASE="Pending Apple Release"
|
22
|
-
# PENDING_CONTRACT = "Pending Contract"
|
23
|
-
# WAITING_FOR_EXPORT_COMPLIANCE = "Waiting For Export Compliance"
|
24
|
-
# METADATA_REJECTED = "Metadata Rejected"
|
25
|
-
# REMOVED_FROM_SALE = "Removed From Sale"
|
26
|
-
# DEVELOPER_REJECTED = "Developer Rejected" # equals PREPARE_FOR_SUBMISSION
|
27
|
-
# DEVELOPER_REMOVED_FROM_SALE = "Developer Removed From Sale"
|
28
|
-
# INVALID_BINARY = "Invalid Binary"
|
29
|
-
end
|
30
|
-
|
31
|
-
# @param apple_id The Apple ID of the app you want to modify or update. This ID has usually 9 digits
|
32
|
-
# @param app_identifier If you don't pass this, it will automatically be fetched from the Apple API
|
33
|
-
# which means it takes longer. If you **can** pass the app_identifier (e.g. com.facebook.Facebook) do it
|
34
|
-
def initialize(apple_id: nil, app_identifier: nil)
|
35
|
-
self.apple_id = apple_id.to_s.gsub('id', '').to_i
|
36
|
-
self.app_identifier = app_identifier
|
37
|
-
|
38
|
-
if apple_id and not app_identifier
|
39
|
-
# Fetch the app identifier based on the given Apple ID
|
40
|
-
self.app_identifier = FastlaneCore::ItunesSearchApi.fetch_bundle_identifier(apple_id)
|
41
|
-
elsif app_identifier and not apple_id
|
42
|
-
# Fetch the Apple ID based on the given app identifier
|
43
|
-
begin
|
44
|
-
begin
|
45
|
-
self.apple_id = FastlaneCore::ItunesSearchApi.fetch_by_identifier(app_identifier)['trackId']
|
46
|
-
rescue
|
47
|
-
Helper.log.warn "App doesn't seem to be in the App Store yet or is not available in the US App Store. Using the iTC API instead."
|
48
|
-
# Use the iTunes Connect API instead: make that default in the future
|
49
|
-
self.apple_id = FastlaneCore::ItunesConnect.new.find_apple_id(app_identifier)
|
50
|
-
raise "Couldn't find Apple ID" unless self.apple_id
|
51
|
-
end
|
52
|
-
rescue
|
53
|
-
unless Helper.is_test?
|
54
|
-
Helper.log.info "Could not find Apple ID based on the app identifier in the US App Store. Maybe the app is not yet in the store?".yellow
|
55
|
-
Helper.log.info "You can provide the Apple ID of your app using `apple_id '974739333'` in your `Deliverfile`".green
|
56
|
-
|
57
|
-
while ((self.apple_id || '').to_s.length == 0) || ((self.apple_id || 0).to_i == 0)
|
58
|
-
self.apple_id = ask("\nApple ID of your app (e.g. 284882215): ")
|
59
|
-
end
|
60
|
-
else
|
61
|
-
raise "Please pass a valid Apple ID using 'apple_id'".red
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def to_s
|
68
|
-
"#{apple_id} - #{app_identifier}"
|
69
|
-
end
|
70
|
-
|
71
|
-
#####################################################
|
72
|
-
# @!group Interacting with iTunesConnect
|
73
|
-
#####################################################
|
74
|
-
|
75
|
-
# The iTC handler which is used to interact with the iTunesConnect backend
|
76
|
-
def itc
|
77
|
-
@itc ||= Deliver::ItunesConnect.new
|
78
|
-
end
|
79
|
-
|
80
|
-
# This method fetches the current app status from iTunesConnect.
|
81
|
-
# This method may take some time to execute, since it uses frontend scripting under the hood.
|
82
|
-
# @return the current App Status defined at {Deliver::App::AppStatus}, like "Waiting For Review"
|
83
|
-
def get_app_status
|
84
|
-
itc.get_app_status(self)
|
85
|
-
end
|
86
|
-
|
87
|
-
# This method fetches the app version of the latest published version
|
88
|
-
# This method may take some time to execute, since it uses frontend scripting under the hood.
|
89
|
-
# @return the currently active app version, which in production
|
90
|
-
def get_live_version
|
91
|
-
itc.get_live_version(self)
|
92
|
-
end
|
93
|
-
|
94
|
-
|
95
|
-
#####################################################
|
96
|
-
# @!group Updating the App Metadata
|
97
|
-
#####################################################
|
98
|
-
|
99
|
-
# Use this method to change the default download location for the metadata packages
|
100
|
-
def set_metadata_directory(dir)
|
101
|
-
raise "Can not change metadata directory after accessing metadata of an app" if @metadata
|
102
|
-
@metadata_dir = dir
|
103
|
-
end
|
104
|
-
|
105
|
-
# @return the path to the directy in which the itmsp files will be downloaded
|
106
|
-
def get_metadata_directory
|
107
|
-
return @metadata_dir if @metadata_dir
|
108
|
-
return "./spec/fixtures/packages/" if Helper.is_test?
|
109
|
-
return "./"
|
110
|
-
end
|
111
|
-
|
112
|
-
# Access to update the metadata of this app
|
113
|
-
#
|
114
|
-
# The first time accessing this, will take some time, since it's downloading
|
115
|
-
# the latest version from iTC.
|
116
|
-
#
|
117
|
-
# Don't forget to call {#upload_metadata!} once you are finished
|
118
|
-
# @return [Deliver::AppMetadata] the latest metadata of this app
|
119
|
-
def metadata
|
120
|
-
@metadata ||= Deliver::AppMetadata.new(self, get_metadata_directory)
|
121
|
-
end
|
122
|
-
|
123
|
-
# Was the app metadata already downloaded?
|
124
|
-
def metadata_downloaded?
|
125
|
-
@metadata != nil
|
126
|
-
end
|
127
|
-
|
128
|
-
|
129
|
-
# Uploads a new app icon to iTunesConnect. This uses a headless browser
|
130
|
-
# which makes this command quite slow.
|
131
|
-
# @param (path) a path to the new app icon. The image must have the resolution of 1024x1024
|
132
|
-
def upload_app_icon!(path)
|
133
|
-
itc.upload_app_icon!(self, path)
|
134
|
-
end
|
135
|
-
|
136
|
-
# Uploads a new apple watch app icon to iTunesConnect. This uses a headless browser
|
137
|
-
# which makes this command quite slow.
|
138
|
-
# @param (path) a path to the new apple watch app icon. The image must have the resolution of 1024x1024
|
139
|
-
def upload_apple_watch_app_icon!(path)
|
140
|
-
itc.upload_apple_watch_app_icon!(self, path)
|
141
|
-
end
|
142
|
-
#####################################################
|
143
|
-
# @!group Destructive/Constructive methods
|
144
|
-
#####################################################
|
145
|
-
|
146
|
-
# This method creates a new version of your app using the
|
147
|
-
# iTunesConnect frontend. This will happen directly after calling
|
148
|
-
# this method.
|
149
|
-
# @param version_number (String) the version number as string for
|
150
|
-
# the new version that should be created
|
151
|
-
def create_new_version!(version_number)
|
152
|
-
itc.create_new_version!(self, version_number)
|
153
|
-
end
|
154
|
-
|
155
|
-
# This method has to be called, after modifying the values of .metadata.
|
156
|
-
# It will take care of uploading all changes to Apple.
|
157
|
-
# This method might take a few minutes to run
|
158
|
-
# @return [bool] true on success
|
159
|
-
# @raise [Deliver::TransporterTransferError]
|
160
|
-
# @raise [Deliver::TransporterInputError]
|
161
|
-
def upload_metadata!
|
162
|
-
raise "You first have to modify the metadata using app.metadata.setDescription" unless @metadata
|
163
|
-
|
164
|
-
self.metadata.upload!
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
data/lib/deliver/app_metadata.rb
DELETED
@@ -1,419 +0,0 @@
|
|
1
|
-
require 'nokogiri'
|
2
|
-
require 'deliver/app_metadata_screenshots'
|
3
|
-
|
4
|
-
module Deliver
|
5
|
-
class AppMetadataError < StandardError
|
6
|
-
end
|
7
|
-
class AppMetadataParameterError < StandardError
|
8
|
-
end
|
9
|
-
class AppMetadataTooManyScreenshotsError < StandardError
|
10
|
-
end
|
11
|
-
|
12
|
-
class AppMetadata
|
13
|
-
ITUNES_NAMESPACE = "http://apple.com/itunes/importer"
|
14
|
-
METADATA_FILE_NAME = "metadata.xml"
|
15
|
-
MAXIMUM_NUMBER_OF_SCREENSHOTS = 5
|
16
|
-
|
17
|
-
# @return Data contains all information for this app, including the unmodified one
|
18
|
-
attr_accessor :information
|
19
|
-
# data = {
|
20
|
-
# 'en-US' => {
|
21
|
-
# title: {
|
22
|
-
# value: "Something",
|
23
|
-
# modified: false
|
24
|
-
# },
|
25
|
-
# version_whats_new: {
|
26
|
-
# value: "Some text",
|
27
|
-
# modified: true
|
28
|
-
# }
|
29
|
-
# screenshots: {
|
30
|
-
# '45' => [
|
31
|
-
# Screenshot1
|
32
|
-
# ]
|
33
|
-
# }
|
34
|
-
# }
|
35
|
-
# }
|
36
|
-
|
37
|
-
private_constant :METADATA_FILE_NAME, :MAXIMUM_NUMBER_OF_SCREENSHOTS
|
38
|
-
|
39
|
-
INVALID_LANGUAGE_ERROR = "The specified language could not be found. Make sure it is available in FastlaneCore::Languages::ALL_LANGUAGES"
|
40
|
-
|
41
|
-
# You don't have to manually create an AppMetadata object. It will
|
42
|
-
# be created when you access the app's metadata ({Deliver::App#metadata})
|
43
|
-
# @param app [Deliver::App] The app this metadata is from/for
|
44
|
-
# @param dir [String] The app this metadata is from/for
|
45
|
-
# @param redownload_package [bool] When true
|
46
|
-
# the current package will be downloaded from iTC before you can
|
47
|
-
# modify any values. This should only be false for unit tests
|
48
|
-
# @raise (AppMetadataParameterError) Is thrown when don't pass a correct app object
|
49
|
-
def initialize(app, dir, redownload_package = true)
|
50
|
-
raise AppMetadataParameterError.new("No valid Deliver::App given") unless app.kind_of?Deliver::App
|
51
|
-
|
52
|
-
@metadata_dir = dir
|
53
|
-
@app = app
|
54
|
-
|
55
|
-
if self.class == AppMetadata
|
56
|
-
if redownload_package
|
57
|
-
# Delete the one that may exists already
|
58
|
-
unless Helper.is_test?
|
59
|
-
`rm -rf #{dir}/*.itmsp`
|
60
|
-
end
|
61
|
-
|
62
|
-
# we want to update the metadata, so first we have to download the existing one
|
63
|
-
transporter.download(app, dir)
|
64
|
-
|
65
|
-
# Parse the downloaded package
|
66
|
-
parse_package(dir)
|
67
|
-
else
|
68
|
-
# use_data contains the data to be used. This is the case for unit tests
|
69
|
-
parse_package(dir)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def information
|
75
|
-
@information ||= {}
|
76
|
-
end
|
77
|
-
|
78
|
-
# Verifies the if the version of iTunesConnect matches the one you pass as parameter
|
79
|
-
def verify_version(version_number)
|
80
|
-
xml_version = self.fetch_value("//x:version").first['string']
|
81
|
-
raise "Version mismatch: on iTunesConnect the latest version is '#{xml_version}', you specified '#{version_number}'" if xml_version != version_number
|
82
|
-
true
|
83
|
-
end
|
84
|
-
|
85
|
-
# Adds a new locale (language) to the given app
|
86
|
-
# @param language (FastlaneCore::Languages::ALL_LANGUAGES) the language you want to add
|
87
|
-
# @raise (AppMetadataParameterError) Is thrown when don't pass a correct hash with correct language codes.
|
88
|
-
# @return (Bool) Is true, if the language was created. False, when the language alreade existed
|
89
|
-
def add_new_locale(language)
|
90
|
-
unless FastlaneCore::Languages::ALL_LANGUAGES.include?language
|
91
|
-
raise "Language '#{language}' is invalid. It must be in #{FastlaneCore::Languages::ALL_LANGUAGES}."
|
92
|
-
end
|
93
|
-
|
94
|
-
if information[language] != nil
|
95
|
-
Helper.log.info("Locale '#{language}' already exists. Can not create it again.")
|
96
|
-
return false
|
97
|
-
end
|
98
|
-
|
99
|
-
|
100
|
-
locales = fetch_value("//x:locales").first
|
101
|
-
|
102
|
-
new_locale = @data.create_element('locale')
|
103
|
-
new_locale['name'] = language
|
104
|
-
locales << new_locale
|
105
|
-
|
106
|
-
# Title is the only thing which is required by iTC
|
107
|
-
default_title = information.values.first[:title][:value]
|
108
|
-
|
109
|
-
title = @data.create_element('title')
|
110
|
-
title.content = default_title
|
111
|
-
new_locale << title
|
112
|
-
|
113
|
-
Helper.log.info("Successfully created the new locale '#{language}'. The default title '#{default_title}' was set.")
|
114
|
-
Helper.log.info("You can update the title using 'app.metadata.update_title'")
|
115
|
-
|
116
|
-
information[language] ||= {}
|
117
|
-
information[language][:title] = { value: default_title, modified: true}
|
118
|
-
|
119
|
-
true
|
120
|
-
end
|
121
|
-
|
122
|
-
|
123
|
-
#####################################################
|
124
|
-
# @!group Updating metadata information
|
125
|
-
#####################################################
|
126
|
-
|
127
|
-
# Updates the app title
|
128
|
-
# @param (Hash) hash The hash should contain the correct language codes ({FastlaneCore::Languages})
|
129
|
-
# as keys.
|
130
|
-
# @raise (AppMetadataParameterError) Is thrown when don't pass a correct hash with correct language codes.
|
131
|
-
def update_title(hash)
|
132
|
-
update_metadata_key(:title, hash)
|
133
|
-
end
|
134
|
-
|
135
|
-
# Updates the app description which is shown in the AppStore
|
136
|
-
# @param (Hash) hash The hash should contain the correct language codes ({FastlaneCore::Languages})
|
137
|
-
# as keys.
|
138
|
-
# @raise (AppMetadataParameterError) Is thrown when don't pass a correct hash with correct language codes.
|
139
|
-
def update_description(hash)
|
140
|
-
update_metadata_key(:description, hash)
|
141
|
-
end
|
142
|
-
|
143
|
-
# Updates the app changelog of the latest version
|
144
|
-
# @param (Hash) hash The hash should contain the correct language codes ({FastlaneCore::Languages})
|
145
|
-
# as keys.
|
146
|
-
# @raise (AppMetadataParameterError) Is thrown when don't pass a correct hash with correct language codes.
|
147
|
-
def update_changelog(hash)
|
148
|
-
update_metadata_key(:version_whats_new, hash)
|
149
|
-
|
150
|
-
|
151
|
-
# The code below doesn't work when the app
|
152
|
-
# is not in the US App Store
|
153
|
-
# https://github.com/KrauseFx/deliver/issues/236#issuecomment-111016436
|
154
|
-
# check if we're allowed to do that
|
155
|
-
# if FastlaneCore::ItunesSearchApi.fetch(@app.apple_id)
|
156
|
-
# # App is already in the store
|
157
|
-
# update_metadata_key(:version_whats_new, hash)
|
158
|
-
# else
|
159
|
-
# # App is not in the store, skipping changelog for now
|
160
|
-
# Helper.log.info "It seems like this it the initial release of your app, which can't contain a changelog. Skipping the changelog for now.".yellow
|
161
|
-
# end
|
162
|
-
end
|
163
|
-
|
164
|
-
# Updates the Marketing URL
|
165
|
-
# @param (Hash) hash The hash should contain the correct language codes ({FastlaneCore::Languages})
|
166
|
-
# as keys.
|
167
|
-
# @raise (AppMetadataParameterError) Is thrown when don't pass a correct hash with correct language codes.
|
168
|
-
def update_marketing_url(hash)
|
169
|
-
update_metadata_key(:software_url, hash)
|
170
|
-
end
|
171
|
-
|
172
|
-
# Updates the Support URL
|
173
|
-
# @param (Hash) hash The hash should contain the correct language codes ({FastlaneCore::Languages})
|
174
|
-
# as keys.
|
175
|
-
# @raise (AppMetadataParameterError) Is thrown when don't pass a correct hash with correct language codes.
|
176
|
-
def update_support_url(hash)
|
177
|
-
update_metadata_key(:support_url, hash)
|
178
|
-
end
|
179
|
-
|
180
|
-
# Updates the Privacy URL
|
181
|
-
# @param (Hash) hash The hash should contain the correct language codes ({FastlaneCore::Languages})
|
182
|
-
# as keys.
|
183
|
-
# @raise (AppMetadataParameterError) Is thrown when don't pass a correct hash with correct language codes.
|
184
|
-
def update_privacy_url(hash)
|
185
|
-
update_metadata_key(:privacy_url, hash)
|
186
|
-
end
|
187
|
-
|
188
|
-
# Updates the app keywords
|
189
|
-
# @param (Hash) hash The hash should contain the correct language codes ({FastlaneCore::Languages})
|
190
|
-
# as keys. The value should be an array of keywords (each keyword is a string)
|
191
|
-
# @raise (AppMetadataParameterError) Is thrown when don't pass a correct hash with correct language codes.
|
192
|
-
def update_keywords(hash)
|
193
|
-
update_localized_value('keywords', hash) do |field, keywords, language|
|
194
|
-
raise AppMetadataParameterError.new("Parameter needs to be a hash (each language) with an array of keywords in it (given: #{hash})") unless keywords.kind_of?Array
|
195
|
-
|
196
|
-
next unless information
|
197
|
-
next unless information[language]
|
198
|
-
next unless information[language][:keywords]
|
199
|
-
|
200
|
-
if not information[language][:keywords] or keywords.sort != information[language][:keywords][:value].sort
|
201
|
-
field.children.remove # remove old keywords
|
202
|
-
|
203
|
-
node_set = Nokogiri::XML::NodeSet.new(@data)
|
204
|
-
keywords.each do |word|
|
205
|
-
keyword = Nokogiri::XML::Node.new('keyword', @data)
|
206
|
-
keyword.content = word
|
207
|
-
node_set << keyword
|
208
|
-
end
|
209
|
-
|
210
|
-
field.children = node_set
|
211
|
-
|
212
|
-
information[language][:keywords] = { value: keywords, modified: true }
|
213
|
-
end
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
# Updates the price tier of the given app
|
218
|
-
# @param (Integer) tier The tier that should be used from now on
|
219
|
-
def update_price_tier(tier)
|
220
|
-
raise "Price Tier '#{tier}' must be of type integer".red unless tier.kind_of?Integer
|
221
|
-
raise "Invalid price tier '#{tier}' given, must be 0 to 94".red unless (tier.to_i >= 0 and tier.to_i <= 87)
|
222
|
-
|
223
|
-
price = fetch_value("//x:wholesale_price_tier").last
|
224
|
-
unless price
|
225
|
-
Helper.log.info "No initial pricing found, setting the first one."
|
226
|
-
|
227
|
-
formatted_date = "2015-01-01"
|
228
|
-
pricing = Nokogiri.XML("
|
229
|
-
<products>
|
230
|
-
<product>
|
231
|
-
<territory>WW</territory>
|
232
|
-
<cleared_for_sale>true</cleared_for_sale>
|
233
|
-
<sales_start_date>#{formatted_date}</sales_start_date>
|
234
|
-
<intervals>
|
235
|
-
<interval>
|
236
|
-
<start_date>#{formatted_date}</start_date>
|
237
|
-
<wholesale_price_tier>0</wholesale_price_tier>
|
238
|
-
</interval>
|
239
|
-
</intervals>
|
240
|
-
<allow_volume_discount>true</allow_volume_discount>
|
241
|
-
</product>
|
242
|
-
</products>")
|
243
|
-
software_metadata = fetch_value("//x:software_metadata").last
|
244
|
-
software_metadata << pricing.root
|
245
|
-
|
246
|
-
# We're done here, now fetch the element again and set the real price tier
|
247
|
-
price = fetch_value("//x:wholesale_price_tier").last
|
248
|
-
raise "Something went wrong creating the new price tier" unless price
|
249
|
-
end
|
250
|
-
|
251
|
-
price.content = tier
|
252
|
-
end
|
253
|
-
|
254
|
-
#####################################################
|
255
|
-
# @!group Manually fetching elements from the metadata.xml
|
256
|
-
#####################################################
|
257
|
-
|
258
|
-
# Directly fetch XML nodes from the metadata.xml.
|
259
|
-
# @example Fetch all keywords
|
260
|
-
# fetch_value("//x:keyword")
|
261
|
-
# @example Fetch a specific locale
|
262
|
-
# fetch_value("//x:locale[@name='de-DE']")
|
263
|
-
# @example Fetch the node that contains all screenshots for a specific language
|
264
|
-
# fetch_value("//x:locale[@name='de-DE']/x:software_screenshots")
|
265
|
-
# @return the requests XML nodes or node set
|
266
|
-
def fetch_value(xpath)
|
267
|
-
@data.xpath(xpath, "x" => ITUNES_NAMESPACE)
|
268
|
-
end
|
269
|
-
|
270
|
-
|
271
|
-
#####################################################
|
272
|
-
# @!group Uploading the updated metadata
|
273
|
-
#####################################################
|
274
|
-
|
275
|
-
# Actually uploads the updated metadata to Apple.
|
276
|
-
# This method might take a while.
|
277
|
-
# @raise (TransporterTransferError) When something goes wrong when uploading
|
278
|
-
# the metadata/app
|
279
|
-
def upload!
|
280
|
-
unless Helper.is_test?
|
281
|
-
# First: Write the current XML state to disk
|
282
|
-
File.write("#{@package_path}/#{METADATA_FILE_NAME}", @data.to_xml)
|
283
|
-
end
|
284
|
-
|
285
|
-
transporter.upload(@app, @metadata_dir)
|
286
|
-
end
|
287
|
-
|
288
|
-
private
|
289
|
-
def update_metadata_key(key, hash)
|
290
|
-
update_localized_value(key, hash) do |field, new_val, language|
|
291
|
-
raise AppMetadataParameterError.new("Parameter needs to be an hash, containg strings.") unless new_val.kind_of?String
|
292
|
-
if field.content != new_val
|
293
|
-
field.content = new_val
|
294
|
-
information[language][key] = { value: new_val, modified: true }
|
295
|
-
end
|
296
|
-
end
|
297
|
-
end
|
298
|
-
|
299
|
-
# @return (Deliver::ItunesTransporter) The iTunesTransporter which is
|
300
|
-
# used to upload/download the app metadata.
|
301
|
-
def transporter
|
302
|
-
@transporter ||= ItunesTransporter.new
|
303
|
-
end
|
304
|
-
|
305
|
-
def update_localized_value(xpath_name, new_value)
|
306
|
-
raise AppMetadataParameterError.new("Please pass a hash of languages to this method") unless new_value.kind_of?Hash
|
307
|
-
raise AppMetadataParameterError.new("Please pass a block, which updates the resulting node") unless block_given?
|
308
|
-
|
309
|
-
xpath_name = xpath_name.to_s
|
310
|
-
|
311
|
-
# Run through all the locales given by the 'user'
|
312
|
-
new_value.each do |language, value|
|
313
|
-
# First, check if this is the legacy locale notation
|
314
|
-
unless FastlaneCore::Languages::ALL_LANGUAGES.include?language
|
315
|
-
# Check if we need to support legacy language codes
|
316
|
-
short_code = language.match(/(\w\w)\-.*/)
|
317
|
-
if short_code and short_code.length == 2 and FastlaneCore::Languages::ALL_LANGUAGES.include?short_code[1]
|
318
|
-
language = short_code[1]
|
319
|
-
else
|
320
|
-
raise AppMetadataParameterError.new("#{INVALID_LANGUAGE_ERROR} (#{language})")
|
321
|
-
end
|
322
|
-
end
|
323
|
-
|
324
|
-
|
325
|
-
create_locale_if_not_exists(language)
|
326
|
-
|
327
|
-
locale = fetch_value("//x:locale[@name='#{language}']").first
|
328
|
-
|
329
|
-
field = locale.search(xpath_name).first
|
330
|
-
|
331
|
-
if not field
|
332
|
-
# This entry does not exist yet, so we have to create it
|
333
|
-
field = Nokogiri::XML::Node.new(xpath_name, @data)
|
334
|
-
locale << field
|
335
|
-
end
|
336
|
-
|
337
|
-
yield(field, value, language)
|
338
|
-
Helper.log.info "Updated #{xpath_name} for locale #{language}"
|
339
|
-
end
|
340
|
-
end
|
341
|
-
|
342
|
-
def create_locale_if_not_exists(locale)
|
343
|
-
add_new_locale(locale) unless information[locale]
|
344
|
-
end
|
345
|
-
|
346
|
-
# Parses the metadata using nokogiri
|
347
|
-
def parse_package(path)
|
348
|
-
unless path.include?".itmsp"
|
349
|
-
path += "/#{@app.apple_id}.itmsp/"
|
350
|
-
end
|
351
|
-
@package_path = path
|
352
|
-
|
353
|
-
@data ||= Nokogiri::XML(File.read("#{path}/#{METADATA_FILE_NAME}"))
|
354
|
-
verify_package
|
355
|
-
clean_package
|
356
|
-
fill_in_data
|
357
|
-
end
|
358
|
-
|
359
|
-
# Checks if there is a non live version available
|
360
|
-
# (a new version, or a new app)
|
361
|
-
def verify_package
|
362
|
-
raise AppMetadataError.new("metadata_token is missing. This package seems to be broken") if fetch_value("//x:metadata_token").count != 1
|
363
|
-
end
|
364
|
-
|
365
|
-
# Cleans up the package of stuff we do not want to modify/upload
|
366
|
-
def clean_package
|
367
|
-
# Remove the live version (if it exists)
|
368
|
-
versions = fetch_value("//x:version")
|
369
|
-
while versions.count > 1
|
370
|
-
versions.last.remove
|
371
|
-
versions = fetch_value("//x:version")
|
372
|
-
end
|
373
|
-
Helper.log.info "Modifying version '#{versions.first['string']}' of app #{@app.app_identifier}"
|
374
|
-
|
375
|
-
# Remove all GameCenter related code
|
376
|
-
fetch_value("//x:game_center").remove
|
377
|
-
|
378
|
-
# Remove all InApp purchases
|
379
|
-
fetch_value("//x:in_app_purchases").remove
|
380
|
-
|
381
|
-
fetch_value("//x:software_screenshots").remove
|
382
|
-
|
383
|
-
# Change the Chinese locale
|
384
|
-
# Radar 22548000
|
385
|
-
# from zh-Hans to cmn-Hans
|
386
|
-
chinese = []
|
387
|
-
chinese << fetch_value("//x:locale[@name='zh-Hant']").last
|
388
|
-
chinese << fetch_value("//x:locale[@name='zh-Hans']").last
|
389
|
-
chinese.each do |entry|
|
390
|
-
entry["name"] = entry["name"].gsub("zh", "cmn") if entry && entry["name"]
|
391
|
-
end
|
392
|
-
end
|
393
|
-
|
394
|
-
# This will fill in all information we got (from the downloaded metadata.xml file) into self.information
|
395
|
-
def fill_in_data
|
396
|
-
locales = fetch_value("//x:locale")
|
397
|
-
locales.each do |locale|
|
398
|
-
language = locale['name']
|
399
|
-
information[language] ||= {}
|
400
|
-
|
401
|
-
all_keys = [:title, :description, :version_whats_new, :software_url, :support_url, :privacy_url]
|
402
|
-
|
403
|
-
all_keys.each do |key|
|
404
|
-
information[language][key] = {
|
405
|
-
value: (locale.search(key.to_s).first.content rescue ''),
|
406
|
-
modified: false
|
407
|
-
}
|
408
|
-
end
|
409
|
-
|
410
|
-
information[language][:keywords] = { value: [], modified: false}
|
411
|
-
locale.search('keyword').each do |current|
|
412
|
-
information[language][:keywords][:value] << current.content
|
413
|
-
end
|
414
|
-
|
415
|
-
information[language][:screenshots] = []
|
416
|
-
end
|
417
|
-
end
|
418
|
-
end
|
419
|
-
end
|