lepus 0.1.2 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 116d40b6e5b9fe75a3d1a97d6a99f73ea17f708d78c3302b2c8d6ea7f1d46b4f
4
- data.tar.gz: '084d2c433cb8357573484b9750dfb2fad6c2c2e9b3f1ba34304a18a3a7858b17'
3
+ metadata.gz: 67f07eb800872caf73d2f74330322cfb4682f740cf60734405f399f815368a9f
4
+ data.tar.gz: 96257edb75021e9154cef73212e26f450f508ad7aa0fb5659756efebb8b3cb5a
5
5
  SHA512:
6
- metadata.gz: fde0c60704a31a5f6686b2344f5489d41111dcb4d1a142e1771941abe9d6ecba60e88cfa5b408697d12ef629289bed5f1e4ebfa9b099c49c41d7d1695aad89b8
7
- data.tar.gz: 42b3caac7804bfcb060606e4fc2c3f9acd732e2a94e28f6104f9ee6ba29f15d453a9e57530d1076add756e875ba9c57e44b85f9cc836c4db5eb2bf07146474ad
6
+ metadata.gz: eab85b9d115eade80732103040ca2e0db25e8146e0dc957005e469ec60776f0a0693a59bd5359bc618a1bb69fafd7b8b0ac19502029355703629f1d5a7040e89
7
+ data.tar.gz: 652b48322bb2372a261c497da6e60d4ea218f90d1ea2ae41da32024da74f6c5d4d288a4249af8279825aba6bf20b385748692190c2db871a63ab35e8e4338d78
data/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## 0.2.0 - 2026-10-28
8
+ * Added: named producer connections via `config.connection(name, url:, **bunny_options)`; `rabbitmq_url` remains the `:default` connection
9
+ * Added: `connection:` option on `Producer.configure`, `Producer.publish` and `Lepus::Publisher.new`, with one producer connection pool per name
10
+ * Added: `Lepus::InvalidConnectionError` raised for unregistered connection names
11
+ * Added: fake messages in `Lepus::Testing` record their `connection`
12
+
7
13
  ## 0.0.1 - 2024-08-01
8
14
  The first release of the gem
9
15
  * Added: Initial implementation with support to Sidekiq and Faktory backends
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lepus (0.1.2)
4
+ lepus (0.2.0)
5
5
  base64
6
6
  bunny
7
7
  concurrent-ruby
@@ -28,6 +28,19 @@ end
28
28
  | `recovery_attempts` | `10` | Max automatic reconnects; `nil` for infinite |
29
29
  | `recover_from_connection_close` | `true` | Auto-recover after a clean close |
30
30
 
