sun-sword 0.0.10 → 0.0.12

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -2
  3. data/Gemfile +20 -0
  4. data/Gemfile.lock +252 -0
  5. data/README.md +74 -15
  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 +212 -29
  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 +398 -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 +177 -29
  48. data/.rspec +0 -3
  49. data/.rubocop.yml +0 -1146
  50. data/lib/generators/sun_sword/templates_frontend/controllers/site_controller.rb +0 -16
  51. data/lib/generators/sun_sword/templates_frontend/db/seeds.rb +0 -3
  52. data/lib/generators/sun_sword/templates_frontend/db/structures/example.yaml.tt +0 -106
  53. data/lib/generators/sun_sword/templates_frontend/frontend/pages/stimulus.js +0 -10
  54. data/lib/generators/sun_sword/templates_frontend/package.json +0 -7
  55. data/lib/generators/sun_sword/templates_frontend/views/site/_comment.html.erb.tt +0 -3
  56. 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,24 @@ 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
+
19
+ def validate_engine
20
+ return unless options[:engine]
21
+
22
+ unless engine_exists?
23
+ raise Thor::Error, "Engine '#{options[:engine]}' not found. Available engines: #{available_engines.join(', ')}"
24
+ end
25
+
26
+ say "Generating scaffold for engine: #{options[:engine]}", :cyan
27
+ end
28
+
8
29
  def running
9
30
  setup_variables
10
31
  create_root_folder
11
32
  create_controller_file
33
+ create_spec_files
12
34
  create_view_file
13
35
  create_link_file
14
36
  end
@@ -16,7 +38,7 @@ module SunSword
16
38
  private
17
39
 
18
40
  def setup_variables
19
- config = YAML.load_file("db/structures/#{arg_structure}_structure.yaml")
41
+ config = YAML.load_file(structure_file_path)
20
42
  @structure = Hashie::Mash.new(config)
21
43
 
22
44
  # Mengambil detail konfigurasi
@@ -26,6 +48,7 @@ module SunSword
26
48
 
27
49
  @actor = @structure.actor
28
50
  @resource_owner_id = @structure.resource_owner_id
51
+ @resource_owner = @structure.resource_owner
29
52
  @uploaders = @structure.uploaders || []
30
53
  @search_able = @structure.search_able || []
31
54
  @services = @structure.domains || {}
@@ -50,6 +73,16 @@ module SunSword
50
73
  @route_scope_path = arg_scope['scope'].to_s.downcase rescue ''
51
74
  @route_scope_class = @route_scope_path.camelize rescue ''
52
75
 
76
+ # Engine scope support
77
+ @engine_structure_path = (options[:engine_structure] || options[:engine]).to_s.downcase
78
+ @engine_structure_class = (options[:engine_structure] || options[:engine]).to_s.camelize
79
+
80
+ @engine_scope_path = options[:engine] ? options[:engine].to_s.downcase : @route_scope_path
81
+ @engine_scope_class = options[:engine] ? options[:engine].to_s.camelize : @route_scope_class
82
+
83
+ # Engine mount path for view rendering
84
+ @engine_mount_path = options[:engine] ? options[:engine].to_s.downcase : ''
85
+
53
86
  @mapping_fields = {
54
87
  string: :text_field,
55
88
  text: :text_area,
@@ -173,25 +206,33 @@ module SunSword
173
206
  end
174
207
 
175
208
  def create_root_folder
176
- empty_directory File.join('app/views', @route_scope_path.to_s, @scope_path.to_s)
209
+ empty_directory File.join(path_app, 'views', @engine_scope_path.to_s, @scope_path.to_s)
177
210
  end
178
211
 
179
212
  def create_controller_file
180
- template 'controllers/controller.rb.tt', File.join('app/controllers', @route_scope_path.to_s, "#{@scope_path}_controller.rb")
213
+ template 'controllers/controller.rb.tt', File.join(path_app, 'controllers', @engine_scope_path.to_s, "#{@scope_path}_controller.rb")
214
+ end
215
+
216
+ def create_spec_files
217
+ # Controller spec - sejajar dengan controller
218
+ controller_path = File.join(path_app, 'controllers', @engine_scope_path.to_s)
219
+ template 'controllers/controller_spec.rb.tt', File.join(controller_path, "#{@scope_path}_controller_spec.rb")
220
+
221
+ say 'Controller spec created successfully!', :green
181
222
  end
182
223
 
183
224
  def create_view_file
184
225
  @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')
226
+ template 'views/_form.html.erb.tt', File.join(path_app, 'views', @engine_scope_path.to_s, @scope_path.to_s, '_form.html.erb')
227
+ template 'views/edit.html.erb.tt', File.join(path_app, 'views', @engine_scope_path.to_s, @scope_path.to_s, 'edit.html.erb')
228
+ template 'views/index.html.erb.tt', File.join(path_app, 'views', @engine_scope_path.to_s, @scope_path.to_s, 'index.html.erb')
229
+ template 'views/new.html.erb.tt', File.join(path_app, 'views', @engine_scope_path.to_s, @scope_path.to_s, 'new.html.erb')
230
+ template 'views/show.html.erb.tt', File.join(path_app, 'views', @engine_scope_path.to_s, @scope_path.to_s, 'show.html.erb')
190
231
  end
191
232
 
192
233
  def namespace_exists?
193
- routes_file = 'config/routes.rb'
194
- scope_pattern = "namespace :#{@route_scope_path} do\n"
234
+ routes_file = routes_file_path
235
+ scope_pattern = "namespace :#{@engine_scope_path} do\n"
195
236
  if File.exist?(routes_file)
196
237
  file_content = File.read(routes_file)
197
238
  file_content.include?(scope_pattern)
@@ -200,40 +241,182 @@ module SunSword
200
241
  end
201
242
  end
202
243
 
244
+ # Engine support methods
245
+ def path_app
246
+ engine_path ? File.join(engine_path, 'app') : 'app'
247
+ end
248
+
249
+ def engine_path
250
+ return nil unless options[:engine]
251
+ @engine_path ||= detect_engine_path
252
+ end
253
+
254
+ def detect_engine_path
255
+ engine_name = options[:engine]
256
+ possible_paths = [
257
+ "engines/#{engine_name}",
258
+ "components/#{engine_name}",
259
+ "gems/#{engine_name}",
260
+ engine_name
261
+ ]
262
+
263
+ possible_paths.each do |path|
264
+ return path if Dir.exist?(path) && File.exist?(File.join(path, "#{engine_name}.gemspec"))
265
+ end
266
+
267
+ nil
268
+ end
269
+
270
+ def engine_exists?
271
+ !engine_path.nil?
272
+ end
273
+
274
+ def available_engines
275
+ engines = []
276
+ ['engines', 'components', 'gems', '.'].each do |dir|
277
+ next unless Dir.exist?(dir)
278
+
279
+ Dir.glob(File.join(dir, '*')).each do |path|
280
+ next unless Dir.exist?(path)
281
+
282
+ engine_name = File.basename(path)
283
+ gemspec = File.join(path, "#{engine_name}.gemspec")
284
+ engines << engine_name if File.exist?(gemspec)
285
+ end
286
+ end
287
+ engines.uniq
288
+ end
289
+
290
+ def structure_file_path
291
+ engine_name = options[:engine_structure] || options[:engine]
292
+
293
+ if engine_name
294
+ structure_engine_path = detect_structure_engine_path(engine_name)
295
+ if structure_engine_path
296
+ File.join(structure_engine_path, 'db/structures', "#{arg_structure}_structure.yaml")
297
+ else
298
+ raise Thor::Error, "Structure file not found in engine '#{engine_name}'"
299
+ end
300
+ else
301
+ "db/structures/#{arg_structure}_structure.yaml"
302
+ end
303
+ end
304
+
305
+ def detect_structure_engine_path(engine_name)
306
+ possible_paths = [
307
+ "engines/#{engine_name}",
308
+ "components/#{engine_name}",
309
+ "gems/#{engine_name}",
310
+ engine_name
311
+ ]
312
+
313
+ possible_paths.each do |path|
314
+ structure_dir = File.join(path, 'db/structures')
315
+ return path if Dir.exist?(structure_dir)
316
+ end
317
+
318
+ nil
319
+ end
320
+
321
+ def routes_file_path
322
+ engine_path ? File.join(engine_path, 'config/routes.rb') : 'config/routes.rb'
323
+ end
324
+
203
325
  def create_link_file
204
- template 'views/components/menu/link.html.erb.tt', File.join('app/views/components/menu', "_link_to_#{@scope_path}.html.erb")
326
+ partial_path = File.join(path_app, 'views/components/menu', "_link_to_#{@scope_path}.html.erb")
327
+ template 'views/components/menu/link.html.erb.tt', partial_path unless File.exist?(partial_path)
328
+
329
+ sidebar = File.join(path_app, 'views/components/layouts/_sidebar.html.erb')
330
+ marker = " <%# generate_link %>\n"
205
331
  link_to = " <li><%= render 'components/menu/#{"link_to_#{@scope_path}"}' %></li>\n"
206
- inject_into_file 'app/views/components/layouts/_sidebar.html.erb', link_to, before: " <%# generate_link %>\n"
207
- routes_file = 'config/routes.rb'
208
- if !namespace_exists? && @route_scope_path.present?
209
- scope_code = <<-RUBY
210
- namespace :#{@route_scope_path} do
211
- end
212
- RUBY
213
- insert_into_file routes_file, scope_code, after: "Rails.application.routes.draw do\n"
332
+ inject_into_file(sidebar, link_to, before: marker) if File.exist?(sidebar) && !File.read(sidebar).include?(link_to)
333
+
334
+ routes_file = routes_file_path
335
+
336
+ # Ensure routes file exists (create if needed for engine)
337
+ unless File.exist?(routes_file)
338
+ if engine_path
339
+ # Create routes file for engine if it doesn't exist
340
+ FileUtils.mkdir_p(File.dirname(routes_file))
341
+ # Use engine routes format: Web::Engine.routes.draw do
342
+ engine_class_name = options[:engine].to_s.camelize
343
+ engine_routes_header = "#{engine_class_name}::Engine.routes.draw do\n"
344
+ File.write(routes_file, "#{engine_routes_header}end\n")
345
+ say "Created routes file at #{routes_file}", :green
346
+ else
347
+ return # Skip if routes file doesn't exist in main app
348
+ end
214
349
  end
215
- if @route_scope_path.present?
216
- inject_into_file routes_file, " resources :#{@scope_path}\n", after: "namespace :#{@route_scope_path} do\n"
350
+
351
+ routes_content = File.read(routes_file)
352
+
353
+ # Determine routes draw pattern based on engine or main app
354
+ has_engine = options[:engine].present?
355
+
356
+ if has_engine
357
+ engine_class_name = options[:engine].to_s.camelize
358
+ routes_draw_pattern = "#{engine_class_name}::Engine.routes.draw do\n"
359
+ routes_draw_pattern_alt = "#{engine_class_name}::Engine.routes.draw do"
217
360
  else
218
- inject_into_file routes_file, " resources :#{@scope_path}\n", after: "Rails.application.routes.draw do\n"
361
+ routes_draw_pattern = "Rails.application.routes.draw do\n"
362
+ routes_draw_pattern_alt = 'Rails.application.routes.draw do'
363
+ end
364
+
365
+ resource_line = " resources :#{@scope_path}\n"
366
+
367
+ # Skip if resource already exists
368
+ return if routes_content.include?(resource_line)
369
+
370
+ begin
371
+ # Inject at root level (no namespace/scope)
372
+ if routes_content.include?(routes_draw_pattern)
373
+ inject_into_file routes_file, resource_line, after: routes_draw_pattern
374
+ say "Added routes '#{resource_line.strip}' to root level in #{routes_file}", :green
375
+ elsif routes_content.include?(routes_draw_pattern_alt)
376
+ inject_into_file routes_file, resource_line, after: routes_draw_pattern_alt
377
+ say "Added routes '#{resource_line.strip}' to root level in #{routes_file}", :green
378
+ else
379
+ say "Warning: Could not find routes draw pattern (#{routes_draw_pattern.strip}) in routes file: #{routes_file}", :yellow
380
+ say "Routes file content:\n#{routes_content}", :yellow if ENV['DEBUG']
381
+ end
382
+ rescue => e
383
+ say "Error adding routes to #{routes_file}: #{e.message}", :red
384
+ say e.backtrace.first(3).join("\n"), :red if ENV['DEBUG']
219
385
  end
220
386
  end
221
387
 
222
388
  def contract_fields
223
389
  skip_contract_fields = @skipped_fields.map(&:strip).uniq
224
- @model_class.columns.reject { |column| skip_contract_fields.include?(column.name.to_s) }.map(&:name).map(&:to_s)
390
+ @model_class.columns.reject { |column| skip_contract_fields.include?(column.name.to_s) }.map { |column| [column.name.to_s, column.type.to_s] }
225
391
  end
226
392
 
227
393
  def strong_params
228
- results = ''
229
- @controllers.form_fields.each do |field|
230
- if field.type.to_s.eql?('files')
231
- results << "{ #{field.name}: [] }, "
394
+ # pakai controllers.form_fields kalau ada, kalau tidak jatuh ke kolom model (contract_fields)
395
+ raw_fields = @controllers&.form_fields || contract_fields
396
+
397
+ # normalisasi jadi pasangan [name, type]
398
+ pairs = raw_fields.map do |f|
399
+ if f.is_a?(Array) && f.length == 2
400
+ # Already a tuple [name, type]
401
+ [f[0].to_s, f[1].to_s]
402
+ elsif f.respond_to?(:name)
403
+ [f.name.to_s, f.type.to_s]
232
404
  else
233
- results << ":#{field.name}, "
405
+ column_type = @model_class.columns_hash[f.to_s]&.type.to_s
406
+ [f.to_s, column_type.presence || 'string']
234
407
  end
235
408
  end
236
- results[0..-2]
409
+
410
+ permitted = pairs.map do |name, type|
411
+ case type
412
+ when 'files' then "{ #{name}: [] }" # multiple
413
+ when 'json', 'jsonb', 'hash' then "{ #{name}: {} }"
414
+ when 'array' then "{ #{name}: [] }"
415
+ else ":#{name}"
416
+ end
417
+ end
418
+
419
+ permitted.join(', ')
237
420
  end
238
421
  end
239
422
  end