nextgen 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +143 -0
  4. data/config/generators.yml +146 -0
  5. data/exe/nextgen +13 -0
  6. data/lib/nextgen/actions/bundler.rb +62 -0
  7. data/lib/nextgen/actions/git.rb +56 -0
  8. data/lib/nextgen/actions/yarn.rb +32 -0
  9. data/lib/nextgen/actions.rb +155 -0
  10. data/lib/nextgen/cli.rb +19 -0
  11. data/lib/nextgen/commands/create.rb +312 -0
  12. data/lib/nextgen/ext/prompt/list.rb +13 -0
  13. data/lib/nextgen/ext/prompt/multilist.rb +16 -0
  14. data/lib/nextgen/generators/action_mailer.rb +26 -0
  15. data/lib/nextgen/generators/annotate.rb +2 -0
  16. data/lib/nextgen/generators/base.rb +60 -0
  17. data/lib/nextgen/generators/basic_auth.rb +5 -0
  18. data/lib/nextgen/generators/brakeman.rb +2 -0
  19. data/lib/nextgen/generators/bundler_audit.rb +2 -0
  20. data/lib/nextgen/generators/capybara_lockstep.rb +4 -0
  21. data/lib/nextgen/generators/clean_gemfile.rb +1 -0
  22. data/lib/nextgen/generators/dotenv.rb +3 -0
  23. data/lib/nextgen/generators/erb_lint.rb +11 -0
  24. data/lib/nextgen/generators/eslint.rb +24 -0
  25. data/lib/nextgen/generators/factory_bot_rails.rb +16 -0
  26. data/lib/nextgen/generators/git_safe.rb +4 -0
  27. data/lib/nextgen/generators/github_actions.rb +2 -0
  28. data/lib/nextgen/generators/good_migrations.rb +1 -0
  29. data/lib/nextgen/generators/home_controller.rb +3 -0
  30. data/lib/nextgen/generators/initial_git_commit.rb +5 -0
  31. data/lib/nextgen/generators/initial_migrations.rb +11 -0
  32. data/lib/nextgen/generators/letter_opener.rb +8 -0
  33. data/lib/nextgen/generators/node.rb +5 -0
  34. data/lib/nextgen/generators/open_browser_on_start.rb +12 -0
  35. data/lib/nextgen/generators/overcommit.rb +1 -0
  36. data/lib/nextgen/generators/pgcli_rails.rb +1 -0
  37. data/lib/nextgen/generators/rack_canonical_host.rb +8 -0
  38. data/lib/nextgen/generators/rack_mini_profiler.rb +2 -0
  39. data/lib/nextgen/generators/rspec_rails.rb +19 -0
  40. data/lib/nextgen/generators/rspec_system_testing.rb +5 -0
  41. data/lib/nextgen/generators/rubocop.rb +32 -0
  42. data/lib/nextgen/generators/shoulda.rb +6 -0
  43. data/lib/nextgen/generators/sidekiq.rb +30 -0
  44. data/lib/nextgen/generators/stylelint.rb +24 -0
  45. data/lib/nextgen/generators/thor.rb +2 -0
  46. data/lib/nextgen/generators/tomo.rb +10 -0
  47. data/lib/nextgen/generators/vite.rb +103 -0
  48. data/lib/nextgen/generators.rb +87 -0
  49. data/lib/nextgen/rails.rb +39 -0
  50. data/lib/nextgen/rails_options.rb +191 -0
  51. data/lib/nextgen/thor_extensions.rb +48 -0
  52. data/lib/nextgen/tidy_gemfile.rb +71 -0
  53. data/lib/nextgen/version.rb +3 -0
  54. data/lib/nextgen.rb +17 -0
  55. data/template/.editorconfig +14 -0
  56. data/template/.env.sample +2 -0
  57. data/template/.erb-lint.yml.tt +25 -0
  58. data/template/.eslintrc.cjs +26 -0
  59. data/template/.github/workflows/ci.yml.tt +142 -0
  60. data/template/.overcommit.yml.tt +86 -0
  61. data/template/.prettierrc.cjs +6 -0
  62. data/template/.rubocop.yml.tt +269 -0
  63. data/template/.stylelintrc.cjs +52 -0
  64. data/template/DEPLOYMENT.md +10 -0
  65. data/template/Procfile.tt +4 -0
  66. data/template/README.md.tt +52 -0
  67. data/template/Thorfile +7 -0
  68. data/template/app/controllers/concerns/basic_auth.rb +20 -0
  69. data/template/app/controllers/home_controller.rb +4 -0
  70. data/template/app/frontend/controllers/index.js +5 -0
  71. data/template/app/frontend/images/example.svg +3 -0
  72. data/template/app/frontend/stylesheets/base.css +8 -0
  73. data/template/app/frontend/stylesheets/index.css +3 -0
  74. data/template/app/frontend/stylesheets/reset.css +36 -0
  75. data/template/app/helpers/inline_svg_helper.rb +9 -0
  76. data/template/app/views/home/index.html.erb.tt +5 -0
  77. data/template/bin/setup +107 -0
  78. data/template/config/initializers/generators.rb +5 -0
  79. data/template/config/initializers/rack_mini_profiler.rb +4 -0
  80. data/template/config/initializers/sidekiq.rb +32 -0
  81. data/template/config/sidekiq.yml +5 -0
  82. data/template/lib/puma/plugin/open.rb +14 -0
  83. data/template/lib/tasks/auto_annotate_models.rake +52 -0
  84. data/template/lib/tasks/erblint.rake +11 -0
  85. data/template/lib/tasks/eslint.rake +11 -0
  86. data/template/lib/tasks/rubocop.rake +4 -0
  87. data/template/lib/tasks/stylelint.rake +11 -0
  88. data/template/lib/templates/rspec/system/system_spec.rb +10 -0
  89. data/template/lib/vite_inline_svg_file_loader.rb +25 -0
  90. data/template/package.json +6 -0
  91. data/template/postcss.config.js +3 -0
  92. data/template/spec/support/factory_bot.rb +3 -0
  93. data/template/spec/support/mailer.rb +5 -0
  94. data/template/spec/support/shoulda.rb +6 -0
  95. data/template/spec/support/system.rb +17 -0
  96. data/template/test/application_system_test_case.rb +18 -0
  97. data/template/test/helpers/inline_svg_helper_test.rb +23 -0
  98. data/template/test/support/factory_bot.rb +3 -0
  99. data/template/test/support/mailer.rb +3 -0
  100. data/template/test/support/shoulda.rb +6 -0
  101. data/template/test/support/vite.rb +5 -0
  102. metadata +220 -0
