discharger 0.2.30 → 0.3.1
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/CHANGELOG.md +8 -4
- data/README.md +30 -0
- data/lib/discharger/prerequisites.rb +15 -0
- data/lib/discharger/setup.rb +104 -0
- data/lib/discharger/setup_runner/commands/base_command.rb +19 -4
- data/lib/discharger/setup_runner/commands/database_command.rb +21 -7
- data/lib/discharger/setup_runner/commands/docker_command.rb +112 -33
- data/lib/discharger/setup_runner/configuration.rb +8 -4
- data/lib/discharger/setup_runner/pre_commands/base_pre_command.rb +46 -0
- data/lib/discharger/setup_runner/pre_commands/homebrew_pre_command.rb +69 -0
- data/lib/discharger/setup_runner/pre_commands/postgresql_tools_pre_command.rb +72 -0
- data/lib/discharger/setup_runner/pre_commands/pre_command_registry.rb +39 -0
- data/lib/discharger/setup_runner/prerequisites_loader.rb +143 -0
- data/lib/discharger/setup_runner.rb +1 -0
- data/lib/discharger/task.rb +17 -3
- data/lib/discharger/version.rb +1 -1
- data/lib/generators/discharger/install/templates/setup +18 -24
- data/lib/generators/discharger/install/templates/setup.yml +23 -14
- metadata +8 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 86a58f41a34e61bfc155a9563e55bbc1eb42d4bd6904c09fe4aafb9bc61f3411
|
|
4
|
+
data.tar.gz: 62d3b3ad5ffec0dcdee073f5ca0109416b383dd2fe180f8501af47f2028d9122
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d0f6c58d5fde62fb7dd63b5dbe56c867600eea46e5c8e74b9eee22025da5dd299bf9d0b1093a40a67411c5cc279b9ca826e0c6d0af8659635fb6692279fa8fab
|
|
7
|
+
data.tar.gz: 4ef09b9ca157755c99aa693d3c768e11df6f4fb470ed2d3c4736cf7c2fb894fb46352e941e550c2c200ec95bfd98f40a5daf22ca0928431231fdad04e07fe1ca
|
data/CHANGELOG.md
CHANGED
|
@@ -5,10 +5,14 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
7
7
|
|
|
8
|
-
## [0.
|
|
8
|
+
## [0.3.1] - 2026-05-13
|
|
9
9
|
|
|
10
|
-
###
|
|
10
|
+
### Added
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- database.prefer_docker setup.yml option for explicit Docker vs native PostgreSQL selection (2fca81e)
|
|
13
13
|
|
|
14
|
-
## [0.2.
|
|
14
|
+
## [0.2.31] - 2026-04-09
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- Unified setup entry point with pre-steps support (1459633)
|
data/README.md
CHANGED
|
@@ -163,6 +163,12 @@ database:
|
|
|
163
163
|
name: "db-your-app"
|
|
164
164
|
version: "14"
|
|
165
165
|
password: "postgres"
|
|
166
|
+
# Optional. Controls how the docker step handles a native PostgreSQL
|
|
167
|
+
# already listening on the configured port:
|
|
168
|
+
# omitted/nil/false - silently skip Docker and use the native instance (legacy default)
|
|
169
|
+
# true - always create the Docker container; fail if a native instance holds the port
|
|
170
|
+
# "prompt" - ask the developer (interactive shells only; non-interactive runs fall back to native)
|
|
171
|
+
prefer_docker: "prompt"
|
|
166
172
|
|
|
167
173
|
redis:
|
|
168
174
|
port: 6379
|
|
@@ -187,6 +193,30 @@ custom_steps:
|
|
|
187
193
|
command: "bin/rails db:seed"
|
|
188
194
|
```
|
|
189
195
|
|
|
196
|
+
### Pre-Rails Steps (Prerequisites)
|
|
197
|
+
|
|
198
|
+
The `pre_steps` array defines commands that run **before Rails loads**. These are for system dependencies and environment setup that must be in place before bundler or Rails can initialize.
|
|
199
|
+
|
|
200
|
+
Built-in pre_steps:
|
|
201
|
+
- `homebrew` - Installs Homebrew if not present (macOS)
|
|
202
|
+
- `postgresql_tools` - Installs PostgreSQL client tools (`pg_dump`, `psql`)
|
|
203
|
+
|
|
204
|
+
```yaml
|
|
205
|
+
pre_steps:
|
|
206
|
+
- homebrew
|
|
207
|
+
- postgresql_tools
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
You can also define custom pre_steps with shell commands:
|
|
211
|
+
|
|
212
|
+
```yaml
|
|
213
|
+
pre_steps:
|
|
214
|
+
- homebrew
|
|
215
|
+
- description: "Set up environment variables"
|
|
216
|
+
command: "cp .env.example .env"
|
|
217
|
+
condition: "!File.exist?('.env')"
|
|
218
|
+
```
|
|
219
|
+
|
|
190
220
|
### Using Default Steps
|
|
191
221
|
|
|
192
222
|
The `steps` array specifies which built-in setup commands to run. Available commands include:
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Standalone loader for Discharger::SetupRunner::PrerequisitesLoader
|
|
4
|
+
# This can be required before Rails loads to set up environment variables
|
|
5
|
+
# and system dependencies.
|
|
6
|
+
#
|
|
7
|
+
# Usage in bin/setup:
|
|
8
|
+
# require "discharger/prerequisites"
|
|
9
|
+
# Discharger::SetupRunner::PrerequisitesLoader.run("config/setup.yml")
|
|
10
|
+
#
|
|
11
|
+
require_relative "setup_runner/pre_commands/base_pre_command"
|
|
12
|
+
require_relative "setup_runner/pre_commands/homebrew_pre_command"
|
|
13
|
+
require_relative "setup_runner/pre_commands/postgresql_tools_pre_command"
|
|
14
|
+
require_relative "setup_runner/pre_commands/pre_command_registry"
|
|
15
|
+
require_relative "setup_runner/prerequisites_loader"
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "pathname"
|
|
5
|
+
|
|
6
|
+
# Unified entry point for Discharger setup.
|
|
7
|
+
# Handles both pre-Rails prerequisites and post-Rails setup commands.
|
|
8
|
+
#
|
|
9
|
+
# Usage in bin/setup:
|
|
10
|
+
# require "discharger/setup"
|
|
11
|
+
# Discharger::Setup.run("config/setup.yml")
|
|
12
|
+
#
|
|
13
|
+
# This will automatically:
|
|
14
|
+
# 1. Load bundler/setup (activates correct gem versions)
|
|
15
|
+
# 2. Run pre_steps (before Rails loads) - env vars, homebrew, etc.
|
|
16
|
+
# 3. Initialize Rails
|
|
17
|
+
# 4. Run regular setup steps and custom_steps
|
|
18
|
+
#
|
|
19
|
+
module Discharger
|
|
20
|
+
class Setup
|
|
21
|
+
attr_reader :config_path, :app_root
|
|
22
|
+
|
|
23
|
+
def initialize(config_path, app_root: nil)
|
|
24
|
+
@config_path = config_path
|
|
25
|
+
@app_root = app_root || Dir.pwd
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.run(config_path = "config/setup.yml", app_root: nil)
|
|
29
|
+
new(config_path, app_root: app_root).run
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def run
|
|
33
|
+
FileUtils.chdir(app_root) do
|
|
34
|
+
validate_environment
|
|
35
|
+
print_header
|
|
36
|
+
|
|
37
|
+
# Phase 1: Load bundler first (activates correct gem versions)
|
|
38
|
+
# This must happen before we parse YAML to avoid psych version conflicts
|
|
39
|
+
load_bundler
|
|
40
|
+
|
|
41
|
+
# Phase 2: Pre-Rails setup (env vars, system dependencies)
|
|
42
|
+
# These run AFTER bundler but BEFORE Rails loads
|
|
43
|
+
run_prerequisites
|
|
44
|
+
|
|
45
|
+
# Phase 3: Load Rails (uses env vars from phase 2)
|
|
46
|
+
load_rails
|
|
47
|
+
|
|
48
|
+
# Phase 4: Run Discharger commands (after Rails loads)
|
|
49
|
+
run_setup_commands
|
|
50
|
+
|
|
51
|
+
print_footer
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def validate_environment
|
|
58
|
+
unless File.exist?("Gemfile")
|
|
59
|
+
puts "No Gemfile found. Please run this script from the root of your Rails application."
|
|
60
|
+
exit 1
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
unless File.exist?(config_path)
|
|
64
|
+
puts "No #{config_path} found. Please run 'rails generate discharger:install' first."
|
|
65
|
+
exit 1
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def print_header
|
|
70
|
+
puts "== Running Discharger setup =="
|
|
71
|
+
puts "Configuration loaded from: #{config_path}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def print_footer
|
|
75
|
+
puts "\n== Setup completed successfully! =="
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def load_bundler
|
|
79
|
+
require "bundler/setup"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def run_prerequisites
|
|
83
|
+
puts "\n== Setting up prerequisites =="
|
|
84
|
+
require_relative "prerequisites"
|
|
85
|
+
SetupRunner::PrerequisitesLoader.run(config_path)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def load_rails
|
|
89
|
+
# Load Rails from the standard location
|
|
90
|
+
rails_config = File.join(app_root, "config", "application.rb")
|
|
91
|
+
if File.exist?(rails_config)
|
|
92
|
+
require rails_config
|
|
93
|
+
Rails.application.initialize!
|
|
94
|
+
else
|
|
95
|
+
puts "Warning: config/application.rb not found. Skipping Rails initialization."
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def run_setup_commands
|
|
100
|
+
require_relative "../discharger"
|
|
101
|
+
SetupRunner.run(config_path)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -156,7 +156,12 @@ module Discharger
|
|
|
156
156
|
|
|
157
157
|
def system_quiet(*args)
|
|
158
158
|
require "open3"
|
|
159
|
-
|
|
159
|
+
# If it's a single string with shell metacharacters, run it through bash
|
|
160
|
+
if args.size == 1 && args.first.is_a?(String) && args.first.match?(/[|><&;]/)
|
|
161
|
+
stdout, _stderr, status = Open3.capture3("bash", "-c", args.first)
|
|
162
|
+
else
|
|
163
|
+
stdout, _stderr, status = Open3.capture3(*args)
|
|
164
|
+
end
|
|
160
165
|
logger&.debug("Quietly executed #{args.join(" ")} - success: #{status.success?}")
|
|
161
166
|
logger&.debug("Output: #{stdout}") if stdout && !stdout.empty? && logger
|
|
162
167
|
status.success?
|
|
@@ -172,10 +177,20 @@ module Discharger
|
|
|
172
177
|
end
|
|
173
178
|
|
|
174
179
|
def proceed_with(task)
|
|
175
|
-
|
|
180
|
+
# Auto-proceed in non-interactive environments (unless forced interactive for testing)
|
|
181
|
+
unless ENV["DISCHARGER_FORCE_INTERACTIVE"]
|
|
182
|
+
if ENV["CI"] || !$stdin.tty? || ENV["QUIET_SETUP"]
|
|
183
|
+
yield
|
|
184
|
+
return
|
|
185
|
+
end
|
|
186
|
+
end
|
|
176
187
|
|
|
177
|
-
|
|
178
|
-
|
|
188
|
+
unless ENV["DISABLE_OUTPUT"]
|
|
189
|
+
puts "Proceed with #{task}?\n ===> Type Y to proceed\nOtherwise hit any key to ignore."
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
input = gets
|
|
193
|
+
if input&.chomp == "Y"
|
|
179
194
|
yield
|
|
180
195
|
end
|
|
181
196
|
end
|
|
@@ -11,17 +11,18 @@ module Discharger
|
|
|
11
11
|
# Drop and recreate development database
|
|
12
12
|
terminate_database_connections
|
|
13
13
|
with_spinner("Dropping and recreating development database") do
|
|
14
|
-
|
|
14
|
+
stdout, stderr, status = Open3.capture3(db_env, "bin/rails", "db:drop", "db:create")
|
|
15
15
|
if status.success?
|
|
16
16
|
{success: true}
|
|
17
17
|
else
|
|
18
|
-
|
|
18
|
+
error_msg = stderr.empty? ? stdout : stderr
|
|
19
|
+
{success: false, error: "Failed to drop/create database: #{error_msg}"}
|
|
19
20
|
end
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
# Load schema and run migrations
|
|
23
24
|
with_spinner("Loading database schema and running migrations") do
|
|
24
|
-
_stdout, stderr, status = Open3.capture3("bin/rails db:schema:load db:migrate")
|
|
25
|
+
_stdout, stderr, status = Open3.capture3(db_env, "bin/rails", "db:schema:load", "db:migrate")
|
|
25
26
|
if status.success?
|
|
26
27
|
{success: true}
|
|
27
28
|
else
|
|
@@ -30,9 +31,9 @@ module Discharger
|
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
# Seed the database
|
|
33
|
-
|
|
34
|
+
seed_env = db_env.merge((config.respond_to?(:seed_env) && config.seed_env) ? {"SEED_DEV_ENV" => "true"} : {})
|
|
34
35
|
with_spinner("Seeding the database") do
|
|
35
|
-
_stdout, stderr, status = Open3.capture3(
|
|
36
|
+
_stdout, stderr, status = Open3.capture3(seed_env, "bin/rails", "db:seed")
|
|
36
37
|
if status.success?
|
|
37
38
|
{success: true}
|
|
38
39
|
else
|
|
@@ -43,11 +44,13 @@ module Discharger
|
|
|
43
44
|
# Setup test database
|
|
44
45
|
terminate_database_connections("test")
|
|
45
46
|
with_spinner("Setting up test database") do
|
|
46
|
-
|
|
47
|
+
test_env = db_env.merge({"RAILS_ENV" => "test"})
|
|
48
|
+
stdout, stderr, status = Open3.capture3(test_env, "bin/rails", "db:drop", "db:create", "db:schema:load")
|
|
47
49
|
if status.success?
|
|
48
50
|
{success: true}
|
|
49
51
|
else
|
|
50
|
-
|
|
52
|
+
error_msg = stderr.empty? ? stdout : stderr
|
|
53
|
+
{success: false, error: "Failed to setup test database: #{error_msg}"}
|
|
51
54
|
end
|
|
52
55
|
end
|
|
53
56
|
|
|
@@ -72,6 +75,17 @@ module Discharger
|
|
|
72
75
|
|
|
73
76
|
private
|
|
74
77
|
|
|
78
|
+
def db_env
|
|
79
|
+
# Use Docker PostgreSQL tools if bin/docker-pg directory exists
|
|
80
|
+
docker_pg_path = File.join(app_root, "bin", "docker-pg")
|
|
81
|
+
if File.directory?(docker_pg_path)
|
|
82
|
+
# Prepend docker-pg directory to PATH to use Docker's pg_dump/psql
|
|
83
|
+
{"PATH" => "#{docker_pg_path}:#{ENV["PATH"]}"}
|
|
84
|
+
else
|
|
85
|
+
{}
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
75
89
|
def terminate_database_connections(rails_env = nil)
|
|
76
90
|
# Use a Rails runner to terminate connections within the Rails context
|
|
77
91
|
env_vars = rails_env ? {"RAILS_ENV" => rails_env} : {}
|
|
@@ -7,48 +7,98 @@ module Discharger
|
|
|
7
7
|
module Commands
|
|
8
8
|
class DockerCommand < BaseCommand
|
|
9
9
|
def execute
|
|
10
|
-
|
|
11
|
-
if
|
|
10
|
+
setup_database if database_configured?
|
|
11
|
+
setup_redis if redis_configured?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def can_execute?
|
|
15
|
+
# Only execute if Docker is available and containers are configured
|
|
16
|
+
docker_available? && (database_configured? || redis_configured?)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def description
|
|
20
|
+
"Setup Docker containers"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def setup_database
|
|
26
|
+
puts " → Checking database configuration..." unless ENV["QUIET_SETUP"]
|
|
27
|
+
|
|
28
|
+
case database_config.prefer_docker
|
|
29
|
+
when true
|
|
30
|
+
create_database_container_or_fail
|
|
31
|
+
when "prompt"
|
|
32
|
+
if native_postgresql_available? && wants_native_postgresql?
|
|
33
|
+
use_native_postgresql
|
|
34
|
+
else
|
|
35
|
+
create_database_container_or_fail
|
|
36
|
+
end
|
|
37
|
+
else
|
|
38
|
+
# Legacy default: prefer the native instance when one is available.
|
|
12
39
|
if native_postgresql_available?
|
|
13
|
-
|
|
14
|
-
ENV["DB_PORT"] ||= native_postgresql_port.to_s
|
|
40
|
+
use_native_postgresql
|
|
15
41
|
else
|
|
16
|
-
|
|
17
|
-
setup_container(
|
|
18
|
-
name: config.database.name || "db-app",
|
|
19
|
-
port: config.database.port || 5432,
|
|
20
|
-
image: "postgres:#{config.database.version || "14"}",
|
|
21
|
-
env: {"POSTGRES_PASSWORD" => config.database.password || "postgres"},
|
|
22
|
-
volume: "#{config.database.name || "db-app"}:/var/lib/postgresql/data",
|
|
23
|
-
internal_port: 5432
|
|
24
|
-
)
|
|
42
|
+
create_database_container
|
|
25
43
|
end
|
|
26
44
|
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def setup_redis
|
|
48
|
+
setup_container(
|
|
49
|
+
name: redis_config.name || "redis-app",
|
|
50
|
+
port: redis_config.port || 6379,
|
|
51
|
+
image: "redis:#{redis_config.version || "latest"}",
|
|
52
|
+
internal_port: 6379
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def use_native_postgresql
|
|
57
|
+
puts " → Native PostgreSQL detected on port #{native_postgresql_port}, skipping Docker setup" unless ENV["QUIET_SETUP"]
|
|
58
|
+
ENV["DB_PORT"] ||= native_postgresql_port.to_s
|
|
59
|
+
end
|
|
27
60
|
|
|
28
|
-
|
|
29
|
-
if
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
image: "redis:#{config.redis.version || "latest"}",
|
|
34
|
-
internal_port: 6379
|
|
35
|
-
)
|
|
61
|
+
def create_database_container_or_fail
|
|
62
|
+
if native_postgresql_available?
|
|
63
|
+
raise "prefer_docker is set in the database config but native PostgreSQL is " \
|
|
64
|
+
"listening on port #{native_postgresql_port}. Stop the native instance " \
|
|
65
|
+
"(e.g., `brew services stop postgresql@15`) or remove prefer_docker."
|
|
36
66
|
end
|
|
67
|
+
create_database_container
|
|
37
68
|
end
|
|
38
69
|
|
|
39
|
-
def
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
70
|
+
def create_database_container
|
|
71
|
+
puts " → No native PostgreSQL found, setting up Docker container..." unless ENV["QUIET_SETUP"]
|
|
72
|
+
ensure_docker_running
|
|
73
|
+
setup_container(
|
|
74
|
+
name: database_config.name || "db-app",
|
|
75
|
+
port: database_config.port || 5432,
|
|
76
|
+
image: "postgres:#{database_config.version || "14"}",
|
|
77
|
+
env: {"POSTGRES_PASSWORD" => database_config.password || "postgres"},
|
|
78
|
+
volume: "#{database_config.name || "db-app"}:/var/lib/postgresql/data",
|
|
79
|
+
internal_port: 5432
|
|
44
80
|
)
|
|
45
81
|
end
|
|
46
82
|
|
|
47
|
-
|
|
48
|
-
|
|
83
|
+
# Ask the developer whether to use the detected native PostgreSQL or set up
|
|
84
|
+
# the configured Docker container instead. Falls back to native (the legacy
|
|
85
|
+
# behavior) when stdin is not a TTY, in CI, or when QUIET_SETUP is set,
|
|
86
|
+
# so non-interactive runs do not hang waiting for input.
|
|
87
|
+
def wants_native_postgresql?
|
|
88
|
+
unless interactive_prompt_available?
|
|
89
|
+
puts " → Non-interactive shell; defaulting to native PostgreSQL on port #{native_postgresql_port}" unless ENV["QUIET_SETUP"]
|
|
90
|
+
return true
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
puts "Native PostgreSQL detected on port #{native_postgresql_port}. Use the Docker container anyway?\n ===> Type Y to set up the Docker container\nOtherwise hit any key to use the native PostgreSQL."
|
|
94
|
+
$stdin.gets&.chomp != "Y"
|
|
49
95
|
end
|
|
50
96
|
|
|
51
|
-
|
|
97
|
+
def interactive_prompt_available?
|
|
98
|
+
return true if ENV["DISCHARGER_FORCE_INTERACTIVE"]
|
|
99
|
+
return false if ENV["CI"] || ENV["QUIET_SETUP"]
|
|
100
|
+
$stdin.tty?
|
|
101
|
+
end
|
|
52
102
|
|
|
53
103
|
def setup_container(name:, port:, image:, internal_port:, env: {}, volume: nil)
|
|
54
104
|
log "Checking #{name} container"
|
|
@@ -151,12 +201,41 @@ module Discharger
|
|
|
151
201
|
true
|
|
152
202
|
end
|
|
153
203
|
|
|
204
|
+
def database_configured?
|
|
205
|
+
config.respond_to?(:database) && config.database&.name
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def redis_configured?
|
|
209
|
+
config.respond_to?(:redis) && config.redis&.name
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def database_config
|
|
213
|
+
config.database
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def redis_config
|
|
217
|
+
config.redis
|
|
218
|
+
end
|
|
219
|
+
|
|
154
220
|
def native_postgresql_available?
|
|
155
|
-
|
|
221
|
+
# If a specific port is configured, ONLY check that port
|
|
222
|
+
# We should not use a native PostgreSQL on a different port
|
|
223
|
+
configured_port = config.database&.port
|
|
224
|
+
if configured_port
|
|
225
|
+
if postgresql_running_on_port?(configured_port)
|
|
226
|
+
@native_pg_port = configured_port
|
|
227
|
+
return true
|
|
228
|
+
end
|
|
229
|
+
# Configured port specified but PostgreSQL not running on it
|
|
230
|
+
return false
|
|
231
|
+
end
|
|
156
232
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
233
|
+
# No specific port configured, check common PostgreSQL ports
|
|
234
|
+
[5432, 5433].each do |port|
|
|
235
|
+
if postgresql_running_on_port?(port)
|
|
236
|
+
@native_pg_port = port
|
|
237
|
+
return true
|
|
238
|
+
end
|
|
160
239
|
end
|
|
161
240
|
false
|
|
162
241
|
end
|
|
@@ -5,15 +5,16 @@ require "yaml"
|
|
|
5
5
|
module Discharger
|
|
6
6
|
module SetupRunner
|
|
7
7
|
class Configuration
|
|
8
|
-
attr_accessor :app_name, :database, :redis, :services, :steps, :custom_steps
|
|
8
|
+
attr_accessor :app_name, :database, :redis, :services, :steps, :custom_steps, :pre_steps
|
|
9
9
|
|
|
10
10
|
def initialize
|
|
11
11
|
@app_name = "Application"
|
|
12
12
|
@database = DatabaseConfig.new
|
|
13
|
-
@redis =
|
|
13
|
+
@redis = nil
|
|
14
14
|
@services = []
|
|
15
15
|
@steps = []
|
|
16
16
|
@custom_steps = []
|
|
17
|
+
@pre_steps = []
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def self.from_file(path)
|
|
@@ -25,23 +26,25 @@ module Discharger
|
|
|
25
26
|
|
|
26
27
|
config.app_name = yaml["app_name"] if yaml["app_name"]
|
|
27
28
|
config.database.from_hash(yaml["database"]) if yaml["database"]
|
|
28
|
-
config.redis.from_hash(yaml["redis"]) if yaml["redis"]
|
|
29
|
+
config.redis = RedisConfig.new.tap { |r| r.from_hash(yaml["redis"]) } if yaml["redis"]
|
|
29
30
|
config.services = yaml["services"] || []
|
|
30
31
|
config.steps = yaml["steps"] || []
|
|
31
32
|
config.custom_steps = yaml["custom_steps"] || []
|
|
33
|
+
config.pre_steps = yaml["pre_steps"] || []
|
|
32
34
|
|
|
33
35
|
config
|
|
34
36
|
end
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
class DatabaseConfig
|
|
38
|
-
attr_accessor :port, :name, :version, :password
|
|
40
|
+
attr_accessor :port, :name, :version, :password, :prefer_docker
|
|
39
41
|
|
|
40
42
|
def initialize
|
|
41
43
|
@port = 5432
|
|
42
44
|
@name = "db-app"
|
|
43
45
|
@version = "14"
|
|
44
46
|
@password = "postgres"
|
|
47
|
+
@prefer_docker = nil
|
|
45
48
|
end
|
|
46
49
|
|
|
47
50
|
def from_hash(hash)
|
|
@@ -49,6 +52,7 @@ module Discharger
|
|
|
49
52
|
@name = hash["name"] if hash["name"]
|
|
50
53
|
@version = hash["version"] if hash["version"]
|
|
51
54
|
@password = hash["password"] if hash["password"]
|
|
55
|
+
@prefer_docker = hash["prefer_docker"] if hash.key?("prefer_docker")
|
|
52
56
|
end
|
|
53
57
|
end
|
|
54
58
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbconfig"
|
|
4
|
+
|
|
5
|
+
module Discharger
|
|
6
|
+
module SetupRunner
|
|
7
|
+
module PreCommands
|
|
8
|
+
# Base class for pre-Rails commands.
|
|
9
|
+
# These commands run BEFORE bundler/Rails loads, so they must be
|
|
10
|
+
# pure Ruby with no gem dependencies.
|
|
11
|
+
class BasePreCommand
|
|
12
|
+
attr_reader :config
|
|
13
|
+
|
|
14
|
+
def initialize(config = {})
|
|
15
|
+
@config = config
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def execute
|
|
19
|
+
raise NotImplementedError, "#{self.class} must implement #execute"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def description
|
|
23
|
+
self.class.name.split("::").last.gsub(/PreCommand$/, "").gsub(/([a-z])([A-Z])/, '\1 \2')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
protected
|
|
27
|
+
|
|
28
|
+
def log(message)
|
|
29
|
+
puts " #{message}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def platform_darwin?
|
|
33
|
+
RbConfig::CONFIG["host_os"] =~ /darwin/
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def platform_linux?
|
|
37
|
+
RbConfig::CONFIG["host_os"] =~ /linux/
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def command_exists?(command)
|
|
41
|
+
system("which #{command} > /dev/null 2>&1")
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_pre_command"
|
|
4
|
+
|
|
5
|
+
module Discharger
|
|
6
|
+
module SetupRunner
|
|
7
|
+
module PreCommands
|
|
8
|
+
# Ensures Homebrew/Linuxbrew is installed.
|
|
9
|
+
# This must run before `brew bundle` can install Brewfile dependencies.
|
|
10
|
+
class HomebrewPreCommand < BasePreCommand
|
|
11
|
+
HOMEBREW_INSTALL_URL = "https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh"
|
|
12
|
+
LINUXBREW_PATH = "/home/linuxbrew/.linuxbrew/bin"
|
|
13
|
+
|
|
14
|
+
def execute
|
|
15
|
+
if command_exists?("brew")
|
|
16
|
+
log "Homebrew found at: #{`which brew`.strip}"
|
|
17
|
+
return true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
log "Homebrew not found. Installing..."
|
|
21
|
+
|
|
22
|
+
if platform_darwin?
|
|
23
|
+
install_homebrew_macos
|
|
24
|
+
elsif platform_linux?
|
|
25
|
+
install_homebrew_linux
|
|
26
|
+
else
|
|
27
|
+
log "WARNING: Unsupported platform for automatic Homebrew installation"
|
|
28
|
+
log "Please install Homebrew manually: https://brew.sh"
|
|
29
|
+
return false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
verify_installation
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def description
|
|
36
|
+
"Ensure Homebrew is installed"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def install_homebrew_macos
|
|
42
|
+
log "Installing Homebrew for macOS..."
|
|
43
|
+
system(%(/bin/bash -c "$(curl -fsSL #{HOMEBREW_INSTALL_URL})"))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def install_homebrew_linux
|
|
47
|
+
log "Installing Linuxbrew for Linux..."
|
|
48
|
+
system(%(/bin/bash -c "$(curl -fsSL #{HOMEBREW_INSTALL_URL})"))
|
|
49
|
+
|
|
50
|
+
# Add Linuxbrew to PATH for current session
|
|
51
|
+
if File.exist?(LINUXBREW_PATH)
|
|
52
|
+
ENV["PATH"] = "#{LINUXBREW_PATH}:#{ENV["PATH"]}"
|
|
53
|
+
log "Added Linuxbrew to PATH"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def verify_installation
|
|
58
|
+
unless command_exists?("brew")
|
|
59
|
+
log "ERROR: Homebrew installation failed or not in PATH"
|
|
60
|
+
log "Please install Homebrew manually and ensure it's in your PATH"
|
|
61
|
+
return false
|
|
62
|
+
end
|
|
63
|
+
log "Homebrew installed successfully"
|
|
64
|
+
true
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_pre_command"
|
|
4
|
+
|
|
5
|
+
module Discharger
|
|
6
|
+
module SetupRunner
|
|
7
|
+
module PreCommands
|
|
8
|
+
# Ensures PostgreSQL client tools (pg_dump, psql) are installed.
|
|
9
|
+
# These tools are needed for database operations like db:structure:dump.
|
|
10
|
+
class PostgresqlToolsPreCommand < BasePreCommand
|
|
11
|
+
def execute
|
|
12
|
+
if command_exists?("pg_dump")
|
|
13
|
+
log "PostgreSQL client tools found"
|
|
14
|
+
return true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
log "PostgreSQL client tools (pg_dump) not found."
|
|
18
|
+
pg_version = config.dig("database", "version") || "14"
|
|
19
|
+
|
|
20
|
+
if platform_darwin?
|
|
21
|
+
install_via_homebrew(pg_version)
|
|
22
|
+
elsif platform_linux?
|
|
23
|
+
install_via_apt(pg_version)
|
|
24
|
+
else
|
|
25
|
+
log "WARNING: Unsupported platform for PostgreSQL client tools installation"
|
|
26
|
+
false
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def description
|
|
31
|
+
"Ensure PostgreSQL client tools are installed"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def install_via_homebrew(version)
|
|
37
|
+
log "Installing PostgreSQL #{version} client tools via Homebrew..."
|
|
38
|
+
if system("brew install postgresql@#{version}")
|
|
39
|
+
log "PostgreSQL #{version} client tools installed successfully"
|
|
40
|
+
true
|
|
41
|
+
else
|
|
42
|
+
log "WARNING: Failed to install PostgreSQL client tools"
|
|
43
|
+
log "Please install manually: brew install postgresql@#{version}"
|
|
44
|
+
false
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def install_via_apt(version)
|
|
49
|
+
unless command_exists?("apt-get")
|
|
50
|
+
log "WARNING: apt-get not found"
|
|
51
|
+
log "Please install postgresql-client-#{version} using your system's package manager"
|
|
52
|
+
return false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
log "Installing PostgreSQL #{version} client tools via apt..."
|
|
56
|
+
if system("sudo apt-get install -y postgresql-client-#{version}")
|
|
57
|
+
# Set up alternatives for the installed version
|
|
58
|
+
priority = version.to_i * 10
|
|
59
|
+
system("sudo update-alternatives --install /usr/bin/pg_dump pg_dump /usr/lib/postgresql/#{version}/bin/pg_dump #{priority}")
|
|
60
|
+
system("sudo update-alternatives --install /usr/bin/psql psql /usr/lib/postgresql/#{version}/bin/psql #{priority}")
|
|
61
|
+
log "PostgreSQL #{version} client tools installed successfully"
|
|
62
|
+
true
|
|
63
|
+
else
|
|
64
|
+
log "WARNING: Failed to install PostgreSQL client tools"
|
|
65
|
+
log "Please install manually: sudo apt-get install postgresql-client-#{version}"
|
|
66
|
+
false
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "homebrew_pre_command"
|
|
4
|
+
require_relative "postgresql_tools_pre_command"
|
|
5
|
+
|
|
6
|
+
module Discharger
|
|
7
|
+
module SetupRunner
|
|
8
|
+
module PreCommands
|
|
9
|
+
# Registry for pre-Rails commands.
|
|
10
|
+
# Maps command names (from setup.yml) to command classes.
|
|
11
|
+
class PreCommandRegistry
|
|
12
|
+
BUILT_IN_COMMANDS = {
|
|
13
|
+
"homebrew" => HomebrewPreCommand,
|
|
14
|
+
"postgresql_tools" => PostgresqlToolsPreCommand
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
def get(name)
|
|
19
|
+
custom_commands[name.to_s] || BUILT_IN_COMMANDS[name.to_s]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def register(name, command_class)
|
|
23
|
+
custom_commands[name.to_s] = command_class
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def names
|
|
27
|
+
(BUILT_IN_COMMANDS.keys + custom_commands.keys).uniq
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def custom_commands
|
|
33
|
+
@custom_commands ||= {}
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require_relative "pre_commands/pre_command_registry"
|
|
5
|
+
|
|
6
|
+
module Discharger
|
|
7
|
+
module SetupRunner
|
|
8
|
+
# PrerequisitesLoader handles setup tasks that must run BEFORE Rails loads.
|
|
9
|
+
# This includes setting environment variables, checking for system dependencies,
|
|
10
|
+
# and installing tools like Homebrew that are needed before bundler/Rails can run.
|
|
11
|
+
#
|
|
12
|
+
# Usage in bin/setup:
|
|
13
|
+
# require "discharger/prerequisites"
|
|
14
|
+
# Discharger::SetupRunner::PrerequisitesLoader.run("config/setup.yml")
|
|
15
|
+
#
|
|
16
|
+
class PrerequisitesLoader
|
|
17
|
+
attr_reader :config_path, :config
|
|
18
|
+
|
|
19
|
+
def initialize(config_path)
|
|
20
|
+
@config_path = config_path
|
|
21
|
+
@config = load_config
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.run(config_path)
|
|
25
|
+
new(config_path).run
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def run
|
|
29
|
+
return false unless config
|
|
30
|
+
|
|
31
|
+
set_database_environment
|
|
32
|
+
run_pre_steps
|
|
33
|
+
true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def load_config
|
|
39
|
+
return nil unless File.exist?(config_path)
|
|
40
|
+
|
|
41
|
+
YAML.load_file(config_path)
|
|
42
|
+
rescue => e
|
|
43
|
+
puts "Warning: Could not load #{config_path}: #{e.message}"
|
|
44
|
+
nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def set_database_environment
|
|
48
|
+
return unless config["database"]
|
|
49
|
+
|
|
50
|
+
db_config = config["database"]
|
|
51
|
+
set_db_port(db_config)
|
|
52
|
+
set_db_name(db_config)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def set_db_port(db_config)
|
|
56
|
+
if db_config["port"] && !ENV["DB_PORT"]
|
|
57
|
+
ENV["DB_PORT"] = db_config["port"].to_s
|
|
58
|
+
ENV["PGPORT"] = db_config["port"].to_s
|
|
59
|
+
puts " Setting DB_PORT=#{db_config["port"]} from config/setup.yml"
|
|
60
|
+
elsif ENV["DB_PORT"]
|
|
61
|
+
warn_if_mismatch("DB_PORT", ENV["DB_PORT"], db_config["port"].to_s)
|
|
62
|
+
ENV["PGPORT"] = ENV["DB_PORT"]
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def set_db_name(db_config)
|
|
67
|
+
if db_config["name"] && !ENV["DB_NAME"]
|
|
68
|
+
container_name = db_config["name"].to_s
|
|
69
|
+
db_name = container_name.sub(/^db-/, "")
|
|
70
|
+
ENV["DB_NAME"] = db_name
|
|
71
|
+
puts " Setting DB_NAME=#{db_name} from config/setup.yml (container: #{container_name})"
|
|
72
|
+
elsif ENV["DB_NAME"]
|
|
73
|
+
container_name = db_config["name"].to_s
|
|
74
|
+
expected_db_name = container_name.sub(/^db-/, "")
|
|
75
|
+
warn_if_mismatch("DB_NAME", ENV["DB_NAME"], expected_db_name)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def warn_if_mismatch(var_name, env_value, config_value)
|
|
80
|
+
return unless config_value && env_value != config_value
|
|
81
|
+
puts "\n⚠️ WARNING: #{var_name} environment variable (#{env_value}) differs from config/setup.yml (#{config_value})"
|
|
82
|
+
puts " Using environment variable value. To use config/setup.yml value, unset #{var_name}."
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def run_pre_steps
|
|
86
|
+
pre_steps = config["pre_steps"] || []
|
|
87
|
+
pre_steps.each do |step|
|
|
88
|
+
run_pre_step(step)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def run_pre_step(step)
|
|
93
|
+
case step
|
|
94
|
+
when String
|
|
95
|
+
run_built_in_pre_step(step)
|
|
96
|
+
when Hash
|
|
97
|
+
run_custom_pre_step(step)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def run_built_in_pre_step(name)
|
|
102
|
+
command_class = PreCommands::PreCommandRegistry.get(name)
|
|
103
|
+
unless command_class
|
|
104
|
+
puts " WARNING: Unknown pre-step '#{name}'"
|
|
105
|
+
return
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
command = command_class.new(config)
|
|
109
|
+
puts " #{command.description}..."
|
|
110
|
+
command.execute
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def run_custom_pre_step(step)
|
|
114
|
+
description = step["description"] || step["command"]
|
|
115
|
+
command = step["command"]
|
|
116
|
+
condition = step["condition"]
|
|
117
|
+
|
|
118
|
+
if condition && !evaluate_condition(condition)
|
|
119
|
+
puts " Skipping: #{description} (condition not met)"
|
|
120
|
+
return
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
puts " Running: #{description}"
|
|
124
|
+
system(command)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def evaluate_condition(condition)
|
|
128
|
+
case condition
|
|
129
|
+
when /^ENV\['(\w+)'\]$/
|
|
130
|
+
!ENV[$1].nil? && !ENV[$1].empty?
|
|
131
|
+
when /^!ENV\['(\w+)'\]$/
|
|
132
|
+
ENV[$1].nil? || ENV[$1].empty?
|
|
133
|
+
when /^File\.exist\?\(['"](.+)['"]\)$/
|
|
134
|
+
File.exist?($1)
|
|
135
|
+
when /^!File\.exist\?\(['"](.+)['"]\)$/
|
|
136
|
+
!File.exist?($1)
|
|
137
|
+
else
|
|
138
|
+
false
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -5,6 +5,7 @@ require_relative "setup_runner/configuration"
|
|
|
5
5
|
require_relative "setup_runner/command_registry"
|
|
6
6
|
require_relative "setup_runner/command_factory"
|
|
7
7
|
require_relative "setup_runner/runner"
|
|
8
|
+
require_relative "setup_runner/prerequisites_loader"
|
|
8
9
|
|
|
9
10
|
module Discharger
|
|
10
11
|
module SetupRunner
|
data/lib/discharger/task.rb
CHANGED
|
@@ -193,6 +193,16 @@ module Discharger
|
|
|
193
193
|
pr.empty? ? nil : pr
|
|
194
194
|
end
|
|
195
195
|
|
|
196
|
+
def pr_already_merged?(pr_ref)
|
|
197
|
+
stdout, _, status = Open3.capture3(
|
|
198
|
+
"gh", "pr", "view", pr_ref.to_s,
|
|
199
|
+
"--json", "state",
|
|
200
|
+
"--jq", ".state"
|
|
201
|
+
)
|
|
202
|
+
return false unless status.success?
|
|
203
|
+
stdout.strip == "MERGED"
|
|
204
|
+
end
|
|
205
|
+
|
|
196
206
|
def define
|
|
197
207
|
require "slack-ruby-client"
|
|
198
208
|
Slack.configure do |config|
|
|
@@ -262,9 +272,13 @@ module Discharger
|
|
|
262
272
|
end
|
|
263
273
|
end
|
|
264
274
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
275
|
+
if pr_already_merged?(pr_ref)
|
|
276
|
+
sysecho "PR #{pr_ref} is already merged. Continuing..."
|
|
277
|
+
else
|
|
278
|
+
syscall(
|
|
279
|
+
["gh pr merge #{pr_ref} --merge"]
|
|
280
|
+
)
|
|
281
|
+
end
|
|
268
282
|
|
|
269
283
|
continue = syscall(
|
|
270
284
|
["git fetch origin #{production_branch}:#{production_branch}"],
|
data/lib/discharger/version.rb
CHANGED
|
@@ -1,36 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
-
require "fileutils"
|
|
3
|
-
require "pathname"
|
|
4
|
-
require "bundler/setup"
|
|
5
2
|
|
|
6
3
|
# Path to the application root.
|
|
7
4
|
APP_ROOT = File.expand_path("..", __dir__)
|
|
8
5
|
|
|
9
|
-
|
|
6
|
+
Dir.chdir(APP_ROOT) do
|
|
10
7
|
# This script uses Discharger to set up your development environment automatically.
|
|
11
8
|
# All setup steps are configured in config/setup.yml
|
|
12
9
|
# This script is idempotent, so you can run it at any time and get an expectable outcome.
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
# Find discharger gem path from Gemfile without loading bundler
|
|
12
|
+
# (loading bundler early can cause gem version conflicts)
|
|
13
|
+
gemfile = File.read("Gemfile")
|
|
14
|
+
if (match = gemfile.match(/gem\s+["']discharger["'].*path:\s*["']([^"']+)["']/))
|
|
15
|
+
discharger_path = File.expand_path(match[1], APP_ROOT)
|
|
16
|
+
$LOAD_PATH.unshift(File.join(discharger_path, "lib"))
|
|
17
|
+
else
|
|
18
|
+
# Fall back to installed gem
|
|
19
|
+
begin
|
|
20
|
+
gem "discharger"
|
|
21
|
+
rescue Gem::MissingSpecError
|
|
22
|
+
puts "Could not find discharger gem."
|
|
23
|
+
puts "If using a custom bundle path, try: bundle exec bin/setup"
|
|
24
|
+
exit 1
|
|
25
|
+
end
|
|
17
26
|
end
|
|
18
27
|
|
|
19
|
-
|
|
20
|
-
|
|
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! =="
|
|
28
|
+
require "discharger/setup"
|
|
29
|
+
Discharger::Setup.run("config/setup.yml")
|
|
36
30
|
end
|
|
@@ -8,7 +8,7 @@ app_name: "YourAppName"
|
|
|
8
8
|
# Database configuration
|
|
9
9
|
database:
|
|
10
10
|
port: 5432
|
|
11
|
-
name: "db-your-app"
|
|
11
|
+
name: "db-your-app" # Docker container name. Rails will use "your-app" for database names
|
|
12
12
|
version: "14"
|
|
13
13
|
password: "postgres"
|
|
14
14
|
|
|
@@ -18,6 +18,23 @@ redis:
|
|
|
18
18
|
name: "redis-your-app"
|
|
19
19
|
version: "latest"
|
|
20
20
|
|
|
21
|
+
# Pre-Rails steps (run BEFORE Rails loads)
|
|
22
|
+
# These are for system dependencies and environment setup that must happen
|
|
23
|
+
# before bundler/Rails can initialize. Use sparingly.
|
|
24
|
+
#
|
|
25
|
+
# Built-in pre_steps:
|
|
26
|
+
# - homebrew # Installs Homebrew if not present
|
|
27
|
+
# - postgresql_tools # Installs PostgreSQL client tools (pg_dump, psql)
|
|
28
|
+
#
|
|
29
|
+
# Custom pre_steps (run shell commands):
|
|
30
|
+
# - description: "Description of step"
|
|
31
|
+
# command: "shell command to run"
|
|
32
|
+
# condition: "ENV['VAR']" # Optional: only run if condition is true
|
|
33
|
+
#
|
|
34
|
+
pre_steps: []
|
|
35
|
+
# - homebrew
|
|
36
|
+
# - postgresql_tools
|
|
37
|
+
|
|
21
38
|
# Built-in commands to run (empty array runs all available commands)
|
|
22
39
|
# Available commands: brew, asdf, git, bundler, yarn, config, docker, pg_tools, env, database
|
|
23
40
|
steps:
|
|
@@ -32,30 +49,22 @@ steps:
|
|
|
32
49
|
- env
|
|
33
50
|
- database
|
|
34
51
|
|
|
35
|
-
# Custom commands for application-specific setup
|
|
52
|
+
# Custom commands for application-specific setup (run AFTER Rails loads)
|
|
36
53
|
custom_steps:
|
|
37
54
|
- description: "Seed application data"
|
|
38
55
|
command: "bin/rails db:seed"
|
|
39
|
-
|
|
40
|
-
- description: "Setup application-specific configurations"
|
|
41
|
-
command: "bin/rails runner 'YourAppSetup.new.configure'"
|
|
42
|
-
condition: "defined?(YourAppSetup)"
|
|
43
|
-
|
|
44
|
-
- description: "Import application data"
|
|
45
|
-
command: "bin/rails db:seed:import"
|
|
46
|
-
condition: "File.exist?('db/seeds/import.rb')"
|
|
47
|
-
|
|
56
|
+
|
|
48
57
|
# Example: Conditional setup based on environment variable
|
|
49
58
|
# - description: "Seed legacy data"
|
|
50
59
|
# command: "LEGACY_DATA=true bin/rails db:seed"
|
|
51
60
|
# condition: "ENV['LEGACY_DATA'] == 'true'"
|
|
52
|
-
|
|
61
|
+
|
|
53
62
|
# Example: Setup external services
|
|
54
63
|
# - description: "Setup Elasticsearch"
|
|
55
64
|
# command: "bin/rails search:setup"
|
|
56
65
|
# condition: "defined?(Elasticsearch)"
|
|
57
|
-
|
|
66
|
+
|
|
58
67
|
# Example: Setup background job processing
|
|
59
68
|
# - description: "Setup Sidekiq"
|
|
60
69
|
# command: "bundle exec sidekiq -d"
|
|
61
|
-
# condition: "defined?(Sidekiq)"
|
|
70
|
+
# condition: "defined?(Sidekiq)"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: discharger
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jim Gay
|
|
@@ -106,7 +106,9 @@ files:
|
|
|
106
106
|
- README.md
|
|
107
107
|
- Rakefile
|
|
108
108
|
- lib/discharger.rb
|
|
109
|
+
- lib/discharger/prerequisites.rb
|
|
109
110
|
- lib/discharger/railtie.rb
|
|
111
|
+
- lib/discharger/setup.rb
|
|
110
112
|
- lib/discharger/setup_runner.rb
|
|
111
113
|
- lib/discharger/setup_runner/command_factory.rb
|
|
112
114
|
- lib/discharger/setup_runner/command_registry.rb
|
|
@@ -124,6 +126,11 @@ files:
|
|
|
124
126
|
- lib/discharger/setup_runner/commands/yarn_command.rb
|
|
125
127
|
- lib/discharger/setup_runner/condition_evaluator.rb
|
|
126
128
|
- lib/discharger/setup_runner/configuration.rb
|
|
129
|
+
- lib/discharger/setup_runner/pre_commands/base_pre_command.rb
|
|
130
|
+
- lib/discharger/setup_runner/pre_commands/homebrew_pre_command.rb
|
|
131
|
+
- lib/discharger/setup_runner/pre_commands/postgresql_tools_pre_command.rb
|
|
132
|
+
- lib/discharger/setup_runner/pre_commands/pre_command_registry.rb
|
|
133
|
+
- lib/discharger/setup_runner/prerequisites_loader.rb
|
|
127
134
|
- lib/discharger/setup_runner/runner.rb
|
|
128
135
|
- lib/discharger/setup_runner/version.rb
|
|
129
136
|
- lib/discharger/task.rb
|