Class: Belt::CLI::GenerateCommand
- Inherits:
-
Object
- Object
- Belt::CLI::GenerateCommand
show all
- Includes:
- AppDetection
- Defined in:
- lib/belt/cli/generate_command.rb
Constant Summary
collapse
- TEMPLATE_DIR =
File.expand_path('../../templates/generate', __dir__)
- GENERATORS =
%w[scaffold resource model controller environment frontend views].freeze
- FIELD_TYPES =
%w[string text integer float boolean date datetime].freeze
- GENERATOR_HELP =
{
'scaffold' => {
description: 'Generate a model, controller, routes, schema, and views for a REST resource.',
usage: 'belt generate scaffold <name> [field:type ...] [options]',
options: [
['--skip-views', 'Skip generating frontend view pages'],
['--force, -f', 'Overwrite existing resource files (skip collision check)']
],
examples: [
['belt g scaffold post title body:text'],
['belt g scaffold comment author body:text status'],
['belt g scaffold task --skip-views'],
['belt g scaffold post title body:text --force']
],
notes: <<~NOTES
Creates:
lambda/models/<name>.rb Model with validations and fields
lambda/controllers/<app>/<names>_controller.rb RESTful controller (index, show, create, update, destroy)
config/routes.rb Route entry added
config/contracts.rb API response contract added
lambda/lib/routes/<app>_routes.rb Route manifest updated
infrastructure/modules/app/dynamodb.tf DynamoDB table generated
frontend/src/pages/<names>/ React pages (if frontend exists)
Alias: `belt g resource` works the same way.
NOTES
},
'model' => {
description: 'Generate an ActiveItem model with validations and DynamoDB field definitions.',
usage: 'belt generate model <name> [field:type ...] [options]',
options: [
['--force, -f', 'Overwrite existing model files (skip collision check)']
],
examples: [
['belt g model user email name'],
['belt g model event title starts_at:datetime']
],
notes: <<~NOTES
Creates:
lambda/models/<name>.rb Model class inheriting from ApplicationRecord
config/contracts.rb API response contract added
infrastructure/modules/app/dynamodb.tf DynamoDB table generated
NOTES
},
'controller' => {
description: 'Generate a controller with standard REST actions.',
usage: 'belt generate controller <name>',
options: [],
examples: [
['belt g controller posts'],
['belt g controller admin/users']
],
notes: <<~NOTES
Creates:
lambda/controllers/<app>/<name>_controller.rb Controller class
NOTES
}
}.freeze
- RESERVED_NAMES =
%w[
help new edit create update destroy index show delete remove
application base controller model resource generate
belt setup environment frontend views routes
].freeze
Class Method Summary
collapse
Instance Method Summary
collapse
#detect_app_name, #detect_environments, #detect_namespace, #find_contracts_file_path, #find_routes_file_path, #find_schema_file_path, #s3_safe_name
Constructor Details
#initialize(generator, name, fields, skip_views: false, force: false) ⇒ GenerateCommand
Returns a new instance of GenerateCommand.
251
252
253
254
255
256
257
258
259
260
261
262
|
# File 'lib/belt/cli/generate_command.rb', line 251
def initialize(generator, name, fields, skip_views: false, force: false)
@generator = generator
@name = name.downcase.gsub(/[^a-z0-9_]/, '_')
@fields = fields
@skip_views = skip_views
@force = force
@app_name = detect_namespace
@module_name = @app_name.split(/[-_]/).map(&:capitalize).join
@singular_name = Belt::Inflector.singularize(@name)
@resource_name = Belt::Inflector.pluralize(@singular_name)
@class_name = Belt::Inflector.classify(@singular_name)
end
|
Class Method Details
.all_generator_names ⇒ Object
.parse_field(arg) ⇒ Object
133
134
135
136
|
# File 'lib/belt/cli/generate_command.rb', line 133
def self.parse_field(arg)
name, type = arg.split(':', 2)
{ name: name, type: type || 'string' }
end
|
.print_generate_help ⇒ Object
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
|
# File 'lib/belt/cli/generate_command.rb', line 168
def self.print_generate_help
gem_generators = GeneratorRegistry.discovered_generators
puts <<~HELP
Usage: belt generate <generator> <name> [field:type ...] [options]
belt g <generator> <name> [field:type ...] [options]
Generators:
scaffold Generate model, controller, routes, schema, and views (full REST resource)
model Generate an ActiveItem model
controller Generate a controller
environment Create a new deployment environment
frontend Scaffold a frontend app (react, vue, svelte)
views Generate React pages for a resource
Aliases:
resource Same as scaffold
HELP
if gem_generators.any?
puts ' Gem Generators:'
gem_generators.each do |name, klass|
desc = klass.respond_to?(:description) ? klass.description : "Generate #{name} (from gem)"
puts format(' %<name>-14s %<desc>s', name: name, desc: desc)
end
puts
end
puts <<~HELP
Field Types:
#{FIELD_TYPES.join(', ')}
(defaults to string if omitted)
Examples:
belt g scaffold post title body:text status
belt g model user email name
belt g controller comments
belt g environment staging
belt g frontend react
belt g views post title body:text
Run 'belt generate <generator> --help' for detailed help on a specific generator.
HELP
end
|
.print_generator_help(generator) ⇒ Object
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
|
# File 'lib/belt/cli/generate_command.rb', line 214
def self.print_generator_help(generator)
info = GENERATOR_HELP[generator]
unless info
puts "Usage: belt generate #{generator} <name>"
puts "\nRun 'belt generate #{generator} --help' is not yet documented."
puts "Try 'belt generate --help' for the full list of generators."
return
end
puts info[:description]
puts
puts "Usage: #{info[:usage]}"
if info[:options].any?
puts "\nOptions:"
info[:options].each do |flag, desc|
puts format(' %<flag>-20s %<desc>s', flag: flag, desc: desc)
end
end
puts "\nField Types:"
puts " #{FIELD_TYPES.join(', ')}"
puts ' (defaults to string if omitted)'
puts "\nExamples:"
info[:examples].each do |cmd, desc|
if desc
puts " #{cmd} ā #{desc}"
else
puts " #{cmd}"
end
end
puts "\n#{info[:notes]}" if info[:notes]
end
|
.run(args) ⇒ Object
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
# File 'lib/belt/cli/generate_command.rb', line 82
def self.run(args)
generator = args.shift
if generator.nil? || generator =~ /\A-/
print_generate_help
exit 0
end
unless GENERATORS.include?(generator) || GeneratorRegistry.generator_names.include?(generator)
puts "Unknown generator: '#{generator}'"
puts "Available generators: #{all_generator_names.join(', ')}"
puts "\nRun 'belt generate --help' for usage information."
exit 1
end
unless GENERATORS.include?(generator)
klass = GeneratorRegistry.find(generator)
return klass.run(args)
end
generator = 'scaffold' if generator == 'resource'
if args.include?('--help') || args.include?('-h')
print_generator_help(generator)
exit 0
end
return Belt::CLI::EnvironmentCommand.run(args) if generator == 'environment'
return Belt::CLI::FrontendCommand.run(args) if generator == 'frontend'
return Belt::CLI::ViewsCommand.run(args) if generator == 'views'
name = args.shift
if name.nil? || name.empty?
print_generator_help(generator)
exit 0
end
validate_resource_name!(name, generator)
force = args.delete('--force') || args.delete('-f')
skip_views = args.delete('--skip-views')
fields = args.map { |arg| parse_field(arg) }
new(generator, name, fields, skip_views: skip_views, force: force).generate
end
|
.validate_resource_name!(name, generator) ⇒ Object
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
|
# File 'lib/belt/cli/generate_command.rb', line 148
def self.validate_resource_name!(name, generator)
errors = []
errors << 'must start with a letter' unless name.match?(/\A[a-zA-Z]/)
errors << 'must be at least 2 characters' if name.length < 2
errors << 'must only contain letters, numbers, and underscores' unless name.match?(/\A[a-zA-Z][a-zA-Z0-9_]*\z/)
errors << 'must not start or end with an underscore' if name.match?(/\A_|_\z/)
errors << 'must not contain consecutive underscores' if name.include?('__')
errors << 'is a reserved name' if RESERVED_NAMES.include?(Belt::Inflector.singularize(name.downcase))
errors << 'is a reserved name' if RESERVED_NAMES.include?(name.downcase)
return if errors.empty?
puts "\nā Invalid #{generator} name '#{name}':"
errors.uniq.each { |e| puts " - #{e}" }
puts "\nName must start with a letter, be at least 2 characters, " \
'and contain only letters, numbers, and single underscores.'
exit 1
end
|
Instance Method Details
#generate ⇒ Object
264
265
266
267
268
269
270
271
272
273
274
|
# File 'lib/belt/cli/generate_command.rb', line 264
def generate
case @generator
when 'scaffold'
check_resource_collision! unless @force
generate_resource
when 'model'
check_model_collision! unless @force
generate_model_standalone
when 'controller' then generate_controller
end
end
|