roda-project 0.1.17 → 0.1.18

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/lib/roda/project/bin/generators/routes.rb +161 -72
  3. data/lib/roda/project/bin/generators.rb +1 -0
  4. data/lib/roda/project/cli.rb +19 -12
  5. data/lib/roda/project/helpers/ids.rb +9 -0
  6. data/lib/roda/project/main_context.rb +22 -1
  7. data/lib/roda/project/templates/base/app/app.rb.erb +11 -2
  8. data/lib/roda/project/templates/{base/scaffold → base-scaffold}/AGENTS.md.erb +12 -6
  9. data/lib/roda/project/templates/{base/scaffold → base-scaffold}/Gemfile.erb +3 -2
  10. data/lib/roda/project/templates/front-end-inertia/app/assets/css/app.css +20 -0
  11. data/lib/roda/project/templates/front-end-inertia/app/assets/js/app.js +11 -0
  12. data/lib/roda/project/templates/front-end-inertia/app/assets/js/inertia/components/Layout.jsx +11 -0
  13. data/lib/roda/project/templates/front-end-inertia/app/assets/js/inertia/views/Index.jsx +13 -0
  14. data/lib/roda/project/templates/front-end-inertia/app/config/locales/en.yml +273 -0
  15. data/lib/roda/project/templates/front-end-inertia/app/config/locales/foo/en.yml +2 -0
  16. data/lib/roda/project/templates/front-end-inertia/app/config/locales/foo/pt-br.yml +2 -0
  17. data/lib/roda/project/templates/front-end-inertia/app/config/locales/pt-br.yml +244 -0
  18. data/lib/roda/project/templates/front-end-inertia/app/views/foo/bar.erb +1 -0
  19. data/lib/roda/project/templates/front-end-inertia/app/views/index.erb +1 -0
  20. data/lib/roda/project/templates/front-end-inertia/app/views/layout.erb +23 -0
  21. data/lib/roda/project/templates/front-end-inertia/esbuild.js +53 -0
  22. data/lib/roda/project/templates/front-end-inertia/package.json +23 -0
  23. data/lib/roda/project/templates/front-end-inertia/public/assets/app.css +1 -0
  24. data/lib/roda/project/templates/front-end-inertia/public/images/roda-project.png +0 -0
  25. data/lib/roda/project/templates/tests-rspec/spec/app/views/html_spec.rb +0 -0
  26. data/lib/roda/project/version.rb +1 -1
  27. data/lib/roda/project.rb +3 -0
  28. metadata +53 -38
  29. data/lib/roda/project/templates/front-end/app/assets/js/some/foo.js +0 -5
  30. /data/lib/roda/project/templates/{base/minimal → base-minimal}/AGENTS.md +0 -0
  31. /data/lib/roda/project/templates/{base/minimal → base-minimal}/Gemfile.erb +0 -0
  32. /data/lib/roda/project/templates/{base/minimal → base-minimal}/Gemfile.lock +0 -0
  33. /data/lib/roda/project/templates/{base/minimal → base-minimal}/Guardfile +0 -0
  34. /data/lib/roda/project/templates/{base/minimal → base-minimal}/README.md +0 -0
  35. /data/lib/roda/project/templates/{base/minimal → base-minimal}/app.rb +0 -0
  36. /data/lib/roda/project/templates/{base/minimal → base-minimal}/bin/roda.erb +0 -0
  37. /data/lib/roda/project/templates/{base/minimal → base-minimal}/boot.rb +0 -0
  38. /data/lib/roda/project/templates/{base/minimal → base-minimal}/config.ru +0 -0
  39. /data/lib/roda/project/templates/{base/minimal → base-minimal}/public/exception_page.css +0 -0
  40. /data/lib/roda/project/templates/{base/scaffold → base-scaffold}/Guardfile +0 -0
  41. /data/lib/roda/project/templates/{base/scaffold → base-scaffold}/README.md +0 -0
  42. /data/lib/roda/project/templates/{base/scaffold → base-scaffold}/app/config/config.rb.erb +0 -0
  43. /data/lib/roda/project/templates/{base/scaffold → base-scaffold}/app/config/providers/logger.rb +0 -0
  44. /data/lib/roda/project/templates/{base/scaffold → base-scaffold}/app/config/providers/mailer.rb +0 -0
  45. /data/lib/roda/project/templates/{base/scaffold → base-scaffold}/app/routes/foo.rb.erb +0 -0
  46. /data/lib/roda/project/templates/{base/scaffold → base-scaffold}/bin/roda.erb +0 -0
  47. /data/lib/roda/project/templates/{base/scaffold → base-scaffold}/boot.rb.erb +0 -0
  48. /data/lib/roda/project/templates/{base/scaffold → base-scaffold}/config.ru.erb +0 -0
  49. /data/lib/roda/project/templates/{base/scaffold → base-scaffold}/public/exception_page.css +0 -0
  50. /data/lib/roda/project/templates/{tests/minitest/spec/app/views/html_spec.rb → front-end-inertia/public/assets/app.js} +0 -0
  51. /data/lib/roda/project/templates/{tests/minimal/minitest → tests-minimal-minitest}/spec/app_spec.rb +0 -0
  52. /data/lib/roda/project/templates/{tests/minimal/minitest → tests-minimal-minitest}/spec/spec_helper.rb.erb +0 -0
  53. /data/lib/roda/project/templates/{tests/minimal/rspec → tests-minimal-rspec}/spec/app_spec.rb +0 -0
  54. /data/lib/roda/project/templates/{tests/minimal/rspec → tests-minimal-rspec}/spec/spec_helper.rb +0 -0
  55. /data/lib/roda/project/templates/{tests/minitest → tests-minitest}/spec/app/app_spec.rb.erb +0 -0
  56. /data/lib/roda/project/templates/{tests/minitest → tests-minitest}/spec/app/routes/foo_spec.rb.erb +0 -0
  57. /data/lib/roda/project/templates/{tests/minitest → tests-minitest}/spec/app/routes/test_branch_spec.rb +0 -0
  58. /data/lib/roda/project/templates/{tests/rspec → tests-minitest}/spec/app/views/html_spec.rb +0 -0
  59. /data/lib/roda/project/templates/{tests/minitest → tests-minitest}/spec/spec_helper.rb.erb +0 -0
  60. /data/lib/roda/project/templates/{tests/rspec → tests-rspec}/spec/app/app_spec.rb.erb +0 -0
  61. /data/lib/roda/project/templates/{tests/rspec → tests-rspec}/spec/app/routes/foo_spec.rb.erb +0 -0
  62. /data/lib/roda/project/templates/{tests/rspec → tests-rspec}/spec/app/routes/test_branch_spec.rb +0 -0
  63. /data/lib/roda/project/templates/{tests/rspec → tests-rspec}/spec/spec_helper.rb.erb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6fa6f449cc4a6f6e7342135d2de859c481b517dc9666ca9e9d64bbc5141b27b5
