railsforge 1.0.1 → 2.0.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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +105 -444
  3. data/lib/railsforge/analyzers/controller_analyzer.rb +29 -55
  4. data/lib/railsforge/analyzers/database_analyzer.rb +16 -30
  5. data/lib/railsforge/analyzers/metrics_analyzer.rb +8 -22
  6. data/lib/railsforge/analyzers/model_analyzer.rb +29 -46
  7. data/lib/railsforge/analyzers/performance_analyzer.rb +34 -94
  8. data/lib/railsforge/analyzers/refactor_analyzer.rb +77 -57
  9. data/lib/railsforge/analyzers/security_analyzer.rb +34 -91
  10. data/lib/railsforge/analyzers/spec_analyzer.rb +17 -31
  11. data/lib/railsforge/cli.rb +14 -636
  12. data/lib/railsforge/cli_minimal.rb +8 -55
  13. data/lib/railsforge/doctor.rb +52 -225
  14. data/lib/railsforge/formatter.rb +102 -0
  15. data/lib/railsforge/issue.rb +23 -0
  16. data/lib/railsforge/loader.rb +5 -54
  17. data/lib/railsforge/version.rb +1 -1
  18. metadata +16 -77
  19. data/lib/railsforge/api_generator.rb +0 -397
  20. data/lib/railsforge/audit.rb +0 -289
  21. data/lib/railsforge/config.rb +0 -181
  22. data/lib/railsforge/database_analyzer.rb +0 -300
  23. data/lib/railsforge/feature_generator.rb +0 -560
  24. data/lib/railsforge/generator.rb +0 -313
  25. data/lib/railsforge/generators/base_generator.rb +0 -70
  26. data/lib/railsforge/generators/demo_generator.rb +0 -307
  27. data/lib/railsforge/generators/devops_generator.rb +0 -287
  28. data/lib/railsforge/generators/monitoring_generator.rb +0 -134
  29. data/lib/railsforge/generators/service_generator.rb +0 -122
  30. data/lib/railsforge/generators/stimulus_controller_generator.rb +0 -129
  31. data/lib/railsforge/generators/test_generator.rb +0 -289
  32. data/lib/railsforge/generators/view_component_generator.rb +0 -169
  33. data/lib/railsforge/graph.rb +0 -270
  34. data/lib/railsforge/mailer_generator.rb +0 -191
  35. data/lib/railsforge/plugins/plugin_loader.rb +0 -60
  36. data/lib/railsforge/plugins.rb +0 -30
  37. data/lib/railsforge/profiles.rb +0 -99
  38. data/lib/railsforge/refactor_analyzer.rb +0 -401
  39. data/lib/railsforge/refactor_controller.rb +0 -277
  40. data/lib/railsforge/refactors/refactor_controller.rb +0 -117
  41. data/lib/railsforge/template_loader.rb +0 -105
  42. data/lib/railsforge/templates/v1/form/spec_template.rb +0 -18
  43. data/lib/railsforge/templates/v1/form/template.rb +0 -28
  44. data/lib/railsforge/templates/v1/job/spec_template.rb +0 -17
  45. data/lib/railsforge/templates/v1/job/template.rb +0 -13
  46. data/lib/railsforge/templates/v1/policy/spec_template.rb +0 -41
  47. data/lib/railsforge/templates/v1/policy/template.rb +0 -57
  48. data/lib/railsforge/templates/v1/presenter/spec_template.rb +0 -12
  49. data/lib/railsforge/templates/v1/presenter/template.rb +0 -13
  50. data/lib/railsforge/templates/v1/query/spec_template.rb +0 -12
  51. data/lib/railsforge/templates/v1/query/template.rb +0 -16
  52. data/lib/railsforge/templates/v1/serializer/spec_template.rb +0 -13
  53. data/lib/railsforge/templates/v1/serializer/template.rb +0 -11
  54. data/lib/railsforge/templates/v1/service/spec_template.rb +0 -12
  55. data/lib/railsforge/templates/v1/service/template.rb +0 -25
  56. data/lib/railsforge/templates/v1/stimulus_controller/template.rb +0 -35
  57. data/lib/railsforge/templates/v1/view_component/template.rb +0 -24
  58. data/lib/railsforge/templates/v2/job/template.rb +0 -49
  59. data/lib/railsforge/templates/v2/query/template.rb +0 -66
  60. data/lib/railsforge/templates/v2/service/spec_template.rb +0 -33
  61. data/lib/railsforge/templates/v2/service/template.rb +0 -71
  62. data/lib/railsforge/templates/v3/job/template.rb +0 -72
  63. data/lib/railsforge/templates/v3/query/spec_template.rb +0 -54
  64. data/lib/railsforge/templates/v3/query/template.rb +0 -115
  65. data/lib/railsforge/templates/v3/service/spec_template.rb +0 -51
  66. data/lib/railsforge/templates/v3/service/template.rb +0 -93
  67. data/lib/railsforge/wizard.rb +0 -265
  68. data/lib/railsforge/wizard_tui.rb +0 -286
