nextgen 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +143 -0
- data/config/generators.yml +146 -0
- data/exe/nextgen +13 -0
- data/lib/nextgen/actions/bundler.rb +62 -0
- data/lib/nextgen/actions/git.rb +56 -0
- data/lib/nextgen/actions/yarn.rb +32 -0
- data/lib/nextgen/actions.rb +155 -0
- data/lib/nextgen/cli.rb +19 -0
- data/lib/nextgen/commands/create.rb +312 -0
- data/lib/nextgen/ext/prompt/list.rb +13 -0
- data/lib/nextgen/ext/prompt/multilist.rb +16 -0
- data/lib/nextgen/generators/action_mailer.rb +26 -0
- data/lib/nextgen/generators/annotate.rb +2 -0
- data/lib/nextgen/generators/base.rb +60 -0
- data/lib/nextgen/generators/basic_auth.rb +5 -0
- data/lib/nextgen/generators/brakeman.rb +2 -0
- data/lib/nextgen/generators/bundler_audit.rb +2 -0
- data/lib/nextgen/generators/capybara_lockstep.rb +4 -0
- data/lib/nextgen/generators/clean_gemfile.rb +1 -0
- data/lib/nextgen/generators/dotenv.rb +3 -0
- data/lib/nextgen/generators/erb_lint.rb +11 -0
- data/lib/nextgen/generators/eslint.rb +24 -0
- data/lib/nextgen/generators/factory_bot_rails.rb +16 -0
- data/lib/nextgen/generators/git_safe.rb +4 -0
- data/lib/nextgen/generators/github_actions.rb +2 -0
- data/lib/nextgen/generators/good_migrations.rb +1 -0
- data/lib/nextgen/generators/home_controller.rb +3 -0
- data/lib/nextgen/generators/initial_git_commit.rb +5 -0
- data/lib/nextgen/generators/initial_migrations.rb +11 -0
- data/lib/nextgen/generators/letter_opener.rb +8 -0
- data/lib/nextgen/generators/node.rb +5 -0
- data/lib/nextgen/generators/open_browser_on_start.rb +12 -0
- data/lib/nextgen/generators/overcommit.rb +1 -0
- data/lib/nextgen/generators/pgcli_rails.rb +1 -0
- data/lib/nextgen/generators/rack_canonical_host.rb +8 -0
- data/lib/nextgen/generators/rack_mini_profiler.rb +2 -0
- data/lib/nextgen/generators/rspec_rails.rb +19 -0
- data/lib/nextgen/generators/rspec_system_testing.rb +5 -0
- data/lib/nextgen/generators/rubocop.rb +32 -0
- data/lib/nextgen/generators/shoulda.rb +6 -0
- data/lib/nextgen/generators/sidekiq.rb +30 -0
- data/lib/nextgen/generators/stylelint.rb +24 -0
- data/lib/nextgen/generators/thor.rb +2 -0
- data/lib/nextgen/generators/tomo.rb +10 -0
- data/lib/nextgen/generators/vite.rb +103 -0
- data/lib/nextgen/generators.rb +87 -0
- data/lib/nextgen/rails.rb +39 -0
- data/lib/nextgen/rails_options.rb +191 -0
- data/lib/nextgen/thor_extensions.rb +48 -0
- data/lib/nextgen/tidy_gemfile.rb +71 -0
- data/lib/nextgen/version.rb +3 -0
- data/lib/nextgen.rb +17 -0
- data/template/.editorconfig +14 -0
- data/template/.env.sample +2 -0
- data/template/.erb-lint.yml.tt +25 -0
- data/template/.eslintrc.cjs +26 -0
- data/template/.github/workflows/ci.yml.tt +142 -0
- data/template/.overcommit.yml.tt +86 -0
- data/template/.prettierrc.cjs +6 -0
- data/template/.rubocop.yml.tt +269 -0
- data/template/.stylelintrc.cjs +52 -0
- data/template/DEPLOYMENT.md +10 -0
- data/template/Procfile.tt +4 -0
- data/template/README.md.tt +52 -0
- data/template/Thorfile +7 -0
- data/template/app/controllers/concerns/basic_auth.rb +20 -0
- data/template/app/controllers/home_controller.rb +4 -0
- data/template/app/frontend/controllers/index.js +5 -0
- data/template/app/frontend/images/example.svg +3 -0
- data/template/app/frontend/stylesheets/base.css +8 -0
- data/template/app/frontend/stylesheets/index.css +3 -0
- data/template/app/frontend/stylesheets/reset.css +36 -0
- data/template/app/helpers/inline_svg_helper.rb +9 -0
- data/template/app/views/home/index.html.erb.tt +5 -0
- data/template/bin/setup +107 -0
- data/template/config/initializers/generators.rb +5 -0
- data/template/config/initializers/rack_mini_profiler.rb +4 -0
- data/template/config/initializers/sidekiq.rb +32 -0
- data/template/config/sidekiq.yml +5 -0
- data/template/lib/puma/plugin/open.rb +14 -0
- data/template/lib/tasks/auto_annotate_models.rake +52 -0
- data/template/lib/tasks/erblint.rake +11 -0
- data/template/lib/tasks/eslint.rake +11 -0
- data/template/lib/tasks/rubocop.rake +4 -0
- data/template/lib/tasks/stylelint.rake +11 -0
- data/template/lib/templates/rspec/system/system_spec.rb +10 -0
- data/template/lib/vite_inline_svg_file_loader.rb +25 -0
- data/template/package.json +6 -0
- data/template/postcss.config.js +3 -0
- data/template/spec/support/factory_bot.rb +3 -0
- data/template/spec/support/mailer.rb +5 -0
- data/template/spec/support/shoulda.rb +6 -0
- data/template/spec/support/system.rb +17 -0
- data/template/test/application_system_test_case.rb +18 -0
- data/template/test/helpers/inline_svg_helper_test.rb +23 -0
- data/template/test/support/factory_bot.rb +3 -0
- data/template/test/support/mailer.rb +3 -0
- data/template/test/support/shoulda.rb +6 -0
- data/template/test/support/vite.rb +5 -0
- 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,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 @@
|
|
1
|
+
Nextgen::TidyGemfile.clean!
|
@@ -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 @@
|
|
1
|
+
install_gem "good_migrations"
|
@@ -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,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,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
|