bug_bunny 4.7.0 → 4.8.1
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/.agents/skills/documentation-writer/SKILL.md +45 -0
- data/.agents/skills/gem-release/SKILL.md +114 -0
- data/.agents/skills/quality-code/SKILL.md +51 -0
- data/.agents/skills/rabbitmq-expert/SKILL.md +1555 -0
- data/.agents/skills/sentry/SKILL.md +135 -0
- data/.agents/skills/sentry/references/api-endpoints.md +147 -0
- data/.agents/skills/sentry/scripts/sentry.rb +194 -0
- data/.agents/skills/skill-builder/SKILL.md +232 -0
- data/.agents/skills/skill-manager/SKILL.md +172 -0
- data/.agents/skills/skill-manager/scripts/sync.rb +310 -0
- data/.agents/skills/yard/SKILL.md +311 -0
- data/.agents/skills/yard/references/tipos.md +144 -0
- data/CHANGELOG.md +21 -0
- data/CLAUDE.md +29 -220
- data/lib/bug_bunny/client.rb +4 -3
- data/lib/bug_bunny/controller.rb +10 -14
- data/lib/bug_bunny/exception.rb +1 -1
- data/lib/bug_bunny/middleware/raise_error.rb +3 -3
- data/lib/bug_bunny/observability.rb +5 -5
- data/lib/bug_bunny/producer.rb +11 -13
- data/lib/bug_bunny/railtie.rb +8 -7
- data/lib/bug_bunny/request.rb +3 -11
- data/lib/bug_bunny/resource.rb +51 -21
- data/lib/bug_bunny/routing/route_set.rb +32 -21
- data/lib/bug_bunny/version.rb +1 -1
- data/lib/bug_bunny.rb +3 -2
- data/lib/generators/bug_bunny/install/install_generator.rb +45 -5
- data/lib/tasks/bug_bunny.rake +50 -0
- data/skill/SKILL.md +230 -0
- data/skill/references/client-middleware.md +144 -0
- data/skill/references/consumer.md +104 -0
- data/skill/references/controller.md +105 -0
- data/skill/references/errores.md +97 -0
- data/skill/references/resource.md +116 -0
- data/skill/references/routing.md +82 -0
- data/skill/references/testing.md +138 -0
- data/skills-lock.json +10 -0
- data/skills.lock +24 -0
- data/skills.yml +19 -0
- metadata +27 -17
- data/.claude/commands/pr.md +0 -42
- data/.claude/commands/release.md +0 -41
- data/.claude/commands/rubocop.md +0 -22
- data/.claude/commands/test.md +0 -28
- data/.claude/commands/yard.md +0 -46
- data/docs/concepts.md +0 -140
- data/docs/howto/controller.md +0 -194
- data/docs/howto/middleware_client.md +0 -119
- data/docs/howto/middleware_consumer.md +0 -127
- data/docs/howto/rails.md +0 -214
- data/docs/howto/resource.md +0 -200
- data/docs/howto/routing.md +0 -133
- data/docs/howto/testing.md +0 -259
- data/docs/howto/tracing.md +0 -119
- data/mejoras.md +0 -33
data/lib/bug_bunny/railtie.rb
CHANGED
|
@@ -14,9 +14,7 @@ module BugBunny
|
|
|
14
14
|
# 1. Configuración de Autoload
|
|
15
15
|
initializer 'bug_bunny.add_autoload_paths' do |app|
|
|
16
16
|
rabbit_path = File.join(app.root, 'app', 'rabbit')
|
|
17
|
-
if Dir.exist?(rabbit_path)
|
|
18
|
-
app.config.paths.add 'app/rabbit', eager_load: true
|
|
19
|
-
end
|
|
17
|
+
app.config.paths.add 'app/rabbit', eager_load: true if Dir.exist?(rabbit_path)
|
|
20
18
|
end
|
|
21
19
|
|
|
22
20
|
# 2. Gestión de Forks (Puma / Spring / otros)
|
|
@@ -25,9 +23,7 @@ module BugBunny
|
|
|
25
23
|
# el hijo empiece a trabajar, para evitar compartir el mismo socket TCP.
|
|
26
24
|
config.after_initialize do
|
|
27
25
|
# Estrategia 1: Rails 7.1+ ForkTracker (La forma estándar moderna)
|
|
28
|
-
if defined?(ActiveSupport::ForkTracker)
|
|
29
|
-
ActiveSupport::ForkTracker.after_fork { BugBunny.disconnect }
|
|
30
|
-
end
|
|
26
|
+
ActiveSupport::ForkTracker.after_fork { BugBunny.disconnect } if defined?(ActiveSupport::ForkTracker)
|
|
31
27
|
|
|
32
28
|
# Estrategia 2: Hook específico de Puma (Legacy)
|
|
33
29
|
# Solo intentamos usarlo si la API 'events' está disponible (Puma < 5).
|
|
@@ -38,7 +34,12 @@ module BugBunny
|
|
|
38
34
|
end
|
|
39
35
|
end
|
|
40
36
|
|
|
41
|
-
# 3.
|
|
37
|
+
# 3. Rake tasks (bug_bunny:sync)
|
|
38
|
+
rake_tasks do
|
|
39
|
+
load File.expand_path('../tasks/bug_bunny.rake', __dir__)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# 4. Hook de Spring (Preloader)
|
|
42
43
|
if defined?(Spring)
|
|
43
44
|
Spring.after_fork do
|
|
44
45
|
BugBunny.disconnect
|
data/lib/bug_bunny/request.rb
CHANGED
|
@@ -23,20 +23,11 @@ module BugBunny
|
|
|
23
23
|
# @attr exchange_options [Hash] Opciones específicas para la declaración del Exchange en esta petición.
|
|
24
24
|
# @attr queue_options [Hash] Opciones específicas para la declaración de la Cola en esta petición.
|
|
25
25
|
class Request
|
|
26
|
-
attr_accessor :body
|
|
27
|
-
|
|
28
|
-
attr_accessor :params
|
|
29
|
-
attr_accessor :path
|
|
30
|
-
attr_accessor :method
|
|
31
|
-
attr_accessor :exchange
|
|
32
|
-
attr_accessor :exchange_type
|
|
33
|
-
attr_accessor :routing_key
|
|
34
|
-
attr_accessor :timeout
|
|
35
|
-
attr_accessor :delivery_mode
|
|
26
|
+
attr_accessor :body, :headers, :params, :path, :method, :exchange, :exchange_type, :routing_key, :timeout,
|
|
27
|
+
:delivery_mode, :queue_options
|
|
36
28
|
|
|
37
29
|
# Configuración de Infraestructura Específica
|
|
38
30
|
attr_accessor :exchange_options
|
|
39
|
-
attr_accessor :queue_options
|
|
40
31
|
|
|
41
32
|
# Metadatos AMQP Estándar
|
|
42
33
|
attr_accessor :app_id, :content_type, :content_encoding, :priority,
|
|
@@ -61,6 +52,7 @@ module BugBunny
|
|
|
61
52
|
@exchange_options = {}
|
|
62
53
|
@queue_options = {}
|
|
63
54
|
end
|
|
55
|
+
|
|
64
56
|
# Combina el path con los params como query string.
|
|
65
57
|
#
|
|
66
58
|
# @return [String] El path completo con query string si hay params, o solo el path.
|
data/lib/bug_bunny/resource.rb
CHANGED
|
@@ -25,8 +25,7 @@ module BugBunny
|
|
|
25
25
|
define_model_callbacks :save, :create, :update, :destroy
|
|
26
26
|
|
|
27
27
|
attr_reader :remote_attributes
|
|
28
|
-
attr_accessor :persisted
|
|
29
|
-
attr_accessor :routing_key, :exchange, :exchange_type
|
|
28
|
+
attr_accessor :persisted, :routing_key, :exchange, :exchange_type
|
|
30
29
|
|
|
31
30
|
# @return [Hash] Opciones específicas de instancia para exchange y queue.
|
|
32
31
|
attr_accessor :exchange_options, :queue_options
|
|
@@ -38,7 +37,9 @@ module BugBunny
|
|
|
38
37
|
attr_writer :exchange_options, :queue_options
|
|
39
38
|
|
|
40
39
|
# @api private
|
|
41
|
-
def thread_config(key)
|
|
40
|
+
def thread_config(key)
|
|
41
|
+
Thread.current["bb_#{object_id}_#{key}"]
|
|
42
|
+
end
|
|
42
43
|
|
|
43
44
|
# Resuelve la configuración buscando en el hilo, luego en la jerarquía de clases.
|
|
44
45
|
# @param key [Symbol] Clave en el Thread.current.
|
|
@@ -47,29 +48,41 @@ module BugBunny
|
|
|
47
48
|
def resolve_config(key, instance_var)
|
|
48
49
|
val = thread_config(key)
|
|
49
50
|
return val if val
|
|
51
|
+
|
|
50
52
|
target = self
|
|
51
53
|
while target <= BugBunny::Resource
|
|
52
54
|
value = target.instance_variable_get(instance_var)
|
|
53
55
|
return value.respond_to?(:call) ? value.call : value unless value.nil?
|
|
56
|
+
|
|
54
57
|
target = target.superclass
|
|
55
58
|
end
|
|
56
59
|
nil
|
|
57
60
|
end
|
|
58
61
|
|
|
59
62
|
# @return [ConnectionPool, nil]
|
|
60
|
-
def connection_pool
|
|
63
|
+
def connection_pool
|
|
64
|
+
resolve_config(:pool, :@connection_pool)
|
|
65
|
+
end
|
|
61
66
|
|
|
62
67
|
# @return [String] Nombre del exchange actual.
|
|
63
|
-
def current_exchange
|
|
68
|
+
def current_exchange
|
|
69
|
+
resolve_config(:exchange, :@exchange) || raise(ArgumentError, "Exchange not defined for #{name}")
|
|
70
|
+
end
|
|
64
71
|
|
|
65
72
|
# @return [String] Tipo de exchange ('direct', 'topic', 'fanout').
|
|
66
|
-
def current_exchange_type
|
|
73
|
+
def current_exchange_type
|
|
74
|
+
resolve_config(:exchange_type, :@exchange_type) || 'direct'
|
|
75
|
+
end
|
|
67
76
|
|
|
68
77
|
# @return [Hash] Opciones de exchange específicas (Nivel 3 de la cascada).
|
|
69
|
-
def current_exchange_options
|
|
78
|
+
def current_exchange_options
|
|
79
|
+
resolve_config(:exchange_options, :@exchange_options) || {}
|
|
80
|
+
end
|
|
70
81
|
|
|
71
82
|
# @return [Hash] Opciones de cola específicas.
|
|
72
|
-
def current_queue_options
|
|
83
|
+
def current_queue_options
|
|
84
|
+
resolve_config(:queue_options, :@queue_options) || {}
|
|
85
|
+
end
|
|
73
86
|
|
|
74
87
|
# @return [String] Nombre del recurso para la construcción de rutas.
|
|
75
88
|
def resource_name
|
|
@@ -126,7 +139,8 @@ module BugBunny
|
|
|
126
139
|
# @param pool [ConnectionPool] Pool de conexiones.
|
|
127
140
|
# @param exchange_options [Hash] Opciones de infraestructura.
|
|
128
141
|
# @param queue_options [Hash] Opciones de cola.
|
|
129
|
-
def with(exchange: nil, routing_key: nil, exchange_type: nil, pool: nil, exchange_options: nil,
|
|
142
|
+
def with(exchange: nil, routing_key: nil, exchange_type: nil, pool: nil, exchange_options: nil,
|
|
143
|
+
queue_options: nil)
|
|
130
144
|
keys = {
|
|
131
145
|
exchange: "bb_#{object_id}_exchange",
|
|
132
146
|
exchange_type: "bb_#{object_id}_exchange_type",
|
|
@@ -163,9 +177,7 @@ module BugBunny
|
|
|
163
177
|
end
|
|
164
178
|
|
|
165
179
|
def method_missing(method, *args, &block)
|
|
166
|
-
if @used
|
|
167
|
-
::Kernel.raise ::BugBunny::Error, "ScopeProxy is single-use. Call .with again for a new context."
|
|
168
|
-
end
|
|
180
|
+
::Kernel.raise ::BugBunny::Error, 'ScopeProxy is single-use. Call .with again for a new context.' if @used
|
|
169
181
|
@used = true
|
|
170
182
|
@target.public_send(method, *args, &block)
|
|
171
183
|
ensure
|
|
@@ -176,11 +188,13 @@ module BugBunny
|
|
|
176
188
|
# Calcula la routing key final.
|
|
177
189
|
# @param id [String, nil] ID del recurso.
|
|
178
190
|
# @return [String]
|
|
179
|
-
def calculate_routing_key(
|
|
191
|
+
def calculate_routing_key(_id = nil)
|
|
180
192
|
manual_rk = thread_config(:routing_key)
|
|
181
193
|
return manual_rk if manual_rk
|
|
194
|
+
|
|
182
195
|
static_rk = resolve_config(:routing_key, :@routing_key)
|
|
183
196
|
return static_rk if static_rk.present?
|
|
197
|
+
|
|
184
198
|
resource_name
|
|
185
199
|
end
|
|
186
200
|
|
|
@@ -206,6 +220,7 @@ module BugBunny
|
|
|
206
220
|
)
|
|
207
221
|
|
|
208
222
|
return [] unless response['body'].is_a?(Array)
|
|
223
|
+
|
|
209
224
|
response['body'].map do |attrs|
|
|
210
225
|
inst = new(attrs)
|
|
211
226
|
inst.persisted = true
|
|
@@ -218,7 +233,9 @@ module BugBunny
|
|
|
218
233
|
|
|
219
234
|
# Devuelve todos los registros.
|
|
220
235
|
# @return [Array<BugBunny::Resource>]
|
|
221
|
-
def all
|
|
236
|
+
def all
|
|
237
|
+
where({})
|
|
238
|
+
end
|
|
222
239
|
|
|
223
240
|
# Busca un registro por ID (GET).
|
|
224
241
|
# Mapea un 404 (NotFound) devolviendo un objeto nulo.
|
|
@@ -275,7 +292,7 @@ module BugBunny
|
|
|
275
292
|
@exchange_options = self.class.thread_config(:exchange_options) || self.class.current_exchange_options
|
|
276
293
|
@queue_options = self.class.thread_config(:queue_options) || self.class.current_queue_options
|
|
277
294
|
|
|
278
|
-
super
|
|
295
|
+
super
|
|
279
296
|
end
|
|
280
297
|
|
|
281
298
|
# Limpia el rastreo de ActiveModel y nuestro rastreo dinámico interno.
|
|
@@ -301,24 +318,35 @@ module BugBunny
|
|
|
301
318
|
end
|
|
302
319
|
|
|
303
320
|
# @return [String]
|
|
304
|
-
def calculate_routing_key(id=nil)
|
|
321
|
+
def calculate_routing_key(id = nil)
|
|
322
|
+
@routing_key || self.class.calculate_routing_key(id)
|
|
323
|
+
end
|
|
305
324
|
|
|
306
325
|
# @return [String]
|
|
307
|
-
def current_exchange
|
|
326
|
+
def current_exchange
|
|
327
|
+
@exchange || self.class.current_exchange
|
|
328
|
+
end
|
|
308
329
|
|
|
309
330
|
# @return [String]
|
|
310
|
-
def current_exchange_type
|
|
331
|
+
def current_exchange_type
|
|
332
|
+
@exchange_type || self.class.current_exchange_type
|
|
333
|
+
end
|
|
311
334
|
|
|
312
335
|
# @return [BugBunny::Client]
|
|
313
|
-
def bug_bunny_client
|
|
336
|
+
def bug_bunny_client
|
|
337
|
+
self.class.bug_bunny_client
|
|
338
|
+
end
|
|
314
339
|
|
|
315
340
|
# @return [Boolean]
|
|
316
|
-
def persisted
|
|
341
|
+
def persisted?
|
|
342
|
+
!!@persisted
|
|
343
|
+
end
|
|
317
344
|
|
|
318
345
|
# Asignación masiva de atributos.
|
|
319
346
|
# @param new_attributes [Hash]
|
|
320
347
|
def assign_attributes(new_attributes)
|
|
321
348
|
return if new_attributes.nil?
|
|
349
|
+
|
|
322
350
|
new_attributes.each { |k, v| public_send("#{k}=", v) }
|
|
323
351
|
end
|
|
324
352
|
|
|
@@ -375,7 +403,7 @@ module BugBunny
|
|
|
375
403
|
|
|
376
404
|
def id=(value)
|
|
377
405
|
if self.class.attribute_names.include?('id')
|
|
378
|
-
super
|
|
406
|
+
super
|
|
379
407
|
else
|
|
380
408
|
@dynamic_changes << 'id' if @extra_attributes['id'] != value
|
|
381
409
|
@extra_attributes['id'] = value
|
|
@@ -433,6 +461,7 @@ module BugBunny
|
|
|
433
461
|
# @return [Boolean]
|
|
434
462
|
def destroy
|
|
435
463
|
return false unless persisted?
|
|
464
|
+
|
|
436
465
|
run_callbacks(:destroy) do
|
|
437
466
|
path = "#{self.class.resource_name}/#{id}"
|
|
438
467
|
rk = calculate_routing_key(id)
|
|
@@ -459,6 +488,7 @@ module BugBunny
|
|
|
459
488
|
# Carga errores remotos en el objeto local (utilizado al recibir 422).
|
|
460
489
|
def load_remote_rabbit_errors(errors_hash)
|
|
461
490
|
return if errors_hash.nil? || errors_hash.empty?
|
|
491
|
+
|
|
462
492
|
if errors_hash.is_a?(String)
|
|
463
493
|
errors.add(:base, errors_hash)
|
|
464
494
|
else
|
|
@@ -57,27 +57,37 @@ module BugBunny
|
|
|
57
57
|
# Registra una ruta para el verbo GET.
|
|
58
58
|
# @param path [String, Symbol] Patrón de la URL.
|
|
59
59
|
# @param to [String, nil] Destino (controlador#accion). Si es nil, se infiere del scope.
|
|
60
|
-
def get(path, to: nil)
|
|
60
|
+
def get(path, to: nil)
|
|
61
|
+
add_route('GET', path, to: to)
|
|
62
|
+
end
|
|
61
63
|
|
|
62
64
|
# Registra una ruta para el verbo POST.
|
|
63
65
|
# @param path [String, Symbol] Patrón de la URL.
|
|
64
66
|
# @param to [String, nil] Destino (controlador#accion). Si es nil, se infiere del scope.
|
|
65
|
-
def post(path, to: nil)
|
|
67
|
+
def post(path, to: nil)
|
|
68
|
+
add_route('POST', path, to: to)
|
|
69
|
+
end
|
|
66
70
|
|
|
67
71
|
# Registra una ruta para el verbo PUT.
|
|
68
72
|
# @param path [String, Symbol] Patrón de la URL.
|
|
69
73
|
# @param to [String, nil] Destino (controlador#accion). Si es nil, se infiere del scope.
|
|
70
|
-
def put(path, to: nil)
|
|
74
|
+
def put(path, to: nil)
|
|
75
|
+
add_route('PUT', path, to: to)
|
|
76
|
+
end
|
|
71
77
|
|
|
72
78
|
# Registra una ruta para el verbo PATCH.
|
|
73
79
|
# @param path [String, Symbol] Patrón de la URL.
|
|
74
80
|
# @param to [String, nil] Destino (controlador#accion). Si es nil, se infiere del scope.
|
|
75
|
-
def patch(path, to: nil)
|
|
81
|
+
def patch(path, to: nil)
|
|
82
|
+
add_route('PATCH', path, to: to)
|
|
83
|
+
end
|
|
76
84
|
|
|
77
85
|
# Registra una ruta para el verbo DELETE.
|
|
78
86
|
# @param path [String, Symbol] Patrón de la URL.
|
|
79
87
|
# @param to [String, nil] Destino (controlador#accion). Si es nil, se infiere del scope.
|
|
80
|
-
def delete(path, to: nil)
|
|
88
|
+
def delete(path, to: nil)
|
|
89
|
+
add_route('DELETE', path, to: to)
|
|
90
|
+
end
|
|
81
91
|
|
|
82
92
|
# @!endgroup
|
|
83
93
|
|
|
@@ -113,7 +123,7 @@ module BugBunny
|
|
|
113
123
|
resource_path = name.to_s
|
|
114
124
|
|
|
115
125
|
# Todas las acciones estándar disponibles
|
|
116
|
-
actions = [
|
|
126
|
+
actions = %i[index show create update destroy]
|
|
117
127
|
|
|
118
128
|
# Aplicamos los filtros si existen
|
|
119
129
|
if only
|
|
@@ -131,10 +141,10 @@ module BugBunny
|
|
|
131
141
|
delete "#{resource_path}/:id", to: "#{resource_path}#destroy" if actions.include?(:destroy)
|
|
132
142
|
|
|
133
143
|
# Si se pasa un bloque, abrimos un Scope de Recurso para rutas anidadas
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
144
|
+
return unless block_given?
|
|
145
|
+
|
|
146
|
+
with_scope({ type: :resource, name: resource_path }) do
|
|
147
|
+
instance_eval(&block)
|
|
138
148
|
end
|
|
139
149
|
end
|
|
140
150
|
|
|
@@ -197,16 +207,16 @@ module BugBunny
|
|
|
197
207
|
# @return [Hash, nil] Hash con `:controller`, `:action`, `:params` y `:namespace`, o `nil` si no hay match.
|
|
198
208
|
def recognize(method, path)
|
|
199
209
|
@routes.each do |route|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
+
next unless route.match?(method, path)
|
|
211
|
+
|
|
212
|
+
extracted_params = route.extract_params(path)
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
controller: route.controller,
|
|
216
|
+
action: route.action,
|
|
217
|
+
params: extracted_params,
|
|
218
|
+
namespace: route.namespace
|
|
219
|
+
}
|
|
210
220
|
end
|
|
211
221
|
|
|
212
222
|
# Si llegamos aquí, es un 404 seguro.
|
|
@@ -238,7 +248,8 @@ module BugBunny
|
|
|
238
248
|
end
|
|
239
249
|
|
|
240
250
|
if final_to.nil?
|
|
241
|
-
raise ArgumentError,
|
|
251
|
+
raise ArgumentError,
|
|
252
|
+
"Falta el destino 'to:' para la ruta #{method} '#{final_path}'. Usa la sintaxis 'controlador#accion'"
|
|
242
253
|
end
|
|
243
254
|
|
|
244
255
|
@routes << Route.new(method, final_path, to: final_to, namespace: current_namespace)
|
data/lib/bug_bunny/version.rb
CHANGED
data/lib/bug_bunny.rb
CHANGED
|
@@ -25,6 +25,7 @@ require_relative 'bug_bunny/railtie' if defined?(Rails)
|
|
|
25
25
|
# Actúa como espacio de nombres y punto de configuración global.
|
|
26
26
|
module BugBunny
|
|
27
27
|
extend BugBunny::Observability
|
|
28
|
+
|
|
28
29
|
private_class_method :safe_log, :exception_metadata, :observability_name
|
|
29
30
|
|
|
30
31
|
class << self
|
|
@@ -95,9 +96,9 @@ module BugBunny
|
|
|
95
96
|
|
|
96
97
|
@global_connection.close if @global_connection.open?
|
|
97
98
|
@global_connection = nil
|
|
98
|
-
|
|
99
|
+
|
|
99
100
|
@logger = configuration.logger
|
|
100
|
-
safe_log(:info,
|
|
101
|
+
safe_log(:info, 'bug_bunny.disconnect')
|
|
101
102
|
end
|
|
102
103
|
|
|
103
104
|
# @api private
|
|
@@ -20,7 +20,7 @@ module BugBunny
|
|
|
20
20
|
# @api private
|
|
21
21
|
source_root File.expand_path('templates', __dir__)
|
|
22
22
|
|
|
23
|
-
desc
|
|
23
|
+
desc 'Instala la configuración inicial de BugBunny y crea la estructura de directorios.'
|
|
24
24
|
|
|
25
25
|
# Genera el archivo de configuración inicial.
|
|
26
26
|
# Copia la plantilla `initializer.rb` a `config/initializers/bug_bunny.rb` en la app destino.
|
|
@@ -33,15 +33,55 @@ module BugBunny
|
|
|
33
33
|
# Crea la estructura de carpetas necesaria para el patrón MVC de BugBunny.
|
|
34
34
|
#
|
|
35
35
|
# Genera:
|
|
36
|
-
# * `app/
|
|
36
|
+
# * `app/bug_bunny/controllers/`: Directorio donde vivirán los controladores de consumidores.
|
|
37
37
|
# * `.keep`: Archivo marcador para asegurar que Git rastree la carpeta aunque esté vacía.
|
|
38
38
|
#
|
|
39
39
|
# @return [void]
|
|
40
40
|
def create_directories
|
|
41
|
-
empty_directory
|
|
42
|
-
create_file
|
|
41
|
+
empty_directory 'app/bug_bunny/controllers'
|
|
42
|
+
create_file 'app/bug_bunny/controllers/.keep', ''
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Escribe el bloque inicial de BugBunny en CLAUDE.md del proyecto consumidor.
|
|
46
|
+
#
|
|
47
|
+
# Si CLAUDE.md no existe, crea uno mínimo con la sección correspondiente.
|
|
48
|
+
# Si ya existe, agrega la sección `## Gemas internas` con el bloque de bug_bunny.
|
|
49
|
+
# En ambos casos, el rake task `bug_bunny:sync` se encarga de las actualizaciones futuras.
|
|
50
|
+
#
|
|
51
|
+
# @return [void]
|
|
52
|
+
def update_claude_md
|
|
53
|
+
spec = Gem::Specification.find_by_name('bug_bunny')
|
|
54
|
+
version = spec.version.to_s
|
|
55
|
+
docs_path = File.join(spec.gem_dir, 'docs', 'ai')
|
|
56
|
+
|
|
57
|
+
block = <<~BLOCK
|
|
58
|
+
## Gemas internas
|
|
59
|
+
|
|
60
|
+
### bug_bunny
|
|
61
|
+
- **Version:** #{version}
|
|
62
|
+
- **Docs:** #{docs_path}
|
|
63
|
+
- **Updated:** #{Time.now.strftime('%Y-%m-%d')}
|
|
64
|
+
BLOCK
|
|
65
|
+
|
|
66
|
+
claude_md = File.join(destination_root, 'CLAUDE.md')
|
|
67
|
+
|
|
68
|
+
if File.exist?(claude_md)
|
|
69
|
+
content = File.read(claude_md)
|
|
70
|
+
if content.include?('### bug_bunny')
|
|
71
|
+
say_status :skip, 'CLAUDE.md already has a bug_bunny block — run `rake bug_bunny:sync` to update'
|
|
72
|
+
elsif content.include?('## Gemas internas')
|
|
73
|
+
inject_into_file 'CLAUDE.md', "\n#{block.lines.drop(2).join}", after: "## Gemas internas\n"
|
|
74
|
+
say_status :update, 'Added bug_bunny block to existing ## Gemas internas section in CLAUDE.md'
|
|
75
|
+
else
|
|
76
|
+
append_to_file 'CLAUDE.md', "\n#{block}"
|
|
77
|
+
say_status :update, 'Added ## Gemas internas section to CLAUDE.md'
|
|
78
|
+
end
|
|
79
|
+
else
|
|
80
|
+
create_file 'CLAUDE.md', "# #{Rails.application.class.module_parent_name}\n\n#{block}"
|
|
81
|
+
say_status :create, 'CLAUDE.md created with bug_bunny block'
|
|
82
|
+
end
|
|
43
83
|
|
|
44
|
-
|
|
84
|
+
say ' Add `bundle exec rake bug_bunny:sync` to bin/setup to keep this block up to date.'
|
|
45
85
|
end
|
|
46
86
|
end
|
|
47
87
|
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'date'
|
|
4
|
+
|
|
5
|
+
namespace :bug_bunny do
|
|
6
|
+
desc 'Sync BugBunny AI docs reference in CLAUDE.md with the installed version'
|
|
7
|
+
task :sync do
|
|
8
|
+
spec = Gem::Specification.find_by_name('bug_bunny')
|
|
9
|
+
version = spec.version.to_s
|
|
10
|
+
docs_path = File.join(spec.gem_dir, 'docs', 'ai')
|
|
11
|
+
claude_md_path = File.join(Dir.pwd, 'CLAUDE.md')
|
|
12
|
+
|
|
13
|
+
content = if File.exist?(claude_md_path)
|
|
14
|
+
File.read(claude_md_path)
|
|
15
|
+
else
|
|
16
|
+
app_name = File.basename(Dir.pwd).split(/[-_]/).map(&:capitalize).join
|
|
17
|
+
puts 'bug_bunny:sync — CLAUDE.md not found, creating it.'
|
|
18
|
+
"# #{app_name}\n"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Idempotent: same version already present, nothing to do
|
|
22
|
+
if content.include?('### bug_bunny') && content.include?("**Version:** #{version}")
|
|
23
|
+
puts "bug_bunny:sync — already at #{version}, nothing to do."
|
|
24
|
+
next
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
block = <<~BLOCK
|
|
28
|
+
### bug_bunny
|
|
29
|
+
- **Version:** #{version}
|
|
30
|
+
- **Docs:** #{docs_path}
|
|
31
|
+
- **Updated:** #{Date.today}
|
|
32
|
+
BLOCK
|
|
33
|
+
|
|
34
|
+
# Replace existing block if present
|
|
35
|
+
if content.match?(/^### bug_bunny\n/)
|
|
36
|
+
updated = content.gsub(/^### bug_bunny\n(?:- \*\*.*\n)*/, block)
|
|
37
|
+
File.write(claude_md_path, updated)
|
|
38
|
+
puts "bug_bunny:sync — updated to #{version} in CLAUDE.md"
|
|
39
|
+
elsif content.include?('## Gemas internas')
|
|
40
|
+
# Append under existing section
|
|
41
|
+
updated = content.sub(/^## Gemas internas\n/, "## Gemas internas\n\n#{block}")
|
|
42
|
+
File.write(claude_md_path, updated)
|
|
43
|
+
puts "bug_bunny:sync — added #{version} under '## Gemas internas' in CLAUDE.md"
|
|
44
|
+
else
|
|
45
|
+
# Create section at end of file
|
|
46
|
+
File.write(claude_md_path, content.rstrip + "\n\n## Gemas internas\n\n#{block}")
|
|
47
|
+
puts "bug_bunny:sync — added '## Gemas internas' section with #{version} to CLAUDE.md"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|