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