yoker 0.1.0
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 +7 -0
- data/.rubocop.yml +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +223 -0
- data/Rakefile +12 -0
- data/exe/yoker +177 -0
- data/exe/yoker (Copy) +87 -0
- data/lib/yoker/cli/base.rb +106 -0
- data/lib/yoker/cli/init.rb +193 -0
- data/lib/yoker/cli/update.rb +457 -0
- data/lib/yoker/configuration.rb +290 -0
- data/lib/yoker/detectors/database_detector.rb +35 -0
- data/lib/yoker/detectors/rails_detector.rb +48 -0
- data/lib/yoker/detectors/version_manager_detector.rb +91 -0
- data/lib/yoker/errors.rb +149 -0
- data/lib/yoker/generators/base_generator.rb +116 -0
- data/lib/yoker/generators/container/docker.rb +255 -0
- data/lib/yoker/generators/container/docker_compose.rb +255 -0
- data/lib/yoker/generators/container/none.rb +314 -0
- data/lib/yoker/generators/database/mysql.rb +147 -0
- data/lib/yoker/generators/database/postgresql.rb +64 -0
- data/lib/yoker/generators/database/sqlite.rb +123 -0
- data/lib/yoker/generators/version_manager/mise.rb +140 -0
- data/lib/yoker/generators/version_manager/rbenv.rb +165 -0
- data/lib/yoker/generators/version_manager/rvm.rb +246 -0
- data/lib/yoker/templates/bin/setup.rb.erb +232 -0
- data/lib/yoker/templates/config/database_mysql.yml.erb +47 -0
- data/lib/yoker/templates/config/database_postgresql.yml.erb +34 -0
- data/lib/yoker/templates/config/database_sqlite.yml.erb +40 -0
- data/lib/yoker/templates/docker/Dockerfile.erb +124 -0
- data/lib/yoker/templates/docker/docker-compose.yml.erb +117 -0
- data/lib/yoker/templates/docker/entrypoint.sh.erb +94 -0
- data/lib/yoker/templates/docker/init_mysql.sql.erb +44 -0
- data/lib/yoker/templates/docker/init_postgresql.sql.erb +203 -0
- data/lib/yoker/templates/version_managers/gemrc.erb +61 -0
- data/lib/yoker/templates/version_managers/mise.toml.erb +72 -0
- data/lib/yoker/templates/version_managers/rbenv_setup.sh.erb +93 -0
- data/lib/yoker/templates/version_managers/rvm_setup.sh.erb +99 -0
- data/lib/yoker/templates/version_managers/rvmrc.erb +70 -0
- data/lib/yoker/version.rb +5 -0
- data/lib/yoker.rb +32 -0
- data/sig/yoker.rbs +4 -0
- metadata +215 -0
@@ -0,0 +1,290 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
require "toml-rb" if defined?(TomlRB)
|
5
|
+
|
6
|
+
module Yoker
|
7
|
+
class Configuration
|
8
|
+
SUPPORTED_DATABASES = %w[postgresql mysql sqlite3].freeze
|
9
|
+
SUPPORTED_VERSION_MANAGERS = %w[mise rbenv rvm none].freeze
|
10
|
+
SUPPORTED_CONTAINERS = %w[docker-compose docker none].freeze
|
11
|
+
SUPPORTED_ADDITIONAL_SERVICES = %w[redis sidekiq mailcatcher].freeze
|
12
|
+
|
13
|
+
DEFAULT_RUBY_VERSION = RUBY_VERSION
|
14
|
+
DEFAULT_DATABASE = "postgresql"
|
15
|
+
DEFAULT_VERSION_MANAGER = "mise"
|
16
|
+
DEFAULT_CONTAINER = "docker-compose"
|
17
|
+
|
18
|
+
attr_accessor :app_name, :ruby_version, :database, :version_manager,
|
19
|
+
:container, :additional_services, :force_overwrite,
|
20
|
+
:backup_existing, :verbose, :database_port, :database_username,
|
21
|
+
:database_password, :database_host
|
22
|
+
|
23
|
+
def initialize(options = {})
|
24
|
+
@app_name = options[:app_name] || detect_app_name
|
25
|
+
@ruby_version = options[:ruby_version] || DEFAULT_RUBY_VERSION
|
26
|
+
@database = options[:database] || DEFAULT_DATABASE
|
27
|
+
@version_manager = options[:version_manager] || DEFAULT_VERSION_MANAGER
|
28
|
+
@container = options[:container] || DEFAULT_CONTAINER
|
29
|
+
@additional_services = Array(options[:additional_services])
|
30
|
+
@force_overwrite = options[:force_overwrite] || false
|
31
|
+
@backup_existing = options[:backup_existing] || true
|
32
|
+
@verbose = options[:verbose] || false
|
33
|
+
|
34
|
+
# Database-specific settings
|
35
|
+
@database_port = options[:database_port] || default_database_port
|
36
|
+
@database_username = options[:database_username] || default_database_username
|
37
|
+
@database_password = options[:database_password] || default_database_password
|
38
|
+
@database_host = options[:database_host] || default_database_host
|
39
|
+
|
40
|
+
validate!
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_h
|
44
|
+
{
|
45
|
+
app_name: app_name,
|
46
|
+
ruby_version: ruby_version,
|
47
|
+
database: database,
|
48
|
+
version_manager: version_manager,
|
49
|
+
container: container,
|
50
|
+
additional_services: additional_services,
|
51
|
+
force_overwrite: force_overwrite,
|
52
|
+
backup_existing: backup_existing,
|
53
|
+
verbose: verbose,
|
54
|
+
database_port: database_port,
|
55
|
+
database_username: database_username,
|
56
|
+
database_password: database_password,
|
57
|
+
database_host: database_host
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.from_file(path)
|
62
|
+
return new unless File.exist?(path)
|
63
|
+
|
64
|
+
case File.extname(path)
|
65
|
+
when ".yml", ".yaml"
|
66
|
+
from_yaml(path)
|
67
|
+
when ".toml"
|
68
|
+
from_toml(path)
|
69
|
+
else
|
70
|
+
raise InvalidConfigurationError, "Unsupported configuration file format: #{path}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.from_yaml(path)
|
75
|
+
config = YAML.safe_load(File.read(path), symbolize_names: true)
|
76
|
+
new(config)
|
77
|
+
rescue StandardError => e
|
78
|
+
raise InvalidConfigurationError, "Failed to load YAML configuration: #{e.message}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.from_toml(path)
|
82
|
+
raise InvalidConfigurationError, "toml-rb gem required to load TOML configuration" unless defined?(TomlRB)
|
83
|
+
|
84
|
+
config = TomlRB.load_file(path)
|
85
|
+
symbolized_config = deep_symbolize_keys(config)
|
86
|
+
new(symbolized_config)
|
87
|
+
rescue StandardError => e
|
88
|
+
raise InvalidConfigurationError, "Failed to load TOML configuration: #{e.message}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def save_to_file(path)
|
92
|
+
case File.extname(path)
|
93
|
+
when ".yml", ".yaml"
|
94
|
+
File.write(path, to_h.to_yaml)
|
95
|
+
when ".toml"
|
96
|
+
raise InvalidConfigurationError, "toml-rb gem required to save TOML configuration" unless defined?(TomlRB)
|
97
|
+
|
98
|
+
File.write(path, TomlRB.dump(to_h.stringify_keys))
|
99
|
+
else
|
100
|
+
raise InvalidConfigurationError, "Unsupported configuration file format: #{path}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Validation methods
|
105
|
+
def valid?
|
106
|
+
validate!
|
107
|
+
true
|
108
|
+
rescue InvalidConfigurationError
|
109
|
+
false
|
110
|
+
end
|
111
|
+
|
112
|
+
def postgresql?
|
113
|
+
database == "postgresql"
|
114
|
+
end
|
115
|
+
|
116
|
+
def mysql?
|
117
|
+
database == "mysql"
|
118
|
+
end
|
119
|
+
|
120
|
+
def sqlite?
|
121
|
+
database == "sqlite3"
|
122
|
+
end
|
123
|
+
|
124
|
+
def uses_containers?
|
125
|
+
container != "none"
|
126
|
+
end
|
127
|
+
|
128
|
+
def uses_docker_compose?
|
129
|
+
container == "docker-compose"
|
130
|
+
end
|
131
|
+
|
132
|
+
def uses_version_manager?
|
133
|
+
version_manager != "none"
|
134
|
+
end
|
135
|
+
|
136
|
+
def uses_mise?
|
137
|
+
version_manager == "mise"
|
138
|
+
end
|
139
|
+
|
140
|
+
def includes_service?(service)
|
141
|
+
additional_services.include?(service.to_s)
|
142
|
+
end
|
143
|
+
|
144
|
+
def database_service_needed?
|
145
|
+
uses_containers? && !sqlite?
|
146
|
+
end
|
147
|
+
|
148
|
+
def redis_needed?
|
149
|
+
includes_service?("redis") || includes_service?("sidekiq")
|
150
|
+
end
|
151
|
+
|
152
|
+
# Merge configurations (for updating existing configs)
|
153
|
+
def merge!(other_config)
|
154
|
+
return self unless other_config.is_a?(Configuration)
|
155
|
+
|
156
|
+
other_config.to_h.each do |key, value|
|
157
|
+
public_send("#{key}=", value) if respond_to?("#{key}=") && !value.nil?
|
158
|
+
end
|
159
|
+
|
160
|
+
validate!
|
161
|
+
self
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
def validate!
|
167
|
+
validate_database!
|
168
|
+
validate_version_manager!
|
169
|
+
validate_container!
|
170
|
+
validate_additional_services!
|
171
|
+
validate_ruby_version!
|
172
|
+
validate_app_name!
|
173
|
+
validate_compatibility!
|
174
|
+
end
|
175
|
+
|
176
|
+
def validate_database!
|
177
|
+
return if SUPPORTED_DATABASES.include?(database)
|
178
|
+
|
179
|
+
raise UnsupportedDatabaseError, database
|
180
|
+
end
|
181
|
+
|
182
|
+
def validate_version_manager!
|
183
|
+
return if SUPPORTED_VERSION_MANAGERS.include?(version_manager)
|
184
|
+
|
185
|
+
raise UnsupportedVersionManagerError, version_manager
|
186
|
+
end
|
187
|
+
|
188
|
+
def validate_container!
|
189
|
+
return if SUPPORTED_CONTAINERS.include?(container)
|
190
|
+
|
191
|
+
raise UnsupportedContainerError, container
|
192
|
+
end
|
193
|
+
|
194
|
+
def validate_additional_services!
|
195
|
+
invalid_services = additional_services - SUPPORTED_ADDITIONAL_SERVICES
|
196
|
+
unless invalid_services.empty?
|
197
|
+
raise InvalidConfigurationError,
|
198
|
+
"Unsupported additional services: #{invalid_services.join(", ")}"
|
199
|
+
end
|
200
|
+
|
201
|
+
# Validate service dependencies
|
202
|
+
return unless includes_service?("sidekiq") && !includes_service?("redis")
|
203
|
+
|
204
|
+
raise ConfigurationConflictError,
|
205
|
+
"Sidekiq requires Redis. Please include 'redis' in additional_services"
|
206
|
+
end
|
207
|
+
|
208
|
+
def validate_ruby_version!
|
209
|
+
return if ruby_version.match?(/\A\d+\.\d+(\.\d+)?\z/)
|
210
|
+
|
211
|
+
raise InvalidConfigurationError,
|
212
|
+
"Invalid Ruby version format: #{ruby_version}. Expected format: X.Y.Z"
|
213
|
+
end
|
214
|
+
|
215
|
+
def validate_app_name!
|
216
|
+
raise InvalidConfigurationError, "App name cannot be empty" if app_name.nil? || app_name.empty?
|
217
|
+
|
218
|
+
return if app_name.match?(/\A[a-zA-Z0-9_-]+\z/)
|
219
|
+
|
220
|
+
raise InvalidConfigurationError,
|
221
|
+
"App name contains invalid characters. Only alphanumeric, underscore, and hyphen allowed"
|
222
|
+
end
|
223
|
+
|
224
|
+
def validate_compatibility!
|
225
|
+
# SQLite doesn't need containers for database
|
226
|
+
if sqlite? && uses_containers?
|
227
|
+
# This is allowed but warn that database container won't be created
|
228
|
+
end
|
229
|
+
|
230
|
+
# Some services only make sense with containers
|
231
|
+
if !uses_containers? && includes_service?("mailcatcher")
|
232
|
+
raise ConfigurationConflictError,
|
233
|
+
"Mailcatcher service requires containerization (Docker or Docker Compose)"
|
234
|
+
end
|
235
|
+
|
236
|
+
# Version manager compatibility
|
237
|
+
nil unless version_manager == "mise" && !uses_containers?
|
238
|
+
# Mise can manage database tools natively
|
239
|
+
end
|
240
|
+
|
241
|
+
def detect_app_name
|
242
|
+
if File.exist?("config/application.rb")
|
243
|
+
# Try to extract from Rails application.rb
|
244
|
+
content = File.read("config/application.rb")
|
245
|
+
return ::Regexp.last_match(1).downcase if content =~ /module\s+(\w+)/
|
246
|
+
end
|
247
|
+
|
248
|
+
# Fallback to directory name
|
249
|
+
File.basename(Dir.pwd).gsub(/[^a-zA-Z0-9]/, "_").downcase
|
250
|
+
end
|
251
|
+
|
252
|
+
def default_database_port
|
253
|
+
case database
|
254
|
+
when "postgresql" then 5432
|
255
|
+
when "mysql" then 3306
|
256
|
+
when "sqlite3" then nil
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def default_database_username
|
261
|
+
case database
|
262
|
+
when "postgresql"
|
263
|
+
uses_containers? ? "postgres" : ENV["USER"] || "postgres"
|
264
|
+
when "mysql"
|
265
|
+
uses_containers? ? "rails" : "root"
|
266
|
+
when "sqlite3" then nil
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def default_database_password
|
271
|
+
case database
|
272
|
+
when "postgresql", "mysql"
|
273
|
+
uses_containers? ? "password" : ""
|
274
|
+
when "sqlite3" then nil
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def default_database_host
|
279
|
+
uses_containers? ? "db" : "localhost"
|
280
|
+
end
|
281
|
+
|
282
|
+
def self.deep_symbolize_keys(hash)
|
283
|
+
hash.each_with_object({}) do |(key, value), result|
|
284
|
+
new_key = key.to_sym
|
285
|
+
new_value = value.is_a?(Hash) ? deep_symbolize_keys(value) : value
|
286
|
+
result[new_key] = new_value
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Yoker
|
4
|
+
module Detectors
|
5
|
+
class DatabaseDetector
|
6
|
+
def self.detect
|
7
|
+
return "postgresql" if gemfile_includes?("pg")
|
8
|
+
return "mysql" if gemfile_includes?("mysql2")
|
9
|
+
return "sqlite3" if gemfile_includes?("sqlite3")
|
10
|
+
|
11
|
+
# Check database.yml
|
12
|
+
detect_from_database_yml
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def self.gemfile_includes?(gem_name)
|
18
|
+
return false unless File.exist?("Gemfile")
|
19
|
+
|
20
|
+
File.read("Gemfile").include?(gem_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.detect_from_database_yml
|
24
|
+
return nil unless File.exist?("config/database.yml")
|
25
|
+
|
26
|
+
db_config = File.read("config/database.yml")
|
27
|
+
return "postgresql" if db_config.include?("postgresql") || db_config.include?("adapter: postgresql")
|
28
|
+
return "mysql" if db_config.include?("mysql") || db_config.include?("adapter: mysql2")
|
29
|
+
return "sqlite3" if db_config.include?("sqlite") || db_config.include?("adapter: sqlite3")
|
30
|
+
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Yoker
|
4
|
+
module Detectors
|
5
|
+
class RailsDetector
|
6
|
+
def self.rails_app?
|
7
|
+
File.exist?("config/application.rb") &&
|
8
|
+
File.exist?("Gemfile") &&
|
9
|
+
File.read("Gemfile").include?("rails")
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.rails_version
|
13
|
+
return nil unless rails_app?
|
14
|
+
|
15
|
+
# Try to get version from Gemfile.lock first
|
16
|
+
if File.exist?("Gemfile.lock")
|
17
|
+
gemfile_lock = File.read("Gemfile.lock")
|
18
|
+
# Look for the rails gem specifically in the GEM section
|
19
|
+
# Format: " rails (8.1.0.beta1)"
|
20
|
+
if gemfile_lock =~ /^\s{4}rails\s+\(([^)]+)\)/
|
21
|
+
return $1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Fallback to checking Rails constant if available
|
26
|
+
begin
|
27
|
+
require_relative File.expand_path("config/application", Dir.pwd)
|
28
|
+
return Rails::VERSION::STRING if defined?(Rails::VERSION::STRING)
|
29
|
+
rescue
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
nil
|
34
|
+
rescue
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.rails_api_only?
|
39
|
+
return false unless rails_app?
|
40
|
+
|
41
|
+
app_rb = File.read("config/application.rb")
|
42
|
+
app_rb.include?("config.api_only = true")
|
43
|
+
rescue
|
44
|
+
false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Yoker
|
4
|
+
module Detectors
|
5
|
+
class VersionManagerDetector
|
6
|
+
# Preference order for version managers
|
7
|
+
PREFERENCE_ORDER = %w[mise rvm rbenv].freeze
|
8
|
+
|
9
|
+
def self.detect
|
10
|
+
# First, check which version managers are actually installed
|
11
|
+
installed_managers = detect_installed_managers
|
12
|
+
|
13
|
+
return "none" if installed_managers.empty?
|
14
|
+
|
15
|
+
# Check for configuration files that match installed managers
|
16
|
+
file_based_detection = detect_from_files
|
17
|
+
|
18
|
+
# If we found a config file for an installed manager, use it
|
19
|
+
return file_based_detection if file_based_detection && installed_managers.include?(file_based_detection)
|
20
|
+
|
21
|
+
# Otherwise, return the highest priority installed manager
|
22
|
+
PREFERENCE_ORDER.each do |manager|
|
23
|
+
return manager if installed_managers.include?(manager)
|
24
|
+
end
|
25
|
+
|
26
|
+
"none"
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.detect_installed_managers
|
30
|
+
managers = []
|
31
|
+
|
32
|
+
# Check for mise
|
33
|
+
managers << "mise" if command_exists?("mise")
|
34
|
+
|
35
|
+
# Check for rvm
|
36
|
+
managers << "rvm" if command_exists?("rvm")
|
37
|
+
|
38
|
+
# Check for rbenv
|
39
|
+
managers << "rbenv" if command_exists?("rbenv")
|
40
|
+
|
41
|
+
managers
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.detect_from_files
|
45
|
+
# Check for version manager configuration files
|
46
|
+
return "mise" if File.exist?("mise.toml") || File.exist?(".mise.toml")
|
47
|
+
return "mise" if File.exist?(".tool-versions") # mise/asdf format
|
48
|
+
return "rvm" if File.exist?(".rvmrc") || File.exist?(".ruby-gemset")
|
49
|
+
# .ruby-version alone suggests rbenv
|
50
|
+
return "rbenv" if File.exist?(".ruby-version") && !File.exist?(".ruby-gemset")
|
51
|
+
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.command_exists?(command)
|
56
|
+
system("which #{command} > /dev/null 2>&1")
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.current_ruby_version
|
60
|
+
RUBY_VERSION
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.ruby_version_from_file
|
64
|
+
if File.exist?("mise.toml")
|
65
|
+
extract_ruby_from_mise_toml
|
66
|
+
elsif File.exist?(".ruby-version")
|
67
|
+
File.read(".ruby-version").strip
|
68
|
+
elsif File.exist?(".tool-versions")
|
69
|
+
extract_ruby_from_tool_versions
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def self.extract_ruby_from_mise_toml
|
76
|
+
content = File.read("mise.toml")
|
77
|
+
::Regexp.last_match(1) if content =~ /ruby\s*=\s*["']([^"']+)["']/
|
78
|
+
rescue StandardError
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.extract_ruby_from_tool_versions
|
83
|
+
content = File.read(".tool-versions")
|
84
|
+
ruby_line = content.lines.find { |line| line.start_with?("ruby ") }
|
85
|
+
ruby_line&.split&.last&.strip
|
86
|
+
rescue StandardError
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/yoker/errors.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Yoker
|
4
|
+
# Base error class for all Yoker errors
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
# Raised when not in a Rails application directory
|
8
|
+
class NotRailsAppError < Error
|
9
|
+
def initialize(path = Dir.pwd)
|
10
|
+
super("Directory '#{path}' is not a Rails application. Please run from Rails app root.")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Raised when required external tools are missing
|
15
|
+
class MissingDependencyError < Error
|
16
|
+
def initialize(tool, installation_hint = nil)
|
17
|
+
message = "Required tool '#{tool}' is not installed or not in PATH."
|
18
|
+
message += " #{installation_hint}" if installation_hint
|
19
|
+
super(message)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Raised when Docker is not running or accessible
|
24
|
+
class DockerNotAvailableError < MissingDependencyError
|
25
|
+
def initialize
|
26
|
+
super(
|
27
|
+
"Docker",
|
28
|
+
"Please ensure Docker is installed and running. Visit https://docs.docker.com/get-docker/"
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Raised when version manager is not available
|
34
|
+
class VersionManagerNotAvailableError < MissingDependencyError
|
35
|
+
def initialize(manager)
|
36
|
+
hints = {
|
37
|
+
"mise" => "Install with: curl https://mise.run | sh",
|
38
|
+
"rbenv" => "Install with: brew install rbenv ruby-build",
|
39
|
+
"rvm" => "Install with: \\curl -sSL https://get.rvm.io | bash"
|
40
|
+
}
|
41
|
+
|
42
|
+
super(manager, hints[manager])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Raised when configuration is invalid
|
47
|
+
class InvalidConfigurationError < Error
|
48
|
+
def initialize(message)
|
49
|
+
super("Invalid configuration: #{message}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Raised when unsupported database is specified
|
54
|
+
class UnsupportedDatabaseError < InvalidConfigurationError
|
55
|
+
def initialize(database)
|
56
|
+
supported = %w[postgresql mysql sqlite3]
|
57
|
+
super("Database '#{database}' is not supported. Supported: #{supported.join(", ")}")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Raised when unsupported version manager is specified
|
62
|
+
class UnsupportedVersionManagerError < InvalidConfigurationError
|
63
|
+
def initialize(manager)
|
64
|
+
supported = %w[mise rbenv rvm none]
|
65
|
+
super("Version manager '#{manager}' is not supported. Supported: #{supported.join(", ")}")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Raised when unsupported container option is specified
|
70
|
+
class UnsupportedContainerError < InvalidConfigurationError
|
71
|
+
def initialize(container)
|
72
|
+
supported = %w[docker-compose docker none]
|
73
|
+
super("Container option '#{container}' is not supported. Supported: #{supported.join(", ")}")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Raised when file operations fail
|
78
|
+
class FileOperationError < Error
|
79
|
+
def initialize(operation, path, reason = nil)
|
80
|
+
message = "Failed to #{operation} '#{path}'"
|
81
|
+
message += ": #{reason}" if reason
|
82
|
+
super(message)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Raised when template is missing or invalid
|
87
|
+
class TemplateError < Error
|
88
|
+
def initialize(template_path, reason = nil)
|
89
|
+
message = "Template error for '#{template_path}'"
|
90
|
+
message += ": #{reason}" if reason
|
91
|
+
super(message)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Raised when generator fails
|
96
|
+
class GeneratorError < Error
|
97
|
+
def initialize(generator_name, reason)
|
98
|
+
super("Generator '#{generator_name}' failed: #{reason}")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Raised when service fails to start (Docker containers, etc.)
|
103
|
+
class ServiceStartupError < Error
|
104
|
+
def initialize(service_name, timeout = nil)
|
105
|
+
message = "Service '#{service_name}' failed to start"
|
106
|
+
message += " within #{timeout} seconds" if timeout
|
107
|
+
super(message)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Raised when database operations fail
|
112
|
+
class DatabaseError < Error
|
113
|
+
def initialize(operation, database, reason = nil)
|
114
|
+
message = "Database operation '#{operation}' failed for #{database}"
|
115
|
+
message += ": #{reason}" if reason
|
116
|
+
super(message)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Raised when Ruby version conflicts occur
|
121
|
+
class RubyVersionError < Error
|
122
|
+
def initialize(required, current)
|
123
|
+
super("Ruby version mismatch. Required: #{required}, Current: #{current}")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Raised when conflicting configurations are detected
|
128
|
+
class ConfigurationConflictError < InvalidConfigurationError
|
129
|
+
def initialize(conflict_description)
|
130
|
+
super("Configuration conflict: #{conflict_description}")
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Raised when backup operations fail
|
135
|
+
class BackupError < FileOperationError
|
136
|
+
def initialize(path, reason = nil)
|
137
|
+
super("backup", path, reason)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Raised when network operations fail (downloading, etc.)
|
142
|
+
class NetworkError < Error
|
143
|
+
def initialize(operation, reason = nil)
|
144
|
+
message = "Network operation failed: #{operation}"
|
145
|
+
message += " (#{reason})" if reason
|
146
|
+
super(message)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|