rage-rb 1.27.0 → 1.28.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 +4 -4
- data/CHANGELOG.md +16 -0
- data/CONTRIBUTING.md +9 -0
- data/exe/rage +1 -1
- data/lib/rage/cable/channel.rb +7 -11
- data/lib/rage/cli/base.rb +27 -0
- data/lib/rage/cli/code_generator.rb +58 -0
- data/lib/rage/cli/new_app_generator.rb +66 -0
- data/lib/rage/cli/openapi.rb +20 -0
- data/lib/rage/cli/skills.rb +222 -220
- data/lib/rage/cli.rb +12 -143
- data/lib/rage/code_loader.rb +5 -1
- data/lib/rage/configuration.rb +72 -2
- data/lib/rage/daemon.rb +5 -2
- data/lib/rage/deferred/backends/disk.rb +544 -199
- data/lib/rage/deferred/backends/nil.rb +16 -2
- data/lib/rage/deferred/deferred.rb +5 -1
- data/lib/rage/deferred/queue.rb +8 -3
- data/lib/rage/deferred/task.rb +1 -0
- data/lib/rage/hooks.rb +6 -1
- data/lib/rage/logger/logger.rb +55 -4
- data/lib/rage/openapi/openapi.rb +11 -0
- data/lib/rage/router/dsl.rb +5 -7
- data/lib/rage/tasks.rb +5 -0
- data/lib/rage/telemetry/capacity.rb +42 -0
- data/lib/rage/telemetry/telemetry.rb +19 -0
- data/lib/rage/version.rb +1 -1
- data/lib/rage-rb.rb +3 -0
- metadata +8 -3
data/lib/rage/cli.rb
CHANGED
|
@@ -4,65 +4,14 @@ require "thor"
|
|
|
4
4
|
require "rack"
|
|
5
5
|
require "rage/version"
|
|
6
6
|
|
|
7
|
+
require "rage/cli/base"
|
|
7
8
|
require "rage/cli/skills"
|
|
9
|
+
require "rage/cli/openapi"
|
|
10
|
+
require "rage/cli/code_generator"
|
|
11
|
+
require "rage/cli/new_app_generator"
|
|
8
12
|
|
|
9
|
-
module Rage
|
|
10
|
-
class
|
|
11
|
-
include Thor::Actions
|
|
12
|
-
|
|
13
|
-
def self.source_root
|
|
14
|
-
File.expand_path("templates", __dir__)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
desc "migration NAME", "Generate a new migration"
|
|
18
|
-
def migration(name = nil)
|
|
19
|
-
return help("migration") if name.nil?
|
|
20
|
-
|
|
21
|
-
setup
|
|
22
|
-
Rake::Task["db:new_migration"].invoke(name)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
desc "model NAME", "Generate a new model"
|
|
26
|
-
def model(name = nil)
|
|
27
|
-
return help("model") if name.nil?
|
|
28
|
-
|
|
29
|
-
setup
|
|
30
|
-
migration("create_#{name.pluralize}")
|
|
31
|
-
@model_name = name.classify
|
|
32
|
-
template("model-template/model.rb", "app/models/#{name.singularize.underscore}.rb")
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
desc "controller NAME", "Generate a new controller"
|
|
36
|
-
def controller(name = nil)
|
|
37
|
-
return help("controller") if name.nil?
|
|
38
|
-
|
|
39
|
-
setup
|
|
40
|
-
unless defined?(ActiveSupport::Inflector)
|
|
41
|
-
raise LoadError, <<~ERR
|
|
42
|
-
ActiveSupport::Inflector is required to run this command. Add the following line to your Gemfile:
|
|
43
|
-
gem "activesupport", require: "active_support/inflector"
|
|
44
|
-
ERR
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# remove trailing Controller if already present
|
|
48
|
-
normalized_name = name.sub(/_?controller$/i, "")
|
|
49
|
-
@controller_name = "#{normalized_name.camelize}Controller"
|
|
50
|
-
file_name = "#{normalized_name.underscore}_controller.rb"
|
|
51
|
-
|
|
52
|
-
template("controller-template/controller.rb", "app/controllers/#{file_name}")
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
private
|
|
56
|
-
|
|
57
|
-
def setup
|
|
58
|
-
@setup ||= begin
|
|
59
|
-
require "rake"
|
|
60
|
-
load "Rakefile"
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
class CLI < Thor
|
|
13
|
+
module Rage::CLI
|
|
14
|
+
class App < Base
|
|
66
15
|
def self.exit_on_failure?
|
|
67
16
|
true
|
|
68
17
|
end
|
|
@@ -74,7 +23,7 @@ module Rage
|
|
|
74
23
|
return help("new") if options.help? || path.nil?
|
|
75
24
|
|
|
76
25
|
require "rage/all"
|
|
77
|
-
|
|
26
|
+
NewAppGenerator.start([path, options[:database]])
|
|
78
27
|
end
|
|
79
28
|
|
|
80
29
|
desc "s", "Start the app server"
|
|
@@ -232,11 +181,14 @@ module Rage
|
|
|
232
181
|
end
|
|
233
182
|
|
|
234
183
|
desc "skills", "Manage coding agent skills"
|
|
235
|
-
subcommand "skills",
|
|
184
|
+
subcommand "skills", Skills
|
|
185
|
+
|
|
186
|
+
desc "openapi", "OpenAPI validation tools"
|
|
187
|
+
subcommand "openapi", OpenAPI
|
|
236
188
|
|
|
237
189
|
map "generate" => :g
|
|
238
190
|
desc "g TYPE", "Generate new code"
|
|
239
|
-
subcommand "g",
|
|
191
|
+
subcommand "g", CodeGenerator
|
|
240
192
|
|
|
241
193
|
map "--tasks" => :tasks
|
|
242
194
|
desc "--tasks", "See the list of available tasks"
|
|
@@ -273,26 +225,6 @@ module Rage
|
|
|
273
225
|
|
|
274
226
|
private
|
|
275
227
|
|
|
276
|
-
def environment
|
|
277
|
-
require File.expand_path("config/application.rb", Dir.pwd)
|
|
278
|
-
|
|
279
|
-
if Rage.config.internal.rails_mode
|
|
280
|
-
require File.expand_path("config/environment.rb", Dir.pwd)
|
|
281
|
-
end
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
def set_env(options)
|
|
285
|
-
if options[:environment]
|
|
286
|
-
ENV["RAGE_ENV"] = ENV["RAILS_ENV"] = options[:environment]
|
|
287
|
-
elsif ENV["RAGE_ENV"]
|
|
288
|
-
ENV["RAILS_ENV"] = ENV["RAGE_ENV"]
|
|
289
|
-
elsif ENV["RAILS_ENV"]
|
|
290
|
-
ENV["RAGE_ENV"] = ENV["RAILS_ENV"]
|
|
291
|
-
else
|
|
292
|
-
ENV["RAGE_ENV"] = ENV["RAILS_ENV"] = "development"
|
|
293
|
-
end
|
|
294
|
-
end
|
|
295
|
-
|
|
296
228
|
def linked_rake_tasks
|
|
297
229
|
require "rake"
|
|
298
230
|
Rake::TaskManager.record_task_metadata = true
|
|
@@ -371,67 +303,4 @@ module Rage
|
|
|
371
303
|
end
|
|
372
304
|
end
|
|
373
305
|
end
|
|
374
|
-
|
|
375
|
-
class CLINewAppGenerator < Thor::Group
|
|
376
|
-
include Thor::Actions
|
|
377
|
-
argument :path, type: :string
|
|
378
|
-
argument :database, type: :string, required: false
|
|
379
|
-
|
|
380
|
-
def self.source_root
|
|
381
|
-
File.expand_path("templates", __dir__)
|
|
382
|
-
end
|
|
383
|
-
|
|
384
|
-
def setup
|
|
385
|
-
@use_database = !database.nil?
|
|
386
|
-
end
|
|
387
|
-
|
|
388
|
-
def create_directory
|
|
389
|
-
empty_directory(path)
|
|
390
|
-
end
|
|
391
|
-
|
|
392
|
-
def copy_files
|
|
393
|
-
inject_templates
|
|
394
|
-
end
|
|
395
|
-
|
|
396
|
-
def install_database
|
|
397
|
-
return unless @use_database
|
|
398
|
-
|
|
399
|
-
@app_name = path.tr("-", "_").downcase
|
|
400
|
-
append_to_file "#{path}/Gemfile", <<~RUBY
|
|
401
|
-
|
|
402
|
-
gem "#{get_db_gem_name}"
|
|
403
|
-
gem "activerecord"
|
|
404
|
-
gem "standalone_migrations", require: false
|
|
405
|
-
RUBY
|
|
406
|
-
|
|
407
|
-
inject_templates("db-templates")
|
|
408
|
-
inject_templates("db-templates/#{database}")
|
|
409
|
-
end
|
|
410
|
-
|
|
411
|
-
private
|
|
412
|
-
|
|
413
|
-
def inject_templates(from = nil)
|
|
414
|
-
root = "#{self.class.source_root}/#{from}"
|
|
415
|
-
|
|
416
|
-
Dir.glob("*", base: root).each do |template|
|
|
417
|
-
next if File.directory?("#{root}/#{template}")
|
|
418
|
-
|
|
419
|
-
*template_path_parts, template_name = template.split("-")
|
|
420
|
-
template("#{root}/#{template}", [path, *template_path_parts, template_name].join("/"))
|
|
421
|
-
end
|
|
422
|
-
end
|
|
423
|
-
|
|
424
|
-
def get_db_gem_name
|
|
425
|
-
case database
|
|
426
|
-
when "mysql"
|
|
427
|
-
"mysql2"
|
|
428
|
-
when "trilogy"
|
|
429
|
-
"trilogy"
|
|
430
|
-
when "postgresql"
|
|
431
|
-
"pg"
|
|
432
|
-
when "sqlite3"
|
|
433
|
-
"sqlite3"
|
|
434
|
-
end
|
|
435
|
-
end
|
|
436
|
-
end
|
|
437
306
|
end
|
data/lib/rage/code_loader.rb
CHANGED
|
@@ -37,6 +37,7 @@ class Rage::CodeLoader
|
|
|
37
37
|
load("#{Rage.root}/config/routes.rb")
|
|
38
38
|
|
|
39
39
|
reload_components
|
|
40
|
+
Rage.config.run_hooks_for(:after_reload)
|
|
40
41
|
end
|
|
41
42
|
|
|
42
43
|
# in Rails mode - reset the routes; everything else will be done by Rails
|
|
@@ -47,6 +48,7 @@ class Rage::CodeLoader
|
|
|
47
48
|
Rage.__router.reset_routes
|
|
48
49
|
|
|
49
50
|
reload_components
|
|
51
|
+
Rage.config.run_hooks_for(:after_reload)
|
|
50
52
|
end
|
|
51
53
|
|
|
52
54
|
def reloading?
|
|
@@ -54,7 +56,9 @@ class Rage::CodeLoader
|
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
def check_updated!
|
|
57
|
-
current_watched = @autoload_path.glob("**/*.rb") + Rage.root.glob("config/routes.rb") + Rage.root.glob("config/openapi_components.*")
|
|
59
|
+
current_watched = @autoload_path.glob("**/*.{rb,erb}") + Rage.root.glob("config/routes.rb") + Rage.root.glob("config/openapi_components.*")
|
|
60
|
+
current_watched += Rage.config.code_loader.reload_paths.flat_map { |pattern| Rage.root.glob(pattern) }
|
|
61
|
+
|
|
58
62
|
current_update_at = current_watched.max_by { |path| path.exist? ? path.mtime.to_f : 0 }&.mtime.to_f
|
|
59
63
|
return false if !@last_watched && !@last_update_at
|
|
60
64
|
|
data/lib/rage/configuration.rb
CHANGED
|
@@ -141,6 +141,15 @@ class Rage::Configuration
|
|
|
141
141
|
push_hook(block, :after_initialize)
|
|
142
142
|
end
|
|
143
143
|
|
|
144
|
+
# Schedule a block of code to run after Rage has reloaded the application code in development. Use this to reset state or re-initialize dependencies that cache application-level constants.
|
|
145
|
+
# @example
|
|
146
|
+
# Rage.config.after_reload do
|
|
147
|
+
# MyCache.clear
|
|
148
|
+
# end
|
|
149
|
+
def after_reload(&block)
|
|
150
|
+
push_hook(block, :after_reload)
|
|
151
|
+
end
|
|
152
|
+
|
|
144
153
|
# Register a custom renderer that generates overloads `render` on all controllers.
|
|
145
154
|
# The block receives the object passed to `render` together with any additional keyword arguments.
|
|
146
155
|
# The code inside the block is executed in the context of the controller instance, so you can access all usual controller methods in it.
|
|
@@ -256,6 +265,29 @@ class Rage::Configuration
|
|
|
256
265
|
def log_tags
|
|
257
266
|
@log_tags ||= LogTags.new
|
|
258
267
|
end
|
|
268
|
+
|
|
269
|
+
# Allows configuring case-insensitive partial matches for redacting structured log context keys.
|
|
270
|
+
# Matching keys will have their values replaced with `"[REDACTED]"` before the log entry is written.
|
|
271
|
+
#
|
|
272
|
+
# @param keys [String, Symbol, Array<String, Symbol>, nil] one or more keys to redact
|
|
273
|
+
# @example Redact common secrets from structured logs
|
|
274
|
+
# Rage.configure do
|
|
275
|
+
# config.log_redact_keys = [:password, :token, :secret]
|
|
276
|
+
# end
|
|
277
|
+
def log_redact_keys=(keys)
|
|
278
|
+
@log_redact_keys = Array(keys).filter_map { |key|
|
|
279
|
+
if !key.is_a?(String) && !key.is_a?(Symbol)
|
|
280
|
+
raise ArgumentError, "log redact keys have to be strings or symbols"
|
|
281
|
+
elsif !key.empty?
|
|
282
|
+
key.to_s
|
|
283
|
+
end
|
|
284
|
+
}.uniq
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# @private
|
|
288
|
+
def log_redact_keys
|
|
289
|
+
@log_redact_keys || []
|
|
290
|
+
end
|
|
259
291
|
# @!endgroup
|
|
260
292
|
|
|
261
293
|
# @!group Telemetry Configuration
|
|
@@ -282,6 +314,14 @@ class Rage::Configuration
|
|
|
282
314
|
end
|
|
283
315
|
# @!endgroup
|
|
284
316
|
|
|
317
|
+
# @!group Code Loader Configuration
|
|
318
|
+
# Allows configuring code loader settings.
|
|
319
|
+
# @return [Rage::Configuration::CodeLoader]
|
|
320
|
+
def code_loader
|
|
321
|
+
@code_loader ||= CodeLoader.new
|
|
322
|
+
end
|
|
323
|
+
# @!endgroup
|
|
324
|
+
|
|
285
325
|
# @!group Blocking Operation Pool Configuration
|
|
286
326
|
# Allows configuring the thread pool for offloading native calls.
|
|
287
327
|
# @return [Rage::Configuration::BlockingOperationPool]
|
|
@@ -435,7 +475,7 @@ class Rage::Configuration
|
|
|
435
475
|
# end
|
|
436
476
|
def <<(reporter)
|
|
437
477
|
validate_input!(reporter)
|
|
438
|
-
|
|
478
|
+
raise ArgumentError, "#{reporter} is already registered" if @objects.include?(reporter)
|
|
439
479
|
|
|
440
480
|
@objects << reporter
|
|
441
481
|
Rage::Errors.__send__(:__register_reporter, reporter)
|
|
@@ -878,6 +918,7 @@ class Rage::Configuration
|
|
|
878
918
|
@backend_options = parse_disk_backend_options(opts)
|
|
879
919
|
Rage::Deferred::Backends::Disk
|
|
880
920
|
when nil
|
|
921
|
+
@backend_options = {} if RUBY_VERSION.start_with?("3.3.")
|
|
881
922
|
Rage::Deferred::Backends::Nil
|
|
882
923
|
else
|
|
883
924
|
raise ArgumentError, "unsupported backend value; supported keys are `:disk` and `nil`"
|
|
@@ -1123,6 +1164,29 @@ class Rage::Configuration
|
|
|
1123
1164
|
attr_accessor :form_actions
|
|
1124
1165
|
end
|
|
1125
1166
|
|
|
1167
|
+
class CodeLoader
|
|
1168
|
+
# @private
|
|
1169
|
+
def initialize
|
|
1170
|
+
@reload_paths = []
|
|
1171
|
+
end
|
|
1172
|
+
|
|
1173
|
+
# @private
|
|
1174
|
+
attr_reader :reload_paths
|
|
1175
|
+
|
|
1176
|
+
# Specify additional paths to watch for changes in development.
|
|
1177
|
+
#
|
|
1178
|
+
# @param paths [Array<Pathname, String>] glob patterns or directory paths to watch
|
|
1179
|
+
# @example Watch HAML templates
|
|
1180
|
+
# Rage.configure do
|
|
1181
|
+
# config.code_loader.reload_paths = ["app/views/**/*.haml"]
|
|
1182
|
+
# end
|
|
1183
|
+
def reload_paths=(paths)
|
|
1184
|
+
@reload_paths = Array(paths).map do |path|
|
|
1185
|
+
path.is_a?(Pathname) ? path : Pathname.new(path)
|
|
1186
|
+
end
|
|
1187
|
+
end
|
|
1188
|
+
end
|
|
1189
|
+
|
|
1126
1190
|
class BlockingOperationPool
|
|
1127
1191
|
# @!attribute enabled
|
|
1128
1192
|
# Enable a background thread pool for offloading native calls that can be executed outside the GVL, freeing
|
|
@@ -1165,7 +1229,11 @@ class Rage::Configuration
|
|
|
1165
1229
|
# end
|
|
1166
1230
|
def <<(daemon)
|
|
1167
1231
|
validate!(daemon)
|
|
1232
|
+
raise ArgumentError, "#{daemon} is already registered" if @klasses.include?(daemon)
|
|
1233
|
+
|
|
1168
1234
|
@klasses << daemon
|
|
1235
|
+
|
|
1236
|
+
self
|
|
1169
1237
|
end
|
|
1170
1238
|
|
|
1171
1239
|
alias_method :push, :<<
|
|
@@ -1260,12 +1328,14 @@ class Rage::Configuration
|
|
|
1260
1328
|
if @logger
|
|
1261
1329
|
@logger.formatter = @log_formatter if @log_formatter
|
|
1262
1330
|
@logger.level = @log_level if @log_level
|
|
1331
|
+
@logger.log_redact_keys = @log_redact_keys if @log_redact_keys
|
|
1263
1332
|
else
|
|
1264
1333
|
@logger = Rage::Logger.new(nil)
|
|
1265
1334
|
end
|
|
1266
1335
|
|
|
1267
|
-
if @log_formatter && @logger.external_logger.is_a?(Rage::Logger::External::Dynamic)
|
|
1336
|
+
if @log_formatter && @logger.external_logger.is_a?(Rage::Logger::External::Dynamic) && !@log_formatter_warning_shown
|
|
1268
1337
|
puts "WARNING: changing the log formatter via `config.log_formatter=` has no effect when using a custom external logger."
|
|
1338
|
+
@log_formatter_warning_shown = true
|
|
1269
1339
|
end
|
|
1270
1340
|
|
|
1271
1341
|
if @log_context
|
data/lib/rage/daemon.rb
CHANGED
|
@@ -174,7 +174,6 @@ class Rage::Daemon
|
|
|
174
174
|
instance = new
|
|
175
175
|
result = instance.perform
|
|
176
176
|
break if result.equal?(Stop) || @__stopping
|
|
177
|
-
Rage.logger.warn("Daemon exited, restarting...")
|
|
178
177
|
rescue => e
|
|
179
178
|
break if @__stopping
|
|
180
179
|
Rage.logger.error("Daemon failed with exception: #{e.class} (#{e.message}):\n#{e.backtrace.join("\n")}")
|
|
@@ -185,7 +184,11 @@ class Rage::Daemon
|
|
|
185
184
|
|
|
186
185
|
# reset backoff if ran successfully for a while
|
|
187
186
|
backoff = INITIAL_BACKOFF if Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at > BACKOFF_RESET_INTERVAL
|
|
188
|
-
|
|
187
|
+
|
|
188
|
+
interval = (backoff / 2 + rand * backoff / 2).round(2)
|
|
189
|
+
Rage.logger.warn("Daemon exited, restarting in #{interval}s")
|
|
190
|
+
sleep(interval)
|
|
191
|
+
|
|
189
192
|
backoff = (backoff * 2).clamp(INITIAL_BACKOFF, MAX_BACKOFF)
|
|
190
193
|
end
|
|
191
194
|
end
|