fastlane-plugin-rustore_publish 0.3.1 → 0.3.2

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: e78e12cd03800029213198db6dc3653ce95c8ea4dd6471d5e067c9ff79b562e1
4
- data.tar.gz: 512f9299d5414564a7c59eeb219fbdedda933d2057941de7d042dc5d013f9183
3
+ metadata.gz: 203b7b3c7f50c633d3c0f66b8cd2816db90124d0119cd38c76dac7ab944a8688
4
+ data.tar.gz: 5e19d93495542d749147529642d383c1b02fcff532c81fe280e1c7bf6a3eafbf
5
5
  SHA512:
6
- metadata.gz: 60b304a1d22e6d5c7edf6ad72fea7c7095c611866c4554f06728eb661f5f53f5bbf6d2b09e70a5b71a77cd8554453d9cd423a55cc1594be8064c7368b5ac4366
7
- data.tar.gz: 9be1cb11bce6018d2254478dd0a3d9b39cb35bc4c90365c828fb73a0cc8ffbdfbde4b38307fa30db399e2a97137ca5a264f6562b9ba03ab89f9a9b78cdc7a89f
6
+ metadata.gz: fce936df0c5381b6ab1f30f0874968e049dc5dd766633d74f070cf2ef15c96e975e1a4425cef5847ac2af74cead7d07d1001c7f0170e9f95c3674e344131def5
7
+ data.tar.gz: f359c5be965c76108185e8e8d1bc0258b24087b3cc46381f8b97e08e0ae84f98f84dfc6c4d7ccb97abf37daa872fd7f7182ed82b50e4eecee712514641e6ce8f
data/README.md CHANGED
@@ -1,10 +1,44 @@
1
1
  # RuStore fastlane plugin — publish Android apps to RuStore from CI
2
2
 
