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: 21d358d66c528f38a3ad99d3b980b4c8e2d57b83e57bfcd5a459384e458f6822
4
- data.tar.gz: 49e5ad56a1c3757f61cc8c8db28904e46ee463eee4d6baf2a18369da56edc1fd
3
+ metadata.gz: 4329f021e36ea416d3dbe494fea656223f7681f01fdaf8c12c067850bd66d042
4
+ data.tar.gz: 5015e8ed07f219d28398b38d4c684db0ddae6342a84270a02cea9c001015041b
5
5
  SHA512:
6
- metadata.gz: 5c7ed8b749f7e73584a502de0603bcf8d11038ea3d20319af5d548f0af753d9438ac8ad3d4c3a0814826469e4bc1a1b068501b837fb58c70e6936f1215e1cf22
7
- data.tar.gz: f2d7560dec9330c9cc110f00efe6b4db5d0a41b4cdf986567a2cee0c3932e2b5edb9ed19bd578e12ff72e68942dd6b649e39ef80318aef3efd35a4f9420c18e7
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
- | skip_upload_changelogs | Whether to skip uploading changelogs | false |
41
- | metadata_path | Path to the directory containing the metadata files | ./fastlane/metadata/android |
42
- | 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 |
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
- | timeout | Timeout for read, open (in seconds) | 300 |
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
- UI.message("Deleting existing edits if needed (overwrite_upload: true)...")
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.delete_edits_if_exists(
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 delete edits (overwrite_upload: true)")
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
- UI.message("Creating new edits...")
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
- if apks.empty?
54
- UI.abort_with_message!("No APK files provided. Please provide either 'apk' or 'apk_paths' parameter")
55
- end
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
- UI.message("Replacing APKs with #{apks.length} file(s)...")
58
- begin
59
- apk_results = Helper::AmazonAppstoreHelper.replace_apks(
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
- # Extract version codes and display results
71
- version_codes = apk_results.map { |result| result[:version_code] }
72
- apk_results.each_with_index do |result, index|
73
- UI.message("Successfully processed APK #{index + 1} with version code: #{result[:version_code]}")
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
- raise StandardError, delete_edits_response.body unless delete_edits_response.success?
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|
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module AmazonAppstore
3
- VERSION = "1.3.0"
3
+ VERSION = "1.5.0"
4
4
  end
5
5
  end
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.3.0
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: 2025-08-24 00:00:00.000000000 Z
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.6'
218
+ version: '2.7'
219
219
  required_rubygems_version: !ruby/object:Gem::Requirement
220
220
  requirements:
221
221
  - - ">="