sun-sword 0.0.11 → 0.0.13
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/CHANGELOG.md +57 -2
- data/Gemfile +8 -0
- data/Gemfile.lock +114 -55
- data/README.md +35 -13
- data/Rakefile +6 -10
- data/lib/generators/sun_sword/USAGE +22 -3
- data/lib/generators/sun_sword/frontend_generator.rb +77 -39
- data/lib/generators/sun_sword/frontend_generator_spec.rb +539 -0
- data/lib/generators/sun_sword/init_generator.rb +2 -0
- data/lib/generators/sun_sword/init_generator_spec.rb +82 -0
- data/lib/generators/sun_sword/scaffold_generator.rb +193 -24
- data/lib/generators/sun_sword/scaffold_generator_spec.rb +1414 -0
- data/lib/generators/sun_sword/templates_frontend/{Procfile.dev → Procfile.dev.tt} +1 -0
- data/lib/generators/sun_sword/templates_frontend/bin/{watch → watch.tt} +2 -1
- data/lib/generators/sun_sword/templates_frontend/config/{vite.json → vite.json.tt} +2 -1
- data/lib/generators/sun_sword/templates_frontend/controllers/application_controller.rb.tt +2 -2
- data/lib/generators/sun_sword/templates_frontend/controllers/tests_controller.rb +36 -0
- data/lib/generators/sun_sword/templates_frontend/controllers/tests_controller_spec.rb +62 -0
- data/lib/generators/sun_sword/templates_frontend/env.development +1 -1
- data/lib/generators/sun_sword/templates_frontend/frontend/entrypoints/application.js +2 -2
- data/lib/generators/sun_sword/templates_frontend/frontend/pages/tests-stimulus.js +31 -0
- data/lib/generators/sun_sword/templates_frontend/frontend/pages/web.js +12 -0
- data/lib/generators/sun_sword/templates_frontend/frontend/stylesheets/application.css +1 -3
- data/lib/generators/sun_sword/templates_frontend/helpers/application_helper.rb +17 -0
- data/lib/generators/sun_sword/templates_frontend/helpers/application_helper_spec.rb +30 -0
- data/lib/generators/sun_sword/templates_frontend/package.json.tt +14 -0
- data/lib/generators/sun_sword/templates_frontend/views/components/_action_destroy.html.erb.tt +1 -1
- data/lib/generators/sun_sword/templates_frontend/views/components/_alert.html.erb.tt +1 -1
- data/lib/generators/sun_sword/templates_frontend/views/layouts/application.html.erb.tt +3 -3
- data/lib/generators/sun_sword/templates_frontend/views/tests/_frame_content.html.erb +9 -0
- data/lib/generators/sun_sword/templates_frontend/views/tests/_log_entry.html.erb +4 -0
- data/lib/generators/sun_sword/templates_frontend/views/tests/_updated_content.html.erb +6 -0
- data/lib/generators/sun_sword/templates_frontend/views/tests/stimulus.html.erb +45 -0
- data/lib/generators/sun_sword/templates_frontend/views/tests/turbo_drive.html.erb +55 -0
- data/lib/generators/sun_sword/templates_frontend/views/tests/turbo_frame.html.erb +87 -0
- data/lib/generators/sun_sword/templates_frontend/vite.config.ts.tt +1 -1
- data/lib/generators/sun_sword/templates_init/config/initializers/sun_sword.rb +1 -0
- data/lib/generators/sun_sword/templates_scaffold/controllers/controller.rb.tt +24 -24
- data/lib/generators/sun_sword/templates_scaffold/controllers/controller_spec.rb.tt +374 -0
- data/lib/generators/sun_sword/templates_scaffold/views/index.html.erb.tt +5 -5
- data/lib/generators/sun_sword/templates_scaffold/views/show.html.erb.tt +3 -0
- data/lib/generators/tmp/db/structures/test_structure.yaml +42 -0
- data/lib/sun-sword.rb +1 -0
- data/lib/sun_sword/configuration_spec.rb +77 -0
- data/lib/sun_sword/version.rb +1 -1
- metadata +84 -30
- data/lib/generators/sun_sword/templates_frontend/controllers/site_controller.rb +0 -16
- data/lib/generators/sun_sword/templates_frontend/db/seeds.rb +0 -3
- data/lib/generators/sun_sword/templates_frontend/db/structures/example.yaml.tt +0 -106
- data/lib/generators/sun_sword/templates_frontend/frontend/pages/stimulus.js +0 -10
- data/lib/generators/sun_sword/templates_frontend/package.json +0 -7
- data/lib/generators/sun_sword/templates_frontend/views/site/_comment.html.erb.tt +0 -3
- data/lib/generators/sun_sword/templates_frontend/views/site/stimulus.html.erb.tt +0 -26
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
require 'rails/generators'
|
|
2
|
+
require 'active_support/inflector'
|
|
3
|
+
require 'active_support/core_ext/object/blank'
|
|
4
|
+
require 'active_support/core_ext/enumerable'
|
|
5
|
+
require 'hashie'
|
|
6
|
+
require 'yaml'
|
|
7
|
+
require 'fileutils'
|
|
8
|
+
|
|
1
9
|
module SunSword
|
|
2
10
|
class ScaffoldGenerator < Rails::Generators::Base
|
|
3
11
|
source_root File.expand_path('templates_scaffold', __dir__)
|
|
@@ -5,10 +13,25 @@ module SunSword
|
|
|
5
13
|
argument :arg_structure, type: :string, default: '', banner: ''
|
|
6
14
|
argument :arg_scope, type: :hash, default: '', banner: 'scope:dashboard'
|
|
7
15
|
|
|
16
|
+
class_option :engine, type: :string, default: nil, desc: 'Specify target engine name (e.g., admin, api)'
|
|
17
|
+
class_option :engine_structure, type: :string, default: nil, desc: 'Specify engine where structure file is located'
|
|
18
|
+
class_option :domain, type: :string, default: nil, desc: 'Specify domain prefix for UseCases (e.g., core)'
|
|
19
|
+
|
|
20
|
+
def validate_engine
|
|
21
|
+
return unless options[:engine]
|
|
22
|
+
|
|
23
|
+
unless engine_exists?
|
|
24
|
+
raise Thor::Error, "Engine '#{options[:engine]}' not found. Available engines: #{available_engines.join(', ')}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
say "Generating scaffold for engine: #{options[:engine]}", :cyan
|
|
28
|
+
end
|
|
29
|
+
|
|
8
30
|
def running
|
|
9
31
|
setup_variables
|
|
10
32
|
create_root_folder
|
|
11
33
|
create_controller_file
|
|
34
|
+
create_spec_files
|
|
12
35
|
create_view_file
|
|
13
36
|
create_link_file
|
|
14
37
|
end
|
|
@@ -16,7 +39,7 @@ module SunSword
|
|
|
16
39
|
private
|
|
17
40
|
|
|
18
41
|
def setup_variables
|
|
19
|
-
config = YAML.load_file(
|
|
42
|
+
config = YAML.load_file(structure_file_path)
|
|
20
43
|
@structure = Hashie::Mash.new(config)
|
|
21
44
|
|
|
22
45
|
# Mengambil detail konfigurasi
|
|
@@ -26,6 +49,7 @@ module SunSword
|
|
|
26
49
|
|
|
27
50
|
@actor = @structure.actor
|
|
28
51
|
@resource_owner_id = @structure.resource_owner_id
|
|
52
|
+
@resource_owner = @structure.resource_owner
|
|
29
53
|
@uploaders = @structure.uploaders || []
|
|
30
54
|
@search_able = @structure.search_able || []
|
|
31
55
|
@services = @structure.domains || {}
|
|
@@ -50,6 +74,19 @@ module SunSword
|
|
|
50
74
|
@route_scope_path = arg_scope['scope'].to_s.downcase rescue ''
|
|
51
75
|
@route_scope_class = @route_scope_path.camelize rescue ''
|
|
52
76
|
|
|
77
|
+
# Engine scope support
|
|
78
|
+
@engine_structure_path = (options[:engine_structure] || options[:engine]).to_s.downcase
|
|
79
|
+
@engine_structure_class = (options[:engine_structure] || options[:engine]).to_s.camelize
|
|
80
|
+
|
|
81
|
+
@engine_scope_path = options[:engine] ? options[:engine].to_s.downcase : @route_scope_path
|
|
82
|
+
@engine_scope_class = options[:engine] ? options[:engine].to_s.camelize : @route_scope_class
|
|
83
|
+
|
|
84
|
+
# Engine mount path for view rendering
|
|
85
|
+
@engine_mount_path = options[:engine] ? options[:engine].to_s.downcase : ''
|
|
86
|
+
|
|
87
|
+
# UseCase domain prefix
|
|
88
|
+
@usecase_domain = options[:domain] ? options[:domain].to_s.camelize : @engine_structure_class
|
|
89
|
+
|
|
53
90
|
@mapping_fields = {
|
|
54
91
|
string: :text_field,
|
|
55
92
|
text: :text_area,
|
|
@@ -173,25 +210,33 @@ module SunSword
|
|
|
173
210
|
end
|
|
174
211
|
|
|
175
212
|
def create_root_folder
|
|
176
|
-
empty_directory File.join('
|
|
213
|
+
empty_directory File.join(path_app, 'views', @engine_scope_path.to_s, @scope_path.to_s)
|
|
177
214
|
end
|
|
178
215
|
|
|
179
216
|
def create_controller_file
|
|
180
|
-
template 'controllers/controller.rb.tt', File.join('
|
|
217
|
+
template 'controllers/controller.rb.tt', File.join(path_app, 'controllers', @engine_scope_path.to_s, "#{@scope_path}_controller.rb")
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def create_spec_files
|
|
221
|
+
# Controller spec - sejajar dengan controller
|
|
222
|
+
controller_path = File.join(path_app, 'controllers', @engine_scope_path.to_s)
|
|
223
|
+
template 'controllers/controller_spec.rb.tt', File.join(controller_path, "#{@scope_path}_controller_spec.rb")
|
|
224
|
+
|
|
225
|
+
say 'Controller spec created successfully!', :green
|
|
181
226
|
end
|
|
182
227
|
|
|
183
228
|
def create_view_file
|
|
184
229
|
@form_fields_html = generate_form_fields_html
|
|
185
|
-
template 'views/_form.html.erb.tt', File.join('
|
|
186
|
-
template 'views/edit.html.erb.tt', File.join('
|
|
187
|
-
template 'views/index.html.erb.tt', File.join('
|
|
188
|
-
template 'views/new.html.erb.tt', File.join('
|
|
189
|
-
template 'views/show.html.erb.tt', File.join('
|
|
230
|
+
template 'views/_form.html.erb.tt', File.join(path_app, 'views', @engine_scope_path.to_s, @scope_path.to_s, '_form.html.erb')
|
|
231
|
+
template 'views/edit.html.erb.tt', File.join(path_app, 'views', @engine_scope_path.to_s, @scope_path.to_s, 'edit.html.erb')
|
|
232
|
+
template 'views/index.html.erb.tt', File.join(path_app, 'views', @engine_scope_path.to_s, @scope_path.to_s, 'index.html.erb')
|
|
233
|
+
template 'views/new.html.erb.tt', File.join(path_app, 'views', @engine_scope_path.to_s, @scope_path.to_s, 'new.html.erb')
|
|
234
|
+
template 'views/show.html.erb.tt', File.join(path_app, 'views', @engine_scope_path.to_s, @scope_path.to_s, 'show.html.erb')
|
|
190
235
|
end
|
|
191
236
|
|
|
192
237
|
def namespace_exists?
|
|
193
|
-
routes_file =
|
|
194
|
-
scope_pattern = "namespace :#{@
|
|
238
|
+
routes_file = routes_file_path
|
|
239
|
+
scope_pattern = "namespace :#{@engine_scope_path} do\n"
|
|
195
240
|
if File.exist?(routes_file)
|
|
196
241
|
file_content = File.read(routes_file)
|
|
197
242
|
file_content.include?(scope_pattern)
|
|
@@ -200,32 +245,153 @@ module SunSword
|
|
|
200
245
|
end
|
|
201
246
|
end
|
|
202
247
|
|
|
248
|
+
# Engine support methods
|
|
249
|
+
def path_app
|
|
250
|
+
engine_path ? File.join(engine_path, 'app') : 'app'
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def engine_path
|
|
254
|
+
return nil unless options[:engine]
|
|
255
|
+
@engine_path ||= detect_engine_path
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def detect_engine_path
|
|
259
|
+
engine_name = options[:engine]
|
|
260
|
+
possible_paths = [
|
|
261
|
+
"engines/#{engine_name}",
|
|
262
|
+
"components/#{engine_name}",
|
|
263
|
+
"gems/#{engine_name}",
|
|
264
|
+
engine_name
|
|
265
|
+
]
|
|
266
|
+
|
|
267
|
+
possible_paths.each do |path|
|
|
268
|
+
return path if Dir.exist?(path) && File.exist?(File.join(path, "#{engine_name}.gemspec"))
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
nil
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def engine_exists?
|
|
275
|
+
!engine_path.nil?
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def available_engines
|
|
279
|
+
engines = []
|
|
280
|
+
['engines', 'components', 'gems', '.'].each do |dir|
|
|
281
|
+
next unless Dir.exist?(dir)
|
|
282
|
+
|
|
283
|
+
Dir.glob(File.join(dir, '*')).each do |path|
|
|
284
|
+
next unless Dir.exist?(path)
|
|
285
|
+
|
|
286
|
+
engine_name = File.basename(path)
|
|
287
|
+
gemspec = File.join(path, "#{engine_name}.gemspec")
|
|
288
|
+
engines << engine_name if File.exist?(gemspec)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
engines.uniq
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def structure_file_path
|
|
295
|
+
engine_name = options[:engine_structure] || options[:engine]
|
|
296
|
+
|
|
297
|
+
if engine_name
|
|
298
|
+
structure_engine_path = detect_structure_engine_path(engine_name)
|
|
299
|
+
if structure_engine_path
|
|
300
|
+
File.join(structure_engine_path, 'db/structures', "#{arg_structure}_structure.yaml")
|
|
301
|
+
else
|
|
302
|
+
raise Thor::Error, "Structure file not found in engine '#{engine_name}'"
|
|
303
|
+
end
|
|
304
|
+
else
|
|
305
|
+
"db/structures/#{arg_structure}_structure.yaml"
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def detect_structure_engine_path(engine_name)
|
|
310
|
+
possible_paths = [
|
|
311
|
+
"engines/#{engine_name}",
|
|
312
|
+
"components/#{engine_name}",
|
|
313
|
+
"gems/#{engine_name}",
|
|
314
|
+
engine_name
|
|
315
|
+
]
|
|
316
|
+
|
|
317
|
+
possible_paths.each do |path|
|
|
318
|
+
structure_dir = File.join(path, 'db/structures')
|
|
319
|
+
return path if Dir.exist?(structure_dir)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
nil
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def routes_file_path
|
|
326
|
+
engine_path ? File.join(engine_path, 'config/routes.rb') : 'config/routes.rb'
|
|
327
|
+
end
|
|
328
|
+
|
|
203
329
|
def create_link_file
|
|
204
|
-
partial_path = File.join('
|
|
330
|
+
partial_path = File.join(path_app, 'views/components/menu', "_link_to_#{@scope_path}.html.erb")
|
|
205
331
|
template 'views/components/menu/link.html.erb.tt', partial_path unless File.exist?(partial_path)
|
|
206
332
|
|
|
207
|
-
sidebar = '
|
|
333
|
+
sidebar = File.join(path_app, 'views/components/layouts/_sidebar.html.erb')
|
|
208
334
|
marker = " <%# generate_link %>\n"
|
|
209
335
|
link_to = " <li><%= render 'components/menu/#{"link_to_#{@scope_path}"}' %></li>\n"
|
|
210
|
-
inject_into_file(sidebar, link_to, before: marker)
|
|
336
|
+
inject_into_file(sidebar, link_to, before: marker) if File.exist?(sidebar) && !File.read(sidebar).include?(link_to)
|
|
211
337
|
|
|
212
|
-
routes_file =
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
338
|
+
routes_file = routes_file_path
|
|
339
|
+
|
|
340
|
+
# Ensure routes file exists (create if needed for engine)
|
|
341
|
+
unless File.exist?(routes_file)
|
|
342
|
+
if engine_path
|
|
343
|
+
# Create routes file for engine if it doesn't exist
|
|
344
|
+
FileUtils.mkdir_p(File.dirname(routes_file))
|
|
345
|
+
# Use engine routes format: Web::Engine.routes.draw do
|
|
346
|
+
engine_class_name = options[:engine].to_s.camelize
|
|
347
|
+
engine_routes_header = "#{engine_class_name}::Engine.routes.draw do\n"
|
|
348
|
+
File.write(routes_file, "#{engine_routes_header}end\n")
|
|
349
|
+
say "Created routes file at #{routes_file}", :green
|
|
350
|
+
else
|
|
351
|
+
return # Skip if routes file doesn't exist in main app
|
|
217
352
|
end
|
|
218
|
-
|
|
219
|
-
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
routes_content = File.read(routes_file)
|
|
356
|
+
|
|
357
|
+
# Determine routes draw pattern based on engine or main app
|
|
358
|
+
has_engine = options[:engine].present?
|
|
359
|
+
|
|
360
|
+
if has_engine
|
|
361
|
+
engine_class_name = options[:engine].to_s.camelize
|
|
362
|
+
routes_draw_pattern = "#{engine_class_name}::Engine.routes.draw do\n"
|
|
363
|
+
routes_draw_pattern_alt = "#{engine_class_name}::Engine.routes.draw do"
|
|
220
364
|
else
|
|
221
|
-
|
|
222
|
-
|
|
365
|
+
routes_draw_pattern = "Rails.application.routes.draw do\n"
|
|
366
|
+
routes_draw_pattern_alt = 'Rails.application.routes.draw do'
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
resource_line = " resources :#{@scope_path}\n"
|
|
370
|
+
|
|
371
|
+
# Skip if resource already exists
|
|
372
|
+
return if routes_content.include?(resource_line)
|
|
373
|
+
|
|
374
|
+
begin
|
|
375
|
+
# Inject at root level (no namespace/scope)
|
|
376
|
+
if routes_content.include?(routes_draw_pattern)
|
|
377
|
+
inject_into_file routes_file, resource_line, after: routes_draw_pattern
|
|
378
|
+
say "Added routes '#{resource_line.strip}' to root level in #{routes_file}", :green
|
|
379
|
+
elsif routes_content.include?(routes_draw_pattern_alt)
|
|
380
|
+
inject_into_file routes_file, resource_line, after: routes_draw_pattern_alt
|
|
381
|
+
say "Added routes '#{resource_line.strip}' to root level in #{routes_file}", :green
|
|
382
|
+
else
|
|
383
|
+
say "Warning: Could not find routes draw pattern (#{routes_draw_pattern.strip}) in routes file: #{routes_file}", :yellow
|
|
384
|
+
say "Routes file content:\n#{routes_content}", :yellow if ENV['DEBUG']
|
|
385
|
+
end
|
|
386
|
+
rescue => e
|
|
387
|
+
say "Error adding routes to #{routes_file}: #{e.message}", :red
|
|
388
|
+
say e.backtrace.first(3).join("\n"), :red if ENV['DEBUG']
|
|
223
389
|
end
|
|
224
390
|
end
|
|
225
391
|
|
|
226
392
|
def contract_fields
|
|
227
393
|
skip_contract_fields = @skipped_fields.map(&:strip).uniq
|
|
228
|
-
@model_class.columns.reject { |column| skip_contract_fields.include?(column.name.to_s) }.map
|
|
394
|
+
@model_class.columns.reject { |column| skip_contract_fields.include?(column.name.to_s) }.map { |column| [column.name.to_s, column.type.to_s] }
|
|
229
395
|
end
|
|
230
396
|
|
|
231
397
|
def strong_params
|
|
@@ -234,7 +400,10 @@ module SunSword
|
|
|
234
400
|
|
|
235
401
|
# normalisasi jadi pasangan [name, type]
|
|
236
402
|
pairs = raw_fields.map do |f|
|
|
237
|
-
if f.
|
|
403
|
+
if f.is_a?(Array) && f.length == 2
|
|
404
|
+
# Already a tuple [name, type]
|
|
405
|
+
[f[0].to_s, f[1].to_s]
|
|
406
|
+
elsif f.respond_to?(:name)
|
|
238
407
|
[f.name.to_s, f.type.to_s]
|
|
239
408
|
else
|
|
240
409
|
column_type = @model_class.columns_hash[f.to_s]&.type.to_s
|