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: 42700d0bc41d91c1a7cebc2f4d5583f703b2532ab72581f6a96e3c2e1a9508e8
4
- data.tar.gz: 0b180a4dafa671749b7f3b95cac6c6991e602230d0aab9f0ce979fa281ecdd92
3
+ metadata.gz: fe05c393d60b47ae7056bba08ebecb83c0938413d9773fafbf4523561a3d80cb
4
+ data.tar.gz: 7ffcd28240b237ebc7d30b9c865b4521662bdd28a9cd33613087f6d7765ed558
5
5
  SHA512:
6
- metadata.gz: 2157e233420340003117f553e20187bff0ab2a66eda203188dd292529e0667f3c77e4ba80a62e3f30e7ba85684ddf787385d2fd92a9d7412f4ba7456d0b9616e
7
- data.tar.gz: 2973cac2d292082f30358107c7017ea7e9efda6d477a320f16d4797022503b98c01acbb2b9b9a5f8d28abc7d07834bfffa1f03d333b5e7945571de05a07e8d4d
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
- | 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
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
- apks = []
66
- apks << params[:apk] if params[:apk]
67
- apks += params[:apk_paths] if params[:apk_paths]
65
+ version_codes = []
68
66
 
69
- if apks.empty?
70
- UI.abort_with_message!("No APK files provided. Please provide either 'apk' or 'apk_paths' parameter")
71
- 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]
72
71
 
73
- UI.message("Replacing APKs with #{apks.length} file(s)...")
74
- begin
75
- apk_results = Helper::AmazonAppstoreHelper.replace_apks(
76
- apk_paths: apks,
77
- app_id: params[:package_name],
78
- edit_id: edit_id,
79
- token: token
80
- )
81
- rescue StandardError => e
82
- UI.error(e.message)
83
- UI.abort_with_message!("Failed to replace APKs")
84
- end
85
- # Extract version codes and display results
86
- version_codes = apk_results.map { |result| result[:version_code] }
87
- apk_results.each_with_index do |result, index|
88
- UI.message("Successfully processed APK #{index + 1} with version code: #{result[:version_code]}")
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.update_listings_for_multiple_apks(
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 listings")
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.update_listings_for_multiple_apks(app_id:, edit_id:, token:, version_codes:, skip_upload_changelogs:, metadata_path:)
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
- UI.message("Updating listings for #{version_codes.length} version codes: #{version_codes.join(', ')}")
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
- # Find the best changelog for multiple version codes
210
- recent_changes = find_changelog_for_multiple_version_codes(
207
+ listing[:recentChanges] = find_changelog(
211
208
  language: listing[:language],
212
- version_codes: version_codes,
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.update_listings(app_id:, edit_id:, token:, version_code:, skip_upload_changelogs:, metadata_path:)
230
- listings_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/listings"
231
- listings_response = api_client.get(listings_path) do |request|
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, listings_response.body unless listings_response.success?
230
+ raise StandardError, etag_response.body unless etag_response.success?
235
231
 
236
- listings_response.body[:listings].each do |lang, listing|
237
- etag_response = api_client.get(listings_path) do |request|
238
- request.headers['Authorization'] = "Bearer #{token}"
239
- end
240
- raise StandardError, etag_response.body unless etag_response.success?
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
- etag = etag_response.headers['Etag']
243
+ upload_response.body[:id]
244
+ end
243
245
 
244
- recent_changes = find_changelog(
245
- language: listing[:language],
246
- version_code: version_code,
247
- skip_upload_changelogs: skip_upload_changelogs,
248
- metadata_path: metadata_path
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
- update_listings_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/listings/#{lang}"
253
- update_listings_response = api_client.put(update_listings_path) do |request|
254
- request.body = listing.to_json
255
- request.headers['Authorization'] = "Bearer #{token}"
256
- request.headers['If-Match'] = etag
257
- end
258
- raise StandardError, update_listings_response.body unless update_listings_response.success?
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.
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module AmazonAppstore
3
- VERSION = "1.4.0"
3
+ VERSION = "1.6.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.4.0
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: 2025-08-30 00:00:00.000000000 Z
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: 3.0.0
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: 3.0.0
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.6'
218
+ version: '2.7'
219
219
  required_rubygems_version: !ruby/object:Gem::Requirement
220
220
  requirements:
221
221
  - - ">="