@@ -0,0 +1,312 @@
1
+ require "active_support/core_ext/string/inflections"
2
+ require "fileutils"
3
+ require "forwardable"
4
+ require "shellwords"
5
+ require "open3"
6
+ require "tmpdir"
7
+ require "tty-prompt"
8
+ require "nextgen/ext/prompt/list"
9
+ require "nextgen/ext/prompt/multilist"
10
+
11
+ module Nextgen
12
+ class Commands::Create # rubocop:disable Metrics/ClassLength
13
+ extend Forwardable
14
+
15
+ def self.run(app_path, options)
16
+ new(app_path, options).run
17
+ end
18
+
19
+ def initialize(app_path, _options)
20
+ @app_path = File.expand_path(app_path)
21
+ @app_name = File.basename(@app_path).gsub(/\W/, "_").squeeze("_").camelize
22
+ @rails_opts = RailsOptions.new
23
+ end
24
+
25
+ def run # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
26
+ say <<~BANNER
27
+ Welcome to nextgen, the interactive Rails app generator!
28
+
29
+ You are about to create a Rails app named "#{app_name}" in the following directory:
30
+
31
+ #{app_path}
32
+
33
+ You'll be asked ~10 questions about database, test framework, and other options.
34
+ The standard Rails "omakase" experience will be selected by default.
35
+
36
+ BANNER
37
+
38
+ continue_if "Ready to start?"
39
+
40
+ ask_rails_version
41
+ ask_database
42
+ ask_full_stack_or_api
43
+ ask_frontend_management unless rails_opts.api?
44
+ ask_css unless rails_opts.api? || rails_opts.skip_asset_pipeline?
45
+ ask_javascript unless rails_opts.api? || rails_opts.skip_asset_pipeline?
46
+ ask_rails_frameworks
47
+ ask_test_framework
48
+ ask_system_testing if rails_opts.frontend? && rails_opts.test_framework?
49
+ ask_optional_enhancements
50
+
51
+ say <<~SUMMARY
52
+
53
+ OK! Your Rails app is ready to be created.
54
+ The following options will be passed to `rails new`:
55
+
56
+ #{rails_new_args.join("\n ")}
57
+
58
+ The following nextgen enhancements will also be applied in individual git commits via `rails app:template`:
59
+
60
+ #{selected_generators.join(", ").scan(/\S.{0,75}(?:,|$)/).join("\n ")}
61
+
62
+ SUMMARY
63
+
64
+ if node?
65
+ say <<~NODE
66
+ Based on the options you selected, your app will require Node and Yarn. For reference, you are using these versions:
67
+
68
+ Node: #{capture_version("node")}
69
+ Yarn: #{capture_version("yarn")}
70
+
71
+ NODE
72
+ end
73
+
74
+ continue_if "Continue?"
75
+
76
+ create_initial_commit_message
77
+ copy_package_json if node?
78
+ Nextgen::Rails.run "new", *rails_new_args
79
+ Dir.chdir(app_path) do
80
+ Nextgen::Rails.run "app:template", "LOCATION=#{write_generators_script}"
81
+ end
82
+
83
+ say <<~DONE.gsub(/^/, " ")
84
+
85
+
86
+ #{set_color("Done!", :green)}
87
+
88
+ A Rails #{rails_version} app was generated in #{set_color(app_path, :cyan)}.
89
+ Run #{set_color("bin/setup", :yellow)} in that directory to get started.
90
+
91
+
92
+ DONE
93
+ end
94
+
95
+ private
96
+
97
+ attr_accessor :app_path, :app_name, :rails_opts, :generators
98
+
99
+ def_delegators :shell, :say, :set_color
100
+
101
+ def continue_if(question)
102
+ if prompt.yes?(question)
103
+ say
104
+ else
105
+ say "Canceled", :red
106
+ exit
107
+ end
108
+ end
109
+
110
+ def copy_package_json
111
+ FileUtils.mkdir_p(app_path)
112
+ FileUtils.cp(
113
+ Nextgen.template_path.join("package.json"),
114
+ File.join(app_path, "package.json")
115
+ )
116
+ end
117
+
118
+ def rails_version
119
+ rails_opts.edge? ? "edge (#{Rails.edge_branch} branch)" : Rails.version
120
+ end
121
+
122
+ def ask_rails_version
123
+ selected = prompt.select(
124
+ "What version of Rails will you use?",
125
+ Rails.version => :current,
126
+ "edge (#{Rails.edge_branch} branch)" => :edge
127
+ )
128
+ rails_opts.edge! if selected == :edge
129
+ end
130
+
131
+ def ask_database
132
+ common_databases = {
133
+ "SQLite3 (default)" => "sqlite3",
134
+ "PostgreSQL (recommended)" => "postgresql",
135
+ "MySQL" => "mysql"
136
+ }
137
+ all_databases = common_databases.merge(
138
+ %w[MySQL Oracle SQLServer JDBCMySQL JDBCSQLite3 JDBCPostgreSQL JDBC].to_h do |name|
139
+ [name, name.downcase]
140
+ end,
141
+ "None (disable Active Record)" => nil
142
+ )
143
+ rails_opts.database =
144
+ prompt.select("Which database?", common_databases.merge("More options..." => false)) ||
145
+ prompt.select("Which database?", all_databases)
146
+ end
147
+
148
+ def ask_full_stack_or_api
149
+ api = prompt.select(
150
+ "What style of Rails app do you need?",
151
+ "Standard, full-stack Rails (default)" => false,
152
+ "API only" => true
153
+ )
154
+ rails_opts.api! if api
155
+ end
156
+
157
+ def ask_frontend_management
158
+ frontend = prompt.select(
159
+ "How will you manage frontend assets?",
160
+ "Sprockets (default)" => "sprockets",
161
+ "Propshaft" => "propshaft",
162
+ "Vite" => :vite
163
+ )
164
+ if frontend == :vite
165
+ rails_opts.asset_pipeline = nil
166
+ rails_opts.javascript = "vite"
167
+ else
168
+ rails_opts.asset_pipeline = frontend
169
+ end
170
+ end
171
+
172
+ def ask_css
173
+ rails_opts.css = prompt.select(
174
+ "Which CSS framework will you use with the asset pipeline?",
175
+ "None (default)" => nil,
176
+ "Bootstrap" => "bootstrap",
177
+ "Bulma" => "bulma",
178
+ "PostCSS" => "postcss",
179
+ "Sass" => "sass",
180
+ "Tailwind" => "tailwind"
181
+ )
182
+ end
183
+
184
+ def ask_javascript
185
+ rails_opts.javascript = prompt.select(
186
+ "Which JavaScript bundler will you use with the asset pipeline?",
187
+ "Importmap (default)" => "importmap",
188
+ "ESBuild" => "esbuild",
189
+ "Rollup" => "rollup",
190
+ "Webpack" => "webpack",
191
+ "None" => nil
192
+ )
193
+ end
194
+
195
+ def ask_rails_frameworks
196
+ frameworks = {
197
+ "JBuilder" => "jbuilder",
198
+ "Action Mailer" => "action_mailer",
199
+ "Active Job" => "active_job",
200
+ "Action Cable" => "action_cable"
201
+ }
202
+
203
+ unless rails_opts.api? || rails_opts.skip_javascript?
204
+ frameworks = {"Hotwire" => "hotwire"}.merge(frameworks)
205
+ end
206
+
207
+ unless rails_opts.skip_active_record?
208
+ frameworks.merge!(
209
+ "Active Storage" => "active_storage",
210
+ "Action Text" => "action_text",
211
+ "Action Mailbox" => "action_mailbox"
212
+ )
213
+ end
214
+
215
+ answers = prompt.multi_select(
216
+ "Which optional Rails frameworks do you need?",
217
+ frameworks,
218
+ default: frameworks.keys.reverse
219
+ )
220
+
221
+ (frameworks.values - answers).each { rails_opts.skip_optional_framework!(_1) }
222
+ end
223
+
224
+ def ask_test_framework
225
+ rails_opts.test_framework = prompt.select(
226
+ "Which test framework will you use?",
227
+ "Minitest (default)" => "minitest",
228
+ "RSpec" => "rspec",
229
+ "None" => nil
230
+ )
231
+ end
232
+
233
+ def ask_system_testing
234
+ system_testing = prompt.select(
235
+ "Include system testing (capybara)?",
236
+ "Yes (default)" => true,
237
+ "No" => false
238
+ )
239
+ rails_opts.skip_system_test! unless system_testing
240
+ end
241
+
242
+ def ask_optional_enhancements
243
+ @generators = Generators.compatible_with(rails_opts: rails_opts)
244
+
245
+ answers = prompt.multi_select(
246
+ "Which optional enhancements would you like to add?",
247
+ generators.optional.sort_by { |label, _| label.downcase }.to_h
248
+ )
249
+ generators.activate(*answers)
250
+ end
251
+
252
+ def create_initial_commit_message
253
+ path = File.join(app_path, "tmp", "initial_nextgen_commit")
254
+ FileUtils.mkdir_p(File.dirname(path))
255
+ File.write(path, <<~COMMIT)
256
+ Init project with `rails new` (#{Nextgen::Rails.version})
257
+
258
+ Nextgen generated this project with the following `rails new` options:
259
+
260
+ ```
261
+ #{rails_opts.to_args.join("\n")}
262
+ ```
263
+ COMMIT
264
+ end
265
+
266
+ def rails_new_args
267
+ [app_path, "--no-rc", *rails_opts.to_args].tap do |args|
268
+ # Work around a Rails bug where --edge causes --no-rc to get ignored.
269
+ # Specifying --rc= with a non-existent file has the same effect as --no-rc.
270
+ @rc_token ||= SecureRandom.hex(8)
271
+ args << "--rc=#{@rc_token}" if rails_opts.edge?
272
+ end
273
+ end
274
+
275
+ def node?
276
+ generators.node_active?
277
+ end
278
+
279
+ def capture_version(command)
280
+ out, _err, status = Open3.capture3(command, "--version")
281
+ version = status.success? && out[/\d[.\d]+\d/]
282
+
283
+ version || "<unknown>"
284
+ end
285
+
286
+ def selected_generators
287
+ optional = generators.optional.invert
288
+ selected = generators.all_active.filter_map { |name| optional[name] }
289
+
290
+ selected.any? ? selected.sort_by(&:downcase) : ["<None>"]
291
+ end
292
+
293
+ def write_generators_script
294
+ new_tempfile_path.tap do |location|
295
+ File.write(location, generators.to_ruby_script)
296
+ end
297
+ end
298
+
299
+ def new_tempfile_path
300
+ token = SecureRandom.hex(8)
301
+ File.join(Dir.tmpdir, "nextgen_create_#{token}.rb")
302
+ end
303
+
304
+ def prompt
305
+ @prompt ||= TTY::Prompt.new
306
+ end
307
+
308
+ def shell
309
+ @shell ||= Thor::Base.shell.new
310
+ end
311
+ end
312
+ end
@@ -0,0 +1,13 @@
1
+ require "tty-prompt"
2
+ require "tty-screen"
3
+
4
+ module Nextgen::Ext
5
+ module Prompt::List
6
+ def initialize(...)
7
+ super
8
+ @per_page ||= [TTY::Prompt::Paginator::DEFAULT_PAGE_SIZE, TTY::Screen.height.to_i - 3].max
9
+ end
10
+ end
11
+ end
12
+
13
+ TTY::Prompt::List.prepend(Nextgen::Ext::Prompt::List)
@@ -0,0 +1,16 @@
1
+ require "tty-prompt"
2
+
3
+ module Nextgen::Ext
4
+ module Prompt::Multilist
5
+ private
6
+
7
+ def selected_names
8
+ return "" unless @done
9
+ return super if @selected.size < 3
10
+
11
+ @selected.first.name + " + #{@selected.size - 1} more"
12
+ end
13
+ end
14
+ end
15
+
16
+ TTY::Prompt::MultiList.prepend(Nextgen::Ext::Prompt::Multilist)
@@ -0,0 +1,26 @@
1
+ say_git "Configure Action Mailer for testing"
2
+ copy_test_support_file "mailer.rb"
3
+ if minitest?
4
+ empty_directory_with_keep_file "test/mailers"
5
+ elsif rspec?
6
+ empty_directory_with_keep_file "spec/mailers"
7
+ end
8
+
9
+ say_git "Ensure absolute URLs can be used in all environments"
10
+ insert_into_file "config/environments/development.rb", <<-RUBY, after: "raise_delivery_errors = false\n"
11
+ config.action_mailer.default_url_options = {host: "localhost:3000"}
12
+ config.action_mailer.asset_host = "http://localhost:3000"
13
+ RUBY
14
+
15
+ insert_into_file "config/environments/test.rb", <<-RUBY, after: "config.action_mailer.delivery_method = :test\n"
16
+ config.action_mailer.default_url_options = {host: "localhost:3000"}
17
+ config.action_mailer.asset_host = "http://localhost:3000"
18
+ RUBY
19
+
20
+ insert_into_file "config/environments/production.rb", <<-RUBY, after: /config\.action_mailer\.raise_deliv.*\n/
21
+ config.action_mailer.default_url_options = {
22
+ host: ENV.fetch("RAILS_HOSTNAME", "app.example.com"),
23
+ protocol: "https"
24
+ }
25
+ config.action_mailer.asset_host = "https://\#{ENV.fetch("RAILS_HOSTNAME", "app.example.com")}"
26
+ RUBY
@@ -0,0 +1,2 @@
1
+ install_gem "annotate", group: :development
2
+ copy_file "lib/tasks/auto_annotate_models.rake"
@@ -0,0 +1,60 @@
1
+ say_git "Add improved bin/setup script"
2
+ copy_file "bin/setup", mode: :preserve, force: true
3
+
4
+ say_git "Specify 2-space indent and other general editor settings"
5
+ copy_file ".editorconfig"
6
+ copy_file ".prettierrc.cjs"
7
+
8
+ say_git "Generate documentation"
9
+ postgres = File.read("Gemfile").match?(/^\s*gem ['"]pg['"]/)
10
+ template "README.md.tt", force: true, context: binding
11
+ copy_file "DEPLOYMENT.md"
12
+
13
+ say_git "Create a Procfile"
14
+ template "Procfile.tt"
15
+
16
+ say_git "Set up default rake task"
17
+ test_task = "test:all" if minitest?
18
+ append_to_file "Rakefile", <<~RUBY
19
+
20
+ Rake::Task[:default].prerequisites.clear if Rake::Task.task_defined?(:default)
21
+
22
+ desc "Run all checks"
23
+ task default: %w[#{test_task}] do
24
+ Thor::Base.shell.new.say_status :OK, "All checks passed!"
25
+ end
26
+ RUBY
27
+
28
+ if File.exist?("test/application_system_test_case.rb")
29
+ say_git "Configure system tests"
30
+ copy_file "test/application_system_test_case.rb", force: true
31
+ end
32
+
33
+ if File.exist?(".ruby-version") && File.read(".ruby-version").match?(/\A\d+\.\d+.\d+.\s*\z/m)
34
+ say_git "DRY up Gemfile and .ruby-version file"
35
+ replacement = 'ruby Pathname.new(__dir__).join(".ruby-version").read.strip'
36
+ gsub_file "Gemfile", /^ruby "\d.*"$/, replacement
37
+ end
38
+
39
+ if File.exist?("app/views/layouts/application.html.erb")
40
+ say_git "Improve title and meta information for HTML layout"
41
+ gsub_file "app/views/layouts/application.html.erb", "<html>", '<html lang="en">'
42
+ gsub_file "app/views/layouts/application.html.erb", %r{^\s*<title>.*</title>}, <<~ERB.gsub(/^/, " ").rstrip
43
+ <title><%= content_for?(:title) ? strip_tags(yield(:title)) : #{app_const_base.titleize.inspect} %></title>
44
+ <meta name="apple-mobile-web-app-title" content="#{app_const_base.titleize}">
45
+ ERB
46
+ end
47
+
48
+ say_git "Disable legacy javascript and stylesheet generators"
49
+ copy_file "config/initializers/generators.rb"
50
+
51
+ say_git "Allow force_ssl to be controlled via env var"
52
+ uncomment_lines "config/environments/production.rb", "config.force_ssl = true"
53
+ gsub_file "config/environments/production.rb",
54
+ "config.force_ssl = true",
55
+ 'config.force_ssl = ENV["RAILS_DISABLE_SSL"].blank?'
56
+
57
+ if File.exist?("config/database.yml")
58
+ say_git "Create initial schema.rb"
59
+ rails_command "db:prepare"
60
+ end
@@ -0,0 +1,5 @@
1
+ document_deploy_var "BASIC_AUTH_PASSWORD"
2
+ document_deploy_var "BASIC_AUTH_USERNAME",
3
+ "If this and `BASIC_AUTH_PASSWORD` are present, visitors must use these credentials to access the app"
4
+ copy_file "app/controllers/concerns/basic_auth.rb"
5
+ inject_into_class "app/controllers/application_controller.rb", "ApplicationController", " include BasicAuth\n"
@@ -0,0 +1,2 @@
1
+ install_gem "brakeman", group: :development, require: false
2
+ binstub "brakeman"
@@ -0,0 +1,2 @@
1
+ install_gem "bundler-audit", group: :development, require: false
2
+ binstub "bundler-audit"
@@ -0,0 +1,4 @@
1
+ install_gem "capybara-lockstep", group: :test
2
+ inject_into_file "app/views/layouts/application.html.erb",
3
+ "\n <%= capybara_lockstep if defined?(Capybara::Lockstep) %>",
4
+ after: /<head\b.*$/
@@ -0,0 +1 @@
1
+ Nextgen::TidyGemfile.clean!
@@ -0,0 +1,3 @@
1
+ install_gem "dotenv-rails", group: %i[development test]
2
+ copy_file ".env.sample"
3
+ gitignore "/.env*", "!/.env.sample"
@@ -0,0 +1,11 @@
1
+ say_git "Install erb_lint"
2
+ install_gem "erb_lint", group: :development, require: false
3
+ binstub "erb_lint"
4
+ template ".erb-lint.yml.tt"
5
+
6
+ say_git "Add erblint to default rake task"
7
+ copy_file "lib/tasks/erblint.rake"
8
+ add_lint_task "erblint"
9
+
10
+ say_git "Auto-correct any existing issues"
11
+ run "bin/erblint --lint-all -a", capture: true
@@ -0,0 +1,24 @@
1
+ say_git "Install eslint"
2
+ add_yarn_packages(
3
+ "eslint",
4
+ "eslint-config-prettier",
5
+ "eslint-plugin-prettier",
6
+ "prettier",
7
+ "npm-run-all",
8
+ "@types/eslint",
9
+ dev: true
10
+ )
11
+ add_package_json_scripts(
12
+ "lint:js": "eslint 'app/{components,frontend,javascript}/**/*.{js,jsx,ts,tsx}'",
13
+ "fix:js": "npm run -- lint:js --fix",
14
+ lint: "npm-run-all lint:**",
15
+ fix: "npm-run-all fix:**"
16
+ )
17
+ copy_file ".eslintrc.cjs"
18
+
19
+ say_git "Add eslint to default rake task"
20
+ copy_file "lib/tasks/eslint.rake"
21
+ add_lint_task "eslint"
22
+
23
+ say_git "Auto-correct any existing issues"
24
+ run "yarn fix:js", capture: true
@@ -0,0 +1,16 @@
1
+ say_git "Install factory_bot_rails"
2
+ install_gem "factory_bot_rails", group: %i[development test]
3
+
4
+ say_git "Include factory_bot methods in tests"
5
+ copy_test_support_file "factory_bot.rb"
6
+
7
+ say_git "Replace fixtures with factories in generators"
8
+ if minitest?
9
+ inject_into_file "config/initializers/generators.rb", <<~RUBY, after: "Rails.application.config.generators do |g|\n"
10
+ g.test_framework :test_unit, fixture: false, fixture_replacement: :factory_bot
11
+ RUBY
12
+ end
13
+ inject_into_file "config/initializers/generators.rb", <<~RUBY, after: "Rails.application.config.generators do |g|\n"
14
+ # Generate "users_factory.rb" instead of "users.rb"
15
+ g.factory_bot suffix: "factory"
16
+ RUBY
@@ -0,0 +1,4 @@
1
+ if Dir.exist?(".git")
2
+ say_git "Mark project as trusted so bin/ can be added to PATH"
3
+ empty_directory ".git/safe"
4
+ end
@@ -0,0 +1,2 @@
1
+ gems = File.exist?("Gemfile") ? File.read("Gemfile").scan(/^\s*gem ["'](.+?)["']/).flatten : []
2
+ template ".github/workflows/ci.yml.tt", context: binding
@@ -0,0 +1 @@
1
+ install_gem "good_migrations"
@@ -0,0 +1,3 @@
1
+ copy_file "app/controllers/home_controller.rb"
2
+ template "app/views/home/index.html.erb.tt"
3
+ route 'root "home#index"'
@@ -0,0 +1,5 @@
1
+ commit_msg = "tmp/initial_nextgen_commit"
2
+ return unless git_working? && File.exist?(commit_msg)
3
+
4
+ gitignore "node_modules/" if Dir.exist?("node_modules")
5
+ git_commit_all(File.read(commit_msg))
@@ -0,0 +1,11 @@
1
+ migration_tasks = %w[
2
+ action_mailbox:install:migrations
3
+ action_text:install:migrations
4
+ active_storage:install
5
+ ]
6
+ all_tasks = rails_command("-T", capture: true).scan(/^\S+\s+(\S+)/).flatten
7
+ tasks_to_run = migration_tasks & all_tasks
8
+ return unless tasks_to_run.any?
9
+
10
+ tasks_to_run.each { |task| rails_command(task) }
11
+ rails_command "db:migrate"
@@ -0,0 +1,8 @@
1
+ say_git "Install letter_opener"
2
+ install_gem "letter_opener", group: :development
3
+
4
+ say_git "Configure Action Mailer to use letter_opener"
5
+ insert_into_file "config/environments/development.rb", <<-RUBY, after: "raise_delivery_errors = false\n"
6
+
7
+ config.action_mailer.delivery_method = :letter_opener
8
+ RUBY
@@ -0,0 +1,5 @@
1
+ say_git "Add Node and Yarn prerequisites"
2
+ copy_file "package.json" unless File.exist?("package.json")
3
+ inject_into_file "README.md", "\n- Node 18 (LTS) or newer\n- Yarn 1.x (classic)", after: /^- Ruby.*$/
4
+ inject_into_file "README.md", "\nbrew install node\nbrew install yarn", after: /^brew install rbenv.*$/
5
+ gitignore "node_modules/"
@@ -0,0 +1,12 @@
1
+ say_git "Install launchy"
2
+ install_gem "launchy", group: %i[development test]
3
+
4
+ say_git "Configure puma to open browser on startup"
5
+ copy_file "lib/puma/plugin/open.rb"
6
+ append_to_file "config/puma.rb", <<~RUBY
7
+
8
+ # Automatically open the browser when in development
9
+ require_relative "../lib/puma/plugin/open"
10
+ plugin :open
11
+ RUBY
12
+ prevent_autoload_lib "puma/plugin"
@@ -0,0 +1 @@
1
+ template ".overcommit.yml"
@@ -0,0 +1 @@
1
+ install_gem "pgcli-rails", group: :development
@@ -0,0 +1,8 @@
1
+ say_git "Install rack-canonical-host"
2
+ install_gem "rack-canonical-host"
3
+
4
+ say_git "Use RAILS_HOSTNAME env var"
5
+ document_deploy_var "RAILS_HOSTNAME", "Redirect all requests to the specified canonical hostname"
6
+ insert_into_file "config.ru",
7
+ %(use Rack::CanonicalHost, ENV.fetch("RAILS_HOSTNAME", nil) if ENV["RAILS_HOSTNAME"].present?\n),
8
+ before: /^run Rails.application/
@@ -0,0 +1,2 @@
1
+ install_gem "rack-mini-profiler", group: :development
2
+ copy_file "config/initializers/rack_mini_profiler.rb" if File.read("Gemfile").match?(/^\s*gem ['"]turbo-rails['"]/)
@@ -0,0 +1,19 @@
1
+ say_git "Install the rspec-rails gem"
2
+ install_gem "rspec-rails", group: %i[development test]
3
+
4
+ say_git "Run the rspec installer"
5
+ generate "rspec:install"
6
+ gitignore "/spec/examples.txt"
7
+ binstub "rspec-core"
8
+
9
+ say_git "Disable auto-generation of routing and view specs"
10
+ inject_into_file "config/initializers/generators.rb", <<~RUBY, after: "g.stylesheets false\n"
11
+ g.routing_specs false
12
+ g.view_specs false
13
+ RUBY
14
+
15
+ say_git "Add spec to default rake task"
16
+ inject_into_file "Rakefile", "spec", after: /task default: %w\[/
17
+
18
+ say_git "Enable auto-loading of the spec/support directory"
19
+ uncomment_lines("spec/rails_helper.rb", /Dir\[Rails.root.join/)
@@ -0,0 +1,5 @@
1
+ install_gems "capybara", "selenium-webdriver", group: :test
2
+ copy_test_support_file "system.rb"
3
+
4
+ copy_file "lib/templates/rspec/system/system_spec.rb"
5
+ prevent_autoload_lib "templates"
@@ -0,0 +1,32 @@
1
+ say_git "Install rubocop gems"
2
+ gemfile = File.read("Gemfile")
3
+ plugins = []
4
+ plugins << "capybara" if gemfile.match?(/^\s*gem ['"]capybara['"]/)
5
+ plugins << "factory_bot" if gemfile.match?(/^\s*gem ['"]factory_bot/)
6
+ plugins << "minitest" if minitest?
7
+ plugins << "performance"
8
+ plugins << "rails"
9
+ install_gems "rubocop", *plugins.map { "rubocop-#{_1}" }, group: :development, require: false
10
+ binstub "rubocop"
11
+
12
+ say_git "Generate .rubocop.yml"
13
+ template ".rubocop.yml", context: binding
14
+
15
+ if File.exist?(".erb-lint.yml")
16
+ say_git "Regenerate .erb-lint.yml with rubocop support"
17
+ template ".erb-lint.yml", force: true
18
+ end
19
+
20
+ say_git "Add rubocop to default rake task"
21
+ copy_file "lib/tasks/rubocop.rake"
22
+ add_lint_task "rubocop", fix: "rubocop:autocorrect_all"
23
+ inject_into_file "README.md", <<~MARKDOWN, after: /rake fix\n```\n/
24
+
25
+ > [!WARNING]
26
+ > A small number of Rubocop's auto-corrections are considered "unsafe" and may
27
+ > occasionally produce incorrect results. After running `fix`, you should
28
+ > review the changes and make sure the code still works as intended.
29
+ MARKDOWN
30
+
31
+ say_git "Auto-correct any existing issues"
32
+ run "bin/rubocop -A --fail-level F", capture: true