regolith 0.1.19 → 0.1.21

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: 9f4a42e2d4a1c0be079dff218128afc9451aeff7dae6c97f1c93ca5a43eb4ee9
4
- data.tar.gz: 5783be43db0adbe9ac420f14190f4c8959130be1bcf9d94409549fb49bb301bd
3
+ metadata.gz: d590506b9d76a0cae7c0b4cc1355860e601c42740d8f4de852e41c001cee08e8
4
+ data.tar.gz: 412b231f3860a025d6f48c6f96aeb2dc5b60d9368d298e05849d490221f0092d
5
5
  SHA512:
6
- metadata.gz: 8f82bd5a6ec97d8924eceb852ce49541808dc53483492968e1fbe9885cca0924d9a173f592e391afa95d6755dbe34d1d1eda89268bf9aadfcb5e2980dc2e200c
7
- data.tar.gz: 462938815ddc6d7fbc4a7ea8c225e578538b90e824e13fc28a7dc4f153ba6e7379d72706b2223bef91782d63fbeea9c432f20b9f3f5eb9736d5828afaeb123b7
6
+ metadata.gz: f22e33c9f838f9a2c61af3808e8c7e0ba5f65e3729653e4674d8e3562dd184d6184f9b284fdcd657ad0f3ac6327521ccd2edbbea5e9a478b8e822f14edcb523d
7
+ data.tar.gz: a79ff4e21eba13f58cbed3d598dad134cf34df9ee9e3d7c99d0209397fec42b58ba180c636c7d8f2984902a49b8fd614335d819de156ea1e5bcd76128990122a
data/lib/regolith/cli.rb CHANGED
@@ -1,62 +1,10 @@
1
1
  require 'fileutils'
2
2
  require 'yaml'
3
- require 'psych'
4
3
  require 'erb'
5
4
  require 'timeout'
6
5
  require 'rubygems'
7
- require 'net/http'
8
- require 'uri'
9
- require 'json'
10
- require 'ostruct'
11
- require 'set'
12
- require 'rbconfig'
13
- require 'shellwords'
14
6
 
15
7
  module Regolith
16
- # NOTE: VERSION is defined in lib/regolith/version.rb.
17
- # Do not define Regolith::VERSION here to avoid constant redefinition warnings.
18
-
19
- class << self
20
- # Lightweight configuration with sane defaults
21
- def configuration
22
- @configuration ||= OpenStruct.new(
23
- timeout: 10,
24
- default_port: 3001
25
- )
26
- end
27
-
28
- def service_registry
29
- @service_registry ||= begin
30
- path = find_regolith_config
31
- if path && File.exist?(path)
32
- config = YAML.load_file(path) || {}
33
- config['services'] || {}
34
- else
35
- {}
36
- end
37
- end
38
- end
39
-
40
- def reload_service_registry!
41
- @service_registry = nil
42
- end
43
-
44
- private
45
-
46
- def find_regolith_config
47
- current_dir = Dir.pwd
48
- loop do
49
- config_path = File.join(current_dir, 'config', 'regolith.yml')
50
- return config_path if File.exist?(config_path)
51
-
52
- parent = File.dirname(current_dir)
53
- break if parent == current_dir
54
- current_dir = parent
55
- end
56
- nil
57
- end
58
- end
59
-
60
8
  class CLI
61
9
  def initialize(args)
62
10
  @args = args
@@ -71,47 +19,11 @@ module Regolith
71
19
  create_new_app(@subcommand)
72
20
  when 'generate'
73
21
  generate_resource(@subcommand, @name)
74
- when 'server', 'up'
75
- start_server(parse_flags(@args[1..-1]))
76
- when 'down'
77
- stop_services
78
- when 'restart'
79
- restart_service(@subcommand)
80
- when 'stop'
81
- stop_service(@subcommand)
82
- when 'ps', 'status'
83
- show_status
84
- when 'logs'
85
- show_logs(@subcommand, parse_flags(@args[2..-1]))
86
- when 'exec'
87
- exec_command(@subcommand, @args[2..-1])
88
- when 'shell'
89
- shell_service(@subcommand)
22
+ when 'server'
23
+ start_server
90
24
  when 'console'
91
25
  open_console(@subcommand)
92
- when 'rails'
93
- rails_passthrough(@subcommand, @args[2..-1])
94
- when 'routes'
95
- show_routes(@subcommand)
96
- when 'open'
97
- open_service(@subcommand)
98
- when 'db'
99
- db_command(@subcommand, @name, parse_flags(@args[3..-1]))
100
- when 'test'
101
- run_tests(@subcommand, parse_flags(@args[2..-1]))
102
- when 'health'
103
- health_check
104
- when 'config'
105
- show_config(parse_flags(@args[1..-1]))
106
- when 'prune'
107
- prune_system
108
- when 'rebuild'
109
- rebuild_service(@subcommand)
110
- when 'doctor'
111
- run_doctor
112
- when 'inspect'
113
- inspect_services(parse_flags(@args[1..-1]))
114
- when 'version', '--version', '-v'
26
+ when 'version'
115
27
  puts "Regolith #{Regolith::VERSION}"
116
28
  else
117
29
  show_help
@@ -120,23 +32,6 @@ module Regolith
120
32
 
121
33
  private
122
34
 
123
- def parse_flags(args)
124
- flags = {}
125
- return flags unless args
126
-
127
- args.each do |arg|
128
- if arg.start_with?('--')
129
- key, value = arg[2..-1].split('=', 2)
130
- flags[key.to_sym] = value || true
131
- elsif arg == '-f'
132
- flags[:follow] = true
133
- elsif arg == '--all'
134
- flags[:all] = true
135
- end
136
- end
137
- flags
138
- end
139
-
140
35
  def create_new_app(app_name)
