fastlane-plugin-upload_to_ru_store 1.0.2 → 1.0.6
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: 8e1130672ff882105f71b7fbd7b9b8e999cd79c791b066531794870299b7b4df
|
4
|
+
data.tar.gz: 0143f940ab249000c845b39750728c49d17063a29ae5fcfba87c2bed45470c29
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3702c06e29af20d29dcb0746dbb1263d8db08c7376054ce5937cc794ed9c537c25f28cf9df99705f10b2803938758480d27891833844a08b5e158356a398633
|
7
|
+
data.tar.gz: 44eabea7c169bf03fad4d73f8087cc1410467fc16e0c2cc5efd5a6411e5615b11412d2092142df5bd9c73bb1305870d8e50d114a119135f200e7f11eb5e73e8b
|
data/README.md
CHANGED
@@ -8,11 +8,11 @@
|
|
8
8
|
|
9
9
|
## Возможности
|
10
10
|
|
11
|
-
- Аутентификация через RSA-SHA512 и получение JWE-токена RuStore API
|
12
|
-
- Автоматическая очистка всех незавершённых черновиков (draft) перед созданием новой версии
|
13
|
-
- Создание драфта с указанием типа публикации и текста «Что нового»
|
14
|
-
- Загрузка AAB или APK (GMS и/или HMS) в один или два шага
|
15
|
-
- Фиксация (commit) драфта и отправка на модерацию
|
11
|
+
- Аутентификация через RSA-SHA512 и получение JWE-токена RuStore API
|
12
|
+
- Автоматическая очистка всех незавершённых черновиков (draft) перед созданием новой версии
|
13
|
+
- Создание драфта с указанием типа публикации и текста «Что нового»
|
14
|
+
- Загрузка AAB или APK (GMS и/или HMS) в один или два шага
|
15
|
+
- Фиксация (commit) драфта и отправка на модерацию
|
16
16
|
|
17
17
|
## Установка
|
18
18
|
|
@@ -27,42 +27,44 @@ fastlane add_plugin upload_to_ru_store
|
|
27
27
|
```ruby
|
28
28
|
lane :publish_to_rustore do
|
29
29
|
upload_to_ru_store(
|
30
|
-
package_name:
|
31
|
-
key_id:
|
32
|
-
private_key:
|
33
|
-
publish_type:
|
30
|
+
package_name: "com.example.app",
|
31
|
+
key_id: ENV["RUSTORE_KEY_ID"],
|
32
|
+
private_key: ENV["RUSTORE_PRIVATE_KEY"],
|
33
|
+
publish_type: "INSTANTLY", # MANUAL, DELAYED или INSTANTLY (по умолчанию INSTANTLY)
|
34
34
|
changelog_path: "metadata/android/ru-RU/changelog.txt", # опционально, макс. 500 символов
|
35
|
-
aab:
|
36
|
-
gms_apk:
|
37
|
-
hms_apk:
|
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 (опционально)
|
38
38
|
)
|
39
39
|
end
|
40
40
|
```
|
41
41
|
|
42
42
|
**Плагин автоматически:**
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
43
|
+
|
44
|
+
1. Получит JWE-токен RuStore
|
45
|
+
2. Удалит все существующие черновики приложения
|
46
|
+
3. Создаст новый драфт
|
47
|
+
4. Загрузит указанные сборки (AAB и/или APK)
|
48
|
+
5. Закоммитит драфт и отправит на модерацию
|
48
49
|
|
49
50
|
## Опции
|
50
51
|
|
51
|
-
| Параметр
|
52
|
-
|
53
|
-
| `package_name`
|
54
|
-
| `key_id`
|
55
|
-
| `private_key`
|
56
|
-
| `publish_type`
|
57
|
-
| `
|
58
|
-
| `
|
59
|
-
| `
|
60
|
-
| `
|
52
|
+
| Параметр | Описание | Обязательный | Формат |
|
53
|
+
|--------------------|--------------------------------------------------------------------------------|----------------------------------------|----------|
|
54
|
+
| `package_name` | Уникальный идентификатор пакета (например, `com.example.app`) | да | `String` |
|
55
|
+
| `key_id` | Идентификатор RSA-ключа в консоли RuStore | да | `String` |
|
56
|
+
| `private_key` | PEM-строка RSA-приватного ключа | да | `String` |
|
57
|
+
| `publish_type` | Тип публикации: `MANUAL`, `DELAYED` или `INSTANTLY` (по умолчанию `INSTANTLY`) | нет | `String` |
|
58
|
+
| `publish_datetime` | Дата и время для отложенной публикации, ISO8601 | да, только если publish_type = DELAYED | `String` |
|
59
|
+
| `changelog_path` | Путь к `.txt`-файлу с описанием «Что нового?» (макс. 500 символов) | нет | `String` |
|
60
|
+
| `aab` | Путь к Android App Bundle (`.aab`). Если указан, APK не загружается | нет | `String` |
|
61
|
+
| `gms_apk` | Путь к APK с Google Mobile Services. Используется, если не указан `aab` | нет | `String` |
|
62
|
+
| `hms_apk` | Путь к APK с Huawei Mobile Services | нет | `String` |
|
61
63
|
|
62
64
|
## Требования
|
63
65
|
|
64
|
-
- Ruby >= 2.6
|
65
|
-
- Fastlane >= 2.214.0
|
66
|
+
- Ruby >= 2.6
|
67
|
+
- Fastlane >= 2.214.0
|
66
68
|
|
67
69
|
## Лицензия
|
68
70
|
|
@@ -70,5 +72,5 @@ MIT. Смотрите [LICENSE](LICENSE)
|
|
70
72
|
|
71
73
|
## Полезные ссылки
|
72
74
|
|
73
|
-
- [Документация RuStore API](https://help.rustore.ru/rustore/for_developers/work_with_RuStore_API)
|
75
|
+
- [Документация RuStore API](https://help.rustore.ru/rustore/for_developers/work_with_RuStore_API)
|
74
76
|
- [Fastlane Plugins Guide](https://docs.fastlane.tools/plugins/create-plugin/)
|
@@ -11,19 +11,81 @@ module Fastlane
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def self.description
|
14
|
-
'Uploads AAB
|
14
|
+
'Uploads AAB/APK release to RuStore'
|
15
15
|
end
|
16
16
|
|
17
17
|
def self.available_options
|
18
18
|
[
|
19
|
-
FastlaneCore::ConfigItem.new(
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
FastlaneCore::ConfigItem.new(
|
19
|
+
FastlaneCore::ConfigItem.new(
|
20
|
+
key: :package_name,
|
21
|
+
env_name: 'RUSTORE_PACKAGE_NAME',
|
22
|
+
description: 'App package (e.g. com.example.app)',
|
23
|
+
optional: false,
|
24
|
+
type: String
|
25
|
+
),
|
26
|
+
FastlaneCore::ConfigItem.new(
|
27
|
+
key: :key_id,
|
28
|
+
env_name: 'RUSTORE_KEY_ID',
|
29
|
+
description: 'RuStore API key ID',
|
30
|
+
optional: false,
|
31
|
+
type: String
|
32
|
+
),
|
33
|
+
FastlaneCore::ConfigItem.new(
|
34
|
+
key: :private_key,
|
35
|
+
env_name: 'RUSTORE_PRIVATE_KEY',
|
36
|
+
description: 'RUStore RSA private key (PEM)',
|
37
|
+
optional: false,
|
38
|
+
type: String
|
39
|
+
),
|
40
|
+
FastlaneCore::ConfigItem.new(
|
41
|
+
key: :publish_type,
|
42
|
+
env_name: 'RUSTORE_PUBLISH_TYPE',
|
43
|
+
description: 'Publication type: MANUAL/DELAYED/INSTANTLY',
|
44
|
+
optional: true,
|
45
|
+
type: String
|
46
|
+
),
|
47
|
+
FastlaneCore::ConfigItem.new(
|
48
|
+
key: :publish_datetime,
|
49
|
+
env_name: 'RUSTORE_PUBLISH_DATETIME',
|
50
|
+
description: "Дата и время отложенной публикации в формате yyyy-MM-dd'T'HH:mm:ssXXX (только для publish_type = DELAYED)",
|
51
|
+
optional: true,
|
52
|
+
type: String,
|
53
|
+
verify_block: proc do |value|
|
54
|
+
begin
|
55
|
+
DateTime.iso8601(value)
|
56
|
+
rescue ArgumentError
|
57
|
+
UI.user_error!("Неверный формат publish_datetime. Ожидается ISO8601, например 2025-06-01T12:00:00+03:00")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
),
|
61
|
+
FastlaneCore::ConfigItem.new(
|
62
|
+
key: :aab,
|
63
|
+
env_name: 'RUSTORE_AAB',
|
64
|
+
description: 'Path to AAB',
|
65
|
+
optional: true,
|
66
|
+
type: String
|
67
|
+
),
|
68
|
+
FastlaneCore::ConfigItem.new(
|
69
|
+
key: :gms_apk,
|
70
|
+
env_name: 'RUSTORE_GMS_APK',
|
71
|
+
description: 'Path to GMS APK',
|
72
|
+
optional: true,
|
73
|
+
type: String
|
74
|
+
),
|
75
|
+
FastlaneCore::ConfigItem.new(
|
76
|
+
key: :hms_apk,
|
77
|
+
env_name: 'RUSTORE_HMS_APK',
|
78
|
+
description: 'Path to HMS APK (optional)',
|
79
|
+
optional: true,
|
80
|
+
type: String
|
81
|
+
),
|
82
|
+
FastlaneCore::ConfigItem.new(
|
83
|
+
key: :changelog_path,
|
84
|
+
env_name: 'RUSTORE_CHANGELOG_PATH',
|
85
|
+
description: 'Path to changelog .txt',
|
86
|
+
optional: true,
|
87
|
+
type: String
|
88
|
+
)
|
27
89
|
]
|
28
90
|
end
|
29
91
|
|
@@ -23,13 +23,19 @@ module Fastlane
|
|
23
23
|
# @return [String] JWE token
|
24
24
|
def fetch_token(key_id:, private_key:)
|
25
25
|
timestamp = DateTime.now.iso8601(3)
|
26
|
-
signature = rsa_sign(
|
27
|
-
|
26
|
+
signature = rsa_sign(
|
27
|
+
key_id: key_id,
|
28
|
+
timestamp: timestamp,
|
29
|
+
private_key: private_key
|
30
|
+
)
|
28
31
|
response = client.post('/public/auth/') do |req|
|
29
32
|
req.body = { keyId: key_id, timestamp: timestamp, signature: signature }
|
30
33
|
end
|
31
34
|
debug(response)
|
32
|
-
response.
|
35
|
+
data = response.body
|
36
|
+
jwe = data.dig('body', 'jwe')
|
37
|
+
UI.user_error!('Не удалось получить токен из RuStore') unless jwe
|
38
|
+
jwe
|
33
39
|
end
|
34
40
|
|
35
41
|
# Remove all existing drafts for package
|
@@ -47,10 +53,14 @@ module Fastlane
|
|
47
53
|
# @param publish_type [String, nil]
|
48
54
|
# @param changelog_path [String, nil]
|
49
55
|
# @return [Integer] draft_id
|
50
|
-
def create_draft(token:, package_name:, publish_type: nil, changelog_path: nil)
|
56
|
+
def create_draft(token:, package_name:, publish_type: nil, publish_datetime: nil, changelog_path: nil)
|
51
57
|
payload = {}
|
52
58
|
payload[:publishType] = publish_type if publish_type
|
53
|
-
|
59
|
+
if publish_type == 'DELAYED'
|
60
|
+
UI.user_error!('Для publish_type = DELAYED обязательно указывать publish_datetime') unless publish_datetime
|
61
|
+
payload[:publishDateTime] = publish_datetime
|
62
|
+
end
|
63
|
+
payload[:whatsNew] = read_changelog(changelog_path) if changelog_path
|
54
64
|
|
55
65
|
response = client.post("/public/v1/application/#{package_name}/version") do |req|
|
56
66
|
req.headers['Public-Token'] = token
|
@@ -74,7 +84,7 @@ module Fastlane
|
|
74
84
|
response = client.post(endpoint) do |req|
|
75
85
|
req.headers['Public-Token'] = token
|
76
86
|
req.params['servicesType'] = service_type if service_type
|
77
|
-
req.params['isMainApk']
|
87
|
+
req.params['isMainApk'] = true if service_type == 'GMS'
|
78
88
|
req.body = { file: part }
|
79
89
|
end
|
80
90
|
debug(response)
|
@@ -112,9 +122,24 @@ module Fastlane
|
|
112
122
|
|
113
123
|
# Sign payload with RSA-SHA512
|
114
124
|
def rsa_sign(key_id:, timestamp:, private_key:)
|
115
|
-
|
116
|
-
|
117
|
-
|
125
|
+
raw = private_key.strip
|
126
|
+
pem = if raw.include?('-----BEGIN')
|
127
|
+
raw
|
128
|
+
else
|
129
|
+
b64 = raw.gsub(/\s+/, '')
|
130
|
+
body = b64.scan(/.{1,64}/).join("\n")
|
131
|
+
<<~PEM
|
132
|
+
-----BEGIN RSA PRIVATE KEY-----
|
133
|
+
#{body}
|
134
|
+
-----END RSA PRIVATE KEY-----
|
135
|
+
PEM
|
136
|
+
end
|
137
|
+
|
138
|
+
key = OpenSSL::PKey::RSA.new(pem)
|
139
|
+
digest = OpenSSL::Digest::SHA512.new
|
140
|
+
signature = key.sign(digest, key_id + timestamp)
|
141
|
+
|
142
|
+
Base64.strict_encode64(signature)
|
118
143
|
end
|
119
144
|
|
120
145
|
# List draft IDs
|