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.
- 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 +189 -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 +398 -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,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(
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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 =
|
|
194
|
-
scope_pattern = "namespace :#{@
|
|
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('
|
|
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 = '
|
|
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)
|
|
332
|
+
inject_into_file(sidebar, link_to, before: marker) if File.exist?(sidebar) && !File.read(sidebar).include?(link_to)
|
|
211
333
|
|
|
212
|
-
routes_file =
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
219
|
-
|
|
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
|
-
|
|
222
|
-
|
|
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
|
|
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.
|
|
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
|