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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 21d358d66c528f38a3ad99d3b980b4c8e2d57b83e57bfcd5a459384e458f6822
|
4
|
+
data.tar.gz: 49e5ad56a1c3757f61cc8c8db28904e46ee463eee4d6baf2a18369da56edc1fd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
52
|
-
|
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
|
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.
|
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
|
-
|
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:
|
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.
|
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
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
88
|
-
|
89
|
-
|
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,
|
175
|
+
raise StandardError, delete_apk_response.body unless delete_apk_response.success?
|
96
176
|
|
97
|
-
|
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.
|
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.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-
|
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.
|
225
|
+
rubygems_version: 3.5.22
|
226
226
|
signing_key:
|
227
227
|
specification_version: 4
|
228
228
|
summary: Upload apps to Amazon Appstore
|