-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtemplate.rb
361 lines (294 loc) · 12.2 KB
/
template.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
def source_paths
[__dir__]
end
ruby_version = ask("Which ruby version are you using? This will add it to the .ruby-version file:")
run "echo \"#{ruby_version}\" > .ruby-version"
ruby_gemset = ask("Enter a gemset name for .ruby-gemset or just hit enter to skip creation of this file:")
if ruby_gemset
run "echo \"#{ruby_gemset}\" > .ruby-gemset"
end
create_file ".env"
# View components for portions of views with more complex logic
gem "view_component"
# Reduce Request logging to a single line in production
gem "lograge"
# JSON performance
gem 'multi_json'
gem 'oj'
# Backgroud jobs
gem 'sidekiq'
# Clean config and type safe/validatable structs
gem 'dry-configurable'
gem 'dry-struct'
gem 'dry-validation'
# Compare hashes and arrays
gem 'hashdiff'
# CLI
gem 'pastel' # styling strings for printing in the terminal
gem 'thor' # used by `bin/cli` and it's commands
gem 'tty-option' # presenting options in an interactive CLI
gem 'tty-progressbar'
# Static security analysis
gem 'brakeman'
add_auth0 = yes?("Do you want to include authentication via Auth0?")
if add_auth0
gem 'omniauth-auth0'
gem 'omniauth-rails_csrf_protection' # prevents forged authentication requests
end
gem_group :development do
# Auto-annotate files with schema and other info
gem "annotate"
# Easily preview ViewComponents
gem "lookbook"
end
gem_group :development, :test do
# Ease of setting environment variables locally
gem "dotenv-rails"
# rspec for unit tests
gem "rspec-rails"
# Factories over fixtures for tests
gem "factory_bot_rails"
# Patch-level verification for bundler
gem 'bundler-audit'
end
is_using_postgres = yes?("Are you using PostgreSQL as your database?")
if is_using_postgres
# Use a version of `config/database.yml` with ENV var support built in for anyone who wants to override defaults locally
template "templates/database.yml.erb", "config/database.yml"
end
# Do not commit local env var files to version control as they may have sensitive credentials or dev-only config
append_to_file ".gitignore", <<-EOS
# Local-only environment variables
# See https://github.com/bkeepers/dotenv#should-i-commit-my-env-file
.env.development.local
.env.test.local
.env.production.local
.env.local
EOS
# Create a command line interface runner for thor so you can run it as `bin/cli subcommand`
copy_file "templates/cli/cli.rb", "bin/cli"
copy_file "templates/cli/example_subcommand.rb", "app/cli/example_subcommand.rb"
# Create a runner for your tests by running `bin/ci` (ci standing for continuous integration)
copy_file "templates/ci.rb", "bin/ci"
# Configure sidekiq and sidekiq web UI
copy_file 'templates/sidekiq.rb', "config/initializers/sidekiq.rb"
copy_file 'templates/routes.rake', "lib/tasks/routes.rake"
prepend_to_file "config/routes.rb", <<-EOS
require 'sidekiq/web'
EOS
route <<-EOS
namespace :admin do
mount Sidekiq::Web => '/jobs', constraints: lambda {|request|
# TODO authorize this
true
}
end
if Rails.env.development?
mount Lookbook::Engine, at: "/lookbook"
end
EOS
# ViewComponents
create_file "app/components/.keep", ''
# ViewComponent previews for lookbook
create_file "spec/components/previews/.keep", ''
# Example ViewComponents
copy_file "templates/link_component.rb", "app/components/link_component.rb"
copy_file "templates/link_component.html.erb", "app/components/link_component.html.erb"
copy_file "templates/link_component_preview.rb", "spec/components/previews/link_component_preview.rb"
copy_file "templates/css_classes_helper.rb", "app/helpers/css_classes_helper.rb"
copy_file "templates/button_component.rb", "app/components/button_component.rb"
copy_file "templates/button_component.html.erb", "app/components/button_component.html.erb"
copy_file "templates/button_component_preview.rb", "spec/components/previews/button_component_preview.rb"
copy_file "templates/button_to_component.rb", "app/components/button_to_component.rb"
copy_file "templates/button_to_component_preview.rb", "spec/components/previews/button_to_component_preview.rb"
# A place for plain old Ruby objects
copy_file "templates/application_service.rb", 'app/services/application_service.rb'
# A layout for lookbook that loads tailwind for you, use it by adding `layout "view_component_preview"` to the preview controllers
copy_file "templates/view_component_preview.html.erb", "app/views/layouts/view_component_preview.html.erb"
environment <<-'EOS'
config.autoload_paths += %W(
#{config.root}/app/components
#{config.root}/spec/components/previews
#{config.root}/app/services
#{config.root}/lib
)
EOS
# Enable lograge in the production environment
environment 'config.lograge.enabled = true', env: 'production'
# Use sidekiq for background jobs
environment 'config.active_job.queue_adapter = :sidekiq'
if is_using_postgres
# Use the sql schema for advanced postgres support
environment 'config.active_record.schema_format = :sql'
# Use UUIDs for primary keys in postgres
environment <<-'EOS'
config.generators do |g|
g.orm :active_record, primary_key_type: :uuid
end
EOS
end
# Configure lookbook preview path
environment 'config.view_component.preview_paths << "#{Rails.root}/spec/components/previews"', env: 'development'
# Easily use Dry::Types in Dry::Structs
initializer 'types.rb', <<-CODE
module Types
include Dry.Types()
end
CODE
after_bundle do
# Setup rspec
generate "rspec:install"
insert_into_file "spec/rails_helper.rb", "\n config.include FactoryBot::Syntax::Methods", after: "RSpec.configure do |config|"
# Setup annotate
generate "annotate:install"
# Enable route and model annotation
gsub_file "lib/tasks/auto_annotate_models.rake", /'models'(\s*)=>(\s*)'false'/, "'models' => 'true'"
gsub_file "lib/tasks/auto_annotate_models.rake", /'routes'(\s*)=>(\s*)'false'/, "'routes' => 'true'"
gsub_file "lib/tasks/auto_annotate_models.rake", /'ignore_routes'(\s*)=>(\s*)nil/, "'ignore_routes' => '(cable|rails|turbo)'"
gsub_file "config/tailwind.config.js", /'\.\/app\/javascript\/\*\*\/\*\.js',/, <<-EOS
'./app/javascript/**/*.{js,ts}',
'./app/components/**/*.{rb,erb,haml,html,slim}',
'./spec/components/previews/**/*.{rb,html.erb}',
EOS
# Example pages
generate "controller Home index"
route "root to: 'home#index'"
nav_markup = <<-EOS
<nav class="mt-8">
<h2 class="font-semibold text-xl">Navigation</h2>
<ul>
<li><%= render LinkComponent.new(url: '/lookbook').with_content("Lookbook (ViewComponent Previews)") %></li>
<li><%= render LinkComponent.new(url: '/admin/jobs').with_content("Sidekiq") %></li>
</ul>
</nav>
EOS
insert_into_file "app/views/home/index.html.erb", nav_markup, before: "</div>"
flash_markup = <<-EOS
<% flash.each do |key, message| %>
<div class="container mx-auto mt-8 px-5">
<p><%= key %>: <%= message %></p>
</div>
<% end %>
EOS
insert_into_file "app/views/layouts/application.html.erb", flash_markup, after: "<body>"
if is_using_postgres
generate "migration enable_postgres_uuid_support"
migration_filename = Dir['db/migrate/*_enable_postgres_uuid_support.rb'].first
insert_into_file migration_filename, "\n enable_extension 'pgcrypto'", after: "def change"
insert_into_file "app/models/application_record.rb", "\n self.implicit_order_column = :created_at", after: "primary_abstract_class"
end
if add_auth0
auth0_client_id = ask("What is your Auth0 Client ID? (or you can manually add to .env later)")
auth0_client_secret = ask("What is your Auth0 Client Secret? (or you can manually add to .env later)")
auth0_domain = ask("What is your Auth0 Domain? (or you can manually add to .env later)")
append_to_file ".env", <<-EOS
AUTH0_CLIENT_ID="#{auth0_client_id}"
AUTH0_CLIENT_SECRET="#{auth0_client_secret}"
AUTH0_DOMAIN="#{auth0_domain}"
EOS
copy_file 'templates/auth0.rb', "config/initializers/auth0.rb"
route <<-EOS
get '/auth/auth0/callback', to: 'auth0#callback'
get '/auth/failure', to: 'auth0#failure'
get '/auth/logout', to: 'auth0#logout'
EOS
copy_file "templates/auth0_controller.rb", "app/controllers/auth0_controller.rb"
copy_file "templates/require_login.rb", "app/controllers/concerns/require_login.rb"
application_controller_code = <<-EOS
helper_method :current_user, :current_user_info, :logged_in?
protected
def logged_in?
current_user.present?
end
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
def current_user_info
@current_user_info ||= OpenStruct.new session[:user_info]
end
EOS
insert_into_file "app/controllers/application_controller.rb", application_controller_code, after: "ActionController::Base"
generate "model User"
migration_filename = Dir['db/migrate/*_create_users.rb'].first
allow_guest_users = yes?("Do you want to support guest user accounts?")
migration_code = <<-EOS
t.string :auth0_id, null: #{allow_guest_users ? 'true': 'false'}
EOS
insert_into_file migration_filename, ", id: :uuid", after: "create_table :users"
insert_into_file migration_filename, migration_code, before: "t.timestamps"
if allow_guest_users
guest_method = <<-EOS
def guest?
auth0_id.blank?
end
EOS
insert_into_file "app/models/user.rb", guest_method, after: "ApplicationRecord"
guest_login_method = <<-EOS
def guest_login
user = User.create!
session[:user_id] = user.id
flash.notice = "You are now logged in as a guest! If you log out or change devices all data will be lost"
redirect_to root_path
end
EOS
insert_into_file "app/controllers/auth0_controller.rb", guest_login_method, before: "def failure"
route "post '/auth/guest_login' => 'auth0#guest_login', as: 'guest_login'"
end
# Example pages/controllers
generate "controller User show"
require_login_code = <<-EOS
include RequireLogin
EOS
insert_into_file "app/controllers/user_controller.rb", require_login_code, after: "ApplicationController"
template_login_button_code = <<-EOS
<% if logged_in? %>
<p class="pt-6">
<%= render ButtonToComponent.new 'Logout', '/auth/logout', method: :get, variant: :default, turbo: false %>
</p>
<% else %>
<p class="pt-6">
<%= render ButtonToComponent.new 'Login', '/auth/auth0', method: :post, variant: :primary, turbo: false %>
</p>
<% end %>
EOS
insert_into_file "app/views/home/index.html.erb", template_login_button_code, after: "<p>Find me in app/views/home/index.html.erb</p>"
user_link_code = <<-EOS
<li><%= render LinkComponent.new(url: '/user/show').with_content("User Info") %></li>
EOS
insert_into_file "app/views/home/index.html.erb", user_link_code, after: "<ul>"
user_info_code = <<-EOS
<div class="mt-8">
<h2 class="font-semibold text-xl">User Info</h2>
<dl>
<% current_user_info.each_pair.each do |key, value| %>
<dt class="font-semibold"><%= key %></dt>
<dd><%= value %></dd>
<% end %>
</dl>
</div>
<nav class="mt-8">
<%= render LinkComponent.new(url: '/').with_content("Back") %>
</nav>
EOS
insert_into_file "app/views/user/show.html.erb", user_info_code, after: "<p>Find me in app/views/user/show.html.erb</p>"
end
git :init
if yes?("Should we commit your empty app to git?")
git add: '.'
git commit: "-a -m 'Initial commit'"
end
puts ""
puts "WARNING: add authorization checks to `config/routes.rb` for the sidekiq web UI at /jobs or remove it from the production environment."
puts ""
puts "Next steps:"
puts "cd #{app_name}"
puts "createuser #{app_name} -s -d -P -r -h localhost -p 5432"
puts " (if your database host or port is different you will need to adjust the above)"
puts " (if you are using Postgres.app you may need a fully qualified path if you've not added the bin dir to your path, "
puts" such as: `/Applications/Postgres.app/Contents/Versions/15/bin/createuser #{app_name} -s -d -P -r -h localhost -p 5432`)"
puts "bin/rake db:create"
puts "bin/rake db:migrate"
puts "overmind start -f Procfile.dev"
puts " (`brew install overmind` if you don't have it yet)"
puts " (or if you prefer the foreman gem: `bundle add foreman && bundle exec foreman start -f Procfile.dev`"
end