railsforge 1.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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +528 -0
- data/bin/railsforge +8 -0
- data/lib/railsforge/analyzers/base_analyzer.rb +41 -0
- data/lib/railsforge/analyzers/controller_analyzer.rb +83 -0
- data/lib/railsforge/analyzers/database_analyzer.rb +55 -0
- data/lib/railsforge/analyzers/metrics_analyzer.rb +55 -0
- data/lib/railsforge/analyzers/model_analyzer.rb +74 -0
- data/lib/railsforge/analyzers/performance_analyzer.rb +161 -0
- data/lib/railsforge/analyzers/refactor_analyzer.rb +118 -0
- data/lib/railsforge/analyzers/security_analyzer.rb +169 -0
- data/lib/railsforge/analyzers/spec_analyzer.rb +58 -0
- data/lib/railsforge/api_generator.rb +397 -0
- data/lib/railsforge/audit.rb +289 -0
- data/lib/railsforge/cli.rb +671 -0
- data/lib/railsforge/config.rb +181 -0
- data/lib/railsforge/database_analyzer.rb +300 -0
- data/lib/railsforge/doctor.rb +250 -0
- data/lib/railsforge/feature_generator.rb +560 -0
- data/lib/railsforge/generator.rb +313 -0
- data/lib/railsforge/generators/base_generator.rb +70 -0
- data/lib/railsforge/generators/demo_generator.rb +307 -0
- data/lib/railsforge/generators/devops_generator.rb +287 -0
- data/lib/railsforge/generators/monitoring_generator.rb +134 -0
- data/lib/railsforge/generators/service_generator.rb +122 -0
- data/lib/railsforge/generators/stimulus_controller_generator.rb +129 -0
- data/lib/railsforge/generators/test_generator.rb +289 -0
- data/lib/railsforge/generators/view_component_generator.rb +169 -0
- data/lib/railsforge/graph.rb +270 -0
- data/lib/railsforge/loader.rb +56 -0
- data/lib/railsforge/mailer_generator.rb +191 -0
- data/lib/railsforge/plugins/plugin_loader.rb +60 -0
- data/lib/railsforge/plugins.rb +30 -0
- data/lib/railsforge/profiles/admin_app.yml +49 -0
- data/lib/railsforge/profiles/api_only.yml +47 -0
- data/lib/railsforge/profiles/blog.yml +47 -0
- data/lib/railsforge/profiles/standard.yml +44 -0
- data/lib/railsforge/profiles.rb +99 -0
- data/lib/railsforge/refactor_analyzer.rb +401 -0
- data/lib/railsforge/refactor_controller.rb +277 -0
- data/lib/railsforge/refactors/refactor_controller.rb +117 -0
- data/lib/railsforge/template_loader.rb +105 -0
- data/lib/railsforge/templates/v1/form/spec_template.rb +18 -0
- data/lib/railsforge/templates/v1/form/template.rb +28 -0
- data/lib/railsforge/templates/v1/job/spec_template.rb +17 -0
- data/lib/railsforge/templates/v1/job/template.rb +13 -0
- data/lib/railsforge/templates/v1/policy/spec_template.rb +41 -0
- data/lib/railsforge/templates/v1/policy/template.rb +57 -0
- data/lib/railsforge/templates/v1/presenter/spec_template.rb +12 -0
- data/lib/railsforge/templates/v1/presenter/template.rb +13 -0
- data/lib/railsforge/templates/v1/query/spec_template.rb +12 -0
- data/lib/railsforge/templates/v1/query/template.rb +16 -0
- data/lib/railsforge/templates/v1/serializer/spec_template.rb +13 -0
- data/lib/railsforge/templates/v1/serializer/template.rb +11 -0
- data/lib/railsforge/templates/v1/service/spec_template.rb +12 -0
- data/lib/railsforge/templates/v1/service/template.rb +25 -0
- data/lib/railsforge/templates/v1/stimulus_controller/template.rb +35 -0
- data/lib/railsforge/templates/v1/view_component/template.rb +24 -0
- data/lib/railsforge/templates/v2/job/template.rb +49 -0
- data/lib/railsforge/templates/v2/query/template.rb +66 -0
- data/lib/railsforge/templates/v2/service/spec_template.rb +33 -0
- data/lib/railsforge/templates/v2/service/template.rb +71 -0
- data/lib/railsforge/templates/v3/job/template.rb +72 -0
- data/lib/railsforge/templates/v3/query/spec_template.rb +54 -0
- data/lib/railsforge/templates/v3/query/template.rb +115 -0
- data/lib/railsforge/templates/v3/service/spec_template.rb +51 -0
- data/lib/railsforge/templates/v3/service/template.rb +84 -0
- data/lib/railsforge/version.rb +5 -0
- data/lib/railsforge/wizard.rb +265 -0
- data/lib/railsforge/wizard_tui.rb +286 -0
- data/lib/railsforge.rb +13 -0
- metadata +216 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# Doctor module for RailsForge
|
|
2
|
+
# Provides a comprehensive project health report
|
|
3
|
+
|
|
4
|
+
module RailsForge
|
|
5
|
+
# Doctor class provides a full project health check
|
|
6
|
+
class Doctor
|
|
7
|
+
# Initialize the Doctor analyzer
|
|
8
|
+
def initialize(base_path = nil)
|
|
9
|
+
@base_path = base_path || find_rails_app_path
|
|
10
|
+
raise DoctorError, "Not in a Rails application directory" unless @base_path
|
|
11
|
+
|
|
12
|
+
@results = {
|
|
13
|
+
controllers: [],
|
|
14
|
+
models: [],
|
|
15
|
+
database: [],
|
|
16
|
+
specs: [],
|
|
17
|
+
refactors: [],
|
|
18
|
+
metrics: {}
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Run all analyzers and generate report
|
|
23
|
+
# @return [Hash] Complete health check results
|
|
24
|
+
def run
|
|
25
|
+
puts "Running RailsForge Doctor..."
|
|
26
|
+
puts ""
|
|
27
|
+
|
|
28
|
+
# Run all analyzers
|
|
29
|
+
run_analyzers
|
|
30
|
+
|
|
31
|
+
# Calculate score
|
|
32
|
+
@results[:score] = calculate_score
|
|
33
|
+
|
|
34
|
+
# Print report
|
|
35
|
+
print_report
|
|
36
|
+
|
|
37
|
+
@results
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
# Run all available analyzers
|
|
43
|
+
def run_analyzers
|
|
44
|
+
# Controller analysis
|
|
45
|
+
begin
|
|
46
|
+
@results[:controllers] = RailsForge::Analyzers::RefactorAnalyzer.analyze_controllers(@base_path)
|
|
47
|
+
rescue => e
|
|
48
|
+
@results[:controllers] = []
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Model analysis
|
|
52
|
+
begin
|
|
53
|
+
@results[:models] = RailsForge::Analyzers::RefactorAnalyzer.analyze_models(@base_path)
|
|
54
|
+
rescue => e
|
|
55
|
+
@results[:models] = []
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Database analysis
|
|
59
|
+
begin
|
|
60
|
+
@results[:database] = RailsForge::Analyzers::DatabaseAnalyzer.analyze(@base_path)
|
|
61
|
+
rescue => e
|
|
62
|
+
@results[:database] = []
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Spec analysis
|
|
66
|
+
begin
|
|
67
|
+
@results[:specs] = RailsForge::Analyzers::SpecAnalyzer.analyze(@base_path)
|
|
68
|
+
rescue => e
|
|
69
|
+
@results[:specs] = []
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Refactor suggestions (from RefactorAnalyzer)
|
|
73
|
+
begin
|
|
74
|
+
@results[:refactors] = []
|
|
75
|
+
@results[:controllers].each do |ctrl|
|
|
76
|
+
@results[:refactors].concat(ctrl[:suggestions] || [])
|
|
77
|
+
end
|
|
78
|
+
@results[:models].each do |model|
|
|
79
|
+
@results[:refactors].concat(model[:suggestions] || [])
|
|
80
|
+
end
|
|
81
|
+
rescue => e
|
|
82
|
+
@results[:refactors] = []
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Metrics
|
|
86
|
+
begin
|
|
87
|
+
@results[:metrics] = RailsForge::Analyzers::MetricsAnalyzer.analyze(@base_path)
|
|
88
|
+
rescue => e
|
|
89
|
+
@results[:metrics] = {}
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Calculate architecture score
|
|
94
|
+
# @return [Integer] Score out of 100
|
|
95
|
+
def calculate_score
|
|
96
|
+
score = 100
|
|
97
|
+
|
|
98
|
+
# Subtract 5 points per fat controller
|
|
99
|
+
fat_controllers = @results[:controllers].count { |c| c[:needs_refactoring] }
|
|
100
|
+
score -= fat_controllers * 5
|
|
101
|
+
|
|
102
|
+
# Subtract 5 points per fat model
|
|
103
|
+
fat_models = @results[:models].count { |m| m[:needs_refactoring] }
|
|
104
|
+
score -= fat_models * 5
|
|
105
|
+
|
|
106
|
+
# Subtract 3 points per missing spec
|
|
107
|
+
missing_specs = @results[:specs].count { |s| s[:has_spec] == false }
|
|
108
|
+
score -= missing_specs * 3
|
|
109
|
+
|
|
110
|
+
# Subtract 2 points per missing index (database issues)
|
|
111
|
+
missing_indexes = @results[:database].count { |d| d[:type] == :index }
|
|
112
|
+
score -= missing_indexes * 2
|
|
113
|
+
|
|
114
|
+
# Subtract 2 points per long method
|
|
115
|
+
long_methods = count_long_methods
|
|
116
|
+
score -= long_methods * 2
|
|
117
|
+
|
|
118
|
+
[score, 0].max
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Count long methods in controllers and models
|
|
122
|
+
# @return [Integer] Number of long methods
|
|
123
|
+
def count_long_methods
|
|
124
|
+
count = 0
|
|
125
|
+
|
|
126
|
+
@results[:controllers].each do |ctrl|
|
|
127
|
+
methods = ctrl[:methods] || []
|
|
128
|
+
count += methods.count { |m| m[:lines].to_i > 15 }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
@results[:models].each do |model|
|
|
132
|
+
methods = model[:methods] || []
|
|
133
|
+
count += methods.count { |m| m[:lines].to_i > 15 }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
count
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Print formatted report
|
|
140
|
+
def print_report
|
|
141
|
+
score = @results[:score]
|
|
142
|
+
score_color = score >= 80 ? :green : score >= 50 ? :yellow : :red
|
|
143
|
+
|
|
144
|
+
puts "RailsForge Doctor Report"
|
|
145
|
+
puts "────────────────────────"
|
|
146
|
+
puts ""
|
|
147
|
+
puts "Architecture Score: #{colorize(score, score_color)}/100"
|
|
148
|
+
puts ""
|
|
149
|
+
|
|
150
|
+
# Controllers
|
|
151
|
+
puts "Controllers:"
|
|
152
|
+
if @results[:controllers].empty?
|
|
153
|
+
puts " ✓ No issues found"
|
|
154
|
+
else
|
|
155
|
+
@results[:controllers].each do |ctrl|
|
|
156
|
+
issues = ctrl[:issues] || []
|
|
157
|
+
if issues.any?
|
|
158
|
+
puts " ⚠ #{ctrl[:file]}: #{issues.join(', ')}"
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
puts ""
|
|
163
|
+
|
|
164
|
+
# Models
|
|
165
|
+
puts "Models:"
|
|
166
|
+
if @results[:models].empty?
|
|
167
|
+
puts " ✓ No issues found"
|
|
168
|
+
else
|
|
169
|
+
@results[:models].each do |model|
|
|
170
|
+
issues = model[:issues] || []
|
|
171
|
+
if issues.any?
|
|
172
|
+
puts " ⚠ #{model[:file]}: #{issues.join(', ')}"
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
puts ""
|
|
177
|
+
|
|
178
|
+
# Database
|
|
179
|
+
puts "Database:"
|
|
180
|
+
if @results[:database].empty?
|
|
181
|
+
puts " ✓ No issues found"
|
|
182
|
+
else
|
|
183
|
+
@results[:database].first(5).each do |db|
|
|
184
|
+
puts " ⚠ #{db[:table]}: #{db[:issue]}"
|
|
185
|
+
end
|
|
186
|
+
puts " ... and #{@results[:database].count - 5} more" if @results[:database].count > 5
|
|
187
|
+
end
|
|
188
|
+
puts ""
|
|
189
|
+
|
|
190
|
+
# Specs
|
|
191
|
+
missing_specs = @results[:specs].select { |s| s[:has_spec] == false }
|
|
192
|
+
puts "Specs:"
|
|
193
|
+
if missing_specs.empty?
|
|
194
|
+
puts " ✓ All components have specs"
|
|
195
|
+
else
|
|
196
|
+
puts " ⚠ #{missing_specs.count} missing specs:"
|
|
197
|
+
missing_specs.first(5).each do |spec|
|
|
198
|
+
puts " - #{spec[:type]}: #{spec[:name]}"
|
|
199
|
+
end
|
|
200
|
+
puts " ... and #{missing_specs.count - 5} more" if missing_specs.count > 5
|
|
201
|
+
end
|
|
202
|
+
puts ""
|
|
203
|
+
|
|
204
|
+
# Suggestions
|
|
205
|
+
puts "Suggestions:"
|
|
206
|
+
if @results[:refactors].empty?
|
|
207
|
+
puts " ✓ No refactoring suggestions"
|
|
208
|
+
else
|
|
209
|
+
@results[:refactors].first(10).each do |suggestion|
|
|
210
|
+
puts " • #{suggestion}"
|
|
211
|
+
end
|
|
212
|
+
puts " ... and #{@results[:refactors].count - 10} more" if @results[:refactors].count > 10
|
|
213
|
+
end
|
|
214
|
+
puts ""
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Find Rails app path
|
|
218
|
+
# @return [String, nil] Rails app root path
|
|
219
|
+
def find_rails_app_path
|
|
220
|
+
path = Dir.pwd
|
|
221
|
+
max_depth = 10
|
|
222
|
+
|
|
223
|
+
max_depth.times do
|
|
224
|
+
return path if File.exist?(File.join(path, "config", "application.rb"))
|
|
225
|
+
parent = File.dirname(path)
|
|
226
|
+
break if parent == path
|
|
227
|
+
path = parent
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
nil
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Colorize output for terminal
|
|
234
|
+
# @param text [String] Text to colorize
|
|
235
|
+
# @param color [Symbol] Color name
|
|
236
|
+
# @return [String] Colorized text
|
|
237
|
+
def colorize(text, color)
|
|
238
|
+
colors = {
|
|
239
|
+
green: "\e[32m",
|
|
240
|
+
yellow: "\e[33m",
|
|
241
|
+
red: "\e[31m",
|
|
242
|
+
reset: "\e[0m"
|
|
243
|
+
}
|
|
244
|
+
"#{colors[color]}#{text}#{colors[:reset]}"
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Error class for doctor issues
|
|
248
|
+
class DoctorError < StandardError; end
|
|
249
|
+
end
|
|
250
|
+
end
|