fastlane-plugin-upload_to_ru_store 1.0.7 → 1.1.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 +4 -4
- data/CHANGELOG.md +48 -0
- data/README.md +388 -41
- data/lib/fastlane/plugin/upload_to_ru_store/actions/rustore_archive_version_action.rb +66 -0
- data/lib/fastlane/plugin/upload_to_ru_store/actions/rustore_publish_settings_action.rb +81 -0
- data/lib/fastlane/plugin/upload_to_ru_store/actions/rustore_publish_version_action.rb +67 -0
- data/lib/fastlane/plugin/upload_to_ru_store/actions/rustore_version_status_action.rb +90 -0
- data/lib/fastlane/plugin/upload_to_ru_store/actions/upload_to_ru_store_action.rb +178 -84
- data/lib/fastlane/plugin/upload_to_ru_store/helper/rustore/api.rb +259 -0
- data/lib/fastlane/plugin/upload_to_ru_store/helper/rustore/client.rb +164 -0
- data/lib/fastlane/plugin/upload_to_ru_store/helper/rustore/errors.rb +30 -0
- data/lib/fastlane/plugin/upload_to_ru_store/helper/rustore/options.rb +143 -0
- data/lib/fastlane/plugin/upload_to_ru_store/helper/rustore/signer.rb +50 -0
- data/lib/fastlane/plugin/upload_to_ru_store/helper/upload_to_ru_store_helper.rb +108 -155
- data/lib/fastlane/plugin/upload_to_ru_store/version.rb +2 -2
- data/lib/fastlane/plugin/upload_to_ru_store.rb +2 -6
- metadata +37 -121
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fe89fe301094e12993917fddd33e1d04d563fe9b7d300f72503f045afb3eda8b
|
|
4
|
+
data.tar.gz: 9e52866000cb8b048a48994dd1772efc5bd3972df9dc5180efda941b267dc2a3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6ceff64b463b21b7a627ce1b5307e82891200eb1db96643f18e44d542ac1a20eb860fbe144928a0e188df8a8dfee2d7a5dc6625eb4692730f7a1178df57250bf
|
|
7
|
+
data.tar.gz: 158f54c8414af4498c9929612cd7acf66d79bc4c05188ccfe9a025c5a2220b0ac93f0a49109ac38870c838ea2d455bfc8f7184735f1336d0dbd2a7b8a0a249eb
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/),
|
|
4
|
+
версии следуют [Semantic Versioning](https://semver.org/lang/ru/).
|
|
5
|
+
|
|
6
|
+
## [1.1.0] — 2026-09-19
|
|
7
|
+
|
|
8
|
+
### Добавлено
|
|
9
|
+
- Действие `rustore_version_status` — статус последней версии приложения.
|
|
10
|
+
- Действие `rustore_publish_version` — ручная публикация прошедшей модерацию версии.
|
|
11
|
+
- Действие `rustore_archive_version` — архивирование версии.
|
|
12
|
+
- Действие `rustore_publish_settings` — изменение типа публикации, даты и процента раскатки.
|
|
13
|
+
- Поэтапная раскатка через `partial_value` (5/10/25/50/75/100).
|
|
14
|
+
- Метаданные черновика: `app_name`, `app_type`, `age_legal`, `short_description`,
|
|
15
|
+
`full_description`, `moder_info`, `price_value`, `whats_new`.
|
|
16
|
+
- Флаги `remove_existing_drafts` и `submit_for_moderation`, параметр `priority_update`.
|
|
17
|
+
- Настраиваемый `timeout` и повтор запроса при сетевых сбоях.
|
|
18
|
+
- `RUSTORE_VERSION_ID` и `RUSTORE_VERSION_STATUS` в `lane_context`.
|
|
19
|
+
|
|
20
|
+
### Исправлено
|
|
21
|
+
- `publish_datetime` не передавался в запрос создания черновика, из-за чего
|
|
22
|
+
`publish_type: "DELAYED"` всегда завершался ошибкой.
|
|
23
|
+
- Плагин не загружался вместе с современным fastlane: `faraday_middleware`
|
|
24
|
+
требует Faraday 1.x, а fastlane использует Faraday 2.x.
|
|
25
|
+
- В gemspec не было ни одной runtime-зависимости, поэтому в чистом окружении
|
|
26
|
+
плагин падал с `cannot load such file`.
|
|
27
|
+
- Токен `Public-Token` попадал в stderr при каждом запуске: HTTP-логгер был
|
|
28
|
+
включён безусловно. Теперь он включается через `RUSTORE_DEBUG`, а токен,
|
|
29
|
+
подпись и JWE маскируются.
|
|
30
|
+
- `key_id` и `private_key` не были помечены `sensitive`, из-за чего fastlane
|
|
31
|
+
печатал их в сводке параметров.
|
|
32
|
+
- Лимит changelog был 500 символов вместо документированных 5000.
|
|
33
|
+
- Ответы не-JSON (HTML-страницы ошибок, 502 от прокси) роняли плагин на `#dig`.
|
|
34
|
+
- Список черновиков читался только с первой страницы, остальные не удалялись.
|
|
35
|
+
- Статус HTTP не проверялся: сбой публикации выглядел как успех.
|
|
36
|
+
- Ключи PKCS#8 в виде голого Base64 не читались.
|
|
37
|
+
- Отсутствовала проверка существования файлов сборки и changelog.
|
|
38
|
+
|
|
39
|
+
### Изменено
|
|
40
|
+
- Код разделён на слои: `Rustore::Signer`, `Rustore::Client`, `Rustore::Api`
|
|
41
|
+
и фасад `UploadToRuStoreHelper`, сохраняющий прежний публичный интерфейс.
|
|
42
|
+
- Минимальная версия Ruby — 3.2: зависимости fastlane (google-apis-*, googleauth,
|
|
43
|
+
signet, excon) больше не собираются на 3.1.
|
|
44
|
+
- CI переведён на Ruby 3.2/3.3/3.4 и `actions/checkout@v4`.
|
|
45
|
+
|
|
46
|
+
## [1.0.7]
|
|
47
|
+
|
|
48
|
+
- Увеличен таймаут запросов.
|
data/README.md
CHANGED
|
@@ -1,28 +1,43 @@
|
|
|
1
|
-
# upload_to_ru_store
|
|
1
|
+
# fastlane-plugin-upload_to_ru_store
|
|
2
2
|
|
|
3
3
|
[](https://rubygems.org/gems/fastlane-plugin-upload_to_ru_store)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Fastlane-плагин для публикации Android-приложений в **RuStore**: загрузка AAB/APK,
|
|
6
|
+
поэтапная раскатка, отложенный релиз, ручная публикация и архивирование версий.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
---
|
|
8
9
|
|
|
9
|
-
##
|
|
10
|
+
## Содержание
|
|
10
11
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
12
|
+
- [Установка](#установка)
|
|
13
|
+
- [Быстрый старт](#быстрый-старт)
|
|
14
|
+
- [Получение ключей](#получение-ключей)
|
|
15
|
+
- [Действия](#действия)
|
|
16
|
+
- [upload_to_ru_store](#upload_to_ru_store)
|
|
17
|
+
- [rustore_version_status](#rustore_version_status)
|
|
18
|
+
- [rustore_publish_version](#rustore_publish_version)
|
|
19
|
+
- [rustore_publish_settings](#rustore_publish_settings)
|
|
20
|
+
- [rustore_archive_version](#rustore_archive_version)
|
|
21
|
+
- [Рецепты](#рецепты)
|
|
22
|
+
- [Переменные окружения](#переменные-окружения)
|
|
23
|
+
- [Отладка](#отладка)
|
|
24
|
+
- [Ограничения RuStore](#ограничения-rustore)
|
|
25
|
+
- [Обновление с 1.0.x](#обновление-с-10x)
|
|
26
|
+
- [Разработка](#разработка)
|
|
16
27
|
|
|
17
|
-
|
|
28
|
+
---
|
|
18
29
|
|
|
19
|
-
|
|
30
|
+
## Установка
|
|
20
31
|
|
|
21
32
|
```bash
|
|
22
33
|
fastlane add_plugin upload_to_ru_store
|
|
23
34
|
```
|
|
24
35
|
|
|
25
|
-
|
|
36
|
+
Требуется Ruby >= 3.2 и fastlane >= 2.240.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Быстрый старт
|
|
26
41
|
|
|
27
42
|
```ruby
|
|
28
43
|
lane :publish_to_rustore do
|
|
@@ -30,47 +45,379 @@ lane :publish_to_rustore do
|
|
|
30
45
|
package_name: "com.example.app",
|
|
31
46
|
key_id: ENV["RUSTORE_KEY_ID"],
|
|
32
47
|
private_key: ENV["RUSTORE_PRIVATE_KEY"],
|
|
33
|
-
|
|
34
|
-
changelog_path: "metadata/android/ru-RU/changelog.txt"
|
|
35
|
-
aab: "app/build/outputs/bundle/release/app-release.aab", # если указан, зальётся только AAB
|
|
36
|
-
gms_apk: "app/build/outputs/apk/release/app-release.apk", # путь к Google-APK (если не указан AAB)
|
|
37
|
-
hms_apk: "app-huawei-release.apk" # путь к Huawei-APK (опционально)
|
|
48
|
+
aab: "app/build/outputs/bundle/release/app-release.aab",
|
|
49
|
+
changelog_path: "metadata/android/ru-RU/changelog.txt"
|
|
38
50
|
)
|
|
39
51
|
end
|
|
40
52
|
```
|
|
41
53
|
|
|
42
|
-
|
|
54
|
+
Что происходит под капотом:
|
|
55
|
+
|
|
56
|
+
1. подпись `key_id + timestamp` по RSA-SHA512, обмен на JWE-токен;
|
|
57
|
+
2. удаление незавершённых черновиков приложения;
|
|
58
|
+
3. создание черновика с метаданными и настройками публикации;
|
|
59
|
+
4. загрузка сборки (AAB, либо GMS APK и опционально HMS APK);
|
|
60
|
+
5. отправка черновика на модерацию.
|
|
61
|
+
|
|
62
|
+
Шаги 2 и 5 отключаются флагами `remove_existing_drafts` и `submit_for_moderation`.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Получение ключей
|
|
67
|
+
|
|
68
|
+
В консоли RuStore: **Настройки → Ключи доступа → Создать ключ**. Вы получите
|
|
69
|
+
идентификатор ключа (`key_id`) и приватный RSA-ключ.
|
|
70
|
+
|
|
71
|
+
`private_key` принимается в любом из форматов:
|
|
72
|
+
|
|
73
|
+
- PEM с заголовком `-----BEGIN PRIVATE KEY-----` (PKCS#8);
|
|
74
|
+
- PEM с заголовком `-----BEGIN RSA PRIVATE KEY-----` (PKCS#1);
|
|
75
|
+
- голый Base64 без заголовков — формат определяется автоматически по структуре DER.
|
|
76
|
+
|
|
77
|
+
Последний вариант удобен для CI: ключ кладётся в секрет одной строкой.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
RUSTORE_PRIVATE_KEY=$(tr -d '\n' < key.pem)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Действия
|
|
86
|
+
|
|
87
|
+
### upload_to_ru_store
|
|
43
88
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
3. Создаст новый драфт
|
|
47
|
-
4. Загрузит указанные сборки (AAB и/или APK)
|
|
48
|
-
5. Закоммитит драфт и отправит на модерацию
|
|
89
|
+
Полный цикл выпуска: черновик → сборка → модерация. Возвращает `versionId`
|
|
90
|
+
созданного черновика и кладёт его в `lane_context[SharedValues::RUSTORE_VERSION_ID]`.
|
|
49
91
|
|
|
50
|
-
|
|
92
|
+
#### Обязательные параметры
|
|
93
|
+
|
|
94
|
+
| Параметр | Описание |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `package_name` | Идентификатор пакета, например `com.example.app` |
|
|
97
|
+
| `key_id` | Идентификатор ключа доступа RuStore |
|
|
98
|
+
| `private_key` | Приватный RSA-ключ |
|
|
99
|
+
|
|
100
|
+
Нужно указать хотя бы `aab` или `gms_apk`.
|
|
101
|
+
|
|
102
|
+
#### Сборки
|
|
103
|
+
|
|
104
|
+
| Параметр | Описание |
|
|
105
|
+
|---|---|
|
|
106
|
+
| `aab` | Путь к Android App Bundle. Если указан, APK не загружаются |
|
|
107
|
+
| `gms_apk` | Путь к APK с Google Mobile Services |
|
|
108
|
+
| `hms_apk` | Путь к APK с Huawei Mobile Services, грузится дополнительно |
|
|
109
|
+
|
|
110
|
+
#### Публикация
|
|
111
|
+
|
|
112
|
+
| Параметр | Значения | Описание |
|
|
113
|
+
|---|---|---|
|
|
114
|
+
| `publish_type` | `MANUAL`, `INSTANTLY`, `DELAYED` | Тип публикации |
|
|
115
|
+
| `publish_datetime` | ISO8601 | Обязателен при `DELAYED`. От 24 часов до 60 дней от момента отправки на модерацию |
|
|
116
|
+
| `partial_value` | `5`, `10`, `25`, `50`, `75`, `100` | Процент поэтапной раскатки |
|
|
117
|
+
| `priority_update` | целое число | Приоритет обновления при отправке на модерацию |
|
|
118
|
+
|
|
119
|
+
#### Метаданные черновика
|
|
120
|
+
|
|
121
|
+
| Параметр | Лимит | Описание |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `changelog_path` | 5000 символов | Путь к файлу «Что нового» |
|
|
124
|
+
| `whats_new` | 5000 символов | То же самое, но строкой |
|
|
125
|
+
| `app_name` | 50 символов | Название приложения |
|
|
126
|
+
| `app_type` | `MAIN`, `GAMES` | Тип приложения |
|
|
127
|
+
| `age_legal` | `0+`, `6+`, `12+`, `16+`, `18+` | Возрастной рейтинг |
|
|
128
|
+
| `short_description` | 80 символов | Краткое описание |
|
|
129
|
+
| `full_description` | 4000 символов | Полное описание |
|
|
130
|
+
| `moder_info` | 180 символов | Комментарий для модератора |
|
|
131
|
+
| `price_value` | целое | Цена в копейках для платных приложений |
|
|
132
|
+
|
|
133
|
+
#### Поведение
|
|
134
|
+
|
|
135
|
+
| Параметр | По умолчанию | Описание |
|
|
136
|
+
|---|---|---|
|
|
137
|
+
| `remove_existing_drafts` | `true` | Удалять существующие черновики перед созданием нового |
|
|
138
|
+
| `submit_for_moderation` | `true` | Отправлять черновик на модерацию |
|
|
139
|
+
| `timeout` | `600` | Таймаут чтения ответа, секунды |
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
### rustore_version_status
|
|
144
|
+
|
|
145
|
+
Возвращает Hash с данными последней версии: `versionId`, `versionCode`,
|
|
146
|
+
`versionName`, `versionStatus`, `publishType`, `partialValue` и остальные поля RuStore.
|
|
147
|
+
Если версий нет — возвращает `nil`.
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
version = rustore_version_status(
|
|
151
|
+
package_name: "com.example.app",
|
|
152
|
+
key_id: ENV["RUSTORE_KEY_ID"],
|
|
153
|
+
private_key: ENV["RUSTORE_PRIVATE_KEY"]
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
UI.message("Статус: #{version['versionStatus']}")
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Параметры: `package_name`, `key_id`, `private_key`, `testing_type` (`ALL`/`RELEASE`/`ALPHA`), `timeout`.
|
|
160
|
+
|
|
161
|
+
Возможные значения `versionStatus`:
|
|
162
|
+
|
|
163
|
+
| Статус | Значение |
|
|
164
|
+
|---|---|
|
|
165
|
+
| `DRAFT` | черновик |
|
|
166
|
+
| `AUTO_CHECK` | антивирусная проверка |
|
|
167
|
+
| `AUTO_CHECK_FAILED` | не прошла антивирусную проверку |
|
|
168
|
+
| `MODERATION` | ждёт модерации |
|
|
169
|
+
| `TAKEN_FOR_MODERATION` | взята модератором |
|
|
170
|
+
| `READY_FOR_PUBLICATION` | прошла модерацию |
|
|
171
|
+
| `REJECTED_BY_MODERATOR` | отклонена модератором |
|
|
172
|
+
| `REJECTED_BY_SECURITY` | отклонена службой безопасности |
|
|
173
|
+
| `ACTIVE` | опубликована |
|
|
174
|
+
| `PARTIAL_ACTIVE` | опубликована частично |
|
|
175
|
+
| `PREVIOUS_ACTIVE` | предыдущая активная версия |
|
|
176
|
+
| `ARCHIVED` | архивирована |
|
|
177
|
+
| `DELETED_DRAFT` | удалённый черновик |
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
### rustore_publish_version
|
|
182
|
+
|
|
183
|
+
Публикует версию, которая прошла модерацию и была создана с `publish_type: "MANUAL"`.
|
|
184
|
+
Без `version_id` берёт последнюю версию приложения.
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
rustore_publish_version(
|
|
188
|
+
package_name: "com.example.app",
|
|
189
|
+
key_id: ENV["RUSTORE_KEY_ID"],
|
|
190
|
+
private_key: ENV["RUSTORE_PRIVATE_KEY"]
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
### rustore_publish_settings
|
|
197
|
+
|
|
198
|
+
Меняет настройки публикации уже существующей версии: тип, дату отложенного
|
|
199
|
+
релиза, процент раскатки.
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
rustore_publish_settings(
|
|
203
|
+
package_name: "com.example.app",
|
|
204
|
+
key_id: ENV["RUSTORE_KEY_ID"],
|
|
205
|
+
private_key: ENV["RUSTORE_PRIVATE_KEY"],
|
|
206
|
+
version_id: 12345,
|
|
207
|
+
partial_value: 50
|
|
208
|
+
)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
RuStore разрешает только увеличивать процент раскатки; `100` означает полный релиз.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
### rustore_archive_version
|
|
216
|
+
|
|
217
|
+
Архивирует версию. RuStore принимает архивирование только для статусов
|
|
218
|
+
`READY_FOR_PUBLICATION` и `REJECTED_BY_MODERATOR`.
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
rustore_archive_version(
|
|
222
|
+
package_name: "com.example.app",
|
|
223
|
+
key_id: ENV["RUSTORE_KEY_ID"],
|
|
224
|
+
private_key: ENV["RUSTORE_PRIVATE_KEY"],
|
|
225
|
+
version_id: 12345
|
|
226
|
+
)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Рецепты
|
|
232
|
+
|
|
233
|
+
### Поэтапная раскатка
|
|
234
|
+
|
|
235
|
+
Выпуск на 10% аудитории, затем расширение:
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
lane :rollout_start do
|
|
239
|
+
upload_to_ru_store(
|
|
240
|
+
package_name: "com.example.app",
|
|
241
|
+
key_id: ENV["RUSTORE_KEY_ID"],
|
|
242
|
+
private_key: ENV["RUSTORE_PRIVATE_KEY"],
|
|
243
|
+
aab: "app-release.aab",
|
|
244
|
+
publish_type: "INSTANTLY",
|
|
245
|
+
partial_value: 10
|
|
246
|
+
)
|
|
247
|
+
end
|
|
51
248
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
249
|
+
lane :rollout_expand do |options|
|
|
250
|
+
version = rustore_version_status(
|
|
251
|
+
package_name: "com.example.app",
|
|
252
|
+
key_id: ENV["RUSTORE_KEY_ID"],
|
|
253
|
+
private_key: ENV["RUSTORE_PRIVATE_KEY"]
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
rustore_publish_settings(
|
|
257
|
+
package_name: "com.example.app",
|
|
258
|
+
key_id: ENV["RUSTORE_KEY_ID"],
|
|
259
|
+
private_key: ENV["RUSTORE_PRIVATE_KEY"],
|
|
260
|
+
version_id: version["versionId"],
|
|
261
|
+
partial_value: options[:percent].to_i
|
|
262
|
+
)
|
|
263
|
+
end
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Отложенная публикация
|
|
267
|
+
|
|
268
|
+
```ruby
|
|
269
|
+
upload_to_ru_store(
|
|
270
|
+
package_name: "com.example.app",
|
|
271
|
+
key_id: ENV["RUSTORE_KEY_ID"],
|
|
272
|
+
private_key: ENV["RUSTORE_PRIVATE_KEY"],
|
|
273
|
+
aab: "app-release.aab",
|
|
274
|
+
publish_type: "DELAYED",
|
|
275
|
+
publish_datetime: (Time.now + 3 * 24 * 3600).iso8601
|
|
276
|
+
)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Ручная публикация после проверки
|
|
280
|
+
|
|
281
|
+
```ruby
|
|
282
|
+
lane :stage do
|
|
283
|
+
upload_to_ru_store(
|
|
284
|
+
package_name: "com.example.app",
|
|
285
|
+
key_id: ENV["RUSTORE_KEY_ID"],
|
|
286
|
+
private_key: ENV["RUSTORE_PRIVATE_KEY"],
|
|
287
|
+
aab: "app-release.aab",
|
|
288
|
+
publish_type: "MANUAL"
|
|
289
|
+
)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
lane :go_live do
|
|
293
|
+
version = rustore_version_status(
|
|
294
|
+
package_name: "com.example.app",
|
|
295
|
+
key_id: ENV["RUSTORE_KEY_ID"],
|
|
296
|
+
private_key: ENV["RUSTORE_PRIVATE_KEY"]
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
UI.user_error!("Версия ещё не прошла модерацию: #{version['versionStatus']}") unless
|
|
300
|
+
version["versionStatus"] == "READY_FOR_PUBLICATION"
|
|
301
|
+
|
|
302
|
+
rustore_publish_version(
|
|
303
|
+
package_name: "com.example.app",
|
|
304
|
+
key_id: ENV["RUSTORE_KEY_ID"],
|
|
305
|
+
private_key: ENV["RUSTORE_PRIVATE_KEY"],
|
|
306
|
+
version_id: version["versionId"]
|
|
307
|
+
)
|
|
308
|
+
end
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Черновик без отправки на модерацию
|
|
312
|
+
|
|
313
|
+
Полезно, когда сборку нужно залить заранее, а метаданные дозаполнить в консоли.
|
|
314
|
+
|
|
315
|
+
```ruby
|
|
316
|
+
upload_to_ru_store(
|
|
317
|
+
package_name: "com.example.app",
|
|
318
|
+
key_id: ENV["RUSTORE_KEY_ID"],
|
|
319
|
+
private_key: ENV["RUSTORE_PRIVATE_KEY"],
|
|
320
|
+
aab: "app-release.aab",
|
|
321
|
+
submit_for_moderation: false
|
|
322
|
+
)
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Переменные окружения
|
|
328
|
+
|
|
329
|
+
Каждый параметр читается из переменной окружения, что удобно в CI:
|
|
330
|
+
|
|
331
|
+
| Переменная | Параметр |
|
|
332
|
+
|---|---|
|
|
333
|
+
| `RUSTORE_PACKAGE_NAME` | `package_name` |
|
|
334
|
+
| `RUSTORE_KEY_ID` | `key_id` |
|
|
335
|
+
| `RUSTORE_PRIVATE_KEY` | `private_key` |
|
|
336
|
+
| `RUSTORE_PUBLISH_TYPE` | `publish_type` |
|
|
337
|
+
| `RUSTORE_PUBLISH_DATETIME` | `publish_datetime` |
|
|
338
|
+
| `RUSTORE_PARTIAL_VALUE` | `partial_value` |
|
|
339
|
+
| `RUSTORE_AAB` | `aab` |
|
|
340
|
+
| `RUSTORE_GMS_APK` | `gms_apk` |
|
|
341
|
+
| `RUSTORE_HMS_APK` | `hms_apk` |
|
|
342
|
+
| `RUSTORE_CHANGELOG_PATH` | `changelog_path` |
|
|
343
|
+
| `RUSTORE_VERSION_ID` | `version_id` |
|
|
344
|
+
| `RUSTORE_TESTING_TYPE` | `testing_type` |
|
|
345
|
+
| `RUSTORE_TIMEOUT` | `timeout` |
|
|
346
|
+
|
|
347
|
+
`key_id` и `private_key` помечены как `sensitive`, поэтому fastlane не печатает
|
|
348
|
+
их в сводке параметров.
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Отладка
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
RUSTORE_DEBUG=1 bundle exec fastlane publish_to_rustore
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Включает подробный HTTP-лог. Заголовок `Public-Token`, поле `signature` и `jwe`
|
|
359
|
+
в выводе маскируются.
|
|
360
|
+
|
|
361
|
+
При сетевых сбоях и ответах 429/5xx запрос повторяется до трёх раз с
|
|
362
|
+
экспоненциальной паузой.
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Ограничения RuStore
|
|
367
|
+
|
|
368
|
+
- Загрузить сборку можно только с `versionCode` больше предыдущего.
|
|
369
|
+
- Отложенная публикация — не раньше чем через 24 часа и не позже чем через 60 дней.
|
|
370
|
+
- Процент раскатки можно только увеличивать.
|
|
371
|
+
- Архивировать можно только версии в статусах `READY_FOR_PUBLICATION` и `REJECTED_BY_MODERATOR`.
|
|
372
|
+
- Публиковать вручную можно только версию, прошедшую модерацию с типом `MANUAL`.
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Обновление с 1.0.x
|
|
377
|
+
|
|
378
|
+
Существующие вызовы `upload_to_ru_store` продолжают работать без изменений.
|
|
379
|
+
На что стоит обратить внимание:
|
|
380
|
+
|
|
381
|
+
- `publish_type: "DELAYED"` наконец работает — в 1.0.x `publish_datetime`
|
|
382
|
+
не доходил до API, и вызов всегда падал;
|
|
383
|
+
- лимит changelog поднят с 500 до документированных API 5000 символов;
|
|
384
|
+
- минимальная версия Ruby — 3.2: дерево зависимостей fastlane требует её;
|
|
385
|
+
- HTTP-лог больше не печатается без запроса, включается через `RUSTORE_DEBUG`.
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## Разработка
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
bundle install
|
|
393
|
+
bundle exec rspec
|
|
394
|
+
bundle exec rubocop
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Структура:
|
|
398
|
+
|
|
399
|
+
```
|
|
400
|
+
lib/fastlane/plugin/upload_to_ru_store/
|
|
401
|
+
├── actions/ действия fastlane
|
|
402
|
+
└── helper/
|
|
403
|
+
├── upload_to_ru_store_helper.rb фасад, сохраняющий обратную совместимость
|
|
404
|
+
└── rustore/
|
|
405
|
+
├── signer.rb загрузка ключа и подпись RSA-SHA512
|
|
406
|
+
├── client.rb HTTP-транспорт, повторы, маскирование секретов
|
|
407
|
+
├── api.rb методы RuStore API и валидация полей
|
|
408
|
+
├── options.rb переиспользуемые ConfigItem
|
|
409
|
+
└── errors.rb типизированные ошибки
|
|
410
|
+
```
|
|
63
411
|
|
|
64
|
-
|
|
412
|
+
Сетевые вызовы в тестах замоканы через `webmock`, реальные соединения запрещены.
|
|
65
413
|
|
|
66
|
-
|
|
67
|
-
- Fastlane >= 2.214.0
|
|
414
|
+
---
|
|
68
415
|
|
|
69
416
|
## Лицензия
|
|
70
417
|
|
|
71
|
-
MIT
|
|
418
|
+
MIT, см. [LICENSE](LICENSE).
|
|
72
419
|
|
|
73
|
-
##
|
|
420
|
+
## Ссылки
|
|
74
421
|
|
|
75
|
-
- [
|
|
422
|
+
- [RuStore API: публикация приложений](https://www.rustore.ru/help/work-with-rustore-api/api-upload-publication-app)
|
|
76
423
|
- [Fastlane Plugins Guide](https://docs.fastlane.tools/plugins/create-plugin/)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require 'fastlane/action'
|
|
2
|
+
require 'fastlane_core/ui/ui'
|
|
3
|
+
require_relative '../helper/upload_to_ru_store_helper'
|
|
4
|
+
require_relative '../helper/rustore/options'
|
|
5
|
+
|
|
6
|
+
module Fastlane
|
|
7
|
+
module Actions
|
|
8
|
+
class RustoreArchiveVersionAction < Action
|
|
9
|
+
Options = Helper::Rustore::Options
|
|
10
|
+
Api = Helper::Rustore::Api
|
|
11
|
+
|
|
12
|
+
def self.authors
|
|
13
|
+
['Kopylov Ivan']
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.description
|
|
17
|
+
'Archives a RuStore application version'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.details
|
|
21
|
+
<<~DETAILS
|
|
22
|
+
RuStore only accepts archiving for versions in #{Api::ARCHIVABLE_STATUSES.join(' or ')}.
|
|
23
|
+
The action checks the current status first and fails early with a clear message
|
|
24
|
+
instead of letting the API return an opaque error.
|
|
25
|
+
DETAILS
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.category
|
|
29
|
+
:production
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.available_options
|
|
33
|
+
Options.auth + [Options.version_id(optional: false), Options.timeout]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.example_code
|
|
37
|
+
[
|
|
38
|
+
<<~EXAMPLE
|
|
39
|
+
rustore_archive_version(
|
|
40
|
+
package_name: "com.example.app",
|
|
41
|
+
key_id: ENV["RUSTORE_KEY_ID"],
|
|
42
|
+
private_key: ENV["RUSTORE_PRIVATE_KEY"],
|
|
43
|
+
version_id: 12345
|
|
44
|
+
)
|
|
45
|
+
EXAMPLE
|
|
46
|
+
]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.is_supported?(platform)
|
|
50
|
+
platform == :android
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.run(params)
|
|
54
|
+
helper = Helper::UploadToRuStoreHelper
|
|
55
|
+
token = helper.fetch_token(key_id: params[:key_id], private_key: params[:private_key])
|
|
56
|
+
|
|
57
|
+
helper.archive_version(
|
|
58
|
+
token: token,
|
|
59
|
+
package_name: params[:package_name],
|
|
60
|
+
version_id: params[:version_id]
|
|
61
|
+
)
|
|
62
|
+
params[:version_id]
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
require 'fastlane/action'
|
|
2
|
+
require 'fastlane_core/ui/ui'
|
|
3
|
+
require_relative '../helper/upload_to_ru_store_helper'
|
|
4
|
+
require_relative '../helper/rustore/options'
|
|
5
|
+
|
|
6
|
+
module Fastlane
|
|
7
|
+
module Actions
|
|
8
|
+
class RustorePublishSettingsAction < Action
|
|
9
|
+
Options = Helper::Rustore::Options
|
|
10
|
+
Api = Helper::Rustore::Api
|
|
11
|
+
|
|
12
|
+
def self.authors
|
|
13
|
+
['Kopylov Ivan']
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.description
|
|
17
|
+
'Changes publication settings of an existing RuStore version'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.details
|
|
21
|
+
<<~DETAILS
|
|
22
|
+
Updates the publication type, the delayed publication moment or the staged
|
|
23
|
+
rollout percentage of a version that already exists. RuStore only allows the
|
|
24
|
+
rollout percentage to grow, and 100 means a full release.
|
|
25
|
+
DETAILS
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.category
|
|
29
|
+
:production
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.available_options
|
|
33
|
+
Options.auth + [
|
|
34
|
+
Options.version_id(optional: false),
|
|
35
|
+
Options.publish_type,
|
|
36
|
+
Options.publish_datetime,
|
|
37
|
+
Options.partial_value,
|
|
38
|
+
Options.timeout
|
|
39
|
+
]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.example_code
|
|
43
|
+
[
|
|
44
|
+
<<~EXAMPLE
|
|
45
|
+
rustore_publish_settings(
|
|
46
|
+
package_name: "com.example.app",
|
|
47
|
+
key_id: ENV["RUSTORE_KEY_ID"],
|
|
48
|
+
private_key: ENV["RUSTORE_PRIVATE_KEY"],
|
|
49
|
+
version_id: 12345,
|
|
50
|
+
partial_value: 50
|
|
51
|
+
)
|
|
52
|
+
EXAMPLE
|
|
53
|
+
]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.is_supported?(platform)
|
|
57
|
+
platform == :android
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.run(params)
|
|
61
|
+
helper = Helper::UploadToRuStoreHelper
|
|
62
|
+
token = helper.fetch_token(key_id: params[:key_id], private_key: params[:private_key])
|
|
63
|
+
|
|
64
|
+
settings = {
|
|
65
|
+
publish_type: params[:publish_type],
|
|
66
|
+
publish_datetime: params[:publish_datetime],
|
|
67
|
+
partial_value: params[:partial_value]
|
|
68
|
+
}.compact
|
|
69
|
+
UI.user_error!('Не передано ни одной настройки публикации') if settings.empty?
|
|
70
|
+
|
|
71
|
+
helper.update_publish_settings(
|
|
72
|
+
token: token,
|
|
73
|
+
package_name: params[:package_name],
|
|
74
|
+
version_id: params[:version_id],
|
|
75
|
+
**settings
|
|
76
|
+
)
|
|
77
|
+
params[:version_id]
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|