regolith 0.1.15 → 0.1.19

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.
data/lib/regolith/cli.rb CHANGED
@@ -1,13 +1,62 @@
1
1
  require 'fileutils'
2
2
  require 'yaml'
3
+ require 'psych'
3
4
  require 'erb'
4
5
  require 'timeout'
5
6
  require 'rubygems'
7
+ require 'net/http'
8
+ require 'uri'
9
+ require 'json'
10
+ require 'ostruct'
11
+ require 'set'
6
12
  require 'rbconfig'
7
13
  require 'shellwords'
8
- require 'psych'
9
14
 
10
15
  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
+
11
60
  class CLI
12
61
  def initialize(args)
13
62
  @args = args
@@ -23,9 +72,9 @@ module Regolith
23
72
  when 'generate'
24
73
  generate_resource(@subcommand, @name)
25
74
  when 'server', 'up'
26
- start_server
75
+ start_server(parse_flags(@args[1..-1]))
27
76
  when 'down'
28
- stop_all_services
77
+ stop_services
29
78
  when 'restart'
30
79
  restart_service(@subcommand)
31
80
  when 'stop'
@@ -33,36 +82,36 @@ module Regolith
33
82
  when 'ps', 'status'
34
83
  show_status
35
84
  when 'logs'
36
- show_logs(@subcommand, @args[2] == '-f' || @args[2] == '--follow')
85
+ show_logs(@subcommand, parse_flags(@args[2..-1]))
37
86
  when 'exec'
38
- exec_in_service(@subcommand, @args[2..-1])
87
+ exec_command(@subcommand, @args[2..-1])
39
88
  when 'shell'
40
89
  shell_service(@subcommand)
41
- when 'rails'
42
- rails_passthrough(@subcommand, @args[2..-1])
43
90
  when 'console'
44
91
  open_console(@subcommand)
92
+ when 'rails'
93
+ rails_passthrough(@subcommand, @args[2..-1])
45
94
  when 'routes'
46
95
  show_routes(@subcommand)
47
96
  when 'open'
48
97
  open_service(@subcommand)
49
- when 'db:create', 'db:migrate', 'db:seed', 'db:reset', 'db:setup', 'db:drop'
50
- db_command(@command, @subcommand)
98
+ when 'db'
99
+ db_command(@subcommand, @name, parse_flags(@args[3..-1]))
51
100
  when 'test'
52
- run_tests(@subcommand)
101
+ run_tests(@subcommand, parse_flags(@args[2..-1]))
53
102
  when 'health'
54
- check_health
103
+ health_check
55
104
  when 'config'
56
- show_config(@subcommand == '--json')
57
- when 'inspect'
58
- inspect_config(@subcommand == '--json')
59
- when 'doctor'
60
- run_diagnostics
105
+ show_config(parse_flags(@args[1..-1]))
61
106
  when 'prune'
62
- prune_docker
107
+ prune_system
63
108
  when 'rebuild'
64
109
  rebuild_service(@subcommand)
65
- when 'version'
110
+ when 'doctor'
111
+ run_doctor
112
+ when 'inspect'
113
+ inspect_services(parse_flags(@args[1..-1]))
114
+ when 'version', '--version', '-v'
66
115
  puts "Regolith #{Regolith::VERSION}"
67
116
  else
68
117
  show_help
@@ -71,6 +120,23 @@ module Regolith
71
120
 
72
121
  private
73
122
 
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
+
74
140
  def create_new_app(app_name)
75
141
  unless app_name
76
142
  puts "❌ Error: App name required"
@@ -108,6 +174,8 @@ module Regolith
108
174
  File.write('docker-compose.yml', generate_docker_compose(app_name))
109
175
  File.write('Makefile', generate_makefile)
110
176
  File.write('.bin/regolith', generate_regolith_shim)
177
+ File.write('.gitignore', generate_gitignore)
178
+ File.write('README.md', generate_readme(app_name))
111
179
  FileUtils.chmod(0755, '.bin/regolith')
112
180
  end
113
181
 
@@ -118,16 +186,17 @@ module Regolith
118
186
  exit 1
119
187
  end
120
188
 
121
- # Validate service name
122
- unless resource_name =~ /\A[a-z][a-z0-9_]*\z/
123
- puts "❌ Invalid service name. Use lowercase letters, digits, and underscores."
124
- exit 1
125
- end
126
-
127
189
  generate_service(resource_name)
128
190
  end
129
191
 
130
192
  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
+
131
200
  puts "🔧 Creating service '#{service_name}'..."
132
201
  config = load_regolith_config
133
202
  port = next_available_port
@@ -161,263 +230,213 @@ module Regolith
161
230
  exit 1
162
231
  end
163
232
 
164
- # Generate custom Gemfile (clean up Rails-generated files first)
165
- puts "🔧 Creating custom Gemfile..."
166
-
167
- # Remove Rails-generated Gemfile and lock file to avoid conflicts
168
- FileUtils.rm_f("#{service_dir}/Gemfile")
169
- FileUtils.rm_f("#{service_dir}/Gemfile.lock")
170
-
171
- generate_gemfile(service_dir)
172
-
173
- puts " → Running bundle install..."
174
- Dir.chdir(service_dir) do
175
- unless system("bundle install")
176
- puts "❌ bundle install failed"
177
- puts "→ You may be missing system libraries like libyaml-dev libsqlite3-dev build-essential pkg-config"
178
- puts "→ Try: sudo apt install -y libyaml-dev libsqlite3-dev build-essential pkg-config"
179
- exit 1
180
- end
181
- end
182
-
183
- patch_rails_app(service_dir, service_name, port)
233
+ customize_service(service_dir, service_name, port)
184
234
 
185
235
  config['services'][service_name] = {
186
236
  'port' => port,
187
237
  'root' => "./#{service_dir}"
188
238
  }
239
+
189
240
  save_regolith_config(config)
190
241
  update_docker_compose(config)
191
242
 
192
243
  puts "✅ Created service '#{service_name}'"
193
244
  puts "🚀 Service will run on port #{port}"
194
- puts "→ Next: regolith server"
245
+ puts "→ Next: regolith generate service <another_service> or regolith server"
195
246
  end
196
247
 
197
- def next_available_port(start = 3001)
198
- config = load_regolith_config
199
- used_ports = config['services']&.values&.map { |s| s['port'] }.to_set || Set.new
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
200
250
  port = start
201
- port += 1 while used_ports.include?(port)
251
+ port += 1 while used.include?(port)
202
252
  port
203
253
  end
204
254
 
205
- def generate_gemfile(service_dir)
206
- ruby_version = RUBY_VERSION.split('.')[0..1].join('.')
207
-
208
- custom_gemfile = <<~GEMFILE
255
+ def customize_service(service_dir, service_name, port)
256
+ # Detect Ruby MAJOR.MINOR
257
+ major_minor = RUBY_VERSION.split(".")[0..1].join(".")
258
+
259
+ # Write Gemfile (no vendoring; pull from RubyGems)
260
+ custom_gemfile = generate_gemfile(major_minor)
261
+ File.write("#{service_dir}/Gemfile", custom_gemfile)
262
+
263
+ puts " → Running bundle install..."
264
+ Dir.chdir(service_dir) do
265
+ unless system("bundle install")
266
+ puts "❌ bundle install failed"
267
+ puts "→ You may be missing system libraries like libyaml-dev build-essential"
268
+ exit 1
269
+ end
270
+ end
271
+
272
+ patch_rails_app(service_dir, service_name, port)
273
+ end
274
+
275
+ def generate_gemfile(ruby_version)
276
+ <<~GEMFILE
209
277
  source "https://rubygems.org"
210
-
278
+
211
279
  ruby "~> #{ruby_version}.0"
212
-
280
+
213
281
  gem "rails", "~> 7.2.2.1"
214
282
  gem "pg", "~> 1.5"
215
283
  gem "puma", ">= 5.0"
216
284
  gem "rack-cors"
217
285
  gem "bootsnap", require: false
218
-
286
+
219
287
  group :development, :test do
220
288
  gem "debug", platforms: %i[mri mswin mswin64 mingw x64_mingw], require: "debug/prelude"
221
289
  gem "brakeman", require: false
222
290
  gem "rubocop-rails-omakase", require: false
223
291
  end
224
-
225
- gem "regolith", "~> 0.1.13"
226
- GEMFILE
227
-
228
- File.write("#{service_dir}/Gemfile", custom_gemfile)
229
- end
230
-
231
-
232
- def vendor_regolith_gem(service_dir)
233
- vendor_dir = File.join(service_dir, "vendor")
234
- FileUtils.mkdir_p(vendor_dir)
235
-
236
- begin
237
- # Try to find the regolith gem path
238
- regolith_gem_path = if Gem.loaded_specs['regolith']
239
- Gem.loaded_specs['regolith'].full_gem_path
240
- else
241
- # Fallback: search gem paths
242
- Gem.path.each do |gem_path|
243
- regolith_path = File.join(gem_path, 'gems', "regolith-#{Regolith::VERSION}")
244
- return regolith_path if File.exist?(regolith_path)
245
- end
246
- nil
247
- end
248
292
 
249
- if regolith_gem_path && File.exist?(regolith_gem_path)
250
- regolith_dest = File.join(vendor_dir, "regolith")
251
- FileUtils.cp_r(regolith_gem_path, regolith_dest)
252
- puts "📦 Vendored Regolith gem from #{regolith_gem_path}"
253
- else
254
- puts "⚠️ Could not find regolith gem path, using system gem instead"
255
- # Update Gemfile to use system gem
256
- gemfile_path = File.join(service_dir, "Gemfile")
257
- gemfile_content = File.read(gemfile_path)
258
- gemfile_content.gsub!('gem "regolith", path: "vendor/regolith"', 'gem "regolith", "~> 0.1.7"')
259
- File.write(gemfile_path, gemfile_content)
260
- end
261
- rescue => e
262
- puts "⚠️ Error vendoring regolith gem: #{e.message}"
263
- puts " Using system gem instead"
264
- # Update Gemfile to use system gem
265
- gemfile_path = File.join(service_dir, "Gemfile")
266
- gemfile_content = File.read(gemfile_path)
267
- gemfile_content.gsub!('gem "regolith", path: "vendor/regolith"', 'gem "regolith", "~> 0.1.7"')
268
- File.write(gemfile_path, gemfile_content)
269
- end
293
+ gem "regolith"
294
+ GEMFILE
270
295
  end
271
296
 
272
297
  def patch_rails_app(service_dir, service_name, port)
273
- # Add health controller
274
- add_health_controller(service_dir)
275
-
276
- # Add health route
298
+ create_initializers(service_dir, service_name)
299
+ create_health_controller(service_dir)
277
300
  add_health_route(service_dir)
278
-
279
- # Create initializer
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)
280
306
  initializer_dir = "#{service_dir}/config/initializers"
281
307
  FileUtils.mkdir_p(initializer_dir)
308
+
282
309
  File.write("#{initializer_dir}/regolith.rb", generate_regolith_initializer(service_name))
283
-
284
- # Create Dockerfile
285
- File.write("#{service_dir}/Dockerfile", generate_dockerfile)
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)
286
316
 
287
- # Patch application.rb
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
332
+
333
+ def patch_application_rb(service_dir, service_name, port)
288
334
  app_rb_path = "#{service_dir}/config/application.rb"
289
335
  app_rb_content = File.read(app_rb_path)
290
336
 
291
337
  cors_config = <<~RUBY
292
338
 
293
339
  # Regolith configuration
294
- config.middleware.insert_before 0, Rack::Cors do
295
- allow do
296
- origins '*'
297
- resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head]
298
- end
299
- end
300
-
301
340
  config.regolith_service_name = '#{service_name}'
302
341
  config.regolith_service_port = #{port}
303
342
  RUBY
304
343
 
305
- app_rb_content.gsub!(/class Application < Rails::Application.*?
306
- end/m) do |match|
307
- match.gsub(/(\n end)$/, "#{cors_config}\1")
308
- end
309
-
344
+ app_rb_content.gsub!(/(\n end\n\z)/, "#{cors_config}\1")
310
345
  File.write(app_rb_path, app_rb_content)
311
346
  end
312
347
 
313
- def add_health_controller(service_dir)
314
- controller_dir = File.join(service_dir, "app", "controllers", "regolith")
315
- FileUtils.mkdir_p(controller_dir)
316
-
317
- health_controller = <<~RUBY
318
- module Regolith
319
- class HealthController < ActionController::API
320
- def show
321
- render json: {
322
- ok: true,
323
- service: Rails.application.config.regolith_service_name,
324
- version: Regolith::VERSION,
325
- time: Time.now.iso8601
326
- }
327
- end
328
- end
329
- end
330
- RUBY
331
-
332
- File.write(File.join(controller_dir, "health_controller.rb"), health_controller)
333
- end
334
-
335
- def add_health_route(service_dir)
336
- routes_path = File.join(service_dir, "config", "routes.rb")
337
- content = File.read(routes_path)
338
-
339
- if content.sub!(/end\s*\z/, " get '/health', to: 'regolith/health#show'\nend\n")
340
- File.write(routes_path, content)
341
- else
342
- # Fallback: append to file
343
- File.open(routes_path, "a") { |f| f.puts "\nget '/health', to: 'regolith/health#show'\n" }
344
- end
345
- end
346
-
347
- def start_server
348
- unless find_regolith_config
348
+ # Service management commands
349
+ def start_server(_flags = {})
350
+ unless File.exist?('docker-compose.yml')
349
351
  puts "❌ Error: Not in a Regolith app directory"
350
352
  exit 1
351
353
  end
352
354
 
353
355
  puts "🚀 Starting Regolith services..."
354
- config = load_regolith_config
355
-
356
- config['services'].each do |name, service|
357
- puts "🚀 #{name}_service will run at http://localhost:#{service['port']}"
358
- end
359
356
 
360
- puts "🧭 Service registry loaded from config/regolith.yml"
361
- puts ""
357
+ config = load_regolith_config
358
+ show_service_info(config)
362
359
 
363
- exec_compose("up", "--build")
360
+ exec_compose('up', '--build')
364
361
  end
365
362
 
366
- def stop_all_services
367
- exec_compose("down")
363
+ def stop_services
364
+ puts "🛑 Stopping all services..."
365
+ exec_compose('down', '-v')
368
366
  end
369
367
 
370
- def restart_service(service_name)
368
+ def restart_service(service_name = nil)
371
369
  if service_name
372
- exec_compose("restart", service_name)
370
+ ensure_service_exists!(service_name)
371
+ puts "🔄 Restarting service '#{service_name}'..."
372
+ exec_compose('restart', service_name)
373
373
  else
374
- exec_compose("restart")
374
+ puts "🔄 Restarting all services..."
375
+ exec_compose('restart')
375
376
  end
376
377
  end
377
378
 
378
- def stop_service(service_name)
379
- unless service_name
380
- puts "❌ Error: Service name required"
381
- puts "Usage: regolith stop <service_name>"
382
- exit 1
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')
383
387
  end
384
- exec_compose("stop", service_name)
385
388
  end
386
389
 
387
390
  def show_status
388
- config = load_regolith_config
389
-
390
- # Try different format options for docker compose ps
391
+ puts "📊 Service Status:"
392
+ puts
393
+
394
+ # Try different format options for maximum compatibility
391
395
  success = system_compose('ps', '--format', 'table') ||
392
396
  system_compose('ps', '--format', 'json') ||
393
397
  system_compose('ps')
394
-
395
- if success && config['services'].any?
396
- puts ""
397
- puts "📋 Summary: #{config['services'].size} service#{'s' if config['services'].size != 1} configured"
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
398
403
  ports = config['services'].values.map { |s| s['port'] }.sort
399
- puts "🚪 Ports: #{ports.join(', ')}"
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]
415
+ end
416
+
417
+ if healthy_services < service_count
418
+ puts "⚠️ #{service_count - healthy_services} services unhealthy"
419
+ exit 1
420
+ end
400
421
  end
401
-
402
- exit($?.exitstatus) unless success
403
422
  end
404
423
 
405
- def show_logs(service_name, follow = false)
424
+ def show_logs(service_name = nil, flags = {})
406
425
  args = ['logs']
426
+ args << '--follow' if flags[:follow] || flags[:f]
407
427
  args << service_name if service_name
408
- args << '-f' if follow
428
+
409
429
  exec_compose(*args)
410
430
  end
411
431
 
412
- def exec_in_service(service_name, command)
413
- unless service_name
414
- puts "❌ Error: Service name required"
415
- puts "Usage: regolith exec <service_name> <command>"
416
- exit 1
432
+ def exec_command(service_name, command_args)
433
+ ensure_service_exists!(service_name)
434
+
435
+ if command_args.empty?
436
+ command_args = ['bash']
417
437
  end
418
-
419
- command = ['bash'] if command.empty?
420
- exec_compose('exec', service_name, *command)
438
+
439
+ exec_compose('exec', service_name, *command_args)
421
440
  end
422
441
 
423
442
  def shell_service(service_name)
@@ -426,23 +445,22 @@ module Regolith
426
445
  puts "Usage: regolith shell <service_name>"
427
446
  exit 1
428
447
  end
448
+
449
+ ensure_service_exists!(service_name)
450
+ puts "🐚 Opening shell for #{service_name}_service..."
429
451
  exec_compose('exec', service_name, 'bash')
430
452
  end
431
453
 
454
+ # Rails integration commands
432
455
  def rails_passthrough(service_name, rails_args)
433
- unless service_name
434
- puts "❌ Error: Service name required"
435
- puts "Usage: regolith rails <service_name> <rails_command>"
436
- exit 1
437
- end
456
+ ensure_service_exists!(service_name)
438
457
 
439
- config = load_regolith_config
440
- unless config['services'][service_name]
441
- puts "❌ Error: Service '#{service_name}' not found"
458
+ if rails_args.empty?
459
+ puts "❌ Error: Rails command required"
460
+ puts "Usage: regolith rails <service> <command>"
442
461
  exit 1
443
462
  end
444
463
 
445
- rails_args = ['--help'] if rails_args.empty?
446
464
  exec_compose('exec', service_name, 'bash', '-lc',
447
465
  "bundle exec rails #{Shellwords.join(rails_args)}")
448
466
  end
@@ -454,25 +472,11 @@ module Regolith
454
472
  exit 1
455
473
  end
456
474
 
457
- config = load_regolith_config
458
- unless config['services'][service_name]
459
- puts "❌ Error: Service '#{service_name}' not found"
460
- exit 1
461
- end
462
-
475
+ ensure_service_exists!(service_name)
463
476
  puts "🧪 Opening Rails console for #{service_name}_service..."
464
477
  exec_compose('exec', service_name, 'rails', 'console')
465
478
  end
466
479
 
467
- def show_routes(service_name)
468
- unless service_name
469
- puts "❌ Error: Service name required"
470
- puts "Usage: regolith routes <service_name>"
471
- exit 1
472
- end
473
- rails_passthrough(service_name, ['routes'])
474
- end
475
-
476
480
  def open_service(service_name)
477
481
  unless service_name
478
482
  puts "❌ Error: Service name required"
@@ -480,232 +484,296 @@ module Regolith
480
484
  exit 1
481
485
  end
482
486
 
487
+ ensure_service_exists!(service_name)
483
488
  config = load_regolith_config
484
- service = config['services'][service_name]
485
- unless service
486
- puts "❌ Error: Service '#{service_name}' not found"
487
- exit 1
488
- end
489
+ port = config['services'][service_name]['port']
490
+ url = "http://localhost:#{port}"
489
491
 
490
- url = "http://localhost:#{service['port']}"
491
492
  puts "🌐 Opening #{url}..."
492
493
 
494
+ # Cross-platform open command
493
495
  case RbConfig::CONFIG['host_os']
494
496
  when /mswin|mingw|cygwin/
495
497
  system(%{start "" "#{url}"})
496
498
  when /darwin/
497
- system("open", url)
498
- when /linux|bsd/
499
- system("xdg-open", url)
499
+ system("open #{url}")
500
500
  else
501
- puts "Please open #{url} in your browser"
501
+ system("xdg-open #{url}") || puts("Visit: #{url}")
502
502
  end
503
503
  end
504
504
 
505
- def db_command(command, service_or_flag)
506
- db_action = command.split(':').last
507
-
508
- if service_or_flag == '--all'
509
- config = load_regolith_config
510
- config['services'].each do |service_name, _|
511
- puts "🗄️ Running db:#{db_action} for #{service_name}..."
512
- run_single_db_command(service_name, db_action)
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
513
517
  end
514
- elsif service_or_flag
515
- run_single_db_command(service_or_flag, db_action)
516
518
  else
517
- puts "❌ Error: Service name or --all flag required"
518
- puts "Usage: regolith db:#{db_action} <service_name|--all>"
519
+ puts "❌ Unknown db action: #{action}"
520
+ puts "Available: create, migrate, seed, reset, setup, drop"
519
521
  exit 1
520
522
  end
521
523
  end
522
524
 
523
- def run_single_db_command(service_name, action)
524
- config = load_regolith_config
525
- unless config['services'][service_name]
526
- puts " Error: Service '#{service_name}' not found"
527
- return false
528
- end
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')
529
530
 
530
- success = system_compose('exec', service_name, 'rails', "db:#{action}")
531
- unless success
532
- puts "❌ db:#{action} failed for #{service_name} (exit code: #{$?.exitstatus})"
533
- return false
531
+ unless success
532
+ puts "❌ Tests failed for #{service} (exit code: #{$?.exitstatus})"
533
+ exit 1 unless flags[:continue_on_failure]
534
+ end
534
535
  end
535
- true
536
+
537
+ puts "✅ All tests passed!" if flags[:all]
536
538
  end
537
539
 
538
- def run_tests(service_or_flag)
539
- if service_or_flag == '--all'
540
- config = load_regolith_config
541
- all_passed = true
542
- config['services'].each do |service_name, _|
543
- puts "🧪 Running tests for #{service_name}..."
544
- unless run_single_test(service_name)
545
- all_passed = false
546
- end
547
- end
548
- exit(1) unless all_passed
549
- elsif service_or_flag
550
- exit(1) unless run_single_test(service_or_flag)
551
- else
552
- puts "❌ Error: Service name or --all flag required"
553
- puts "Usage: regolith test <service_name|--all>"
540
+ def show_routes(service_name)
541
+ unless service_name
542
+ puts "❌ Error: Service name required"
543
+ puts "Usage: regolith routes <service_name>"
554
544
  exit 1
555
545
  end
546
+
547
+ ensure_service_exists!(service_name)
548
+ puts "🛤 Routes for #{service_name}:"
549
+ system_compose('exec', service_name, 'bash', '-lc', 'bundle exec rails routes')
556
550
  end
557
551
 
558
- def run_single_test(service_name)
552
+ # Health and monitoring
553
+ def health_check
554
+ puts "🔍 Health Check Results:"
555
+ puts
556
+
559
557
  config = load_regolith_config
560
- unless config['services'][service_name]
561
- puts "❌ Error: Service '#{service_name}' not found"
562
- return false
563
- end
564
558
 
565
- success = system_compose('exec', service_name, 'rails', 'test')
566
- unless success
567
- puts "❌ Tests failed for #{service_name} (exit code: #{$?.exitstatus})"
568
- return false
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
569
573
  end
570
- true
571
574
  end
572
575
 
573
- def check_health
574
- config = load_regolith_config
575
- puts "🔍 Health Check Results:"
576
-
577
- all_healthy = true
578
- config['services'].each do |service_name, service|
579
- health_data = check_service_health(service_name, service['port'])
580
- if health_data
581
- puts "✅ #{service_name} (port #{service['port']}) - healthy"
582
- puts " Version: #{health_data['version']}" if health_data['version']
583
- puts " Last seen: #{Time.parse(health_data['time']).strftime('%H:%M:%S')}" if health_data['time']
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
+ }
584
589
  else
585
- puts " #{service_name} (port #{service['port']}) - unreachable"
586
- all_healthy = false
590
+ { healthy: false, message: "HTTP #{response.code}" }
587
591
  end
592
+ rescue
593
+ { healthy: false, message: 'unreachable' }
588
594
  end
589
-
590
- exit(1) unless all_healthy
591
- end
592
-
593
- def check_service_health(service_name, port)
594
- require_relative 'service_client'
595
- response = Regolith::ServiceClient.health(service_name)
596
- Regolith::ServiceClient.parsed_body(response) if response.code.to_i == 200
597
- rescue => e
598
- nil
599
595
  end
600
596
 
601
- def show_config(json_format = false)
597
+ # Configuration
598
+ def show_config(flags = {})
602
599
  config = load_regolith_config
603
- if json_format
600
+
601
+ if flags[:json]
604
602
  puts JSON.pretty_generate(config)
605
603
  else
604
+ puts "📋 Current Configuration:"
605
+ puts
606
606
  puts YAML.dump(config)
607
607
  end
608
608
  end
609
609
 
610
- def inspect_config(json_format = false)
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
+
611
671
  config = load_regolith_config
612
-
613
- if json_format
614
- inspection = {
615
- endpoints: {},
616
- configuration: 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
617
690
  }
618
- config['services'].each do |name, service|
619
- inspection[:endpoints][name] = "http://localhost:#{service['port']}"
620
- end
621
- puts JSON.pretty_generate(inspection)
691
+ puts JSON.pretty_generate(inspection_data)
622
692
  else
623
- puts "📊 Service Endpoints:"
624
- config['services'].each do |name, service|
625
- puts " #{name}: http://localhost:#{service['port']}"
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}"
626
698
  end
627
- puts ""
628
- puts "📋 Full Configuration:"
699
+
700
+ puts "\n📋 Full Configuration:"
629
701
  puts YAML.dump(config)
630
702
  end
631
703
  end
632
704
 
633
- def run_diagnostics
634
- puts "🔍 Regolith System Diagnostics"
635
- puts ""
636
-
637
- # Check Docker
638
- if system("docker --version > /dev/null 2>&1")
639
- puts "✅ Docker is available"
640
- else
641
- puts "❌ Docker is not available"
642
- end
643
-
644
- # Check Docker Compose
645
- if system("docker compose version > /dev/null 2>&1") ||
646
- system("docker-compose --version > /dev/null 2>&1")
647
- puts "✅ Docker Compose is available"
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]
648
716
  else
649
- puts "❌ Docker Compose is not available"
717
+ puts "❌ Docker Compose not found"
718
+ exit 1
650
719
  end
651
-
652
- # Check Rails
653
- if system("rails --version > /dev/null 2>&1")
654
- puts "✅ Rails is available"
655
- else
656
- puts "❌ Rails is not available"
657
- end
658
-
659
- # Check Ruby version
660
- puts "✅ Ruby #{RUBY_VERSION}"
661
-
662
- # Check Regolith config
663
- if find_regolith_config
664
- puts "✅ Regolith configuration found"
665
- config = load_regolith_config
666
- puts " #{config['services'].size} service(s) configured"
667
- else
668
- puts "⚠️ No Regolith configuration found (not in a Regolith app directory)"
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
+
739
+ services.each do |service|
740
+ ensure_service_exists!(service)
741
+ yield(service)
669
742
  end
670
743
  end
671
744
 
672
- def prune_docker
673
- puts "🧹 Cleaning up Docker resources..."
674
- system("docker system prune -f")
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
675
752
  end
676
753
 
677
- def rebuild_service(service_name)
678
- if service_name
679
- puts "🔨 Rebuilding #{service_name}..."
680
- exec_compose("build", "--no-cache", service_name)
681
- else
682
- puts "🔨 Rebuilding all services..."
683
- exec_compose("build", "--no-cache")
754
+ def show_service_info(config)
755
+ config['services'].each do |name, service|
756
+ puts "🚀 #{name}_service: http://localhost:#{service['port']}"
684
757
  end
758
+
759
+ puts "🧭 Service registry: config/regolith.yml"
760
+ puts
685
761
  end
686
762
 
687
763
  def load_regolith_config
688
- config_path = find_regolith_config
689
- return { 'services' => {} } unless config_path
690
-
764
+ config_path = Regolith.send(:find_regolith_config)
765
+ return { 'services' => {} } unless config_path && File.exist?(config_path)
766
+
767
+ # Use safe YAML loading
691
768
  config = Psych.safe_load(File.read(config_path), permitted_classes: [], aliases: false) || {}
692
769
  config['services'] ||= {}
693
770
  config
694
771
  end
695
772
 
696
- def find_regolith_config
697
- current_dir = Dir.pwd
698
- while current_dir != '/'
699
- config_path = File.join(current_dir, 'config', 'regolith.yml')
700
- return config_path if File.exist?(config_path)
701
- current_dir = File.dirname(current_dir)
702
- end
703
- nil
704
- end
705
-
706
773
  def save_regolith_config(config)
707
774
  FileUtils.mkdir_p('config')
708
775
  File.write('config/regolith.yml', YAML.dump(config))
776
+ Regolith.reload_service_registry!
709
777
  end
710
778
 
711
779
  def update_docker_compose(config)
@@ -713,82 +781,89 @@ module Regolith
713
781
  File.write('docker-compose.yml', docker_compose)
714
782
  end
715
783
 
784
+ # File generators
716
785
  def generate_docker_compose(app_name, services = {})
717
- template = <<~YAML
718
- version: '3.8'
719
-
720
- networks:
721
- regolith:
722
-
723
- services:
724
- db:
725
- image: postgres:14
726
- environment:
727
- POSTGRES_DB: #{app_name}_development
728
- POSTGRES_USER: postgres
729
- POSTGRES_PASSWORD: password
730
- ports:
731
- - "5432:5432"
732
- volumes:
733
- - postgres_data:/var/lib/postgresql/data
734
- networks:
735
- - regolith
736
- healthcheck:
737
- test: ["CMD-SHELL", "pg_isready -U postgres"]
738
- interval: 30s
739
- timeout: 10s
740
- retries: 3
741
-
742
- <% services.each do |name, service| %>
743
- <%= name %>:
744
- build:
745
- context: <%= service['root'] %>
746
- args:
747
- BUILD_ENV: development
748
- ports:
749
- - "<%= service['port'] %>:3000"
750
- depends_on:
751
- db:
752
- condition: service_healthy
753
- environment:
754
- DATABASE_URL: postgres://postgres:password@db:5432/<%= app_name %>_development
755
- REGOLITH_SERVICE_NAME: <%= name %>
756
- REGOLITH_SERVICE_PORT: <%= service['port'] %>
757
- volumes:
758
- - <%= service['root'] %>:/app
759
- networks:
760
- - regolith
761
- command: bash -c "rm -f tmp/pids/server.pid && bundle install && rails db:prepare && rails server -b 0.0.0.0"
762
- healthcheck:
763
- test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
764
- interval: 30s
765
- timeout: 10s
766
- retries: 3
767
- <% end %>
768
-
769
- volumes:
770
- postgres_data:
771
- YAML
772
-
773
- ERB.new(template).result(binding)
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
774
842
  end
775
843
 
776
844
  def generate_dockerfile
777
845
  <<~DOCKERFILE
778
- FROM ruby:3.1
846
+ FROM ruby:3.1-slim
779
847
 
780
- WORKDIR /app
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/*
781
853
 
782
- RUN apt-get update -qq && apt-get install -y nodejs postgresql-client libyaml-dev libsqlite3-dev build-essential pkg-config curl
854
+ WORKDIR /app
783
855
 
856
+ # Copy Gemfile and install gems
784
857
  COPY Gemfile Gemfile.lock* ./
785
858
 
859
+ # Conditional bundler config for dev vs prod
786
860
  ARG BUILD_ENV=development
787
861
  RUN if [ "$BUILD_ENV" = "production" ]; then \\
788
862
  bundle config set --local deployment 'true' && \\
789
863
  bundle config set --local without 'development test'; \\
790
864
  fi && bundle install
791
865
 
866
+ # Copy application code
792
867
  COPY . .
793
868
 
794
869
  EXPOSE 3000
@@ -810,6 +885,7 @@ module Regolith
810
885
  )
811
886
  end
812
887
 
888
+ # Load service registry if available
813
889
  if File.exist?(Rails.application.config.regolith.service_registry)
814
890
  REGOLITH_SERVICES = YAML.load_file(Rails.application.config.regolith.service_registry)['services'] || {}
815
891
  else
@@ -818,22 +894,184 @@ module Regolith
818
894
  RUBY
819
895
  end
820
896
 
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
+
821
1024
  def generate_makefile
822
1025
  <<~MAKEFILE
823
- .PHONY: server console build clean
1026
+ .PHONY: server up down restart logs console test health doctor
824
1027
 
825
- server:
1028
+ # Start services
1029
+ server up:
826
1030
  \tregolith server
827
1031
 
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)
828
1045
  console:
829
- \tregolith 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
830
1056
 
831
- build:
832
- \tdocker-compose build
1057
+ # System diagnostics
1058
+ doctor:
1059
+ \tregolith doctor
833
1060
 
1061
+ # Database operations
1062
+ db-migrate:
1063
+ \tregolith db:migrate --all
1064
+
1065
+ db-setup:
1066
+ \tregolith db:setup --all
1067
+
1068
+ # Cleanup
834
1069
  clean:
835
- \tdocker-compose down -v
836
- \tdocker system prune -f
1070
+ \tregolith prune
1071
+
1072
+ # Shortcuts
1073
+ dev: up
1074
+ stop: down
837
1075
  MAKEFILE
838
1076
  end
839
1077
 
@@ -844,82 +1082,64 @@ module Regolith
844
1082
  RUBY
845
1083
  end
846
1084
 
847
- def exec_compose(*args)
848
- if system("docker compose version > /dev/null 2>&1")
849
- exec("docker", "compose", *args)
850
- elsif system("docker-compose --version > /dev/null 2>&1")
851
- exec("docker-compose", *args)
852
- else
853
- puts "❌ Error: Neither 'docker compose' nor 'docker-compose' is available"
854
- exit 1
855
- end
856
- end
857
-
858
- def system_compose(*args)
859
- if system("docker compose version > /dev/null 2>&1")
860
- system("docker", "compose", *args)
861
- elsif system("docker-compose --version > /dev/null 2>&1")
862
- system("docker-compose", *args)
863
- else
864
- puts "❌ Error: Neither 'docker compose' nor 'docker-compose' is available"
865
- false
866
- end
867
- end
868
-
869
1085
  def show_help
870
1086
  puts <<~HELP
871
- Regolith #{Regolith::VERSION} - Rails Microservices Framework
1087
+ Regolith #{Regolith::VERSION} - Rails for Distributed Systems
872
1088
 
873
1089
  USAGE:
874
1090
  regolith <command> [options]
875
1091
 
876
- PROJECT MANAGEMENT:
1092
+ PROJECT COMMANDS:
877
1093
  new <app_name> Create a new Regolith application
878
1094
  generate service <name> Generate a new microservice
879
1095
 
880
1096
  SERVICE MANAGEMENT:
881
1097
  server, up Start all services with Docker Compose
882
- down Stop all services
883
- restart [service] Restart all services or specific service
884
- stop <service> Stop a specific service
885
- ps, status Show service status
886
- logs [service] [-f] Show logs (optionally follow)
887
- exec <service> <command> Execute command in service container
888
- shell <service> Open bash shell in service
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
889
1106
 
890
1107
  RAILS INTEGRATION:
891
- rails <service> <command> Run Rails command in service
892
1108
  console <service> Open Rails console for service
1109
+ rails <service> <cmd> Run Rails command in service
893
1110
  routes <service> Show routes for service
894
- open <service> Open service URL in browser
1111
+ open <service> Open service in browser
895
1112
 
896
1113
  DATABASE OPERATIONS:
897
- db:create [service|--all] Create databases
898
- db:migrate [service|--all] Run migrations
899
- db:seed [service|--all] Seed databases
900
- db:reset [service|--all] Reset databases
901
- db:setup [service|--all] Setup databases
902
- db:drop [service|--all] Drop databases
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
903
1120
 
904
1121
  TESTING & HEALTH:
905
1122
  test [service|--all] Run tests
906
- health Check service health
1123
+ health Health check all services
907
1124
 
908
1125
  UTILITIES:
909
- config [--json] Show configuration
910
- inspect [--json] Show endpoints and config
1126
+ config [--json] Show current configuration
1127
+ inspect [--json] Show services and resolved endpoints
1128
+ prune Clean up Docker system
911
1129
  doctor Run system diagnostics
912
- prune Clean up Docker resources
913
- rebuild [service] Rebuild service images
914
- version Show version information
1130
+ version Show version
915
1131
 
916
1132
  EXAMPLES:
917
1133
  regolith new marketplace
918
1134
  regolith generate service products
919
1135
  regolith server
920
1136
  regolith rails products db:migrate
1137
+ regolith routes products
921
1138
  regolith open products
922
- regolith health
1139
+ regolith shell products
1140
+ regolith inspect --json
1141
+ regolith config --json | jq '.services'
1142
+ regolith test --all
923
1143
 
924
1144
  Get started:
925
1145
  regolith new myapp