fastlane-plugin-amazon_appstore 1.2.0 → 1.3.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: fc301533c2007325736e250da85e0efa77f6c24dc9e96575b660252dfe1fdfc1
4
- data.tar.gz: cc8fb420eefe04b69812b2cfa6b0a24b158f90713de4521183d24670022d6b8c
3
+ metadata.gz: 21d358d66c528f38a3ad99d3b980b4c8e2d57b83e57bfcd5a459384e458f6822
4
+ data.tar.gz: 49e5ad56a1c3757f61cc8c8db28904e46ee463eee4d6baf2a18369da56edc1fd
5
5
  SHA512:
6
- metadata.gz: f317b197eef47b6d805949fb5cb89d8e41dd4d10da35d5bd30cbb28d19f766320ad85ce594312b136832761ff83e16615b60ce436fda7b3820aa0c3f04b6ba2a
7
- data.tar.gz: 1ba515e18f5f67027b1843aa5c571bb90d3c9b24db1027d1d8627cbdc9ba5b78cc0650d79d2c5fd70218baa10fc7e7a15a99d8c0bfb16ee291bd41f783aadb05
6
+ metadata.gz: 5c7ed8b749f7e73584a502de0603bcf8d11038ea3d20319af5d548f0af753d9438ac8ad3d4c3a0814826469e4bc1a1b068501b837fb58c70e6936f1215e1cf22
7
+ data.tar.gz: f2d7560dec9330c9cc110f00efe6b4db5d0a41b4cdf986567a2cee0c3932e2b5edb9ed19bd578e12ff72e68942dd6b649e39ef80318aef3efd35a4f9420c18e7
data/README.md CHANGED
@@ -15,8 +15,6 @@ fastlane add_plugin amazon_appstore
15
15
 
