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: 188a6a5731e82ab480b88165bcd3d34e6bcdb6fa237b3516a32e1d642f24a25b
4
- data.tar.gz: bdc3ac1405edb623d3aba986da974580f7eca7ea940968b44185ffb5eec62173
3
+ metadata.gz: 8e1130672ff882105f71b7fbd7b9b8e999cd79c791b066531794870299b7b4df
4
+ data.tar.gz: 0143f940ab249000c845b39750728c49d17063a29ae5fcfba87c2bed45470c29
5
5
  SHA512:
6
- metadata.gz: df9e0563eeb904bf300c2de64b52a663fd318ab5b5f5e0f262f27b2d40ef287df4cd445335974126acb9eeb11e6ac4385d63f3907dd54b658f18c3e5b3363cbf
7
- data.tar.gz: 5553edbfa6321bec486381545da6a986723cb418af422a574c764bb5ac0c8438af3473e647af7dbc2db706ca508dd641fc8e47e023e8868c28dc594fdce1be03
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: "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)
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: "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 (опционально)
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
- 1. Получит JWE-токен RuStore
44
- 2. Удалит все существующие черновики приложения
45
- 3. Создаст новый драфт
46
- 4. Загрузит указанные сборки (AAB и/или APK)
47
- 5. Закоммитит драфт и отправит на модерацию
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` | Уникальный идентификатор пакета (например, `com.example.app`) | да | `String` |
54
- | `key_id` | Идентификатор RSA-ключа в консоли RuStore | да | `String` |
55
- | `private_key` | PEM-строка RSA-приватного ключа (без обёрток `BEGIN/END`) | да | `String` |
56
- | `publish_type` | Тип публикации: `MANUAL`, `DELAYED` или `INSTANTLY` (по умолчанию `INSTANTLY`) | нет | `String` |
57
- | `changelog_path` | Путь к `.txt`-файлу с описанием «Что нового?» (макс. 500 символов) | нет | `String` |
58
- | `aab` | Путь к Android App Bundle (`.aab`). Если указан, APK не загружается | нет | `String` |
59
- | `gms_apk` | Путь к APK с Google Mobile Services. Используется, если не указан `aab` | нет | `String` |
60
- | `hms_apk` | Путь к APK с Huawei Mobile Services (опционально) | нет | `String` |
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 and optional APK bundles to RuStore, cleans up drafts before publishing.'
14
+ 'Uploads AAB/APK release to RuStore'
15
15
  end
16
16
 
17
17
  def self.available_options
18
18
  [
19
- FastlaneCore::ConfigItem.new(key: :package_name, env_name: 'RUSTORE_PACKAGE_NAME', description: 'App package (e.g. com.example.app)', optional: false, type: String),
20
- FastlaneCore::ConfigItem.new(key: :key_id, env_name: 'RUSTORE_KEY_ID', description: 'RuStore API key ID', optional: false, type: String),
21
- FastlaneCore::ConfigItem.new(key: :private_key, env_name: 'RUSTORE_PRIVATE_KEY', description: 'RUStore RSA private key (PEM)', optional: false, type: String),
22
- FastlaneCore::ConfigItem.new(key: :publish_type, env_name: 'RUSTORE_PUBLISH_TYPE', description: 'Publication type: MANUAL/DELAYED/INSTANTLY', optional: true, type: String),
23
- FastlaneCore::ConfigItem.new(key: :aab, env_name: 'RUSTORE_AAB', description: 'Path to AAB', optional: true, type: String),
24
- FastlaneCore::ConfigItem.new(key: :gms_apk, env_name: 'RUSTORE_GMS_APK', description: 'Path to GMS APK', optional: true, type: String),
25
- FastlaneCore::ConfigItem.new(key: :hms_apk, env_name: 'RUSTORE_HMS_APK', description: 'Path to HMS APK (optional)', optional: true, type: String),
26
- FastlaneCore::ConfigItem.new(key: :changelog_path, env_name: 'RUSTORE_CHANGELOG_PATH', description: 'Path to changelog .txt', optional: true, type: String)
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(key_id: key_id, timestamp: timestamp, private_key: private_key)
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.dig('body', 'jwe') || UI.user_error!('Не удалось получить токен из RuStore')
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
- payload[:whatsNew] = read_changelog(changelog_path) if changelog_path
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'] = true if service_type == 'GMS'
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
- rsa = OpenSSL::PKey::RSA.new(private_key)
116
- sig = rsa.sign(OpenSSL::Digest::SHA512.new, key_id + timestamp)
117
- Base64.strict_encode64(sig)
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
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module UploadToRuStore
3
- VERSION = "1.0.2"
3
+ VERSION = "1.0.6"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane-plugin-upload_to_ru_store
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kopylov Ivan