fastlane-plugin-amazon_appstore 1.4.0 → 1.6.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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fe05c393d60b47ae7056bba08ebecb83c0938413d9773fafbf4523561a3d80cb
|
|
4
|
+
data.tar.gz: 7ffcd28240b237ebc7d30b9c865b4521662bdd28a9cd33613087f6d7765ed558
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aa9e81ecd54a493a472978e12a151d24314b04177771ff3448e89b88bf5e00c622816582671ae52beba5dc6340f4eb16fb2fabe4bc309b788153fbd3a6db761a
|
|
7
|
+
data.tar.gz: 0d5c668cf8d8d77c5275b26bf17043b22a584f0f5b0d271edfc50b355e76e0c05acacf618350de4747a6941dd112e1511f9a28515f7db9d78aca937534e50a25
|
data/README.md
CHANGED
|
@@ -30,19 +30,23 @@ upload_to_amazon_appstore(
|
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
### Parameters
|
|
33
|
-
| Key | Description | Default |
|
|
34
|
-
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------- |
|
|
35
|
-
| package_name | The package name of the application to use | * |
|
|
33
|
+
| Key | Description | Default |
|
|
34
|
+
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------- |
|
|
35
|
+
| package_name | The package name of the application to use | * |
|
|
36
36
|
| apk | Path to the APK file to upload (optional if apk_paths is provided) | |
|
|
37
37
|
| apk_paths | An array of paths to APK files to upload (optional if apk is provided) | |
|
|
38
|
-
| client_id | The client ID you saved | |
|
|
39
|
-
| client_secret | The client secret you saved | |
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
38
|
+
| client_id | The client ID you saved | |
|
|
39
|
+
| client_secret | The client secret you saved | |
|
|
40
|
+
| skip_upload_apk | Whether to skip uploading APK | false |
|
|
41
|
+
| skip_upload_metadata | Whether to skip uploading metadata (title, descriptions) | false |
|
|
42
|
+
| skip_upload_changelogs | Whether to skip uploading changelogs | false |
|
|
43
|
+
| skip_upload_images | Whether to skip uploading images (icons, promo images) | true |
|
|
44
|
+
| skip_upload_screenshots | Whether to skip uploading screenshots | true |
|
|
45
|
+
| metadata_path | Path to the directory containing the metadata files | ./fastlane/metadata/android |
|
|
46
|
+
| changes_not_sent_for_review | Indicates that the changes in this edit will not be reviewed until they are explicitly sent for review from the Amazon Appstore Console UI | false |
|
|
43
47
|
| overwrite_upload | Whether to allow overwriting an existing upload | false |
|
|
44
48
|
| overwrite_upload_mode | Upload strategy when overwrite_upload is true. Can be 'new' (delete existing edit and create new) or 'reuse' (reuse existing edit) | new |
|
|
45
|
-
| timeout | Timeout for read, open (in seconds) | 300 |
|
|
49
|
+
| timeout | Timeout for read, open (in seconds) | 300 |
|
|
46
50
|
* = default value is dependent on the user's system
|
|
47
51
|
|
|
48
52
|
### Changelogs
|
|
@@ -70,6 +74,74 @@ When uploading multiple APKs with different version codes, the plugin will use t
|
|
|
70
74
|
One difference from Google Play is that the Amazon Appstore always requires release notes to be entered before review.
|
|
71
75
|
For this reason, `-` will be entered by default if the corresponding changelogs file is not found, or if the `skip_upload_changelogs` parameter is used.
|
|
72
76
|
|
|
77
|
+
### Metadata (Title and Descriptions)
|
|
78
|
+
|
|
79
|
+
You can update store listing metadata (title, short description, full description) by adding text files in the metadata directory. The plugin uses the same structure as Google Play's [supply](https://docs.fastlane.tools/actions/upload_to_play_store/).
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
└── fastlane
|
|
83
|
+
└── metadata
|
|
84
|
+
└── android
|
|
85
|
+
├── en-US
|
|
86
|
+
│ ├── title.txt
|
|
87
|
+
│ ├── short_description.txt
|
|
88
|
+
│ └── full_description.txt
|
|
89
|
+
└── ja-JP
|
|
90
|
+
├── title.txt
|
|
91
|
+
├── short_description.txt
|
|
92
|
+
└── full_description.txt
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Images and Screenshots
|
|
96
|
+
|
|
97
|
+
You can upload images (icons, promo images) and screenshots by placing them in the `images/` directory. The plugin supports the Google Play metadata structure and maps it to Amazon Appstore image types.
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
└── fastlane
|
|
101
|
+
└── metadata
|
|
102
|
+
└── android
|
|
103
|
+
└── en-US
|
|
104
|
+
└── images
|
|
105
|
+
├── icon.png → small-icons (114x114)
|
|
106
|
+
├── large_icon.png → large-icons (512x512)
|
|
107
|
+
├── featureGraphic.png → promo-images (1024x500)
|
|
108
|
+
├── phoneScreenshots/ → screenshots
|
|
109
|
+
│ ├── 1.png
|
|
110
|
+
│ └── 2.png
|
|
111
|
+
├── tvBanner.png → firetv-icons
|
|
112
|
+
├── tvBackground.png → firetv-backgrounds
|
|
113
|
+
└── tvScreenshots/ → firetv-screenshots
|
|
114
|
+
├── 1.png
|
|
115
|
+
└── 2.png
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
To enable image uploads, set `skip_upload_images: false` and/or `skip_upload_screenshots: false`:
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
upload_to_amazon_appstore(
|
|
122
|
+
apk: "app/build/outputs/apk/release/app-release.apk",
|
|
123
|
+
client_id: <YOUR_CLIENT_ID>,
|
|
124
|
+
client_secret: <YOUR_CLIENT_SECRET>,
|
|
125
|
+
skip_upload_images: false,
|
|
126
|
+
skip_upload_screenshots: false
|
|
127
|
+
)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Metadata-only Upload
|
|
131
|
+
|
|
132
|
+
You can upload only metadata (without APK) by using `skip_upload_apk: true`:
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
upload_to_amazon_appstore(
|
|
136
|
+
client_id: <YOUR_CLIENT_ID>,
|
|
137
|
+
client_secret: <YOUR_CLIENT_SECRET>,
|
|
138
|
+
skip_upload_apk: true,
|
|
139
|
+
skip_upload_metadata: false,
|
|
140
|
+
skip_upload_images: false,
|
|
141
|
+
skip_upload_screenshots: false
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
|
|
73
145
|
## Run tests for this plugin
|
|
74
146
|
|
|
75
147
|
To run both the tests, and code style validation, run
|
|
@@ -62,35 +62,38 @@ module Fastlane
|
|
|
62
62
|
UI.abort_with_message!("Failed to get edit_id") if edit_id.nil?
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
apks << params[:apk] if params[:apk]
|
|
67
|
-
apks += params[:apk_paths] if params[:apk_paths]
|
|
65
|
+
version_codes = []
|
|
68
66
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
unless params[:skip_upload_apk]
|
|
68
|
+
apks = []
|
|
69
|
+
apks << params[:apk] if params[:apk]
|
|
70
|
+
apks += params[:apk_paths] if params[:apk_paths]
|
|
72
71
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
72
|
+
if apks.empty?
|
|
73
|
+
UI.abort_with_message!("No APK files provided. Please provide either 'apk' or 'apk_paths' parameter")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
UI.message("Replacing APKs with #{apks.length} file(s)...")
|
|
77
|
+
begin
|
|
78
|
+
apk_results = Helper::AmazonAppstoreHelper.replace_apks(
|
|
79
|
+
apk_paths: apks,
|
|
80
|
+
app_id: params[:package_name],
|
|
81
|
+
edit_id: edit_id,
|
|
82
|
+
token: token
|
|
83
|
+
)
|
|
84
|
+
rescue StandardError => e
|
|
85
|
+
UI.error(e.message)
|
|
86
|
+
UI.abort_with_message!("Failed to replace APKs")
|
|
87
|
+
end
|
|
88
|
+
version_codes = apk_results.map { |result| result[:version_code] }
|
|
89
|
+
apk_results.each_with_index do |result, index|
|
|
90
|
+
UI.message("Successfully processed APK #{index + 1} with version code: #{result[:version_code]}")
|
|
91
|
+
end
|
|
89
92
|
end
|
|
90
93
|
|
|
91
94
|
UI.message("Updating release notes...")
|
|
92
95
|
begin
|
|
93
|
-
Helper::AmazonAppstoreHelper.
|
|
96
|
+
Helper::AmazonAppstoreHelper.update_changelogs(
|
|
94
97
|
app_id: params[:package_name],
|
|
95
98
|
edit_id: edit_id,
|
|
96
99
|
token: token,
|
|
@@ -100,9 +103,13 @@ module Fastlane
|
|
|
100
103
|
)
|
|
101
104
|
rescue StandardError => e
|
|
102
105
|
UI.error(e.message)
|
|
103
|
-
UI.abort_with_message!("Failed to update
|
|
106
|
+
UI.abort_with_message!("Failed to update changelogs")
|
|
104
107
|
end
|
|
105
108
|
|
|
109
|
+
upload_metadata(params, edit_id, token) unless params[:skip_upload_metadata]
|
|
110
|
+
upload_images(params, edit_id, token) unless params[:skip_upload_images]
|
|
111
|
+
upload_screenshots(params, edit_id, token) unless params[:skip_upload_screenshots]
|
|
112
|
+
|
|
106
113
|
if params[:changes_not_sent_for_review]
|
|
107
114
|
UI.success('Successfully finished the upload to Amazon Appstore')
|
|
108
115
|
return
|
|
@@ -123,6 +130,89 @@ module Fastlane
|
|
|
123
130
|
UI.success('Successfully finished the upload to Amazon Appstore')
|
|
124
131
|
end
|
|
125
132
|
|
|
133
|
+
def self.upload_metadata(params, edit_id, token)
|
|
134
|
+
UI.message("Uploading metadata...")
|
|
135
|
+
languages = available_languages(params[:metadata_path])
|
|
136
|
+
languages.each do |language|
|
|
137
|
+
metadata = Helper::AmazonAppstoreHelper.load_metadata_from_files(
|
|
138
|
+
metadata_path: params[:metadata_path],
|
|
139
|
+
language: language
|
|
140
|
+
)
|
|
141
|
+
next if metadata.values.all?(&:nil?)
|
|
142
|
+
|
|
143
|
+
begin
|
|
144
|
+
Helper::AmazonAppstoreHelper.update_listing_metadata(
|
|
145
|
+
app_id: params[:package_name],
|
|
146
|
+
edit_id: edit_id,
|
|
147
|
+
language: language,
|
|
148
|
+
listing_data: metadata,
|
|
149
|
+
token: token
|
|
150
|
+
)
|
|
151
|
+
UI.message("Updated metadata for #{language}")
|
|
152
|
+
rescue StandardError => e
|
|
153
|
+
UI.error("Failed to update metadata for #{language}: #{e.message}")
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def self.upload_images(params, edit_id, token)
|
|
159
|
+
UI.message("Uploading images...")
|
|
160
|
+
upload_image_assets(params, edit_id, token, %w[small-icons large-icons promo-images firetv-icons firetv-backgrounds])
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def self.upload_screenshots(params, edit_id, token)
|
|
164
|
+
UI.message("Uploading screenshots...")
|
|
165
|
+
upload_image_assets(params, edit_id, token, %w[screenshots firetv-screenshots])
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def self.upload_image_assets(params, edit_id, token, image_types)
|
|
169
|
+
languages = available_languages(params[:metadata_path])
|
|
170
|
+
|
|
171
|
+
languages.each do |language|
|
|
172
|
+
image_types.each do |image_type|
|
|
173
|
+
images = Helper::AmazonAppstoreHelper.find_images_for_type(
|
|
174
|
+
metadata_path: params[:metadata_path],
|
|
175
|
+
language: language,
|
|
176
|
+
image_type: image_type
|
|
177
|
+
)
|
|
178
|
+
next if images.empty?
|
|
179
|
+
|
|
180
|
+
begin
|
|
181
|
+
Helper::AmazonAppstoreHelper.delete_all_images(
|
|
182
|
+
app_id: params[:package_name],
|
|
183
|
+
edit_id: edit_id,
|
|
184
|
+
language: language,
|
|
185
|
+
image_type: image_type,
|
|
186
|
+
token: token
|
|
187
|
+
)
|
|
188
|
+
rescue StandardError => e
|
|
189
|
+
UI.message("Failed to delete existing #{image_type} for #{language}: #{e.message}")
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
images.each do |image_path|
|
|
193
|
+
Helper::AmazonAppstoreHelper.upload_image(
|
|
194
|
+
app_id: params[:package_name],
|
|
195
|
+
edit_id: edit_id,
|
|
196
|
+
language: language,
|
|
197
|
+
image_type: image_type,
|
|
198
|
+
image_path: image_path,
|
|
199
|
+
token: token
|
|
200
|
+
)
|
|
201
|
+
UI.message("Uploaded #{image_type} for #{language}: #{File.basename(image_path)}")
|
|
202
|
+
rescue StandardError => e
|
|
203
|
+
UI.error("Failed to upload #{image_type} for #{language}: #{e.message}")
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def self.available_languages(metadata_path)
|
|
210
|
+
return [] unless File.directory?(metadata_path)
|
|
211
|
+
|
|
212
|
+
Dir.entries(metadata_path)
|
|
213
|
+
.select { |entry| File.directory?(File.join(metadata_path, entry)) && !entry.start_with?('.') }
|
|
214
|
+
end
|
|
215
|
+
|
|
126
216
|
def self.description
|
|
127
217
|
"Upload apps to Amazon Appstore"
|
|
128
218
|
end
|
|
@@ -167,12 +257,36 @@ module Fastlane
|
|
|
167
257
|
description: "An array of paths to APK files to upload",
|
|
168
258
|
optional: true,
|
|
169
259
|
type: Array),
|
|
260
|
+
FastlaneCore::ConfigItem.new(key: :skip_upload_apk,
|
|
261
|
+
env_name: "AMAZON_APPSTORE_SKIP_UPLOAD_APK",
|
|
262
|
+
description: "Whether to skip uploading APK",
|
|
263
|
+
default_value: false,
|
|
264
|
+
optional: true,
|
|
265
|
+
type: Boolean),
|
|
266
|
+
FastlaneCore::ConfigItem.new(key: :skip_upload_metadata,
|
|
267
|
+
env_name: "AMAZON_APPSTORE_SKIP_UPLOAD_METADATA",
|
|
268
|
+
description: "Whether to skip uploading metadata (title, descriptions)",
|
|
269
|
+
default_value: false,
|
|
270
|
+
optional: true,
|
|
271
|
+
type: Boolean),
|
|
170
272
|
FastlaneCore::ConfigItem.new(key: :skip_upload_changelogs,
|
|
171
273
|
env_name: "AMAZON_APPSTORE_SKIP_UPLOAD_CHANGELOGS",
|
|
172
274
|
description: "Whether to skip uploading changelogs",
|
|
173
275
|
default_value: false,
|
|
174
276
|
optional: true,
|
|
175
277
|
type: Boolean),
|
|
278
|
+
FastlaneCore::ConfigItem.new(key: :skip_upload_images,
|
|
279
|
+
env_name: "AMAZON_APPSTORE_SKIP_UPLOAD_IMAGES",
|
|
280
|
+
description: "Whether to skip uploading images (icons, promo images)",
|
|
281
|
+
default_value: true,
|
|
282
|
+
optional: true,
|
|
283
|
+
type: Boolean),
|
|
284
|
+
FastlaneCore::ConfigItem.new(key: :skip_upload_screenshots,
|
|
285
|
+
env_name: "AMAZON_APPSTORE_SKIP_UPLOAD_SCREENSHOTS",
|
|
286
|
+
description: "Whether to skip uploading screenshots",
|
|
287
|
+
default_value: true,
|
|
288
|
+
optional: true,
|
|
289
|
+
type: Boolean),
|
|
176
290
|
FastlaneCore::ConfigItem.new(key: :metadata_path,
|
|
177
291
|
env_name: "AMAZON_APPSTORE_METADATA_PATH",
|
|
178
292
|
description: "Path to the directory containing the metadata files",
|
|
@@ -7,7 +7,7 @@ module Fastlane
|
|
|
7
7
|
UI = FastlaneCore::UI unless Fastlane.const_defined?(:UI)
|
|
8
8
|
|
|
9
9
|
module Helper
|
|
10
|
-
class AmazonAppstoreHelper
|
|
10
|
+
class AmazonAppstoreHelper # rubocop:disable Metrics/ClassLength
|
|
11
11
|
BASE_URL = 'https://developer.amazon.com'
|
|
12
12
|
AUTH_URL = 'https://api.amazon.com/auth/o2/token'
|
|
13
13
|
|
|
@@ -99,7 +99,6 @@ module Fastlane
|
|
|
99
99
|
existing_apks = get_apks_response.body
|
|
100
100
|
raise StandardError, 'No existing APKs found in edit' if existing_apks.empty?
|
|
101
101
|
|
|
102
|
-
version_codes = []
|
|
103
102
|
apk_results = []
|
|
104
103
|
|
|
105
104
|
apk_paths.each_with_index do |apk_path, index|
|
|
@@ -129,7 +128,6 @@ module Fastlane
|
|
|
129
128
|
raise StandardError, replace_apk_response.body unless replace_apk_response.success?
|
|
130
129
|
|
|
131
130
|
version_code = replace_apk_response.body[:versionCode]
|
|
132
|
-
version_codes << version_code
|
|
133
131
|
apk_results << { version_code: version_code, apk_id: apk_id }
|
|
134
132
|
else
|
|
135
133
|
# Upload new APK if there are more APK paths than existing APKs
|
|
@@ -139,7 +137,6 @@ module Fastlane
|
|
|
139
137
|
edit_id: edit_id,
|
|
140
138
|
token: token
|
|
141
139
|
)
|
|
142
|
-
version_codes << result[:version_code]
|
|
143
140
|
apk_results << result
|
|
144
141
|
end
|
|
145
142
|
end
|
|
@@ -184,19 +181,20 @@ module Fastlane
|
|
|
184
181
|
UI.message("Successfully deleted APK #{apk_id}")
|
|
185
182
|
end
|
|
186
183
|
|
|
187
|
-
def self.
|
|
184
|
+
def self.update_changelogs(app_id:, edit_id:, token:, version_codes:, skip_upload_changelogs:, metadata_path:)
|
|
188
185
|
return if skip_upload_changelogs
|
|
186
|
+
return if version_codes.empty?
|
|
189
187
|
|
|
190
|
-
|
|
188
|
+
# Use the highest version code's changelog (same as Fastlane's approach)
|
|
189
|
+
max_version_code = version_codes.max
|
|
190
|
+
UI.message("Updating changelogs using highest version code: #{max_version_code}")
|
|
191
191
|
|
|
192
|
-
# Get listings once with ETag
|
|
193
192
|
listings_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/listings"
|
|
194
193
|
listings_response = api_client.get(listings_path) do |request|
|
|
195
194
|
request.headers['Authorization'] = "Bearer #{token}"
|
|
196
195
|
end
|
|
197
196
|
raise StandardError, listings_response.body unless listings_response.success?
|
|
198
197
|
|
|
199
|
-
# Process each language once
|
|
200
198
|
listings_response.body[:listings].each do |lang, listing|
|
|
201
199
|
# Get fresh ETag for each language update to avoid conflicts
|
|
202
200
|
etag_response = api_client.get(listings_path) do |request|
|
|
@@ -206,15 +204,13 @@ module Fastlane
|
|
|
206
204
|
|
|
207
205
|
etag = etag_response.headers['Etag']
|
|
208
206
|
|
|
209
|
-
|
|
210
|
-
recent_changes = find_changelog_for_multiple_version_codes(
|
|
207
|
+
listing[:recentChanges] = find_changelog(
|
|
211
208
|
language: listing[:language],
|
|
212
|
-
|
|
209
|
+
version_code: max_version_code,
|
|
210
|
+
skip_upload_changelogs: false,
|
|
213
211
|
metadata_path: metadata_path
|
|
214
212
|
)
|
|
215
|
-
listing[:recentChanges] = recent_changes
|
|
216
213
|
|
|
217
|
-
# Update listings once per language
|
|
218
214
|
update_listings_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/listings/#{lang}"
|
|
219
215
|
update_listings_response = api_client.put(update_listings_path) do |request|
|
|
220
216
|
request.body = listing.to_json
|
|
@@ -226,40 +222,142 @@ module Fastlane
|
|
|
226
222
|
nil
|
|
227
223
|
end
|
|
228
224
|
|
|
229
|
-
def self.
|
|
230
|
-
|
|
231
|
-
|
|
225
|
+
def self.upload_image(app_id:, edit_id:, language:, image_type:, image_path:, token:)
|
|
226
|
+
images_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/listings/#{language}/#{image_type}"
|
|
227
|
+
etag_response = api_client.get(images_path) do |request|
|
|
232
228
|
request.headers['Authorization'] = "Bearer #{token}"
|
|
233
229
|
end
|
|
234
|
-
raise StandardError,
|
|
230
|
+
raise StandardError, etag_response.body unless etag_response.success?
|
|
235
231
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
232
|
+
etag = etag_response.headers['Etag']
|
|
233
|
+
upload_path = "#{images_path}/upload"
|
|
234
|
+
upload_response = api_client.post(upload_path) do |request|
|
|
235
|
+
request.body = File.binread(image_path)
|
|
236
|
+
request.headers['Content-Length'] = request.body.bytesize.to_s
|
|
237
|
+
request.headers['Content-Type'] = 'application/octet-stream'
|
|
238
|
+
request.headers['Authorization'] = "Bearer #{token}"
|
|
239
|
+
request.headers['If-Match'] = etag
|
|
240
|
+
end
|
|
241
|
+
raise StandardError, upload_response.body unless upload_response.success?
|
|
241
242
|
|
|
242
|
-
|
|
243
|
+
upload_response.body[:id]
|
|
244
|
+
end
|
|
243
245
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
listing[:recentChanges] = recent_changes
|
|
246
|
+
def self.get_images(app_id:, edit_id:, language:, image_type:, token:)
|
|
247
|
+
images_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/listings/#{language}/#{image_type}"
|
|
248
|
+
images_response = api_client.get(images_path) do |request|
|
|
249
|
+
request.headers['Authorization'] = "Bearer #{token}"
|
|
250
|
+
end
|
|
251
|
+
raise StandardError, images_response.body unless images_response.success?
|
|
251
252
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
253
|
+
images_response.body
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def self.delete_all_images(app_id:, edit_id:, language:, image_type:, token:)
|
|
257
|
+
images_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/listings/#{language}/#{image_type}"
|
|
258
|
+
etag_response = api_client.get(images_path) do |request|
|
|
259
|
+
request.headers['Authorization'] = "Bearer #{token}"
|
|
259
260
|
end
|
|
261
|
+
raise StandardError, etag_response.body unless etag_response.success?
|
|
262
|
+
|
|
263
|
+
etag = etag_response.headers['Etag']
|
|
264
|
+
delete_response = api_client.delete(images_path) do |request|
|
|
265
|
+
request.headers['Authorization'] = "Bearer #{token}"
|
|
266
|
+
request.headers['If-Match'] = etag
|
|
267
|
+
end
|
|
268
|
+
raise StandardError, delete_response.body unless delete_response.success?
|
|
269
|
+
|
|
260
270
|
nil
|
|
261
271
|
end
|
|
262
272
|
|
|
273
|
+
def self.upload_video(app_id:, edit_id:, language:, video_path:, token:)
|
|
274
|
+
videos_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/listings/#{language}/videos"
|
|
275
|
+
etag_response = api_client.get(videos_path) do |request|
|
|
276
|
+
request.headers['Authorization'] = "Bearer #{token}"
|
|
277
|
+
end
|
|
278
|
+
raise StandardError, etag_response.body unless etag_response.success?
|
|
279
|
+
|
|
280
|
+
etag = etag_response.headers['Etag']
|
|
281
|
+
upload_response = api_client.post(videos_path) do |request|
|
|
282
|
+
request.body = File.binread(video_path)
|
|
283
|
+
request.headers['Content-Length'] = request.body.bytesize.to_s
|
|
284
|
+
request.headers['Content-Type'] = 'application/octet-stream'
|
|
285
|
+
request.headers['Authorization'] = "Bearer #{token}"
|
|
286
|
+
request.headers['If-Match'] = etag
|
|
287
|
+
end
|
|
288
|
+
raise StandardError, upload_response.body unless upload_response.success?
|
|
289
|
+
|
|
290
|
+
upload_response.body[:id]
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def self.update_listing_metadata(app_id:, edit_id:, language:, listing_data:, token:)
|
|
294
|
+
listings_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/listings/#{language}"
|
|
295
|
+
etag_response = api_client.get(listings_path) do |request|
|
|
296
|
+
request.headers['Authorization'] = "Bearer #{token}"
|
|
297
|
+
end
|
|
298
|
+
raise StandardError, etag_response.body unless etag_response.success?
|
|
299
|
+
|
|
300
|
+
etag = etag_response.headers['Etag']
|
|
301
|
+
existing_data = etag_response.body
|
|
302
|
+
merged_data = existing_data.merge(listing_data.compact)
|
|
303
|
+
merged_data[:recentChanges] = '-' if merged_data[:recentChanges].nil? || merged_data[:recentChanges].empty?
|
|
304
|
+
|
|
305
|
+
update_response = api_client.put(listings_path) do |request|
|
|
306
|
+
request.body = merged_data.to_json
|
|
307
|
+
request.headers['Content-Type'] = 'application/json'
|
|
308
|
+
request.headers['Authorization'] = "Bearer #{token}"
|
|
309
|
+
request.headers['If-Match'] = etag
|
|
310
|
+
end
|
|
311
|
+
raise StandardError, update_response.body unless update_response.success?
|
|
312
|
+
|
|
313
|
+
nil
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def self.load_metadata_from_files(metadata_path:, language:)
|
|
317
|
+
lang_path = File.join(metadata_path, language)
|
|
318
|
+
{
|
|
319
|
+
title: read_metadata_file(lang_path, 'title.txt'),
|
|
320
|
+
shortDescription: read_metadata_file(lang_path, 'short_description.txt'),
|
|
321
|
+
fullDescription: read_metadata_file(lang_path, 'full_description.txt')
|
|
322
|
+
}
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def self.read_metadata_file(lang_path, filename)
|
|
326
|
+
path = File.join(lang_path, filename)
|
|
327
|
+
return nil unless File.exist?(path)
|
|
328
|
+
|
|
329
|
+
File.read(path, encoding: 'UTF-8').strip
|
|
330
|
+
end
|
|
331
|
+
private_class_method :read_metadata_file
|
|
332
|
+
|
|
333
|
+
IMAGE_TYPE_MAPPING = {
|
|
334
|
+
'screenshots' => 'phoneScreenshots',
|
|
335
|
+
'small-icons' => 'icon.png',
|
|
336
|
+
'large-icons' => 'large_icon.png',
|
|
337
|
+
'promo-images' => 'featureGraphic.png',
|
|
338
|
+
'firetv-icons' => 'tvBanner.png',
|
|
339
|
+
'firetv-backgrounds' => 'tvBackground.png',
|
|
340
|
+
'firetv-screenshots' => 'tvScreenshots',
|
|
341
|
+
'firetv-featured-backgrounds' => 'tvFeaturedBackground.png',
|
|
342
|
+
'firetv-featured-logos' => 'tvFeaturedLogo.png'
|
|
343
|
+
}.freeze
|
|
344
|
+
|
|
345
|
+
def self.find_images_for_type(metadata_path:, language:, image_type:)
|
|
346
|
+
images_path = File.join(metadata_path, language, 'images')
|
|
347
|
+
mapping = IMAGE_TYPE_MAPPING[image_type]
|
|
348
|
+
return [] if mapping.nil?
|
|
349
|
+
|
|
350
|
+
target_path = File.join(images_path, mapping)
|
|
351
|
+
|
|
352
|
+
if File.directory?(target_path)
|
|
353
|
+
Dir.glob(File.join(target_path, '*.{png,jpg,jpeg}')).sort
|
|
354
|
+
elsif File.exist?(target_path)
|
|
355
|
+
[target_path]
|
|
356
|
+
else
|
|
357
|
+
[]
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
263
361
|
def self.commit_edits(app_id:, edit_id:, token:)
|
|
264
362
|
get_etag_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}"
|
|
265
363
|
etag_response = api_client.get(get_etag_path) do |request|
|
|
@@ -302,18 +400,6 @@ module Fastlane
|
|
|
302
400
|
end
|
|
303
401
|
private_class_method :auth_client
|
|
304
402
|
|
|
305
|
-
def self.find_changelog_for_multiple_version_codes(language:, version_codes:, metadata_path:)
|
|
306
|
-
# Use the highest version code's changelog (same as Fastlane's approach)
|
|
307
|
-
max_version_code = version_codes.max
|
|
308
|
-
UI.message("Using changelog for highest version code: #{max_version_code}")
|
|
309
|
-
find_changelog(
|
|
310
|
-
language: language,
|
|
311
|
-
version_code: max_version_code,
|
|
312
|
-
skip_upload_changelogs: false,
|
|
313
|
-
metadata_path: metadata_path
|
|
314
|
-
)
|
|
315
|
-
end
|
|
316
|
-
|
|
317
403
|
def self.find_changelog(language:, version_code:, skip_upload_changelogs:, metadata_path:)
|
|
318
404
|
# The Amazon appstore requires you to enter changelogs before reviewing.
|
|
319
405
|
# Therefore, if there is no metadata, hyphen text is returned.
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fastlane-plugin-amazon_appstore
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ntsk
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-05-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -47,7 +47,7 @@ dependencies:
|
|
|
47
47
|
version: 1.12.0
|
|
48
48
|
- - "<"
|
|
49
49
|
- !ruby/object:Gem::Version
|
|
50
|
-
version:
|
|
50
|
+
version: 5.0.0
|
|
51
51
|
type: :development
|
|
52
52
|
prerelease: false
|
|
53
53
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -57,7 +57,7 @@ dependencies:
|
|
|
57
57
|
version: 1.12.0
|
|
58
58
|
- - "<"
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
|
-
version:
|
|
60
|
+
version: 5.0.0
|
|
61
61
|
- !ruby/object:Gem::Dependency
|
|
62
62
|
name: fastlane
|
|
63
63
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -215,7 +215,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
215
215
|
requirements:
|
|
216
216
|
- - ">="
|
|
217
217
|
- !ruby/object:Gem::Version
|
|
218
|
-
version: '2.
|
|
218
|
+
version: '2.7'
|
|
219
219
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
220
220
|
requirements:
|
|
221
221
|
- - ">="
|