16
16
  Upload the apk to the Amazon Appstore using the [App Submission API](https://developer.amazon.com/docs/app-submission-api/overview.html).
17
17
 
18
- In the future, it would be nice to be able to use it to update store information like `upload_to_play_store`, but for now, it only supports replacing apk and submitting it for review.
19
-
20
18
  ## Usage
21
19
 
22
20
  Following the [guide](https://developer.amazon.com/docs/app-submission-api/auth.html), you will need to generate `client_id` and `client_secret` to access the console in advance.
@@ -35,7 +33,8 @@ upload_to_amazon_appstore(
35
33
  | Key | Description | Default |
36
34
  | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------- |
37
35
  | package_name | The package name of the application to use | * |
38
- | apk | Path to the APK file to upload | |
36
+ | apk | Path to the APK file to upload (optional if apk_paths is provided) | |
37
+ | apk_paths | An array of paths to APK files to upload (optional if apk is provided) | |
39
38
  | client_id | The client ID you saved | |
40
39
  | client_secret | The client secret you saved | |
41
40
  | skip_upload_changelogs | Whether to skip uploading changelogs | false |
@@ -48,7 +47,9 @@ upload_to_amazon_appstore(
48
47
  ### Changelogs
49
48
 
50
49
  You can update the release notes by adding a file under `changelogs/` in the same way as [supply](https://docs.fastlane.tools/actions/upload_to_play_store/).
51
- The filename should exactly match the version code of the APK that it represents. You can also provide default notes that will be used if no files match the version code by adding a default.txt file.
50
+ The filename should exactly match the version code of the APK that it represents. You can also provide default notes that will be used if no files match the version code by adding a default.txt file.
51
+
52
+ When uploading multiple APKs with different version codes, the plugin will use the changelog from the highest version code, following the same approach as Fastlane's `upload_to_play_store` action.
52
53
 
53
54
  ```
54
55
  └── fastlane
@@ -46,27 +46,40 @@ module Fastlane
46
46
  end
47
47
  UI.abort_with_message!("Failed to get edit_id") if edit_id.nil?
48
48
 
49
- UI.message("Replacing apk...")
49
+ apks = []
50
+ apks << params[:apk] if params[:apk]
51
+ apks += params[:apk_paths] if params[:apk_paths]
52
+
53
+ if apks.empty?
54
+ UI.abort_with_message!("No APK files provided. Please provide either 'apk' or 'apk_paths' parameter")
55
+ end
56
+
57
+ UI.message("Replacing APKs with #{apks.length} file(s)...")
50
58
  begin
51
- version_code = Helper::AmazonAppstoreHelper.replace_apk(
52
- local_apk_path: params[:apk],
59
+ apk_results = Helper::AmazonAppstoreHelper.replace_apks(
60
+ apk_paths: apks,
53
61
  app_id: params[:package_name],
54
62
  edit_id: edit_id,
55
63
  token: token
56
64
  )
57
65
  rescue StandardError => e
58
66
  UI.error(e.message)
59
- UI.abort_with_message!("Failed to replace apk")
67
+ UI.abort_with_message!("Failed to replace APKs")
68
+ end
69
+
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]}")
60
74
  end
61
- UI.abort_with_message!("Failed to get version_code") if version_code.nil?
62
75
 
63
76
  UI.message("Updating release notes...")
64
77
  begin
65
- Helper::AmazonAppstoreHelper.update_listings(
78
+ Helper::AmazonAppstoreHelper.update_listings_for_multiple_apks(
66
79
  app_id: params[:package_name],
67
80
  edit_id: edit_id,
68
81
  token: token,
69
- version_code: version_code,
82
+ version_codes: version_codes,
70
83
  skip_upload_changelogs: params[:skip_upload_changelogs],
71
84
  metadata_path: params[:metadata_path]
72
85
  )
@@ -132,8 +145,13 @@ module Fastlane
132
145
  FastlaneCore::ConfigItem.new(key: :apk,
133
146
  env_name: "AMAZON_APPSTORE_APK",
134
147
  description: "The path of the apk file",
135
- optional: false,
148
+ optional: true,
136
149
  type: String),
150
+ FastlaneCore::ConfigItem.new(key: :apk_paths,
151
+ env_name: "AMAZON_APPSTORE_APK_PATHS",
152
+ description: "An array of paths to APK files to upload",
153
+ optional: true,
154
+ type: Array),
137
155
  FastlaneCore::ConfigItem.new(key: :skip_upload_changelogs,
138
156
  env_name: "AMAZON_APPSTORE_SKIP_UPLOAD_CHANGELOGS",
139
157
  description: "Whether to skip uploading changelogs",
@@ -65,17 +65,99 @@ module Fastlane
65
65
  raise StandardError, delete_edits_response.body unless delete_edits_response.success?
66
66
  end
67
67
 
68
- def self.replace_apk(local_apk_path:, app_id:, edit_id:, token:)
68
+ def self.upload_apk(local_apk_path:, app_id:, edit_id:, token:)
69
+ upload_apk_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/apks/upload"
70
+ upload_apk_response = api_client.post(upload_apk_path) do |request|
71
+ request.body = Faraday::UploadIO.new(local_apk_path, 'application/vnd.android.package-archive')
72
+ request.headers['Content-Length'] = request.body.stat.size.to_s
73
+ request.headers['Content-Type'] = 'application/vnd.android.package-archive'
74
+ request.headers['Authorization'] = "Bearer #{token}"
75
+ end
76
+ raise StandardError, upload_apk_response.body unless upload_apk_response.success?
77
+
78
+ {
79
+ version_code: upload_apk_response.body[:versionCode],
80
+ apk_id: upload_apk_response.body[:id]
81
+ }
82
+ end
83
+
84
+ def self.replace_apks(apk_paths:, app_id:, edit_id:, token:)
85
+ # Get existing APKs in the edit
69
86
  get_apks_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/apks"
70
87
  get_apks_response = api_client.get(get_apks_path) do |request|
71
88
  request.headers['Authorization'] = "Bearer #{token}"
72
89
  end
73
90
  raise StandardError, get_apks_response.body unless get_apks_response.success?
74
91
 
75
- first_apk = get_apks_response.body[0]
76
- apk_id = first_apk[:id]
77
- raise StandardError, 'apk_id is nil' if apk_id.nil?
92
+ existing_apks = get_apks_response.body
93
+ raise StandardError, 'No existing APKs found in edit' if existing_apks.empty?
94
+
95
+ version_codes = []
96
+ apk_results = []
97
+
98
+ apk_paths.each_with_index do |apk_path, index|
99
+ if index < existing_apks.length
100
+ # Replace existing APK at the specified index
101
+ apk_id = existing_apks[index][:id]
102
+ raise StandardError, "apk_id is nil for index #{index}" if apk_id.nil?
103
+
104
+ # Get ETag for the specific APK
105
+ get_etag_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/apks/#{apk_id}"
106
+ etag_response = api_client.get(get_etag_path) do |request|
107
+ request.headers['Authorization'] = "Bearer #{token}"
108
+ end
109
+ raise StandardError, etag_response.body unless etag_response.success?
78
110
 
111
+ etag = etag_response.headers['Etag']
112
+
113
+ # Replace the APK
114
+ replace_apk_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/apks/#{apk_id}/replace"
115
+ replace_apk_response = api_client.put(replace_apk_path) do |request|
116
+ request.body = Faraday::UploadIO.new(apk_path, 'application/vnd.android.package-archive')
117
+ request.headers['Content-Length'] = request.body.stat.size.to_s
118
+ request.headers['Content-Type'] = 'application/vnd.android.package-archive'
119
+ request.headers['Authorization'] = "Bearer #{token}"
120
+ request.headers['If-Match'] = etag
121
+ end
122
+ raise StandardError, replace_apk_response.body unless replace_apk_response.success?
123
+
124
+ version_code = replace_apk_response.body[:versionCode]
125
+ version_codes << version_code
126
+ apk_results << { version_code: version_code, apk_id: apk_id }
127
+ else
128
+ # Upload new APK if there are more APK paths than existing APKs
129
+ result = upload_apk(
130
+ local_apk_path: apk_path,
131
+ app_id: app_id,
132
+ edit_id: edit_id,
133
+ token: token
134
+ )
135
+ version_codes << result[:version_code]
136
+ apk_results << result
137
+ end
138
+ end
139
+
140
+ # Delete remaining APKs if there are more existing APKs than specified APK paths
141
+ if existing_apks.length > apk_paths.length
142
+ remaining_apks = existing_apks[apk_paths.length..]
143
+ UI.message("Deleting #{remaining_apks.length} remaining APK(s)...")
144
+
145
+ remaining_apks.each_with_index do |apk, index|
146
+ delete_apk(
147
+ app_id: app_id,
148
+ edit_id: edit_id,
149
+ apk_id: apk[:id],
150
+ token: token
151
+ )
152
+ UI.message("Deleted APK ID: #{apk[:id]} (position #{apk_paths.length + index + 1})")
153
+ end
154
+ end
155
+
156
+ apk_results
157
+ end
158
+
159
+ def self.delete_apk(app_id:, edit_id:, apk_id:, token:)
160
+ # Get ETag for the APK to be deleted
79
161
  get_etag_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/apks/#{apk_id}"
80
162
  etag_response = api_client.get(get_etag_path) do |request|
81
163
  request.headers['Authorization'] = "Bearer #{token}"
@@ -84,17 +166,57 @@ module Fastlane
84
166
 
85
167
  etag = etag_response.headers['Etag']
86
168
 
87
- replace_apk_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/apks/#{apk_id}/replace"
88
- replace_apk_response = api_client.put(replace_apk_path) do |request|
89
- request.body = Faraday::UploadIO.new(local_apk_path, 'application/vnd.android.package-archive')
90
- request.headers['Content-Length'] = request.body.stat.size.to_s
91
- request.headers['Content-Type'] = 'application/vnd.android.package-archive'
169
+ # Delete the APK
170
+ delete_apk_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/apks/#{apk_id}"
171
+ delete_apk_response = api_client.delete(delete_apk_path) do |request|
92
172
  request.headers['Authorization'] = "Bearer #{token}"
93
173
  request.headers['If-Match'] = etag
94
174
  end
95
- raise StandardError, replace_apk_response.body unless replace_apk_response.success?
175
+ raise StandardError, delete_apk_response.body unless delete_apk_response.success?
96
176
 
97
- replace_apk_response.body[:versionCode]
177
+ UI.message("Successfully deleted APK #{apk_id}")
178
+ end
179
+
180
+ def self.update_listings_for_multiple_apks(app_id:, edit_id:, token:, version_codes:, skip_upload_changelogs:, metadata_path:)
181
+ return if skip_upload_changelogs
182
+
183
+ UI.message("Updating listings for #{version_codes.length} version codes: #{version_codes.join(', ')}")
184
+
185
+ # Get listings once with ETag
186
+ listings_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/listings"
187
+ listings_response = api_client.get(listings_path) do |request|
188
+ request.headers['Authorization'] = "Bearer #{token}"
189
+ end
190
+ raise StandardError, listings_response.body unless listings_response.success?
191
+
192
+ # Process each language once
193
+ listings_response.body[:listings].each do |lang, listing|
194
+ # Get fresh ETag for each language update to avoid conflicts
195
+ etag_response = api_client.get(listings_path) do |request|
196
+ request.headers['Authorization'] = "Bearer #{token}"
197
+ end
198
+ raise StandardError, etag_response.body unless etag_response.success?
199
+
200
+ etag = etag_response.headers['Etag']
201
+
202
+ # Find the best changelog for multiple version codes
203
+ recent_changes = find_changelog_for_multiple_version_codes(
204
+ language: listing[:language],
205
+ version_codes: version_codes,
206
+ metadata_path: metadata_path
207
+ )
208
+ listing[:recentChanges] = recent_changes
209
+
210
+ # Update listings once per language
211
+ update_listings_path = "api/appstore/v1/applications/#{app_id}/edits/#{edit_id}/listings/#{lang}"
212
+ update_listings_response = api_client.put(update_listings_path) do |request|
213
+ request.body = listing.to_json
214
+ request.headers['Authorization'] = "Bearer #{token}"
215
+ request.headers['If-Match'] = etag
216
+ end
217
+ raise StandardError, update_listings_response.body unless update_listings_response.success?
218
+ end
219
+ nil
98
220
  end
99
221
 
100
222
  def self.update_listings(app_id:, edit_id:, token:, version_code:, skip_upload_changelogs:, metadata_path:)
@@ -173,6 +295,18 @@ module Fastlane
173
295
  end
174
296
  private_class_method :auth_client
175
297
 
298
+ def self.find_changelog_for_multiple_version_codes(language:, version_codes:, metadata_path:)
299
+ # Use the highest version code's changelog (same as Fastlane's approach)
300
+ max_version_code = version_codes.max
301
+ UI.message("Using changelog for highest version code: #{max_version_code}")
302
+ find_changelog(
303
+ language: language,
304
+ version_code: max_version_code,
305
+ skip_upload_changelogs: false,
306
+ metadata_path: metadata_path
307
+ )
308
+ end
309
+
176
310
  def self.find_changelog(language:, version_code:, skip_upload_changelogs:, metadata_path:)
177
311
  # The Amazon appstore requires you to enter changelogs before reviewing.
178
312
  # 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.2.0"
3
+ VERSION = "1.3.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.2.0
4
+ version: 1.3.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-07-19 00:00:00.000000000 Z
11
+ date: 2025-08-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -222,7 +222,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
222
222
  - !ruby/object:Gem::Version
223
223
  version: '0'
224
224
  requirements: []
225
- rubygems_version: 3.5.9
225
+ rubygems_version: 3.5.22
226
226
  signing_key:
227
227
  specification_version: 4
228
228
  summary: Upload apps to Amazon Appstore