nextgen 0.1.0
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 +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
|