discharger 0.2.10 → 0.2.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/README.md +10 -0
- data/Rakefile +3 -1
- data/lib/discharger/setup_runner/command_factory.rb +56 -0
- data/lib/discharger/setup_runner/command_registry.rb +62 -0
- data/lib/discharger/setup_runner/commands/asdf_command.rb +61 -0
- data/lib/discharger/setup_runner/commands/base_command.rb +185 -0
- data/lib/discharger/setup_runner/commands/brew_command.rb +26 -0
- data/lib/discharger/setup_runner/commands/bundler_command.rb +25 -0
- data/lib/discharger/setup_runner/commands/config_command.rb +51 -0
- data/lib/discharger/setup_runner/commands/custom_command.rb +41 -0
- data/lib/discharger/setup_runner/commands/database_command.rb +111 -0
- data/lib/discharger/setup_runner/commands/docker_command.rb +100 -0
- data/lib/discharger/setup_runner/commands/env_command.rb +42 -0
- data/lib/discharger/setup_runner/commands/git_command.rb +45 -0
- data/lib/discharger/setup_runner/commands/yarn_command.rb +45 -0
- data/lib/discharger/setup_runner/condition_evaluator.rb +130 -0
- data/lib/discharger/setup_runner/configuration.rb +71 -0
- data/lib/discharger/setup_runner/runner.rb +111 -0
- data/lib/discharger/setup_runner/version.rb +7 -0
- data/lib/discharger/setup_runner.rb +55 -0
- data/lib/discharger/version.rb +1 -1
- data/lib/discharger.rb +1 -0
- data/lib/generators/discharger/install/install_generator.rb +14 -0
- data/lib/generators/discharger/install/templates/setup +36 -0
- data/lib/generators/discharger/install/templates/setup.yml +60 -0
- metadata +48 -2
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base_command"
|
4
|
+
|
5
|
+
module Discharger
|
6
|
+
module SetupRunner
|
7
|
+
module Commands
|
8
|
+
class DockerCommand < BaseCommand
|
9
|
+
def execute
|
10
|
+
log "Ensure Docker is running"
|
11
|
+
|
12
|
+
unless system_quiet("docker info > /dev/null 2>&1")
|
13
|
+
log "Starting Docker..."
|
14
|
+
system_quiet("open -a Docker")
|
15
|
+
sleep 10
|
16
|
+
unless system_quiet("docker info > /dev/null 2>&1")
|
17
|
+
log "Docker is not running. Please start Docker manually."
|
18
|
+
return
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Setup database container if configured
|
23
|
+
if config.respond_to?(:database) && config.database
|
24
|
+
setup_container(
|
25
|
+
name: config.database.name || "db-app",
|
26
|
+
port: config.database.port || 5432,
|
27
|
+
image: "postgres:#{config.database.version || "14"}",
|
28
|
+
env: {"POSTGRES_PASSWORD" => config.database.password || "postgres"},
|
29
|
+
volume: "#{config.database.name || "db-app"}:/var/lib/postgresql/data",
|
30
|
+
internal_port: 5432
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Setup Redis container if configured
|
35
|
+
if config.respond_to?(:redis) && config.redis
|
36
|
+
setup_container(
|
37
|
+
name: config.redis.name || "redis-app",
|
38
|
+
port: config.redis.port || 6379,
|
39
|
+
image: "redis:#{config.redis.version || "latest"}",
|
40
|
+
internal_port: 6379
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def can_execute?
|
46
|
+
# Only execute if Docker is available and containers are configured
|
47
|
+
system_quiet("which docker") && (
|
48
|
+
(config.respond_to?(:database) && config.database) ||
|
49
|
+
(config.respond_to?(:redis) && config.redis)
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def description
|
54
|
+
"Setup Docker containers"
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def setup_container(name:, port:, image:, internal_port:, env: {}, volume: nil)
|
60
|
+
log "Checking #{name} container"
|
61
|
+
|
62
|
+
if system_quiet("docker ps | grep #{name} > /dev/null 2>&1")
|
63
|
+
log "#{name} container is already running"
|
64
|
+
return
|
65
|
+
end
|
66
|
+
|
67
|
+
# Check if container exists but is stopped
|
68
|
+
if system_quiet("docker inspect #{name} > /dev/null 2>&1")
|
69
|
+
log "Starting existing #{name} container"
|
70
|
+
unless system_quiet("docker start #{name}")
|
71
|
+
log "Removing failed #{name} container"
|
72
|
+
system_quiet("docker rm -f #{name}")
|
73
|
+
create_container(name: name, port: port, image: image, env: env, volume: volume, internal_port: internal_port)
|
74
|
+
end
|
75
|
+
else
|
76
|
+
create_container(name: name, port: port, image: image, env: env, volume: volume, internal_port: internal_port)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Verify container is running
|
80
|
+
sleep 2
|
81
|
+
unless system_quiet("docker ps | grep #{name} > /dev/null 2>&1")
|
82
|
+
log "#{name} container failed to start"
|
83
|
+
raise "#{name} container failed to start"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def create_container(name:, port:, image:, internal_port:, env: {}, volume: nil)
|
88
|
+
log "Creating new #{name} container"
|
89
|
+
|
90
|
+
cmd = ["docker", "run", "-d", "--name", name, "-p", "#{port}:#{internal_port}"]
|
91
|
+
env.each { |k, v| cmd.push("-e", "#{k}=#{v}") }
|
92
|
+
cmd.push("-v", volume) if volume
|
93
|
+
cmd.push(image)
|
94
|
+
|
95
|
+
system!(*cmd)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
require_relative "base_command"
|
5
|
+
|
6
|
+
module Discharger
|
7
|
+
module SetupRunner
|
8
|
+
module Commands
|
9
|
+
class EnvCommand < BaseCommand
|
10
|
+
def execute
|
11
|
+
if File.exist?(".env")
|
12
|
+
unless ENV["QUIET_SETUP"] || ENV["DISABLE_OUTPUT"]
|
13
|
+
require "rainbow"
|
14
|
+
puts Rainbow(" → .env file already exists. Skipping.").yellow
|
15
|
+
end
|
16
|
+
return
|
17
|
+
end
|
18
|
+
|
19
|
+
unless File.exist?(".env.example")
|
20
|
+
unless ENV["QUIET_SETUP"] || ENV["DISABLE_OUTPUT"]
|
21
|
+
require "rainbow"
|
22
|
+
puts Rainbow(" → WARNING: .env.example not found. Skipping .env creation").yellow
|
23
|
+
end
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
simple_action("Creating .env from .env.example") do
|
28
|
+
FileUtils.cp(".env.example", ".env")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def can_execute?
|
33
|
+
File.exist?(".env.example")
|
34
|
+
end
|
35
|
+
|
36
|
+
def description
|
37
|
+
"Setup environment file"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base_command"
|
4
|
+
|
5
|
+
module Discharger
|
6
|
+
module SetupRunner
|
7
|
+
module Commands
|
8
|
+
class GitCommand < BaseCommand
|
9
|
+
def execute
|
10
|
+
log "Setting up git configuration"
|
11
|
+
|
12
|
+
# Set up commit template if it exists
|
13
|
+
commit_template = File.join(app_root, ".commit-template")
|
14
|
+
if File.exist?(commit_template)
|
15
|
+
system! "git config --local commit.template .commit-template"
|
16
|
+
log "Git commit template configured"
|
17
|
+
end
|
18
|
+
|
19
|
+
# Set up git hooks if .githooks directory exists
|
20
|
+
githooks_dir = File.join(app_root, ".githooks")
|
21
|
+
if File.directory?(githooks_dir)
|
22
|
+
system! "git config --local core.hooksPath .githooks"
|
23
|
+
log "Git hooks path configured"
|
24
|
+
end
|
25
|
+
|
26
|
+
# Any other git config from the setup.yml
|
27
|
+
if config.respond_to?(:git_config) && config.git_config
|
28
|
+
config.git_config.each do |key, value|
|
29
|
+
system! "git config --local #{key} '#{value}'"
|
30
|
+
log "Set git config #{key}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def can_execute?
|
36
|
+
File.directory?(File.join(app_root, ".git"))
|
37
|
+
end
|
38
|
+
|
39
|
+
def description
|
40
|
+
"Setup git configuration"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base_command"
|
4
|
+
|
5
|
+
module Discharger
|
6
|
+
module SetupRunner
|
7
|
+
module Commands
|
8
|
+
class YarnCommand < BaseCommand
|
9
|
+
def execute
|
10
|
+
log "Installing Node modules"
|
11
|
+
|
12
|
+
# Enable corepack if yarn.lock exists (Yarn 2+)
|
13
|
+
if File.exist?(File.join(app_root, "yarn.lock"))
|
14
|
+
if system_quiet("which corepack")
|
15
|
+
system! "corepack enable"
|
16
|
+
system! "corepack use yarn@stable"
|
17
|
+
end
|
18
|
+
|
19
|
+
# Install dependencies
|
20
|
+
system_quiet("yarn check --check-files > /dev/null 2>&1") || system!("yarn install")
|
21
|
+
elsif File.exist?(File.join(app_root, "package-lock.json"))
|
22
|
+
# NPM project
|
23
|
+
log "Found package-lock.json, using npm"
|
24
|
+
system! "npm ci"
|
25
|
+
elsif File.exist?(File.join(app_root, "package.json"))
|
26
|
+
# Generic package.json - try yarn first, fall back to npm
|
27
|
+
if system_quiet("which yarn")
|
28
|
+
system! "yarn install"
|
29
|
+
else
|
30
|
+
system! "npm install"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def can_execute?
|
36
|
+
File.exist?(File.join(app_root, "package.json"))
|
37
|
+
end
|
38
|
+
|
39
|
+
def description
|
40
|
+
"Install JavaScript dependencies"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "prism"
|
4
|
+
|
5
|
+
module Discharger
|
6
|
+
module SetupRunner
|
7
|
+
class ConditionEvaluator
|
8
|
+
class << self
|
9
|
+
def evaluate(condition, context = {})
|
10
|
+
return true if condition.nil? || condition.strip.empty?
|
11
|
+
ast = Prism.parse(condition).value
|
12
|
+
raise "Parse error" unless ast
|
13
|
+
evaluate_node(ast)
|
14
|
+
rescue => e
|
15
|
+
log_warning("Condition evaluation failed: #{e.message}")
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def evaluate_node(node)
|
22
|
+
case node.type
|
23
|
+
when :program_node
|
24
|
+
# Evaluate the first statement in the program
|
25
|
+
stmts = node.statements
|
26
|
+
if stmts&.body&.any?
|
27
|
+
evaluate_node(stmts.body.first)
|
28
|
+
else
|
29
|
+
true
|
30
|
+
end
|
31
|
+
when :statements_node
|
32
|
+
# Evaluate the first statement
|
33
|
+
if node.body.any?
|
34
|
+
evaluate_node(node.body.first)
|
35
|
+
else
|
36
|
+
true
|
37
|
+
end
|
38
|
+
when :and_node
|
39
|
+
left = evaluate_node(node.left)
|
40
|
+
right = evaluate_node(node.right)
|
41
|
+
left && right
|
42
|
+
when :or_node
|
43
|
+
left = evaluate_node(node.left)
|
44
|
+
right = evaluate_node(node.right)
|
45
|
+
left || right
|
46
|
+
when :call_node
|
47
|
+
# Handle method calls
|
48
|
+
if node.receiver&.type == :constant_read_node
|
49
|
+
case node.receiver.name
|
50
|
+
when :ENV
|
51
|
+
if node.name == :[]
|
52
|
+
ENV[evaluate_node(node.arguments.arguments.first)]
|
53
|
+
else
|
54
|
+
raise "Unsafe ENV method: #{node.name}"
|
55
|
+
end
|
56
|
+
when :File
|
57
|
+
case node.name
|
58
|
+
when :exist?
|
59
|
+
File.exist?(evaluate_node(node.arguments.arguments.first))
|
60
|
+
when :directory?
|
61
|
+
File.directory?(evaluate_node(node.arguments.arguments.first))
|
62
|
+
when :file?
|
63
|
+
File.file?(evaluate_node(node.arguments.arguments.first))
|
64
|
+
else
|
65
|
+
raise "Unsafe File method: #{node.name}"
|
66
|
+
end
|
67
|
+
when :Dir
|
68
|
+
if node.name == :exist?
|
69
|
+
Dir.exist?(evaluate_node(node.arguments.arguments.first))
|
70
|
+
else
|
71
|
+
raise "Unsafe Dir method: #{node.name}"
|
72
|
+
end
|
73
|
+
else
|
74
|
+
raise "Unsafe method call: #{node.receiver.name}.#{node.name}"
|
75
|
+
end
|
76
|
+
elsif node.receiver&.type == :call_node
|
77
|
+
# Handle chained calls like ENV['FOO'] == 'bar'
|
78
|
+
if node.name == :==
|
79
|
+
left = evaluate_node(node.receiver)
|
80
|
+
right = evaluate_node(node.arguments.arguments.first)
|
81
|
+
left == right
|
82
|
+
elsif node.name == :!=
|
83
|
+
left = evaluate_node(node.receiver)
|
84
|
+
right = evaluate_node(node.arguments.arguments.first)
|
85
|
+
left != right
|
86
|
+
else
|
87
|
+
raise "Unsafe operator: #{node.name}"
|
88
|
+
end
|
89
|
+
elsif node.receiver.nil?
|
90
|
+
# Method call without receiver (like system)
|
91
|
+
raise "Unsafe method call: #{node.name}"
|
92
|
+
else
|
93
|
+
raise "Unsafe method call: #{node.receiver&.name}.#{node.name}"
|
94
|
+
end
|
95
|
+
when :constant_read_node
|
96
|
+
node.name
|
97
|
+
when :string_node
|
98
|
+
node.unescaped
|
99
|
+
when :true_node # standard:disable Lint/BooleanSymbol
|
100
|
+
true
|
101
|
+
when :false_node # standard:disable Lint/BooleanSymbol
|
102
|
+
false
|
103
|
+
when :array_node
|
104
|
+
node.elements.map { |el| evaluate_node(el) }
|
105
|
+
when :symbol_node
|
106
|
+
node.unescaped
|
107
|
+
when :integer_node
|
108
|
+
node.value
|
109
|
+
when :x_string_node
|
110
|
+
# Backtick commands - block for security
|
111
|
+
raise "Unsafe backtick command"
|
112
|
+
when :parentheses_node
|
113
|
+
# Evaluate the expression inside parentheses
|
114
|
+
evaluate_node(node.body)
|
115
|
+
else
|
116
|
+
raise "Unsafe node: #{node.type}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def log_warning(message)
|
121
|
+
if defined?(Rails)
|
122
|
+
Rails.logger.warn(message)
|
123
|
+
else
|
124
|
+
warn("[SetupRunner] #{message}")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
|
5
|
+
module Discharger
|
6
|
+
module SetupRunner
|
7
|
+
class Configuration
|
8
|
+
attr_accessor :app_name, :db_config, :redis_config, :services, :steps, :custom_steps
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@app_name = "Application"
|
12
|
+
@db_config = DatabaseConfig.new
|
13
|
+
@redis_config = RedisConfig.new
|
14
|
+
@services = []
|
15
|
+
@steps = []
|
16
|
+
@custom_steps = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.from_file(path)
|
20
|
+
config = new
|
21
|
+
yaml = YAML.load_file(path)
|
22
|
+
|
23
|
+
# Handle empty YAML files
|
24
|
+
return config if yaml.nil? || yaml == false
|
25
|
+
|
26
|
+
config.app_name = yaml["app_name"] if yaml["app_name"]
|
27
|
+
config.db_config.from_hash(yaml["database"]) if yaml["database"]
|
28
|
+
config.redis_config.from_hash(yaml["redis"]) if yaml["redis"]
|
29
|
+
config.services = yaml["services"] || []
|
30
|
+
config.steps = yaml["steps"] || []
|
31
|
+
config.custom_steps = yaml["custom_steps"] || []
|
32
|
+
|
33
|
+
config
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class DatabaseConfig
|
38
|
+
attr_accessor :port, :name, :version, :password
|
39
|
+
|
40
|
+
def initialize
|
41
|
+
@port = 5432
|
42
|
+
@name = "db-app"
|
43
|
+
@version = "14"
|
44
|
+
@password = "postgres"
|
45
|
+
end
|
46
|
+
|
47
|
+
def from_hash(hash)
|
48
|
+
@port = hash["port"] if hash["port"]
|
49
|
+
@name = hash["name"] if hash["name"]
|
50
|
+
@version = hash["version"] if hash["version"]
|
51
|
+
@password = hash["password"] if hash["password"]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class RedisConfig
|
56
|
+
attr_accessor :port, :name, :version
|
57
|
+
|
58
|
+
def initialize
|
59
|
+
@port = 6379
|
60
|
+
@name = "redis-app"
|
61
|
+
@version = "latest"
|
62
|
+
end
|
63
|
+
|
64
|
+
def from_hash(hash)
|
65
|
+
@port = hash["port"] if hash["port"]
|
66
|
+
@name = hash["name"] if hash["name"]
|
67
|
+
@version = hash["version"] if hash["version"]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
require "logger"
|
5
|
+
require_relative "command_factory"
|
6
|
+
|
7
|
+
module Discharger
|
8
|
+
module SetupRunner
|
9
|
+
class Error < StandardError; end
|
10
|
+
|
11
|
+
class Runner
|
12
|
+
attr_reader :config, :app_root, :logger, :command_factory
|
13
|
+
|
14
|
+
def initialize(config, app_root = nil, logger = nil)
|
15
|
+
@config = config
|
16
|
+
@app_root = app_root || Dir.pwd
|
17
|
+
@logger = logger || Logger.new($stdout)
|
18
|
+
@command_factory = CommandFactory.new(config, app_root, logger)
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
require "rainbow"
|
23
|
+
unless ENV["QUIET_SETUP"] || ENV["DISABLE_OUTPUT"]
|
24
|
+
puts Rainbow("\n🚀 Starting setup for #{config.app_name}").bright.blue
|
25
|
+
puts Rainbow("=" * 50).blue
|
26
|
+
end
|
27
|
+
|
28
|
+
FileUtils.chdir app_root do
|
29
|
+
execute_commands
|
30
|
+
end
|
31
|
+
|
32
|
+
unless ENV["QUIET_SETUP"] || ENV["DISABLE_OUTPUT"]
|
33
|
+
puts Rainbow("\n✅ Setup completed successfully!").bright.green
|
34
|
+
end
|
35
|
+
rescue => e
|
36
|
+
unless ENV["QUIET_SETUP"] || ENV["DISABLE_OUTPUT"]
|
37
|
+
puts Rainbow("\n❌ Setup failed: #{e.message}").bright.red
|
38
|
+
end
|
39
|
+
raise Error, e.message
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_command(command)
|
43
|
+
commands << command
|
44
|
+
end
|
45
|
+
|
46
|
+
def remove_command(command_name)
|
47
|
+
commands.reject! { |cmd| cmd.class.name.demodulize.underscore == command_name.to_s }
|
48
|
+
end
|
49
|
+
|
50
|
+
def replace_command(command_name, new_command)
|
51
|
+
remove_command(command_name)
|
52
|
+
add_command(new_command)
|
53
|
+
end
|
54
|
+
|
55
|
+
def insert_command_before(target_command_name, new_command)
|
56
|
+
target_index = commands.find_index { |cmd| cmd.class.name.demodulize.underscore == target_command_name.to_s }
|
57
|
+
if target_index
|
58
|
+
commands.insert(target_index, new_command)
|
59
|
+
else
|
60
|
+
add_command(new_command)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def insert_command_after(target_command_name, new_command)
|
65
|
+
target_index = commands.find_index { |cmd| cmd.class.name.demodulize.underscore == target_command_name.to_s }
|
66
|
+
if target_index
|
67
|
+
commands.insert(target_index + 1, new_command)
|
68
|
+
else
|
69
|
+
add_command(new_command)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def commands
|
76
|
+
@commands ||= command_factory.create_all_commands
|
77
|
+
end
|
78
|
+
|
79
|
+
def execute_commands
|
80
|
+
commands.each do |command|
|
81
|
+
execute_command(command)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def execute_command(command)
|
86
|
+
unless command.can_execute?
|
87
|
+
unless ENV["QUIET_SETUP"] || ENV["DISABLE_OUTPUT"]
|
88
|
+
require "rainbow"
|
89
|
+
puts Rainbow("⏭️ Skipping #{command.description} (prerequisites not met)").yellow
|
90
|
+
end
|
91
|
+
return
|
92
|
+
end
|
93
|
+
|
94
|
+
unless ENV["QUIET_SETUP"] || ENV["DISABLE_OUTPUT"]
|
95
|
+
puts Rainbow("\n▶️ #{command.description}").bright
|
96
|
+
end
|
97
|
+
command.execute
|
98
|
+
rescue => e
|
99
|
+
unless ENV["QUIET_SETUP"] || ENV["DISABLE_OUTPUT"]
|
100
|
+
require "rainbow"
|
101
|
+
puts Rainbow("❌ Command #{command.description} failed: #{e.message}").red
|
102
|
+
end
|
103
|
+
raise e
|
104
|
+
end
|
105
|
+
|
106
|
+
def log(message)
|
107
|
+
logger.info message
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "setup_runner/version"
|
4
|
+
require_relative "setup_runner/configuration"
|
5
|
+
require_relative "setup_runner/command_registry"
|
6
|
+
require_relative "setup_runner/command_factory"
|
7
|
+
require_relative "setup_runner/runner"
|
8
|
+
|
9
|
+
module Discharger
|
10
|
+
module SetupRunner
|
11
|
+
class << self
|
12
|
+
def configure
|
13
|
+
yield configuration if block_given?
|
14
|
+
end
|
15
|
+
|
16
|
+
def configuration
|
17
|
+
@configuration ||= Configuration.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def run(config_path = nil, logger = nil)
|
21
|
+
config = config_path ? Configuration.from_file(config_path) : configuration
|
22
|
+
runner = Runner.new(config, Dir.pwd, logger)
|
23
|
+
yield runner if block_given?
|
24
|
+
runner.run
|
25
|
+
end
|
26
|
+
|
27
|
+
# Extension points for adding custom commands
|
28
|
+
def register_command(name, command_class)
|
29
|
+
CommandRegistry.register(name, command_class)
|
30
|
+
end
|
31
|
+
|
32
|
+
def unregister_command(name)
|
33
|
+
# Re-register all commands except the one to remove
|
34
|
+
all_commands = {}
|
35
|
+
CommandRegistry.names.each do |cmd_name|
|
36
|
+
unless cmd_name == name.to_s
|
37
|
+
all_commands[cmd_name] = CommandRegistry.get(cmd_name)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
CommandRegistry.clear
|
41
|
+
all_commands.each do |cmd_name, cmd_class|
|
42
|
+
CommandRegistry.register(cmd_name, cmd_class)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def list_commands
|
47
|
+
CommandRegistry.names
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_command(name)
|
51
|
+
CommandRegistry.get(name)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/discharger/version.rb
CHANGED
data/lib/discharger.rb
CHANGED
@@ -3,9 +3,23 @@ module Discharger
|
|
3
3
|
class InstallGenerator < Rails::Generators::Base
|
4
4
|
source_root File.expand_path("templates", __dir__)
|
5
5
|
|
6
|
+
class_option :setup_path,
|
7
|
+
type: :string,
|
8
|
+
default: "bin/setup",
|
9
|
+
desc: "Path where the setup script should be created"
|
10
|
+
|
6
11
|
def copy_initializer
|
7
12
|
template "discharger_initializer.rb", "config/initializers/discharger.rb"
|
8
13
|
end
|
14
|
+
|
15
|
+
def create_setup_script
|
16
|
+
template "setup", options[:setup_path]
|
17
|
+
chmod options[:setup_path], 0o755
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_sample_setup_yml
|
21
|
+
template "setup.yml", "config/setup.yml"
|
22
|
+
end
|
9
23
|
end
|
10
24
|
end
|
11
25
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "fileutils"
|
3
|
+
require "pathname"
|
4
|
+
require "bundler/setup"
|
5
|
+
|
6
|
+
# Path to the application root.
|
7
|
+
APP_ROOT = File.expand_path("..", __dir__)
|
8
|
+
|
9
|
+
FileUtils.chdir APP_ROOT do
|
10
|
+
# This script uses Discharger to set up your development environment automatically.
|
11
|
+
# All setup steps are configured in config/setup.yml
|
12
|
+
# This script is idempotent, so you can run it at any time and get an expectable outcome.
|
13
|
+
|
14
|
+
unless File.exist?("Gemfile")
|
15
|
+
puts "No Gemfile found. Please run this script from the root of your Rails application."
|
16
|
+
exit 1
|
17
|
+
end
|
18
|
+
|
19
|
+
unless File.exist?("config/setup.yml")
|
20
|
+
puts "No config/setup.yml found. Please run 'rails generate discharger:install' first."
|
21
|
+
exit 1
|
22
|
+
end
|
23
|
+
|
24
|
+
puts "== Running Discharger setup =="
|
25
|
+
puts "Configuration loaded from: config/setup.yml"
|
26
|
+
|
27
|
+
# Load Rails environment first, then discharger
|
28
|
+
require_relative "../config/application"
|
29
|
+
Rails.application.initialize!
|
30
|
+
|
31
|
+
# Load the discharger gem and run the setup
|
32
|
+
require "discharger"
|
33
|
+
Discharger::SetupRunner.run("config/setup.yml")
|
34
|
+
|
35
|
+
puts "\n== Setup completed successfully! =="
|
36
|
+
end
|