4
- data.tar.gz: f95248f05abe4a1df4b5cd1018823c9243d1fa27fab9904b6f3c9dc966f972e4
3
+ metadata.gz: f0b645d449bd52931506a9e9ee0ef203d14f8b6606cee831d1570bae7e8b8e66
4
+ data.tar.gz: ca8c1717d95f3bb0aebe8f698df9dee7306eb687b2c7d763a2f6d3d4ea46a746
5
5
  SHA512:
6
- metadata.gz: 9022aca6837ac8803328e3d785514fe3c93d9bdbd2a272c1eee5c1ea231298543db5ef3840fd53f83ba5a6f56d92199a1750d8462635b3146ddb6adea4407067
7
- data.tar.gz: a589f4edadacd2f4aa54a5ef53526764c6bebefbacc16553768fc0d24a38851c7744577c843279d84a8976a287aeb0208f37e66f15e6f2ec5a8e4c0a4a060645
6
+ metadata.gz: f60f531f4683da41ba01d19e67ded642ffb4cf8e828044287354d11362bb31a07cdb6ff4affd75483ebc384d11140b4c9014173af24116b95c4efaaba93173c5
7
+ data.tar.gz: 0d87b9e35efedb3f34dd250edfbdf12bae629d1c92562d9a0652aff2c6dd741a67515119ec08ac79855a7626f5f65766c5a59c13a2399cdeef126b3a58a42717
@@ -4,9 +4,12 @@ class Roda
4
4
  module Bin