141
36
  unless app_name
142
37
  puts "❌ Error: App name required"
@@ -174,9 +69,7 @@ module Regolith
174
69
  File.write('docker-compose.yml', generate_docker_compose(app_name))
175
70
  File.write('Makefile', generate_makefile)
176
71
  File.write('.bin/regolith', generate_regolith_shim)
177
- File.write('.gitignore', generate_gitignore)
178
- File.write('README.md', generate_readme(app_name))
179
- FileUtils.chmod(0755, '.bin/regolith')
72
+ FileUtils.chmod(0o755, '.bin/regolith')
180
73
  end
181
74
 
182
75
  def generate_resource(resource_type, resource_name)
@@ -190,16 +83,9 @@ module Regolith
190
83
  end
191
84
 
192
85
  def generate_service(service_name)
193
- # Validate service name
194
- unless service_name =~ /\A[a-z][a-z0-9_]*\z/
195
- puts "❌ Invalid service name. Use lowercase, digits, and underscores only."
196
- puts "Examples: users, user_profiles, api_gateway"
197
- exit 1
198
- end
199
-
200
86
  puts "🔧 Creating service '#{service_name}'..."
201
87
  config = load_regolith_config
202
- port = next_available_port
88
+ port = 3001 + (config['services']&.size || 0)
203
89
  service_dir = "services/#{service_name}_service"
204
90
 
205
91
  puts " → Generating Rails API app..."
@@ -230,542 +116,154 @@ module Regolith
230
116
  exit 1
231
117
  end
232
118
 
233
- customize_service(service_dir, service_name, port)
119
+ # Overwrite Gemfile before bundle install
120
+ puts "🔧 Overwriting Gemfile to remove sqlite3 and other defaults..."
234
121
 
235
- config['services'][service_name] = {
236
- 'port' => port,
237
- 'root' => "./#{service_dir}"
238
- }
122
+ major_minor = `ruby -e 'print RUBY_VERSION.split(".")[0..1].join(".")'`.strip
239
123
 
240
- save_regolith_config(config)
241
- update_docker_compose(config)
124
+ custom_gemfile = <<~GEMFILE
125
+ source "https://rubygems.org"
242
126
 
243
- puts " Created service '#{service_name}'"
244
- puts "🚀 Service will run on port #{port}"
245
- puts "→ Next: regolith generate service <another_service> or regolith server"
246
- end
127
+ ruby "~> #{major_minor}.0"
247
128
 
248
- def next_available_port(start = (Regolith.configuration.respond_to?(:default_port) ? Regolith.configuration.default_port : 3001))
249
- used = load_regolith_config['services'].values.map { |s| s['port'] }.to_set
250
- port = start
251
- port += 1 while used.include?(port)
252
- port
253
- end
129
+ gem "rails", "~> 7.2.2.1"
130
+ gem "pg", "~> 1.5"
131
+ gem "puma", ">= 5.0"
132
+ gem "rack-cors"
254
133
 
255
- def customize_service(service_dir, service_name, port)
256
- # Detect Ruby MAJOR.MINOR
257
- major_minor = RUBY_VERSION.split(".")[0..1].join(".")
134
+ group :development, :test do
135
+ gem "debug", platforms: %i[ mri mswin mswin64 mingw x64_mingw ], require: "debug/prelude"
136
+ gem "brakeman", require: false
137
+ gem "rubocop-rails-omakase", require: false
138
+ end
139
+
140
+ # Regolith vendored gem so the container can build without network access to your local gem
141
+ gem "regolith", path: "vendor/regolith"
142
+ GEMFILE
258
143
 
259
- # Write Gemfile (no vendoring; pull from RubyGems)
260
- custom_gemfile = generate_gemfile(major_minor)
261
144
  File.write("#{service_dir}/Gemfile", custom_gemfile)
262
145
 
146
+ # Vendor Regolith gem into service for Docker compatibility
147
+ vendor_dir = File.join(service_dir, "vendor")
148
+ FileUtils.mkdir_p(vendor_dir)
149
+ FileUtils.cp_r(File.expand_path("../..", __dir__), File.join(vendor_dir, "regolith"))
150
+ puts "📦 Vendored Regolith gem into #{File.join(service_dir, 'vendor', 'regolith')}"
151
+
263
152
  puts " → Running bundle install..."
264
153
  Dir.chdir(service_dir) do
265
154
  unless system("bundle install")
266
155
  puts "❌ bundle install failed"
267
- puts "→ You may be missing system libraries like libyaml-dev build-essential"
156
+ puts "→ You may be missing system libraries like libyaml-dev libsqlite3-dev build-essential pkg-config"
157
+ puts "→ Try: sudo apt install -y libyaml-dev libsqlite3-dev build-essential pkg-config"
268
158
  exit 1
269
159
  end
270
160
  end
271
161
 
272
162
  patch_rails_app(service_dir, service_name, port)
273
- end
274
-
275
- def generate_gemfile(ruby_version)
276
- <<~GEMFILE
277
- source "https://rubygems.org"
163
+ patch_bootsnap(service_dir)
278
164
 
