fastlane-plugin-amazon_appstore 1.3.0 → 1.5.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: 4329f021e36ea416d3dbe494fea656223f7681f01fdaf8c12c067850bd66d042
|
|
4
|
+
data.tar.gz: 5015e8ed07f219d28398b38d4c684db0ddae6342a84270a02cea9c001015041b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4b213a4b4855aa766bb8a5bd789a1c8cfae4ea1a6786d66986828442672b96030b45bc881bb30e8bcd513437396229f004ababe31c6b817b399f4cc23d9eb117
|
|
7
|
+
data.tar.gz: 5bee681d6820d171c9a09c33da5ac3306e3583156748128684c88596d69159a5b4e1858a9a2adf3f90c68a24264cb6f1c06eb56750aaac4fcda4972e8e490fd0
|
data/README.md
CHANGED
|
@@ -30,18 +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 |
|
|
49
|
+
| timeout | Timeout for read, open (in seconds) | 300 |
|
|
45
50
|
* = default value is dependent on the user's system
|
|
46
51
|
|
|
47
52
|
### Changelogs
|
|
@@ -69,6 +74,74 @@ When uploading multiple APKs with different version codes, the plugin will use t
|
|
|
69
74
|
One difference from Google Play is that the Amazon Appstore always requires release notes to be entered before review.
|
|
70
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.
|
|
71
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
|
+
|
|
72
145
|
## Run tests for this plugin
|
|
73
146
|
|
|
74
147
|
To run both the tests, and code style validation, run
|
|
@@ -22,55 +22,73 @@ module Fastlane
|
|
|
22
22
|
UI.abort_with_message!("Failed to get token") if token.nil?
|
|
23
23
|
|
|
24
24
|
if params[:overwrite_upload]
|
|
25
|
-
|
|
25
|
+
if params[:overwrite_upload_mode] == "new"
|
|
26
|
+
UI.message("Deleting existing edits if needed (overwrite_upload: true, overwrite_upload_mode: new)...")
|
|
27
|
+
begin
|
|
28
|
+
Helper::AmazonAppstoreHelper.delete_edits_if_exists(
|
|
29
|
+
app_id: params[:package_name],
|
|
30
|
+
token: token
|
|
31
|
+
)
|
|
32
|
+
rescue StandardError => e
|
|
33
|
+
UI.error(e.message)
|
|
34
|
+
UI.abort_with_message!("Failed to delete edits (overwrite_upload: true, overwrite_upload_mode: new)")
|
|
35
|
+
end
|
|
36
|
+
elsif params[:overwrite_upload_mode] == "reuse"
|
|
37
|
+
UI.message("Retrieving active edit (overwrite_upload: true, overwrite_upload_mode: reuse)...")
|
|
38
|
+
begin
|
|
39
|
+
edit_id, = Helper::AmazonAppstoreHelper.get_edits(
|
|
40
|
+
app_id: params[:package_name],
|
|
41
|
+
token: token
|
|
42
|
+
)
|
|
43
|
+
rescue StandardError => e
|
|
44
|
+
UI.error(e.message)
|
|
45
|
+
UI.abort_with_message!("Failed to get edit_id (overwrite_upload: true, overwrite_upload_mode: reuse)")
|
|
46
|
+
end
|
|
47
|
+
UI.message("No active edit") if edit_id.nil?
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
if edit_id.nil?
|
|
52
|
+
UI.message("Creating new edits...")
|
|
26
53
|
begin
|
|
27
|
-
Helper::AmazonAppstoreHelper.
|
|
54
|
+
edit_id = Helper::AmazonAppstoreHelper.create_edits(
|
|
28
55
|
app_id: params[:package_name],
|
|
29
56
|
token: token
|
|
30
57
|
)
|
|
31
58
|
rescue StandardError => e
|
|
32
59
|
UI.error(e.message)
|
|
33
|
-
UI.abort_with_message!("Failed to
|
|
60
|
+
UI.abort_with_message!("Failed to create edits")
|
|
34
61
|
end
|
|
62
|
+
UI.abort_with_message!("Failed to get edit_id") if edit_id.nil?
|
|
35
63
|
end
|
|
36
64
|
|
|
37
|
-
|
|
38
|
-
begin
|
|
39
|
-
edit_id = Helper::AmazonAppstoreHelper.create_edits(
|
|
40
|
-
app_id: params[:package_name],
|
|
41
|
-
token: token
|
|
42
|
-
)
|
|
43
|
-
rescue StandardError => e
|
|
44
|
-
UI.error(e.message)
|
|
45
|
-
UI.abort_with_message!("Failed to create edits")
|
|
46
|
-
end
|
|
47
|
-
UI.abort_with_message!("Failed to get edit_id") if edit_id.nil?
|
|
48
|
-
|
|
49
|
-
apks = []
|
|
50
|
-
apks << params[:apk] if params[:apk]
|
|
51
|
-
apks += params[:apk_paths] if params[:apk_paths]
|
|
65
|
+
version_codes = []
|
|
52
66
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
67
|
+
unless params[:skip_upload_apk]
|
|
68
|
+
apks = []
|
|
69
|
+
apks << params[:apk] if params[:apk]
|
|
70
|
+
apks += params[:apk_paths] if params[:apk_paths]
|
|
56
71
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
apk_paths: apks,
|
|
61
|
-
app_id: params[:package_name],
|
|
62
|
-
edit_id: edit_id,
|
|
63
|
-
token: token
|
|
64
|
-
)
|
|
65
|
-
rescue StandardError => e
|
|
66
|
-
UI.error(e.message)
|
|
67
|
-
UI.abort_with_message!("Failed to replace APKs")
|
|
68
|
-
end
|
|
72
|
+
if apks.empty?
|
|
73
|
+
UI.abort_with_message!("No APK files provided. Please provide either 'apk' or 'apk_paths' parameter")
|
|
74
|
+
end
|
|
69
75
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
74
92
|
end
|
|
75
93
|
|
|
76
94
|
UI.message("Updating release notes...")
|
|
@@ -88,6 +106,10 @@ module Fastlane
|
|
|
88
106
|
UI.abort_with_message!("Failed to update listings")
|
|
89
107
|
end
|
|
90
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
|
+
|
|
91
113
|
if params[:changes_not_sent_for_review]
|
|
92
114
|
UI.success('Successfully finished the upload to Amazon Appstore')
|
|
93
115
|
return
|
|
@@ -108,6 +130,89 @@ module Fastlane
|
|
|
108
130
|
UI.success('Successfully finished the upload to Amazon Appstore')
|
|
109
131
|
end
|
|
110
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
|
+
|
|
111
216
|
def self.description
|
|
112
217
|
"Upload apps to Amazon Appstore"
|
|
113
218
|
end
|
|
@@ -152,12 +257,36 @@ module Fastlane
|
|
|
152
257
|
description: "An array of paths to APK files to upload",
|
|
153
258
|
optional: true,
|
|
154
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),
|
|
155
272
|
FastlaneCore::ConfigItem.new(key: :skip_upload_changelogs,
|
|
156
273
|
env_name: "AMAZON_APPSTORE_SKIP_UPLOAD_CHANGELOGS",
|
|
157
274
|
description: "Whether to skip uploading changelogs",
|
|
158
275
|
default_value: false,
|
|
159
276
|
optional: true,
|
|
160
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),
|
|
161
290
|
FastlaneCore::ConfigItem.new(key: :metadata_path,
|
|
162
291
|
env_name: "AMAZON_APPSTORE_METADATA_PATH",
|
|
163
292
|
description: "Path to the directory containing the metadata files",
|
|
@@ -176,6 +305,13 @@ module Fastlane
|
|
|
176
305
|
default_value: false,
|
|
177
306
|
optional: true,
|
|
178
307
|
type: Boolean),
|
|
308
|
+
FastlaneCore::ConfigItem.new(key: :overwrite_upload_mode,
|
|
309
|
+
env_name: "AMAZON_APPSTORE_OVERWRITE_UPLOAD_MODE",
|
|
310
|
+
description: "Upload strategy. Can be 'new' or 'reuse'",
|
|
311
|
+
default_value: 'new',
|
|
312
|
+
verify_block: proc do |value|
|
|
313
|
+
UI.user_error!("overwrite_upload can only be 'new' or 'reuse'") unless %w(new reuse).include?(value)
|
|
314
|
+
end),
|
|
179
315
|
FastlaneCore::ConfigItem.new(key: :timeout,
|
|
180
316
|
env_name: "AMAZON_APPSTORE_TIMEOUT",
|
|
181
317
|
description: "Timeout for read, open (in seconds)",
|
|
@@ -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
|
|
|
@@ -47,6 +47,19 @@ module Fastlane
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def self.delete_edits_if_exists(app_id:, token:)
|
|
50
|
+
edits_id, etag = self.get_edits(app_id: app_id, token: token)
|
|
51
|
+
return nil if edits_id.nil? || etag.nil? # Do nothing if edits do not exist
|
|
52
|
+
|
|
53
|
+
edits_path = "api/appstore/v1/applications/#{app_id}/edits"
|
|
54
|
+
delete_edits_response = api_client.delete("#{edits_path}/#{edits_id}") do |request|
|
|
55
|
+
request.headers['Authorization'] = "Bearer #{token}"
|
|
56
|
+
request.headers['If-Match'] = etag
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
raise StandardError, delete_edits_response.body unless delete_edits_response.success?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.get_edits(app_id:, token:)
|
|
50
63
|
edits_path = "api/appstore/v1/applications/#{app_id}/edits"
|
|
51
64
|
edits_response = api_client.get(edits_path) do |request|
|
|
52
65
|
request.headers['Authorization'] = "Bearer #{token}"
|
|
@@ -55,14 +68,8 @@ module Fastlane
|
|
|
55
68
|
|
|
56
69
|
edits_id = edits_response.body[:id]
|
|
57
70
|
etag = edits_response.headers['Etag']
|
|
58
|
-
return nil if edits_id.nil? || etag.nil? # Do nothing if edits do not exist
|
|
59
|
-
|
|
60
|
-
delete_edits_response = api_client.delete("#{edits_path}/#{edits_id}") do |request|
|
|
61
|
-
request.headers['Authorization'] = "Bearer #{token}"
|
|
62
|
-
request.headers['If-Match'] = etag
|
|
63
|
-
end
|
|
64
71
|
|
|
65
|
-
|
|
72
|
+
return edits_id, etag
|
|
66
73
|
end
|
|
67
74
|
|
|
68
75
|
def self.upload_apk(local_apk_path:, app_id:, edit_id:, token:)
|
|
@@ -179,6 +186,7 @@ module Fastlane
|
|
|
179
186
|
|
|
180
187
|
def self.update_listings_for_multiple_apks(app_id:, edit_id:, token:, version_codes:, skip_upload_changelogs:, metadata_path:)
|
|
181
188
|
return if skip_upload_changelogs
|
|
189
|
+
return if version_codes.empty?
|
|
182
190
|
|
|
183
191
|
UI.message("Updating listings for #{version_codes.length} version codes: #{version_codes.join(', ')}")
|
|
184
192
|
|
|
@@ -253,6 +261,142 @@ module Fastlane
|
|
|
253
261
|
nil
|
|
254
262
|
end
|
|
255
263
|
|
|
264
|
+
def self.upload_image(app_id:, edit_id:, language:, image_type:, image_path:, token:)
|
|
265
|
+
images_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/listings/#{language}/#{image_type}"
|
|
266
|
+
etag_response = api_client.get(images_path) do |request|
|
|
267
|
+
request.headers['Authorization'] = "Bearer #{token}"
|
|
268
|
+
end
|
|
269
|
+
raise StandardError, etag_response.body unless etag_response.success?
|
|
270
|
+
|
|
271
|
+
etag = etag_response.headers['Etag']
|
|
272
|
+
upload_path = "#{images_path}/upload"
|
|
273
|
+
upload_response = api_client.post(upload_path) do |request|
|
|
274
|
+
request.body = File.binread(image_path)
|
|
275
|
+
request.headers['Content-Length'] = request.body.bytesize.to_s
|
|
276
|
+
request.headers['Content-Type'] = 'application/octet-stream'
|
|
277
|
+
request.headers['Authorization'] = "Bearer #{token}"
|
|
278
|
+
request.headers['If-Match'] = etag
|
|
279
|
+
end
|
|
280
|
+
raise StandardError, upload_response.body unless upload_response.success?
|
|
281
|
+
|
|
282
|
+
upload_response.body[:id]
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def self.get_images(app_id:, edit_id:, language:, image_type:, token:)
|
|
286
|
+
images_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/listings/#{language}/#{image_type}"
|
|
287
|
+
images_response = api_client.get(images_path) do |request|
|
|
288
|
+
request.headers['Authorization'] = "Bearer #{token}"
|
|
289
|
+
end
|
|
290
|
+
raise StandardError, images_response.body unless images_response.success?
|
|
291
|
+
|
|
292
|
+
images_response.body
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def self.delete_all_images(app_id:, edit_id:, language:, image_type:, token:)
|
|
296
|
+
images_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/listings/#{language}/#{image_type}"
|
|
297
|
+
etag_response = api_client.get(images_path) do |request|
|
|
298
|
+
request.headers['Authorization'] = "Bearer #{token}"
|
|
299
|
+
end
|
|
300
|
+
raise StandardError, etag_response.body unless etag_response.success?
|
|
301
|
+
|
|
302
|
+
etag = etag_response.headers['Etag']
|
|
303
|
+
delete_response = api_client.delete(images_path) do |request|
|
|
304
|
+
request.headers['Authorization'] = "Bearer #{token}"
|
|
305
|
+
request.headers['If-Match'] = etag
|
|
306
|
+
end
|
|
307
|
+
raise StandardError, delete_response.body unless delete_response.success?
|
|
308
|
+
|
|
309
|
+
nil
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def self.upload_video(app_id:, edit_id:, language:, video_path:, token:)
|
|
313
|
+
videos_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/listings/#{language}/videos"
|
|
314
|
+
etag_response = api_client.get(videos_path) do |request|
|
|
315
|
+
request.headers['Authorization'] = "Bearer #{token}"
|
|
316
|
+
end
|
|
317
|
+
raise StandardError, etag_response.body unless etag_response.success?
|
|
318
|
+
|
|
319
|
+
etag = etag_response.headers['Etag']
|
|
320
|
+
upload_response = api_client.post(videos_path) do |request|
|
|
321
|
+
request.body = File.binread(video_path)
|
|
322
|
+
request.headers['Content-Length'] = request.body.bytesize.to_s
|
|
323
|
+
request.headers['Content-Type'] = 'application/octet-stream'
|
|
324
|
+
request.headers['Authorization'] = "Bearer #{token}"
|
|
325
|
+
request.headers['If-Match'] = etag
|
|
326
|
+
end
|
|
327
|
+
raise StandardError, upload_response.body unless upload_response.success?
|
|
328
|
+
|
|
329
|
+
upload_response.body[:id]
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def self.update_listing_metadata(app_id:, edit_id:, language:, listing_data:, token:)
|
|
333
|
+
listings_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/listings/#{language}"
|
|
334
|
+
etag_response = api_client.get(listings_path) do |request|
|
|
335
|
+
request.headers['Authorization'] = "Bearer #{token}"
|
|
336
|
+
end
|
|
337
|
+
raise StandardError, etag_response.body unless etag_response.success?
|
|
338
|
+
|
|
339
|
+
etag = etag_response.headers['Etag']
|
|
340
|
+
existing_data = etag_response.body
|
|
341
|
+
merged_data = existing_data.merge(listing_data.compact)
|
|
342
|
+
merged_data[:recentChanges] = '-' if merged_data[:recentChanges].nil? || merged_data[:recentChanges].empty?
|
|
343
|
+
|
|
344
|
+
update_response = api_client.put(listings_path) do |request|
|
|
345
|
+
request.body = merged_data.to_json
|
|
346
|
+
request.headers['Content-Type'] = 'application/json'
|
|
347
|
+
request.headers['Authorization'] = "Bearer #{token}"
|
|
348
|
+
request.headers['If-Match'] = etag
|
|
349
|
+
end
|
|
350
|
+
raise StandardError, update_response.body unless update_response.success?
|
|
351
|
+
|
|
352
|
+
nil
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def self.load_metadata_from_files(metadata_path:, language:)
|
|
356
|
+
lang_path = File.join(metadata_path, language)
|
|
357
|
+
{
|
|
358
|
+
title: read_metadata_file(lang_path, 'title.txt'),
|
|
359
|
+
shortDescription: read_metadata_file(lang_path, 'short_description.txt'),
|
|
360
|
+
fullDescription: read_metadata_file(lang_path, 'full_description.txt')
|
|
361
|
+
}
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def self.read_metadata_file(lang_path, filename)
|
|
365
|
+
path = File.join(lang_path, filename)
|
|
366
|
+
return nil unless File.exist?(path)
|
|
367
|
+
|
|
368
|
+
File.read(path, encoding: 'UTF-8').strip
|
|
369
|
+
end
|
|
370
|
+
private_class_method :read_metadata_file
|
|
371
|
+
|
|
372
|
+
IMAGE_TYPE_MAPPING = {
|
|
373
|
+
'screenshots' => 'phoneScreenshots',
|
|
374
|
+
'small-icons' => 'icon.png',
|
|
375
|
+
'large-icons' => 'large_icon.png',
|
|
376
|
+
'promo-images' => 'featureGraphic.png',
|
|
377
|
+
'firetv-icons' => 'tvBanner.png',
|
|
378
|
+
'firetv-backgrounds' => 'tvBackground.png',
|
|
379
|
+
'firetv-screenshots' => 'tvScreenshots',
|
|
380
|
+
'firetv-featured-backgrounds' => 'tvFeaturedBackground.png',
|
|
381
|
+
'firetv-featured-logos' => 'tvFeaturedLogo.png'
|
|
382
|
+
}.freeze
|
|
383
|
+
|
|
384
|
+
def self.find_images_for_type(metadata_path:, language:, image_type:)
|
|
385
|
+
images_path = File.join(metadata_path, language, 'images')
|
|
386
|
+
mapping = IMAGE_TYPE_MAPPING[image_type]
|
|
387
|
+
return [] if mapping.nil?
|
|
388
|
+
|
|
389
|
+
target_path = File.join(images_path, mapping)
|
|
390
|
+
|
|
391
|
+
if File.directory?(target_path)
|
|
392
|
+
Dir.glob(File.join(target_path, '*.{png,jpg,jpeg}')).sort
|
|
393
|
+
elsif File.exist?(target_path)
|
|
394
|
+
[target_path]
|
|
395
|
+
else
|
|
396
|
+
[]
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
256
400
|
def self.commit_edits(app_id:, edit_id:, token:)
|
|
257
401
|
get_etag_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}"
|
|
258
402
|
etag_response = api_client.get(get_etag_path) do |request|
|
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.5.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-03-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -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
|
- - ">="
|