regolith 0.1.8 → 0.1.10
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 +478 -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,49 @@ 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
|
+
# Vendor Regolith gem - FIXED VERSION
|
169
|
+
vendor_regolith_gem(service_dir)
|
170
|
+
|
171
|
+
puts " → Running bundle install..."
|
172
|
+
Dir.chdir(service_dir) do
|
173
|
+
unless system("bundle install")
|
174
|
+
puts "❌ bundle install failed"
|
175
|
+
puts "→ You may be missing system libraries like libyaml-dev libsqlite3-dev build-essential pkg-config"
|
176
|
+
puts "→ Try: sudo apt install -y libyaml-dev libsqlite3-dev build-essential pkg-config"
|
177
|
+
exit 1
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
patch_rails_app(service_dir, service_name, port)
|
182
|
+
|
233
183
|
config['services'][service_name] = {
|
234
184
|
'port' => port,
|
235
185
|
'root' => "./#{service_dir}"
|
236
186
|
}
|
237
|
-
|
238
187
|
save_regolith_config(config)
|
239
188
|
update_docker_compose(config)
|
240
189
|
|
241
190
|
puts "✅ Created service '#{service_name}'"
|
242
191
|
puts "🚀 Service will run on port #{port}"
|
243
|
-
puts "→ Next: regolith
|
192
|
+
puts "→ Next: regolith server"
|
244
193
|
end
|
245
194
|
|
246
|
-
def next_available_port(start =
|
247
|
-
|
195
|
+
def next_available_port(start = 3001)
|
196
|
+
config = load_regolith_config
|
197
|
+
used_ports = config['services']&.values&.map { |s| s['port'] }.to_set || Set.new
|
248
198
|
port = start
|
249
|
-
port += 1 while
|
199
|
+
port += 1 while used_ports.include?(port)
|
250
200
|
port
|
251
201
|
end
|
252
202
|
|
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
|
203
|
+
def generate_gemfile(service_dir)
|
204
|
+
ruby_version = RUBY_VERSION.split('.')[0..1].join('.')
|
273
205
|
|
274
|
-
|
275
|
-
<<~GEMFILE
|
206
|
+
custom_gemfile = <<~GEMFILE
|
276
207
|
source "https://rubygems.org"
|
277
208
|
|
278
209
|
ruby "~> #{ruby_version}.0"
|
@@ -281,7 +212,6 @@ module Regolith
|
|
281
212
|
gem "pg", "~> 1.5"
|
282
213
|
gem "puma", ">= 5.0"
|
283
214
|
gem "rack-cors"
|
284
|
-
gem "bootsnap", require: false
|
285
215
|
|
286
216
|
group :development, :test do
|
287
217
|
gem "debug", platforms: %i[ mri mswin mswin64 mingw x64_mingw ], require: "debug/prelude"
|
@@ -291,162 +221,199 @@ module Regolith
|
|
291
221
|
|
292
222
|
gem "regolith", path: "vendor/regolith"
|
293
223
|
GEMFILE
|
224
|
+
|
225
|
+
File.write("#{service_dir}/Gemfile", custom_gemfile)
|
294
226
|
end
|
295
227
|
|
296
228
|
def vendor_regolith_gem(service_dir)
|
297
229
|
vendor_dir = File.join(service_dir, "vendor")
|
298
230
|
FileUtils.mkdir_p(vendor_dir)
|
299
231
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
232
|
+
begin
|
233
|
+
# Try to find the regolith gem path
|
234
|
+
regolith_gem_path = if Gem.loaded_specs['regolith']
|
235
|
+
Gem.loaded_specs['regolith'].full_gem_path
|
236
|
+
else
|
237
|
+
# Fallback: search gem paths
|
238
|
+
Gem.path.each do |gem_path|
|
239
|
+
regolith_path = File.join(gem_path, 'gems', "regolith-#{Regolith::VERSION}")
|
240
|
+
return regolith_path if File.exist?(regolith_path)
|
241
|
+
end
|
242
|
+
nil
|
243
|
+
end
|
244
|
+
|
245
|
+
if regolith_gem_path && File.exist?(regolith_gem_path)
|
246
|
+
regolith_dest = File.join(vendor_dir, "regolith")
|
247
|
+
FileUtils.cp_r(regolith_gem_path, regolith_dest)
|
248
|
+
puts "📦 Vendored Regolith gem from #{regolith_gem_path}"
|
249
|
+
else
|
250
|
+
puts "⚠️ Could not find regolith gem path, using system gem instead"
|
251
|
+
# Update Gemfile to use system gem
|
252
|
+
gemfile_path = File.join(service_dir, "Gemfile")
|
253
|
+
gemfile_content = File.read(gemfile_path)
|
254
|
+
gemfile_content.gsub!('gem "regolith", path: "vendor/regolith"', 'gem "regolith", "~> 0.1.10"')
|
255
|
+
File.write(gemfile_path, gemfile_content)
|
256
|
+
end
|
257
|
+
rescue => e
|
258
|
+
puts "⚠️ Error vendoring regolith gem: #{e.message}"
|
259
|
+
puts " Using system gem instead"
|
260
|
+
# Update Gemfile to use system gem
|
261
|
+
gemfile_path = File.join(service_dir, "Gemfile")
|
262
|
+
gemfile_content = File.read(gemfile_path)
|
263
|
+
gemfile_content.gsub!('gem "regolith", path: "vendor/regolith"', 'gem "regolith", "~> 0.1.10"')
|
264
|
+
File.write(gemfile_path, gemfile_content)
|
265
|
+
end
|
305
266
|
end
|
306
267
|
|
307
268
|
def patch_rails_app(service_dir, service_name, port)
|
308
|
-
|
309
|
-
|
269
|
+
# Add health controller
|
270
|
+
add_health_controller(service_dir)
|
271
|
+
|
272
|
+
# Add health route
|
310
273
|
add_health_route(service_dir)
|
311
|
-
|
312
|
-
|
313
|
-
end
|
314
|
-
|
315
|
-
def create_initializers(service_dir, service_name)
|
274
|
+
|
275
|
+
# Create initializer
|
316
276
|
initializer_dir = "#{service_dir}/config/initializers"
|
317
277
|
FileUtils.mkdir_p(initializer_dir)
|
318
|
-
|
319
278
|
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
|
-
|
327
|
-
File.write("#{controller_dir}/health_controller.rb", generate_health_controller)
|
328
|
-
end
|
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
279
|
|
334
|
-
#
|
335
|
-
|
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
|
280
|
+
# Create Dockerfile
|
281
|
+
File.write("#{service_dir}/Dockerfile", generate_dockerfile)
|
342
282
|
|
343
|
-
|
283
|
+
# Patch application.rb
|
344
284
|
app_rb_path = "#{service_dir}/config/application.rb"
|
345
285
|
app_rb_content = File.read(app_rb_path)
|
346
286
|
|
347
287
|
cors_config = <<~RUBY
|
348
288
|
|
349
289
|
# Regolith configuration
|
290
|
+
config.middleware.insert_before 0, Rack::Cors do
|
291
|
+
allow do
|
292
|
+
origins '*'
|
293
|
+
resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head]
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
350
297
|
config.regolith_service_name = '#{service_name}'
|
351
298
|
config.regolith_service_port = #{port}
|
352
299
|
RUBY
|
353
300
|
|
354
|
-
app_rb_content.gsub!(/
|
301
|
+
app_rb_content.gsub!(/class Application < Rails::Application.*?
|
302
|
+
end/m) do |match|
|
303
|
+
match.gsub(/(\n end)$/, "#{cors_config}\1")
|
304
|
+
end
|
305
|
+
|
355
306
|
File.write(app_rb_path, app_rb_content)
|
356
307
|
end
|
357
308
|
|
358
|
-
|
359
|
-
|
360
|
-
|
309
|
+
def add_health_controller(service_dir)
|
310
|
+
controller_dir = File.join(service_dir, "app", "controllers", "regolith")
|
311
|
+
FileUtils.mkdir_p(controller_dir)
|
312
|
+
|
313
|
+
health_controller = <<~RUBY
|
314
|
+
module Regolith
|
315
|
+
class HealthController < ActionController::API
|
316
|
+
def show
|
317
|
+
render json: {
|
318
|
+
ok: true,
|
319
|
+
service: Rails.application.config.regolith_service_name,
|
320
|
+
version: Regolith::VERSION,
|
321
|
+
time: Time.now.iso8601
|
322
|
+
}
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
RUBY
|
327
|
+
|
328
|
+
File.write(File.join(controller_dir, "health_controller.rb"), health_controller)
|
329
|
+
end
|
330
|
+
|
331
|
+
def add_health_route(service_dir)
|
332
|
+
routes_path = File.join(service_dir, "config", "routes.rb")
|
333
|
+
content = File.read(routes_path)
|
334
|
+
|
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 to file
|
339
|
+
File.open(routes_path, "a") { |f| f.puts "\nget '/health', to: 'regolith/health#show'\n" }
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
def start_server
|
344
|
+
unless find_regolith_config
|
361
345
|
puts "❌ Error: Not in a Regolith app directory"
|
362
346
|
exit 1
|
363
347
|
end
|
364
348
|
|
365
349
|
puts "🚀 Starting Regolith services..."
|
366
|
-
|
367
350
|
config = load_regolith_config
|
368
|
-
show_service_info(config)
|
369
351
|
|
370
|
-
|
352
|
+
config['services'].each do |name, service|
|
353
|
+
puts "🚀 #{name}_service will run at http://localhost:#{service['port']}"
|
354
|
+
end
|
355
|
+
|
356
|
+
puts "🧭 Service registry loaded from config/regolith.yml"
|
357
|
+
puts ""
|
358
|
+
|
359
|
+
exec_compose("up", "--build")
|
371
360
|
end
|
372
361
|
|
373
|
-
def
|
374
|
-
|
375
|
-
exec_compose('down', '-v')
|
362
|
+
def stop_all_services
|
363
|
+
exec_compose("down")
|
376
364
|
end
|
377
365
|
|
378
|
-
def restart_service(service_name
|
366
|
+
def restart_service(service_name)
|
379
367
|
if service_name
|
380
|
-
|
381
|
-
puts "🔄 Restarting service '#{service_name}'..."
|
382
|
-
exec_compose('restart', service_name)
|
368
|
+
exec_compose("restart", service_name)
|
383
369
|
else
|
384
|
-
|
385
|
-
exec_compose('restart')
|
370
|
+
exec_compose("restart")
|
386
371
|
end
|
387
372
|
end
|
388
373
|
|
389
|
-
def stop_service(service_name
|
390
|
-
|
391
|
-
|
392
|
-
puts "
|
393
|
-
|
394
|
-
else
|
395
|
-
puts "⏹ Stopping all services..."
|
396
|
-
exec_compose('stop')
|
374
|
+
def stop_service(service_name)
|
375
|
+
unless service_name
|
376
|
+
puts "❌ Error: Service name required"
|
377
|
+
puts "Usage: regolith stop <service_name>"
|
378
|
+
exit 1
|
397
379
|
end
|
380
|
+
exec_compose("stop", service_name)
|
398
381
|
end
|
399
382
|
|
400
383
|
def show_status
|
401
|
-
|
402
|
-
puts
|
384
|
+
config = load_regolith_config
|
403
385
|
|
404
|
-
# Try different format options for
|
386
|
+
# Try different format options for docker compose ps
|
405
387
|
success = system_compose('ps', '--format', 'table') ||
|
406
388
|
system_compose('ps', '--format', 'json') ||
|
407
389
|
system_compose('ps')
|
408
390
|
|
409
|
-
|
410
|
-
|
411
|
-
config
|
412
|
-
service_count = config['services'].size
|
391
|
+
if success && config['services'].any?
|
392
|
+
puts ""
|
393
|
+
puts "📋 Summary: #{config['services'].size} service#{'s' if config['services'].size != 1} configured"
|
413
394
|
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
|
395
|
+
puts "🚪 Ports: #{ports.join(', ')}"
|
431
396
|
end
|
397
|
+
|
398
|
+
exit($?.exitstatus) unless success
|
432
399
|
end
|
433
400
|
|
434
|
-
def show_logs(service_name
|
401
|
+
def show_logs(service_name, follow = false)
|
435
402
|
args = ['logs']
|
436
|
-
args << '--follow' if flags[:follow] || flags[:f]
|
437
403
|
args << service_name if service_name
|
438
|
-
|
404
|
+
args << '-f' if follow
|
439
405
|
exec_compose(*args)
|
440
406
|
end
|
441
407
|
|
442
|
-
def
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
408
|
+
def exec_in_service(service_name, command)
|
409
|
+
unless service_name
|
410
|
+
puts "❌ Error: Service name required"
|
411
|
+
puts "Usage: regolith exec <service_name> <command>"
|
412
|
+
exit 1
|
447
413
|
end
|
448
414
|
|
449
|
-
|
415
|
+
command = ['bash'] if command.empty?
|
416
|
+
exec_compose('exec', service_name, *command)
|
450
417
|
end
|
451
418
|
|
452
419
|
def shell_service(service_name)
|
@@ -455,24 +422,25 @@ module Regolith
|
|
455
422
|
puts "Usage: regolith shell <service_name>"
|
456
423
|
exit 1
|
457
424
|
end
|
458
|
-
|
459
|
-
ensure_service_exists!(service_name)
|
460
|
-
puts "🐚 Opening shell for #{service_name}_service..."
|
461
425
|
exec_compose('exec', service_name, 'bash')
|
462
426
|
end
|
463
427
|
|
464
|
-
# Rails integration commands
|
465
428
|
def rails_passthrough(service_name, rails_args)
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
puts "❌ Error: Rails command required"
|
470
|
-
puts "Usage: regolith rails <service> <command>"
|
429
|
+
unless service_name
|
430
|
+
puts "❌ Error: Service name required"
|
431
|
+
puts "Usage: regolith rails <service_name> <rails_command>"
|
471
432
|
exit 1
|
472
433
|
end
|
473
|
-
|
474
|
-
|
475
|
-
|
434
|
+
|
435
|
+
config = load_regolith_config
|
436
|
+
unless config['services'][service_name]
|
437
|
+
puts "❌ Error: Service '#{service_name}' not found"
|
438
|
+
exit 1
|
439
|
+
end
|
440
|
+
|
441
|
+
rails_args = ['--help'] if rails_args.empty?
|
442
|
+
exec_compose('exec', service_name, 'bash', '-lc',
|
443
|
+
"bundle exec rails #{Shellwords.join(rails_args)}")
|
476
444
|
end
|
477
445
|
|
478
446
|
def open_console(service_name)
|
@@ -482,11 +450,25 @@ module Regolith
|
|
482
450
|
exit 1
|
483
451
|
end
|
484
452
|
|
485
|
-
|
453
|
+
config = load_regolith_config
|
454
|
+
unless config['services'][service_name]
|
455
|
+
puts "❌ Error: Service '#{service_name}' not found"
|
456
|
+
exit 1
|
457
|
+
end
|
458
|
+
|
486
459
|
puts "🧪 Opening Rails console for #{service_name}_service..."
|
487
460
|
exec_compose('exec', service_name, 'rails', 'console')
|
488
461
|
end
|
489
462
|
|
463
|
+
def show_routes(service_name)
|
464
|
+
unless service_name
|
465
|
+
puts "❌ Error: Service name required"
|
466
|
+
puts "Usage: regolith routes <service_name>"
|
467
|
+
exit 1
|
468
|
+
end
|
469
|
+
rails_passthrough(service_name, ['routes'])
|
470
|
+
end
|
471
|
+
|
490
472
|
def open_service(service_name)
|
491
473
|
unless service_name
|
492
474
|
puts "❌ Error: Service name required"
|
@@ -494,296 +476,232 @@ module Regolith
|
|
494
476
|
exit 1
|
495
477
|
end
|
496
478
|
|
497
|
-
ensure_service_exists!(service_name)
|
498
479
|
config = load_regolith_config
|
499
|
-
|
500
|
-
|
501
|
-
|
480
|
+
service = config['services'][service_name]
|
481
|
+
unless service
|
482
|
+
puts "❌ Error: Service '#{service_name}' not found"
|
483
|
+
exit 1
|
484
|
+
end
|
485
|
+
|
486
|
+
url = "http://localhost:#{service['port']}"
|
502
487
|
puts "🌐 Opening #{url}..."
|
503
|
-
|
504
|
-
# Cross-platform open command
|
488
|
+
|
505
489
|
case RbConfig::CONFIG['host_os']
|
506
490
|
when /mswin|mingw|cygwin/
|
507
491
|
system(%{start "" "#{url}"})
|
508
492
|
when /darwin/
|
509
|
-
system("open
|
493
|
+
system("open", url)
|
494
|
+
when /linux|bsd/
|
495
|
+
system("xdg-open", url)
|
510
496
|
else
|
511
|
-
|
497
|
+
puts "Please open #{url} in your browser"
|
512
498
|
end
|
513
499
|
end
|
514
500
|
|
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
|
501
|
+
def db_command(command, service_or_flag)
|
502
|
+
db_action = command.split(':').last
|
503
|
+
|
504
|
+
if service_or_flag == '--all'
|
505
|
+
config = load_regolith_config
|
506
|
+
config['services'].each do |service_name, _|
|
507
|
+
puts "🗄️ Running db:#{db_action} for #{service_name}..."
|
508
|
+
run_single_db_command(service_name, db_action)
|
527
509
|
end
|
510
|
+
elsif service_or_flag
|
511
|
+
run_single_db_command(service_or_flag, db_action)
|
528
512
|
else
|
529
|
-
puts "❌
|
530
|
-
puts "
|
513
|
+
puts "❌ Error: Service name or --all flag required"
|
514
|
+
puts "Usage: regolith db:#{db_action} <service_name|--all>"
|
531
515
|
exit 1
|
532
516
|
end
|
533
517
|
end
|
534
518
|
|
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
|
519
|
+
def run_single_db_command(service_name, action)
|
520
|
+
config = load_regolith_config
|
521
|
+
unless config['services'][service_name]
|
522
|
+
puts "❌ Error: Service '#{service_name}' not found"
|
523
|
+
return false
|
545
524
|
end
|
546
|
-
|
547
|
-
|
525
|
+
|
526
|
+
success = system_compose('exec', service_name, 'rails', "db:#{action}")
|
527
|
+
unless success
|
528
|
+
puts "❌ db:#{action} failed for #{service_name} (exit code: #{$?.exitstatus})"
|
529
|
+
return false
|
530
|
+
end
|
531
|
+
true
|
548
532
|
end
|
549
533
|
|
550
|
-
def
|
551
|
-
|
552
|
-
|
553
|
-
|
534
|
+
def run_tests(service_or_flag)
|
535
|
+
if service_or_flag == '--all'
|
536
|
+
config = load_regolith_config
|
537
|
+
all_passed = true
|
538
|
+
config['services'].each do |service_name, _|
|
539
|
+
puts "🧪 Running tests for #{service_name}..."
|
540
|
+
unless run_single_test(service_name)
|
541
|
+
all_passed = false
|
542
|
+
end
|
543
|
+
end
|
544
|
+
exit(1) unless all_passed
|
545
|
+
elsif service_or_flag
|
546
|
+
exit(1) unless run_single_test(service_or_flag)
|
547
|
+
else
|
548
|
+
puts "❌ Error: Service name or --all flag required"
|
549
|
+
puts "Usage: regolith test <service_name|--all>"
|
554
550
|
exit 1
|
555
551
|
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
552
|
end
|
561
553
|
|
562
|
-
|
563
|
-
def health_check
|
564
|
-
puts "🔍 Health Check Results:"
|
565
|
-
puts
|
566
|
-
|
554
|
+
def run_single_test(service_name)
|
567
555
|
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
|
556
|
+
unless config['services'][service_name]
|
557
|
+
puts "❌ Error: Service '#{service_name}' not found"
|
558
|
+
return false
|
583
559
|
end
|
584
|
-
end
|
585
560
|
|
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' }
|
561
|
+
success = system_compose('exec', service_name, 'rails', 'test')
|
562
|
+
unless success
|
563
|
+
puts "❌ Tests failed for #{service_name} (exit code: #{$?.exitstatus})"
|
564
|
+
return false
|
604
565
|
end
|
566
|
+
true
|
605
567
|
end
|
606
568
|
|
607
|
-
|
608
|
-
def show_config(flags = {})
|
569
|
+
def check_health
|
609
570
|
config = load_regolith_config
|
571
|
+
puts "🔍 Health Check Results:"
|
610
572
|
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
573
|
+
all_healthy = true
|
574
|
+
config['services'].each do |service_name, service|
|
575
|
+
health_data = check_service_health(service_name, service['port'])
|
576
|
+
if health_data
|
577
|
+
puts "✅ #{service_name} (port #{service['port']}) - healthy"
|
578
|
+
puts " Version: #{health_data['version']}" if health_data['version']
|
579
|
+
puts " Last seen: #{Time.parse(health_data['time']).strftime('%H:%M:%S')}" if health_data['time']
|
580
|
+
else
|
581
|
+
puts "❌ #{service_name} (port #{service['port']}) - unreachable"
|
582
|
+
all_healthy = false
|
583
|
+
end
|
617
584
|
end
|
585
|
+
|
586
|
+
exit(1) unless all_healthy
|
618
587
|
end
|
619
588
|
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
589
|
+
def check_service_health(service_name, port)
|
590
|
+
require_relative 'service_client'
|
591
|
+
response = Regolith::ServiceClient.health(service_name)
|
592
|
+
Regolith::ServiceClient.parsed_body(response) if response.code.to_i == 200
|
593
|
+
rescue => e
|
594
|
+
nil
|
626
595
|
end
|
627
596
|
|
628
|
-
def
|
629
|
-
|
630
|
-
|
631
|
-
puts
|
632
|
-
exec_compose('build', '--no-cache', service_name)
|
597
|
+
def show_config(json_format = false)
|
598
|
+
config = load_regolith_config
|
599
|
+
if json_format
|
600
|
+
puts JSON.pretty_generate(config)
|
633
601
|
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]}"
|
602
|
+
puts YAML.dump(config)
|
655
603
|
end
|
656
|
-
|
657
|
-
puts
|
658
|
-
check_regolith_config
|
659
604
|
end
|
660
605
|
|
661
|
-
def
|
662
|
-
|
606
|
+
def inspect_config(json_format = false)
|
607
|
+
config = load_regolith_config
|
663
608
|
|
664
|
-
if
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
609
|
+
if json_format
|
610
|
+
inspection = {
|
611
|
+
endpoints: {},
|
612
|
+
configuration: config
|
613
|
+
}
|
614
|
+
config['services'].each do |name, service|
|
615
|
+
inspection[:endpoints][name] = "http://localhost:#{service['port']}"
|
671
616
|
end
|
617
|
+
puts JSON.pretty_generate(inspection)
|
672
618
|
else
|
673
|
-
puts "
|
619
|
+
puts "📊 Service Endpoints:"
|
620
|
+
config['services'].each do |name, service|
|
621
|
+
puts " #{name}: http://localhost:#{service['port']}"
|
622
|
+
end
|
623
|
+
puts ""
|
624
|
+
puts "📋 Full Configuration:"
|
625
|
+
puts YAML.dump(config)
|
674
626
|
end
|
675
627
|
end
|
676
628
|
|
677
|
-
def
|
678
|
-
puts "🔍 Regolith
|
679
|
-
puts "
|
680
|
-
|
681
|
-
config = load_regolith_config
|
629
|
+
def run_diagnostics
|
630
|
+
puts "🔍 Regolith System Diagnostics"
|
631
|
+
puts ""
|
682
632
|
|
683
|
-
|
684
|
-
|
685
|
-
|
633
|
+
# Check Docker
|
634
|
+
if system("docker --version > /dev/null 2>&1")
|
635
|
+
puts "✅ Docker is available"
|
636
|
+
else
|
637
|
+
puts "❌ Docker is not available"
|
686
638
|
end
|
687
639
|
|
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)
|
640
|
+
# Check Docker Compose
|
641
|
+
if system("docker compose version > /dev/null 2>&1") ||
|
642
|
+
system("docker-compose --version > /dev/null 2>&1")
|
643
|
+
puts "✅ Docker Compose is available"
|
702
644
|
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)
|
645
|
+
puts "❌ Docker Compose is not available"
|
712
646
|
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]
|
647
|
+
|
648
|
+
# Check Rails
|
649
|
+
if system("rails --version > /dev/null 2>&1")
|
650
|
+
puts "✅ Rails is available"
|
726
651
|
else
|
727
|
-
puts "❌
|
728
|
-
exit 1
|
652
|
+
puts "❌ Rails is not available"
|
729
653
|
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
654
|
|
749
|
-
|
750
|
-
|
751
|
-
|
655
|
+
# Check Ruby version
|
656
|
+
puts "✅ Ruby #{RUBY_VERSION}"
|
657
|
+
|
658
|
+
# Check Regolith config
|
659
|
+
if find_regolith_config
|
660
|
+
puts "✅ Regolith configuration found"
|
661
|
+
config = load_regolith_config
|
662
|
+
puts " #{config['services'].size} service(s) configured"
|
663
|
+
else
|
664
|
+
puts "⚠️ No Regolith configuration found (not in a Regolith app directory)"
|
752
665
|
end
|
753
666
|
end
|
754
667
|
|
755
|
-
def
|
756
|
-
|
757
|
-
|
758
|
-
puts "❌ Service '#{service_name}' not found"
|
759
|
-
puts "Available services: #{config['services'].keys.join(', ')}"
|
760
|
-
exit 1
|
761
|
-
end
|
668
|
+
def prune_docker
|
669
|
+
puts "🧹 Cleaning up Docker resources..."
|
670
|
+
system("docker system prune -f")
|
762
671
|
end
|
763
672
|
|
764
|
-
def
|
765
|
-
|
766
|
-
puts "
|
673
|
+
def rebuild_service(service_name)
|
674
|
+
if service_name
|
675
|
+
puts "🔨 Rebuilding #{service_name}..."
|
676
|
+
exec_compose("build", "--no-cache", service_name)
|
677
|
+
else
|
678
|
+
puts "🔨 Rebuilding all services..."
|
679
|
+
exec_compose("build", "--no-cache")
|
767
680
|
end
|
768
|
-
|
769
|
-
puts "🧭 Service registry: config/regolith.yml"
|
770
|
-
puts
|
771
681
|
end
|
772
682
|
|
773
683
|
def load_regolith_config
|
774
|
-
config_path =
|
775
|
-
return { 'services' => {} } unless config_path
|
684
|
+
config_path = find_regolith_config
|
685
|
+
return { 'services' => {} } unless config_path
|
776
686
|
|
777
|
-
# Use safe YAML loading
|
778
687
|
config = Psych.safe_load(File.read(config_path), permitted_classes: [], aliases: false) || {}
|
779
688
|
config['services'] ||= {}
|
780
689
|
config
|
781
690
|
end
|
782
691
|
|
692
|
+
def find_regolith_config
|
693
|
+
current_dir = Dir.pwd
|
694
|
+
while current_dir != '/'
|
695
|
+
config_path = File.join(current_dir, 'config', 'regolith.yml')
|
696
|
+
return config_path if File.exist?(config_path)
|
697
|
+
current_dir = File.dirname(current_dir)
|
698
|
+
end
|
699
|
+
nil
|
700
|
+
end
|
701
|
+
|
783
702
|
def save_regolith_config(config)
|
784
703
|
FileUtils.mkdir_p('config')
|
785
704
|
File.write('config/regolith.yml', YAML.dump(config))
|
786
|
-
Regolith.reload_service_registry!
|
787
705
|
end
|
788
706
|
|
789
707
|
def update_docker_compose(config)
|
@@ -791,95 +709,82 @@ module Regolith
|
|
791
709
|
File.write('docker-compose.yml', docker_compose)
|
792
710
|
end
|
793
711
|
|
794
|
-
# File generators
|
795
712
|
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
|
713
|
+
template = <<~YAML
|
714
|
+
version: '3.8'
|
715
|
+
|
716
|
+
networks:
|
717
|
+
regolith:
|
718
|
+
|
719
|
+
services:
|
720
|
+
db:
|
721
|
+
image: postgres:14
|
722
|
+
environment:
|
723
|
+
POSTGRES_DB: #{app_name}_development
|
724
|
+
POSTGRES_USER: postgres
|
725
|
+
POSTGRES_PASSWORD: password
|
726
|
+
ports:
|
727
|
+
- "5432:5432"
|
728
|
+
volumes:
|
729
|
+
- postgres_data:/var/lib/postgresql/data
|
730
|
+
networks:
|
731
|
+
- regolith
|
732
|
+
healthcheck:
|
733
|
+
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
734
|
+
interval: 30s
|
735
|
+
timeout: 10s
|
736
|
+
retries: 3
|
737
|
+
|
738
|
+
<% services.each do |name, service| %>
|
739
|
+
<%= name %>:
|
740
|
+
build:
|
741
|
+
context: <%= service['root'] %>
|
742
|
+
args:
|
743
|
+
BUILD_ENV: development
|
744
|
+
ports:
|
745
|
+
- "<%= service['port'] %>:3000"
|
746
|
+
depends_on:
|
747
|
+
db:
|
748
|
+
condition: service_healthy
|
749
|
+
environment:
|
750
|
+
DATABASE_URL: postgres://postgres:password@db:5432/<%= app_name %>_development
|
751
|
+
REGOLITH_SERVICE_NAME: <%= name %>
|
752
|
+
REGOLITH_SERVICE_PORT: <%= service['port'] %>
|
753
|
+
volumes:
|
754
|
+
- <%= service['root'] %>:/app
|
755
|
+
networks:
|
756
|
+
- regolith
|
757
|
+
command: bash -c "rm -f tmp/pids/server.pid && bundle install && rails db:prepare && rails server -b 0.0.0.0"
|
758
|
+
healthcheck:
|
759
|
+
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
760
|
+
interval: 30s
|
761
|
+
timeout: 10s
|
762
|
+
retries: 3
|
763
|
+
<% end %>
|
764
|
+
|
765
|
+
volumes:
|
766
|
+
postgres_data:
|
767
|
+
YAML
|
768
|
+
|
769
|
+
ERB.new(template).result(binding)
|
858
770
|
end
|
859
771
|
|
860
772
|
def generate_dockerfile
|
861
773
|
<<~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/*
|
774
|
+
FROM ruby:3.1
|
869
775
|
|
870
776
|
WORKDIR /app
|
871
777
|
|
872
|
-
|
778
|
+
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client libyaml-dev libsqlite3-dev build-essential pkg-config curl
|
779
|
+
|
873
780
|
COPY Gemfile Gemfile.lock* ./
|
874
781
|
|
875
|
-
# Conditional bundler config for dev vs prod
|
876
782
|
ARG BUILD_ENV=development
|
877
783
|
RUN if [ "$BUILD_ENV" = "production" ]; then \\
|
878
784
|
bundle config set --local deployment 'true' && \\
|
879
785
|
bundle config set --local without 'development test'; \\
|
880
786
|
fi && bundle install
|
881
787
|
|
882
|
-
# Copy application code
|
883
788
|
COPY . .
|
884
789
|
|
885
790
|
EXPOSE 3000
|
@@ -891,7 +796,7 @@ module Regolith
|
|
891
796
|
def generate_regolith_initializer(service_name)
|
892
797
|
<<~RUBY
|
893
798
|
require 'ostruct'
|
894
|
-
|
799
|
+
|
895
800
|
# Regolith service configuration
|
896
801
|
Rails.application.configure do
|
897
802
|
config.regolith = OpenStruct.new(
|
@@ -901,7 +806,6 @@ module Regolith
|
|
901
806
|
)
|
902
807
|
end
|
903
808
|
|
904
|
-
# Load service registry if available
|
905
809
|
if File.exist?(Rails.application.config.regolith.service_registry)
|
906
810
|
REGOLITH_SERVICES = YAML.load_file(Rails.application.config.regolith.service_registry)['services'] || {}
|
907
811
|
else
|
@@ -910,184 +814,22 @@ module Regolith
|
|
910
814
|
RUBY
|
911
815
|
end
|
912
816
|
|
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
817
|
def generate_makefile
|
1041
818
|
<<~MAKEFILE
|
1042
|
-
.PHONY: server
|
1043
|
-
|
1044
|
-
# Start services
|
1045
|
-
server up:
|
1046
|
-
regolith server
|
1047
|
-
|
1048
|
-
# Stop services
|
1049
|
-
down:
|
1050
|
-
regolith down
|
819
|
+
.PHONY: server console build clean
|
1051
820
|
|
1052
|
-
|
1053
|
-
|
1054
|
-
regolith restart
|
821
|
+
server:
|
822
|
+
\tregolith server
|
1055
823
|
|
1056
|
-
# View logs
|
1057
|
-
logs:
|
1058
|
-
regolith logs -f
|
1059
|
-
|
1060
|
-
# Open console (usage: make console SERVICE=users)
|
1061
824
|
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
|
825
|
+
\tregolith console
|
1072
826
|
|
1073
|
-
|
1074
|
-
|
1075
|
-
regolith doctor
|
827
|
+
build:
|
828
|
+
\tdocker-compose build
|
1076
829
|
|
1077
|
-
# Database operations
|
1078
|
-
db-migrate:
|
1079
|
-
regolith db:migrate --all
|
1080
|
-
|
1081
|
-
db-setup:
|
1082
|
-
regolith db:setup --all
|
1083
|
-
|
1084
|
-
# Cleanup
|
1085
830
|
clean:
|
1086
|
-
|
1087
|
-
|
1088
|
-
# Shortcuts
|
1089
|
-
dev: up
|
1090
|
-
stop: down
|
831
|
+
\tdocker-compose down -v
|
832
|
+
\tdocker system prune -f
|
1091
833
|
MAKEFILE
|
1092
834
|
end
|
1093
835
|
|
@@ -1098,64 +840,82 @@ module Regolith
|
|
1098
840
|
RUBY
|
1099
841
|
end
|
1100
842
|
|
843
|
+
def exec_compose(*args)
|
844
|
+
if system("docker compose version > /dev/null 2>&1")
|
845
|
+
exec("docker", "compose", *args)
|
846
|
+
elsif system("docker-compose --version > /dev/null 2>&1")
|
847
|
+
exec("docker-compose", *args)
|
848
|
+
else
|
849
|
+
puts "❌ Error: Neither 'docker compose' nor 'docker-compose' is available"
|
850
|
+
exit 1
|
851
|
+
end
|
852
|
+
end
|
853
|
+
|
854
|
+
def system_compose(*args)
|
855
|
+
if system("docker compose version > /dev/null 2>&1")
|
856
|
+
system("docker", "compose", *args)
|
857
|
+
elsif system("docker-compose --version > /dev/null 2>&1")
|
858
|
+
system("docker-compose", *args)
|
859
|
+
else
|
860
|
+
puts "❌ Error: Neither 'docker compose' nor 'docker-compose' is available"
|
861
|
+
false
|
862
|
+
end
|
863
|
+
end
|
864
|
+
|
1101
865
|
def show_help
|
1102
866
|
puts <<~HELP
|
1103
|
-
Regolith #{Regolith::VERSION} - Rails
|
867
|
+
Regolith #{Regolith::VERSION} - Rails Microservices Framework
|
1104
868
|
|
1105
869
|
USAGE:
|
1106
870
|
regolith <command> [options]
|
1107
871
|
|
1108
|
-
PROJECT
|
872
|
+
PROJECT MANAGEMENT:
|
1109
873
|
new <app_name> Create a new Regolith application
|
1110
874
|
generate service <name> Generate a new microservice
|
1111
875
|
|
1112
876
|
SERVICE MANAGEMENT:
|
1113
877
|
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
|
878
|
+
down Stop all services
|
879
|
+
restart [service] Restart all services or specific service
|
880
|
+
stop <service> Stop a specific service
|
881
|
+
ps, status Show service status
|
882
|
+
logs [service] [-f] Show logs (optionally follow)
|
883
|
+
exec <service> <command> Execute command in service container
|
884
|
+
shell <service> Open bash shell in service
|
1122
885
|
|
1123
886
|
RAILS INTEGRATION:
|
887
|
+
rails <service> <command> Run Rails command in service
|
1124
888
|
console <service> Open Rails console for service
|
1125
|
-
rails <service> <cmd> Run Rails command in service
|
1126
889
|
routes <service> Show routes for service
|
1127
|
-
open <service> Open service in browser
|
890
|
+
open <service> Open service URL in browser
|
1128
891
|
|
1129
892
|
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]
|
893
|
+
db:create [service|--all] Create databases
|
894
|
+
db:migrate [service|--all] Run migrations
|
895
|
+
db:seed [service|--all] Seed databases
|
896
|
+
db:reset [service|--all] Reset databases
|
897
|
+
db:setup [service|--all] Setup databases
|
898
|
+
db:drop [service|--all] Drop databases
|
1136
899
|
|
1137
900
|
TESTING & HEALTH:
|
1138
901
|
test [service|--all] Run tests
|
1139
|
-
health
|
902
|
+
health Check service health
|
1140
903
|
|
1141
904
|
UTILITIES:
|
1142
|
-
config [--json] Show
|
1143
|
-
inspect [--json] Show
|
1144
|
-
prune Clean up Docker system
|
905
|
+
config [--json] Show configuration
|
906
|
+
inspect [--json] Show endpoints and config
|
1145
907
|
doctor Run system diagnostics
|
1146
|
-
|
908
|
+
prune Clean up Docker resources
|
909
|
+
rebuild [service] Rebuild service images
|
910
|
+
version Show version information
|
1147
911
|
|
1148
912
|
EXAMPLES:
|
1149
913
|
regolith new marketplace
|
1150
914
|
regolith generate service products
|
1151
915
|
regolith server
|
1152
916
|
regolith rails products db:migrate
|
1153
|
-
regolith routes products
|
1154
917
|
regolith open products
|
1155
|
-
regolith
|
1156
|
-
regolith inspect --json
|
1157
|
-
regolith config --json | jq '.services'
|
1158
|
-
regolith test --all
|
918
|
+
regolith health
|
1159
919
|
|
1160
920
|
Get started:
|
1161
921
|
regolith new myapp
|
@@ -1165,4 +925,4 @@ module Regolith
|
|
1165
925
|
HELP
|
1166
926
|
end
|
1167
927
|
end
|
1168
|
-
end
|
928
|
+
end
|