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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +528 -0
  4. data/bin/railsforge +8 -0
  5. data/lib/railsforge/analyzers/base_analyzer.rb +41 -0
  6. data/lib/railsforge/analyzers/controller_analyzer.rb +83 -0
  7. data/lib/railsforge/analyzers/database_analyzer.rb +55 -0
  8. data/lib/railsforge/analyzers/metrics_analyzer.rb +55 -0
  9. data/lib/railsforge/analyzers/model_analyzer.rb +74 -0
  10. data/lib/railsforge/analyzers/performance_analyzer.rb +161 -0
  11. data/lib/railsforge/analyzers/refactor_analyzer.rb +118 -0
  12. data/lib/railsforge/analyzers/security_analyzer.rb +169 -0
  13. data/lib/railsforge/analyzers/spec_analyzer.rb +58 -0
  14. data/lib/railsforge/api_generator.rb +397 -0
  15. data/lib/railsforge/audit.rb +289 -0
  16. data/lib/railsforge/cli.rb +671 -0
  17. data/lib/railsforge/config.rb +181 -0
  18. data/lib/railsforge/database_analyzer.rb +300 -0
  19. data/lib/railsforge/doctor.rb +250 -0
  20. data/lib/railsforge/feature_generator.rb +560 -0
  21. data/lib/railsforge/generator.rb +313 -0
  22. data/lib/railsforge/generators/base_generator.rb +70 -0
  23. data/lib/railsforge/generators/demo_generator.rb +307 -0
  24. data/lib/railsforge/generators/devops_generator.rb +287 -0
  25. data/lib/railsforge/generators/monitoring_generator.rb +134 -0
  26. data/lib/railsforge/generators/service_generator.rb +122 -0
  27. data/lib/railsforge/generators/stimulus_controller_generator.rb +129 -0
  28. data/lib/railsforge/generators/test_generator.rb +289 -0
  29. data/lib/railsforge/generators/view_component_generator.rb +169 -0
  30. data/lib/railsforge/graph.rb +270 -0
  31. data/lib/railsforge/loader.rb +56 -0
  32. data/lib/railsforge/mailer_generator.rb +191 -0
  33. data/lib/railsforge/plugins/plugin_loader.rb +60 -0
  34. data/lib/railsforge/plugins.rb +30 -0
  35. data/lib/railsforge/profiles/admin_app.yml +49 -0
  36. data/lib/railsforge/profiles/api_only.yml +47 -0
  37. data/lib/railsforge/profiles/blog.yml +47 -0
  38. data/lib/railsforge/profiles/standard.yml +44 -0
  39. data/lib/railsforge/profiles.rb +99 -0
  40. data/lib/railsforge/refactor_analyzer.rb +401 -0
  41. data/lib/railsforge/refactor_controller.rb +277 -0
  42. data/lib/railsforge/refactors/refactor_controller.rb +117 -0
  43. data/lib/railsforge/template_loader.rb +105 -0
  44. data/lib/railsforge/templates/v1/form/spec_template.rb +18 -0
  45. data/lib/railsforge/templates/v1/form/template.rb +28 -0
  46. data/lib/railsforge/templates/v1/job/spec_template.rb +17 -0
  47. data/lib/railsforge/templates/v1/job/template.rb +13 -0
  48. data/lib/railsforge/templates/v1/policy/spec_template.rb +41 -0
  49. data/lib/railsforge/templates/v1/policy/template.rb +57 -0
  50. data/lib/railsforge/templates/v1/presenter/spec_template.rb +12 -0
  51. data/lib/railsforge/templates/v1/presenter/template.rb +13 -0
  52. data/lib/railsforge/templates/v1/query/spec_template.rb +12 -0
  53. data/lib/railsforge/templates/v1/query/template.rb +16 -0
  54. data/lib/railsforge/templates/v1/serializer/spec_template.rb +13 -0
  55. data/lib/railsforge/templates/v1/serializer/template.rb +11 -0
  56. data/lib/railsforge/templates/v1/service/spec_template.rb +12 -0
  57. data/lib/railsforge/templates/v1/service/template.rb +25 -0
  58. data/lib/railsforge/templates/v1/stimulus_controller/template.rb +35 -0
  59. data/lib/railsforge/templates/v1/view_component/template.rb +24 -0
  60. data/lib/railsforge/templates/v2/job/template.rb +49 -0
  61. data/lib/railsforge/templates/v2/query/template.rb +66 -0
  62. data/lib/railsforge/templates/v2/service/spec_template.rb +33 -0
  63. data/lib/railsforge/templates/v2/service/template.rb +71 -0
  64. data/lib/railsforge/templates/v3/job/template.rb +72 -0
  65. data/lib/railsforge/templates/v3/query/spec_template.rb +54 -0
  66. data/lib/railsforge/templates/v3/query/template.rb +115 -0
  67. data/lib/railsforge/templates/v3/service/spec_template.rb +51 -0
  68. data/lib/railsforge/templates/v3/service/template.rb +84 -0
  69. data/lib/railsforge/version.rb +5 -0
  70. data/lib/railsforge/wizard.rb +265 -0
  71. data/lib/railsforge/wizard_tui.rb +286 -0
  72. data/lib/railsforge.rb +13 -0
  73. metadata +216 -0
@@ -0,0 +1,289 @@
1
+ # Test generator for RailsForge
2
+ # Generates comprehensive test files for any Rails component
3
+
4
+ require 'fileutils'
5
+
6
+ module RailsForge
7
+ module Generators
8
+ # Test generator
9
+ class TestGenerator < BaseGenerator
10
+ # Initialize the generator
11
+ def initialize(name = "test", options = {})
12
+ super(name, options)
13
+ @component_type = options[:type] || "model"
14
+ @spec_path = options[:spec_path] || "spec"
15
+ end
16
+
17
+ # Generate test files
18
+ def generate
19
+ return "Not in a Rails application directory" unless @base_path
20
+
21
+ results = []
22
+ results << generate_factory
23
+ results << generate_spec
24
+ results << generate_integration_spec if @component_type == "feature"
25
+
26
+ results.join("\n")
27
+ end
28
+
29
+ private
30
+
31
+ # Generate factory file
32
+ def generate_factory
33
+ factories_dir = File.join(@base_path, "spec", "factories")
34
+ FileUtils.mkdir_p(factories_dir)
35
+
36
+ factory_path = File.join(factories_dir, "#{@name.underscore.pluralize}.rb")
37
+ return "Factory already exists" if File.exist?(factory_path)
38
+
39
+ content = <<~RUBY
40
+ # Factory for #{@name.titleize}
41
+ FactoryBot.define do
42
+ factory :#{@name.underscore} do
43
+ #{factory_attributes}
44
+ end
45
+ end
46
+ RUBY
47
+
48
+ File.write(factory_path, content)
49
+ "Created spec/factories/#{@name.underscore.pluralize}.rb"
50
+ end
51
+
52
+ # Generate model spec
53
+ def generate_spec
54
+ spec_dir = File.join(@base_path, @spec_path, "models")
55
+ FileUtils.mkdir_p(spec_dir)
56
+
57
+ spec_path = File.join(spec_dir, "#{@name.underscore}_spec.rb")
58
+ return "Spec already exists" if File.exist?(spec_path)
59
+
60
+ content = case @component_type
61
+ when "model"
62
+ model_spec
63
+ when "controller"
64
+ controller_spec
65
+ when "service"
66
+ service_spec
67
+ when "job"
68
+ job_spec
69
+ else
70
+ generic_spec
71
+ end
72
+
73
+ File.write(spec_path, content)
74
+ "Created spec/models/#{@name.underscore}_spec.rb"
75
+ end
76
+
77
+ # Generate integration/feature spec
78
+ def generate_integration_spec
79
+ spec_dir = File.join(@base_path, @spec_path, "features")
80
+ FileUtils.mkdir_p(spec_dir)
81
+
82
+ spec_path = File.join(spec_dir, "#{@name.underscore}_spec.rb")
83
+ return "Feature spec already exists" if File.exist?(spec_path)
84
+
85
+ content = feature_spec
86
+ File.write(spec_path, content)
87
+ "Created spec/features/#{@name.underscore}_spec.rb"
88
+ end
89
+
90
+ # Model spec template
91
+ def model_spec
92
+ <<~RUBY
93
+ require 'rails_helper'
94
+
95
+ RSpec.describe #{@name}, type: :model do
96
+ describe 'associations' do
97
+ it { should have_many(:related) }
98
+ it { should belong_to(:owner) }
99
+ end
100
+
101
+ describe 'validations' do
102
+ it { should validate_presence_of(:name) }
103
+ it { should validate_uniqueness_of(:slug).scoped_to(:tenant) }
104
+ end
105
+
106
+ describe 'scopes' do
107
+ describe '.active' do
108
+ it 'returns only active records' do
109
+ active = create(:#{@name.underscore}, status: 'active')
110
+ create(:#{@name.underscore}, status: 'inactive')
111
+ expect(described_class.active).to include(active)
112
+ end
113
+ end
114
+ end
115
+
116
+ describe '#activate!' do
117
+ it 'sets status to active' do
118
+ record = create(:#{@name.underscore}, status: 'inactive')
119
+ record.activate!
120
+ expect(record.status).to eq('active')
121
+ end
122
+ end
123
+
124
+ describe '#to_s' do
125
+ it 'returns the name' do
126
+ record = build(:#{@name.underscore}, name: 'Test Name')
127
+ expect(record.to_s).to eq('Test Name')
128
+ end
129
+ end
130
+ end
131
+ RUBY
132
+ end
133
+
134
+ # Controller spec template
135
+ def controller_spec
136
+ controller_name = "#{@name.underscore.pluralize}_controller"
137
+ <<~RUBY
138
+ require 'rails_helper'
139
+
140
+ RSpec.describe #{@name.pluralize}Controller, type: :controller do
141
+ let(:user) { create(:user) }
142
+ let(:#{@name.underscore}) { create(:#{@name.underscore}) }
143
+
144
+ before { sign_in user }
145
+
146
+ describe 'GET #index' do
147
+ it 'returns a success response' do
148
+ get :index
149
+ expect(response).to be_successful
150
+ end
151
+
152
+ it 'assigns all #{@name.underscore.pluralize}' do
153
+ get :index
154
+ expect(assigns(:#{@name.underscore.pluralize})).to include(#{@name.underscore})
155
+ end
156
+ end
157
+
158
+ describe 'GET #show' do
159
+ it 'returns a success response' do
160
+ get :show, params: { id: #{@name.underscore}.id }
161
+ expect(response).to be_successful
162
+ end
163
+ end
164
+
165
+ describe 'POST #create' do
166
+ context 'with valid params' do
167
+ let(:valid_attributes) { attributes_for(:#{@name.underscore}) }
168
+
169
+ it 'creates a new #{@name}' do
170
+ expect {
171
+ post :create, params: { #{@name.underscore}: valid_attributes }
172
+ }.to change(described_class, :count).by(1)
173
+ end
174
+
175
+ it 'redirects to the created #{@name}' do
176
+ post :create, params: { #{@name.underscore}: valid_attributes }
177
+ expect(response).to redirect_to(#{@name.underscore}.id)
178
+ end
179
+ end
180
+ end
181
+ end
182
+ RUBY
183
+ end
184
+
185
+ # Service spec template
186
+ def service_spec
187
+ <<~RUBY
188
+ require 'rails_helper'
189
+
190
+ RSpec.describe #{@name}Service do
191
+ let(:service) { described_class.new(params) }
192
+ let(:params) { { id: 1 } }
193
+
194
+ describe '#call' do
195
+ context 'with valid params' do
196
+ it 'returns success' do
197
+ result = service.call
198
+ expect(result).to be_success
199
+ end
200
+ end
201
+
202
+ context 'with invalid params' do
203
+ let(:params) { {} }
204
+
205
+ it 'returns failure' do
206
+ result = service.call
207
+ expect(result).to be_failure
208
+ end
209
+ end
210
+ end
211
+ end
212
+ RUBY
213
+ end
214
+
215
+ # Job spec template
216
+ def job_spec
217
+ <<~RUBY
218
+ require 'rails_helper'
219
+
220
+ RSpec.describe #{@name}Job, type: :job do
221
+ let(:args) { { user_id: 1 } }
222
+
223
+ describe '#perform' do
224
+ it 'executes the job' do
225
+ expect {
226
+ described_class.new.perform(args)
227
+ }.not_to raise_error
228
+ end
229
+ end
230
+
231
+ describe '.perform_later' do
232
+ it 'enqueues the job' do
233
+ ActiveJob::Base.queue_adapter = :test
234
+ expect {
235
+ described_class.perform_later(args)
236
+ }.to have_enqueued_job(described_class)
237
+ end
238
+ end
239
+ end
240
+ RUBY
241
+ end
242
+
243
+ # Generic spec template
244
+ def generic_spec
245
+ <<~RUBY
246
+ require 'rails_helper'
247
+
248
+ RSpec.describe #{@name} do
249
+ it 'has valid factory' do
250
+ expect(build(:#{@name.underscore})).to be_valid
251
+ end
252
+ end
253
+ RUBY
254
+ end
255
+
256
+ # Feature spec template
257
+ def feature_spec
258
+ <<~RUBY
259
+ require 'rails_helper'
260
+
261
+ feature '#{@name.titleize} Management' do
262
+ scenario 'user creates a new #{@name.underscore}' do
263
+ user = create(:user)
264
+ sign_in user
265
+
266
+ visit new_#{@name.underscore}_path
267
+ fill_in 'Name', with: 'Test #{@name}'
268
+ click_button 'Create'
269
+
270
+ expect(page).to have_content('Test #{@name}')
271
+ end
272
+ end
273
+ RUBY
274
+ end
275
+
276
+ # Factory attributes
277
+ def factory_attributes
278
+ case @component_type
279
+ when "user"
280
+ "sequence(:email) { |n| \"user#{n}@example.com\" }\n password { 'password123' }\n name { 'Test User' }"
281
+ when "post"
282
+ "title { 'Test Post' }\n body { 'Post content' }\n author { create(:user) }"
283
+ else
284
+ "sequence(:name) { |n| \"#{@name} #{n}\" }"
285
+ end
286
+ end
287
+ end
288
+ end
289
+ end
@@ -0,0 +1,169 @@
1
+ # ViewComponent generator for RailsForge
2
+ # Generates ViewComponent files
3
+
4
+ require 'fileutils'
5
+
6
+ module RailsForge
7
+ module Generators
8
+ # ViewComponent generator
9
+ class ViewComponentGenerator < BaseGenerator
10
+ # Template version
11
+ TEMPLATE_VERSION = "v1"
12
+
13
+ # Initialize the generator
14
+ # @param name [String] Component name
15
+ # @param options [Hash] Generator options
16
+ def initialize(name, options = {})
17
+ super(name, options)
18
+ validate_name!
19
+ end
20
+
21
+ # Generate ViewComponent files
22
+ # @return [String] Success message
23
+ def generate
24
+ return "Not in a Rails application directory" unless @base_path
25
+
26
+ results = []
27
+ results << generate_component
28
+ results << generate_template if with_template?
29
+ results << generate_spec if with_spec?
30
+
31
+ results.join("\n")
32
+ end
33
+
34
+ private
35
+
36
+ # Validate component name
37
+ def validate_name!
38
+ validate_name!(@name, /\A[A-Z][a-zA-Z0-9]*\z/, InvalidNameError)
39
+ end
40
+
41
+ # Get template version
42
+ def template_version
43
+ @options[:template_version] || TEMPLATE_VERSION
44
+ end
45
+
46
+ # Check if should generate template
47
+ def with_template?
48
+ @options[:template] != false
49
+ end
50
+
51
+ # Check if should generate spec
52
+ def with_spec?
53
+ @options[:spec] != false
54
+ end
55
+
56
+ # Generate the component Ruby file
57
+ def generate_component
58
+ component_dir = File.join(@base_path, "app", "components")
59
+ FileUtils.mkdir_p(component_dir)
60
+
61
+ file_name = "#{underscore}.rb"
62
+ file_path = File.join(component_dir, file_name)
63
+
64
+ return "Skipping component (already exists)" if File.exist?(file_path)
65
+
66
+ # Try to load template
67
+ template_content = load_template
68
+ content = apply_template(template_content)
69
+
70
+ File.write(file_path, content)
71
+ "Created app/components/#{file_name}"
72
+ end
73
+
74
+ # Generate the component template ERB file
75
+ def generate_template
76
+ template_dir = File.join(@base_path, "app", "components", underscore)
77
+ FileUtils.mkdir_p(template_dir)
78
+
79
+ file_name = "#{underscore}.html.erb"
80
+ file_path = File.join(template_dir, file_name)
81
+
82
+ return "Skipping template (already exists)" if File.exist?(file_path)
83
+
84
+ content = <<~ERB
85
+ <div class="#{underscore}">
86
+ <%% if title? %>
87
+ <h2><%%= @title %></h2>
88
+ <%% end %>
89
+ <%%= content %>
90
+ </div>
91
+ ERB
92
+
93
+ File.write(file_path, content)
94
+ "Created app/components/#{underscore}/#{file_name}"
95
+ end
96
+
97
+ # Generate the component spec
98
+ def generate_spec
99
+ spec_dir = File.join(@base_path, "spec", "components")
100
+ FileUtils.mkdir_p(spec_dir)
101
+
102
+ file_name = "#{underscore}_spec.rb"
103
+ file_path = File.join(spec_dir, file_name)
104
+
105
+ return "Skipping spec (already exists)" if File.exist?(file_path)
106
+
107
+ content = <<~RUBY
108
+ # frozen_string_literal: true
109
+
110
+ require "rails_helper"
111
+
112
+ RSpec.describe #{camelize}, type: :component do
113
+ let(:options) { { title: "Test" } }
114
+
115
+ it "renders successfully" do
116
+ render_inline(described_class.new(**options))
117
+
118
+ expect(page).to have_content("Test")
119
+ end
120
+ end
121
+ RUBY
122
+
123
+ File.write(file_path, content)
124
+ "Created spec/components/#{file_name}"
125
+ end
126
+
127
+ # Load template content
128
+ def load_template
129
+ template_path = File.join(
130
+ File.dirname(__FILE__),
131
+ "..",
132
+ "templates",
133
+ template_version,
134
+ "view_component",
135
+ "template.rb"
136
+ )
137
+
138
+ if File.exist?(template_path)
139
+ File.read(template_path)
140
+ else
141
+ default_template
142
+ end
143
+ end
144
+
145
+ # Default template content
146
+ def default_template
147
+ <<~RUBY
148
+ class <%= class_name %> < ViewComponent::Base
149
+ def initialize(title: "", classes: "")
150
+ @title = title
151
+ @classes = classes
152
+ end
153
+
154
+ def title?
155
+ @title.present?
156
+ end
157
+ end
158
+ RUBY
159
+ end
160
+
161
+ # Apply template variables
162
+ def apply_template(content)
163
+ content
164
+ .gsub("<%= class_name %>", camelize)
165
+ .gsub("<%= underscore_name %>", underscore)
166
+ end
167
+ end
168
+ end
169
+ end