regolith 0.1.15 → 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,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)
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
286
332
 
287
- # Patch application.rb
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,95 @@ 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
+ 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
774
848
  end
775
849
 
776
850
  def generate_dockerfile
777
851
  <<~DOCKERFILE
778
- FROM ruby:3.1
852
+ FROM ruby:3.1-slim
779
853
 
780
- 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/*
781
859
 
782
- RUN apt-get update -qq && apt-get install -y nodejs postgresql-client libyaml-dev libsqlite3-dev build-essential pkg-config curl
860
+ WORKDIR /app
783
861
 
862
+ # Copy Gemfile and install gems
784
863
  COPY Gemfile Gemfile.lock* ./
785
864
 
865
+ # Conditional bundler config for dev vs prod
786
866
  ARG BUILD_ENV=development
787
867
  RUN if [ "$BUILD_ENV" = "production" ]; then \\
788
868
  bundle config set --local deployment 'true' && \\
789
869
  bundle config set --local without 'development test'; \\
790
870
  fi && bundle install
791
871
 
872
+ # Copy application code
792
873
  COPY . .
793
874
 
794
875
  EXPOSE 3000
@@ -810,6 +891,7 @@ module Regolith
810
891
  )
811
892
  end
812
893
 
894
+ # Load service registry if available
813
895
  if File.exist?(Rails.application.config.regolith.service_registry)
814
896
  REGOLITH_SERVICES = YAML.load_file(Rails.application.config.regolith.service_registry)['services'] || {}
815
897
  else
@@ -818,22 +900,184 @@ module Regolith
818
900
  RUBY
819
901
  end
820
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
+
821
1030
  def generate_makefile
822
1031
  <<~MAKEFILE
823
- .PHONY: server console build clean
1032
+ .PHONY: server up down restart logs console test health doctor
824
1033
 
825
- server:
1034
+ # Start services
1035
+ server up:
826
1036
  \tregolith server
827
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)
828
1051
  console:
829
- \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
830
1062
 
831
- build:
832
- \tdocker-compose build
1063
+ # System diagnostics
1064
+ doctor:
1065
+ \tregolith doctor
833
1066
 
1067
+ # Database operations
1068
+ db-migrate:
1069
+ \tregolith db:migrate --all
1070
+
1071
+ db-setup:
1072
+ \tregolith db:setup --all
1073
+
1074
+ # Cleanup
834
1075
  clean:
835
- \tdocker-compose down -v
836
- \tdocker system prune -f
1076
+ \tregolith prune
1077
+
1078
+ # Shortcuts
1079
+ dev: up
1080
+ stop: down
837
1081
  MAKEFILE
838
1082
  end
839
1083
 
@@ -844,82 +1088,64 @@ module Regolith
844
1088
  RUBY
845
1089
  end
846
1090
 
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
1091
  def show_help
870
1092
  puts <<~HELP
871
- Regolith #{Regolith::VERSION} - Rails Microservices Framework
1093
+ Regolith #{Regolith::VERSION} - Rails for Distributed Systems
872
1094
 
873
1095
  USAGE:
874
1096
  regolith <command> [options]
875
1097
 
876
- PROJECT MANAGEMENT:
1098
+ PROJECT COMMANDS:
877
1099
  new <app_name> Create a new Regolith application
878
1100
  generate service <name> Generate a new microservice
879
1101
 
880
1102
  SERVICE MANAGEMENT:
881
1103
  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
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
889
1112
 
890
1113
  RAILS INTEGRATION:
891
- rails <service> <command> Run Rails command in service
892
1114
  console <service> Open Rails console for service
1115
+ rails <service> <cmd> Run Rails command in service
893
1116
  routes <service> Show routes for service
894
- open <service> Open service URL in browser
1117
+ open <service> Open service in browser
895
1118
 
896
1119
  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
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
903
1126
 
904
1127
  TESTING & HEALTH:
905
1128
  test [service|--all] Run tests
906
- health Check service health
1129
+ health Health check all services
907
1130
 
908
1131
  UTILITIES:
909
- config [--json] Show configuration
910
- 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
911
1135
  doctor Run system diagnostics
912
- prune Clean up Docker resources
913
- rebuild [service] Rebuild service images
914
- version Show version information
1136
+ version Show version
915
1137
 
916
1138
  EXAMPLES:
917
1139
  regolith new marketplace
918
1140
  regolith generate service products
919
1141
  regolith server
920
1142
  regolith rails products db:migrate
1143
+ regolith routes products
921
1144
  regolith open products
922
- regolith health
1145
+ regolith shell products
1146
+ regolith inspect --json
1147
+ regolith config --json | jq '.services'
1148
+ regolith test --all
923
1149
 
924
1150
  Get started:
925
1151
  regolith new myapp