5
5
  class Generators < ::Thor
6
6
  class Routes < Roda::Project::Bin::Generator
7
+ DEFAULT_VIEW_NAME = "index"
8
+ VALID_METHODS = %w[get post delete put patch].freeze
9
+
7
10
  def call
8
11
  if branch_name.nil? || branch_name.empty? || routes_list.nil? || routes_list.empty?
9
- puts "Usage: bin/roda g routes branch_name route1 route2:method"
12
+ puts "Usage: bin/roda g routes branch_name route1 method:route2"
10
13
  exit 1
11
14
  end
12
15
 
@@ -21,52 +24,40 @@ class Roda
21
24
 
22
25
  def generate_routes
23
26
  filename = File.join(ensure_and_get_path("app/routes", branch_name), "#{branch_name}.rb")
24
- route_definitions = routes_list.map do |route_str|
27
+ route_definitions = routes_list.map { |route_str| build_route_definition(route_str) }.join("\n\n")
28
+
29
+ write_file(filename, routes_file_content(route_definitions))
30
+ end
31
+
32
+ def generate_views
33
+ return unless must_generate_views?
34
+
35
+ @context.inertia? ? generate_inertia_views : generate_erb_views
36
+ end
37
+
38
+ def generate_erb_views
39
+ branch_views_dir = File.join("app/views", branch_name)
40
+ FileUtils.mkdir_p(branch_views_dir)
41
+ routes_list.each do |route_str|
25
42
  method, name = parse_route_string(route_str)
26
- view_name = name
27
- view_name = "index" if view_name == nil
28
- view_line = (method == "get" && must_generate_views?) ? "\n view('#{view_name}')" : ""
29
-
30
- if name != nil
31
- " r.#{method} \"#{name}\" do#{view_line}\n end"
32
- else
33
- " r.#{method} do#{view_line}\n end"
34
- end
35
- end.join("\n\n")
43
+ next unless method == "get"
36
44
 
37
- hash_branch_header = if branch_name.include?("/")
38
- parts = branch_name.split("/")
39
- sub_path = parts[0..-2].join("/")
40
- branch_segment = parts.last
41
- "hash_branch #{namespace_for(sub_path)}, \"#{branch_segment}\" do |r|"
42
- else
43
- "hash_branch \"#{branch_name}\" do |r|"
45
+ name = DEFAULT_VIEW_NAME if name.nil?
46
+ write_file(File.join(branch_views_dir, "#{name}.erb"), "")
44
47
  end
45
-
46
- content = <<~RUBY
47
- class #{@context.const_project_name}
48
- #{hash_branch_header}
49
- #{route_definitions}
50
- end
51
- end
52
- RUBY
53
- File.write(filename, content)
54
- action_success_message(filename)
55
48
  end
56
49
 
57
- def generate_views
58
- if must_generate_views?
59
- branch_views_dir = File.join("app/views", branch_name)
60
- FileUtils.mkdir_p(branch_views_dir)
61
- routes_list.each do |route_str|
62
- method, name = parse_route_string(route_str)
63
- if method == "get"
64
- name = "index" if name == nil
65
- view_filename = File.join(branch_views_dir, "#{name}.erb")
66
- File.write(view_filename, "")
67
- action_success_message(view_filename)
68
- end
69
- end
50
+ def generate_inertia_views
51
+ views_dir = "app/assets/js/inertia/views"
52
+ FileUtils.mkdir_p(views_dir)
53
+ routes_list.each do |route_str|
54
+ method, name = parse_route_string(route_str)
55
+ next unless method == "get"
56
+
57
+ path = InertiaPath.new(branch_name, name)
58
+ view_filename = File.join(views_dir, "#{path.view_path}.jsx")
59
+ FileUtils.mkdir_p(File.dirname(view_filename))
60
+ write_file(view_filename, inertia_component_content(path))
70
61
  end
