railsforge 1.0.2 → 2.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 +4 -4
- data/README.md +129 -435
- data/lib/railsforge/analyzers/controller_analyzer.rb +29 -55
- data/lib/railsforge/analyzers/database_analyzer.rb +16 -30
- data/lib/railsforge/analyzers/metrics_analyzer.rb +8 -22
- data/lib/railsforge/analyzers/model_analyzer.rb +29 -46
- data/lib/railsforge/analyzers/performance_analyzer.rb +34 -94
- data/lib/railsforge/analyzers/refactor_analyzer.rb +77 -57
- data/lib/railsforge/analyzers/security_analyzer.rb +34 -91
- data/lib/railsforge/analyzers/spec_analyzer.rb +17 -31
- data/lib/railsforge/cli.rb +28 -645
- data/lib/railsforge/cli_minimal.rb +10 -55
- data/lib/railsforge/diff.rb +57 -0
- data/lib/railsforge/doctor.rb +52 -225
- data/lib/railsforge/formatter.rb +128 -0
- data/lib/railsforge/issue.rb +23 -0
- data/lib/railsforge/loader.rb +5 -64
- data/lib/railsforge/version.rb +1 -1
- metadata +15 -82
- data/lib/railsforge/api_generator.rb +0 -397
- data/lib/railsforge/audit.rb +0 -289
- data/lib/railsforge/config.rb +0 -181
- data/lib/railsforge/database_analyzer.rb +0 -300
- data/lib/railsforge/feature_generator.rb +0 -560
- data/lib/railsforge/generator.rb +0 -313
- data/lib/railsforge/generators/api_generator.rb +0 -392
- data/lib/railsforge/generators/base_generator.rb +0 -75
- data/lib/railsforge/generators/demo_generator.rb +0 -307
- data/lib/railsforge/generators/devops_generator.rb +0 -287
- data/lib/railsforge/generators/form_generator.rb +0 -180
- data/lib/railsforge/generators/job_generator.rb +0 -176
- data/lib/railsforge/generators/monitoring_generator.rb +0 -134
- data/lib/railsforge/generators/policy_generator.rb +0 -220
- data/lib/railsforge/generators/presenter_generator.rb +0 -173
- data/lib/railsforge/generators/query_generator.rb +0 -174
- data/lib/railsforge/generators/serializer_generator.rb +0 -166
- data/lib/railsforge/generators/service_generator.rb +0 -122
- data/lib/railsforge/generators/stimulus_controller_generator.rb +0 -129
- data/lib/railsforge/generators/test_generator.rb +0 -289
- data/lib/railsforge/generators/view_component_generator.rb +0 -169
- data/lib/railsforge/graph.rb +0 -270
- data/lib/railsforge/mailer_generator.rb +0 -191
- data/lib/railsforge/plugins/plugin_loader.rb +0 -60
- data/lib/railsforge/plugins.rb +0 -30
- data/lib/railsforge/profiles.rb +0 -99
- data/lib/railsforge/refactor_analyzer.rb +0 -401
- data/lib/railsforge/refactor_controller.rb +0 -277
- data/lib/railsforge/refactors/refactor_controller.rb +0 -117
- data/lib/railsforge/template_loader.rb +0 -105
- data/lib/railsforge/templates/v1/form/spec_template.rb +0 -18
- data/lib/railsforge/templates/v1/form/template.rb +0 -28
- data/lib/railsforge/templates/v1/job/spec_template.rb +0 -17
- data/lib/railsforge/templates/v1/job/template.rb +0 -13
- data/lib/railsforge/templates/v1/policy/spec_template.rb +0 -41
- data/lib/railsforge/templates/v1/policy/template.rb +0 -57
- data/lib/railsforge/templates/v1/presenter/spec_template.rb +0 -12
- data/lib/railsforge/templates/v1/presenter/template.rb +0 -13
- data/lib/railsforge/templates/v1/query/spec_template.rb +0 -12
- data/lib/railsforge/templates/v1/query/template.rb +0 -16
- data/lib/railsforge/templates/v1/serializer/spec_template.rb +0 -13
- data/lib/railsforge/templates/v1/serializer/template.rb +0 -11
- data/lib/railsforge/templates/v1/service/spec_template.rb +0 -12
- data/lib/railsforge/templates/v1/service/template.rb +0 -25
- data/lib/railsforge/templates/v1/stimulus_controller/template.rb +0 -35
- data/lib/railsforge/templates/v1/view_component/template.rb +0 -24
- data/lib/railsforge/templates/v2/job/template.rb +0 -49
- data/lib/railsforge/templates/v2/query/template.rb +0 -66
- data/lib/railsforge/templates/v2/service/spec_template.rb +0 -33
- data/lib/railsforge/templates/v2/service/template.rb +0 -71
- data/lib/railsforge/templates/v3/job/template.rb +0 -72
- data/lib/railsforge/templates/v3/query/spec_template.rb +0 -54
- data/lib/railsforge/templates/v3/query/template.rb +0 -115
- data/lib/railsforge/templates/v3/service/spec_template.rb +0 -51
- data/lib/railsforge/templates/v3/service/template.rb +0 -93
- data/lib/railsforge/wizard.rb +0 -265
- data/lib/railsforge/wizard_tui.rb +0 -286
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# Base generator class for RailsForge
|
|
2
|
-
# All generators inherit from this class
|
|
3
|
-
|
|
4
|
-
require 'fileutils'
|
|
5
|
-
|
|
6
|
-
module RailsForge
|
|
7
|
-
module Generators
|
|
8
|
-
# Base class for all generators
|
|
9
|
-
class BaseGenerator
|
|
10
|
-
# Error class for invalid names
|
|
11
|
-
class InvalidNameError < StandardError; end
|
|
12
|
-
|
|
13
|
-
# Default template version
|
|
14
|
-
TEMPLATE_VERSION = "v1".freeze
|
|
15
|
-
|
|
16
|
-
# Initialize generator
|
|
17
|
-
# @param name [String] Name for the generated object
|
|
18
|
-
# @param options [Hash] Generator options
|
|
19
|
-
def initialize(name, options = {})
|
|
20
|
-
@name = name
|
|
21
|
-
@options = options
|
|
22
|
-
@base_path = find_rails_app_path
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# Find Rails app path
|
|
26
|
-
# @return [String, nil] Rails app root path
|
|
27
|
-
def find_rails_app_path
|
|
28
|
-
path = Dir.pwd
|
|
29
|
-
max_depth = 10
|
|
30
|
-
|
|
31
|
-
max_depth.times do
|
|
32
|
-
return path if File.exist?(File.join(path, "config", "application.rb"))
|
|
33
|
-
parent = File.dirname(path)
|
|
34
|
-
break if parent == path
|
|
35
|
-
path = parent
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
nil
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Validate name
|
|
42
|
-
# @param name [String] Name to validate
|
|
43
|
-
# @param pattern [Regexp] Pattern to match
|
|
44
|
-
# @param error_class [Class] Error class to raise
|
|
45
|
-
def validate_name!(name, pattern, error_class)
|
|
46
|
-
raise error_class, "#{self.class.name.split('::').last} name cannot be empty" if name.nil? || name.strip.empty?
|
|
47
|
-
raise error_class, "Name must match pattern: #{pattern}" unless name =~ pattern
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# Convert to underscore
|
|
51
|
-
# @return [String] Underscored name
|
|
52
|
-
def underscore
|
|
53
|
-
@name.to_s.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
54
|
-
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
55
|
-
.downcase
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Convert to camelize
|
|
59
|
-
# @return [String] Camelized name
|
|
60
|
-
def camelize
|
|
61
|
-
result = @name.to_s.gsub(/::/, '/')
|
|
62
|
-
result.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
63
|
-
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
64
|
-
.split('_')
|
|
65
|
-
.map(&:capitalize)
|
|
66
|
-
.join
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# Generate the file
|
|
70
|
-
def generate
|
|
71
|
-
raise NotImplementedError, "Subclasses must implement #generate"
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
# Demo project generator for RailsForge
|
|
2
|
-
# Creates a complete demo Rails project
|
|
3
|
-
|
|
4
|
-
require 'fileutils'
|
|
5
|
-
|
|
6
|
-
module RailsForge
|
|
7
|
-
module Generators
|
|
8
|
-
# Demo generator creates a sample Rails project
|
|
9
|
-
class DemoGenerator < BaseGenerator
|
|
10
|
-
# Initialize the generator
|
|
11
|
-
def initialize(name, options = {})
|
|
12
|
-
super(name, options)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
# Generate demo project
|
|
16
|
-
def generate
|
|
17
|
-
return "Not in a Rails application directory" unless @base_path
|
|
18
|
-
|
|
19
|
-
results = []
|
|
20
|
-
results << create_readme
|
|
21
|
-
results << create_sample_services
|
|
22
|
-
results << create_sample_queries
|
|
23
|
-
results << create_sample_jobs
|
|
24
|
-
results << create_sample_serializers
|
|
25
|
-
results << create_sample_policies
|
|
26
|
-
results << create_sample_forms
|
|
27
|
-
|
|
28
|
-
results.join("\n")
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
# Create README.md
|
|
34
|
-
def create_readme
|
|
35
|
-
readme_path = File.join(@base_path, "README.md")
|
|
36
|
-
return "Skipping README (already exists)" if File.exist?(readme_path)
|
|
37
|
-
|
|
38
|
-
content = <<~MD
|
|
39
|
-
# #{@name.titleize}
|
|
40
|
-
|
|
41
|
-
Generated with RailsForge
|
|
42
|
-
|
|
43
|
-
## Setup
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
bundle install
|
|
47
|
-
rails db:create db:migrate
|
|
48
|
-
rails server
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Available Generators
|
|
52
|
-
|
|
53
|
-
Run `railsforge generate --help` to see all available generators.
|
|
54
|
-
|
|
55
|
-
### Services
|
|
56
|
-
```bash
|
|
57
|
-
railsforge generate service UserService
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Queries
|
|
61
|
-
```bash
|
|
62
|
-
railsforge generate query FindUsers
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### Jobs
|
|
66
|
-
```bash
|
|
67
|
-
railsforge generate job ProcessData
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## Analyzers
|
|
71
|
-
|
|
72
|
-
Run code analysis:
|
|
73
|
-
```bash
|
|
74
|
-
railsforge analyze
|
|
75
|
-
railsforge analyze security
|
|
76
|
-
railsforge analyze performance
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## Project Structure
|
|
80
|
-
|
|
81
|
-
- `app/services/` - Service objects
|
|
82
|
-
- `app/queries/` - Query objects
|
|
83
|
-
- `app/jobs/` - Background jobs
|
|
84
|
-
- `app/policies/` - Policy objects
|
|
85
|
-
- `app/forms/` - Form objects
|
|
86
|
-
- `app/serializers/` - JSON serializers
|
|
87
|
-
MD
|
|
88
|
-
|
|
89
|
-
File.write(readme_path, content)
|
|
90
|
-
"Created README.md"
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Create sample services
|
|
94
|
-
def create_sample_services
|
|
95
|
-
service_dir = File.join(@base_path, "app", "services")
|
|
96
|
-
FileUtils.mkdir_p(service_dir)
|
|
97
|
-
|
|
98
|
-
samples = %w[user_service notification_service payment_service]
|
|
99
|
-
created = []
|
|
100
|
-
|
|
101
|
-
samples.each do |name|
|
|
102
|
-
path = File.join(service_dir, "#{name}.rb")
|
|
103
|
-
next if File.exist?(path)
|
|
104
|
-
|
|
105
|
-
File.write(path, service_template(name.classify))
|
|
106
|
-
created << name
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
created.any? ? "Created #{created.count} sample services" : "Services already exist"
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
# Create sample queries
|
|
113
|
-
def create_sample_queries
|
|
114
|
-
query_dir = File.join(@base_path, "app", "queries")
|
|
115
|
-
FileUtils.mkdir_p(query_dir)
|
|
116
|
-
|
|
117
|
-
samples = %w[find_active_users find_recent_orders]
|
|
118
|
-
created = []
|
|
119
|
-
|
|
120
|
-
samples.each do |name|
|
|
121
|
-
path = File.join(query_dir, "#{name}.rb")
|
|
122
|
-
next if File.exist?(path)
|
|
123
|
-
|
|
124
|
-
File.write(path, query_template(name.classify))
|
|
125
|
-
created << name
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
created.any? ? "Created #{created.count} sample queries" : "Queries already exist"
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# Create sample jobs
|
|
132
|
-
def create_sample_jobs
|
|
133
|
-
job_dir = File.join(@base_path, "app", "jobs")
|
|
134
|
-
FileUtils.mkdir_p(job_dir)
|
|
135
|
-
|
|
136
|
-
samples = %w[process_order_job send_email_job]
|
|
137
|
-
created = []
|
|
138
|
-
|
|
139
|
-
samples.each do |name|
|
|
140
|
-
path = File.join(job_dir, "#{name}.rb")
|
|
141
|
-
next if File.exist?(path)
|
|
142
|
-
|
|
143
|
-
File.write(path, job_template(name.classify))
|
|
144
|
-
created << name
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
created.any? ? "Created #{created.count} sample jobs" : "Jobs already exist"
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
# Create sample serializers
|
|
151
|
-
def create_sample_serializers
|
|
152
|
-
serializer_dir = File.join(@base_path, "app", "serializers")
|
|
153
|
-
FileUtils.mkdir_p(serializer_dir)
|
|
154
|
-
|
|
155
|
-
samples = %w[user_serializer article_serializer]
|
|
156
|
-
created = []
|
|
157
|
-
|
|
158
|
-
samples.each do |name|
|
|
159
|
-
path = File.join(serializer_dir, "#{name}.rb")
|
|
160
|
-
next if File.exist?(path)
|
|
161
|
-
|
|
162
|
-
File.write(path, serializer_template(name.classify))
|
|
163
|
-
created << name
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
created.any? ? "Created #{created.count} sample serializers" : "Serializers already exist"
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
# Create sample policies
|
|
170
|
-
def create_sample_policies
|
|
171
|
-
policy_dir = File.join(@base_path, "app", "policies")
|
|
172
|
-
FileUtils.mkdir_p(policy_dir)
|
|
173
|
-
|
|
174
|
-
samples = %w[user_policy article_policy]
|
|
175
|
-
created = []
|
|
176
|
-
|
|
177
|
-
samples.each do |name|
|
|
178
|
-
path = File.join(policy_dir, "#{name}.rb")
|
|
179
|
-
next if File.exist?(path)
|
|
180
|
-
|
|
181
|
-
File.write(path, policy_template(name.classify))
|
|
182
|
-
created << name
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
created.any? ? "Created #{created.count} sample policies" : "Policies already exist"
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
# Create sample forms
|
|
189
|
-
def create_sample_forms
|
|
190
|
-
form_dir = File.join(@base_path, "app", "forms")
|
|
191
|
-
FileUtils.mkdir_p(form_dir)
|
|
192
|
-
|
|
193
|
-
samples = %w[contact_form signup_form]
|
|
194
|
-
created = []
|
|
195
|
-
|
|
196
|
-
samples.each do |name|
|
|
197
|
-
path = File.join(form_dir, "#{name}.rb")
|
|
198
|
-
next if File.exist?(path)
|
|
199
|
-
|
|
200
|
-
File.write(path, form_template(name.classify))
|
|
201
|
-
created << name
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
created.any? ? "Created #{created.count} sample forms" : "Forms already exist"
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
# Templates
|
|
208
|
-
def service_template(name)
|
|
209
|
-
<<~RUBY
|
|
210
|
-
class #{name}
|
|
211
|
-
def initialize(params = {})
|
|
212
|
-
@params = params
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
def call
|
|
216
|
-
# TODO: Implement service logic
|
|
217
|
-
{ success: true }
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
def self.call(params = {})
|
|
221
|
-
new(params).call
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
RUBY
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
def query_template(name)
|
|
228
|
-
<<~RUBY
|
|
229
|
-
class #{name}
|
|
230
|
-
def initialize(relation = nil)
|
|
231
|
-
@relation = relation || default_scope
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
def call
|
|
235
|
-
@relation
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
private
|
|
239
|
-
|
|
240
|
-
def default_scope
|
|
241
|
-
# TODO: Replace with actual model
|
|
242
|
-
OpenStruct.all
|
|
243
|
-
end
|
|
244
|
-
end
|
|
245
|
-
RUBY
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
def job_template(name)
|
|
249
|
-
<<~RUBY
|
|
250
|
-
class #{name} < ApplicationJob
|
|
251
|
-
queue_as :default
|
|
252
|
-
|
|
253
|
-
def perform(*args)
|
|
254
|
-
# TODO: Implement job logic
|
|
255
|
-
end
|
|
256
|
-
end
|
|
257
|
-
RUBY
|
|
258
|
-
end
|
|
259
|
-
|
|
260
|
-
def serializer_template(name)
|
|
261
|
-
<<~RUBY
|
|
262
|
-
class #{name} < ActiveModel::Serializer
|
|
263
|
-
attributes :id, :created_at
|
|
264
|
-
|
|
265
|
-
# TODO: Add attributes
|
|
266
|
-
end
|
|
267
|
-
RUBY
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
def policy_template(name)
|
|
271
|
-
<<~RUBY
|
|
272
|
-
class #{name} < ApplicationPolicy
|
|
273
|
-
def index?
|
|
274
|
-
true
|
|
275
|
-
end
|
|
276
|
-
|
|
277
|
-
def show?
|
|
278
|
-
true
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
def create?
|
|
282
|
-
user.present?
|
|
283
|
-
end
|
|
284
|
-
end
|
|
285
|
-
RUBY
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
def form_template(name)
|
|
289
|
-
<<~RUBY
|
|
290
|
-
class #{name} < ApplicationForm
|
|
291
|
-
attr_accessor :name, :email
|
|
292
|
-
|
|
293
|
-
validates :name, presence: true
|
|
294
|
-
validates :email, presence: true
|
|
295
|
-
|
|
296
|
-
def save
|
|
297
|
-
return false unless valid?
|
|
298
|
-
|
|
299
|
-
# TODO: Implement save logic
|
|
300
|
-
true
|
|
301
|
-
end
|
|
302
|
-
end
|
|
303
|
-
RUBY
|
|
304
|
-
end
|
|
305
|
-
end
|
|
306
|
-
end
|
|
307
|
-
end
|
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
# DevOps generator for RailsForge
|
|
2
|
-
# Generates Dockerfile, docker-compose, and CI/CD configs
|
|
3
|
-
|
|
4
|
-
require 'fileutils'
|
|
5
|
-
|
|
6
|
-
module RailsForge
|
|
7
|
-
module Generators
|
|
8
|
-
# DevOps generator
|
|
9
|
-
class DevopsGenerator < BaseGenerator
|
|
10
|
-
# Initialize the generator
|
|
11
|
-
def initialize(name = "app", options = {})
|
|
12
|
-
super(name, options)
|
|
13
|
-
@docker_image = options[:docker_image] || "railsforge/#{name}"
|
|
14
|
-
@ci_provider = options[:ci_provider] || "github"
|
|
15
|
-
@ruby_version = options[:ruby_version] || "3.2.0"
|
|
16
|
-
@node_version = options[:node_version] || "18"
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# Generate DevOps files
|
|
20
|
-
def generate
|
|
21
|
-
return "Not in a Rails application directory" unless @base_path
|
|
22
|
-
|
|
23
|
-
results = []
|
|
24
|
-
results << create_dockerfile
|
|
25
|
-
results << create_docker_compose
|
|
26
|
-
results << create_dockerignore
|
|
27
|
-
results << create_github_workflow if @ci_provider == "github"
|
|
28
|
-
results << create_gitlab_ci if @ci_provider == "gitlab"
|
|
29
|
-
|
|
30
|
-
results.join("\n")
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
private
|
|
34
|
-
|
|
35
|
-
# Create Dockerfile
|
|
36
|
-
def create_dockerfile
|
|
37
|
-
dockerfile_path = File.join(@base_path, "Dockerfile")
|
|
38
|
-
return "Skipping Dockerfile (already exists)" if File.exist?(dockerfile_path)
|
|
39
|
-
|
|
40
|
-
content = <<~DOCKERFILE
|
|
41
|
-
# Ruby version
|
|
42
|
-
FROM ruby:#{@ruby_version}-slim
|
|
43
|
-
|
|
44
|
-
# Install system dependencies
|
|
45
|
-
RUN apt-get update && apt-get install -y \\
|
|
46
|
-
build-essential \\
|
|
47
|
-
libpq-dev \\
|
|
48
|
-
curl \\
|
|
49
|
-
git \\
|
|
50
|
-
nodejs \\
|
|
51
|
-
npm \\
|
|
52
|
-
&& rm -rf /var/lib/apt/lists/*
|
|
53
|
-
|
|
54
|
-
# Set working directory
|
|
55
|
-
WORKDIR /app
|
|
56
|
-
|
|
57
|
-
# Copy Gemfile
|
|
58
|
-
COPY Gemfile Gemfile.lock ./
|
|
59
|
-
|
|
60
|
-
# Install gems
|
|
61
|
-
RUN bundle install --jobs 4 --retry 3
|
|
62
|
-
|
|
63
|
-
# Copy package.json for assets
|
|
64
|
-
COPY package.json package-lock.json ./
|
|
65
|
-
RUN npm install --production
|
|
66
|
-
|
|
67
|
-
# Copy application code
|
|
68
|
-
COPY . .
|
|
69
|
-
|
|
70
|
-
# Precompile assets
|
|
71
|
-
RUN RAILS_ENV=production SECRET_KEY_BASE=dummy bundle exec rails assets:precompile
|
|
72
|
-
|
|
73
|
-
# Expose port
|
|
74
|
-
EXPOSE 3000
|
|
75
|
-
|
|
76
|
-
# Start server
|
|
77
|
-
CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]
|
|
78
|
-
DOCKERFILE
|
|
79
|
-
|
|
80
|
-
File.write(dockerfile_path, content)
|
|
81
|
-
"Created Dockerfile"
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Create docker-compose.yml
|
|
85
|
-
def create_docker_compose
|
|
86
|
-
compose_path = File.join(@base_path, "docker-compose.yml")
|
|
87
|
-
return "Skipping docker-compose.yml (already exists)" if File.exist?(compose_path)
|
|
88
|
-
|
|
89
|
-
content = <<~YAML
|
|
90
|
-
version: '3.8'
|
|
91
|
-
|
|
92
|
-
services:
|
|
93
|
-
app:
|
|
94
|
-
build: .
|
|
95
|
-
ports:
|
|
96
|
-
- "3000:3000"
|
|
97
|
-
environment:
|
|
98
|
-
- RAILS_ENV=development
|
|
99
|
-
- DATABASE_HOST=postgres
|
|
100
|
-
- REDIS_HOST=redis
|
|
101
|
-
depends_on:
|
|
102
|
-
- postgres
|
|
103
|
-
- redis
|
|
104
|
-
volumes:
|
|
105
|
-
- .:/app
|
|
106
|
-
|
|
107
|
-
postgres:
|
|
108
|
-
image: postgres:15-alpine
|
|
109
|
-
environment:
|
|
110
|
-
- POSTGRES_USER=rails
|
|
111
|
-
- POSTGRES_PASSWORD=password
|
|
112
|
-
- POSTGRES_DB=app_development
|
|
113
|
-
ports:
|
|
114
|
-
- "5432:5432"
|
|
115
|
-
volumes:
|
|
116
|
-
- postgres_data:/var/lib/postgresql/data
|
|
117
|
-
|
|
118
|
-
redis:
|
|
119
|
-
image: redis:7-alpine
|
|
120
|
-
ports:
|
|
121
|
-
- "6379:6379"
|
|
122
|
-
|
|
123
|
-
sidekiq:
|
|
124
|
-
build: .
|
|
125
|
-
command: bundle exec sidekiq
|
|
126
|
-
environment:
|
|
127
|
-
- RAILS_ENV=development
|
|
128
|
-
- DATABASE_HOST=postgres
|
|
129
|
-
- REDIS_HOST=redis
|
|
130
|
-
depends_on:
|
|
131
|
-
- postgres
|
|
132
|
-
- redis
|
|
133
|
-
volumes:
|
|
134
|
-
- .:/app
|
|
135
|
-
|
|
136
|
-
volumes:
|
|
137
|
-
postgres_data:
|
|
138
|
-
YAML
|
|
139
|
-
|
|
140
|
-
File.write(compose_path, content)
|
|
141
|
-
"Created docker-compose.yml"
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
# Create .dockerignore
|
|
145
|
-
def create_dockerignore
|
|
146
|
-
ignore_path = File.join(@base_path, ".dockerignore")
|
|
147
|
-
return "Skipping .dockerignore (already exists)" if File.exist?(ignore_path)
|
|
148
|
-
|
|
149
|
-
content = <<~IGNORE
|
|
150
|
-
.git
|
|
151
|
-
.github
|
|
152
|
-
.gitignore
|
|
153
|
-
log/*.log
|
|
154
|
-
tmp/
|
|
155
|
-
.bundle/
|
|
156
|
-
node_modules/
|
|
157
|
-
public/assets/
|
|
158
|
-
.DS_Store
|
|
159
|
-
*.swp
|
|
160
|
-
*.swo
|
|
161
|
-
IGNORE
|
|
162
|
-
|
|
163
|
-
File.write(ignore_path, content)
|
|
164
|
-
"Created .dockerignore"
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
# Create GitHub Actions workflow
|
|
168
|
-
def create_github_workflow
|
|
169
|
-
workflow_dir = File.join(@base_path, ".github", "workflows")
|
|
170
|
-
FileUtils.mkdir_p(workflow_dir)
|
|
171
|
-
|
|
172
|
-
workflow_path = File.join(workflow_dir, "ci.yml")
|
|
173
|
-
return "Skipping GitHub workflow (already exists)" if File.exist?(workflow_path)
|
|
174
|
-
|
|
175
|
-
content = <<~YAML
|
|
176
|
-
name: CI
|
|
177
|
-
|
|
178
|
-
on:
|
|
179
|
-
push:
|
|
180
|
-
branches: [main, develop]
|
|
181
|
-
pull_request:
|
|
182
|
-
branches: [main, develop]
|
|
183
|
-
|
|
184
|
-
jobs:
|
|
185
|
-
test:
|
|
186
|
-
runs-on: ubuntu-latest
|
|
187
|
-
services:
|
|
188
|
-
postgres:
|
|
189
|
-
image: postgres:15-alpine
|
|
190
|
-
env:
|
|
191
|
-
POSTGRES_USER: rails
|
|
192
|
-
POSTGRES_PASSWORD: password
|
|
193
|
-
POSTGRES_DB: app_test
|
|
194
|
-
ports:
|
|
195
|
-
- 5432:5432
|
|
196
|
-
redis:
|
|
197
|
-
image: redis:7-alpine
|
|
198
|
-
ports:
|
|
199
|
-
- 6379:6379
|
|
200
|
-
|
|
201
|
-
steps:
|
|
202
|
-
- uses: actions/checkout@v3
|
|
203
|
-
- name: Set up Ruby
|
|
204
|
-
uses: ruby/setup-ruby@v1
|
|
205
|
-
with:
|
|
206
|
-
ruby-version: #{@ruby_version}
|
|
207
|
-
bundler-cache: true
|
|
208
|
-
- name: Install dependencies
|
|
209
|
-
run: |
|
|
210
|
-
bundle install
|
|
211
|
-
npm install
|
|
212
|
-
- name: Setup database
|
|
213
|
-
env:
|
|
214
|
-
RAILS_ENV: test
|
|
215
|
-
POSTGRES_HOST: localhost
|
|
216
|
-
POSTGRES_USER: rails
|
|
217
|
-
POSTGRES_PASSWORD: password
|
|
218
|
-
POSTGRES_DB: app_test
|
|
219
|
-
run: bundle exec rails db:create db:schema:load
|
|
220
|
-
- name: Run tests
|
|
221
|
-
env:
|
|
222
|
-
RAILS_ENV: test
|
|
223
|
-
POSTGRES_HOST: localhost
|
|
224
|
-
POSTGRES_USER: rails
|
|
225
|
-
POSTGRES_PASSWORD: password
|
|
226
|
-
POSTGRES_DB: app_test
|
|
227
|
-
run: bundle exec rspec
|
|
228
|
-
- name: Run linter
|
|
229
|
-
run: bundle exec rubocop
|
|
230
|
-
YAML
|
|
231
|
-
|
|
232
|
-
File.write(workflow_path, content)
|
|
233
|
-
"Created GitHub Actions workflow"
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
# Create GitLab CI config
|
|
237
|
-
def create_gitlab_ci
|
|
238
|
-
ci_path = File.join(@base_path, ".gitlab-ci.yml")
|
|
239
|
-
return "Skipping .gitlab-ci.yml (already exists)" if File.exist?(ci_path)
|
|
240
|
-
|
|
241
|
-
content = <<~YAML
|
|
242
|
-
stages:
|
|
243
|
-
- test
|
|
244
|
-
- lint
|
|
245
|
-
- deploy
|
|
246
|
-
|
|
247
|
-
variables:
|
|
248
|
-
RUBY_VERSION: #{@ruby_version}
|
|
249
|
-
POSTGRES_DB: app_test
|
|
250
|
-
POSTGRES_USER: rails
|
|
251
|
-
POSTGRES_PASSWORD: password
|
|
252
|
-
|
|
253
|
-
test:
|
|
254
|
-
stage: test
|
|
255
|
-
image: ruby:#{@ruby_version}
|
|
256
|
-
services:
|
|
257
|
-
- postgres:15-alpine
|
|
258
|
-
- redis:7-alpine
|
|
259
|
-
before_script:
|
|
260
|
-
- apt-get update -qq && apt-get install -y -qq nodejs npm
|
|
261
|
-
- bundle install
|
|
262
|
-
- npm install
|
|
263
|
-
script:
|
|
264
|
-
- RAILS_ENV=test bundle exec rails db:create db:schema:load
|
|
265
|
-
- bundle exec rspec
|
|
266
|
-
only:
|
|
267
|
-
- main
|
|
268
|
-
- develop
|
|
269
|
-
|
|
270
|
-
rubocop:
|
|
271
|
-
stage: lint
|
|
272
|
-
image: ruby:#{@ruby_version}
|
|
273
|
-
before_script:
|
|
274
|
-
- bundle install
|
|
275
|
-
script:
|
|
276
|
-
- bundle exec rubocop
|
|
277
|
-
only:
|
|
278
|
-
- main
|
|
279
|
-
- develop
|
|
280
|
-
YAML
|
|
281
|
-
|
|
282
|
-
File.write(ci_path, content)
|
|
283
|
-
"Created GitLab CI config"
|
|
284
|
-
end
|
|
285
|
-
end
|
|
286
|
-
end
|
|
287
|
-
end
|