31
+ ### Named connections
32
+
33
+ Producers can publish to more than one broker. Register extra brokers by name; `rabbitmq_url` stays the `:default` connection and remains the only one used by consumers, the supervisor, the process registry and the web dashboard.
34
+
35
+ ```ruby
36
+ Lepus.configure do |config|
37
+ config.rabbitmq_url = ENV.fetch('RABBITMQ_URL')
38
+ config.connection(:replica, url: ENV.fetch('REPLICA_RABBITMQ_URL'), recovery_attempts: 3)
39
+ end
40
+ ```
41
+
42
+ `url:` is required and must not be blank. Extra keyword arguments are Bunny session options and override the global ones for that connection only; a `connection_name:` given here is used as-is instead of the generated label. The name `:default` is reserved. Publishing through a name that was never registered raises `Lepus::InvalidConnectionError`. See [producers.md](producers.md#named-connections).
43
+
31
44
  ## Application metadata
32
45
 
33
46
  ```ruby
@@ -72,7 +85,7 @@ Assign consumers to a named worker via their own `configure(process: { name: :hi
72
85
 
73
86
  ## Producer pool
74
87
 
75
- Producers share a connection pool:
88
+ Producers share a connection pool per named connection (see [Named connections](#named-connections)); every pool uses these settings:
76
89
 
77
90
  ```ruby
78
91
  config.producer do |p|
data/docs/producers.md CHANGED
@@ -30,7 +30,8 @@ OrdersProducer.publish(
30
30
  ```ruby
31
31
  configure(
32
32
  exchange: { name: 'orders', type: :topic, durable: true },
33
- publish: { persistent: true, mandatory: false }
33
+ publish: { persistent: true, mandatory: false },
34
+ connection: :default
34
35
  )
35
36
  ```
36
37
 
@@ -38,6 +39,7 @@ configure(
38
39
  |-----|---------|
39
40
  | `exchange` | String (name only) or hash (`name`, `type`, `durable`, `auto_delete`). Auto-declared on first publish. |
40
41
  | `publish` | Default publish options merged into every `publish` call: `persistent`, `mandatory`, `content_type`, `expiration`, etc. |
42
+ | `connection` | Named connection to publish through. Defaults to `:default` (`rabbitmq_url`). See [Named connections](#named-connections). |
41
43
 
42
44
  ## Publishing
43
45
 
@@ -119,7 +121,7 @@ OrdersProducer.order_created(order)
119
121
 
120
122
  ## Connection pooling
121
123
 
122
- Producers share a single connection pool across the process:
124
+ Producers share one connection pool per named connection across the process:
123
125
 
124
126
  ```ruby
125
127
  Lepus.configure do |config|
@@ -130,6 +132,32 @@ Lepus.configure do |config|
130
132
  end
131
133
  ```
132
134
 
135
+ ## Named connections
136
+
137
+ Register extra brokers in the configuration (see [configuration.md](configuration.md#named-connections)):
138
+
139
+ ```ruby
140
+ Lepus.configure do |config|
141
+ config.connection(:replica, url: ENV.fetch('REPLICA_RABBITMQ_URL'))
142
+ end
143
+ ```
144
+
145
+ Then choose the connection per producer or per call. The per-call value wins, then the one in `configure`, then `:default`:
146
+
147
+ ```ruby
148
+ class AuditProducer < Lepus::Producer
149
+ configure(exchange: 'audit', connection: :replica)
150
+ end
151
+
152
+ OrdersProducer.publish(payload, routing_key: 'order.created') # :default
153
+ OrdersProducer.publish(payload, routing_key: 'order.created', connection: :replica) # :replica
154
+ AuditProducer.publish(payload) # :replica
155
+ ```
156
+
157
+ `connection` is never sent to Bunny as a publish option. Each connection gets its own lazily created pool, so a broker that is down only affects the publishes routed to it. An unregistered name raises `Lepus::InvalidConnectionError`.
158
+
159
+ The low-level publisher takes the same option: `Lepus::Publisher.new('audit', connection: :replica)`.
160
+
133
161
  ## Enable / disable publishing
134
162
 
135
163
  Useful when you want to disable side effects in tests or a specific environment.
data/docs/testing.md CHANGED
@@ -89,6 +89,14 @@ end
89
89
 
90
90
  `producer_messages(ProducerClass)` returns an array of hashes with `:payload`, `:routing_key`, `:delivery_info`, `:metadata`.
91
91
 
92
+ Each fake message also records the named connection it was published through:
93
+
94
+ ```ruby
95
+ Lepus::Testing.exchange('orders').find_messages(connection: :replica)
96
+ ```
97
+
98
+ The connection may be given as a symbol or a string. Publishing through a connection that was never registered with `config.connection` raises `Lepus::InvalidConnectionError`, as it does outside of fake mode.
99
+
92
100
  ## RSpec matchers
93
101
 
94
102
  ```ruby
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- lepus (0.1.2)
4
+ lepus (0.2.0)
5
5
  base64
6
6
  bunny
7
7
  concurrent-ruby
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- lepus (0.1.2)
4
+ lepus (0.2.0)
5
5
  base64
6
6
  bunny
7
7
  concurrent-ruby
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- lepus (0.1.2)
4
+ lepus (0.2.0)
5
5
  base64
6
6
  bunny
7
7
  concurrent-ruby
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- lepus (0.1.2)
4
+ lepus (0.2.0)
5
5
  base64
6
6
  bunny
7
7
  concurrent-ruby
@@ -8,6 +8,7 @@ module Lepus
8
8
  DEFAULT_RECOVERY_INTERVAL = 5.0
9
9
  DEFAULT_RECOVER_FROM_CONNECTION_CLOSE = true
10
10
  DEFAULT_CONSUMERS_DIRECTORY = Pathname.new("app/consumers")
11
+ DEFAULT_CONNECTION = :default
11
12
 
12
13
  # @return [String] the connection string for RabbitMQ.
13
14
  attr_accessor :rabbitmq_url
@@ -93,17 +94,48 @@ module Lepus
93
94
  Web::ManagementAPI.new(base_url: management_api_url)
94
95
  end
95
96
 
97
+ # Registers a named RabbitMQ connection that producers can publish through.
98
+ # The :default connection is reserved and always uses {#rabbitmq_url}.
99
+ # @param name [Symbol, String] the connection name
100
+ # @param url [String] the connection string for RabbitMQ
101
+ # @param options [Hash] Bunny session options overriding the global ones
102
+ # @return [Hash] the connection settings
103
+ def connection(name, url:, **options)
104
+ name = name.to_sym
105
+ raise ArgumentError, "The #{DEFAULT_CONNECTION.inspect} connection is configured through rabbitmq_url" if name == DEFAULT_CONNECTION
106
+ raise ArgumentError, "The #{name.inspect} connection requires a url" if url.to_s.strip.empty?
107
+
108
+ connections[name] = options.merge(url: url)
109
+ end
110
+
111
+ # @return [Hash{Symbol => Hash}] the named connections registered via {#connection}
112
+ def connections
113
+ @connections ||= {}
114
+ end
115
+
116
+ # @param name [Symbol, String, nil] the connection name
117
+ # @return [Boolean] whether the connection is the default one or a registered named connection
118
+ def connection?(name)
119
+ name = (name || DEFAULT_CONNECTION).to_sym
120
+ name == DEFAULT_CONNECTION || connections.key?(name)
121
+ end
122
+
96
123
  # @param suffix [String] the suffix to add to the connection name
124
+ # @param connection [Symbol, String] the named connection to open, :default uses {#rabbitmq_url}
97
125
  # @return [Bunny::Session] the connection to RabbitMQ
98
- def create_connection(suffix: nil)
99
- kwargs = connection_config
100
-
101
- if suffix && connection_name
102
- kwargs[:connection_name] = "#{connection_name} #{suffix}"
126
+ # @raise [Lepus::InvalidConnectionError] when the connection name is not registered
127
+ def create_connection(suffix: nil, connection: DEFAULT_CONNECTION)
128
+ name = (connection || DEFAULT_CONNECTION).to_sym
129
+ url, options = connection_settings(name)
130
+ kwargs = connection_config.merge(options)
131
+
132
+ if connection_name && !options.key?(:connection_name)
133
+ label = [suffix, (name == DEFAULT_CONNECTION) ? nil : "(#{name})"].compact.join(" ")
134
+ kwargs[:connection_name] = "#{connection_name} #{label}" unless label.empty?
103
135
  end
104
136
 
105
137
  ::Bunny
106
- .new(rabbitmq_url, **kwargs)
138
+ .new(url, **kwargs)
107
139
  .tap { |conn| conn.start }
108
140
  end
109
141
 
@@ -174,6 +206,15 @@ module Lepus
174
206
 
175
207
  protected
176
208
 
209
+ def connection_settings(name)
210
+ return [rabbitmq_url, {}] if name == DEFAULT_CONNECTION
211
+
212
+ settings = connections.fetch(name) do
213
+ raise Lepus::InvalidConnectionError, "Unknown connection #{name.inspect}. Register it with Lepus.config.connection(#{name.inspect}, url: ...)"
214
+ end
215
+ [settings[:url], settings.reject { |key, _| key == :url }]
216
+ end
217
+
177
218
  def connection_config
178
219
  {
179
220
  connection_name: connection_name,
@@ -9,12 +9,13 @@ module Lepus
9
9
  DEFAULT_SIZE = 5
10
10
  DEFAULT_TIMEOUT = 5.0
11
11
 
12
- attr_reader :pool_size, :timeout, :conn_suffix
12
+ attr_reader :pool_size, :timeout, :conn_suffix, :conn_name
13
13
 
14
- def initialize(size: DEFAULT_SIZE, timeout: DEFAULT_TIMEOUT, suffix: nil)
14
+ def initialize(size: DEFAULT_SIZE, timeout: DEFAULT_TIMEOUT, suffix: nil, connection: Lepus::Configuration::DEFAULT_CONNECTION)
15
15
  @pool_size = size
16
16
  @timeout = timeout
17
17
  @conn_suffix = suffix
18
+ @conn_name = connection
18
19
  @available = Concurrent::Array.new
19
20
  @in_use = Concurrent::Array.new
20
21
  @semaphore = Concurrent::Semaphore.new(pool_size)
@@ -62,7 +63,7 @@ module Lepus
62
63
  end
63
64
 
64
65
  # Create a new connection
65
- connection = Lepus.config.create_connection(suffix: @conn_suffix)
66
+ connection = Lepus.config.create_connection(suffix: @conn_suffix, connection: @conn_name)
66
67
  @mutex.with_write_lock do
67
68
  @in_use << connection
68
69
  end
@@ -58,7 +58,7 @@ module Lepus::Processes
58
58
 
59
59
  def heartbeat
60
60
  process.heartbeat
61
- rescue Process::NotFoundError
61
+ rescue Lepus::Process::NotFoundError
62
62
  self.process = nil
63
63
  interrupt
64
64
  end
@@ -35,6 +35,7 @@ module Lepus
35
35
  # @param [Hash] opts The options to configure the producer with.
36
36
  # @option opts [String, Hash] :exchange The name of the exchange to publish to.
37
37
  # @option opts [Hash] :publish Default publish options (persistent, mandatory, immediate).
38
+ # @option opts [Symbol] :connection The named connection to publish through. Defaults to :default.
38
39
  # @yield [definition] Optional block to further configure the producer.
39
40
  # @yieldparam [Lepus::Producers::Definition] definition The definition object.
40
41
  # @return [Lepus::Producers::Definition] The configured producer definition.
@@ -42,6 +43,7 @@ module Lepus
42
43
  raise ArgumentError, "Cannot configure an abstract class" if abstract_class?
43
44
 
44
45
  @definition = Producers::Definition.new(opts)
46
+ @publishers = nil
45
47
  yield(@definition) if block_given?
46
48
  @definition
47
49
  end
@@ -54,10 +56,13 @@ module Lepus
54
56
  descendants.uniq
55
57
  end
56
58
 
57
- # Creates a publisher instance configured with this producer's settings.
59
+ # Returns a publisher configured with this producer's settings, memoized per connection.
60
+ # @param connection [Symbol, String, nil] The named connection. Defaults to the configured one.
58
61
  # @return [Lepus::Publisher] A publisher instance ready to send messages.
59
- def publisher
60
- @publisher ||= Publisher.new(definition.exchange_name, **definition.exchange_options)
62
+ def publisher(connection = nil)
63
+ name = (connection || definition.connection).to_sym
64
+ @publishers ||= {}
65
+ @publishers[name] ||= Publisher.new(definition.exchange_name, connection: name, **definition.exchange_options)
61
66
  end
62
67
 
63
68
  # Returns the middleware chain for this producer.
@@ -79,9 +84,11 @@ module Lepus
79
84
  # Executes the middleware chain (global + per-producer) before publishing.
80
85
  #
81
86
  # @param payload [String, Hash] The message payload to publish.
87
+ # @param connection [Symbol, String, nil] The named connection to publish through,
88
+ # overriding the one set in {.configure}.
82
89
  # @param options [Hash] Additional publish options (routing_key, headers, etc.).
83
90
  # @return [void]
84
- def publish(payload, **options)
91
+ def publish(payload, connection: nil, **options)
85
92
  if definition.nil?
86
93
  raise InvalidProducerConfigError, <<~ERROR
87
94
  The #{name} producer is not configured.
@@ -99,7 +106,7 @@ module Lepus
99
106
  )
100
107
 
101
108
  combined_chain.execute(message) do |msg|
102
- publisher.publish(msg.payload, **msg.to_publish_options)
109
+ publisher(connection).publish(msg.payload, **msg.to_publish_options)
103
110
  end
104
111
  end
105
112
 
@@ -142,8 +149,8 @@ module Lepus
142
149
 
143
150
  attr_reader :definition
144
151
 
145
- def publisher
146
- @publisher ||= Publisher.new(definition.exchange_name, **definition.exchange_options)
152
+ def publisher(connection = nil)
153
+ self.class.publisher(connection)
147
154
  end
148
155
 
149
156
  def publish(message, **options)
@@ -1,23 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "forwardable"
4
-
5
3
  module Lepus
6
4
  module Producers
7
5
  # Configuration class for producer settings
8
6
  class Config
9
- extend Forwardable
10
-
11
7
  DEFAULT_POOL_SIZE = 1
12
8
  DEFAULT_POOL_TIMEOUT = 5.0
13
9
 
14
10
  attr_accessor :pool_size, :pool_timeout
15
11
 
16
- def_delegator :connection_pool, :with_connection
17
-
18
12
  def initialize
19
13
  @pool_size = DEFAULT_POOL_SIZE
20
14
  @pool_timeout = DEFAULT_POOL_TIMEOUT
15
+ @connection_pools = {}
16
+ @connection_pools_mutex = Mutex.new
17
+ end
18
+
19
+ # Checks out a connection from the pool of the given named connection.
20
+ # @param name [Symbol, String] the connection name, :default uses Lepus.config.rabbitmq_url
21
+ # @yieldparam connection [Bunny::Session]
22
+ # @raise [Lepus::InvalidConnectionError] when the connection name is not registered
23
+ def with_connection(name = Lepus::Configuration::DEFAULT_CONNECTION, &block)
24
+ connection_pool(name).with_connection(&block)
21
25
  end
22
26
 
23
27
  # Assign multiple attributes at once from a hash of options.
@@ -33,13 +37,22 @@ module Lepus
33
37
 
34
38
  private
35
39
 
40
+ # @param name [Symbol, String] the connection name
36
41
  # @return [Lepus::ConnectionPool] a connection pool instance configured for producers
37
- def connection_pool
38
- @connection_pool ||= Lepus::ConnectionPool.new(
39
- size: pool_size,
40
- timeout: pool_timeout,
41
- suffix: "producer"
42
- )
42
+ def connection_pool(name = Lepus::Configuration::DEFAULT_CONNECTION)
43
+ name = (name || Lepus::Configuration::DEFAULT_CONNECTION).to_sym
44
+ unless name == Lepus::Configuration::DEFAULT_CONNECTION || Lepus.config.connection?(name)
45
+ raise Lepus::InvalidConnectionError, "Unknown connection #{name.inspect}"
46
+ end
47
+
48
+ @connection_pools_mutex.synchronize do
49
+ @connection_pools[name] ||= Lepus::ConnectionPool.new(
50
+ size: pool_size,
51
+ timeout: pool_timeout,
52
+ suffix: "producer",
53
+ connection: name
54
+ )
55
+ end
43
56
  end
44
57
  end
45
58
  end
@@ -4,7 +4,7 @@ module Lepus
4
4
  module Producers
5
5
  # Definition class for producer-specific settings
6
6
  class Definition
7
- attr_reader :publish_options
7
+ attr_reader :publish_options, :connection
8
8
 
9
9
  def initialize(options = {})
10
10
  opts = Lepus::Primitive::Hash.new(options).deep_symbolize_keys
@@ -16,6 +16,8 @@ module Lepus
16
16
  # Handle default publish options
17
17
  @publish_options = Lepus::Publisher::DEFAULT_PUBLISH_OPTIONS.merge(opts.delete(:publish) || {})
18
18
 
19
+ @connection = (opts.delete(:connection) || Lepus::Configuration::DEFAULT_CONNECTION).to_sym
20
+
19
21
  # Store any remaining options for future use
20
22
  @options = opts
21
23
  end
@@ -15,17 +15,22 @@ module Lepus
15
15
  }.freeze
16
16
 
17
17
  # @param exchange_name [String] The name of the exchange to publish messages to.
18
+ # @param connection [Symbol, String] The named connection to publish through.
18
19
  # @param options [Hash] Additional options for the exchange (type, durable, auto_delete).
19
20
  # @return [void]
20
- def initialize(exchange_name, **options)
21
+ def initialize(exchange_name, connection: Lepus::Configuration::DEFAULT_CONNECTION, **options)
21
22
  @exchange_name = exchange_name
23
+ @connection = (connection || Lepus::Configuration::DEFAULT_CONNECTION).to_sym
22
24
  @exchange_options = DEFAULT_EXCHANGE_OPTIONS.merge(options)
23
25
  end
24
26
 
27
+ # @return [Symbol] the named connection this publisher uses
28
+ attr_reader :connection
29
+
25
30
  def publish(message, **options)
26
31
  return unless Producers.exchange_enabled?(exchange_name)
27
32
 
28
- Lepus.config.producer_config.with_connection do |connection|
33
+ Lepus.config.producer_config.with_connection(connection) do |connection|
29
34
  connection.with_channel do |channel|
30
35
  channel_publish(channel, message, **options)
31
36
  end
@@ -45,6 +45,8 @@ module Lepus
45
45
  message[:routing_key] == value
46
46
  when :payload
47
47
  message[:payload] == value
48
+ when :connection
49
+ message[:connection] == value&.to_sym
48
50
  when :headers
49
51
  message[:headers]&.any? { |k, v| value.any? { |vk, vv| k == vk && v == vv } }
50
52
  else
data/lib/lepus/testing.rb CHANGED
@@ -158,6 +158,10 @@ module Lepus
158
158
  return unless Lepus::Producers.exchange_enabled?(exchange_name)
159
159
 
160
160
  if Lepus::Testing.fake_publisher_enabled?
161
+ unless Lepus.config.connection?(connection)
162
+ raise Lepus::InvalidConnectionError, "Unknown connection #{connection.inspect}"
163
+ end
164
+
161
165
  return fake_publish(message, **options)
162
166
  end
163
167
 
@@ -186,6 +190,7 @@ module Lepus
186
190
 
187
191
  fake_message = {
188
192
  exchange: exchange_name,
193
+ connection: connection,
189
194
  payload: message, # Store the original message, not the JSON string
190
195
  routing_key: opts[:routing_key],
191
196
  headers: opts[:headers],
data/lib/lepus/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lepus
4
- VERSION = "0.1.2"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/lepus/web.rb CHANGED
@@ -92,7 +92,7 @@ module Lepus
92
92
 
93
93
  def heartbeat
94
94
  process.heartbeat(metrics: metrics_data)
95
- rescue Process::NotFoundError
95
+ rescue Lepus::Process::NotFoundError
96
96
  self.process = nil
97
97
  interrupt
98
98
  end
data/lib/lepus.rb CHANGED
@@ -56,6 +56,10 @@ module Lepus
56
56
  class InvalidProducerConfigError < Error
57
57
  end
58
58
 
59
+ # Error that is raised when a named connection is not registered.
60
+ class InvalidConnectionError < Error
61
+ end
62
+
59
63
  class ShutdownError < Error
60
64
  end
61
65
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lepus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcos G. Zimmermann
8
8
  autorequire:
9
9
  bindir: exec
10
10
  cert_chain: []
11
- date: 2026-05-18 00:00:00.000000000 Z
11
+ date: 2026-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64