71
62
  end
72
63
 
@@ -75,31 +66,16 @@ class Roda
75
66
  nesting_level = branch_name.count("/")
76
67
  relative_spec_helper_path = "../" * (2 + nesting_level) + "spec_helper"
77
68
 
78
- test_route_definitions = routes_list.map do |route_str|
79
- method, name = parse_route_string(route_str)
80
-
81
- if name != nil
82
- " it \"responds to #{method.upcase} /#{branch_name}/#{name}\" do\n" \
83
- " #{method} \"/#{branch_name}/#{name}\"\n" \
84
- " expect(last_response.status).to eq(200)\n" \
85
- " end"
86
- else
87
- " it \"responds to #{method.upcase} /#{branch_name}\" do\n" \
88
- " #{method} \"/#{branch_name}\"\n" \
89
- " expect(last_response.status).to eq(200)\n" \
90
- " end"
91
- end
92
- end.join("\n")
69
+ test_route_definitions = routes_list.map { |route_str| build_test_definition(route_str) }.join("\n")
93
70
 
94
71
  test_content = <<~RUBY
95
- require_relative "#{relative_spec_helper_path}"
72
+ require_relative "#{relative_spec_helper_path}"
96
73
 
97
- describe "Routes for #{branch_name}" do
98
- #{test_route_definitions}
99
- end
74
+ describe "Routes for #{branch_name}" do
75
+ #{test_route_definitions}
76
+ end
100
77
  RUBY
101
- File.write(test_filename, test_content)
102
- action_success_message(test_filename)
78
+ write_file(test_filename, test_content)
103
79
  end
104
80
 
105
81
  def generate_nested_branch_files
@@ -117,11 +93,61 @@ class Roda
117
93
 
118
94
  dir = File.dirname(filename)
119
95
  FileUtils.mkdir_p(dir) unless File.directory?(dir)
120
- File.write(filename, nested_branch_file_content(prefix, index))
121
- action_success_message(filename)
96
+ write_file(filename, nested_branch_file_content(prefix, index))
97
+ end
98
+ end
99
+
100
+ def build_route_definition(route_str)
101
+ method, name = parse_route_string(route_str)
102
+ view_line = build_view_line(method, name)
103
+ route_block(method, name, view_line)
104
+ end
105
+
106
+ def build_view_line(method, name)
107
+ return "" unless method == "get" && must_generate_views?
108
+
109
+ if @context.inertia?
110
+ "\n inertia('#{InertiaPath.new(branch_name, name).view_path}')"
111
+ else
112
+ "\n view('#{name || DEFAULT_VIEW_NAME}')"
122
113
  end
123
114
  end
124
115
 
116
+ def route_block(method, name, view_line)
117
+ route_name = name ? " \"#{name}\"" : ""
118
+ " r.#{method}#{route_name} do#{view_line}\n end"
119
+ end
120
+
121
+ def build_test_definition(route_str)
122
+ method, name = parse_route_string(route_str)
123
+ test_name = name ? " /#{branch_name}/#{name}" : " /#{branch_name}"
124
+ test_path = name ? " \"/#{branch_name}/#{name}\"" : " \"/#{branch_name}\""
125
+
126
+ " it \"responds to #{method.upcase}#{test_name}\" do\n" \
127
+ " #{method}#{test_path}\n" \
128
+ " expect(last_response.status).to eq(200)\n" \
129
+ " end"
130
+ end
131
+
132
+ def routes_file_content(route_definitions)
133
+ <<~RUBY
134
+ class #{@context.const_project_name}
135
+ #{build_hash_branch_header}
136
+ #{route_definitions}
137
+ end
138
+ end
139
+ RUBY
140
+ end
141
+
142
+ def build_hash_branch_header
143
+ return "hash_branch \"#{branch_name}\" do |r|" unless branch_name.include?("/")
144
+
145
+ parts = branch_name.split("/")
146
+ sub_path = parts[0..-2].join("/")
147
+ branch_segment = parts.last
148
+ "hash_branch #{namespace_for(sub_path)}, \"#{branch_segment}\" do |r|"
149
+ end
150
+
125
151
  def nested_branch_file_content(prefix, index)
126
152
  parts = prefix.split("/")
127
153
  segment = parts.last
@@ -133,11 +159,11 @@ class Roda
133
159
  end
134
160
 
135
161
  <<~RUBY
136
- class #{@context.const_project_name}
137
- #{header} # #{prefix} branch
138
- r.hash_branches(#{namespace_for(prefix)}) # #{prefix}/ +1 routes
139
- end
140
- end
162
+ class #{@context.const_project_name}
163
+ #{header} # #{prefix} branch
164
+ r.hash_branches(#{namespace_for(prefix)}) # #{prefix}/ +1 routes
165
+ end
166
+ end
141
167
  RUBY
142
168
  end
143
169
 
@@ -154,12 +180,47 @@ class Roda
154
180
  end
155
181
 
156
182
  def must_generate_views?
157
- @options[:views] && Dir.exist?("app/views")
183
+ return false unless @options[:views]
184
+
185
+ if @context.inertia?
186
+ Dir.exist?("app/assets/js/inertia/views")
187
+ else
188
+ Dir.exist?("app/views")
189
+ end
190
+ end
191
+
192
+ def inertia_component_content(path)
193
+ <<~JSX
194
+ import React from 'react'
195
+ import { Link } from '@inertiajs/react'
196
+ import Layout from '../components/Layout'
197
+
198
+ const #{path.component_name} = props => {
199
+ return (<Layout>
200
+ <div>
201
+ #{path.display_path}
202
+ </div>
203
+ </Layout>)
204
+ }
205
+
206
+ export default #{path.component_name}
207
+ JSX
158
208
  end
159
209
 
160
210
  def parse_route_string(route_str)
161
211
  method, name = route_str.split(":", 2)
162
- [method, name]
212
+ if name && VALID_METHODS.include?(method)
213
+ [method, name]
214
+ elsif name.nil? && VALID_METHODS.include?(route_str)
215
+ [route_str, nil]
216
+ else
217
+ ["get", route_str]
218
+ end
219
+ end
220
+
221
+ def write_file(filename, content)
222
+ File.write(filename, content)
223
+ action_success_message(filename)
163
224
  end
164
225
 
165
226
  def routes_list
@@ -169,6 +230,34 @@ class Roda
169
230
  def branch_name
170
231
  @branch_name ||= @args[0]
171
232
  end
233
+
234
+ class InertiaPath
235
+ attr_reader :view_path, :component_name, :display_path
236
+
237
+ def initialize(branch_name, name)
238
+ @view_path = build_view_path(branch_name, name)
239
+ @component_name = build_component_name(name)
240
+ @display_path = build_display_path(branch_name, name)
241
+ end
242
+
243
+ private
244
+
245
+ def build_view_path(branch_name, name)
246
+ segments = branch_name.split("/")
247
+ segments << (name || "index")
248
+ (segments[0..-2] + [segments.last.capitalize]).join("/")
249
+ end
250
+
251
+ def build_component_name(name)
252
+ (name || "index").capitalize
253
+ end
254
+
255
+ def build_display_path(branch_name, name)
256
+ segments = branch_name.split("/")
257
+ segments << (name || "index")
258
+ segments.join("/")
259
+ end
260
+ end
172
261
  end
173
262
  end
174
263
  end
@@ -37,6 +37,7 @@ class Roda
37
37
  @context = Roda::Project::MainContext.new
38
38
  @context.project_name = options[:main_context][:project_name]
39
39
  @context.base = options[:main_context][:base]
40
+ @context.frontend = options[:main_context][:frontend]
40
41
  @context.database = options[:main_context][:database]
41
42
  @context.database_type = options[:main_context][:database_type]
42
43
  @context.rodauth = options[:main_context][:rodauth]
@@ -49,6 +49,10 @@ class Roda
49
49
  return if jeremy?
50
50
 
51
51
  retry_on_error { @context.base = read_line("(#{fullstack_id}) Fullstack (#{api_id}) API (#{minimal_id}) Minimal › ", fullstack_id).to_i }
52
+ if @context.fullstack?
53
+ retry_on_error { @context.frontend = read_line("(#{erb_id}) only ERB (#{inertia_id}) Inertia.js › ", erb_id).to_i }
54
+ end
55
+
52
56
  retry_on_error { @context.tests = read_line("(#{rspec_id}) RSpec (#{minitest_id}) Minitest › ", rspec_id).to_i }
53
57
 
54
58
  unless @context.minimal?
@@ -75,7 +79,7 @@ class Roda
75
79
  puts "* creating base project"
76
80
  if @context.minimal?
77
81
  TTY::File.copy_directory(
78
- File.expand_path("templates/base/minimal", __dir__),
82
+ File.expand_path("templates/base-minimal", __dir__),
79
83
  "#{@dir}#{@context.project_name}",
80
84
  context: @context
81
85
  )
@@ -84,7 +88,7 @@ class Roda
84
88
  end
85
89
 
86
90
  TTY::File.copy_directory(
87
- File.expand_path("templates/base/scaffold", __dir__),
91
+ File.expand_path("templates/base-scaffold", __dir__),
88
92
  "#{@dir}#{@context.project_name}",
89
93
  context: @context
90
94
  )
@@ -101,16 +105,19 @@ class Roda
101
105
  def add_front_end
102
106
  if @context.fullstack?
103
107
  puts "* adding front-end"
104
- erb_cp_dir("front-end", "app/assets")
105
- erb_cp_file("front-end", "esbuild.js")
106
- erb_cp_file("front-end", "package.json")
107
- cp_dir("front-end", "app/config/locales")
108
+ from_template = "front-end"
109
+ from_template = "front-end-inertia" if @context.inertia?
110
+
111
+ erb_cp_dir(from_template, "app/assets")
112
+ erb_cp_file(from_template, "esbuild.js")
113
+ erb_cp_file(from_template, "package.json")
114
+ cp_dir(from_template, "app/config/locales")
108
115
  action_success_message("app/config/locales")
109
- cp_dir("front-end", "app/views")
116
+ cp_dir(from_template, "app/views")
110
117
  action_success_message("app/views")
111
- cp_dir("front-end", "public/assets")
118
+ cp_dir(from_template, "public/assets")
112
119
  action_success_message("public/assets")
113
- cp_dir("front-end", "public/images")
120
+ cp_dir(from_template, "public/images")
114
121
  action_success_message("public/images")
115
122
  end
116
123
  end
@@ -138,12 +145,12 @@ class Roda
138
145
 
139
146
  def add_test_files
140
147
  puts "* adding test files"
141
- minimal_dir = @context.minimal? ? "minimal/" : ""
148
+ minimal_dir = @context.minimal? ? "minimal-" : ""
142
149
 
143
150
  if @context.rspec?
144
- erb_cp_dir("tests/#{minimal_dir}rspec", "spec")
151
+ erb_cp_dir("tests-#{minimal_dir}rspec", "spec")
145
152
  else
146
- erb_cp_dir("tests/#{minimal_dir}minitest", "spec")
153
+ erb_cp_dir("tests-#{minimal_dir}minitest", "spec")
147
154
  end
148
155
  end
149
156
  end
@@ -3,6 +3,8 @@ class Roda
3
3
  module Helpers
4
4
  module Ids
5
5
  def fullstack_id = Roda::Project::FULLSTACK
6
+ def erb_id = Roda::Project::ERB
7
+ def inertia_id = Roda::Project::INERTIA
6
8
  def api_id = Roda::Project::API
7
9
  def minimal_id = Roda::Project::MINIMAL
8
10
  def mysql_id = Roda::Project::MYSQL
@@ -23,6 +25,13 @@ class Roda
23
25
  when minimal_id
24
26
  "MINIMAL"
25
27
  end
28
+ when :frontend
29
+ case id
30
+ when erb_id
31
+ "ERB"
32
+ when inertia_id
33
+ "INERTIA"
34
+ end
26
35
  when :database
27
36
  case id
28
37
  when mysql_id
@@ -15,7 +15,8 @@ class Roda
15
15
  :default_db_url,
16
16
  :db_gem,
17
17
  :project_name,
18
- :base
18
+ :base,
19
+ :frontend
19
20
  )
20
21
 
21
22
  def to_thor_option
@@ -30,6 +31,9 @@ class Roda
30
31
  if database?
31
32
  option << " database_type: #{id_to_string(database_type, :database)},"
32
33
  end
34
+ if fullstack?
35
+ option << "\n frontend: #{id_to_string(frontend, :frontend)},"
36
+ end
33
37
  option << "\n }"
34
38
  end
35
39
 
@@ -49,6 +53,14 @@ class Roda
49
53
  @base = val
50
54
  end
51
55
 
56
+ def frontend=(val)
57
+ if ![erb_id, inertia_id].include?(val)
58
+ raise InvalidValue, "Invalid frontend option: #{val}"
59
+ end
60
+
61
+ @frontend = val
62
+ end
63
+
52
64
  def rodauth=(val)
53
65
  val = false if ![true, false].include?(val)
54
66
 
@@ -106,6 +118,7 @@ class Roda
106
118
  end
107
119
 
108
120
  def root_example
121
+ return 'inertia("Index")' if inertia?
109
122
  return 'view("index")' if fullstack?
110
123
 
111
124
  '{ message: "hello world" }'
@@ -124,6 +137,14 @@ class Roda
124
137
  base == minimal_id
125
138
  end
126
139
 
140
+ def inertia?
141
+ frontend == inertia_id
142
+ end
143
+
144
+ def erb?
145
+ frontend == erb_id
146
+ end
147
+
127
148
  def rodauth?
128
149
  rodauth == true
129
150
  end
@@ -12,8 +12,17 @@ class <%= context.const_project_name %> < Roda
12
12
  plugin :render, **Config.get[:render]
13
13
  plugin :content_for, append: false
14
14
  plugin :flash
15
- plugin :assets_manifest
16
- <% end %>
15
+ plugin :assets_manifest<% if context.inertia? %>
16
+ plugin :inertia,
17
+ version: assets_version,
18
+ layout: {
19
+ head: [
20
+ '<meta http-equiv="x-ua-compatible" content="ie=edge">',
21
+ css_entrypoint_tag(:app)
22
+ ].join,
23
+ body: js_entrypoint_tag(:app)
24
+ }
25
+ <% end %><% end %>
17
26
  # Request / Response
18
27
  plugin :halt
19
28
  plugin :json
@@ -5,8 +5,9 @@
5
5
  - **Web Framework**: Roda (STRICT: Do NOT use Rails, ActionController, or Sinatra conventions).
6
6
  - **No Rails Magic:** Do not use `ActiveSupport` methods (like `.present?` or `.blank?`) unless the gem is explicitly in the Gemfile.<% if context.database %>
7
7
  - **ORM**: Sequel (STRICT: Do NOT use ActiveRecord. Use Sequel Models, Datasets, and Migrations).<% end %>
8
- - **Autoloader**: Zeitwerk (Do NOT manually `require` files located in the `app/` directory. Only require standard libraries or gems).<% if context.fullstack? %>
9
- - **Frontend**: esbuild + ERB (Fullstack mode).<% end %>
8
+ - **Autoloader**: Zeitwerk (Do NOT manually `require` files located in the `app/` directory. Only require standard libraries or gems).<% if context.fullstack? && context.erb? %>
9
+ - **Frontend**: esbuild + ERB (Fullstack mode).<% else %>
10
+ - **Frontend**: esbuild + Inertia.js + React.js (Fullstack mode).<% end %>
10
11
 
11
12
  ## Code Style & Conventions<%= i = 0 %>
12
13
 
@@ -15,13 +16,18 @@
15
16
  - Rely on standard Ruby 3.x patterns. Use explicit YARD docs for method signatures.
16
17
 
17
18
  <% if context.fullstack? %>### <%= i=i+1 %>. Views & Presentation
18
-
19
+ <% if context.erb? %>
19
20
  - ERB templates go in `app/views/`.
20
21
  - The `view` method inside a route block will match the hash branch to find the view path location:
21
22
  - For `hash_branch "foo/bar", "zas" do |r|`, calling `view('something')` will search an erb file in "views/foo/bar/zas/something.erb".
22
- - For `hash_branch "zas" do |r|`, calling `view('something')` will search an erb file in "views/zas/something.erb".
23
-
24
- <% end %>### <%= i=i+1 %>. Routing
23
+ - For `hash_branch "zas" do |r|`, calling `view('something')` will search an erb file in "views/zas/something.erb".<% else %>
24
+ - Inertia.js views go in `app/assets/js/inertia/views/`.
25
+ - The `inertia` method inside a route block will find the inertia view location and pass the props to the component:
26
+ - Calling `inertia('Something')` will search an inertia view file in "app/assets/js/inertia/views/Something.jsx".
27
+ - Calling `inertia('Zoo', { name: "foo", age: 3 })` will search an inertia view file in "app/assets/js/inertia/views/Zoo.jsx" and set the props data in the React function component.
28
+ - Calling `inertia('Foo/Bar')` will search an inertia view file in "app/assets/js/inertia/views/Foo/Bar.jsx".<% end %>
29
+ <% end %>
30
+ ### <%= i=i+1 %>. Routing
25
31
 
26
32
  - The main app resides in `app/[project_name].rb`.
27
33
  - Sub-routes MUST be implemented using Roda **Hash Branches** (`hash_branches` plugin) and placed in `app/routes/`.
@@ -22,8 +22,9 @@ gem "puma"
22
22
  # gem 'thin'<% if context.fullstack? %>
23
23
 
24
24
  # Views
25
- gem 'roda-assets_manifest'
26
- <% end %>
25
+ gem 'roda-assets_manifest'<% if context.inertia? %>
26
+ gem 'roda-inertia-lite'
27
+ <% end %><% end %>
27
28
  # Performance
28
29
  gem "oj"
29
30
  # gem 'async'
@@ -0,0 +1,20 @@
1
+ body {
2
+ background: black;
3
+ color: white;
4
+ margin: 1em 2em;
5
+ font-family: monospace;
6
+ }
7
+
8
+ a {
9
+ color: #ef4348;
10
+ margin-right: 1em;
11
+ }
12
+
13
+ img {
14
+ width: 25%;
15
+ margin-bottom: 1em;
16
+ }
17
+
18
+ .navbar {
19
+ margin-bottom: 1em;
20
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react'
2
+ import { createInertiaApp } from '@inertiajs/react'
3
+ import { createRoot } from 'react-dom/client'
4
+
5
+ createInertiaApp({
6
+ resolve: name => import(`./inertia/views/${name}.jsx`),
7
+ page: JSON.parse(document.getElementById('app').dataset.page),
8
+ setup({ el, App, props }) {
9
+ createRoot(el).render(<App {...props} />)
10
+ },
11
+ })
@@ -0,0 +1,11 @@
1
+ import React from 'react'
2
+ //import { Link } from "@inertiajs/react"
3
+
4
+ export default function Layout({ children }) {
5
+ return (
6
+ <div>
7
+ (Layout)
8
+ {children}
9
+ </div>
10
+ )
11
+ }
@@ -0,0 +1,13 @@
1
+ import React from 'react'
2
+ import Layout from '../components/Layout'
3
+ //import { Link } from '@inertiajs/react'
4
+
5
+ const Index = props => {
6
+ return (<Layout>
7
+ <div>
8
+ Hello from Roda + Inertia.js!
9
+ </div>
10
+ </Layout>)
11
+ }
12
+
13
+ export default Index