279
- ruby "~> #{ruby_version}.0"
280
-
281
- gem "rails", "~> 7.2.2.1"
282
- gem "pg", "~> 1.5"
283
- gem "puma", ">= 5.0"
284
- gem "rack-cors"
285
- gem "bootsnap", require: false
286
-
287
- group :development, :test do
288
- gem "debug", platforms: %i[mri mswin mswin64 mingw x64_mingw], require: "debug/prelude"
289
- gem "brakeman", require: false
290
- gem "rubocop-rails-omakase", require: false
291
- end
165
+ config['services'][service_name] = {
166
+ 'port' => port,
167
+ 'root' => "./#{service_dir}"
168
+ }
169
+ save_regolith_config(config)
170
+ update_docker_compose(config)
292
171
 
293
- gem "regolith"
294
- GEMFILE
172
+ puts "✅ Created service '#{service_name}'"
173
+ puts "🚀 Service running on port #{port}"
174
+ puts "→ Next: regolith generate service <another_service> or regolith server"
295
175
  end
296
176
 
297
177
  def patch_rails_app(service_dir, service_name, port)
298
- create_initializers(service_dir, service_name)
299
- create_health_controller(service_dir)
300
- add_health_route(service_dir)
301
- File.write("#{service_dir}/Dockerfile", generate_dockerfile)
302
- patch_application_rb(service_dir, service_name, port)
303
- end
304
-
305
- def create_initializers(service_dir, service_name)
306
178
  initializer_dir = "#{service_dir}/config/initializers"
307
179
  FileUtils.mkdir_p(initializer_dir)
308
-
309
180
  File.write("#{initializer_dir}/regolith.rb", generate_regolith_initializer(service_name))
310
- File.write("#{initializer_dir}/cors.rb", generate_cors_initializer)
311
- end
312
-
313
- def create_health_controller(service_dir)
314
- controller_dir = "#{service_dir}/app/controllers/regolith"
315
- FileUtils.mkdir_p(controller_dir)
316
-
317
- File.write("#{controller_dir}/health_controller.rb", generate_health_controller)
318
- end
319
-
320
- def add_health_route(service_dir)
321
- routes_path = File.join(service_dir, "config/routes.rb")
322
- content = File.read(routes_path)
323
-
324
- # Try to inject before the final end, or append if no clear structure
325
- if content.sub!(/end\s*\z/, " get '/health', to: 'regolith/health#show'\nend\n")
326
- File.write(routes_path, content)
327
- else
328
- # Fallback: append inside the draw block
329
- File.open(routes_path, "a") { |f| f.puts "get '/health', to: 'regolith/health#show'" }
330
- end
331
- end
181
+ File.write("#{service_dir}/Dockerfile", generate_dockerfile)
332
182
 
333
- def patch_application_rb(service_dir, service_name, port)
334
183
  app_rb_path = "#{service_dir}/config/application.rb"
335
184
  app_rb_content = File.read(app_rb_path)
336
185
 
337
186
  cors_config = <<~RUBY
338
187
 
339
188
  # Regolith configuration
189
+ config.middleware.insert_before 0, Rack::Cors do
190
+ allow do
191
+ origins '*'
192
+ resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head]
193
+ end
194
+ end
195
+
340
196
  config.regolith_service_name = '#{service_name}'
341
197
  config.regolith_service_port = #{port}
342
198
  RUBY
343
199
 
344
- app_rb_content.gsub!(/(\n end\n\z)/, "#{cors_config}\1")
345
- File.write(app_rb_path, app_rb_content)
346
- end
347
-
348
- # Service management commands
349
- def start_server(_flags = {})
350
- unless File.exist?('docker-compose.yml')
351
- puts "❌ Error: Not in a Regolith app directory"
352
- exit 1
200
+ app_rb_content.gsub!(/class Application < Rails::Application.*?
201
+ end/m) do |match|
202
+ match.gsub(/(\n end)$/, "#{cors_config}\1")
353
203
  end
354
204
 
355
- puts "🚀 Starting Regolith services..."
356
-
357
- config = load_regolith_config
358
- show_service_info(config)
359
-
360
- exec_compose('up', '--build')
361
- end
362
-
363
- def stop_services
364
- puts "🛑 Stopping all services..."
365
- exec_compose('down', '-v')
205
+ File.write(app_rb_path, app_rb_content)
366
206
  end
367
207
 
368
- def restart_service(service_name = nil)
369
- if service_name
370
- ensure_service_exists!(service_name)
371
- puts "🔄 Restarting service '#{service_name}'..."
372
- exec_compose('restart', service_name)
373
- else
374
- puts "🔄 Restarting all services..."
375
- exec_compose('restart')
376
- end
377
- end
208
+ # ---- Bootsnap Patch: make it optional & non-fatal ----
209
+ def patch_bootsnap(service_dir)
210
+ boot_path = File.join(service_dir, "config", "boot.rb")
211
+ return unless File.exist?(boot_path)
378
212
 
379
- def stop_service(service_name = nil)
380
- if service_name
381
- ensure_service_exists!(service_name)
382
- puts "⏹ Stopping service '#{service_name}'..."
383
- exec_compose('stop', service_name)
384
- else
385
- puts "⏹ Stopping all services..."
386
- exec_compose('stop')
387
- end
388
- end
213
+ content = File.read(boot_path)
389
214
 