3
+ [![Gem Version](https://img.shields.io/gem/v/fastlane-plugin-rustore_publish)](https://rubygems.org/gems/fastlane-plugin-rustore_publish)
4
+ [![Downloads](https://img.shields.io/gem/dt/fastlane-plugin-rustore_publish)](https://rubygems.org/gems/fastlane-plugin-rustore_publish)
5
+ [![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-rustore_publish)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
+
3
8
  ![RuStore × fastlane — automated Android publishing](docs/assets/cover.jpg)
4
9
 
5
10
  Fastlane plugin for publishing Android applications to [RuStore](https://rustore.ru) — the Russian app store — via the [RuStore Public API](https://www.rustore.ru/help/work-with-rustore-api/api-upload-publication-app). Uses the new `public-api-m.rustore.ru` domain with the Russian Trusted Root CA bundled, so nothing has to be installed on the runner.
6
11
 
7
- Gem: `fastlane-plugin-rustore_publish` · Action: `rustore_upload`
12
+ Gem: `fastlane-plugin-rustore_publish` · Actions: `rustore_upload`, `rustore_check`
13
+
14
+ ## Quick start
15
+
16
+ ```sh
17
+ fastlane add_plugin rustore_publish
18
+ ```
19
+
20
+ ```ruby
21
+ # fastlane/Fastfile
22
+ lane :deploy_rustore do
23
+ rustore_upload(
24
+ package_name: "com.example.app",
25
+ key_id: ENV["RUSTORE_KEY_ID"],
26
+ private_key: ENV["RUSTORE_PRIVATE_KEY"],
27
+ aab_path: "app/build/outputs/bundle/release/app-release.aab",
28
+ whats_new: "Bug fixes and improvements"
29
+ )
30
+ end
31
+ ```
32
+
33
+ The key comes from RuStore Console → Company → API RuStore, see [Setup](#setup-rustore-api-credentials).
34
+
35
+ ## По-русски
36
+
37
+ Плагин fastlane для публикации Android-приложений в RuStore из CI: создаёт черновик, загружает AAB и HMS APK, отправляет на модерацию. Работает через новый домен `public-api-m.rustore.ru`, на который RuStore переводит Public API; корневой сертификат Минцифры уже внутри плагина, ставить его на раннер не нужно. В логах CI видно, сколько шла отправка файла и сколько RuStore его проверял, а ошибки (таймаут, TLS, 504, новые чувствительные разрешения) приходят с подсказкой, что делать.
38
+
39
+ Для пробного прогона без выкладки есть `rustore_check`: получает токен и показывает последние версии приложения, ничего не загружая; 404 на списке версий считается успехом (ключ рабочий, версий через API пока нет).
40
+
41
+ Установка — `fastlane add_plugin rustore_publish`, пример — в [Quick start](#quick-start) выше. Вопросы и баги — в [Issues](https://github.com/gorban-dev/rustore-fastlane-plugin/issues), можно на русском.
8
42
 
9
43
  ## Features
10
44
 
@@ -218,6 +252,39 @@ end
218
252
 
219
253
  ---
220
254
 
255
+ ## Check access (`rustore_check`)
256
+
257
+ A dry run for CI: gets a token, reads the first page of the app's versions and logs up to 5 latest of them. Creates, deletes and uploads nothing, and does not touch drafts. Use it to verify that the key in CI works and covers the package before a real release.
258
+
259
+ ```ruby
260
+ lane :check_rustore do
261
+ rustore_check(
262
+ package_name: "com.example.app",
263
+ key_id: ENV["RUSTORE_KEY_ID"],
264
+ private_key: ENV["RUSTORE_PRIVATE_KEY"] # or private_key_path:
265
+ )
266
+ end
267
+ ```
268
+
269
+ ```
270
+ [RuStore] ✓ JWE token obtained
271
+ [RuStore] versionId=1234 versionName=2.4.0 versionCode=240 versionStatus=ACTIVE
272
+ [RuStore] versionId=1201 versionName=2.3.1 versionCode=231 versionStatus=PREVIOUS_ACTIVE
273
+ [RuStore] ✓ RuStore OK — ключ рабочий, версий: 2
274
+ ```
275
+
276
+ | Result | Behaviour |
277
+ |---|---|
278
+ | Versions found | Logs up to 5 latest, returns the Array of version Hashes |
279
+ | Empty list | Success, returns `[]` |
280
+ | HTTP 404 on the version list | Success: the key is valid and covers the package, but RuStore opens the versions API only after the first publication from RuStore Console. Returns `[]` |
281
+ | `private_key` and `private_key_path` both missing or blank | Fails before any request: `RuStore: private_key или private_key_path не задан` |
282
+ | Auth error, 401/403, 5xx, network | Fails with a hint, like `rustore_upload` |
283
+
284
+ The 404 case is detected by HTTP status, not by message text.
285
+
286
+ ---
287
+
221
288
  ## CI/CD integration
222
289
 
223
290
  ### GitLab CI
@@ -268,6 +335,28 @@ lane :deploy_rustore do
268
335
  end
269
336
  ```
270
337
 
338
+ **Checking the key without a release.** A manual job that runs `rustore_check`, e.g. after rotating the key:
339
+
340
+ ```yaml
341
+ check_rustore:
342
+ stage: deploy
343
+ script:
344
+ - bundle exec fastlane check_rustore
345
+ rules:
346
+ - when: manual
347
+ ```
348
+
349
+ ```ruby
350
+ # fastlane/Fastfile
351
+ lane :check_rustore do
352
+ rustore_check(
353
+ package_name: "com.example.app",
354
+ key_id: ENV["RUSTORE_KEY_ID"],
355
+ private_key_path: ENV["RUSTORE_PRIVATE_KEY"]
356
+ )
357
+ end
358
+ ```
359
+
271
360
  **Storing the private key in GitLab:**
272
361
 
273
362
  - Go to **Settings → CI/CD → Variables**
@@ -0,0 +1,113 @@
1
+ require "fastlane/action"
2
+ require_relative "../helper/rustore_auth"
3
+ require_relative "../helper/rustore_client"
4
+ require_relative "../helper/rustore_logger"
5
+ require_relative "../helper/rustore_options"
6
+
7
+ module Fastlane
8
+ module Actions
9
+ # Dry run for CI: checks that the RuStore API key works and covers the
10
+ # package. Reads the version list only; never creates, deletes or uploads.
11
+ class RustoreCheckAction < Action
12
+ TOTAL_STEPS = 2
13
+ SHOWN_VERSION_COUNT = 5
14
+
15
+ def self.run(params)
16
+ private_key = RustorePublish::RustoreOptions.private_key!(params)
17
+ package_name = params[:package_name]
18
+ logger = RustorePublish::RustoreLogger.new(total_steps: TOTAL_STEPS)
19
+
20
+ # ── Step 1: Authentication ─────────────────────────────────────────────
21
+ logger.step(1, "Authentication")
22
+ auth = RustorePublish::RustoreAuth.new(
23
+ key_id: params[:key_id],
24
+ private_key: private_key,
25
+ logger: logger
26
+ )
27
+ auth.refresh!
28
+ logger.success("JWE token obtained")
29
+
30
+ # ── Step 2: Versions ───────────────────────────────────────────────────
31
+ logger.step(2, "Versions")
32
+ client = RustorePublish::RustoreClient.new(auth: auth, logger: logger)
33
+
34
+ begin
35
+ versions = client.list_versions(package_name: package_name)
36
+ rescue RustorePublish::RustoreClient::NotFoundError
37
+ logger.success(
38
+ "RuStore OK — авторизация прошла, ключ покрывает #{package_name}; активных версий нет, " \
39
+ "API версий откроется после первой публикации в консоли"
40
+ )
41
+ logger.finalize
42
+ return []
43
+ end
44
+
45
+ if versions.empty?
46
+ logger.success("RuStore OK — авторизация прошла, версий у #{package_name} нет")
47
+ else
48
+ latest = versions.sort_by { |v| -v["versionId"].to_i }.first(SHOWN_VERSION_COUNT)
49
+ latest.each { |v| logger.info(format_version(v)) }
50
+ logger.success("RuStore OK — ключ рабочий, версий: #{versions.size}")
51
+ end
52
+
53
+ logger.finalize
54
+ versions
55
+ end
56
+
57
+ def self.format_version(version)
58
+ %w[versionId versionName versionCode versionStatus]
59
+ .map { |field| "#{field}=#{version[field]}" }
60
+ .join(" ")
61
+ end
62
+
63
+ # ── Parameter definitions ────────────────────────────────────────────────
64
+
65
+ def self.available_options
66
+ RustorePublish::RustoreOptions.credentials
67
+ end
68
+
69
+ def self.description
70
+ "Check that the RuStore API key works for a package, without uploading anything"
71
+ end
72
+
73
+ def self.details
74
+ <<~DETAILS
75
+ Gets a token and reads the first page of the app's versions from the
76
+ RuStore Public API, then logs up to 5 latest of them.
77
+
78
+ HTTP 404 on the version list counts as success: the key is valid and
79
+ covers the package, but RuStore opens the versions API only after the
80
+ first publication from RuStore Console.
81
+
82
+ Does not create, delete or upload anything and does not touch drafts.
83
+ DETAILS
84
+ end
85
+
86
+ def self.return_value
87
+ "Array of version hashes as returned by RuStore ([] when the app has no versions)"
88
+ end
89
+
90
+ def self.authors
91
+ ["Sergey Gorban"]
92
+ end
93
+
94
+ def self.example_code
95
+ [
96
+ 'rustore_check(
97
+ package_name: "com.example.app",
98
+ key_id: ENV["RUSTORE_KEY_ID"],
99
+ private_key: ENV["RUSTORE_PRIVATE_KEY"]
100
+ )'
101
+ ]
102
+ end
103
+
104
+ def self.category
105
+ :production
106
+ end
107
+
108
+ def self.is_supported?(platform)
109
+ platform == :android
110
+ end
111
+ end
112
+ end
113
+ end
@@ -2,6 +2,7 @@ require "fastlane/action"
2
2
  require_relative "../helper/rustore_auth"
3
3
  require_relative "../helper/rustore_client"
4
4
  require_relative "../helper/rustore_logger"
5
+ require_relative "../helper/rustore_options"
5
6
 
6
7
  module Fastlane
7
8
  module Actions
@@ -173,41 +174,7 @@ module Fastlane
173
174
  # ── Parameter definitions ────────────────────────────────────────────────
174
175
 
175
176
  def self.available_options
176
- [
177
- # ── Authentication ─────────────────────────────────────────────────
178
- FastlaneCore::ConfigItem.new(
179
- key: :key_id,
180
- env_name: "RUSTORE_KEY_ID",
181
- description: "API key ID from RuStore Console → Company → API RuStore",
182
- type: String,
183
- optional: false
184
- ),
185
- FastlaneCore::ConfigItem.new(
186
- key: :private_key_path,
187
- env_name: "RUSTORE_PRIVATE_KEY_PATH",
188
- description: "Path to RSA private key PEM file (mutually exclusive with private_key)",
189
- type: String,
190
- optional: true,
191
- verify_block: proc { |v| UI.user_error!("private_key_path file not found: #{v}") unless File.exist?(v) }
192
- ),
193
- FastlaneCore::ConfigItem.new(
194
- key: :private_key,
195
- env_name: "RUSTORE_PRIVATE_KEY",
196
- description: "RSA private key PEM content (mutually exclusive with private_key_path)",
197
- type: String,
198
- optional: true,
199
- sensitive: true
200
- ),
201
-
202
- # ── App identity ───────────────────────────────────────────────────
203
- FastlaneCore::ConfigItem.new(
204
- key: :package_name,
205
- env_name: "RUSTORE_PACKAGE_NAME",
206
- description: "Application package name (e.g. com.example.app)",
207
- type: String,
208
- optional: false
209
- ),
210
-
177
+ RustorePublish::RustoreOptions.credentials + [
211
178
  # ── Build files ────────────────────────────────────────────────────
212
179
  FastlaneCore::ConfigItem.new(
213
180
  key: :aab_path,
@@ -25,6 +25,17 @@ module Fastlane
25
25
  SERVICES_TYPE_UNKNOWN = "Unknown"
26
26
  SERVICES_TYPE_HMS = "HMS"
27
27
 
28
+ # Raised by #list_versions when RuStore answers 404. The caller decides
29
+ # whether that is a failure; the check is by HTTP status, not by text.
30
+ class NotFoundError < StandardError
31
+ attr_reader :path
32
+
33
+ def initialize(path)
34
+ @path = path
35
+ super("RuStore returned 404 for #{path}")
36
+ end
37
+ end
38
+
28
39
  def initialize(auth:, logger: nil, upload_timeout: DEFAULT_UPLOAD_TIMEOUT, wait_log_interval: WAIT_LOG_INTERVAL)
29
40
  @auth = auth
30
41
  @logger = logger || RustoreLogger.new
@@ -67,6 +78,17 @@ module Fastlane
67
78
  versions.find { |v| v["versionStatus"] == "DRAFT" }
68
79
  end
69
80
 
81
+ # GET /application/{package}/version?page=0&size={page_size}
82
+ # Reads the first page of the app's versions.
83
+ #
84
+ # @return [Array<Hash>] versions as RuStore returns them, [] if there are none
85
+ # @raise [NotFoundError] on HTTP 404
86
+ def list_versions(package_name:, page_size: 20)
87
+ resp = get("application/#{package_name}/version?page=0&size=#{page_size}", raise_not_found: true)
88
+ versions = resp.dig("body", "content")
89
+ versions.is_a?(Array) ? versions : []
90
+ end
91
+
70
92
  # DELETE /application/{package}/version/{versionId}
71
93
  def delete_draft(package_name:, version_id:)
72
94
  delete("application/#{package_name}/version/#{version_id}")
@@ -122,10 +144,12 @@ module Fastlane
122
144
 
123
145
  # ── HTTP Helpers ─────────────────────────────────────────────────────────
124
146
 
125
- def get(path)
147
+ def get(path, raise_not_found: false)
126
148
  resp = with_network_errors(path) do
127
149
  @conn.get(path) { |r| r.headers["Public-Token"] = @auth.token }
128
150
  end
151
+ raise NotFoundError, path if raise_not_found && resp.status == 404
152
+
129
153
  handle_response(resp, path)
130
154
  end
131
155
 
@@ -0,0 +1,53 @@
1
+ require "fastlane_core"
2
+
3
+ module Fastlane
4
+ module RustorePublish
5
+ # Options shared by all RuStore actions: API key and package name.
6
+ module RustoreOptions
7
+ def self.credentials
8
+ [
9
+ FastlaneCore::ConfigItem.new(
10
+ key: :key_id,
11
+ env_name: "RUSTORE_KEY_ID",
12
+ description: "API key ID from RuStore Console → Company → API RuStore",
13
+ type: String,
14
+ optional: false
15
+ ),
16
+ FastlaneCore::ConfigItem.new(
17
+ key: :private_key_path,
18
+ env_name: "RUSTORE_PRIVATE_KEY_PATH",
19
+ description: "Path to RSA private key PEM file (mutually exclusive with private_key)",
20
+ type: String,
21
+ optional: true,
22
+ verify_block: proc { |v| UI.user_error!("private_key_path file not found: #{v}") unless File.exist?(v) }
23
+ ),
24
+ FastlaneCore::ConfigItem.new(
25
+ key: :private_key,
26
+ env_name: "RUSTORE_PRIVATE_KEY",
27
+ description: "RSA private key PEM content (mutually exclusive with private_key_path)",
28
+ type: String,
29
+ optional: true,
30
+ sensitive: true
31
+ ),
32
+ FastlaneCore::ConfigItem.new(
33
+ key: :package_name,
34
+ env_name: "RUSTORE_PACKAGE_NAME",
35
+ description: "Application package name (e.g. com.example.app)",
36
+ type: String,
37
+ optional: false
38
+ )
39
+ ]
40
+ end
41
+
42
+ # Returns the PEM from private_key or private_key_path. Fails before any
43
+ # request when neither is set or the key is blank, so an empty CI
44
+ # variable does not end up as an OpenSSL error.
45
+ def self.private_key!(params)
46
+ key = params[:private_key].to_s
47
+ key = File.read(params[:private_key_path]) if key.strip.empty? && params[:private_key_path]
48
+ UI.user_error!("RuStore: private_key или private_key_path не задан") if key.to_s.strip.empty?
49
+ key
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module RustorePublish
3
- VERSION = "0.3.1"
3
+ VERSION = "0.3.2"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane-plugin-rustore_publish
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergey Gorban
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-30 00:00:00.000000000 Z
11
+ date: 2026-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fastlane
@@ -108,12 +108,14 @@ files:
108
108
  - LICENSE
109
109
  - README.md
110
110
  - lib/fastlane/plugin/rustore_publish.rb
111
+ - lib/fastlane/plugin/rustore_publish/actions/rustore_check.rb
111
112
  - lib/fastlane/plugin/rustore_publish/actions/rustore_upload.rb
112
113
  - lib/fastlane/plugin/rustore_publish/certs/russian_trusted_root_ca.crt
113
114
  - lib/fastlane/plugin/rustore_publish/helper/rustore_auth.rb
114
115
  - lib/fastlane/plugin/rustore_publish/helper/rustore_client.rb
115
116
  - lib/fastlane/plugin/rustore_publish/helper/rustore_endpoint.rb
116
117
  - lib/fastlane/plugin/rustore_publish/helper/rustore_logger.rb
118
+ - lib/fastlane/plugin/rustore_publish/helper/rustore_options.rb
117
119
  - lib/fastlane/plugin/rustore_publish/version.rb
118
120
  homepage: https://github.com/gorban-dev/rustore-fastlane-plugin
119
121
  licenses: