regolith 0.1.14 → 0.1.18

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,262 +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
286
+
218
287
  group :development, :test do
219
288
  gem "debug", platforms: %i[mri mswin mswin64 mingw x64_mingw], require: "debug/prelude"
220
289
  gem "brakeman", require: false
221
290
  gem "rubocop-rails-omakase", require: false
222
291
  end
223
-
224
- gem "regolith", "~> 0.1.13"
225
- GEMFILE
226
-
227
- File.write("#{service_dir}/Gemfile", custom_gemfile)
228
- end
229
-
230
-
231
- def vendor_regolith_gem(service_dir)
232
- vendor_dir = File.join(service_dir, "vendor")
233
- FileUtils.mkdir_p(vendor_dir)
234
-
235
- begin
236
- # Try to find the regolith gem path
237
- regolith_gem_path = if Gem.loaded_specs['regolith']
238
- Gem.loaded_specs['regolith'].full_gem_path
239
- else
240
- # Fallback: search gem paths
241
- Gem.path.each do |gem_path|
242
- regolith_path = File.join(gem_path, 'gems', "regolith-#{Regolith::VERSION}")
243
- return regolith_path if File.exist?(regolith_path)
244
- end
245
- nil
246
- end
247
292
 
248
- if regolith_gem_path && File.exist?(regolith_gem_path)
249
- regolith_dest = File.join(vendor_dir, "regolith")
250
- FileUtils.cp_r(regolith_gem_path, regolith_dest)
251
- puts "📦 Vendored Regolith gem from #{regolith_gem_path}"
252
- else
253
- puts "⚠️ Could not find regolith gem path, using system gem instead"
254
- # Update Gemfile to use system gem
255
- gemfile_path = File.join(service_dir, "Gemfile")
256
- gemfile_content = File.read(gemfile_path)
257
- gemfile_content.gsub!('gem "regolith", path: "vendor/regolith"', 'gem "regolith", "~> 0.1.7"')
258
- File.write(gemfile_path, gemfile_content)
259
- end
260
- rescue => e
261
- puts "⚠️ Error vendoring regolith gem: #{e.message}"
262
- puts " Using system gem instead"
263
- # Update Gemfile to use system gem
264
- gemfile_path = File.join(service_dir, "Gemfile")
265
- gemfile_content = File.read(gemfile_path)
266
- gemfile_content.gsub!('gem "regolith", path: "vendor/regolith"', 'gem "regolith", "~> 0.1.7"')
267
- File.write(gemfile_path, gemfile_content)
268
- end
293
+ gem "regolith"
294
+ GEMFILE
269
295
  end
270
296
 
271
297
  def patch_rails_app(service_dir, service_name, port)
272
- # Add health controller
273
- add_health_controller(service_dir)
274
-
275
- # Add health route
298
+ create_initializers(service_dir, service_name)
299
+ create_health_controller(service_dir)
276
300
  add_health_route(service_dir)
277
-
278
- # 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)
279
306
  initializer_dir = "#{service_dir}/config/initializers"
280
307
  FileUtils.mkdir_p(initializer_dir)
308
+
281
309
  File.write("#{initializer_dir}/regolith.rb", generate_regolith_initializer(service_name))
282
-
283
- # Create Dockerfile
284
- 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)
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
285
332
 
286
- # Patch application.rb
333
+ def patch_application_rb(service_dir, service_name, port)
287
334
  app_rb_path = "#{service_dir}/config/application.rb"
288
335
  app_rb_content = File.read(app_rb_path)
289
336
 
290
337
  cors_config = <<~RUBY
291
338
 
292
339
  # Regolith configuration
293
- config.middleware.insert_before 0, Rack::Cors do
294
- allow do
295
- origins '*'
296
- resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head]
297
- end
298
- end
299
-
300
340
  config.regolith_service_name = '#{service_name}'
301
341
  config.regolith_service_port = #{port}
302
342
  RUBY
303
343
 
304
- app_rb_content.gsub!(/class Application < Rails::Application.*?
305
- end/m) do |match|
306
- match.gsub(/(\n end)$/, "#{cors_config}\1")
307
- end
308
-
344
+ app_rb_content.gsub!(/(\n end\n\z)/, "#{cors_config}\1")
309
345
  File.write(app_rb_path, app_rb_content)
310
346
  end
311
347
 
312
- def add_health_controller(service_dir)
313
- controller_dir = File.join(service_dir, "app", "controllers", "regolith")
314
- FileUtils.mkdir_p(controller_dir)
315
-
316
- health_controller = <<~RUBY
317
- module Regolith
318
- class HealthController < ActionController::API
319
- def show
320
- render json: {
321
- ok: true,
322
- service: Rails.application.config.regolith_service_name,
323
- version: Regolith::VERSION,
324
- time: Time.now.iso8601
325
- }
326
- end
327
- end
328
- end
329
- RUBY
330
-
331
- File.write(File.join(controller_dir, "health_controller.rb"), health_controller)
332
- end
333
-
334
- def add_health_route(service_dir)
335
- routes_path = File.join(service_dir, "config", "routes.rb")
336
- content = File.read(routes_path)
337
-
338
- if content.sub!(/end\s*\z/, " get '/health', to: 'regolith/health#show'\nend\n")
339
- File.write(routes_path, content)
340
- else
341
- # Fallback: append to file
342
- File.open(routes_path, "a") { |f| f.puts "\nget '/health', to: 'regolith/health#show'\n" }
343
- end
344
- end
345
-
346
- def start_server
347
- unless find_regolith_config
348
+ # Service management commands
349
+ def start_server(_flags = {})
350
+ unless File.exist?('docker-compose.yml')
348
351
  puts "❌ Error: Not in a Regolith app directory"
349
352
  exit 1
350
353
  end
351
354
 
352
355
  puts "🚀 Starting Regolith services..."
353
- config = load_regolith_config
354
-
355
- config['services'].each do |name, service|
356
- puts "🚀 #{name}_service will run at http://localhost:#{service['port']}"
357
- end
358
356
 
359
- puts "🧭 Service registry loaded from config/regolith.yml"
360
- puts ""
357
+ config = load_regolith_config
358
+ show_service_info(config)
361
359
 
362
- exec_compose("up", "--build")
360
+ exec_compose('up', '--build')
363
361
  end
364
362
 
365
- def stop_all_services
366
- exec_compose("down")
363
+ def stop_services
364
+ puts "🛑 Stopping all services..."
365
+ exec_compose('down', '-v')
367
366
  end
368
367
 
369
- def restart_service(service_name)
368
+ def restart_service(service_name = nil)
370
369
  if service_name
371
- exec_compose("restart", service_name)
370
+ ensure_service_exists!(service_name)
371
+ puts "🔄 Restarting service '#{service_name}'..."
372
+ exec_compose('restart', service_name)
372
373
  else
373
- exec_compose("restart")
374
+ puts "🔄 Restarting all services..."
375
+ exec_compose('restart')
374
376
  end
375
377
  end
376
378
 
377
- def stop_service(service_name)
378
- unless service_name
379
- puts "❌ Error: Service name required"
380
- puts "Usage: regolith stop <service_name>"
381
- 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')
382
387
  end
383
- exec_compose("stop", service_name)
384
388
  end
385
389
 
386
390
  def show_status
387
- config = load_regolith_config
388
-
389
- # Try different format options for docker compose ps
391
+ puts "📊 Service Status:"
392
+ puts
393
+
394
+ # Try different format options for maximum compatibility
390
395
  success = system_compose('ps', '--format', 'table') ||
391
396
  system_compose('ps', '--format', 'json') ||
392
397
  system_compose('ps')
393
-
394
- if success && config['services'].any?
395
- puts ""
396
- 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
397
403
  ports = config['services'].values.map { |s| s['port'] }.sort
398
- 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
399
421
  end
400
-
401
- exit($?.exitstatus) unless success
402
422
  end
403
423
 
404
- def show_logs(service_name, follow = false)
424
+ def show_logs(service_name = nil, flags = {})
405
425
  args = ['logs']
426
+ args << '--follow' if flags[:follow] || flags[:f]
406
427
  args << service_name if service_name
407
- args << '-f' if follow
428
+
408
429
  exec_compose(*args)
409
430
  end
410
431
 
411
- def exec_in_service(service_name, command)
412
- unless service_name
413
- puts "❌ Error: Service name required"
414
- puts "Usage: regolith exec <service_name> <command>"
415
- 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']
416
437
  end
417
-
418
- command = ['bash'] if command.empty?
419
- exec_compose('exec', service_name, *command)
438
+
439
+ exec_compose('exec', service_name, *command_args)
420
440
  end
421
441
 
422
442
  def shell_service(service_name)
@@ -425,23 +445,22 @@ module Regolith
425
445
  puts "Usage: regolith shell <service_name>"
426
446
  exit 1
427
447
  end
448
+
449
+ ensure_service_exists!(service_name)
450
+ puts "🐚 Opening shell for #{service_name}_service..."
428
451
  exec_compose('exec', service_name, 'bash')
429
452
  end
430
453
 
454
+ # Rails integration commands
431
455
  def rails_passthrough(service_name, rails_args)
432
- unless service_name
433
- puts "❌ Error: Service name required"
434
- puts "Usage: regolith rails <service_name> <rails_command>"
435
- exit 1
436
- end
456
+ ensure_service_exists!(service_name)
437
457
 
438
- config = load_regolith_config
439
- unless config['services'][service_name]
440
- 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>"
441
461
  exit 1
442
462
  end
443
463
 
444
- rails_args = ['--help'] if rails_args.empty?
445
464
  exec_compose('exec', service_name, 'bash', '-lc',
446
465
  "bundle exec rails #{Shellwords.join(rails_args)}")
447
466
  end
@@ -453,25 +472,11 @@ module Regolith
453
472
  exit 1
454
473
  end
455
474
 
456
- config = load_regolith_config
457
- unless config['services'][service_name]
458
- puts "❌ Error: Service '#{service_name}' not found"
459
- exit 1
460
- end
461
-
475
+ ensure_service_exists!(service_name)
462
476
  puts "🧪 Opening Rails console for #{service_name}_service..."
463
477
  exec_compose('exec', service_name, 'rails', 'console')
464
478
  end
465
479
 
466
- def show_routes(service_name)
467
- unless service_name
468
- puts "❌ Error: Service name required"
469
- puts "Usage: regolith routes <service_name>"
470
- exit 1
471
- end
472
- rails_passthrough(service_name, ['routes'])
473
- end
474
-
475
480
  def open_service(service_name)
476
481
  unless service_name
477
482
  puts "❌ Error: Service name required"
@@ -479,232 +484,296 @@ module Regolith
479
484
  exit 1
480
485
  end
481
486
 
487
+ ensure_service_exists!(service_name)
482
488
  config = load_regolith_config
483
- service = config['services'][service_name]
484
- unless service
485
- puts "❌ Error: Service '#{service_name}' not found"
486
- exit 1
487
- end
489
+ port = config['services'][service_name]['port']
490
+ url = "http://localhost:#{port}"
488
491
 
489
- url = "http://localhost:#{service['port']}"
490
492
  puts "🌐 Opening #{url}..."
491
493
 
494
+ # Cross-platform open command
492
495
  case RbConfig::CONFIG['host_os']
493
496
  when /mswin|mingw|cygwin/
494
497
  system(%{start "" "#{url}"})
495
498
  when /darwin/
496
- system("open", url)
497
- when /linux|bsd/
498
- system("xdg-open", url)
499
+ system("open #{url}")
499
500
  else
500
- puts "Please open #{url} in your browser"
501
+ system("xdg-open #{url}") || puts("Visit: #{url}")
501
502
  end
502
503
  end
503
504
 
504
- def db_command(command, service_or_flag)
505
- db_action = command.split(':').last
506
-
507
- if service_or_flag == '--all'
508
- config = load_regolith_config
509
- config['services'].each do |service_name, _|
510
- puts "🗄️ Running db:#{db_action} for #{service_name}..."
511
- 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
512
517
  end
513
- elsif service_or_flag
514
- run_single_db_command(service_or_flag, db_action)
515
518
  else
516
- puts "❌ Error: Service name or --all flag required"
517
- puts "Usage: regolith db:#{db_action} <service_name|--all>"
519
+ puts "❌ Unknown db action: #{action}"
520
+ puts "Available: create, migrate, seed, reset, setup, drop"
518
521
  exit 1
519
522
  end
520
523
  end
521
524
 
522
- def run_single_db_command(service_name, action)
523
- config = load_regolith_config
524
- unless config['services'][service_name]
525
- puts " Error: Service '#{service_name}' not found"
526
- return false
527
- 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')
528
530
 
529
- success = system_compose('exec', service_name, 'rails', "db:#{action}")
530
- unless success
531
- puts "❌ db:#{action} failed for #{service_name} (exit code: #{$?.exitstatus})"
532
- return false
531
+ unless success
532
+ puts "❌ Tests failed for #{service} (exit code: #{$?.exitstatus})"
533
+ exit 1 unless flags[:continue_on_failure]
534
+ end
533
535
  end
534
- true
536
+
537
+ puts "✅ All tests passed!" if flags[:all]
535
538
  end
536
539
 
537
- def run_tests(service_or_flag)
538
- if service_or_flag == '--all'
539
- config = load_regolith_config
540
- all_passed = true
541
- config['services'].each do |service_name, _|
542
- puts "🧪 Running tests for #{service_name}..."
543
- unless run_single_test(service_name)
544
- all_passed = false
545
- end
546
- end
547
- exit(1) unless all_passed
548
- elsif service_or_flag
549
- exit(1) unless run_single_test(service_or_flag)
550
- else
551
- puts "❌ Error: Service name or --all flag required"
552
- 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>"
553
544
  exit 1
554
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')
555
550
  end
556
551
 
557
- def run_single_test(service_name)
552
+ # Health and monitoring
553
+ def health_check
554
+ puts "🔍 Health Check Results:"
555
+ puts
556
+
558
557
  config = load_regolith_config
559
- unless config['services'][service_name]
560
- puts "❌ Error: Service '#{service_name}' not found"
561
- return false
562
- end
563
558
 
564
- success = system_compose('exec', service_name, 'rails', 'test')
565
- unless success
566
- puts "❌ Tests failed for #{service_name} (exit code: #{$?.exitstatus})"
567
- 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
568
573
  end
569
- true
570
574
  end
571
575
 
572
- def check_health
573
- config = load_regolith_config
574
- puts "🔍 Health Check Results:"
575
-
576
- all_healthy = true
577
- config['services'].each do |service_name, service|
578
- health_data = check_service_health(service_name, service['port'])
579
- if health_data
580
- puts "✅ #{service_name} (port #{service['port']}) - healthy"
581
- puts " Version: #{health_data['version']}" if health_data['version']
582
- 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
+ }
583
589
  else
584
- puts " #{service_name} (port #{service['port']}) - unreachable"
585
- all_healthy = false
590
+ { healthy: false, message: "HTTP #{response.code}" }
586
591
  end
592
+ rescue
593
+ { healthy: false, message: 'unreachable' }
587
594
  end
588
-
589
- exit(1) unless all_healthy
590
- end
591
-
592
- def check_service_health(service_name, port)
593
- require_relative 'service_client'
594
- response = Regolith::ServiceClient.health(service_name)
595
- Regolith::ServiceClient.parsed_body(response) if response.code.to_i == 200
596
- rescue => e
597
- nil
598
595
  end
599
596
 
600
- def show_config(json_format = false)
597
+ # Configuration
598
+ def show_config(flags = {})
601
599
  config = load_regolith_config
602
- if json_format
600
+
601
+ if flags[:json]
603
602
  puts JSON.pretty_generate(config)
604
603
  else
604
+ puts "📋 Current Configuration:"
605
+ puts
605
606
  puts YAML.dump(config)
606
607
  end
607
608
  end
608
609
 
609
- 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
+
610
671
  config = load_regolith_config
611
-
612
- if json_format
613
- inspection = {
614
- endpoints: {},
615
- 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
616
690
  }
617
- config['services'].each do |name, service|
618
- inspection[:endpoints][name] = "http://localhost:#{service['port']}"
619
- end
620
- puts JSON.pretty_generate(inspection)
691
+ puts JSON.pretty_generate(inspection_data)
621
692
  else
622
- puts "📊 Service Endpoints:"
623
- config['services'].each do |name, service|
624
- 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}"
625
698
  end
626
- puts ""
627
- puts "📋 Full Configuration:"
699
+
700
+ puts "\n📋 Full Configuration:"
628
701
  puts YAML.dump(config)
629
702
  end
630
703
  end
631
704
 
632
- def run_diagnostics
633
- puts "🔍 Regolith System Diagnostics"
634
- puts ""
635
-
636
- # Check Docker
637
- if system("docker --version > /dev/null 2>&1")
638
- puts "✅ Docker is available"
639
- else
640
- puts "❌ Docker is not available"
641
- end
642
-
643
- # Check Docker Compose
644
- if system("docker compose version > /dev/null 2>&1") ||
645
- system("docker-compose --version > /dev/null 2>&1")
646
- 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]
647
716
  else
648
- puts "❌ Docker Compose is not available"
717
+ puts "❌ Docker Compose not found"
718
+ exit 1
649
719
  end
650
-
651
- # Check Rails
652
- if system("rails --version > /dev/null 2>&1")
653
- puts "✅ Rails is available"
654
- else
655
- puts "❌ Rails is not available"
656
- end
657
-
658
- # Check Ruby version
659
- puts "✅ Ruby #{RUBY_VERSION}"
660
-
661
- # Check Regolith config
662
- if find_regolith_config
663
- puts "✅ Regolith configuration found"
664
- config = load_regolith_config
665
- puts " #{config['services'].size} service(s) configured"
666
- else
667
- 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)
668
742
  end
669
743
  end
670
744
 
671
- def prune_docker
672
- puts "🧹 Cleaning up Docker resources..."
673
- 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
674
752
  end
675
753
 
676
- def rebuild_service(service_name)
677
- if service_name
678
- puts "🔨 Rebuilding #{service_name}..."
679
- exec_compose("build", "--no-cache", service_name)
680
- else
681
- puts "🔨 Rebuilding all services..."
682
- 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']}"
683
757
  end
758
+
759
+ puts "🧭 Service registry: config/regolith.yml"
760
+ puts
684
761
  end
685
762
 
686
763
  def load_regolith_config
687
- config_path = find_regolith_config
688
- return { 'services' => {} } unless config_path
689
-
764
+ config_path = Regolith.send(:find_regolith_config)
765
+ return { 'services' => {} } unless config_path && File.exist?(config_path)
766
+
767
+ # Use safe YAML loading
690
768
  config = Psych.safe_load(File.read(config_path), permitted_classes: [], aliases: false) || {}
691
769
  config['services'] ||= {}
692
770
  config
693
771
  end
694
772
 
695
- def find_regolith_config
696
- current_dir = Dir.pwd
697
- while current_dir != '/'
698
- config_path = File.join(current_dir, 'config', 'regolith.yml')
699
- return config_path if File.exist?(config_path)
700
- current_dir = File.dirname(current_dir)
701
- end
702
- nil
703
- end
704
-
705
773
  def save_regolith_config(config)
706
774
  FileUtils.mkdir_p('config')
707
775
  File.write('config/regolith.yml', YAML.dump(config))
776
+ Regolith.reload_service_registry!
708
777
  end
709
778
 
710
779
  def update_docker_compose(config)
@@ -712,82 +781,95 @@ module Regolith
712
781
  File.write('docker-compose.yml', docker_compose)
713
782
  end
714
783
 
784
+ # File generators
715
785
  def generate_docker_compose(app_name, services = {})
716
- template = <<~YAML
717
- version: '3.8'
718
-
719
- networks:
720
- regolith:
721
-
722
- services:
723
- db:
724
- image: postgres:14
725
- environment:
726
- POSTGRES_DB: #{app_name}_development
727
- POSTGRES_USER: postgres
728
- POSTGRES_PASSWORD: password
729
- ports:
730
- - "5432:5432"
731
- volumes:
732
- - postgres_data:/var/lib/postgresql/data
733
- networks:
734
- - regolith
735
- healthcheck:
736
- test: ["CMD-SHELL", "pg_isready -U postgres"]
737
- interval: 30s
738
- timeout: 10s
739
- retries: 3
740
-
741
- <% services.each do |name, service| %>
742
- <%= name %>:
743
- build:
744
- context: <%= service['root'] %>
745
- args:
746
- BUILD_ENV: development
747
- ports:
748
- - "<%= service['port'] %>:3000"
749
- depends_on:
750
- db:
751
- condition: service_healthy
752
- environment:
753
- DATABASE_URL: postgres://postgres:password@db:5432/<%= app_name %>_development
754
- REGOLITH_SERVICE_NAME: <%= name %>
755
- REGOLITH_SERVICE_PORT: <%= service['port'] %>
756
- volumes:
757
- - <%= service['root'] %>:/app
758
- networks:
759
- - regolith
760
- command: bash -c "rm -f tmp/pids/server.pid && bundle install && rails db:prepare && rails server -b 0.0.0.0"
761
- healthcheck:
762
- test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
763
- interval: 30s
764
- timeout: 10s
765
- retries: 3
766
- <% end %>
767
-
768
- volumes:
769
- postgres_data:
770
- YAML
771
-
772
- 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
+ compose_services[name] = {
808
+ 'build' => {
809
+ 'context' => service['root'],
810
+ 'args' => {
811
+ 'BUILD_ENV' => 'development'
812
+ }
813
+ },
814
+ 'ports' => ["#{service['port']}:3000"],
815
+ 'networks' => ['regolith'],
816
+ 'depends_on' => {
817
+ 'db' => {
818
+ 'condition' => 'service_healthy'
819
+ }
820
+ },
821
+ 'environment' => {
822
+ 'DATABASE_URL' => "postgres://postgres:password@db:5432/#{app_name}_development",
823
+ 'REGOLITH_SERVICE_NAME' => name,
824
+ 'REGOLITH_SERVICE_PORT' => service['port']
825
+ },
826
+ 'volumes' => ["#{service['root']}:/app"],
827
+ 'command' => 'bash -c "rm -f tmp/pids/server.pid && bundle install && rails db:prepare && rails server -b 0.0.0.0"',
828
+ 'healthcheck' => {
829
+ 'test' => ['CMD-SHELL', 'curl -f http://localhost:3000/health || exit 1'],
830
+ 'interval' => '30s',
831
+ 'timeout' => '10s',
832
+ 'retries' => 3,
833
+ 'start_period' => '40s'
834
+ }
835
+ }
836
+ end
837
+
838
+ {
839
+ 'version' => '3.8',
840
+ 'networks' => {
841
+ 'regolith' => {}
842
+ },
843
+ 'services' => compose_services,
844
+ 'volumes' => {
845
+ 'postgres_data' => nil
846
+ }
847
+ }.to_yaml
773
848
  end
774
849
 
775
850
  def generate_dockerfile
776
851
  <<~DOCKERFILE
777
- FROM ruby:3.1
852
+ FROM ruby:3.1-slim
778
853
 
779
- WORKDIR /app
854
+ # Install system dependencies
855
+ RUN apt-get update -qq && \\
856
+ apt-get install -y nodejs postgresql-client libyaml-dev build-essential pkg-config curl && \\
857
+ apt-get clean && \\
858
+ rm -rf /var/lib/apt/lists/*
780
859
 
781
- RUN apt-get update -qq && apt-get install -y nodejs postgresql-client libyaml-dev libsqlite3-dev build-essential pkg-config curl
860
+ WORKDIR /app
782
861
 
862
+ # Copy Gemfile and install gems
783
863
  COPY Gemfile Gemfile.lock* ./
784
864
 
865
+ # Conditional bundler config for dev vs prod
785
866
  ARG BUILD_ENV=development
786
867
  RUN if [ "$BUILD_ENV" = "production" ]; then \\
787
868
  bundle config set --local deployment 'true' && \\
788
869
  bundle config set --local without 'development test'; \\
789
870
  fi && bundle install
790
871
 
872
+ # Copy application code
791
873
  COPY . .
792
874
 
793
875
  EXPOSE 3000
@@ -809,6 +891,7 @@ module Regolith
809
891
  )
810
892
  end
811
893
 
894
+ # Load service registry if available
812
895
  if File.exist?(Rails.application.config.regolith.service_registry)
813
896
  REGOLITH_SERVICES = YAML.load_file(Rails.application.config.regolith.service_registry)['services'] || {}
814
897
  else
@@ -817,22 +900,184 @@ module Regolith
817
900
  RUBY
818
901
  end
819
902
 
903
+ def generate_cors_initializer
904
+ <<~RUBY
905
+ # CORS configuration for microservices
906
+ Rails.application.config.middleware.insert_before 0, Rack::Cors do
907
+ allow do
908
+ origins '*' # Configure appropriately for production
909
+ resource '*',
910
+ headers: :any,
911
+ methods: %i[get post put patch delete options head],
912
+ expose: %w[Authorization Content-Type],
913
+ max_age: 600
914
+ end
915
+ end
916
+ RUBY
917
+ end
918
+
919
+ def generate_health_controller
920
+ <<~RUBY
921
+ module Regolith
922
+ class HealthController < ActionController::API
923
+ def show
924
+ render json: {
925
+ ok: true,
926
+ service: Rails.application.config.regolith_service_name,
927
+ time: Time.now.to_i,
928
+ version: Rails.application.config.regolith.version
929
+ }
930
+ end
931
+ end
932
+ end
933
+ RUBY
934
+ end
935
+
936
+ def generate_gitignore
937
+ <<~GITIGNORE
938
+ # Regolith
939
+ /services/*/log/*
940
+ /services/*/tmp/*
941
+ /services/*/.env*
942
+ .DS_Store
943
+
944
+ # Docker
945
+ docker-compose.override.yml
946
+
947
+ # Logs
948
+ *.log
949
+
950
+ # Runtime data
951
+ pids
952
+ *.pid
953
+ *.seed
954
+
955
+ # Environment variables
956
+ .env*
957
+ !.env.example
958
+ GITIGNORE
959
+ end
960
+
961
+ def generate_readme(app_name)
962
+ <<~MARKDOWN
963
+ # #{app_name.capitalize}
964
+
965
+ A Regolith microservices application built with Rails and Docker.
966
+
967
+ ## Getting Started
968
+
969
+ ```bash
970
+ # Start all services
971
+ regolith server
972
+
973
+ # Generate a new service
974
+ regolith generate service users
975
+
976
+ # Open service in browser
977
+ regolith open users
978
+
979
+ # View service logs
980
+ regolith logs users -f
981
+
982
+ # Run database migrations
983
+ regolith db:migrate --all
984
+
985
+ # Health check
986
+ regolith health
987
+ ```
988
+
989
+ ## Services
990
+
991
+ #{services_documentation}
992
+
993
+ ## Development
994
+
995
+ ```bash
996
+ # Open Rails console for a service
997
+ regolith console users
998
+
999
+ # Run Rails commands
1000
+ regolith rails users db:migrate
1001
+ regolith rails users routes
1002
+
1003
+ # Run tests
1004
+ regolith test --all
1005
+
1006
+ # Execute commands in service
1007
+ regolith exec users bash
1008
+ ```
1009
+
1010
+ ## Architecture
1011
+
1012
+ - **Rails 7** API-only applications
1013
+ - **PostgreSQL** for persistence
1014
+ - **Docker Compose** for orchestration
1015
+ - **Service registry** for inter-service communication
1016
+
1017
+ Built with [Regolith](https://regolith.bio) - Rails for distributed systems.
1018
+ MARKDOWN
1019
+ end
1020
+
1021
+ def services_documentation
1022
+ config = load_regolith_config
1023
+ return "No services yet. Run `regolith generate service <name>` to create one." if config['services'].empty?
1024
+
1025
+ config['services'].map do |name, service|
1026
+ "- **#{name}** - http://localhost:#{service['port']}"
1027
+ end.join("\n")
1028
+ end
1029
+
820
1030
  def generate_makefile
821
1031
  <<~MAKEFILE
822
- .PHONY: server console build clean
1032
+ .PHONY: server up down restart logs console test health doctor
823
1033
 
824
- server:
1034
+ # Start services
1035
+ server up:
825
1036
  \tregolith server
826
1037
 
1038
+ # Stop services
1039
+ down:
1040
+ \tregolith down
1041
+
1042
+ # Restart services
1043
+ restart:
1044
+ \tregolith restart
1045
+
1046
+ # View logs
1047
+ logs:
1048
+ \tregolith logs -f
1049
+
1050
+ # Open console (usage: make console SERVICE=users)
827
1051
  console:
828
- \tregolith console
1052
+ \t@if [ -z "$(SERVICE)" ]; then echo "Usage: make console SERVICE=service_name"; exit 1; fi
1053
+ \tregolith console $(SERVICE)
1054
+
1055
+ # Run tests
1056
+ test:
1057
+ \tregolith test --all
1058
+
1059
+ # Health check
1060
+ health:
1061
+ \tregolith health
829
1062
 
830
- build:
831
- \tdocker-compose build
1063
+ # System diagnostics
1064
+ doctor:
1065
+ \tregolith doctor
832
1066
 
1067
+ # Database operations
1068
+ db-migrate:
1069
+ \tregolith db:migrate --all
1070
+
1071
+ db-setup:
1072
+ \tregolith db:setup --all
1073
+
1074
+ # Cleanup
833
1075
  clean:
834
- \tdocker-compose down -v
835
- \tdocker system prune -f
1076
+ \tregolith prune
1077
+
1078
+ # Shortcuts
1079
+ dev: up
1080
+ stop: down
836
1081
  MAKEFILE
837
1082
  end
838
1083
 
@@ -843,82 +1088,64 @@ module Regolith
843
1088
  RUBY
844
1089
  end
845
1090
 
846
- def exec_compose(*args)
847
- if system("docker compose version > /dev/null 2>&1")
848
- exec("docker", "compose", *args)
849
- elsif system("docker-compose --version > /dev/null 2>&1")
850
- exec("docker-compose", *args)
851
- else
852
- puts "❌ Error: Neither 'docker compose' nor 'docker-compose' is available"
853
- exit 1
854
- end
855
- end
856
-
857
- def system_compose(*args)
858
- if system("docker compose version > /dev/null 2>&1")
859
- system("docker", "compose", *args)
860
- elsif system("docker-compose --version > /dev/null 2>&1")
861
- system("docker-compose", *args)
862
- else
863
- puts "❌ Error: Neither 'docker compose' nor 'docker-compose' is available"
864
- false
865
- end
866
- end
867
-
868
1091
  def show_help
869
1092
  puts <<~HELP
870
- Regolith #{Regolith::VERSION} - Rails Microservices Framework
1093
+ Regolith #{Regolith::VERSION} - Rails for Distributed Systems
871
1094
 
872
1095
  USAGE:
873
1096
  regolith <command> [options]
874
1097
 
875
- PROJECT MANAGEMENT:
1098
+ PROJECT COMMANDS:
876
1099
  new <app_name> Create a new Regolith application
877
1100
  generate service <name> Generate a new microservice
878
1101
 
879
1102
  SERVICE MANAGEMENT:
880
1103
  server, up Start all services with Docker Compose
881
- down Stop all services
882
- restart [service] Restart all services or specific service
883
- stop <service> Stop a specific service
884
- ps, status Show service status
885
- logs [service] [-f] Show logs (optionally follow)
886
- exec <service> <command> Execute command in service container
887
- shell <service> Open bash shell in service
1104
+ down Stop and remove all services
1105
+ restart [service] Restart one or all services
1106
+ stop [service] Stop one or all services
1107
+ ps, status Show running containers
1108
+ logs [service] [-f] View service logs
1109
+ exec <service> [cmd] Execute command in service container
1110
+ shell <service> Open shell in service container
1111
+ rebuild [service] Rebuild service images
888
1112
 
889
1113
  RAILS INTEGRATION:
890
- rails <service> <command> Run Rails command in service
891
1114
  console <service> Open Rails console for service
1115
+ rails <service> <cmd> Run Rails command in service
892
1116
  routes <service> Show routes for service
893
- open <service> Open service URL in browser
1117
+ open <service> Open service in browser
894
1118
 
895
1119
  DATABASE OPERATIONS:
896
- db:create [service|--all] Create databases
897
- db:migrate [service|--all] Run migrations
898
- db:seed [service|--all] Seed databases
899
- db:reset [service|--all] Reset databases
900
- db:setup [service|--all] Setup databases
901
- db:drop [service|--all] Drop databases
1120
+ db:migrate [service|--all] Run migrations
1121
+ db:create [service|--all] Create databases
1122
+ db:seed [service|--all] Seed databases
1123
+ db:reset [service|--all] Reset databases
1124
+ db:setup [service|--all] Setup databases
1125
+ db:drop [service|--all] Drop databases
902
1126
 
903
1127
  TESTING & HEALTH:
904
1128
  test [service|--all] Run tests
905
- health Check service health
1129
+ health Health check all services
906
1130
 
907
1131
  UTILITIES:
908
- config [--json] Show configuration
909
- inspect [--json] Show endpoints and config
1132
+ config [--json] Show current configuration
1133
+ inspect [--json] Show services and resolved endpoints
1134
+ prune Clean up Docker system
910
1135
  doctor Run system diagnostics
911
- prune Clean up Docker resources
912
- rebuild [service] Rebuild service images
913
- version Show version information
1136
+ version Show version
914
1137
 
915
1138
  EXAMPLES:
916
1139
  regolith new marketplace
917
1140
  regolith generate service products
918
1141
  regolith server
919
1142
  regolith rails products db:migrate
1143
+ regolith routes products
920
1144
  regolith open products
921
- regolith health
1145
+ regolith shell products
1146
+ regolith inspect --json
1147
+ regolith config --json | jq '.services'
1148
+ regolith test --all
922
1149
 
923
1150
  Get started:
924
1151
  regolith new myapp