sun-sword 0.0.11 → 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 (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 +189 -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 +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 +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,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,32 +241,153 @@ 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
- partial_path = 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")
205
327
  template 'views/components/menu/link.html.erb.tt', partial_path unless File.exist?(partial_path)
206
328
 
207
- sidebar = 'app/views/components/layouts/_sidebar.html.erb'
329
+ sidebar = File.join(path_app, 'views/components/layouts/_sidebar.html.erb')
208
330
  marker = " <%# generate_link %>\n"
209
331
  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)
332
+ inject_into_file(sidebar, link_to, before: marker) if File.exist?(sidebar) && !File.read(sidebar).include?(link_to)
211
333
 
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)
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
217
348
  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)
349
+ end
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"
220
360
  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)
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']
223
385
  end
224
386
  end
225
387
 
226
388
  def contract_fields
227
389
  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)
390
+ @model_class.columns.reject { |column| skip_contract_fields.include?(column.name.to_s) }.map { |column| [column.name.to_s, column.type.to_s] }
229
391
  end
230
392
 
231
393
  def strong_params
@@ -234,7 +396,10 @@ module SunSword
234
396
 
235
397
  # normalisasi jadi pasangan [name, type]
236
398
  pairs = raw_fields.map do |f|
237
- if f.respond_to?(:name)
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)
238
403
  [f.name.to_s, f.type.to_s]
239
404
  else
240
405
  column_type = @model_class.columns_hash[f.to_s]&.type.to_s