@@ -1,270 +0,0 @@
1
- # Graph module for RailsForge
2
- # Generates dependency graphs using Graphviz
3
-
4
- require 'fileutils'
5
-
6
- module RailsForge
7
- # Graph class generates visual dependency graphs
8
- class Graph
9
- # Initialize the graph generator
10
- def initialize(base_path = nil)
11
- @base_path = base_path || find_rails_app_path
12
- raise GraphError, "Not in a Rails application directory" unless @base_path
13
-
14
- @output_dir = File.join(@base_path, "tmp", "railsforge")
15
- @relationships = []
16
- end
17
-
18
- # Generate dependency graph
19
- # @return [String] Path to generated files
20
- def generate
21
- puts "Analyzing Rails app structure..."
22
- puts ""
23
-
24
- # Collect relationships
25
- analyze_controllers
26
- analyze_services
27
- analyze_queries
28
- analyze_models
29
- analyze_jobs
30
-
31
- # Create output directory
32
- FileUtils.mkdir_p(@output_dir)
33
-
34
- # Generate DOT file
35
- dot_file = generate_dot_file
36
-
37
- # Generate SVG if Graphviz is available
38
- svg_file = generate_svg(dot_file)
39
-
40
- puts "Dependency graph generated!"
41
- puts " DOT: #{dot_file}"
42
- puts " SVG: #{svg_file}" if svg_file
43
-
44
- { dot: dot_file, svg: svg_file }
45
- end
46
-
47
- private
48
-
49
- # Find Rails app path
50
- def find_rails_app_path
51
- path = Dir.pwd
52
- max_depth = 10
53
-
54
- max_depth.times do
55
- return path if File.exist?(File.join(path, "config", "application.rb"))
56
- parent = File.dirname(path)
57
- break if parent == path
58
- path = parent
59
- end
60
-
61
- nil
62
- end
63
-
64
- # Analyze controllers for dependencies
65
- def analyze_controllers
66
- controllers_dir = File.join(@base_path, "app", "controllers")
67
- return unless Dir.exist?(controllers_dir)
68
-
69
- Dir.glob(File.join(controllers_dir, "**", "*_controller.rb")).each do |file|
70
- next if file.end_with?("_application_controller.rb")
71
-
72
- controller_name = File.basename(file, "_controller.rb").camelize
73
- content = File.read(file)
74
-
75
- # Find service dependencies
76
- content.scan(/(\w+Service)\.call/).each do |service|
77
- @relationships << { from: controller_name, to: service[0], type: "uses" }
78
- end
79
-
80
- # Find query dependencies
81
- content.scan(/(\w+Query)\.new|(\w+)\.find_/).each do |match|
82
- query = match[0] || match[1]
83
- @relationships << { from: controller_name, to: query, type: "queries" }
84
- end
85
-
86
- # Find model dependencies
87
- content.scan(/@(\w+)\s*=/).each do |model|
88
- @relationships << { from: controller_name, to: model[0].singularize.camelize, type: "uses" }
89
- end
90
-
91
- # Find job dependencies
92
- content.scan(/(\w+Job)\.perform_later/).each do |job|
93
- @relationships << { from: controller_name, to: job[0], type: "enqueues" }
94
- end
95
- end
96
- end
97
-
98
- # Analyze services for dependencies
99
- def analyze_services
100
- services_dir = File.join(@base_path, "app", "services")
101
- return unless Dir.exist?(services_dir)
102
-
103
- Dir.glob(File.join(services_dir, "**", "*.rb")).each do |file|
104
- service_name = File.basename(file, ".rb").camelize
105
- content = File.read(file)
106
-
107
- # Find model dependencies
108
- content.scan(/(\w+)\.find|(\w+)\.where/).each do |match|
109
- model = match[0] || match[1]
110
- @relationships << { from: service_name, to: model.singularize.camelize, type: "queries" }
111
- end
112
-
113
- # Find other service dependencies
114
- content.scan(/(\w+Service)\.call/).each do |service|
115
- @relationships << { from: service_name, to: service[0], type: "uses" }
116
- end
117
-
118
- # Find job dependencies
119
- content.scan(/(\w+Job)\.perform/).each do |job|
120
- @relationships << { from: service_name, to: job[0], type: "enqueues" }
121
- end
122
- end
123
- end
124
-
125
- # Analyze queries for dependencies
126
- def analyze_queries
127
- queries_dir = File.join(@base_path, "app", "queries")
128
- return unless Dir.exist?(queries_dir)
129
-
130
- Dir.glob(File.join(queries_dir, "**", "*.rb")).each do |file|
131
- query_name = File.basename(file, ".rb").camelize
132
- content = File.read(file)
133
-
134
- # Find model dependencies
135
- content.scan(/(\w+)\.find|(\w+)\.where/).each do |match|
136
- model = match[0] || match[1]
137
- @relationships << { from: query_name, to: model.singularize.camelize, type: "queries" }
138
- end
139
- end
140
- end
141
-
142
- # Analyze models for associations
143
- def analyze_models
144
- models_dir = File.join(@base_path, "app", "models")
145
- return unless Dir.exist?(models_dir)
146
-
147
- Dir.glob(File.join(models_dir, "**", "*.rb")).each do |file|
148
- next if file.end_with?("_application_name = File.basename(file, ".rb.rb")
149
-
150
- model").camelize
151
- content = File.read(file)
152
-
153
- # Find associations
154
- content.scan(/belongs_to\s+:(\w+)/).each do |assoc|
155
- @relationships << { from: model_name, to: assoc[0].camelize, type: "belongs_to" }
156
- end
157
-
158
- content.scan(/has_many\s+:(\w+)/).each do |assoc|
159
- @relationships << { from: model_name, to: assoc[0].camelize, type: "has_many" }
160
- end
161
-
162
- content.scan(/has_one\s+:(\w+)/).each do |assoc|
163
- @relationships << { from: model_name, to: assoc[0].camelize, type: "has_one" }
164
- end
165
- end
166
- end
167
-
168
- # Analyze jobs for dependencies
169
- def analyze_jobs
170
- jobs_dir = File.join(@base_path, "app", "jobs")
171
- return unless Dir.exist?(jobs_dir)
172
-
173
- Dir.glob(File.join(jobs_dir, "**", "*.rb")).each do |file|
174
- job_name = File.basename(file, ".rb").camelize
175
- content = File.read(file)
176
-
177
- # Find service dependencies
178
- content.scan(/(\w+Service)\.call/).each do |service|
179
- @relationships << { from: job_name, to: service[0], type: "uses" }
180
- end
181
- end
182
- end
183
-
184
- # Generate DOT file
185
- def generate_dot_file
186
- dot_path = File.join(@output_dir, "dependency_graph.dot")
187
-
188
- # Define colors for different types
189
- type_colors = {
190
- "controller" => "#3498db",
191
- "service" => "#2ecc71",
192
- "query" => "#9b59b6",
193
- "model" => "#e74c3c",
194
- "job" => "#f39c12",
195
- "uses" => "#95a5a6",
196
- "queries" => "#95a5a6",
197
- "enqueues" => "#95a5a6",
198
- "belongs_to" => "#e67e22",
199
- "has_many" => "#1abc9c",
200
- "has_one" => "#16a085"
201
- }
202
-
203
- dot_content = []
204
- dot_content << "digraph RailsApp {"
205
- dot_content << " rankdir=LR;"
206
- dot_content << " node [shape=box, style=rounded];"
207
- dot_content << ""
208
-
209
- # Collect all nodes
210
- nodes = Set.new
211
- @relationships.each do |rel|
212
- nodes.add(rel[:from])
213
- nodes.add(rel[:to])
214
- end
215
-
216
- # Define node styles
217
- nodes.each do |node|
218
- type = detect_node_type(node)
219
- color = type_colors[type] || "#95a5a6"
220
- dot_content << " \"#{node}\" [fillcolor=\"#{color}\", style=filled, fontcolor=white];"
221
- end
222
-
223
- dot_content << ""
224
-
225
- # Define edges
226
- @relationships.each do |rel|
227
- style = "solid"
228
- color = type_colors[rel[:type]] || "#95a5a6"
229
- arrow = "normal"
230
-
231
- if %w[belongs_to has_many has_one].include?(rel[:type])
232
- style = "dashed"
233
- end
234
-
235
- dot_content << " \"#{rel[:from]}\" -> \"#{rel[:to]}\" [style=#{style}, color=#{color}, arrowhead=#{arrow}];"
236
- end
237
-
238
- dot_content << "}"
239
-
240
- File.write(dot_path, dot_content.join("\n"))
241
- dot_path
242
- end
243
-
244
- # Detect node type based on name
245
- def detect_node_type(name)
246
- return "controller" if name.downcase.end_with?("controller")
247
- return "service" if name.downcase.end_with?("service")
248
- return "query" if name.downcase.start_with?("find") || name.downcase.end_with?("query")
249
- return "model" if name.downcase.end_with?("job")
250
- return "job" if name.downcase.end_with?("job")
251
- "model"
252
- end
253
-
254
- # Generate SVG from DOT file
255
- def generate_svg(dot_path)
256
- svg_path = dot_path.sub(".dot", ".svg")
257
-
258
- # Check if Graphviz is installed
259
- return nil unless system("which dot > /dev/null 2>&1")
260
-
261
- system("dot -Tsvg #{dot_path} -o #{svg_path} 2>/dev/null")
262
- svg_path if File.exist?(svg_path)
263
- rescue
264
- nil
265
- end
266
-
267
- # Error class
268
- class GraphError < StandardError; end
269
- end
270
- end
@@ -1,191 +0,0 @@
1
- module RailsForge
2
- # MailerGenerator module handles generating mailers
3
- module MailerGenerator
4
- # Error class for invalid mailer names
5
- class InvalidMailerNameError < StandardError; end
6
-
7
- # Validates the mailer name to ensure it's valid Ruby class name
8
- def self.validate_mailer_name(name)
9
- if name.nil? || name.strip.empty?
10
- raise InvalidMailerNameError, "Mailer name cannot be empty"
11
- end
12
-
13
- unless name =~ /\A[A-Z][a-zA-Z0-9]*\z/
14
- raise InvalidMailerNameError, "Mailer name must be in PascalCase (e.g., UserMailer)"
15
- end
16
- end
17
-
18
- # Generates a mailer file with views and optionally an RSpec test
19
- def self.generate(mailer_name, with_spec: true, with_jobs: false)
20
- validate_mailer_name(mailer_name)
21
-
22
- base_path = find_rails_app_path
23
- unless base_path
24
- raise "Not in a Rails application directory"
25
- end
26
-
27
- generate_mailer_file(base_path, mailer_name, with_jobs)
28
- generate_view_files(base_path, mailer_name)
29
-
30
- if with_spec
31
- generate_spec_file(base_path, mailer_name)
32
- end
33
-
34
- "Mailer '#{mailer_name}' generated successfully!"
35
- end
36
-
37
- def self.generate_mailer_file(base_path, mailer_name, with_jobs = false)
38
- mailer_dir = File.join(base_path, "app", "mailers")
39
- FileUtils.mkdir_p(mailer_dir)
40
-
41
- file_name = "#{mailer_name.underscore}.rb"
42
- file_path = File.join(mailer_dir, file_name)
43
-
44
- if File.exist?(file_path)
45
- puts " Skipping mailer (already exists)"
46
- return file_path
47
- end
48
-
49
- File.write(file_path, mailer_template(mailer_name, with_jobs))
50
- puts " Created app/mailers/#{file_name}"
51
- file_path
52
- end
53
-
54
- def self.generate_view_files(base_path, mailer_name)
55
- views_dir = File.join(base_path, "app", "views", mailer_name.underscore)
56
- FileUtils.mkdir_p(views_dir)
57
-
58
- methods = ["welcome_email", "notification_email"]
59
-
60
- methods.each do |method|
61
- html_path = File.join(views_dir, "#{method}.html.erb")
62
- unless File.exist?(html_path)
63
- File.write(html_path, view_html_template(mailer_name, method))
64
- puts " Created app/views/#{mailer_name.underscore}/#{method}.html.erb"
65
- end
66
-
67
- text_path = File.join(views_dir, "#{method}.text.erb")
68
- unless File.exist?(text_path)
69
- File.write(text_path, view_text_template(mailer_name, method))
70
- puts " Created app/views/#{mailer_name.underscore}/#{method}.text.erb"
71
- end
72
- end
73
- end
74
-
75
- def self.generate_spec_file(base_path, mailer_name)
76
- spec_dir = File.join(base_path, "spec", "mailers")
77
- FileUtils.mkdir_p(spec_dir)
78
-
79
- file_name = "#{mailer_name.underscore}_spec.rb"
80
- file_path = File.join(spec_dir, file_name)
81
-
82
- if File.exist?(file_path)
83
- puts " Skipping spec (already exists)"
84
- return file_path
85
- end
86
-
87
- File.write(file_path, spec_template(mailer_name))
88
- puts " Created spec/mailers/#{file_name}"
89
- file_path
90
- end
91
-
92
- def self.find_rails_app_path
93
- path = Dir.pwd
94
- 10.times do
95
- return path if File.exist?(File.join(path, "config", "application.rb"))
96
- parent = File.dirname(path)
97
- break if parent == path
98
- path = parent
99
- end
100
- nil
101
- end
102
-
103
- def self.mailer_template(mailer_name, with_jobs = false)
104
- <<~RUBY
105
- # Mailer class for #{mailer_name}
106
- # Handles sending emails related to #{mailer_name.underscore}
107
- #
108
- # Usage:
109
- # #{mailer_name}.with(user: @user).welcome_email.deliver_later
110
- # #{mailer_name}.with(user: @user).notification_email.deliver_now
111
- class #{mailer_name} < ApplicationMailer
112
- # Sends a welcome email to the user
113
- # @param user [User] The user to send the welcome email to
114
- # @return [Mail::Message] The sent mail message
115
- def welcome_email(user)
116
- mail(to: user.email, subject: "Welcome to Our App!")
117
- end
118
-
119
- # Sends a notification email to the user
120
- # @param user [User] The user to send the notification to
121
- # @param message [String] The notification message
122
- # @return [Mail::Message] The sent mail message
123
- def notification_email(user, message: "You have a new notification")
124
- @user = user
125
- @message = message
126
- mail(to: user.email, subject: "Notification from Our App")
127
- end
128
-
129
- # Class method to queue email via ActiveJob
130
- # Use with --jobs flag for background delivery
131
- # def self.deliver_with_job(user, method: :welcome_email, **args)
132
- # #{mailer_name}Job.perform_later(user, method: method, **args)
133
- # end
134
- end
135
- RUBY
136
- end
137
-
138
- def self.view_html_template(mailer_name, method_name)
139
- <<~ERB
140
- <!DOCTYPE html>
141
- <html>
142
- <head>
143
- <meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
144
- <style>body { font-family: Arial, sans-serif; }</style>
145
- </head>
146
- <body>
147
- <h1>#{method_name == 'welcome_email' ? 'Welcome!' : 'Notification'}</h1>
148
- #{method_name == 'welcome_email' ? '<p>Welcome to our application!</p>' : '<p><%= @message %></p>'}
149
- </body>
150
- </html>
151
- ERB
152
- end
153
-
154
- def self.view_text_template(mailer_name, method_name)
155
- <<~TEXT
156
- #{method_name == 'welcome_email' ? 'Welcome to our application!' : '<%= @message %>'}
157
-
158
- --
159
- Our App
160
- TEXT
161
- end
162
-
163
- def self.spec_template(mailer_name)
164
- <<~RUBY
165
- require "rails_helper"
166
-
167
- RSpec.describe #{mailer_name}, type: :mailer do
168
- describe "welcome_email" do
169
- let(:user) { User.create!(name: "Test", email: "test@example.com") }
170
- let(:mail) { described_class.welcome_email(user) }
171
-
172
- it "renders the headers" do
173
- expect(mail.subject).to eq("Welcome to Our App!")
174
- expect(mail.to).to eq([user.email])
175
- end
176
- end
177
-
178
- describe "notification_email" do
179
- let(:user) { User.create!(name: "Test", email: "test@example.com") }
180
- let(:mail) { described_class.notification_email(user, message: "Test") }
181
-
182
- it "renders the body" do
183
- expect(mail.body.encoded).to include("Test")
184
- end
185
- end
186
- end
187
- RUBY
188
- end
189
- end
190
-
191
- end
@@ -1,60 +0,0 @@
1
- # Plugin loader for RailsForge
2
- # Handles plugin discovery and loading
3
-
4
- require 'fileutils'
5
-
6
- module RailsForge
7
- module Plugins
8
- # PluginLoader manages RailsForge plugins
9
- class PluginLoader
10
- PLUGINS_DIR = File.expand_path('../../plugins', __FILE__)
11
-
12
- # List available plugins
13
- def self.list
14
- return [] unless Dir.exist?(PLUGINS_DIR)
15
-
16
- Dir.glob(File.join(PLUGINS_DIR, "*.rb")).map do |file|
17
- File.basename(file, ".rb")
18
- end
19
- end
20
-
21
- # Load a plugin
22
- def self.load(plugin_name)
23
- plugin_path = File.join(PLUGINS_DIR, "#{plugin_name}.rb")
24
-
25
- unless File.exist?(plugin_path)
26
- puts "Plugin '#{plugin_name}' not found"
27
- return false
28
- end
29
-
30
- require plugin_path
31
- puts "Plugin '#{plugin_name}' loaded"
32
- true
33
- end
34
-
35
- # Load all plugins
36
- def self.load_all
37
- list.each { |plugin| load(plugin) }
38
- end
39
-
40
- # Create a new plugin
41
- def self.create(plugin_name)
42
- FileUtils.mkdir_p(PLUGINS_DIR)
43
-
44
- path = File.join(PLUGINS_DIR, "#{plugin_name}.rb")
45
- return if File.exist?(path)
46
-
47
- File.write(path, <<~RUBY)
48
- module RailsForge
49
- module #{plugin_name.split('_').map(&:capitalize).join}
50
- PLUGIN_NAME = "#{plugin_name}"
51
- VERSION = "0.1.0"
52
- end
53
- end
54
- RUBY
55
-
56
- puts "Created #{path}"
57
- end
58
- end
59
- end
60
- end
@@ -1,30 +0,0 @@
1
- # RailsForge Plugins module
2
- # Provides plugin management functionality
3
-
4
- require_relative "plugins/plugin_loader"
5
-
6
- module RailsForge
7
- module Plugins
8
- # Show installed/available plugins
9
- def self.show_installed
10
- plugins = PluginLoader.list
11
- if plugins.empty?
12
- puts "No plugins installed."
13
- puts "Create one with: railsforge plugins create <name>"
14
- else
15
- puts "Available plugins:"
16
- plugins.each { |p| puts " - #{p}" }
17
- end
18
- end
19
-
20
- # Load a plugin by name
21
- def self.load(plugin_name)
22
- PluginLoader.load(plugin_name)
23
- end
24
-
25
- # Create a new plugin
26
- def self.create(plugin_name)
27
- PluginLoader.create(plugin_name)
28
- end
29
- end
30
- end
@@ -1,99 +0,0 @@
1
- # ProfileLoader module handles loading RailsForge profiles
2
- require 'yaml'
3
-
4
- module RailsForge
5
- # Profile module for managing application profiles
6
- module Profile
7
- # Error class for profile loading issues
8
- class ProfileError < StandardError; end
9
-
10
- # Profile directory path
11
- PROFILES_DIR = File.expand_path('../profiles', __FILE__)
12
-
13
- # Load a profile by name
14
- # @param profile_name [String] The name of the profile to load
15
- # @return [Hash] The profile configuration
16
- # @raises [ProfileError] If profile not found or invalid
17
- def self.load(profile_name)
18
- profile_path = File.join(PROFILES_DIR, "#{profile_name}.yml")
19
-
20
- unless File.exist?(profile_path)
21
- raise ProfileError, "Profile '#{profile_name}' not found. Available profiles: #{available_profiles.join(', ')}"
22
- end
23
-
24
- begin
25
- YAML.safe_load(File.read(profile_path), permitted_classes: [], permitted_symbols: [], aliases: true)
26
- rescue => e
27
- raise ProfileError, "Failed to load profile '#{profile_name}': #{e.message}"
28
- end
29
- end
30
-
31
- # Get list of available profiles
32
- # @return [Array<String>] Array of profile names
33
- def self.available_profiles
34
- return [] unless Dir.exist?(PROFILES_DIR)
35
-
36
- Dir.glob(File.join(PROFILES_DIR, '*.yml')).map do |file|
37
- File.basename(file, '.yml')
38
- end.sort
39
- end
40
-
41
- # Validate a profile configuration
42
- # @param profile [Hash] The profile configuration to validate
43
- # @return [Boolean] True if valid
44
- def self.validate?(profile)
45
- return false unless profile.is_a?(Hash)
46
-
47
- required_keys = %w[name description folders]
48
- required_keys.all? { |key| profile.key?(key) }
49
- end
50
-
51
- # Create folders defined in profile
52
- # @param base_path [String] The Rails app root path
53
- # @param profile [Hash] The profile configuration
54
- def self.create_folders(base_path, profile)
55
- folders = profile['folders'] || []
56
-
57
- folders.each do |folder|
58
- full_path = File.join(base_path, folder)
59
- unless Dir.exist?(full_path)
60
- FileUtils.mkdir_p(full_path)
61
- puts " Created #{folder}"
62
- end
63
- end
64
- end
65
-
66
- # Get default features from profile
67
- # @param profile [Hash] The profile configuration
68
- # @return [Array<String>] Array of feature names
69
- def self.default_features(profile)
70
- profile['features'] || []
71
- end
72
-
73
- # Get default template variables from profile
74
- # @param profile [Hash] The profile configuration
75
- # @return [Hash] Default template variables
76
- def self.defaults(profile)
77
- profile['defaults'] || {}
78
- end
79
-
80
- # Show available profiles with descriptions
81
- # @return [String] Formatted list of profiles
82
- def self.show_available
83
- profiles = available_profiles
84
-
85
- if profiles.empty?
86
- return "No profiles available."
87
- end
88
-
89
- output = "Available profiles:\n\n"
90
-
91
- profiles.each do |name|
92
- profile = load(name)
93
- output += " #{name.ljust(15)} - #{profile['description']}\n"
94
- end
95
-
96
- output
97
- end
98
- end
99
- end