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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -2
  3. data/Gemfile +8 -0
  4. data/Gemfile.lock +114 -55
  5. data/README.md +35 -13
  6. data/Rakefile +6 -10
  7. data/lib/generators/sun_sword/USAGE +22 -3
  8. data/lib/generators/sun_sword/frontend_generator.rb +77 -39
  9. data/lib/generators/sun_sword/frontend_generator_spec.rb +539 -0
  10. data/lib/generators/sun_sword/init_generator.rb +2 -0
  11. data/lib/generators/sun_sword/init_generator_spec.rb +82 -0
  12. data/lib/generators/sun_sword/scaffold_generator.rb +193 -24
  13. data/lib/generators/sun_sword/scaffold_generator_spec.rb +1414 -0
  14. data/lib/generators/sun_sword/templates_frontend/{Procfile.dev → Procfile.dev.tt} +1 -0
  15. data/lib/generators/sun_sword/templates_frontend/bin/{watch → watch.tt} +2 -1
  16. data/lib/generators/sun_sword/templates_frontend/config/{vite.json → vite.json.tt} +2 -1
  17. data/lib/generators/sun_sword/templates_frontend/controllers/application_controller.rb.tt +2 -2
  18. data/lib/generators/sun_sword/templates_frontend/controllers/tests_controller.rb +36 -0
  19. data/lib/generators/sun_sword/templates_frontend/controllers/tests_controller_spec.rb +62 -0
  20. data/lib/generators/sun_sword/templates_frontend/env.development +1 -1
  21. data/lib/generators/sun_sword/templates_frontend/frontend/entrypoints/application.js +2 -2
  22. data/lib/generators/sun_sword/templates_frontend/frontend/pages/tests-stimulus.js +31 -0
  23. data/lib/generators/sun_sword/templates_frontend/frontend/pages/web.js +12 -0
  24. data/lib/generators/sun_sword/templates_frontend/frontend/stylesheets/application.css +1 -3
  25. data/lib/generators/sun_sword/templates_frontend/helpers/application_helper.rb +17 -0
  26. data/lib/generators/sun_sword/templates_frontend/helpers/application_helper_spec.rb +30 -0
  27. data/lib/generators/sun_sword/templates_frontend/package.json.tt +14 -0
  28. data/lib/generators/sun_sword/templates_frontend/views/components/_action_destroy.html.erb.tt +1 -1
  29. data/lib/generators/sun_sword/templates_frontend/views/components/_alert.html.erb.tt +1 -1
  30. data/lib/generators/sun_sword/templates_frontend/views/layouts/application.html.erb.tt +3 -3
  31. data/lib/generators/sun_sword/templates_frontend/views/tests/_frame_content.html.erb +9 -0
  32. data/lib/generators/sun_sword/templates_frontend/views/tests/_log_entry.html.erb +4 -0
  33. data/lib/generators/sun_sword/templates_frontend/views/tests/_updated_content.html.erb +6 -0
  34. data/lib/generators/sun_sword/templates_frontend/views/tests/stimulus.html.erb +45 -0
  35. data/lib/generators/sun_sword/templates_frontend/views/tests/turbo_drive.html.erb +55 -0
  36. data/lib/generators/sun_sword/templates_frontend/views/tests/turbo_frame.html.erb +87 -0
  37. data/lib/generators/sun_sword/templates_frontend/vite.config.ts.tt +1 -1
  38. data/lib/generators/sun_sword/templates_init/config/initializers/sun_sword.rb +1 -0
  39. data/lib/generators/sun_sword/templates_scaffold/controllers/controller.rb.tt +24 -24
  40. data/lib/generators/sun_sword/templates_scaffold/controllers/controller_spec.rb.tt +374 -0
  41. data/lib/generators/sun_sword/templates_scaffold/views/index.html.erb.tt +5 -5
  42. data/lib/generators/sun_sword/templates_scaffold/views/show.html.erb.tt +3 -0
  43. data/lib/generators/tmp/db/structures/test_structure.yaml +42 -0
  44. data/lib/sun-sword.rb +1 -0
  45. data/lib/sun_sword/configuration_spec.rb +77 -0
  46. data/lib/sun_sword/version.rb +1 -1
  47. metadata +84 -30
  48. data/lib/generators/sun_sword/templates_frontend/controllers/site_controller.rb +0 -16
  49. data/lib/generators/sun_sword/templates_frontend/db/seeds.rb +0 -3
  50. data/lib/generators/sun_sword/templates_frontend/db/structures/example.yaml.tt +0 -106
  51. data/lib/generators/sun_sword/templates_frontend/frontend/pages/stimulus.js +0 -10
  52. data/lib/generators/sun_sword/templates_frontend/package.json +0 -7
  53. data/lib/generators/sun_sword/templates_frontend/views/site/_comment.html.erb.tt +0 -3
  54. 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("db/structures/#{arg_structure}_structure.yaml")
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('app/views', @route_scope_path.to_s, @scope_path.to_s)
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('app/controllers', @route_scope_path.to_s, "#{@scope_path}_controller.rb")
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('app/views', @route_scope_path.to_s, @scope_path.to_s, '_form.html.erb')
186
- template 'views/edit.html.erb.tt', File.join('app/views', @route_scope_path.to_s, @scope_path.to_s, 'edit.html.erb')
187
- template 'views/index.html.erb.tt', File.join('app/views', @route_scope_path.to_s, @scope_path.to_s, 'index.html.erb')
188
- template 'views/new.html.erb.tt', File.join('app/views', @route_scope_path.to_s, @scope_path.to_s, 'new.html.erb')
189
- template 'views/show.html.erb.tt', File.join('app/views', @route_scope_path.to_s, @scope_path.to_s, 'show.html.erb')
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 = 'config/routes.rb'
194
- scope_pattern = "namespace :#{@route_scope_path} do\n"
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('app/views/components/menu', "_link_to_#{@scope_path}.html.erb")
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 = 'app/views/components/layouts/_sidebar.html.erb'
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) unless File.read(sidebar).include?(link_to)
336
+ inject_into_file(sidebar, link_to, before: marker) if File.exist?(sidebar) && !File.read(sidebar).include?(link_to)
211
337
 
212
- routes_file = 'config/routes.rb'
213
- if @route_scope_path.present?
214
- unless namespace_exists?
215
- scope_code = " namespace :#{@route_scope_path} do\n end\n"
216
- insert_into_file routes_file, scope_code, after: "Rails.application.routes.draw do\n" unless File.read(routes_file).include?(scope_code)
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
- resource_line = " resources :#{@scope_path}\n"
219
- inject_into_file routes_file, resource_line, after: "namespace :#{@route_scope_path} do\n" unless File.read(routes_file).include?(resource_line)
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
- resource_line = " resources :#{@scope_path}\n"
222
- inject_into_file routes_file, resource_line, after: "Rails.application.routes.draw do\n" unless File.read(routes_file).include?(resource_line)
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(&:name).map(&:to_s)
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.respond_to?(:name)
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