fastlane-plugin-lazurite 0.1.0.beta.1
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +66 -0
- data/lib/fastlane/plugin/lazurite/actions/rustore_credentials_action.rb +83 -0
- data/lib/fastlane/plugin/lazurite/actions/upload_to_rustore_action.rb +310 -0
- data/lib/fastlane/plugin/lazurite/helper/config.rb +40 -0
- data/lib/fastlane/plugin/lazurite/helper/enums.rb +80 -0
- data/lib/fastlane/plugin/lazurite/helper/uploader.rb +214 -0
- data/lib/fastlane/plugin/lazurite/version.rb +5 -0
- data/lib/fastlane/plugin/rustore.rb +16 -0
- metadata +150 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e9dc6aa238669fce3d0ca63a9dfb4e5447e4d61f73d1ea23dc84210cf36abb4b
|
4
|
+
data.tar.gz: 611fa67d879ad86ec9b006a3e0cad6e007aa0841265018cecc4321fe9b77e84a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0ae50a59b5c7d41b80b1471bc65705df7a6cdc9ea6ad984c2be1c54a0302920a15f01d6cb8e5f852cb8d592982a11a007d87b00520f883aecc11dfa1051dc6e0
|
7
|
+
data.tar.gz: d80df8c63ccfe88954fea80b66029422e3bedd5523c7789ecf57dd817bcafb83e6e62af0c7819f1298d594a975a4c1ba76d67ae5a6469d38c25511364318e31a
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2023 CheeryLee
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# Lazurite
|
2
|
+
|
3
|
+
<p align="center">
|
4
|
+
<a href="https://opensource.org/licenses/MIT">
|
5
|
+
<img alt="Gem" src="https://img.shields.io/badge/License-MIT-yellow.svg">
|
6
|
+
</a>
|
7
|
+
<a href="https://github.com/CheeryLee/fastlane-plugin-lazurite/releases">
|
8
|
+
<img alt="Gem" src="https://img.shields.io/github/v/release/CheeryLee/fastlane-plugin-lazurite?display_name=tag&include_prereleases">
|
9
|
+
</a>
|
10
|
+
<img alt="Work in progress" src="https://img.shields.io/badge/stability-work_in_progress-lightgrey.svg">
|
11
|
+
</p>
|
12
|
+
|
13
|
+
<br/>
|
14
|
+
<p align="center">Плагин Fastlane для работы с развертыванием Android-приложений в <b>RuStore</b></p>
|
15
|
+
<br/>
|
16
|
+
|
17
|
+
## Возможности
|
18
|
+
Данный плагин предлагает широкий функционал:
|
19
|
+
* заливка пакета в магазин;
|
20
|
+
* добавление скриншотов и иконки;
|
21
|
+
* изменение названия и описания приложения;
|
22
|
+
* выбор категории и типа приложения;
|
23
|
+
* непрерывная интеграция за счет автоматического удаления черновиков, не прошедших модерацию.
|
24
|
+
|
25
|
+
|
26
|
+
## Установка
|
27
|
+
|
28
|
+
Чтобы начать работу, добавьте ссылку на плагин в своем файле `fastlane/Pluginfile`:
|
29
|
+
```ruby
|
30
|
+
gem "fastlane-plugin-lazurite", git: "https://github.com/CheeryLee/fastlane-plugin-lazurite.git"
|
31
|
+
```
|
32
|
+
|
33
|
+
## Использование
|
34
|
+
### Получение информации
|
35
|
+
После установки вы можете вызвать следующую команду для получения дополнительной информации о каждом action:
|
36
|
+
```shell
|
37
|
+
fastlane action upload_to_rustore # или любой другой action
|
38
|
+
```
|
39
|
+
|
40
|
+
### Action-ы
|
41
|
+
Ниже представлен список всех доступных action-ов. Чтобы узнать подробнее о каждом, нажмите на соответствующее поле в таблице.
|
42
|
+
Список сгруппирован по категории Fastlane.
|
43
|
+
|
44
|
+
#### 📦 Production
|
45
|
+
|
46
|
+
| Аргумент | Описание |
|
47
|
+
|--------------------------------------------------------|---------------------------------|
|
48
|
+
| [upload_to_rustore](docs/actions/upload_to_rustore.md) | Загружает новый пакет в RuStore |
|
49
|
+
#### 🔆 Misc
|
50
|
+
|
51
|
+
| Аргумент | Описание |
|
52
|
+
|------------------------------------------------------------|----------------------------------|
|
53
|
+
| [rustore_credentials](docs/actions/rustore_credentials.md) | Настраивает данные доступа к API |
|
54
|
+
|
55
|
+
## Обратная связь
|
56
|
+
|
57
|
+
По всем вопросам, связанным с данным проектом, используйте [GitHub issue](https://github.com/CheeryLee/fastlane-plugin-lazurite/issues).
|
58
|
+
|
59
|
+
## Решение проблем
|
60
|
+
|
61
|
+
Если остались какие-то вопросы касательно работы с плагинами, загляните на эту страницу:
|
62
|
+
[Plugins Troubleshooting](https://docs.fastlane.tools/plugins/plugins-troubleshooting/).
|
63
|
+
|
64
|
+
## Лицензия
|
65
|
+
|
66
|
+
Проект лицензирован под [MIT лицензией](LICENSE).
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require "fastlane/action"
|
2
|
+
require_relative "../helper/uploader"
|
3
|
+
|
4
|
+
module Fastlane
|
5
|
+
module Actions
|
6
|
+
class RustoreCredentialsAction < Action
|
7
|
+
def self.run(params)
|
8
|
+
private_key = ""
|
9
|
+
|
10
|
+
if params.values.include?(:private_key_file) && !params[:private_key_file].nil?
|
11
|
+
private_key = File.read(File.expand_path(params[:private_key_file]))
|
12
|
+
elsif params.values.include?(:private_key) && !params[:private_key].nil?
|
13
|
+
private_key = params[:private_key]
|
14
|
+
else
|
15
|
+
UI.user_error!("You need to provide the private API key")
|
16
|
+
end
|
17
|
+
|
18
|
+
private_key = private_key.strip
|
19
|
+
Helper::Uploader.authorize(params[:company_id], private_key)
|
20
|
+
UI.success("Credentials for RuStore account are successfully saved for further actions")
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.description
|
24
|
+
"Sets RuStore account credentials needed for authorization"
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.authors
|
28
|
+
["CheeryLee"]
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.is_supported?(platform)
|
32
|
+
[:android].include?(platform)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.available_options
|
36
|
+
[
|
37
|
+
FastlaneCore::ConfigItem.new(
|
38
|
+
key: :company_id,
|
39
|
+
description: "ID of RuStore company",
|
40
|
+
optional: false,
|
41
|
+
type: String,
|
42
|
+
verify_block: proc do |value|
|
43
|
+
UI.user_error!("Company ID can't be empty") unless value && !value.empty?
|
44
|
+
end
|
45
|
+
),
|
46
|
+
FastlaneCore::ConfigItem.new(
|
47
|
+
key: :private_key_file,
|
48
|
+
description: [
|
49
|
+
"The path to file where private API key is located.",
|
50
|
+
"Either use it or provide the raw key content with `private_key` option"
|
51
|
+
].join("\n"),
|
52
|
+
optional: true,
|
53
|
+
conflicting_options: [:private_key],
|
54
|
+
type: String,
|
55
|
+
verify_block: proc do |value|
|
56
|
+
UI.user_error!("Path to the private API key file is invalid") unless value && !value.empty?
|
57
|
+
UI.user_error!("There is no any file that provided in API key file path") unless
|
58
|
+
File.exist?(File.expand_path(value))
|
59
|
+
end
|
60
|
+
),
|
61
|
+
FastlaneCore::ConfigItem.new(
|
62
|
+
key: :private_key,
|
63
|
+
description: [
|
64
|
+
"The private API key content.",
|
65
|
+
"Either use it or provide the key content in a file with `private_key_file` option"
|
66
|
+
].join("\n"),
|
67
|
+
optional: true,
|
68
|
+
conflicting_options: [:private_key_file],
|
69
|
+
sensitive: true,
|
70
|
+
type: String,
|
71
|
+
verify_block: proc do |value|
|
72
|
+
UI.user_error!("API key can't be empty") unless value && !value.empty?
|
73
|
+
end
|
74
|
+
)
|
75
|
+
]
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.category
|
79
|
+
:misc
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,310 @@
|
|
1
|
+
require "fastlane/action"
|
2
|
+
require_relative "../helper/enums"
|
3
|
+
|
4
|
+
module Fastlane
|
5
|
+
module Actions
|
6
|
+
class UploadToRustoreAction < Action
|
7
|
+
SHORT_DESCRIPTION_LENGTH = 80
|
8
|
+
FULL_DESCRIPTION_LENGTH = 4000
|
9
|
+
CHANGELOG_LENGTH = 500
|
10
|
+
SCREENSHOTS_COUNT = 10
|
11
|
+
APP_CATEGORIES_COUNT = 2
|
12
|
+
PACKAGES_COUNT = 10
|
13
|
+
|
14
|
+
def self.run(params)
|
15
|
+
UI.user_error!("Publish time must be provided while uploading with DELAYED type of publishing") if
|
16
|
+
params.values[:publish_type] == Helper::PublishType::DELAYED && params.values[:publish_date_time].nil?
|
17
|
+
UI.user_error!("App categories validation failed. Check the errors above.") unless validate_app_categories(params)
|
18
|
+
|
19
|
+
version = create_draft(params)
|
20
|
+
upload_apk(params, version)
|
21
|
+
upload_icon(params, version)
|
22
|
+
upload_screenshots(params, version)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.description
|
26
|
+
"Uploads app metadata, screenshots, icons and app bundles to RuStore"
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.authors
|
30
|
+
["CheeryLee"]
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.is_supported?(platform)
|
34
|
+
[:android].include?(platform)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.available_options
|
38
|
+
[
|
39
|
+
FastlaneCore::ConfigItem.new(
|
40
|
+
key: :package_name,
|
41
|
+
description: "The package name of the application to use",
|
42
|
+
optional: false,
|
43
|
+
type: String,
|
44
|
+
verify_block: proc do |value|
|
45
|
+
UI.user_error!("Package name can't be empty") unless value && !value.empty?
|
46
|
+
end
|
47
|
+
),
|
48
|
+
FastlaneCore::ConfigItem.new(
|
49
|
+
key: :app_name,
|
50
|
+
description: "The custom application name",
|
51
|
+
optional: true,
|
52
|
+
type: String,
|
53
|
+
verify_block: proc do |value|
|
54
|
+
UI.user_error!("Custom application name can't be empty") unless value && !value.empty?
|
55
|
+
end
|
56
|
+
),
|
57
|
+
FastlaneCore::ConfigItem.new(
|
58
|
+
key: :short_description,
|
59
|
+
description: "The short application description text",
|
60
|
+
optional: true,
|
61
|
+
type: String,
|
62
|
+
verify_block: proc do |value|
|
63
|
+
UI.important("Short description length is more than #{SHORT_DESCRIPTION_LENGTH} characters. The rest part will be ended with ellipsis.") if
|
64
|
+
!value.nil? && value.length > SHORT_DESCRIPTION_LENGTH
|
65
|
+
end
|
66
|
+
),
|
67
|
+
FastlaneCore::ConfigItem.new(
|
68
|
+
key: :full_description,
|
69
|
+
description: "The full application description text",
|
70
|
+
optional: true,
|
71
|
+
type: String,
|
72
|
+
verify_block: proc do |value|
|
73
|
+
UI.important("Full description length is more than #{FULL_DESCRIPTION_LENGTH} characters. The rest part will be ended with ellipsis.") if
|
74
|
+
!value.nil? && value.length > FULL_DESCRIPTION_LENGTH
|
75
|
+
end
|
76
|
+
),
|
77
|
+
FastlaneCore::ConfigItem.new(
|
78
|
+
key: :changelog,
|
79
|
+
description: "The changelog text",
|
80
|
+
optional: true,
|
81
|
+
type: String,
|
82
|
+
verify_block: proc do |value|
|
83
|
+
UI.important("Changelog length is more than #{CHANGELOG_LENGTH} characters. The rest part will be ended with ellipsis.") if
|
84
|
+
!value.nil? && value.length > CHANGELOG_LENGTH
|
85
|
+
end
|
86
|
+
),
|
87
|
+
FastlaneCore::ConfigItem.new(
|
88
|
+
key: :publish_type,
|
89
|
+
description: "Should app be published after moderation",
|
90
|
+
optional: true,
|
91
|
+
default_value: Helper::PublishType::MANUAL,
|
92
|
+
type: String,
|
93
|
+
verify_block: proc do |value|
|
94
|
+
UI.user_error!("Publish type must have one of the followed values: #{Helper.all_constants(Helper::PublishType).join(", ")}") unless
|
95
|
+
value && Helper.has_constant?(Helper::PublishType, value)
|
96
|
+
end
|
97
|
+
),
|
98
|
+
FastlaneCore::ConfigItem.new(
|
99
|
+
key: :publish_date_time,
|
100
|
+
description: "The date time when app would be automatically released after moderation passed",
|
101
|
+
optional: true,
|
102
|
+
type: Time,
|
103
|
+
verify_block: proc do |value|
|
104
|
+
UI.user_error!("The specified time can't be older than now") unless
|
105
|
+
value && value >= Time.now
|
106
|
+
end
|
107
|
+
),
|
108
|
+
FastlaneCore::ConfigItem.new(
|
109
|
+
key: :app_type,
|
110
|
+
description: "The app type: MAIN or GAMES",
|
111
|
+
optional: true,
|
112
|
+
type: String,
|
113
|
+
verify_block: proc do |value|
|
114
|
+
UI.user_error!("App type must have one of the followed values: #{Helper.all_constants(Helper::AppType).join(", ")}") unless
|
115
|
+
value && Helper.has_constant?(Helper::AppType, value)
|
116
|
+
end
|
117
|
+
),
|
118
|
+
FastlaneCore::ConfigItem.new(
|
119
|
+
key: :categories,
|
120
|
+
description: "The app categories",
|
121
|
+
optional: true,
|
122
|
+
type: Array,
|
123
|
+
verify_block: proc do |array|
|
124
|
+
UI.user_error!("App categories array can't be empty") if array.empty?
|
125
|
+
UI.important("Maximum count of app categories is #{APP_CATEGORIES_COUNT}") if array.length > APP_CATEGORIES_COUNT
|
126
|
+
end
|
127
|
+
),
|
128
|
+
FastlaneCore::ConfigItem.new(
|
129
|
+
key: :apks,
|
130
|
+
description: "An array of paths to APK files and their metadata to upload",
|
131
|
+
optional: false,
|
132
|
+
type: Array,
|
133
|
+
verify_block: proc do |array|
|
134
|
+
UI.user_error!("Packages array can't be empty") if array.empty?
|
135
|
+
UI.important("Maximum count of packages is #{PACKAGES_COUNT}") if array.length > PACKAGES_COUNT
|
136
|
+
|
137
|
+
counter = 0
|
138
|
+
has_error = false
|
139
|
+
|
140
|
+
array.each do |value|
|
141
|
+
has_error &&= !validate_apk(value, counter)
|
142
|
+
counter += 1
|
143
|
+
end
|
144
|
+
|
145
|
+
unless array.find_all { |x| x.include?(:is_main_apk) && x[:is_main_apk] == true }.empty?
|
146
|
+
UI.error(`Only one package can be chosen as main`)
|
147
|
+
has_error = true
|
148
|
+
end
|
149
|
+
|
150
|
+
UI.user_error!("Some APK settings contain errors. Check them above.") if has_error
|
151
|
+
end
|
152
|
+
),
|
153
|
+
FastlaneCore::ConfigItem.new(
|
154
|
+
key: :remove_active_draft,
|
155
|
+
description: "Automatically remove the last created draft",
|
156
|
+
optional: true,
|
157
|
+
default_value: false,
|
158
|
+
type: Fastlane::Boolean
|
159
|
+
),
|
160
|
+
FastlaneCore::ConfigItem.new(
|
161
|
+
key: :timeout,
|
162
|
+
description: "Request timeout in seconds applied to individual HTTP requests",
|
163
|
+
optional: true,
|
164
|
+
default_value: 1200,
|
165
|
+
type: Integer,
|
166
|
+
verify_block: proc do |value|
|
167
|
+
UI.important("Request timeout can't be less than or equal to zero. Using default value.") if
|
168
|
+
value.zero? || value.negative?
|
169
|
+
end
|
170
|
+
)
|
171
|
+
]
|
172
|
+
end
|
173
|
+
|
174
|
+
def self.category
|
175
|
+
:production
|
176
|
+
end
|
177
|
+
|
178
|
+
def self.create_draft(params)
|
179
|
+
# remove active draft if needed
|
180
|
+
if params.values.include?(:remove_active_draft) && [true].include?(params.values[:remove_active_draft])
|
181
|
+
active_version = Helper::Uploader.get_active_version(params[:package_name], params[:timeout])
|
182
|
+
|
183
|
+
unless active_version == -1
|
184
|
+
data = {
|
185
|
+
package: params[:package_name],
|
186
|
+
version: active_version
|
187
|
+
}
|
188
|
+
|
189
|
+
UI.message("Removing active draft with ID = #{active_version} ...")
|
190
|
+
Helper::Uploader.remove_draft(data, params[:timeout])
|
191
|
+
UI.success("The draft ID = #{active_version} has been removed")
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
data = {
|
196
|
+
package: params[:package_name],
|
197
|
+
publish_type: params.values[:publish_type]
|
198
|
+
}
|
199
|
+
|
200
|
+
data[:app_name] = params.values[:app_name] if
|
201
|
+
!params.values[:app_name].nil? && !params.values[:app_name].empty?
|
202
|
+
data[:short_description] = params.values[:short_description] if
|
203
|
+
!params.values[:short_description].nil? && !params.values[:short_description].empty?
|
204
|
+
data[:full_description] = params.values[:full_description] if
|
205
|
+
!params.values[:full_description].nil? && !params.values[:full_description].empty?
|
206
|
+
data[:changelog] = params.values[:changelog] if
|
207
|
+
!params.values[:changelog].nil? && !params.values[:changelog].empty?
|
208
|
+
data[:publish_date_time] = params.values[:publish_date_time] unless params.values[:publish_date_time].nil?
|
209
|
+
|
210
|
+
UI.message("Creating a draft ...")
|
211
|
+
version = Helper::Uploader.create_draft(data, params[:timeout])
|
212
|
+
UI.success("A new draft has been created: #{version}")
|
213
|
+
end
|
214
|
+
|
215
|
+
def self.upload_apk(params, version)
|
216
|
+
UI.message("Process packages ...")
|
217
|
+
|
218
|
+
counter = 0
|
219
|
+
params.values[:apks].each do |apk|
|
220
|
+
break if counter >= PACKAGES_COUNT
|
221
|
+
|
222
|
+
data = {
|
223
|
+
package: params[:package_name],
|
224
|
+
version: version,
|
225
|
+
file: File.expand_path(apk[:file]),
|
226
|
+
services_type: apk.include?(:services_type) && apk[:services_type].is_a?(String) ? apk[:services_type] : Helper::ServicesType::UNKNOWN,
|
227
|
+
is_main_apk: apk.include?(:is_main_apk) && [true, false].include?(apk[:is_main_apk]) ? apk[:is_main_apk] : false
|
228
|
+
}
|
229
|
+
|
230
|
+
UI.message("Uploading package with the following values:")
|
231
|
+
UI.message("---------------------")
|
232
|
+
|
233
|
+
data_str = JSON.pretty_generate(data)
|
234
|
+
data_str.each_line do |x|
|
235
|
+
UI.message(x.gsub("\n", ""))
|
236
|
+
end
|
237
|
+
|
238
|
+
UI.message("---------------------")
|
239
|
+
Helper::Uploader.upload_apk(data, params[:timeout])
|
240
|
+
UI.success("Package has been uploaded successfully")
|
241
|
+
|
242
|
+
counter += 1
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def self.commit(params, version)
|
247
|
+
data = {
|
248
|
+
package: params[:package_name],
|
249
|
+
version: version,
|
250
|
+
priority_update: 0
|
251
|
+
}
|
252
|
+
|
253
|
+
UI.message("Committing the draft ...")
|
254
|
+
Helper::Uploader.commit(data, params[:timeout])
|
255
|
+
UI.success("Submission passed! Wait until moderation process ends")
|
256
|
+
end
|
257
|
+
|
258
|
+
def self.validate_apk(value, counter)
|
259
|
+
has_error = false
|
260
|
+
path = File.expand_path(value[:path])
|
261
|
+
services_type = value[:services_type]
|
262
|
+
|
263
|
+
unless value.include?(:is_main_apk)
|
264
|
+
UI.error("[#{counter}] You must define is this APK file main or not")
|
265
|
+
has_error = true
|
266
|
+
end
|
267
|
+
|
268
|
+
unless path && !path.empty?
|
269
|
+
UI.error("[#{counter}] Path to the APK file is invalid: #{path}")
|
270
|
+
has_error = true
|
271
|
+
end
|
272
|
+
|
273
|
+
unless File.exist?(path)
|
274
|
+
UI.error("[#{counter}] There is no any file that provided in APK file path: #{path}")
|
275
|
+
has_error = true
|
276
|
+
end
|
277
|
+
|
278
|
+
unless path.end_with?(".apk")
|
279
|
+
UI.error("[#{counter}] Path to the APK file doesn't point to the file with .apk extension: #{path}")
|
280
|
+
has_error = true
|
281
|
+
end
|
282
|
+
|
283
|
+
if !services_type.is_a?(String) || (!services_type.nil? && !services_type.empty? && !Helper.has_constant?(Helper::ServicesType, services_type))
|
284
|
+
constants = Helper.all_constants(Helper::ServicesType)
|
285
|
+
UI.error("[#{counter}] Incorrect services type for #{path}. Pick one from this array: #{constants.join(", ")}")
|
286
|
+
has_error = true
|
287
|
+
end
|
288
|
+
|
289
|
+
!has_error
|
290
|
+
end
|
291
|
+
|
292
|
+
def self.validate_app_categories(params)
|
293
|
+
return true if params.values[:categories].nil?
|
294
|
+
|
295
|
+
has_error = false
|
296
|
+
|
297
|
+
if params.values[:app_type].nil?
|
298
|
+
UI.error("App type must be defined along with chosen categories")
|
299
|
+
else
|
300
|
+
app_category_module = params.values[:app_type] == Helper::AppType::MAIN ? Helper::AppCategory::Main : Helper::AppCategory::Games
|
301
|
+
params.values[:categories].each do |x|
|
302
|
+
UI.error("Unknown app category: #{x}") if Helper.has_constant?(app_category_module, x)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
!has_error
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "time"
|
2
|
+
|
3
|
+
module Fastlane
|
4
|
+
module Helper
|
5
|
+
class Config
|
6
|
+
attr_accessor :company_id
|
7
|
+
attr_accessor :private_key
|
8
|
+
end
|
9
|
+
|
10
|
+
class AuthData
|
11
|
+
attr_accessor :token
|
12
|
+
attr_accessor :created_at
|
13
|
+
attr_accessor :time
|
14
|
+
|
15
|
+
def valid?
|
16
|
+
current_time = Time.now.to_i
|
17
|
+
token? && current_time < (@created_at + @time)
|
18
|
+
end
|
19
|
+
|
20
|
+
def token?
|
21
|
+
@token.is_a?(String) && !@token.nil? && !@token.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def setup(token, time)
|
25
|
+
@token = token
|
26
|
+
@created_at = Time.now.to_i
|
27
|
+
@time = time
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class << self
|
32
|
+
attr_accessor :config
|
33
|
+
attr_accessor :auth_data
|
34
|
+
end
|
35
|
+
|
36
|
+
self.config = Config.new
|
37
|
+
self.auth_data = AuthData.new
|
38
|
+
self.auth_data.setup("", 0)
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Helper
|
3
|
+
def self.has_constant?(module_type, value)
|
4
|
+
!module_type.constants.find { |x| module_type.const_get(x) == value }.nil?
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.all_constants(module_type)
|
8
|
+
module_type.constants.map { |x| module_type.const_get(x) }
|
9
|
+
end
|
10
|
+
|
11
|
+
module ScreenshotOrientation
|
12
|
+
LANDSCAPE = "LANDSCAPE"
|
13
|
+
PORTRAIT = "PORTRAIT"
|
14
|
+
end
|
15
|
+
|
16
|
+
module AppType
|
17
|
+
MAIN = "MAIN"
|
18
|
+
GAMES = "GAMES"
|
19
|
+
end
|
20
|
+
|
21
|
+
module ServicesType
|
22
|
+
UNKNOWN = "Unknown"
|
23
|
+
HMS = "HMS"
|
24
|
+
end
|
25
|
+
|
26
|
+
module PublishType
|
27
|
+
MANUAL = "MANUAL"
|
28
|
+
INSTANTLY = "INSTANTLY"
|
29
|
+
DELAYED = "DELAYED"
|
30
|
+
end
|
31
|
+
|
32
|
+
module AppCategory
|
33
|
+
module Main
|
34
|
+
BUSINESS = "business"
|
35
|
+
STATE = "state"
|
36
|
+
FOOD_AND_DRINK = "foodAndDrink"
|
37
|
+
HEALTH = "health"
|
38
|
+
BOOKS = "books"
|
39
|
+
NEWS = "news"
|
40
|
+
LIFESTYLE = "lifestyle"
|
41
|
+
EDUCATION = "education"
|
42
|
+
SOCIAL = "social"
|
43
|
+
ADS_AND_SERVICES = "adsAndServices"
|
44
|
+
PETS = "pets"
|
45
|
+
PURCHASES = "purchases"
|
46
|
+
TOOLS = "tools"
|
47
|
+
TRAVELLING = "travelling"
|
48
|
+
ENTERTAINMENT = "entertainment"
|
49
|
+
PARENTING = "parenting"
|
50
|
+
SPORT = "sport"
|
51
|
+
GAMBLING = "gambling"
|
52
|
+
TRANSPORT = "transport"
|
53
|
+
FINANCE = "finance"
|
54
|
+
end
|
55
|
+
|
56
|
+
module Games
|
57
|
+
ARCADE = "arcade"
|
58
|
+
QUIZ = "quiz"
|
59
|
+
PUZZLE = "puzzle"
|
60
|
+
RACE = "race"
|
61
|
+
CHILDREN = "children"
|
62
|
+
AR = "ar"
|
63
|
+
INDIE = "indie"
|
64
|
+
CASINO = "casino"
|
65
|
+
CASUAL = "casual"
|
66
|
+
CARD = "card"
|
67
|
+
MUSIC = "music"
|
68
|
+
BOARD = "board"
|
69
|
+
ADVENTURE = "adventure"
|
70
|
+
ROLE_PLAYING = "rolePlaying"
|
71
|
+
FAMILY = "family"
|
72
|
+
SIMULATOR = "simulator"
|
73
|
+
WORD = "word"
|
74
|
+
SPORTS = "sports"
|
75
|
+
STRATEGY = "strategy"
|
76
|
+
ACTION = "action"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
require "time"
|
2
|
+
require "openssl"
|
3
|
+
require "base64"
|
4
|
+
|
5
|
+
module Fastlane
|
6
|
+
module Helper
|
7
|
+
class Uploader
|
8
|
+
HOST = "https://public-api.rustore.ru".freeze
|
9
|
+
API_VERSION = "/public/v1".freeze
|
10
|
+
REQUEST_HEADERS = {
|
11
|
+
"Accept": "application/json"
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
def self.authorize(company_id, private_key, timeout = 0)
|
15
|
+
timestamp = Time.at(Time.now.to_i - 10).iso8601
|
16
|
+
signature_data = "#{company_id}#{timestamp}"
|
17
|
+
private_key = "-----BEGIN RSA PRIVATE KEY-----\n#{private_key}\n-----END RSA PRIVATE KEY-----"
|
18
|
+
signature = OpenSSL::PKey::RSA.new(private_key).sign(OpenSSL::Digest.new("SHA512"), signature_data)
|
19
|
+
signature_hex = Base64.encode64(signature)
|
20
|
+
|
21
|
+
body = {
|
22
|
+
companyId: company_id,
|
23
|
+
timestamp: timestamp,
|
24
|
+
signature: signature_hex
|
25
|
+
}
|
26
|
+
headers = {
|
27
|
+
"Content-Type": "application/json"
|
28
|
+
}.merge(REQUEST_HEADERS)
|
29
|
+
|
30
|
+
begin
|
31
|
+
connection = Faraday.new(HOST)
|
32
|
+
response = connection.post("/public/auth") do |req|
|
33
|
+
req.headers = headers
|
34
|
+
req.body = body.to_json
|
35
|
+
req.options.timeout = timeout if timeout.positive?
|
36
|
+
end
|
37
|
+
|
38
|
+
response_data = JSON.parse(response.body)
|
39
|
+
|
40
|
+
if response.status == 200
|
41
|
+
token = response_data["body"]["jwe"]
|
42
|
+
time = response_data["body"]["ttl"]
|
43
|
+
|
44
|
+
Helper.auth_data.setup(token, time)
|
45
|
+
UI.message("Received a new authorization token. Current time: #{Helper.auth_data.created_at}, time to live: #{Helper.auth_data.time}")
|
46
|
+
else
|
47
|
+
request_error(response_data)
|
48
|
+
end
|
49
|
+
rescue StandardError => ex
|
50
|
+
UI.user_error!("Authorization failed: #{ex}")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.create_draft(data, timeout = 0)
|
55
|
+
check_auth_token
|
56
|
+
|
57
|
+
body = {
|
58
|
+
appName: data[:app_name],
|
59
|
+
shortDescription: data[:short_description],
|
60
|
+
fullDescription: data[:full_description],
|
61
|
+
whatsNew: data[:changelog],
|
62
|
+
publishType: data[:publish_type],
|
63
|
+
publishDateTime: data[:publish_date_time]
|
64
|
+
}
|
65
|
+
|
66
|
+
begin
|
67
|
+
connection = Faraday.new(HOST)
|
68
|
+
response = connection.post("#{API_VERSION}/application/#{data[:package]}/version") do |req|
|
69
|
+
req.headers = build_headers(Helper.auth_data.token)
|
70
|
+
req.body = body.to_json
|
71
|
+
req.options.timeout = timeout if timeout.positive?
|
72
|
+
end
|
73
|
+
|
74
|
+
response_data = JSON.parse(response.body)
|
75
|
+
request_error(response_data) if response.status != 200
|
76
|
+
|
77
|
+
response_data["body"]
|
78
|
+
rescue StandardError => ex
|
79
|
+
UI.user_error!("Draft creation failed: #{ex}")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.remove_draft(data, timeout = 0)
|
84
|
+
check_auth_token
|
85
|
+
|
86
|
+
begin
|
87
|
+
connection = Faraday.new(HOST)
|
88
|
+
response = connection.delete("#{API_VERSION}/application/#{data[:package]}/version/#{data[:version]}") do |req|
|
89
|
+
req.headers = build_headers(Helper.auth_data.token)
|
90
|
+
req.options.timeout = timeout if timeout.positive?
|
91
|
+
end
|
92
|
+
|
93
|
+
response_data = JSON.parse(response.body)
|
94
|
+
request_error(response_data) if response.status != 200
|
95
|
+
rescue StandardError => ex
|
96
|
+
UI.user_error!("Draft removal failed: #{ex}")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.get_active_version(package_id, timeout = 0)
|
101
|
+
check_auth_token
|
102
|
+
|
103
|
+
begin
|
104
|
+
connection = Faraday.new(HOST)
|
105
|
+
response = connection.get("#{API_VERSION}/application/#{package_id}/version") do |req|
|
106
|
+
req.headers = build_headers(Helper.auth_data.token)
|
107
|
+
req.options.timeout = timeout if timeout.positive?
|
108
|
+
end
|
109
|
+
|
110
|
+
response_data = JSON.parse(response.body)
|
111
|
+
request_error(response_data) if response.status != 200
|
112
|
+
|
113
|
+
versions = response_data["body"]["content"]
|
114
|
+
version_obj = versions.find { |x| x["versionStatus"] == "DRAFT" }
|
115
|
+
|
116
|
+
!version_obj.nil? ? version_obj["versionId"] : -1
|
117
|
+
rescue StandardError => ex
|
118
|
+
UI.user_error!("Can't get active draft version: #{ex}")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.upload_apk(data, timeout = 0)
|
123
|
+
check_auth_token
|
124
|
+
|
125
|
+
body = {
|
126
|
+
file: Faraday::Multipart::FilePart.new(data[:file], "application/octet-stream")
|
127
|
+
}
|
128
|
+
query = {
|
129
|
+
servicesType: data[:services_type],
|
130
|
+
isMainApk: data[:is_main_apk]
|
131
|
+
}
|
132
|
+
headers = build_headers(Helper.auth_data.token)
|
133
|
+
headers["Content-Type"] = "multipart/form-data"
|
134
|
+
|
135
|
+
begin
|
136
|
+
connection = Faraday.new(HOST) do |con|
|
137
|
+
con.request :multipart
|
138
|
+
end
|
139
|
+
response = connection.post("#{API_VERSION}/application/#{data[:package]}/version/#{data[:version]}/apk") do |req|
|
140
|
+
req.headers = headers
|
141
|
+
req.params = query
|
142
|
+
req.body = body
|
143
|
+
req.options.timeout = timeout if timeout.positive?
|
144
|
+
end
|
145
|
+
|
146
|
+
response_data = JSON.parse(response.body)
|
147
|
+
request_error(response_data) if response.status != 200
|
148
|
+
rescue StandardError => ex
|
149
|
+
UI.user_error!("Package uploading failed: #{ex}")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.commit(data, timeout = 0)
|
154
|
+
check_auth_token
|
155
|
+
|
156
|
+
query = {
|
157
|
+
priorityUpdate: data[:priority_update]
|
158
|
+
}
|
159
|
+
|
160
|
+
begin
|
161
|
+
connection = Faraday.new(HOST)
|
162
|
+
response = connection.post("#{API_VERSION}/application/#{data[:package]}/version/#{data[:version]}/commit") do |req|
|
163
|
+
req.headers = build_headers(Helper.auth_data.token)
|
164
|
+
req.params = query
|
165
|
+
req.body = body
|
166
|
+
req.options.timeout = timeout if timeout.positive?
|
167
|
+
end
|
168
|
+
|
169
|
+
response_data = JSON.parse(response.body)
|
170
|
+
request_error(response_data) if response.status != 200
|
171
|
+
rescue StandardError => ex
|
172
|
+
UI.user_error!("Draft committing failed: #{ex}")
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.check_auth_token
|
177
|
+
return if Helper.auth_data.valid?
|
178
|
+
|
179
|
+
UI.user_error!("No authorization data has provided. You need to call `rustore_credentials` action first.") unless
|
180
|
+
Helper.auth_data.token?
|
181
|
+
UI.important("Authorization token is not valid. Renewing...")
|
182
|
+
authorize(Helper.config.company_id, Helper.config.private_key)
|
183
|
+
end
|
184
|
+
|
185
|
+
def self.build_headers(auth_token)
|
186
|
+
{
|
187
|
+
"Public-Token": auth_token,
|
188
|
+
"Content-Type": "application/json"
|
189
|
+
}.merge(REQUEST_HEADERS)
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.request_error(data)
|
193
|
+
error_code = data["code"]
|
194
|
+
error_description = data["message"]
|
195
|
+
UI.user_error!("Request returned the error.\nCode: #{error_code}\nDescription: #{error_description}")
|
196
|
+
end
|
197
|
+
|
198
|
+
public_class_method(:authorize)
|
199
|
+
public_class_method(:create_draft)
|
200
|
+
public_class_method(:remove_draft)
|
201
|
+
public_class_method(:get_active_version)
|
202
|
+
public_class_method(:upload_apk)
|
203
|
+
public_class_method(:commit)
|
204
|
+
|
205
|
+
private_class_method(:check_auth_token)
|
206
|
+
private_class_method(:build_headers)
|
207
|
+
private_class_method(:request_error)
|
208
|
+
|
209
|
+
private_constant(:HOST)
|
210
|
+
private_constant(:API_VERSION)
|
211
|
+
private_constant(:REQUEST_HEADERS)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "fastlane/plugin/lazurite/version"
|
2
|
+
|
3
|
+
module Fastlane
|
4
|
+
module Lazurite
|
5
|
+
# Return all .rb files inside the "actions" and "helper" directory
|
6
|
+
def self.all_classes
|
7
|
+
Dir[File.expand_path('**/{actions,helper}/*.rb', File.dirname(__FILE__))]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# By default we want to import all available actions and helpers
|
13
|
+
# A plugin can contain any number of actions and plugins
|
14
|
+
Fastlane::Lazurite.all_classes.each do |current|
|
15
|
+
require current
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fastlane-plugin-lazurite
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0.beta.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- CheeryLee
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-02-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: fastlane
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.200.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.200.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.48.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.48.0
|
111
|
+
description:
|
112
|
+
email:
|
113
|
+
- cheerylee90@gmail.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- LICENSE
|
119
|
+
- README.md
|
120
|
+
- lib/fastlane/plugin/lazurite/actions/rustore_credentials_action.rb
|
121
|
+
- lib/fastlane/plugin/lazurite/actions/upload_to_rustore_action.rb
|
122
|
+
- lib/fastlane/plugin/lazurite/helper/config.rb
|
123
|
+
- lib/fastlane/plugin/lazurite/helper/enums.rb
|
124
|
+
- lib/fastlane/plugin/lazurite/helper/uploader.rb
|
125
|
+
- lib/fastlane/plugin/lazurite/version.rb
|
126
|
+
- lib/fastlane/plugin/rustore.rb
|
127
|
+
homepage: https://github.com/CheeryLee/fastlane-plugin-rustore
|
128
|
+
licenses:
|
129
|
+
- MIT
|
130
|
+
metadata: {}
|
131
|
+
post_install_message:
|
132
|
+
rdoc_options: []
|
133
|
+
require_paths:
|
134
|
+
- lib
|
135
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: 2.6.0
|
140
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">"
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: 1.3.1
|
145
|
+
requirements: []
|
146
|
+
rubygems_version: 3.2.3
|
147
|
+
signing_key:
|
148
|
+
specification_version: 4
|
149
|
+
summary: Faslane plugin for RuStore deployment
|
150
|
+
test_files: []
|