roda-project 0.1.11 → 0.1.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.
- checksums.yaml +4 -4
- data/lib/roda/project/bin/generators/migration.rb +1 -1
- data/lib/roda/project/bin/generators/model.rb +2 -2
- data/lib/roda/project/bin/generators/routes.rb +25 -9
- data/lib/roda/project/bin/generators/service.rb +120 -0
- data/lib/roda/project/bin/generators.rb +9 -0
- data/lib/roda/project/cli.rb +18 -8
- data/lib/roda/project/generator.rb +2 -2
- data/lib/roda/project/jeremy_template_creator.rb +32 -0
- data/lib/roda/project/main_context.rb +3 -3
- data/lib/roda/project/templates/base/app/app.rb.erb +1 -2
- data/lib/roda/project/templates/base/minimal/AGENTS.md +15 -6
- data/lib/roda/project/templates/base/minimal/bin/roda.erb +4 -0
- data/lib/roda/project/templates/base/scaffold/AGENTS.md.erb +47 -0
- data/lib/roda/project/templates/base/scaffold/Gemfile.erb +2 -1
- data/lib/roda/project/templates/base/scaffold/app/config/config.rb.erb +14 -1
- data/lib/roda/project/templates/base/scaffold/app/routes/foo.rb.erb +2 -1
- data/lib/roda/project/templates/base/scaffold/bin/roda.erb +5 -1
- data/lib/roda/project/templates/base/scaffold/boot.rb.erb +7 -1
- data/lib/roda/project/version.rb +1 -1
- data/lib/roda/project.rb +3 -0
- metadata +8 -6
- data/lib/roda/project/templates/base/scaffold/AGENTS.md +0 -9
- /data/lib/roda/project/templates/{base/scaffold → front-end}/app/config/locales/en.yml +0 -0
- /data/lib/roda/project/templates/{base/scaffold → front-end}/app/config/locales/foo/en.yml +0 -0
- /data/lib/roda/project/templates/{base/scaffold → front-end}/app/config/locales/foo/pt-br.yml +0 -0
- /data/lib/roda/project/templates/{base/scaffold → front-end}/app/config/locales/pt-br.yml +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3156e8ffc1fbf7c48c30c1224bfccd2bf66c775b90cb802ada325c2ef2a5607e
|
|
4
|
+
data.tar.gz: 02e04e9cb18a32f82cad2fceb59f1d567d33fe61330f1f0a26bb6e38ebf3b618
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b2a91d0af7f801951d65f288a3fcbc0012b3779e6736dfdd94d7703377834cad15d8a8680293b1800a8293e90cfb59d8d3f971e1e67abedf682b32a432e9920a
|
|
7
|
+
data.tar.gz: b80916d58cdd4a167f8be8a9e5e43f4c56730457818b6cfee081ac4946d1e353284c7b6d087f9d030a03d7a705426e78fe871610dd5438dfe3c1c9bb1d426563
|
|
@@ -22,7 +22,7 @@ class Roda
|
|
|
22
22
|
filename = File.join("spec/app/models", *name_segments[0..-2], "#{model_file_basename}_spec.rb")
|
|
23
23
|
File.write(filename, spec_code)
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
action_success_message(filename)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def create_model
|
|
@@ -30,7 +30,7 @@ class Roda
|
|
|
30
30
|
filename = File.join("app/models", *name_segments[0..-2], "#{model_file_basename}.rb")
|
|
31
31
|
File.write(filename, code)
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
action_success_message(filename)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def spec_code
|
|
@@ -23,8 +23,15 @@ class Roda
|
|
|
23
23
|
filename = File.join(ensure_and_get_path("app/routes", branch_name), "#{branch_name}.rb")
|
|
24
24
|
route_definitions = routes_list.map do |route_str|
|
|
25
25
|
method, name = parse_route_string(route_str)
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
view_name = name
|
|
27
|
+
view_name = "index" if view_name == ""
|
|
28
|
+
view_line = (method == "get" && must_generate_views?) ? "\n view('#{view_name}')" : ""
|
|
29
|
+
|
|
30
|
+
if name != ""
|
|
31
|
+
" r.#{method} \"#{name}\" do#{view_line}\n end"
|
|
32
|
+
else
|
|
33
|
+
" r.#{method} do#{view_line}\n end"
|
|
34
|
+
end
|
|
28
35
|
end.join("\n\n")
|
|
29
36
|
|
|
30
37
|
hash_branch_header = if branch_name.include?("/")
|
|
@@ -45,7 +52,7 @@ class Roda
|
|
|
45
52
|
end
|
|
46
53
|
RUBY
|
|
47
54
|
File.write(filename, content)
|
|
48
|
-
|
|
55
|
+
action_success_message(filename)
|
|
49
56
|
end
|
|
50
57
|
|
|
51
58
|
def generate_views
|
|
@@ -55,9 +62,10 @@ class Roda
|
|
|
55
62
|
routes_list.each do |route_str|
|
|
56
63
|
method, name = parse_route_string(route_str)
|
|
57
64
|
if method == "get"
|
|
65
|
+
name = "index" if name == ""
|
|
58
66
|
view_filename = File.join(branch_views_dir, "#{name}.erb")
|
|
59
67
|
File.write(view_filename, "")
|
|
60
|
-
|
|
68
|
+
action_success_message(view_filename)
|
|
61
69
|
end
|
|
62
70
|
end
|
|
63
71
|
end
|
|
@@ -70,10 +78,18 @@ class Roda
|
|
|
70
78
|
|
|
71
79
|
test_route_definitions = routes_list.map do |route_str|
|
|
72
80
|
method, name = parse_route_string(route_str)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"
|
|
76
|
-
|
|
81
|
+
|
|
82
|
+
if name != ""
|
|
83
|
+
" it \"responds to #{method.upcase} /#{branch_name}/#{name}\" do\n" \
|
|
84
|
+
" #{method} \"/#{branch_name}/#{name}\"\n" \
|
|
85
|
+
" expect(last_response.status).to eq(200)\n" \
|
|
86
|
+
" end"
|
|
87
|
+
else
|
|
88
|
+
" it \"responds to #{method.upcase} /#{branch_name} do\n" \
|
|
89
|
+
" #{method} \"/#{branch_name}\n" \
|
|
90
|
+
" expect(last_response.status).to eq(200)\n" \
|
|
91
|
+
" end"
|
|
92
|
+
end
|
|
77
93
|
end.join("\n")
|
|
78
94
|
|
|
79
95
|
test_content = <<~RUBY
|
|
@@ -84,7 +100,7 @@ class Roda
|
|
|
84
100
|
end
|
|
85
101
|
RUBY
|
|
86
102
|
File.write(test_filename, test_content)
|
|
87
|
-
|
|
103
|
+
action_success_message(test_filename)
|
|
88
104
|
end
|
|
89
105
|
|
|
90
106
|
def must_generate_views?
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
class Roda
|
|
2
|
+
module Project
|
|
3
|
+
module Bin
|
|
4
|
+
class Generators < ::Thor
|
|
5
|
+
class Service < Roda::Project::Bin::Generator
|
|
6
|
+
include Roda::Project::Helpers::Inflections
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
unless valid_args?
|
|
10
|
+
puts "Usage: bin/roda g service <module|class> <name>"
|
|
11
|
+
exit 1
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
puts "* creating service"
|
|
15
|
+
create_service
|
|
16
|
+
create_service_spec
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def create_service
|
|
20
|
+
ensure_and_get_path("app/services", service_relative_path)
|
|
21
|
+
filename = File.join("app/services", *name_segments[0..-2], "#{service_file_basename}.rb")
|
|
22
|
+
File.write(filename, code)
|
|
23
|
+
|
|
24
|
+
action_success_message(filename)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def create_service_spec
|
|
28
|
+
ensure_and_get_path("spec/app/services", service_relative_path)
|
|
29
|
+
filename = File.join("spec/app/services", *name_segments[0..-2], "#{service_file_basename}_spec.rb")
|
|
30
|
+
File.write(filename, spec_code)
|
|
31
|
+
|
|
32
|
+
action_success_message(filename)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def spec_code
|
|
36
|
+
depth = 2 + name_segments.length - 1
|
|
37
|
+
relative_prefix = "../" * depth
|
|
38
|
+
<<~RUBY
|
|
39
|
+
require_relative "#{relative_prefix}spec_helper"
|
|
40
|
+
|
|
41
|
+
describe #{qualified_class_name} do
|
|
42
|
+
end
|
|
43
|
+
RUBY
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def code
|
|
47
|
+
leaf_keyword = service_type
|
|
48
|
+
if name_segments.length == 1
|
|
49
|
+
<<~RUBY
|
|
50
|
+
#{leaf_keyword} #{camelize(name_segments.last)}
|
|
51
|
+
end
|
|
52
|
+
RUBY
|
|
53
|
+
else
|
|
54
|
+
depth = name_segments.length - 1
|
|
55
|
+
leaf_indent = " " * depth
|
|
56
|
+
lines = [
|
|
57
|
+
"#{leaf_indent}#{leaf_keyword} #{camelize(name_segments.last)}",
|
|
58
|
+
"#{leaf_indent}end"
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
name_segments[0..-2].map { |s| camelize(s) }.reverse.each_with_index do |mod, idx|
|
|
62
|
+
mod_indent = " " * (depth - 1 - idx)
|
|
63
|
+
lines = ["#{mod_indent}module #{mod}"] + lines + ["#{mod_indent}end"]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
lines.join("\n") + "\n"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def name_segments
|
|
71
|
+
@name_segments ||= service_name.split("/")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def qualified_class_name
|
|
75
|
+
@qualified_class_name ||= name_segments.map { |s| camelize(s) }.join("::")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def service_relative_path
|
|
79
|
+
@service_relative_path ||= if name_segments.length > 1
|
|
80
|
+
File.join(*name_segments[0..-2], service_file_basename)
|
|
81
|
+
else
|
|
82
|
+
service_file_basename
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def service_file_basename
|
|
87
|
+
@service_file_basename ||= underscore(name_segments.last)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def service_type
|
|
91
|
+
@service_type ||= parse_args[:type]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def service_name
|
|
95
|
+
@service_name ||= parse_args[:name]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def parse_args
|
|
99
|
+
@parse_args ||= begin
|
|
100
|
+
if %w[module class].include?(@args[0].to_s.downcase)
|
|
101
|
+
{ type: @args[0].to_s.downcase, name: @args[1].to_s }
|
|
102
|
+
else
|
|
103
|
+
{ type: "class", name: @args[0].to_s }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def valid_args?
|
|
109
|
+
return false unless %w[module class].include?(service_type)
|
|
110
|
+
return false if service_name.empty?
|
|
111
|
+
return false if service_name.include?(" ")
|
|
112
|
+
return false if service_name.match?(/\A\d/)
|
|
113
|
+
|
|
114
|
+
true
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -20,6 +20,15 @@ class Roda
|
|
|
20
20
|
Model.new(context:, args:, options:).call
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
desc "service", "Create service module or class and test scaffold"
|
|
24
|
+
def service(*args)
|
|
25
|
+
Service.new(context:, args:, options:).call
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.exit_on_failure?
|
|
29
|
+
true
|
|
30
|
+
end
|
|
31
|
+
|
|
23
32
|
private
|
|
24
33
|
|
|
25
34
|
def context
|
data/lib/roda/project/cli.rb
CHANGED
|
@@ -10,8 +10,8 @@ class Roda
|
|
|
10
10
|
puts pastel.italic("#{Roda::Project.messages.sample.first}\n")
|
|
11
11
|
|
|
12
12
|
get_user_context
|
|
13
|
-
|
|
14
13
|
puts pastel.bright_black("\n[project: #{@context.project_name}]\n")
|
|
14
|
+
return JeremyTemplateCreator.new(context: @context).call if jeremy?
|
|
15
15
|
|
|
16
16
|
create_base_project
|
|
17
17
|
add_front_end
|
|
@@ -26,14 +26,16 @@ class Roda
|
|
|
26
26
|
puts "\n* create your database\n"
|
|
27
27
|
puts "$ bin/roda db create"
|
|
28
28
|
end
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
if @context.rodauth?
|
|
30
|
+
puts "\nmigrate the database (use RACK_ENV to migrate 'test' or 'production' environments):\n\n"
|
|
31
|
+
puts "$ bin/roda db migrate"
|
|
32
|
+
end
|
|
31
33
|
end
|
|
32
34
|
puts "\nrun and watch the project in dev mode:\n"
|
|
33
35
|
puts "\n$ bin/roda dev"
|
|
34
36
|
if @context.fullstack?
|
|
35
37
|
puts "\ncompile and watch assets:\n"
|
|
36
|
-
puts "\n$ bin/roda assets
|
|
38
|
+
puts "\n$ bin/roda assets -w"
|
|
37
39
|
end
|
|
38
40
|
puts "\nrun 'bin/roda' inside #{@context.project_name} to see all available tasks\n\n"
|
|
39
41
|
rescue TTY::Reader::InputInterrupt
|
|
@@ -44,6 +46,8 @@ class Roda
|
|
|
44
46
|
|
|
45
47
|
def get_user_context
|
|
46
48
|
retry_on_error { @context.project_name = read_line("Project name › ", "project") }
|
|
49
|
+
return if jeremy?
|
|
50
|
+
|
|
47
51
|
retry_on_error { @context.base = read_line("(#{fullstack_id}) Fullstack (#{api_id}) API (#{minimal_id}) Minimal › ", fullstack_id).to_i }
|
|
48
52
|
retry_on_error { @context.tests = read_line("(#{rspec_id}) RSpec (#{minitest_id}) Minitest › ", rspec_id).to_i }
|
|
49
53
|
|
|
@@ -63,6 +67,10 @@ class Roda
|
|
|
63
67
|
end
|
|
64
68
|
end
|
|
65
69
|
|
|
70
|
+
def jeremy?
|
|
71
|
+
@jeremy ||= ARGV[0] == "--jeremy"
|
|
72
|
+
end
|
|
73
|
+
|
|
66
74
|
def create_base_project
|
|
67
75
|
puts "* creating base project"
|
|
68
76
|
if @context.minimal?
|
|
@@ -96,12 +104,14 @@ class Roda
|
|
|
96
104
|
erb_cp_dir("front-end", "app/assets")
|
|
97
105
|
erb_cp_file("front-end", "esbuild.js")
|
|
98
106
|
erb_cp_file("front-end", "package.json")
|
|
107
|
+
cp_dir("front-end", "app/config/locales")
|
|
108
|
+
action_success_message("app/config/locales")
|
|
99
109
|
cp_dir("front-end", "app/views")
|
|
100
|
-
|
|
110
|
+
action_success_message("app/views")
|
|
101
111
|
cp_dir("front-end", "public/assets")
|
|
102
|
-
|
|
112
|
+
action_success_message("public/assets")
|
|
103
113
|
cp_dir("front-end", "public/images")
|
|
104
|
-
|
|
114
|
+
action_success_message("public/images")
|
|
105
115
|
end
|
|
106
116
|
end
|
|
107
117
|
|
|
@@ -121,7 +131,7 @@ class Roda
|
|
|
121
131
|
erb_cp_file("rodauth", "db/migrations/001_add_rodauth.rb")
|
|
122
132
|
if @context.fullstack?
|
|
123
133
|
cp_file("rodauth", "app/views/create-account.erb")
|
|
124
|
-
|
|
134
|
+
action_success_message("app/views/create-account.erb")
|
|
125
135
|
end
|
|
126
136
|
end
|
|
127
137
|
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
|
|
3
|
+
class Roda
|
|
4
|
+
module Project
|
|
5
|
+
class JeremyTemplateCreator < Generator
|
|
6
|
+
include Helpers::Inflections
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
repo_url = "https://github.com/jeremyevans/roda-sequel-stack"
|
|
10
|
+
puts "* Downloading template with git...\n\n"
|
|
11
|
+
|
|
12
|
+
success = system("git clone --depth 1 #{repo_url} #{@context.project_name}")
|
|
13
|
+
abort("\nCould not download the template.") unless success
|
|
14
|
+
|
|
15
|
+
git_dir = File.join(@context.project_name, ".git")
|
|
16
|
+
if Dir.exist?(git_dir)
|
|
17
|
+
FileUtils.rm_rf(git_dir)
|
|
18
|
+
puts "\n* Cleaned up template git history.\n\n"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
action_success_message("./#{@context.project_name}")
|
|
22
|
+
|
|
23
|
+
setup_command = "cd #{@context.project_name} && rake setup[#{camelize(@context.project_name)}]"
|
|
24
|
+
system(setup_command)
|
|
25
|
+
|
|
26
|
+
action_success_message(setup_command, "run")
|
|
27
|
+
puts "\nSetup is done! follow the link below to learn about this template:"
|
|
28
|
+
puts "\n#{repo_url}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -23,9 +23,9 @@ class Roda
|
|
|
23
23
|
option :main_context, type: :hash, default: {
|
|
24
24
|
project_name: "#{project_name}",
|
|
25
25
|
base: #{id_to_string(base, :base)},
|
|
26
|
-
rodauth: #{rodauth},
|
|
26
|
+
rodauth: #{rodauth || false},
|
|
27
27
|
tests: #{id_to_string(tests, :tests)},
|
|
28
|
-
database: #{database},
|
|
28
|
+
database: #{database || false},
|
|
29
29
|
RUBY
|
|
30
30
|
if database?
|
|
31
31
|
option << " database_type: #{id_to_string(database_type, :database)},"
|
|
@@ -108,7 +108,7 @@ class Roda
|
|
|
108
108
|
def root_example
|
|
109
109
|
return 'view("index")' if fullstack?
|
|
110
110
|
|
|
111
|
-
'{ message: "
|
|
111
|
+
'{ message: "hello world" }'
|
|
112
112
|
end
|
|
113
113
|
# rubocop:enable Lint/InterpolationCheck
|
|
114
114
|
|
|
@@ -8,7 +8,7 @@ class <%= context.const_project_name %> < Roda
|
|
|
8
8
|
<% if context.fullstack? %>
|
|
9
9
|
# Views
|
|
10
10
|
plugin :partials, views: "app/views"
|
|
11
|
-
plugin :render,
|
|
11
|
+
plugin :render, **Config.get[:render]
|
|
12
12
|
plugin :content_for, append: false
|
|
13
13
|
plugin :flash
|
|
14
14
|
plugin :assets_manifest
|
|
@@ -69,7 +69,6 @@ class <%= context.const_project_name %> < Roda
|
|
|
69
69
|
# r.i18n_set_locale_from(:session)<% end %>
|
|
70
70
|
<% if context.rodauth? %>
|
|
71
71
|
r.rodauth
|
|
72
|
-
rodauth.require_authentication
|
|
73
72
|
<% end %>
|
|
74
73
|
r.root do
|
|
75
74
|
<%= context.root_example %>
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
# Project Code Generation Rules
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Core Stack
|
|
4
|
+
- **Web Framework**: Roda (STRICT: Do NOT use Rails, ActionController, or Sinatra conventions).
|
|
5
|
+
- **Autoloader**: Zeitwerk (Do NOT manually `require` files located in the `app/` directory. Only require standard libraries or gems).
|
|
6
|
+
- **No Rails Magic:** Do not use `ActiveSupport` methods (like `.present?` or `.blank?`) unless the gem is explicitly in the Gemfile.
|
|
4
7
|
|
|
5
|
-
##
|
|
8
|
+
## Code Style & Conventions
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
+
### 1. Types
|
|
11
|
+
- Rely on standard Ruby 3.x patterns. Use explicit YARD docs for method signatures.
|
|
12
|
+
|
|
13
|
+
### 2. Routing (Roda)
|
|
14
|
+
- Do not create deep routing blocks. Use Roda's tree routing efficiently (`r.on`, `r.is`, `r.get`, `r.post`).
|
|
15
|
+
|
|
16
|
+
### 3. Boot Sequence & Initialization
|
|
17
|
+
- **`boot.rb`**: The absolute starting point. It sets up `Bundler` and boots `Zeitwerk`.
|
|
18
|
+
- **`config.ru`**: The Rack entrypoint. It defines middleware and mounts the primary Roda application class defined in `app.rb`.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Project Code Generation Rules
|
|
2
|
+
|
|
3
|
+
## Core Stack
|
|
4
|
+
- **Web Framework**: Roda (STRICT: Do NOT use Rails, ActionController, or Sinatra conventions).
|
|
5
|
+
- **No Rails Magic:** Do not use `ActiveSupport` methods (like `.present?` or `.blank?`) unless the gem is explicitly in the Gemfile.<% if context.database %>
|
|
6
|
+
- **ORM**: Sequel (STRICT: Do NOT use ActiveRecord. Use Sequel Models, Datasets, and Migrations).<% end %>
|
|
7
|
+
- **Autoloader**: Zeitwerk (Do NOT manually `require` files located in the `app/` directory. Only require standard libraries or gems).<% if context.fullstack? %>
|
|
8
|
+
- **Frontend**: esbuild + ERB (Fullstack mode).<% end %>
|
|
9
|
+
|
|
10
|
+
## Code Style & Conventions
|
|
11
|
+
|
|
12
|
+
### 1. Types
|
|
13
|
+
- Rely on standard Ruby 3.x patterns. Use explicit YARD docs for method signatures.
|
|
14
|
+
|
|
15
|
+
### 2. Routing (Roda)
|
|
16
|
+
- The main app resides in `app/[project_name].rb`.
|
|
17
|
+
- Sub-routes MUST be implemented using Roda **Hash Branches** (`hash_branches` plugin) and placed in `app/routes/`.
|
|
18
|
+
- Do not create deep routing blocks. Use Roda's tree routing efficiently (`r.on`, `r.is`, `r.get`, `r.post`).<% if context.database %>
|
|
19
|
+
|
|
20
|
+
### 3. Database & Models (Sequel)
|
|
21
|
+
- Models reside in `app/models/`.
|
|
22
|
+
- Migrations reside in `db/migrations/` and MUST use sequential numbering (e.g., `001_...`).
|
|
23
|
+
- When writing database queries, use Sequel's dataset methods (e.g., `where`, `select`, `exclude`, `join`). Do not use string interpolation for SQL queries.
|
|
24
|
+
|
|
25
|
+
<% end %><% if context.fullstack? %>### 4. Views & Presentation
|
|
26
|
+
- ERB templates go in `app/views/`.
|
|
27
|
+
|
|
28
|
+
<% end %>### 5. Testing
|
|
29
|
+
- Specs reside in `spec/app/`.
|
|
30
|
+
- Use `rack-test` for route testing.
|
|
31
|
+
- Ensure database transactions are rolled back after each test block.
|
|
32
|
+
- Mirror the file structure of `app/` inside `spec/app/` (e.g., `app/models/user.rb` -> `spec/app/models/user_spec.rb`).
|
|
33
|
+
|
|
34
|
+
### 6. Boot Sequence & Initialization
|
|
35
|
+
- **`boot.rb`**: The absolute starting point. It sets up `Bundler`, boots `Zeitwerk` for autoloading the `app/` directory, and loads service providers from `app/config/providers/`.
|
|
36
|
+
- **`config.ru`**: The Rack entrypoint. It defines middleware (like `Rack::LiveReload` in development) and mounts the primary Roda application class defined in `app/[project_name].rb`.
|
|
37
|
+
|
|
38
|
+
### 7. Tooling
|
|
39
|
+
- Do NOT run `bundle exec rake` for scaffolding/servers. The project uses a custom CLI: `bin/roda`. Suggest commands using `bin/roda` (e.g., generators, running the server).
|
|
40
|
+
|
|
41
|
+
#### Generators
|
|
42
|
+
- To maintain consistency and avoid manual boilerplate, the project uses custom generators for scaffolding new features. These automatically hook into Zeitwerk's expectations, Sequel's DSL, and Roda's Hash Branches.
|
|
43
|
+
- **`bin/roda g routes <branch> [options]`**: Scaffolds a Roda hash branch file in `app/routes/`, matching spec files, and ERB views (for GET routes). Supports nested paths (e.g., `api/v1`) and bare verbs (e.g., `books :get :post`).<% if context.database %>
|
|
44
|
+
- **`bin/roda g model <name> [fields]`**: Generates a Sequel ORM model class, a matching model spec, and a timestamped Sequel migration. Supports namespacing (`Admin/User`) and auto-pluralizes table names.
|
|
45
|
+
- **`bin/roda g migration <name> [fields]`**: Creates a timestamped Sequel migration. It automatically detects intent from the name (e.g., `add_status_to_users` generates the `alter_table` block) and maps Rails-like field types (e.g., `user:references`, `body:text`) to Sequel schema methods.
|
|
46
|
+
<% end %>- **`bin/roda g service <type> <name>`**: Scaffolds a class or module service in `app/services/` and a matching spec. Defaults to `class` if type is omitted.
|
|
47
|
+
|
|
@@ -5,13 +5,26 @@ module Config
|
|
|
5
5
|
secret: ENV["SESSION_SECRET"] || 'DEV-ONLY-8<FQF8HiF)>l0hbPk£vBQ#IrYsoO}14k\l+-/gIU[j}l0hbPk£vBQ#IrY',
|
|
6
6
|
environment:,<% if context.rodauth? %>
|
|
7
7
|
hmac_secret: ENV["RODAUTH_HMAC_SECRET"] || 'DEV-ONLY',
|
|
8
|
-
jwt_secret: ENV["JWT_SECRET"] || 'DEV-ONLY'
|
|
8
|
+
jwt_secret: ENV["JWT_SECRET"] || 'DEV-ONLY'<% end %><% if context.fullstack? %>,
|
|
9
9
|
i18n: {
|
|
10
10
|
translations: ["app/config/locales", "app/config/locales/foo"],
|
|
11
11
|
locale: ["en", "pt-br"]
|
|
12
12
|
}<% end %><% if context.database? %>,
|
|
13
13
|
db: {
|
|
14
14
|
url: not_production? ? <%= context.default_db_url %> : ENV["DATABASE_URL"]
|
|
15
|
+
}<% end %><% if context.fullstack? %>,
|
|
16
|
+
render: {
|
|
17
|
+
layout: "./layout",
|
|
18
|
+
assume_fixed_locals: true, # Optimize plugin by assuming all templates use fixed locals
|
|
19
|
+
template_opts: {
|
|
20
|
+
scope_class: self, # Always uses current class as scope class for compiled templates
|
|
21
|
+
freeze: true, # Freeze string literals in templates
|
|
22
|
+
extract_fixed_locals: true, # Support fixed locals in templates
|
|
23
|
+
default_fixed_locals: '()', # Default to templates not supporting local variables
|
|
24
|
+
escape: true, # For Erubi templates, escapes %= by default (use %== for unescaped
|
|
25
|
+
chain_appends: true, # For Erubi templates, improves performance
|
|
26
|
+
skip_compiled_encoding_detection: true, # Unless you need encodings explicitly specified
|
|
27
|
+
}
|
|
15
28
|
}<% end %>
|
|
16
29
|
}
|
|
17
30
|
end
|
|
@@ -3,7 +3,7 @@ require "thor"
|
|
|
3
3
|
require "roda/project/bin/generators"
|
|
4
4
|
<% if context.database? %>
|
|
5
5
|
class Db < Thor
|
|
6
|
-
desc "migrate", "Migrate the database. Optional: provide target version, e.g., 'roda db:migrate 1'"
|
|
6
|
+
desc "migrate", "Migrate the database (use RACK_ENV to migrate 'test' or 'production' environments). Optional: provide target version, e.g., 'roda db:migrate 1'"
|
|
7
7
|
option :target, type: :numeric, aliases: "-t", desc: "provide target version"
|
|
8
8
|
def migrate
|
|
9
9
|
require_db_deps
|
|
@@ -148,6 +148,10 @@ class CLI < Thor
|
|
|
148
148
|
desc "generate", "Commands to generate code"
|
|
149
149
|
<%= context.to_thor_option %>
|
|
150
150
|
subcommand "generate", Roda::Project::Bin::Generators
|
|
151
|
+
|
|
152
|
+
def self.exit_on_failure?
|
|
153
|
+
true
|
|
154
|
+
end
|
|
151
155
|
end
|
|
152
156
|
|
|
153
157
|
CLI.start(ARGV)
|
|
@@ -32,4 +32,10 @@ Providers::Mailer.boot
|
|
|
32
32
|
Providers::Logger.boot
|
|
33
33
|
|
|
34
34
|
# Consts<% if context.database %>
|
|
35
|
-
DB = Providers::DB::Conn.get
|
|
35
|
+
DB = Providers::DB::Conn.get
|
|
36
|
+
|
|
37
|
+
# Automatic models annotations in dev environment
|
|
38
|
+
if Config.not_production?
|
|
39
|
+
require 'sequel/annotate'
|
|
40
|
+
Sequel::Annotate.annotate(Dir['app/models/*.rb'], position: :before, border: true) rescue nil
|
|
41
|
+
end<% end %>
|
data/lib/roda/project/version.rb
CHANGED
data/lib/roda/project.rb
CHANGED
|
@@ -59,6 +59,7 @@ require_relative "project/helpers/inflections"
|
|
|
59
59
|
# Base
|
|
60
60
|
require_relative "project/main_context"
|
|
61
61
|
require_relative "project/generator"
|
|
62
|
+
require_relative "project/jeremy_template_creator"
|
|
62
63
|
require_relative "project/cli"
|
|
63
64
|
require_relative "project/bin/generator"
|
|
64
65
|
# Generators/migration
|
|
@@ -70,6 +71,8 @@ require_relative "project/bin/generators/migration/code_builder"
|
|
|
70
71
|
require_relative "project/bin/generators/model"
|
|
71
72
|
# Generators/routes
|
|
72
73
|
require_relative "project/bin/generators/routes"
|
|
74
|
+
# Generators/service
|
|
75
|
+
require_relative "project/bin/generators/service"
|
|
73
76
|
# Generators/base
|
|
74
77
|
require_relative "project/bin/generators"
|
|
75
78
|
# Version
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: roda-project
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.13
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Henrique F. Teixeira
|
|
@@ -85,12 +85,14 @@ files:
|
|
|
85
85
|
- lib/roda/project/bin/generators/migration/field_parser.rb
|
|
86
86
|
- lib/roda/project/bin/generators/model.rb
|
|
87
87
|
- lib/roda/project/bin/generators/routes.rb
|
|
88
|
+
- lib/roda/project/bin/generators/service.rb
|
|
88
89
|
- lib/roda/project/cli.rb
|
|
89
90
|
- lib/roda/project/generator.rb
|
|
90
91
|
- lib/roda/project/helpers/ids.rb
|
|
91
92
|
- lib/roda/project/helpers/inflections.rb
|
|
92
93
|
- lib/roda/project/helpers/interactive_input.rb
|
|
93
94
|
- lib/roda/project/helpers/template.rb
|
|
95
|
+
- lib/roda/project/jeremy_template_creator.rb
|
|
94
96
|
- lib/roda/project/main_context.rb
|
|
95
97
|
- lib/roda/project/templates/base/app/app.rb.erb
|
|
96
98
|
- lib/roda/project/templates/base/minimal/AGENTS.md
|
|
@@ -103,15 +105,11 @@ files:
|
|
|
103
105
|
- lib/roda/project/templates/base/minimal/boot.rb
|
|
104
106
|
- lib/roda/project/templates/base/minimal/config.ru
|
|
105
107
|
- lib/roda/project/templates/base/minimal/public/exception_page.css
|
|
106
|
-
- lib/roda/project/templates/base/scaffold/AGENTS.md
|
|
108
|
+
- lib/roda/project/templates/base/scaffold/AGENTS.md.erb
|
|
107
109
|
- lib/roda/project/templates/base/scaffold/Gemfile.erb
|
|
108
110
|
- lib/roda/project/templates/base/scaffold/Guardfile
|
|
109
111
|
- lib/roda/project/templates/base/scaffold/README.md
|
|
110
112
|
- lib/roda/project/templates/base/scaffold/app/config/config.rb.erb
|
|
111
|
-
- lib/roda/project/templates/base/scaffold/app/config/locales/en.yml
|
|
112
|
-
- lib/roda/project/templates/base/scaffold/app/config/locales/foo/en.yml
|
|
113
|
-
- lib/roda/project/templates/base/scaffold/app/config/locales/foo/pt-br.yml
|
|
114
|
-
- lib/roda/project/templates/base/scaffold/app/config/locales/pt-br.yml
|
|
115
113
|
- lib/roda/project/templates/base/scaffold/app/config/providers/logger.rb
|
|
116
114
|
- lib/roda/project/templates/base/scaffold/app/config/providers/mailer.rb
|
|
117
115
|
- lib/roda/project/templates/base/scaffold/app/routes/foo.rb.erb
|
|
@@ -124,6 +122,10 @@ files:
|
|
|
124
122
|
- lib/roda/project/templates/front-end/app/assets/css/app.css
|
|
125
123
|
- lib/roda/project/templates/front-end/app/assets/js/app.js
|
|
126
124
|
- lib/roda/project/templates/front-end/app/assets/js/some/foo.js
|
|
125
|
+
- lib/roda/project/templates/front-end/app/config/locales/en.yml
|
|
126
|
+
- lib/roda/project/templates/front-end/app/config/locales/foo/en.yml
|
|
127
|
+
- lib/roda/project/templates/front-end/app/config/locales/foo/pt-br.yml
|
|
128
|
+
- lib/roda/project/templates/front-end/app/config/locales/pt-br.yml
|
|
127
129
|
- lib/roda/project/templates/front-end/app/views/foo/bar.erb
|
|
128
130
|
- lib/roda/project/templates/front-end/app/views/index.erb
|
|
129
131
|
- lib/roda/project/templates/front-end/app/views/layout.erb
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
Act as a Ruby specialist.
|
|
2
|
-
|
|
3
|
-
This is a web application written using the Roda web framework
|
|
4
|
-
|
|
5
|
-
## Project guidelines
|
|
6
|
-
|
|
7
|
-
- **Never** deliver code without tests validating the code (execute with `rake test`)
|
|
8
|
-
- **Never** write long comments in the code
|
|
9
|
-
- **Always** use clean code
|
|
File without changes
|
|
File without changes
|
/data/lib/roda/project/templates/{base/scaffold → front-end}/app/config/locales/foo/pt-br.yml
RENAMED
|
File without changes
|
|
File without changes
|