390
- def show_status
391
- puts "📊 Service Status:"
392
- puts
393
-
394
- # Try different format options for maximum compatibility
395
- success = system_compose('ps', '--format', 'table') ||
396
- system_compose('ps', '--format', 'json') ||
397
- system_compose('ps')
398
-
399
- # Show summary counts and health check for exit code
400
- if success
401
- config = load_regolith_config
402
- service_count = config['services'].size
403
- ports = config['services'].values.map { |s| s['port'] }.sort
404
-
405
- puts
406
- puts "📋 Summary: #{service_count} services configured"
407
- puts "🚪 Ports: #{ports.join(', ')}" if ports.any?
408
-
409
- # Check health for proper exit code
410
- healthy_services = 0
411
- config['services'].each do |name, service_config|
412
- port = service_config['port']
413
- status = check_service_health(port)
414
- healthy_services += 1 if status[:healthy]
215
+ # Remove any unconditional bootsnap require and replace with guarded load
216
+ content.gsub!(/^\s*require\s+["']bootsnap\/setup["']\s*$/, <<~RUBY.strip)
217
+ begin
218
+ require "bootsnap/setup"
219
+ rescue LoadError
220
+ warn "[regolith] bootsnap not found; continuing without it"
415
221
  end
222
+ RUBY
416
223
 
417
- if healthy_services < service_count
418
- puts "⚠️ #{service_count - healthy_services} services unhealthy"
419
- exit 1
420
- end
421
- end
422
- end
423
-
424
- def show_logs(service_name = nil, flags = {})
425
- args = ['logs']
426
- args << '--follow' if flags[:follow] || flags[:f]
427
- args << service_name if service_name
428
-
429
- exec_compose(*args)
430
- end
431
-
432
- def exec_command(service_name, command_args)
433
- ensure_service_exists!(service_name)
434
-
435
- if command_args.empty?
436
- command_args = ['bash']
437
- end
438
-
439
- exec_compose('exec', service_name, *command_args)
440
- end
441
-
442
- def shell_service(service_name)
443
- unless service_name
444
- puts "❌ Error: Service name required"
445
- puts "Usage: regolith shell <service_name>"
446
- exit 1
447
- end
448
-
449
- ensure_service_exists!(service_name)
450
- puts "🐚 Opening shell for #{service_name}_service..."
451
- exec_compose('exec', service_name, 'bash')
452
- end
453
-
454
- # Rails integration commands
455
- def rails_passthrough(service_name, rails_args)
456
- ensure_service_exists!(service_name)
457
-
458
- if rails_args.empty?
459
- puts "❌ Error: Rails command required"
460
- puts "Usage: regolith rails <service> <command>"
461
- exit 1
462
- end
463
-
464
- exec_compose('exec', service_name, 'bash', '-lc',
465
- "bundle exec rails #{Shellwords.join(rails_args)}")
466
- end
467
-
468
- def open_console(service_name)
469
- unless service_name
470
- puts "❌ Error: Service name required"
471
- puts "Usage: regolith console <service_name>"
472
- exit 1
473
- end
474
-
475
- ensure_service_exists!(service_name)
476
- puts "🧪 Opening Rails console for #{service_name}_service..."
477
- exec_compose('exec', service_name, 'rails', 'console')
224
+ File.write(boot_path, content)
478
225
  end
226
+ # ------------------------------------------------------
479
227
 
480
- def open_service(service_name)
481
- unless service_name
482
- puts "❌ Error: Service name required"
483
- puts "Usage: regolith open <service_name>"
228
+ def start_server
229
+ unless File.exist?('docker-compose.yml')
230
+ puts "❌ Error: Not in a Regolith app directory"
484
231
  exit 1
485
232
  end
486
233
 
487
- ensure_service_exists!(service_name)
234
+ puts "🚀 Starting Regolith services..."
488
235
  config = load_regolith_config
489
- port = config['services'][service_name]['port']
490
- url = "http://localhost:#{port}"
491
-
492
- puts "🌐 Opening #{url}..."
493
236
 
494
- # Cross-platform open command
495
- case RbConfig::CONFIG['host_os']
496
- when /mswin|mingw|cygwin/
497
- system(%{start "" "#{url}"})
498
- when /darwin/
499
- system("open #{url}")
500
- else
501
- system("xdg-open #{url}") || puts("Visit: #{url}")
502
- end
503
- end
504
-
505
- # Database commands
506
- def db_command(action, target, flags = {})
507
- case action
508
- when 'create', 'migrate', 'seed', 'reset', 'setup', 'drop'
509
- each_target_service(target, flags) do |service|
510
- puts "🗄 Running db:#{action} for #{service}..."
511
- success = system_compose('exec', service, 'bash', '-lc', "bundle exec rails db:#{action}")
512
-
513
- unless success
514
- puts "❌ db:#{action} failed for #{service} (exit code: #{$?.exitstatus})"
515
- exit 1 unless flags[:continue_on_failure]
516
- end
517
- end
518
- else
519
- puts "❌ Unknown db action: #{action}"
520
- puts "Available: create, migrate, seed, reset, setup, drop"
521
- exit 1
237
+ config['services'].each do |name, service|
238
+ puts "🚀 #{name}_service running at http://localhost:#{service['port']}"
522
239
  end
523
- end
524
240
 
525
- # Testing commands
526
- def run_tests(target = nil, flags = {})
527
- each_target_service(target, flags) do |service|
528
- puts "🧪 Running tests for #{service}..."
529
- success = system_compose('exec', service, 'bash', '-lc', 'bundle exec rails test')
241
+ puts "🧭 Service registry loaded from config/regolith.yml"
242
+ puts ""
530
243
 
531
- unless success
532
- puts "❌ Tests failed for #{service} (exit code: #{$?.exitstatus})"
533
- exit 1 unless flags[:continue_on_failure]
534
- end
535
- end
536
-
537
- puts "✅ All tests passed!" if flags[:all]
244
+ exec("docker-compose up --build")
538
245
  end
539
246
 
540
- def show_routes(service_name)
247
+ def open_console(service_name)
541
248
  unless service_name
542
249
  puts "❌ Error: Service name required"
543
- puts "Usage: regolith routes <service_name>"
250
+ puts "Usage: regolith console <service_name>"
544
251
  exit 1
545
252
  end
546
253
 
547
- ensure_service_exists!(service_name)
548
- puts "🛤 Routes for #{service_name}:"
549
- system_compose('exec', service_name, 'bash', '-lc', 'bundle exec rails routes')
550
- end
551
-
552
- # Health and monitoring
553
- def health_check
554
- puts "🔍 Health Check Results:"
555
- puts
556
-
557
- config = load_regolith_config
558
-
559
- config['services'].each do |name, service_config|
560
- port = service_config['port']
561
- status = check_service_health(port)
562
-
563
- status_icon = status[:healthy] ? '✅' : '❌'
564
- puts "#{status_icon} #{name} (port #{port}) - #{status[:message]}"
565
-
566
- # Show additional health data if available
567
- if status[:data] && status[:data]['version']
568
- puts " Version: #{status[:data]['version']}"
569
- end
570
- if status[:data] && status[:data]['time']
571
- puts " Last seen: #{Time.at(status[:data]['time']).strftime('%H:%M:%S')}"
572
- end
573
- end
574
- end
575
-
576
- def check_service_health(port)
577
- begin
578
- uri = URI("http://localhost:#{port}/health")
579
- response = Net::HTTP.get_response(uri)
580
-
581
- if response.code.to_i == 200
582
- # Try to parse structured health data
583
- health_data = JSON.parse(response.body) rescue {}
584
- {
585
- healthy: true,
586
- message: 'healthy',
587
- data: health_data
588
- }
589
- else
590
- { healthy: false, message: "HTTP #{response.code}" }
591
- end
592
- rescue
593
- { healthy: false, message: 'unreachable' }
594
- end
595
- end
596
-
597
- # Configuration
598
- def show_config(flags = {})
599
- config = load_regolith_config
600
-
601
- if flags[:json]
602
- puts JSON.pretty_generate(config)
603
- else
604
- puts "📋 Current Configuration:"
605
- puts
606
- puts YAML.dump(config)
607
- end
608
- end
609
-
610
- # Maintenance commands
611
- def prune_system
612
- puts "🧹 Pruning Docker system..."
613
- exec_compose('down', '-v')
614
- system('docker', 'system', 'prune', '-f')
615
- puts "✅ System pruned"
616
- end
617
-
618
- def rebuild_service(service_name = nil)
619
- if service_name
620
- ensure_service_exists!(service_name)
621
- puts "🔨 Rebuilding service '#{service_name}'..."
622
- exec_compose('build', '--no-cache', service_name)
623
- else
624
- puts "🔨 Rebuilding all services..."
625
- exec_compose('build', '--no-cache')
626
- end
627
- end
628
-
629
- # System diagnostics
630
- def run_doctor
631
- puts "🩺 Regolith System Doctor"
632
- puts "=" * 40
633
-
634
- checks = [
635
- { name: "Docker", check: -> { system('docker --version > /dev/null 2>&1') } },
636
- { name: "Docker Compose", check: -> { docker_compose_available? } },
637
- { name: "Ruby", check: -> { system('ruby --version > /dev/null 2>&1') } },
638
- { name: "Rails", check: -> { system('rails --version > /dev/null 2>&1') } },
639
- { name: "PostgreSQL Client", check: -> { system('psql --version > /dev/null 2>&1') } }
640
- ]
641
-
642
- checks.each do |check|
643
- status = check[:check].call ? "✅" : "❌"
644
- puts "#{status} #{check[:name]}"
645
- end
646
-
647
- puts
648
- check_regolith_config
649
- end
650
-
651
- def check_regolith_config
652
- puts "📋 Checking Regolith configuration..."
653
-
654
- if File.exist?('config/regolith.yml')
655
- config = load_regolith_config
656
-
657
- if config['services'].empty?
658
- puts "⚠️ No services configured"
659
- else
660
- puts "✅ Configuration valid (#{config['services'].size} services)"
661
- end
662
- else
663
- puts "❌ No regolith.yml found - not in a Regolith project?"
664
- end
665
- end
666
-
667
- def inspect_services(flags = {})
668
- puts "🔍 Regolith Services Inspection"
669
- puts "=" * 40
670
-
671
254
  config = load_regolith_config
672
-
673
- if config['services'].empty?
674
- puts "No services configured yet."
675
- return
676
- end
677
-
678
- if flags[:json]
679
- # JSON output for automation
680
- inspection_data = {
681
- services: config['services'].map do |name, service_config|
682
- {
683
- name: name,
684
- port: service_config['port'],
685
- endpoint: "http://localhost:#{service_config['port']}",
686
- root: service_config['root']
687
- }
688
- end,
689
- config: config
690
- }
691
- puts JSON.pretty_generate(inspection_data)
692
- else
693
- # Human-readable output
694
- puts "\n📊 Service Endpoints:"
695
- config['services'].each do |name, service_config|
696
- port = service_config['port']
697
- puts " #{name}: http://localhost:#{port}"
698
- end
699
-
700
- puts "\n📋 Full Configuration:"
701
- puts YAML.dump(config)
702
- end
703
- end
704
-
705
- # Helper methods
706
- def docker_compose_available?
707
- system('docker compose version > /dev/null 2>&1') ||
708
- system('docker-compose version > /dev/null 2>&1')
709
- end
710
-
711
- def docker_compose_command
712
- if system('docker compose version > /dev/null 2>&1')
713
- %w[docker compose]
714
- elsif system('docker-compose version > /dev/null 2>&1')
715
- %w[docker-compose]
716
- else
717
- puts "❌ Docker Compose not found"
255
+ unless config['services'][service_name]
256
+ puts "❌ Error: Service '#{service_name}' not found"
718
257
  exit 1
719
258
  end
720
- end
721
-
722
- def exec_compose(*args)
723
- cmd = docker_compose_command + args
724
- exec(*cmd)
725
- end
726
-
727
- def system_compose(*args)
728
- cmd = docker_compose_command + args
729
- system(*cmd)
730
- end
731
-
732
- def each_target_service(target, flags = {})
733
- services = if target == '--all' || target.nil? || flags[:all]
734
- load_regolith_config['services'].keys
735
- else
736
- [target]
737
- end
738
259
 
739
- services.each do |service|
740
- ensure_service_exists!(service)
741
- yield(service)
742
- end
743
- end
744
-
745
- def ensure_service_exists!(service_name)
746
- config = load_regolith_config
747
- unless config['services'].key?(service_name)
748
- puts "❌ Service '#{service_name}' not found"
749
- puts "Available services: #{config['services'].keys.join(', ')}"
750
- exit 1
751
- end
752
- end
753
-
754
- def show_service_info(config)
755
- config['services'].each do |name, service|
756
- puts "🚀 #{name}_service: http://localhost:#{service['port']}"
757
- end
758
-
759
- puts "🧭 Service registry: config/regolith.yml"
760
- puts
260
+ puts "🧪 Opening Rails console for #{service_name}_service..."
261
+ exec("docker-compose exec #{service_name} rails console")
761
262
  end
762
263
 
763
264
  def load_regolith_config
764
- config_path = Regolith.send(:find_regolith_config)
765
- return { 'services' => {} } unless config_path && File.exist?(config_path)
766
-
767
- # Use safe YAML loading
768
- config = Psych.safe_load(File.read(config_path), permitted_classes: [], aliases: false) || {}
265
+ return({ 'services' => {} }) unless File.exist?('config/regolith.yml')
266
+ config = YAML.load_file('config/regolith.yml') || {}
769
267
  config['services'] ||= {}
770
268
  config
771
269
  end
@@ -773,7 +271,6 @@ module Regolith
773
271
  def save_regolith_config(config)
774
272
  FileUtils.mkdir_p('config')
775
273
  File.write('config/regolith.yml', YAML.dump(config))
776
- Regolith.reload_service_registry!
777
274
  end
778
275
 
779
276
  def update_docker_compose(config)
@@ -781,91 +278,57 @@ module Regolith
781
278
  File.write('docker-compose.yml', docker_compose)
782
279
  end
783
280
 
784
- # File generators
785
281
  def generate_docker_compose(app_name, services = {})
786
- compose_services = {
787
- 'db' => {
788
- 'image' => 'postgres:14',
789
- 'environment' => {
790
- 'POSTGRES_DB' => "#{app_name}_development",
791
- 'POSTGRES_USER' => 'postgres',
792
- 'POSTGRES_PASSWORD' => 'password'
793
- },
794
- 'ports' => ['5432:5432'],
795
- 'volumes' => ['postgres_data:/var/lib/postgresql/data'],
796
- 'networks' => ['regolith'],
797
- 'healthcheck' => {
798
- 'test' => ['CMD-SHELL', 'pg_isready -U postgres'],
799
- 'interval' => '10s',
800
- 'timeout' => '5s',
801
- 'retries' => 5
802
- }
803
- }
804
- }
805
-
806
- services.each do |name, service|
807
- db_name = "#{app_name}_#{name}_development" # ← per-service DB
808
-
809
- compose_services[name] = {
810
- 'build' => {
811
- 'context' => service['root'],
812
- 'args' => { 'BUILD_ENV' => 'development' }
813
- },
814
- 'ports' => ["#{service['port']}:3000"],
815
- 'networks' => ['regolith'],
816
- 'depends_on' => { 'db' => { 'condition' => 'service_healthy' } },
817
- 'environment' => {
818
- 'DATABASE_URL' => "postgres://postgres:password@db:5432/#{db_name}",
819
- 'REGOLITH_SERVICE_NAME' => name,
820
- 'REGOLITH_SERVICE_PORT' => service['port']
821
- },
822
- 'volumes' => ["#{service['root']}:/app"],
823
- 'command' => 'bash -c "rm -f tmp/pids/server.pid && bundle install && rails db:prepare && rails server -b 0.0.0.0"',
824
- 'healthcheck' => {
825
- 'test' => ['CMD-SHELL', 'curl -f http://localhost:3000/health || exit 1'],
826
- 'interval' => '30s', 'timeout' => '10s', 'retries' => 3, 'start_period' => '40s'
827
- }
828
- }
829
- end
830
-
831
-
832
- {
833
- 'version' => '3.8',
834
- 'networks' => {
835
- 'regolith' => {}
836
- },
837
- 'services' => compose_services,
838
- 'volumes' => {
839
- 'postgres_data' => nil
840
- }
841
- }.to_yaml
282
+ template = <<~YAML
283
+ version: '3.8'
284
+
285
+ services:
286
+ db:
287
+ image: postgres:14
288
+ environment:
289
+ POSTGRES_DB: #{app_name}_development
290
+ POSTGRES_USER: postgres
291
+ POSTGRES_PASSWORD: password
292
+ ports:
293
+ - "5432:5432"
294
+ volumes:
295
+ - postgres_data:/var/lib/postgresql/data
296
+
297
+ <% services.each do |name, service| %>
298
+ <%= name %>:
299
+ build: <%= service['root'] %>
300
+ ports:
301
+ - "<%= service['port'] %>:3000"
302
+ depends_on:
303
+ - db
304
+ environment:
305
+ DATABASE_URL: postgres://postgres:password@db:5432/<%= app_name %>_development
306
+ REGOLITH_SERVICE_NAME: <%= name %>
307
+ REGOLITH_SERVICE_PORT: <%= service['port'] %>
308
+ volumes:
309
+ - <%= service['root'] %>:/app
310
+ command: bash -c "rm -f tmp/pids/server.pid && bundle install && rails server -b 0.0.0.0"
311
+ <% end %>
312
+
313
+ volumes:
314
+ postgres_data:
315
+ YAML
316
+
317
+ ERB.new(template).result(binding)
842
318
  end
843
319
 
844
320
  def generate_dockerfile
845
321
  <<~DOCKERFILE
846
- FROM ruby:3.1-slim
847
-
848
- # Install system dependencies
849
- RUN apt-get update -qq && \\
850
- apt-get install -y nodejs postgresql-client libyaml-dev build-essential pkg-config curl && \\
851
- apt-get clean && \\
852
- rm -rf /var/lib/apt/lists/*
322
+ FROM ruby:3.1
853
323
 
854
324
  WORKDIR /app
855
325
 
856
- # Copy Gemfile and install gems
857
- COPY Gemfile Gemfile.lock* ./
326
+ RUN apt-get update -qq && apt-get install -y nodejs postgresql-client libyaml-dev libsqlite3-dev build-essential pkg-config
858
327
 
859
- # Conditional bundler config for dev vs prod
860
- ARG BUILD_ENV=development
861
- RUN if [ "$BUILD_ENV" = "production" ]; then \\
862
- bundle config set --local deployment 'true' && \\
863
- bundle config set --local without 'development test'; \\
864
- fi && bundle install
865
-
866
- # Copy application code
867
328
  COPY . .
868
329
 
330
+ RUN bundle install
331
+
869
332
  EXPOSE 3000
870
333
 
871
334
  CMD ["rails", "server", "-b", "0.0.0.0"]
@@ -875,17 +338,15 @@ module Regolith
875
338
  def generate_regolith_initializer(service_name)
876
339
  <<~RUBY
877
340
  require 'ostruct'
878
-
341
+
879
342
  # Regolith service configuration
880
343
  Rails.application.configure do
881
344
  config.regolith = OpenStruct.new(
882
345
  service_name: '#{service_name}',
883
- service_registry: Rails.root.join('../../config/regolith.yml'),
884
- version: Regolith::VERSION
346
+ service_registry: Rails.root.join('../../config/regolith.yml')
885
347
  )
886
348
  end
887
349
 
888
- # Load service registry if available
889
350
  if File.exist?(Rails.application.config.regolith.service_registry)
890
351
  REGOLITH_SERVICES = YAML.load_file(Rails.application.config.regolith.service_registry)['services'] || {}
891
352
  else
@@ -894,184 +355,22 @@ module Regolith
894
355
  RUBY
895
356
  end
896
357
 
897
- def generate_cors_initializer
898
- <<~RUBY
899
- # CORS configuration for microservices
900
- Rails.application.config.middleware.insert_before 0, Rack::Cors do
901
- allow do
902
- origins '*' # Configure appropriately for production
903
- resource '*',
904
- headers: :any,
905
- methods: %i[get post put patch delete options head],
906
- expose: %w[Authorization Content-Type],
907
- max_age: 600
908
- end
909
- end
910
- RUBY
911
- end
912
-
913
- def generate_health_controller
914
- <<~RUBY
915
- module Regolith
916
- class HealthController < ActionController::API
917
- def show
918
- render json: {
919
- ok: true,
920
- service: Rails.application.config.regolith_service_name,
921
- time: Time.now.to_i,
922
- version: Rails.application.config.regolith.version
923
- }
924
- end
925
- end
926
- end
927
- RUBY
928
- end
929
-
930
- def generate_gitignore
931
- <<~GITIGNORE
932
- # Regolith
933
- /services/*/log/*
934
- /services/*/tmp/*
935
- /services/*/.env*
936
- .DS_Store
937
-
938
- # Docker
939
- docker-compose.override.yml
940
-
941
- # Logs
942
- *.log
943
-
944
- # Runtime data
945
- pids
946
- *.pid
947
- *.seed
948
-
949
- # Environment variables
950
- .env*
951
- !.env.example
952
- GITIGNORE
953
- end
954
-
955
- def generate_readme(app_name)
956
- <<~MARKDOWN
957
- # #{app_name.capitalize}
958
-
959
- A Regolith microservices application built with Rails and Docker.
960
-
961
- ## Getting Started
962
-
963
- ```bash
964
- # Start all services
965
- regolith server
966
-
967
- # Generate a new service
968
- regolith generate service users
969
-
970
- # Open service in browser
971
- regolith open users
972
-
973
- # View service logs
974
- regolith logs users -f
975
-
976
- # Run database migrations
977
- regolith db:migrate --all
978
-
979
- # Health check
980
- regolith health
981
- ```
982
-
983
- ## Services
984
-
985
- #{services_documentation}
986
-
987
- ## Development
988
-
989
- ```bash
990
- # Open Rails console for a service
991
- regolith console users
992
-
993
- # Run Rails commands
994
- regolith rails users db:migrate
995
- regolith rails users routes
996
-
997
- # Run tests
998
- regolith test --all
999
-
1000
- # Execute commands in service
1001
- regolith exec users bash
1002
- ```
1003
-
1004
- ## Architecture
1005
-
1006
- - **Rails 7** API-only applications
1007
- - **PostgreSQL** for persistence
1008
- - **Docker Compose** for orchestration
1009
- - **Service registry** for inter-service communication
1010
-
1011
- Built with [Regolith](https://regolith.bio) - Rails for distributed systems.
1012
- MARKDOWN
1013
- end
1014
-
1015
- def services_documentation
1016
- config = load_regolith_config
1017
- return "No services yet. Run `regolith generate service <name>` to create one." if config['services'].empty?
1018
-
1019
- config['services'].map do |name, service|
1020
- "- **#{name}** - http://localhost:#{service['port']}"
1021
- end.join("\n")
1022
- end
1023
-
1024
358
  def generate_makefile
1025
359
  <<~MAKEFILE
1026
- .PHONY: server up down restart logs console test health doctor
360
+ .PHONY: server console build clean
1027
361
 
1028
- # Start services
1029
- server up:
1030
- \tregolith server
362
+ server:
363
+ regolith server
1031
364
 
1032
- # Stop services
1033
- down:
1034
- \tregolith down
1035
-
1036
- # Restart services
1037
- restart:
1038
- \tregolith restart
1039
-
1040
- # View logs
1041
- logs:
1042
- \tregolith logs -f
1043
-
1044
- # Open console (usage: make console SERVICE=users)
1045
365
  console:
1046
- \t@if [ -z "$(SERVICE)" ]; then echo "Usage: make console SERVICE=service_name"; exit 1; fi
1047
- \tregolith console $(SERVICE)
1048
-
1049
- # Run tests
1050
- test:
1051
- \tregolith test --all
1052
-
1053
- # Health check
1054
- health:
1055
- \tregolith health
366
+ regolith console
1056
367
 
1057
- # System diagnostics
1058
- doctor:
1059
- \tregolith doctor
368
+ build:
369
+ docker-compose build
1060
370
 
1061
- # Database operations
1062
- db-migrate:
1063
- \tregolith db:migrate --all
1064
-
1065
- db-setup:
1066
- \tregolith db:setup --all
1067
-
1068
- # Cleanup
1069
371
  clean:
1070
- \tregolith prune
1071
-
1072
- # Shortcuts
1073
- dev: up
1074
- stop: down
372
+ docker-compose down -v
373
+ docker system prune -f
1075
374
  MAKEFILE
1076
375
  end
1077
376
 
@@ -1084,62 +383,24 @@ module Regolith
1084
383
 
1085
384
  def show_help
1086
385
  puts <<~HELP
1087
- Regolith #{Regolith::VERSION} - Rails for Distributed Systems
386
+ Regolith #{Regolith::VERSION} - Microservices framework
1088
387
 
1089
388
  USAGE:
1090
389
  regolith <command> [options]
1091
390
 
1092
- PROJECT COMMANDS:
391
+ COMMANDS:
1093
392
  new <app_name> Create a new Regolith application
1094
393
  generate service <name> Generate a new microservice
1095
-
1096
- SERVICE MANAGEMENT:
1097
- server, up Start all services with Docker Compose
1098
- down Stop and remove all services
1099
- restart [service] Restart one or all services
1100
- stop [service] Stop one or all services
1101
- ps, status Show running containers
1102
- logs [service] [-f] View service logs
1103
- exec <service> [cmd] Execute command in service container
1104
- shell <service> Open shell in service container
1105
- rebuild [service] Rebuild service images
1106
-
1107
- RAILS INTEGRATION:
1108
- console <service> Open Rails console for service
1109
- rails <service> <cmd> Run Rails command in service
1110
- routes <service> Show routes for service
1111
- open <service> Open service in browser
1112
-
1113
- DATABASE OPERATIONS:
1114
- db:migrate [service|--all] Run migrations
1115
- db:create [service|--all] Create databases
1116
- db:seed [service|--all] Seed databases
1117
- db:reset [service|--all] Reset databases
1118
- db:setup [service|--all] Setup databases
1119
- db:drop [service|--all] Drop databases
1120
-
1121
- TESTING & HEALTH:
1122
- test [service|--all] Run tests
1123
- health Health check all services
1124
-
1125
- UTILITIES:
1126
- config [--json] Show current configuration
1127
- inspect [--json] Show services and resolved endpoints
1128
- prune Clean up Docker system
1129
- doctor Run system diagnostics
1130
- version Show version
394
+ server Start all services with Docker Compose
395
+ console <service_name> Open Rails console for a service
396
+ version Show version information
1131
397
 
1132
398
  EXAMPLES:
1133
- regolith new marketplace
1134
- regolith generate service products
399
+ regolith new observatory
400
+ regolith generate service telescope
401
+ regolith generate service records
1135
402
  regolith server
1136
- regolith rails products db:migrate
1137
- regolith routes products
1138
- regolith open products
1139
- regolith shell products
1140
- regolith inspect --json
1141
- regolith config --json | jq '.services'
1142
- regolith test --all
403
+ regolith console telescope
1143
404
 
1144
405
  Get started:
1145
406
  regolith new myapp
@@ -1149,4 +410,4 @@ module Regolith
1149
410
  HELP
1150
411
  end
1151
412
  end
1152
- end
413
+ end
@@ -1,4 +1,4 @@
1
1
  # lib/regolith/version.rb
2
2
  module Regolith
3
- VERSION = "0.1.19"
3
+ VERSION = "0.1.21"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: regolith
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.19
4
+ version: 0.1.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Regolith Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-09 00:00:00.000000000 Z
11
+ date: 2025-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport